// lmProjectionPlane
// Author: Aloys Baillet for La Maison
// Date 09 february 2005



var NULL_PREFIX = "Null_";

/*****************************************************************************************
* 		lmProjectionPlane Plugin Definition
*****************************************************************************************/
function XSILoadPlugin( in_reg )
{
	in_reg.Author = "Aloys Baillet for La Maison";
	in_reg.Name = "lmProjectionPlane Plug-in";
	in_reg.Major = 1;
	in_reg.Minor = 0;

	in_reg.RegisterCommand("lmCreateProjectionPlane","lmCreateProjectionPlane");
	in_reg.RegisterMenu(siMenuTbGetCameraID,"lmCreateProjectionPlane_Menu",false,false);
	
	in_reg.RegisterProperty("lmProjectionParameters");

	return true;
}

function XSIUnloadPlugin( in_reg )
{
	strPluginName = in_reg.Name;
	return true;
}


/*****************************************************************************************
*	Add a Null on a projection plane and add the proxy parameters to the CPSet
*****************************************************************************************/
function addNull( oPlane, oProp, x, y, createProxy ){
	var pos = XSIMath.CreateVector3();
	pos.x = x;
	pos.y = y;
	pos.z = 0.0;
	var nbNulls = oProp.Parameters("NbNulls").Value + 1;
	var is = nbNulls.toString();
	if (is.length == 1)
		is = "0" + is;
	var name = NULL_PREFIX + is;
	
	var n = oPlane.AddNull(name);
	var transfo = XSIMath.CreateTransform();
	transfo.SetIdentity();
	n.Kinematics.Local.Transform = transfo;
	n.LocalTranslation = pos;
	
	if (createProxy){
		n.ActivePrimitive.Parameters("size").Value = .1;
		
		var param;
		param = oProp.AddProxyParameter( n.Kinematics.Local.Parameters("posx"), name + "_posx", name + "_posx");
		EditParameterDefinition( param, null, null, null, null, 0., 1., null, null);

		param = oProp.AddProxyParameter( n.Kinematics.Local.Parameters("posy"), name + "_posy", name + "_posy");
		EditParameterDefinition( param, null, null, null, null, 0., 1., null, null);
	}
	
	oProp.Parameters("NbNulls").Value = nbNulls;
	LogMessage( "Null added to " + oPlane.Name + ": " + n.Name);
	return n;
}

/*****************************************************************************************
* lmNullProjectOp Scripted Operator Update Code (applied to the local kinematics of the a null in the Projection Plane)
*****************************************************************************************/
function lmNullProjectOp_Update(In_UpdateContext, OutLocal, InLocal, InNullGlobal, InCamera, InCamGlobal, InParams){
	var distToPlane = InParams.Value.Parameters("ClippingDistance").Value;
	var clipTolerance = InParams.Value.Parameters("ClippingTolerance").Value;
	var aspect = InCamera.Value.Parameters("aspect").Value;
	var fov = InCamera.Value.Parameters("fov").Value / 2.;
	var fovtype = InCamera.Value.Parameters("fovtype").Value;
		
	var multx = 1.0;
	var multy = 1. / aspect;
	if (fovtype == 0){
		multx = aspect;
		multy = 1.0;
	}

	var vArray = new VBArray( InNullGlobal.Value.Transform.GetTranslationValues2() ).toArray();
	var nPos = XSIMath.CreateVector3();
	nPos.Set(vArray[0], vArray[1], vArray[2]);
	var nPosLoc = XSIMath.MapWorldPositionToObjectSpace(InCamGlobal.Value.Transform, nPos);

	var dist = Math.abs(nPosLoc.z);
	var scalex = 2. * Math.tan( Math.PI * fov / 180.0 ) * multx * dist;
	var scaley = 2. * Math.tan( Math.PI * fov / 180.0 ) * multy * dist;

	var x = nPosLoc.x / scalex + .5;
	var y = nPosLoc.y / scaley + .5;
	var z = 0;
	if (x > 1. + clipTolerance || x < -clipTolerance || y > 1. + clipTolerance || y < -clipTolerance || nPosLoc.z>0)
		z = distToPlane;
	OutLocal.Value.Parameters("posx").Value = x;
	OutLocal.Value.Parameters("posy").Value = y;
	OutLocal.Value.Parameters("posz").Value = z;
	OutLocal.Value.Parameters("rotx").Value = 0.;
	OutLocal.Value.Parameters("roty").Value = 0.;
	OutLocal.Value.Parameters("rotz").Value = 0.;
	OutLocal.Value.Parameters("sclx").Value = 1./dist;
	OutLocal.Value.Parameters("scly").Value = 1./dist;
	OutLocal.Value.Parameters("sclz").Value = 1./dist;
}

