/*******************************************************************************
 * photons.h
 *
 * This module contains all defines, typedefs, and prototypes for photons.cpp.
 *
 * Author: Nathan Kopp
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/backend/lighting/photons.h $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#ifndef PHOTONS_H
#define PHOTONS_H

#include "base/povms.h"
#include "backend/frame.h"
#include "backend/render/trace.h"
#include "backend/control/messagefactory.h"
#include "backend/colour/colutils.h"
#include "backend/interior/media.h"

namespace pov
{

using namespace pov_base;

#define MEDIA_INTERACTION 1

/* ------------------------------------------------------ */
class ScenePhotonSettings
{
	public:
		ScenePhotonSettings()
		{
			photonsEnabled = false;

			surfaceSeparation = 0.1;
			globalSeparation = 0.1;
			surfaceCount = 0;

			expandTolerance = 0;
			minExpandCount = 0;
			minGatherCount = 10;
			maxGatherCount = 100;

			// trace settings for the photon pre-trace
			Max_Trace_Level = 10;
			adcBailout = 0.001;
			jitter = 0.25;

			// see the POV documentation for info about autostop
			autoStopPercent = 1.0;  // disabled by default

			// these are used for saving or loading the photon map
			// note: save and load are mutually exclusive - there are three states: save, load, neither
			fileName = NULL;
			saveFile = false;
			loadFile = false;

			#ifdef GLOBAL_PHOTONS
			// ---------- global photon map ----------
			int globalCount = 0;  // disabled by default
			#endif

			// settings for media photons
			mediaSpacingFactor = 0;
			maxMediaSteps = 0;  // disabled by default

			// refleciton blur settings
			// to be used with the reflection blur patch
			//photonReflectionBlur = false;
		}
		~ScenePhotonSettings()
		{
			if ( fileName )
			{
				POV_FREE(fileName);
				fileName=NULL;
			}
		}



		bool photonsEnabled;

		// photon separation (a.k.a. spacing)
		DBL surfaceSeparation;
		DBL globalSeparation;
		// ...or photon count
		int surfaceCount;

		// settings for the adaptive search
		// see POV documentation for an explanation
		DBL expandTolerance;
		int minExpandCount;
		int minGatherCount;
		int maxGatherCount;

		// trace settings for the photon pre-trace
		int Max_Trace_Level;
		DBL adcBailout;
		DBL jitter;

		// see the POV documentation for info about autostop
		DBL autoStopPercent;

		// these are used for saving or loading the photon map
		// note: save and load are mutually exclusive - there are three states: save, load, neither
		char* fileName;
		bool saveFile;
		bool loadFile;

		#ifdef GLOBAL_PHOTONS
		// ---------- global photon map ----------
		int globalPhotonsToShoot;      // number of global photons to shoot
		DBL globalGatherRad;           // minimum gather radius
		int globalCount;
		#endif

		// settings for media photons
		DBL mediaSpacingFactor;
		int maxMediaSteps;

		// refleciton blur settings
		// to be used with the reflection blur patch
		//bool photonReflectionBlur;
};

/* ------------------------------------------------------ */
/* photon */
/* ------------------------------------------------------ */
struct Photon
{
	void init(unsigned char _info)
	{
		this->info = _info;
	}

	SNGL_VECT Loc;          /* location */
	SMALL_COLOUR colour;    /* color & intensity (flux) */
	unsigned char info;     /* info byte for kd-tree */
	signed char theta, phi; /* incoming direction */
};

typedef Photon* PhotonBlock;

/* ------------------------------------------------------ */
/* photon map */
/* ------------------------------------------------------ */

/* this is for photon block allocation and array mapping functions */
extern const int PHOTON_BLOCK_POWER;
extern const int PHOTON_BLOCK_SIZE;
extern const int PHOTON_BLOCK_MASK;
extern const int INITIAL_BASE_ARRAY_SIZE;

class PhotonMap
{
	public:
		Photon **head;
		int numBlocks;        /* number of blocks in base array */
		int numPhotons;       /* total number of photons used */

