import de.grogra.imp3d.*;
import de.grogra.ray2.tracing.*;
import de.grogra.rgg.*;
import de.grogra.imp.*;
import de.grogra.math.*;
import de.grogra.vecmath.*;
import de.grogra.vecmath.geom.*;
import de.grogra.imp3d.ray2.*;
import de.grogra.pf.registry.expr.*;
import de.grogra.pf.registry.Item;
import de.grogra.util.*;
import de.grogra.pf.ui.util.*;
import java.awt.image.*;
import java.awt.*;
import java.io.*;
import javax.vecmath.*;
import de.grogra.imp3d.objects.*;
//import de.grogra.vecmath.VecmathOperators;

public class BillboardCake extends Billboarder {
		
	private LockProtectedCommand lpc;
	
	public BillboardCake(RotationCamera camera, String mode)
	{		
		super.camera 				= camera;	
		super.view3d				= camera.getView3D();
		super.mode					= mode;
	}
	
	public void initialize(double distance, double zOfsset, int sides)
	{
		super.initialize(distance, zOfsset, sides);
		cake();
	}
	
	protected void setRunLater()
	{
		LockProtectedCommand lpc = new LockProtectedCommand(view3d.getGraph(),true,0)
		{
			protected void runImpl(Object arg, de.grogra.pf.ui.Context c)
			{
				// This is the main Thread
				//println("Fertig");
									
				// Count sides which allready rendered
				currentSide++;
									
				// Only invoke new render-thread, when not all sides are rendered
				if(currentSide < sides)
				{
					// Rotate the camera to the next viewpoint
					camera.moveArroundZ(RotationAngle);
											
					cake();
					// Invoke next render-thread
					billboarding();
				} else {									
					writeVRML()	;
				}
				// Writes the VRML-file
				//prepareVRML(targetFileTmpl + currentSide + fileExt);
			}
		};
		this.lpc = lpc;
	}

	public void cake()
	{
		cake(currentSide);
	}
	
	public synchronized void cake(int side)
	{
		double angleOffset 	= RotationAngle < 0 ? -RotationAngle	: RotationAngle;
		
		angleOffset 		= angleOffset < 180 ? angleOffset+=180 	: angleOffset;
		
		final HalfSpace hs1 = new HalfSpace();
		final HalfSpace hs2 = new HalfSpace();
		final BoundingBox b	= new BoundingBox(new Point3d(1,1,0), new Point3d(-1,-1,30));
		
		double radient1 	= Math.toRadians((float) (RotationAngle * side));
		double radient2 	= Math.toRadians((float) ((RotationAngle * side) - angleOffset));
		
		Point3d origin1 	= new Point3d(0.0,0,0.0);
		Point3d origin2 	= new Point3d(0.0,0,0.0);
		TVector3d axis1 	= new TVector3d(Math.sin(radient1),Math.cos(radient1),0);
		TVector3d axis2		= new TVector3d(Math.sin(radient2),Math.cos(radient2),0);
		
		hs1.setTransformation(origin1, axis1);
		hs2.setTransformation(origin2, axis2);
		
		synthesize((* ^ -minDescendants-> Null *),
		
			Null n => Null*
			(* n -minDescendants-> Null *),
		
			BooleanSynth syn => boolean
			(boolean v = (((Volume) hs1).contains(location(((Null) syn.object)), true)  &&
						  ((Volume) hs2).contains(location(((Null) syn.object)), true)) ||
						 (!((Volume) hs1).contains(location(((Null) syn.object)), true)  &&
						  !((Volume) hs2).contains(location(((Null) syn.object)), true)) ||
						  (((Volume) b).contains(location(((Null) syn.object)), true)),
				((Null) syn.object)[layer] = v ? 4 : 15,
			 v)
		);
		apply();
	}
	