/*****************************************************************************************
* lmGeometryProjectOp Scripted Operator Update Code (applied to the local kinematics of the a null in the Projection Plane)
*****************************************************************************************/
function lmGeometryProjectOp_Update(In_UpdateContext, Out, InPrim, InCamera, InCamGlobal, InObjPrim, InObjGlobal, InParams){
	var distToPlane = InParams.Value.Parameters("ClippingDistance").Value;
	var clipTolerance = InParams.Value.Parameters("ClippingTolerance").Value;
	var aspect = InCamera.Value.Parameters("aspect").Value;
	var fov = InCamera.Value.Parameters("fov").Value / 2.;
	var fovtype = InCamera.Value.Parameters("fovtype").Value;
		
	var multx = 1.0;
	var multy = 1. / aspect;
	if (fovtype == 0){
		multx = aspect;
		multy = 1.0;
	}

	var points = InObjPrim.Value.Geometry.Points;
	var posArray = new VBArray( points.PositionArray ).toArray();
	var vPos = XSIMath.CreateVector3();
	var nbPoints = points.Count;
	if (nbPoints != Out.Value.Geometry.Points.Count){
		LogMessage("lmProjectionOp: Invalid Number of Points in Cloned Geometry: " + InObjPrim.Value.FullName, 2);
		return;
	}
	var locCamTrans = InCamGlobal.Value.Transform;
	var locObjTrans = InObjGlobal.Value.Transform;
	var x, y, z;
	for (var i=0, j=0; i<nbPoints; i++, j+=3){
		vPos.Set(posArray[j], posArray[j+1], posArray[j+2]);
		var vPosLoc = XSIMath.MapObjectPositionToObjectSpace(locObjTrans, locCamTrans, vPos);
		
		var dist = Math.abs(vPosLoc.z);
		var scalex = 2. * Math.tan( Math.PI * fov / 180.0 ) * multx * dist;
		var scaley = 2. * Math.tan( Math.PI * fov / 180.0 ) * multy * dist;

		x = vPosLoc.x / scalex + .5;
		y = vPosLoc.y / scaley + .5;
		z = 0;
		if (x > 1. + clipTolerance || x < -clipTolerance || y > 1. + clipTolerance || y < -clipTolerance || vPosLoc.z>0)
			z = distToPlane;
		posArray[j] = x;
		posArray[j+1] = y;
		posArray[j+2] = z;
	}
	Out.Value.Geometry.Points.PositionArray = posArray;
}