		DBL minGatherRad;       /* minimum gather radius */
		DBL minGatherRadMult;   /* minimum gather radius multiplier (for speed adjustments) */
		DBL gatherRadStep;      /* step size for gather expansion */
		int gatherNumSteps;     /* maximum times to perform 'gather' */

		PhotonMap();
		~PhotonMap();

		void swapPhotons(int a, int b);
		void insertSort(int start, int end, int d);
		void quickSortRec(int left, int right, int d);
		void halfSortRec(int left, int right, int d, int mid);
		void sortAndSubdivide(int start, int end, int /*sorted*/);
		void buildTree();

		void setGatherOptions(ScenePhotonSettings& photonSettings, int mediaMap);

		Photon* AllocatePhoton();
		//mutex allocatePhotonMutex;

		void mergeMap(PhotonMap* map);
};


/* ------------------------------------------------------ */
/*
  Photon array mapping function

  This converts a one-dimensional index into a two-dimensional address
  in the photon array.

  Photon memory looks like this:

    # -> **********
    # -> **********  <- blocks of photons
    # -> **********
    # -> /
    # -> /
    :
    ^
    |
    Base array.

  The base array (which is dynamically resized as needed) contians pointers
  to blocks of photons.  Blocks of photons (of fixed size of a power of two)
  are allocated as needed.

  This mapping function converts a one-dimensional index and into a two-
  dimensional address consisting of a block index and an offset within
  that block.

  Note that, while this data structure allows easy allocation of photons,
  it does not easily permit deallocation of photons.  Once photons are placed
  into the photon map, they are not removed.
*/
// if this is changed, you must also change swapPhotons() and
// allocatePhoton, both in photons.c
#define PHOTON_AMF(map, idx)   map[(idx)>>PHOTON_BLOCK_POWER][(idx) & PHOTON_BLOCK_MASK]


/* ------------------------------------------------------ */
/* photon gatherer */
/* ------------------------------------------------------ */
class GatheredPhotons
{
	public:
		// priority queue arrays
		Photon** photonGatherList;  // photons
		DBL *photonDistances;       // priorities
		int numFound;  // number of photons found

		void swapWith(GatheredPhotons& theOther);
	
		GatheredPhotons(int maxGatherCount);
		~GatheredPhotons();
};

class PhotonGatherer
{
	public:
		ScenePhotonSettings& photonSettings;
		PhotonMap *map;

		DBL size_sq_s;   // search radius squared
		DBL Size_s;      // search radius (static)
		DBL sqrt_dmax_s, dmax_s;      // dynamic search radius... current maximum
		int TargetNum_s; // how many to gather
		const DBL *pt_s;       // point around which we are gathering
		const DBL *norm_s;     // surface normal
		DBL flattenFactor; // amount to flatten the spher to make it
		                   // an ellipsoid when gathering photons
		                   // zero = no flatten, one = regular
		bool gathered;
		DBL alreadyGatheredRadius;

		GatheredPhotons gatheredPhotons;

		PhotonGatherer(PhotonMap *map, ScenePhotonSettings& photonSettings);

		void gatherPhotonsRec(int start, int end);
		int gatherPhotons(const VECTOR pt, DBL Size, DBL *r, const VECTOR norm, bool flatten);
		DBL gatherPhotonsAdaptive(const VECTOR pt, const VECTOR norm, bool flatten);

		void PQInsert(Photon *photon, DBL d);
		void FullPQInsert(Photon *photon, DBL d);
};

class PhotonMediaFunction : public MediaFunction
{
	public:
		PhotonMediaFunction(shared_ptr<SceneData> sd, TraceThreadData *td, Trace *t, PhotonGatherer *pg);

		void ComputeMediaAndDepositPhotons(MediaVector& medias, const Ray& ray, const Intersection& isect, Colour& colour, Trace::TraceTicket& ticket);
	protected:
		void DepositMediaPhotons(Colour& colour, MediaVector& medias, LightSourceEntryVector& lights, MediaIntervalVector& mediaintervals,
		                         const Ray& ray, int minsamples, bool ignore_photons, bool use_scattering, bool all_constant_and_light_ray, Trace::TraceTicket& ticket);
	private:
		shared_ptr<SceneData> sceneData;

