package lpv.simulation.propagator;

import javax.vecmath.Point3i;
import javax.vecmath.Vector4f;

import lpv.LPV;

public class RafalWrobelPropagator implements Propagator {
	// Original author: Rafal Wrobel
	// /home/dt07jo1/Documents/lpv2/lpv/shader/propagate.fp
	// http://www.rafwrobel.com/lpv/lpv.html
	
	
	
	
	// calculate incident flux for a face of the destination cell
	private Vector4f create_vpl(Vector4f in, 
				    Vector4f central_direction_sh_basis,
				    Vector4f vpl,
				    float solid_angle) {
		
		// calculate avarage light intensity
		float intensity = Math.max(0.0f, central_direction_sh_basis.dot(in));
		
		// multiply by solid angle to calculate the flux
		float flux = intensity * solid_angle;
		
		// create virtual point light pointing towards the face
		Vector4f contribution = new Vector4f(vpl);
		contribution.scale(flux);
		return contribution;
	}

	// read sh vectors storing the light intensity
	private Vector4f get_vpls(LPV lpv, Point3i source_cell) {
		if(source_cell.x >= 0 && source_cell.x < lpv.getWidth() && 
				source_cell.y >= 0 && source_cell.y < lpv.getHeight() && 
				source_cell.z >= 0 && source_cell.z < lpv.getDepth()) {
			Vector4f out = new Vector4f(lpv.get(source_cell.x, source_cell.y, source_cell.z));
			return out;
		} else {
			return new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
		}
	}

	// propagate light from neighouring cell to the 5 faces of this cell
	private Vector4f propagate(Vector4f in, // vpl of neighbour

				   Vector4f central_direction_sh_basis1,
				   Vector4f vpl1,
				   float solid_angle1,

				   Vector4f central_direction_sh_basis2,
				   Vector4f vpl2,
				   float solid_angle2,

				   Vector4f central_direction_sh_basis3,
				   Vector4f vpl3,
				   float solid_angle3,

				   Vector4f central_direction_sh_basis4,
				   Vector4f vpl4,
				   float solid_angle4,

				   Vector4f central_direction_sh_basis5,
				   Vector4f vpl5,
				   float solid_angle5){ 
		
		Vector4f vpl = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
		vpl.add(create_vpl(in, central_direction_sh_basis1, vpl1, solid_angle1));
		vpl.add(create_vpl(in, central_direction_sh_basis2, vpl2, solid_angle2));
		vpl.add(create_vpl(in, central_direction_sh_basis3, vpl3, solid_angle3));
		vpl.add(create_vpl(in, central_direction_sh_basis4, vpl4, solid_angle4));
		vpl.add(create_vpl(in, central_direction_sh_basis5, vpl5, solid_angle5));
		
		return vpl;
	}
	
	private float step(float edge, float x) {
		return x < edge ? 0.0f : 1.0f;
	}
	
	
	@Override
	public void propagate(LPV input, LPV output, LPV acc, LPV geometry, Point3i pos, int iteration) {
		Vector4f total_vpl = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
		Vector4f vpl = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);

		Point3i sample_pos_neg = new Point3i(pos.x - 1, pos.y - 1, pos.z - 1);
		Point3i sample_pos_pos = new Point3i(pos.x + 1, pos.y + 1, pos.z + 1);
		
		Point3i grid_sample_pos;
		Vector4f in;
		float s;
		
		// get light intensity sh vectors from the cell left to this cell
		grid_sample_pos = new Point3i(sample_pos_neg.x, pos.y, pos.z);
		in = get_vpls(input, grid_sample_pos);

		Vector4f sh_basic_central_dir1 = new Vector4f(0.28209481f, 0.0f, 0.25687355f, -0.41563013f);
		Vector4f vpl1 = new Vector4f(0.88622689f, 0.0f, 1.0233266f, 0.0f);
		float solid_angle1 = 0.42343098f;