/*****************************************************************************************
*	Import Objects from Group in the Projection Plane
*****************************************************************************************/
function importObjects( oPlane, oProp, objCollection, groupName ){
	var oCam = oPlane.Parent;
	var ident = XSIMath.CreateTransform();
	ident.SetIdentity();
	if (objCollection.Count == 0)
		return;
	var oProgressBar = XSIUIToolkit.ProgressBar;
	oProgressBar.Maximum = objCollection.Count + 1; 
	oProgressBar.Caption = "Cloning objects...";
	oProgressBar.Visible = true;

	var dupCollection = Clone(objCollection, 1, siNoParent, siNoGrouping, siDuplicateProperties, siDuplicateAnimation, siNoConstraints );
	CreateGroup(groupName + "_" + oPlane.Name);
	oProgressBar.Increment();
	var e = new Enumerator( objCollection );
	var eDup = new Enumerator( dupCollection );
	for (; !e.atEnd(); e.moveNext(), eDup.moveNext() ){
		var orgObject = e.item();
		var dupObject = eDup.item();
		dupObject.Name = orgObject.Name + "_" + oPlane.Name;
		oProgressBar.Increment();
		oProgressBar.StatusText = "Applying lmProjectionPlane Scop to " + dupObject.Name;
		CopyPaste(dupObject, null, oPlane, 1);
		// Check that there is no operator on Local Transforms
		var elems = EnumElements( EnumElements( dupObject.Kinematics.Local ).Item(0) );
		var eElems = new Enumerator( elems );
		for (; !eElems.atEnd(); eElems.moveNext() ){
			var child = eElems.item();
			if (ClassName(child) == "CustomOperator")
				DeleteObj(child);
		}
		dupObject.Kinematics.Local.Transform = ident;
		if (dupObject.Type == "null") {
			var op = XSIFactory.CreateScriptedOp("lmNullProjectOp", lmNullProjectOp_Update.toString(), "JScript");
			var group1 = op.AddPortGroup( "MainGroup" );
			op.AddOutputPort( dupObject.Kinematics.Local, "OutLocal", group1.index );
			op.AddInputPort( dupObject.Kinematics.Local, "InLocal", group1.index );
			op.AddInputPort( orgObject.Kinematics.Global, "InNullGlobal", group1.index );
			op.AddInputPort( oCam.ActivePrimitive, "InCamera", group1.index );
			op.AddInputPort( oCam.Kinematics.Global, "InCamGlobal", group1.index );
			op.AddInputPort( oProp, "InParams", group1.index );
			op.connect();
		}
		else {
			var op = XSIFactory.CreateScriptedOp("lmGeometryProjectOp", lmGeometryProjectOp_Update.toString(), "JScript");
			var group1 = op.AddPortGroup( "MainGroup" );
			op.AddOutputPort( dupObject.ActivePrimitive, "OutPrim", group1.index );
			op.AddInputPort( dupObject.ActivePrimitive, "InPrim", group1.index );
			op.AddInputPort( oCam.ActivePrimitive, "InCamera", group1.index );
			op.AddInputPort( oCam.Kinematics.Global, "InCamGlobal", group1.index );
			op.AddInputPort( orgObject.ActivePrimitive, "InObjPrim", group1.index );
			op.AddInputPort( orgObject.Kinematics.Global, "InObjGlobal", group1.index );
			op.AddInputPort( oProp, "InParams", group1.index );
			op.connect();
		}
		if (oProgressBar.CancelPressed){
			LogMessage("Canceled... some objects are not projected correctly!", siError);
			return;
		} 
	}
	oProgressBar.Visible = false;
}

/*****************************************************************************************
* PlaneOp Scripted Operator Update Code (applied to the local kinematics of the Projection Plane)
*****************************************************************************************/
function PlaneOp_Update(In_UpdateContext, Out, Incpset, Incamlocal, Incamera){
	var aspect = Incamera.Value.Parameters("aspect").Value;
	var fov = Incamera.Value.Parameters("fov").Value;
	var fovtype = Incamera.Value.Parameters("fovtype").Value;
	var offsetx = Incpset.Value.Parameters("OffsetX").Value;
	var offsety = Incpset.Value.Parameters("OffsetY").Value;
	var dist = Incpset.Value.Parameters("Distance").Value;
		
	var multx = 1.0;
	var multy = 1.0 / aspect;
	if (fovtype == 0){
		multx = aspect;
		multy = 1.0;
	}
	var scalex = 2.0 * Math.tan( Math.PI * fov / 360.0 ) * multx * dist;
	var scaley = 2.0 * Math.tan( Math.PI * fov / 360.0 ) * multy * dist;

	Out.Value.Parameters("posx").Value = -.5 + offsetx;
	Out.Value.Parameters("posy").Value = -.5 + offsety;
	Out.Value.Parameters("posz").Value = -dist;
	Out.Value.Parameters("pposx").Value = .5;
	Out.Value.Parameters("pposy").Value = .5;
	Out.Value.Parameters("pposz").Value = 0.;
	Out.Value.Parameters("rotx").Value = 0.;
	Out.Value.Parameters("roty").Value = 0.;
	Out.Value.Parameters("rotz").Value = 0.;
	Out.Value.Parameters("sclx").Value = scalex;
	Out.Value.Parameters("scly").Value = scaley;
	Out.Value.Parameters("sclz").Value = 1.;
	Out.Value.Parameters("pcposx").Value = 0.;
	Out.Value.Parameters("pcposy").Value = 0.;
	Out.Value.Parameters("pcposz").Value = 0.;
	Out.Value.Parameters("pivotcompactive").Value = false;
}