		void addMediaPhoton(const VECTOR Point, const VECTOR Origin, const RGBColour& LightCol, DBL depthDiff);
};

class PhotonTrace : public Trace
{
	public:
		PhotonTrace(shared_ptr<SceneData> sd, TraceThreadData *td, unsigned int mtl, DBL adcb, unsigned int qf, Trace::CooperateFunctor& cf);
		~PhotonTrace();

		virtual DBL TraceRay(const Ray& ray, Colour& colour, COLC weight, Trace::TraceTicket& ticket, bool continuedRay, DBL maxDepth = 0.0);
	protected:
		virtual void ComputeLightedTexture(Colour& LightCol, const TEXTURE *Texture, vector<const TEXTURE *>& warps, const Vector3d& ipoint, const Vector3d& rawnormal, const Ray& ray, COLC weight, Intersection& isect, Trace::TraceTicket& ticket);
		bool ComputeRefractionForPhotons(const FINISH* finish, Interior *interior, const Vector3d& ipoint, const Ray& ray, const Vector3d& normal, const Vector3d& rawnormal, Colour& colour, COLC weight, Trace::TraceTicket& ticket);
		bool TraceRefractionRayForPhotons(const FINISH* finish, const Vector3d& ipoint, const Ray& ray, Ray& nray, DBL ior, DBL n, const Vector3d& normal, const Vector3d& rawnormal, const Vector3d& localnormal, Colour& colour, COLC weight, Trace::TraceTicket& ticket);
	private:
		PhotonMediaFunction mediaPhotons;
		RadiosityFunctor noRadiosity;

		void addSurfacePhoton(const VECTOR Point, const VECTOR Origin, const RGBColour& LightCol);
};

// foward declaration
class PhotonShootingStrategy;
class LightTargetCombo;

/* ------------------------------------------------------ */
/* photon map builder */
/* ------------------------------------------------------ */

class ShootingDirection
{
	public:
		ShootingDirection(LightSource* light, ObjectPtr target):light(light),target(target) {}

		LightSource* light;
		ObjectPtr target;
		VECTOR up, left, ctr, toctr, v; /* vectors to determine direction of shot */
		DBL dist;                      /* distance from light to center of bounding sphere */
		DBL rad;                       /* radius of bounding sphere */

		void compute();
		void recomputeForAreaLight(Ray& ray, int area_x, int area_y);
	};


class LightTargetCombo
{
	public:
		LightTargetCombo(LightSource *light, ObjectPtr target):light(light),target(target),shootingDirection(light,target) {}
		LightSource *light;
		ObjectPtr target;
		int estimate;
		DBL mintheta;
		DBL maxtheta;
		DBL dtheta;
		DBL photonSpread;
		ShootingDirection shootingDirection;

		int computeMergedFlags();
		void computeAnglesAndDeltas(shared_ptr<SceneData> sceneData);
};


class PhotonShootingUnit
{
	public:
		PhotonShootingUnit(LightSource* light, ObjectPtr target):lightAndObject(light,target) {}
		LightTargetCombo lightAndObject;
};




class SinCosOptimizations
{
	public:
		// speed optimization data - sin/cos stored in two arrays
		// these are only created if photon mapping is used
		// TODO move these to their own class
		// these are thread safe - used many times but not modified after initialization
		DBL *cosTheta;
		DBL *sinTheta;
		SinCosOptimizations();
		~SinCosOptimizations();
};

extern SinCosOptimizations sinCosData;

/* ------------------------------------------------------ */
/* global functions */
/* for documentation of these functions, see photons.c */
/* ------------------------------------------------------ */
void ChooseRay(Ray &NewRay, const VECTOR Normal, const Ray &Ray, const VECTOR Raw_Normal, int WhichRay);
int GetPhotonStat(POVMSType a);

}

#endif