		Vector4f sh_basic_central_dir2 = new Vector4f(0.28209481f, 0.0f, -0.25687355f, -0.41563013f);
		Vector4f vpl2 = new Vector4f(0.88622689f, 0.0f, -1.0233266f, 0.0f);
		float solid_angle2 = solid_angle1;

		Vector4f sh_basic_central_dir3 = new Vector4f(0.28209481f, 0.0f, 0.0f, -0.48860252f);
		Vector4f vpl3 = new Vector4f(0.88622689f, 0.0f, 0.0f, -1.0233266f);
		float solid_angle3 = 0.40067077f;

		Vector4f sh_basic_central_dir4 = new Vector4f(0.28209481f, 0.25687355f, 0.0f, -0.41563013f);
		Vector4f vpl4 = new Vector4f(0.88622689f, 1.0233266f, 0.0f, 0.0f);
		float solid_angle4 = solid_angle1;

		Vector4f sh_basic_central_dir5 = new Vector4f(0.28209481f, -0.25687355f, 0.0f, -0.41563013f);
		Vector4f vpl5 = new Vector4f(0.88622689f, -1.0233266f, 0.0f, 0.0f);
		float	solid_angle5 = solid_angle1;

		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in, 
				  sh_basic_central_dir1,
				  vpl1,
				  solid_angle1,
				  sh_basic_central_dir2,
				  vpl2,
				  solid_angle2,
				  sh_basic_central_dir3,
				  vpl3,
				  solid_angle3,
				  sh_basic_central_dir4,
				  vpl4,
				  solid_angle4,
				  sh_basic_central_dir5,
				  vpl5,
				  solid_angle5);

		
		s = step(0.0f, sample_pos_neg.x);
		total_vpl = new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s);
		
		
		
		// get light intensity sh vectors from the cell right to this cell
		grid_sample_pos = new Point3i(sample_pos_pos.x, pos.y, pos.z);
		in = get_vpls(input, grid_sample_pos);

		Vector4f sh_basic_central_dir6 = new Vector4f(0.28209481f, 0.0f, 0.25687355f, 0.41563013f);
		Vector4f vpl6 = vpl1;
		float	solid_angle6 = solid_angle1;

		Vector4f sh_basic_central_dir7 = new Vector4f(0.28209481f, 0.0f, -0.25687355f, 0.41563013f);
		Vector4f vpl7 = vpl2;
		float solid_angle7 = solid_angle1;

		Vector4f sh_basic_central_dir8 = new Vector4f(0.28209481f, 0.0f, 0.0f, 0.48860252f);
		Vector4f vpl8 = new Vector4f(0.88622689f, 0.0f, 0.0f, 1.0233266f);
		float solid_angle8 = solid_angle3;

		Vector4f sh_basic_central_dir9 = new Vector4f(0.28209481f, 0.25687355f, 0.0f, 0.41563013f);
		Vector4f vpl9 = vpl4;
		float solid_angle9 = solid_angle1;

		Vector4f sh_basic_central_dir10 = new Vector4f(0.28209481f, -0.25687355f, 0.0f, 0.41563013f);
		Vector4f vpl10 = vpl5;
		float	solid_angle10 =	solid_angle1;

		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in,
				  sh_basic_central_dir6,
				  vpl6,
				  solid_angle6,
				  sh_basic_central_dir7,
				  vpl7,
				  solid_angle7,
				  sh_basic_central_dir8,
				  vpl8,
				  solid_angle8,
				  sh_basic_central_dir9,
				  vpl9,
				  solid_angle9,
				  sh_basic_central_dir10,
				  vpl10,
				  solid_angle10);

		s = step(sample_pos_pos.x, 1.0f);
		total_vpl.add(new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s));

		
		
		
		// get light intensity sh vectors from the cell above this cell
		grid_sample_pos = new Point3i(pos.x, sample_pos_pos.y, pos.z);
		in = get_vpls(input, grid_sample_pos);

		Vector4f sh_basic_central_dir16 = new Vector4f(0.28209481f, 0.0f, -0.48860252f, 0.0f);
		Vector4f vpl16 = vpl2;
		float solid_angle16 =	solid_angle3;

		Vector4f sh_basic_central_dir17 = new Vector4f(0.28209481f, 0.0f, -0.41563013f, -0.25687355f);
		Vector4f vpl17 = vpl3;
		float	solid_angle17 = solid_angle1;

		Vector4f sh_basic_central_dir18 = new Vector4f(0.28209481f, 0.0f, -0.41563013f, 0.25687355f);
		Vector4f vpl18 = vpl8;
		float	solid_angle18 = solid_angle1;

		Vector4f sh_basic_central_dir19 = new Vector4f(0.28209481f, 0.25687355f, -0.41563013f, 0.0f);
		Vector4f vpl19 = vpl4;
		float	solid_angle19 =	solid_angle1;

		Vector4f sh_basic_central_dir20 = new Vector4f(0.28209481f, -0.25687355f, -0.41563013f, 0.0f);
		Vector4f vpl20 = vpl5;
		float	solid_angle20 = solid_angle1;

		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in, 
				  sh_basic_central_dir16,
				  vpl16,
				  solid_angle16,
				  sh_basic_central_dir17,
				  vpl17,
				  solid_angle17,
				  sh_basic_central_dir18,
				  vpl18,
				  solid_angle18,
				  sh_basic_central_dir19,
				  vpl19,
				  solid_angle19,
				  sh_basic_central_dir20,
				  vpl20,
				  solid_angle20);

		s = step(sample_pos_pos.y, 1.0f);
		total_vpl.add(new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s));

		// get light intensity sh vectors from the cell below this cell
		grid_sample_pos = new Point3i(pos.x, sample_pos_neg.y, pos.z);
		in = get_vpls(input, grid_sample_pos);

		Vector4f sh_basic_central_dir11 = new Vector4f(0.28209481f, 0.0f, 0.48860252f, 0.0f);
		Vector4f vpl11 = vpl1;
		float solid_angle11 = solid_angle3;

		Vector4f sh_basic_central_dir12 = new Vector4f(0.28209481f, 0.0f, 0.41563013f, -0.25687355f);
		Vector4f vpl12 = vpl3;
		float solid_angle12 =	solid_angle1;

		Vector4f sh_basic_central_dir13 = new Vector4f(0.28209481f, 0.0f, 0.41563013f, 0.25687355f);
		Vector4f vpl13 = vpl8;
		float solid_angle13 =	solid_angle1;

		Vector4f sh_basic_central_dir14 = new Vector4f(0.28209481f, 0.25687355f, 0.41563013f, 0.0f);
		Vector4f vpl14 = vpl4;
		float solid_angle14 = solid_angle1;

		Vector4f sh_basic_central_dir15 = new Vector4f(0.28209481f, -0.25687355f, 0.41563013f, 0.0f);
		Vector4f vpl15 = vpl5;
		float solid_angle15 = solid_angle1;

		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in, 
				  sh_basic_central_dir11,
				  vpl11,
				  solid_angle11,
				  sh_basic_central_dir12,
				  vpl12,
				  solid_angle12,
				  sh_basic_central_dir13,
				  vpl13,
				  solid_angle13,
				  sh_basic_central_dir14,
				  vpl14,
				  solid_angle14,
				  sh_basic_central_dir15,
				  vpl15,
				  solid_angle15);

		s = step(0.0f, sample_pos_neg.y);
		total_vpl.add(new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s));
		
		// get light intensity sh vectors from the cell behind this cell
		grid_sample_pos = new Point3i(pos.x, pos.y, sample_pos_neg.z);
		in = get_vpls(input, grid_sample_pos);


		Vector4f sh_basic_central_dir21 = new Vector4f(0.28209481f, 0.48860252f, 0.0f, 0.0f);
		Vector4f vpl21 = vpl4;
		float solid_angle21 =	solid_angle3;

		Vector4f sh_basic_central_dir22 = new Vector4f(0.28209481f, 0.41563013f, 0.25687355f, 0.0f);
		Vector4f vpl22 = vpl1;
		float solid_angle22 = solid_angle1;

		Vector4f sh_basic_central_dir23 = new Vector4f(0.28209481f, 0.41563013f, -0.25687355f, 0.0f);
		Vector4f vpl23 = vpl2;
		float	solid_angle23 = solid_angle1;

		Vector4f sh_basic_central_dir24 = new Vector4f(0.28209481f, 0.41563013f, 0.0f, 0.25687355f);
		Vector4f vpl24 = vpl8;
		float	solid_angle24 = solid_angle1;

		Vector4f sh_basic_central_dir25 = new Vector4f(0.28209481f, 0.41563013f, 0.0f, -0.25687355f);
		Vector4f vpl25 = vpl3;
		float solid_angle25 = solid_angle1;

		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in, 
				  sh_basic_central_dir21,
				  vpl21,
				  solid_angle21,
				  sh_basic_central_dir22,
				  vpl22,
				  solid_angle22,
				  sh_basic_central_dir23,
				  vpl23,
				  solid_angle23,
				  sh_basic_central_dir24,
				  vpl24,
				  solid_angle24,
				  sh_basic_central_dir25,
				  vpl25,
				  solid_angle25);


		s = step(0.0f, sample_pos_neg.z);
		total_vpl.add(new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s));

		// get light intensity sh vectors from the cell in front of this cell
		grid_sample_pos = new Point3i(pos.x, pos.y, sample_pos_pos.z);
		in = get_vpls(input, grid_sample_pos);

		Vector4f sh_basic_central_dir26 = new Vector4f(0.28209481f, -0.48860252f, 0.0f, 0.0f);
		Vector4f vpl26 = vpl5;
		float	solid_angle26 = solid_angle3;

		Vector4f sh_basic_central_dir27 = new Vector4f(0.28209481f, -0.41563013f, 0.25687355f, 0.0f);
		Vector4f vpl27 = vpl1;
		float	solid_angle27 = solid_angle1;

		Vector4f sh_basic_central_dir28 = new Vector4f(0.28209481f, -0.41563013f, -0.25687355f, 0.0f);
		Vector4f vpl28 = new Vector4f(0.88622689f, 0.0f, -1.0233266f, 0.0f);
		float	solid_angle28 = solid_angle1;

		Vector4f sh_basic_central_dir29 = new Vector4f(0.28209481f, -0.41563013f, 0.0f, 0.25687355f);
		Vector4f vpl29 = vpl8;
		float	solid_angle29 = solid_angle1;

		Vector4f sh_basic_central_dir30 = new Vector4f(0.28209481f, -0.41563013f, 0.0f, -0.25687355f);
		Vector4f vpl30 = vpl3;
		float	solid_angle30 = solid_angle1;
			
		// propagate light from adjacent cell to the five faces of this cell
		vpl = propagate(in, 
				  sh_basic_central_dir26,
				  vpl26,
				  solid_angle26,
				  sh_basic_central_dir27,
				  vpl27,
				  solid_angle27,
				  sh_basic_central_dir28,
				  vpl28,
				  solid_angle28,
				  sh_basic_central_dir29,
				  vpl29,
				  solid_angle29,
				  sh_basic_central_dir30,
				  vpl30,
				  solid_angle30);


		s = step(sample_pos_pos.z, 1.0f);
		total_vpl.add(new Vector4f(vpl.x * s, vpl.y * s, vpl.z * s, vpl.w * s));

		// normalize
		float pi = 3.141592f;		
		total_vpl.scale(1.0f/pi);
		
		output.get(pos.x, pos.y, pos.z).set(total_vpl);
		acc.get(pos.x, pos.y, pos.z).add(total_vpl);
	}
	
}