/*****************************************************************************************
*	Apply planeOp on a object
*****************************************************************************************/
function applyPlaneOp( oPlane, oProp, oCam){
	var op = XSIFactory.CreateScriptedOp("PlaneOp", PlaneOp_Update.toString(), "JScript"); 
	var group1 = op.AddPortGroup( "MainGroup" );
	op.AddOutputPort( oPlane.Kinematics.Local, "Outlocal", group1.index ); 
	op.AddInputPort( oProp, "Incpset", group1.index ); 
	op.AddInputPort( oCam.Kinematics.Local, "Incamlocal", group1.index ); 
	op.AddInputPort( oCam.ActivePrimitive, "Incamera", group1.index ); 
	op.connect();
}

/*****************************************************************************************
*		Create a Projection Plane on a camera
*****************************************************************************************/
function createProjectionPlane( oCam ){
	
	// Create Plane PolyMesh
	var aVertices = new Array(0,0,0, 1,0,0, 1,1,0, 0,1,0);
	var aPolygons = new Array(4,0,1,2,3); 
	var plane = oCam.AddPolygonMesh( aVertices, aPolygons, oCam.Name + "_" + "plane");
	var oProp = plane.AddProperty("lmProjectionParameters", false, "ProjectionParams");
	applyPlaneOp( plane, oProp, oCam);
	
	// Create Plane borders (Linear Curve)
	var posArray = new Array(0,0,0,1, 0,1,0,1, 1,1,0,1, 1,0,0,1);
	var degrees = new Array(1);
	degrees[0] = 1;
	var params = new Array(1);
	params[0] = siUniformParameterization;
	var closed = new Array(1);
	closed[0] = true;
	var borders = oCam.AddNurbsCurveList2(1, posArray, null, null, null, closed, degrees, params, siSINurbs, "ProjectionBorders" );
	applyPlaneOp( borders, oProp, oCam);
	
	lmProjectionParameters_RebuildLayout( oProp.PPGLayout, oProp );
	InspectObj( oProp );
}

/*****************************************************************************************
* ParticleOp Scripted Operator Update Code (applied to a particle cloud in the Projection Plane)
*****************************************************************************************/
function ParticleOp_Update(In_UpdateContext, Out, Inpartlocal, Incamglobal, Inpartprim, Incamera){
	var aspect = Incamera.Value.Parameters("aspect").Value;
	var fov = Incamera.Value.Parameters("fov").Value / 2.;
	var fovtype = Incamera.Value.Parameters("fovtype").Value;
		
	var multx = 1.0;
	var multy = 1. / aspect;
	if (fovtype == 0){
		multx = aspect;
		multy = 1.0;
	}

	var scalex = 2. * Math.tan( Math.PI * fov / 180.0 ) * multx;
	var scaley = 2. * Math.tan( Math.PI * fov / 180.0 ) * multy;

	var posArray = Inpartprim.Value.Particles.PositionArray.toArray();
	var nbPos = posArray.length;
	var nbParticles = nbPos/3;
	LogMessage("SCOP: NbParticles : " + nbParticles.toString() );
	var camT = Incamglobal.Value.Transform;
	var nPos = XSIMath.CreateVector3();
	var nPosLoc, dist;
	for (var i=0; i<nbPos; i+=3 ){
		nPos.Set(posArray[i], posArray[i+1], posArray[i+2]);
		nPosLoc = XSIMath.MapWorldPositionToObjectSpace(camT, nPos);
		dist = Math.abs(nPosLoc.z);
		posArray[i] = nPosLoc.x / (scalex*dist) + .5;
		posArray[i+1] = nPosLoc.y / (scaley*dist) + .5;
		posArray[i+2] = 0.;
	}
	Out.Value.Particles.PositionArray = posArray;
	
}