	public void griding(int stepCount)
	{
		// Rueckwaerts gehen Halfspace hat alle Objekte hinter dem Ursprung
		
		// Das ganze wird von 2 oder 4 Seiten gemacht.
		
		//int stepCount 				==> wie viele Schritte pro Haelfte
		//float stepWidth pro Schritt	==> wie weit wird HS verschoben pro Schritt (in Richtung Kamera)
		//boolean sliceOnly 			==> sollen nur scheiben angezeigt werden oder komplett...
	/*	HalfSpace hs		= new HalfSpace();
		
		Point3d origin1 	= new Point3d(0.0,-(stepCount * stepWidth),0.0);
		TVector3d axis1 	= new TVector3d(0,-1,0);*/
	}

	
	public void writeVRML()
	{
		//if(mode == "dynamic")
		
		String wrlTempl = "";
		
		if(mode == "dynamic")
			wrlTempl = "billboard_dynamisch";
		else if (mode ==  "static")
			wrlTempl = "billboard_statisch";
		
		Object f = Library.workbench().getRegistry ().getProjectFile ("pfs:" + wrlTempl +".wrl");
	
		String str = f.toString();
		
		int i = 0;
		float angle = 0;
		
		while(i<sides)
		{
			if(mode == "dynamic")
				this.vrmlBB += "PngFrame { url \"bill_" + i + ".png\" }\n\t\t";
			else if (mode ==  "static")
				this.vrmlBB += "PngFrame { url \"bill_" + i + ".png\" rotate 0 1 0 " + ( (i/(double) sides) * (Math.PI*2) )  + "}\n\t\t";
			
			i++;
		}
			
		str	=	str.replaceAll("::REL_HEIGHT::", (imgHeight/imgWidth));
		str	=	str.replaceAll("::PNG_PROTO_CALL::", vrmlBB);
		str	=	str.replaceAll("::BILL_NUM::", sides);
		
		FileWriter outputStream 	= new FileWriter("c:/billboard-test/bb.wrl");
		BufferedWriter  outputObj	= new BufferedWriter(outputStream);

		try 
		{
			outputObj.write(str);
		} catch (ClassNotFoundException ex)
		{
			println("Fehler: " + ex.getMessage());
		}
	
		outputObj.flush();
		outputStream.close();
	}
}

public class RotationCamera extends Library implements java.io.Serializable {
	
	private Camera camera;
	private View3D view3d;
	private double RotationAngle	= 0;
	private Point3d CamPos			= new Point3d(0,0,8);
	private TVector3d rotationPoint	= new TVector3d(0,0,0);
	
	public RotationCamera()
	{		
		view3d 						= View3D.getDefaultView(workbench());
		camera 						= view3d.getCamera();
		
		init(view3d, camera, CamPos, 0);		
	}
	
	public RotationCamera(double angel)
	{
		view3d 						= View3D.getDefaultView(workbench());
		camera 						= view3d.getCamera();
		
		
		init(view3d, camera, CamPos, 0);
		//apply();
		
		
		this.view3d 				= view3d;
		this.camera 				= camera;
		
	//	this.moveArroundZ(angel);
	}
	
	public void setDim(double dist, double zOffset)
	{
		Matrix4d initMatrix4d 		= camera.getTransformation();
		
		initMatrix4d.m13 			= zOffset;
		initMatrix4d.m23 			= -dist;		
		
		camera.setTransformation(initMatrix4d);
		apply();
	}
	
	protected void init(View3D view3d, Camera camera, Point3d p, double angle)
	{
		this.view3d 				= view3d;
		this.camera 				= camera;
		
		Matrix4d initMatrix4d 		= new Matrix4d();
		initMatrix4d.m00 			= 1;
		initMatrix4d.m12 			= 1;
		initMatrix4d.m21 			= -1;
		initMatrix4d.m03 			= -p.x;
		initMatrix4d.m13 			= -p.y;
		initMatrix4d.m23 			= -p.z;
		initMatrix4d.m33			= 1;
		

		camera.setTransformation(initMatrix4d);
		
		//makeParallel();
	}
	
	public void moveArroundZ(double angleSpeed)
	{
		
		Quat4d q 					= new Quat4d();
		Matrix4d m 					= new Matrix4d();
		
		// Current Position of the camera
		TMatrix4d tm 				= camera.getTransformation();
	//	tm.m03-=2;
		// Which Axis has to rotate and speed
		q.set((new AxisAngle4d(0, 0, 1, Math.toRadians(angleSpeed))));
		RotationAngle += angleSpeed;
		
		// Set camera to Zero-Point
		//m.setTranslation(new Vector3d(0, -2, -8));
	
		// Just to make thing less complicated
		//rotationPoint.negate();
		
		// Sets the rotation-point
		m.set(q, rotationPoint, 1);
		
		// Combine the 2 matrix
		tm.mul(tm, m);	 
		
		// Set the camera the new Position
		camera.setTransformation(tm);	
		
		tm 				= camera.getTransformation();
	//	tm.m03+=2;
		//camera.setTransformation(tm);	
		
	}
	
	public void makeParallel()
	{
		this.camera.setProjection(new ParallelProjection());		
	}
	
	public void makePerspective()
	{
		this.camera.setProjection(new PerspectiveProjection());		
	}
	
	public View3D getView3D()
	{
		return this.view3d;
	}
	
	public void lookUpAndDown(double angle)
	{
		TMatrix4d tm 				= camera.getTransformation();
		Matrix4d m 					= new Matrix4d();
		
		m.rotX(Math.toRadians(angle));
		tm.mul(tm, m);	
		
		camera.setTransformation(tm);
	}
}