/*****************************************************************************************
*	Import Freezed Nulls With Cloud in the Projection Plane
*****************************************************************************************/
function importPositionsWithCloud( oPlane, oProp, x3dCollection){
	var oCam = oPlane.Parent;
	var nbParticles = x3dCollection.Count;
	LogMessage("NB Particles: " + nbParticles.toString());
	// Define a new particle type with the billboard shader applied by default
	var oParTypeCol = CreateParticleType();
	// Create a new particle cloud based on the type just defined 
	var oParticleCloud = ActiveSceneRoot.AddParticleCloud( oParTypeCol, "ParticlesFromGroup" );
	var oCloudPrimitive = oParticleCloud.ActivePrimitive;
	oCloudPrimitive.AddParticles( nbParticles, oParTypeCol(0));
	// Get the collection of particles
	var e = new Enumerator( x3dCollection );
	var posArray = new Array(3*nbParticles);
	for (var i=0; !e.atEnd(); e.moveNext(), i+=3 ){
		var ar = (e.item().Kinematics.Global.Transform).GetTranslationValues2().toArray();
		//LogMessage(e.item().FullName);
		posArray[i] = ar[0];
		posArray[i+1] = ar[1];
		posArray[i+2] = ar[2];
	}
	oCloudPrimitive.Particles.PositionArray = posArray;
	
	var oParticleCloudPlane = oPlane.AddParticleCloud( oParTypeCol, "Particles" );
	var transfo = XSIMath.CreateTransform();
	transfo.SetIdentity();
	oParticleCloudPlane.Kinematics.Local.Transform = transfo;
	var oCloudPrimitivePlane = oParticleCloudPlane.ActivePrimitive;
	oCloudPrimitivePlane.AddParticles( nbParticles, oParTypeCol(0));
	
	var op = XSIFactory.CreateScriptedOp("ParticleOp", ParticleOp_Update.toString(), "JScript");
	var group1 = op.AddPortGroup( "MainGroup" );
	op.AddOutputPort( oCloudPrimitivePlane, "OutPart", group1.index );
	op.AddInputPort( oParticleCloudPlane.Kinematics.Local, "Inpartlocal", group1.index );
	op.AddInputPort( oCam.Kinematics.Global, "Incamglobal", group1.index );
	op.AddInputPort( oCloudPrimitive, "Inpartprim", group1.index );
	op.AddInputPort( oCam.ActivePrimitive, "Incamera", group1.index );
	op.connect();
}

/*****************************************************************************************
*		lmCreateProjectionPlane Handlers
*****************************************************************************************/
function lmCreateProjectionPlane_Init( ctxt )
{
	var oCmd;
	oCmd = ctxt.Source;
	oCmd.Description = "Create a Projection Plane linked to a Camera";
	oCmd.Tooltip = "";
	oCmd.SetFlag(siSupportsKeyAssignment,true);
	oCmd.ReturnValue = true;

	var oArgs;
	oArgs = oCmd.Arguments;
	oArgs.AddWithHandler("InputSelection","Collection");
	return true;
}

function lmCreateProjectionPlane_Execute( InputSelection ){
	if (InputSelection == null){
		LogMessage("Please Select a Camera and do it again!");
		return false;
	}
	if ( InputSelection.Count > 0 ){
		for ( var i = 0; i<InputSelection.Count; i++ ){
			var camera = InputSelection(i);
			if (camera.Type == "camera" ) {
				createProjectionPlane(camera);
			}
			else{
				LogMessage( camera.FullName + " is not a Camera!");
			}
		}
	}
	else{
		LogMessage("Please Select a Camera and do it again!");
		return false;
	}
	return true;
}

function lmCreateProjectionPlane_Menu_Init( ctxt ){
	var oMenu;
	oMenu = ctxt.Source;
	oMenu.AddCommandItem("Create lmProjectionPlane","lmCreateProjectionPlane");
	return true;
}

/*****************************************************************************************
*		lmProjectionParameters Rebuild Layout
*****************************************************************************************/
function lmProjectionParameters_RebuildLayout( oLayout, oProp )
{
	var oItem;
	oLayout.Clear();
	oLayout.AddTab("Projection Plane");
	oLayout.AddItem("Distance");
	oLayout.AddItem("OffsetX");
	oLayout.AddItem("OffsetY");
	
	oLayout.AddTab("Project");
	oLayout.AddRow();
	oLayout.AddButton("AddNull", "Create Null");
	oLayout.AddButton("ImportGroup", "Project Group...");
	oLayout.EndRow();
	oLayout.AddGroup("Clipping");
	oLayout.AddItem("ClippingTolerance", "Tolerance");
	oLayout.AddItem("ClippingDistance", "Distance");
	oLayout.EndGroup();
	if (oProp != null){
		var nbNulls = oProp.Parameters("NbNulls").Value;
		if (nbNulls>0){
			for ( var i=1; i<=nbNulls; i++ ){
				var is = i.toString();
				if (is.length == 1)
					is = "0" + is;
				var name = NULL_PREFIX + is;
				oLayout.AddGroup(name);
				oLayout.AddItem(name + "_posx", "X");
				oLayout.AddItem(name + "_posy", "Y");
				oLayout.EndGroup();
			}
		}
	}
	return true;
}

/*****************************************************************************************
*		lmProjectionParameters Handlers
*****************************************************************************************/
function lmProjectionParameters_Define( ctxt )
{
	var oCustomProperty;
	oCustomProperty = ctxt.Source;
	oCustomProperty.AddParameter2("Distance",siDouble,1,0,100000,0,100,0,siPersistable | siAnimatable);
	oCustomProperty.AddParameter2("OffsetX",siDouble,0,-1000,1000,-1,1,0,siPersistable | siAnimatable);
	oCustomProperty.AddParameter2("OffsetY",siDouble,0,-1000,1000,-1,1,0,siPersistable | siAnimatable);
	oCustomProperty.AddParameter2("NbNulls",siUInt4,0,0,1000,0,1000,0,siPersistable | siAnimatable);
	oCustomProperty.AddParameter2("ClippingDistance",siDouble,0,-1000000,100000,-10,10,0,siPersistable | siAnimatable);
	oCustomProperty.AddParameter2("ClippingTolerance",siDouble,0,-1000000,100000,0,2,0,siPersistable | siAnimatable);
	return true;
}

function lmProjectionParameters_OnInit( ctxt )
{
	var oLayout = PPG.PPGLayout;
	var oProp = PPG.Inspected(0);
	lmProjectionParameters_RebuildLayout( oLayout, oProp );
	PPG.Refresh();
}

function lmProjectionParameters_AddNull_OnClicked( ){
	var oProp = PPG.Inspected(0);
	var oPlane = oProp.Parent;
	addNull( oPlane, oProp, 0., 0., true);
	lmProjectionParameters_RebuildLayout( PPG.PPGLayout, oProp );
}

function lmProjectionParameters_ImportGroup_OnClicked( ){
	var oProp = PPG.Inspected(0);
	var oPlane = oProp.Parent;
	var button = 1, nulls;
	var objCollection = new ActiveXObject("XSI.Collection");
	var n, b;
	var rtn = PickElement( "group", "Select Group", "Select Group", n, b, 0 );
	var button = rtn.Value("ButtonPressed"); 
	if ( button != 0 ){
		var element = rtn.Value("PickedElement");
		var groupName = element.Name;
		var e = new Enumerator(element.Members);
		for (; !e.atEnd(); e.moveNext())
			objCollection.Add( e.item() );
		importObjects( oPlane, oProp, objCollection, groupName);
		lmProjectionParameters_RebuildLayout( PPG.PPGLayout, oProp );
	}
	else {
		LogMessage("Nothing Picked!");
	}
	
}

