
// The MoveTo feature in this web page was created by Dr Todd A Carlson and Dr Clark Wells of
// Grand Valley State University,  Allendale  MI,  49401 in June, 2001.  This work was made 
// possible with a grant from the Pew Faculty Teaching and Learning Center at GVSU.
//
// Paul Pillot provided the execScript function which executes chime commands from javascript by
// writing a chime button in a dummy frame and is executed immediately.  This circumvents the 
// use of the executeScript command which is not supported by Internet Explorer.   The original 
// executeScript commands have been left in as comments.
//
// Instructions for incorporating this feature in your web page is at the end of the javascript.
//
// When the MoveTo javascript function is called, the Chime image will rotate, zoom, and 
// translate to the view defined by the parameters used to call the MoveTo function.
// The syntax for the MoveTo function is:
//
//	MoveTo(xr, yr, zr, zm, xt, yt, sec, fps, maxacc)
//
// The first six MoveTo parameters define the view of the molecule using the convention of 
// Chime's "view show view" script command.  xr, yr, and zr are the rotations about the 
// x, y and z axes.  zm is the size of the image as generated by the zoom script command.
// xt and yt are the translations along the x and y axes. To create the view, the rotations 
// must be executed in the order z-y-x, starting with the default orientation.  These six 
// paramters can also be generated by using the "copy chime script" feature in Chime's edit menu. 
// The last three MoveTo parameters are the total time of the animation in seconds, the number of
// frames to be plotted per second, and the maximum acceleration that will be used.  These are
// the same as the parameters described by the Chime move script command.  MoveTo uses Chime's
// move command to execute the animation.  However, the rotation, zoom, and translation
// parameters for the move command do NOT follow the convention described for view show view
// above.  When executing a "move", Chime divides the parameters by the total number of frames 
// that will be plotted.  The fractional rotations are executed in the order x-y-z, the size of
// the image is increased or decrease by the zoom factor, the image is translated, and the image
// for the frame is plotted.  The MoveToCalc javascript function uses the desired view 
// paramteters (defined in the MoveTo function) and the current orientation (obtained in the echo
// function) to calculate the move parameters to move the the view from the current view to the 
// desired view.

var doingMoveTo=false

// The MoveTo function assigns the view and animation parameters (defined above) to global
// variables so they can be utilized by the other two javascript functions
// (dxr=desired x-rotation; dyt=desired y-translation; dzm=desired zoom; etc).
// The last line executes a view show view command to obtain the current view on the screen.
// This imput is passed to the echo javascript function which follows.

function MoveTo(xr,yr,zr,zm,xt,yt,t,f,a)
{
	document.form.text.value = " "
	doingMoveTo=true
	dxr=xr
	dyr=yr
	dzr=zr
	dzm=zm
	dxt=xt
	dyt=yt
	sec=t
	fps=f
	acc=a

        execScript('view show view','myoglobin');
//	document.myoglobin.executeScript("view show view")
}

// Output from the Chime plug-in is passed to the echo function due to the MessageCallback 
// command in the Chime embed tag.  The data from the view set message is split into the data
// array.  The "for" loop assigns the default value of zero to any value which is not returned.
// The exception is that the default zoom value is 100.  The data array is assigned to variable
// using the same format as above (cxr=current x-rotation, etc).  The move parameters are
// calculated by calling the MoveToCalc function.

function echo(myoglobin,message)
{
	if (doingMoveTo)
	{
		if (message.substr(0,9)=="view set ")
		{
			doingMoveTo=false
			data = message.split(" ")
			for (i = 2; i < 8; i++)
			{
				if (isNaN(parseFloat(data[i]))) {data[i]="0.00"}
				data[i]=parseFloat(data[i])
			}
			if (data[5]==0){data[5]=100}
			cxr = data[2]
			cyr = data[3]
			czr = data[4]
			czm = data[5]
			cxt = data[6]
			cyt = data[7]
			MoveToCalc()
		}		
	}
}


// MoveToCalc() is where the move parameters are calculated using the current and desired view
// parameters defined above.

function MoveToCalc()
{
	var ang
	
	// These statements only give output to the text box below the image.  Otherwise, they serve no function.

	document.form.text.value = "x rot =" + cxr + "  " + dxr + "\r" + document.form.text.value
	document.form.text.value = "y rot =" + cyr + "  " + dyr + "\r" + document.form.text.value
	document.form.text.value = "z rot =" + czr + "  " + dzr + "\r" + document.form.text.value
	document.form.text.value = "zoom  =" + czm + "  " + dzm + "\r" + document.form.text.value
	document.form.text.value = "x tra =" + cxt + "  " + dxt + "\r" + document.form.text.value
	document.form.text.value = "y tra =" + cyt + "  " + dyt + "\r" + document.form.text.value
	document.form.text.value = "    initial     final\r" + document.form.text.value

	// Zoom and translations are easy.  Since they are commutative and linear,
	// just subtract current from desired to get net.  Translations greater than
        // 100 are not supported by Chime's move command so these are reset to be 100.

	nzm=dzm-czm
	nxt=dxt-cxt
	nyt=dyt-cyt

        if (nxt>100){nxt=100}
        if (nyt>100){nyt=100}

	// Now it's time to work on the rotations.  First, initialize netRot.
	// Later it will contain the net rotation current -> reference -> desired.

	netRot=new Array()

	for(i=0;i<3;i++)
	{
		netRot[i]=new Array()
	}
	
	// Since Chime uses degrees and javascript uses radians, a quick unit conversion and
	// change of variables (so as not to affect the global variables).

		deg = 180/Math.PI

		Dxr=dxr/deg
		Dyr=dyr/deg
		Dzr=dzr/deg

		Cxr=cxr/deg
		Cyr=cyr/deg
		Czr=czr/deg

	// The net rotation matrix.  This accounts both for Chime's quirk of giving the
	// rotations in the opposite order from what "move" takes and the fact that we
	// need to go current -> reference rather than the other way.

	with(Math)
	{
		netRot[0][0]=cos(Dyr)*cos(Dzr)*cos(Czr)*cos(Cyr)+cos(Dyr)*sin(Dzr)*sin(Czr)*cos(Cyr)+sin(Dyr)*sin(Cyr)
		netRot[0][1]=cos(Dyr)*cos(Dzr)*(-sin(Czr)*cos(Cxr)+cos(Czr)*sin(Cyr)*sin(Cxr))+cos(Dyr)*sin(Dzr)*(cos(Czr)*cos(Cxr)+sin(Czr)*sin(Cyr)*sin(Cxr))-sin(Dyr)*cos(Cyr)*sin(Cxr)
		netRot[0][2]=cos(Dyr)*cos(Dzr)*(-sin(Czr)*sin(Cxr)-cos(Czr)*sin(Cyr)*cos(Cxr))+cos(Dyr)*sin(Dzr)*(cos(Czr)*sin(Cxr)-sin(Czr)*sin(Cyr)*cos(Cxr))+sin(Dyr)*cos(Cyr)*cos(Cxr)

		netRot[1][0]=(sin(Dxr)*sin(Dyr)*cos(Dzr)-cos(Dxr)*sin(Dzr))*cos(Czr)*cos(Cyr)+(sin(Dxr)*sin(Dyr)*sin(Dzr)+cos(Dxr)*cos(Dzr))*sin(Czr)*cos(Cyr)-sin(Dxr)*cos(Dyr)*sin(Cyr)
		netRot[1][1]=(sin(Dxr)*sin(Dyr)*cos(Dzr)-cos(Dxr)*sin(Dzr))*(-sin(Czr)*cos(Cxr)+cos(Czr)*sin(Cyr)*sin(Cxr))+(sin(Dxr)*sin(Dyr)*sin(Dzr)+cos(Dxr)*cos(Dzr))*(cos(Czr)*cos(Cxr)+sin(Czr)*sin(Cyr)*sin(Cxr))+sin(Dxr)*cos(Dyr)*cos(Cyr)*sin(Cxr)
		netRot[1][2]=(sin(Dxr)*sin(Dyr)*cos(Dzr)-cos(Dxr)*sin(Dzr))*(-sin(Czr)*sin(Cxr)-cos(Czr)*sin(Cyr)*cos(Cxr))+(sin(Dxr)*sin(Dyr)*sin(Dzr)+cos(Dxr)*cos(Dzr))*(cos(Czr)*sin(Cxr)-sin(Czr)*sin(Cyr)*cos(Cxr))-sin(Dxr)*cos(Dyr)*cos(Cyr)*cos(Cxr)

		netRot[2][0]=(-cos(Dxr)*sin(Dyr)*cos(Dzr)-sin(Dxr)*sin(Dzr))*cos(Czr)*cos(Cyr)+(-cos(Dxr)*sin(Dyr)*sin(Dzr)+sin(Dxr)*cos(Dzr))*sin(Czr)*cos(Cyr)+cos(Dxr)*cos(Dyr)*sin(Cyr)
		netRot[2][1]=(-cos(Dxr)*sin(Dyr)*cos(Dzr)-sin(Dxr)*sin(Dzr))*(-sin(Czr)*cos(Cxr)+cos(Czr)*sin(Cyr)*sin(Cxr))+(-cos(Dxr)*sin(Dyr)*sin(Dzr)+sin(Dxr)*cos(Dzr))*(cos(Czr)*cos(Cxr)+sin(Czr)*sin(Cyr)*sin(Cxr))-cos(Dxr)*cos(Dyr)*cos(Cyr)*sin(Cxr)
		netRot[2][2]=(-cos(Dxr)*sin(Dyr)*cos(Dzr)-sin(Dxr)*sin(Dzr))*(-sin(Czr)*sin(Cxr)-cos(Czr)*sin(Cyr)*cos(Cxr))+(-cos(Dxr)*sin(Dyr)*sin(Dzr)+sin(Dxr)*cos(Dzr))*(cos(Czr)*sin(Cxr)-sin(Czr)*sin(Cyr)*cos(Cxr))+cos(Dxr)*cos(Dyr)*cos(Cyr)*cos(Cxr)
	}

	// numframes is frames per second times seconds --- the total number of frames to
	// compute.  Note that if maxaccel is not 1, the number of frames may be changed inside 
	// the "move" command, and may cause (very slight) variations from desired end result.

	numframes = fps*sec

	// We need to extract the axis and the angle about that axis from netRot, split the 
	// rotation into "numframes" identical parts, and "lie" to "move" about the angles. 
	// To do this, we divide the angle by "numframes", find the rotation matrix for that
	// fraction of the total rotation about the axis, determine the x, y, and z components,
	// then multiply each by "numframes" ("move" will subsequently divide these angles by
	// the number of frames, thus doing what we want).

	Axis = new Array()
	// Axis = netRot * e3 + (netRot)^T * e3 + (1-Tr(netRot)) e3
	Axis[0] = netRot[0][2] + netRot[2][0]
	Axis[1] = netRot[1][2] + netRot[2][1]
	Axis[2] = netRot[2][2] + 1 - netRot[0][0] - netRot[1][1] 

	// Make Axis a unit vector to save trouble later.
	with(Math)
	{
		maxAx = max(abs(Axis[0]),max(abs(Axis[1]),abs(Axis[2])))
		axnorm = maxAx*sqrt( (Axis[0]/maxAx)*(Axis[0]/maxAx) + (Axis[1]/maxAx)*(Axis[1]/maxAx) + (Axis[2]/maxAx)*(Axis[2]/maxAx) )
		for(i=0;i<3;i++)
		{
			Axis[i] = Axis[i]/axnorm
		}
	}
	

	// If the rotation is about the z-axis, then the algorithm used for
	// calculating the angle will return zero (since it uses the cross
	// product of e3 and netRot*e3), so we must deal with it separately.
	ang = 0
	if((Axis[0]+1) == 1 && (Axis[1]+1) == 1)
	{
		Axis[0] = 0
		Axis[1] = 0
		Axis[2] = 1
		ang = Math.atan2(netRot[0][1], netRot[0][0])
	}else
	{
		ang = Math.acos( (netRot[0][0] + netRot[1][1] + netRot[2][2] - 1)/2)
		dir = Axis[0]*netRot[1][2] - Axis[1]*netRot[0][2] 
		if(dir < 0)
		{
			ang = -1*ang
		}
	}
	// We now have an appropriate angle and axis.  Now divide the angle by numframes and
	// discover the new rotation.  At this point, we no longer need the original rotation, 
	// since the original will be the new one iterated "numframes" times.

	ang = ang/numframes

	// In order to accomplish a rotation about any given axis, first we move the axis to the 
	// x-axis, then we rotate about the x-axis, then we move the x-axis back where it came 
	// from.  So, first we rotate about the z-axis to make the y-component zero.  Then
	// rotate about the y-axis to zero out the z-component.  The third step will be the
	// rotation about the x-axis.  Next, undo the rotation about y, then undo the roation
	// about x.  The end results are as follows:

	with(Math)
	{

		netRot[0][0] = Axis[0]*Axis[0]+cos(ang)*Axis[1]*Axis[1]+cos(ang)*Axis[2]*Axis[2]

// The commented lines are unnecessary to subsequent calculations, but
// are included for completeness.

//		netRot[0][1] = -(-Axis[1]*Axis[0]+Axis[0]*cos(ang)*Axis[1]+Axis[2]*sin(ang))
		netRot[0][2] = -(-Axis[0]*Axis[2]+Axis[2]*Axis[0]*cos(ang)-Axis[1]*sin(ang))

		netRot[1][0] = -(-Axis[1]*Axis[0]+Axis[0]*cos(ang)*Axis[1]-Axis[2]*sin(ang))
		netRot[1][1] = (cos(ang)*Axis[0]*Axis[0]+Axis[1]*Axis[1]+cos(ang)*Axis[2]*Axis[2])
//		netRot[1][2] = -(Axis[0]*sin(ang)-Axis[1]*Axis[2]+Axis[2]*Axis[1]*cos(ang))

		netRot[2][0] = -(-Axis[2]*Axis[0]+Axis[2]*Axis[0]*cos(ang)+Axis[1]*Axis[1]*Axis[1]*sin(ang)+Axis[1]*sin(ang)*Axis[2]*Axis[2]+Axis[1]*sin(ang)*Axis[0]*Axis[0])
		netRot[2][1] = (Axis[2]*Axis[1]-Axis[2]*Axis[1]*cos(ang)+sin(ang)*Axis[0]*Axis[1]*Axis[1]+sin(ang)*Axis[0]*Axis[2]*Axis[2]+sin(ang)*Axis[0]*Axis[0]*Axis[0])
		netRot[2][2] = (Axis[2]*Axis[2]+cos(ang)*Axis[1]*Axis[1]+cos(ang)*Axis[0]*Axis[0])
	
		maxNet = max( abs(netRot[0][0]), abs(netRot[1][0]) )
		zNet = -atan2(-netRot[1][0]/maxNet,netRot[0][0]/maxNet)*deg*numframes

		maxNet = max( abs(netRot[2][1]), abs(netRot[2][2]) )
		xNet = -atan2(netRot[2][1]/maxNet,netRot[2][2]/maxNet)*deg*numframes

		maxNet = max( abs(netRot[0][0]),abs(netRot[1][1]) )
		maxNet = max( maxNet,abs(netRot[2][2]) )
		maxNet = max( maxNet,abs(netRot[0][2]) )

		yNet = -atan2(-netRot[2][0]/maxNet,sqrt(netRot[0][2]/maxNet*netRot[0][2]/maxNet+netRot[0][0]/maxNet*netRot[0][0]/maxNet+netRot[2][2]/maxNet*netRot[2][2]/maxNet-netRot[1][1]/maxNet*netRot[1][1]/maxNet))*deg*numframes

	}

	// This is to try to fix the problem of "trivial" move commands
	// freezing things up, although it still doesn't catch all of them.

	if( (1+xNet) == 1 && (1+yNet) == 1 && (1+zNet) == 1 && (1+nzm) == 1 && (1+nxt) == 1 && (1+nty) == 1 )
	{
		command=""
	}
	else
	{
		command="move " + xNet + " " + yNet + " " + zNet + " " + nzm + " " + nxt + " " + nyt + " 0 0 " + sec + " " + fps + " " + acc
	}

	// This statement only gives output to the text box below the image.  Otherwise, it serves no function.

	document.form.text.value = command + "\r" + document.form.text.value

	// While debugging, I needed the rotation matrix.  It occurs to me that
	// it might be useful to someone else to see what's going on, so I am
	// leaving it commented to save retyping it.

	//	document.form.text.value = document.form.text.value+ "\n Net Rotation =\n[" + netRot[0][0] + " " + netRot[0][1] + " " + netRot[0][2] + "]\n"
	//	document.form.text.value = document.form.text.value + "[" + netRot[1][0] + " " + netRot[1][1] + " " + netRot[1][2] + "]\n"
	//	document.form.text.value = document.form.text.value + "[" + netRot[2][0] + " " + netRot[2][1] + " " + netRot[2][2] + "]"
	//	document.form.text.value = document.form.text.value + "\nang = " + ang*180/Math.PI*numframes + " deg\naxis = \n[" + Axis[0] + "]\n[" + Axis[1] + "]\n[" + Axis[2] + "]\nnumframes = " + numframes

	// This is where the move command is executed.  Becuase the desired and current rotations
	// assume rotation around the default center, we reset the center of rotation.  If the
	// user has changed the center of rotation and we did not reset it, the move command
	// would not give the correct results.
	

        execScript('centre','myoglobin');
        execScript(command,'myoglobin');

//	document.myoglobin.executeScript("centre")
//	document.myoglobin.executeScript(command)

}

function execScript(spt,plugInName,plugInName2)
{
if (navigator.appName != 'Netscape')
	with (document.dummy.document)
	{
		open();
	
		writeln("<html><body>");

		// Writes a Chime button which executes imediately
		writeln("<embed type='application/x-spt' hidden=true");
		writeln(" width=10 height=10 button=push target='" + plugInName + "'");
		writeln(" script='" + spt + "' immediate=1>");
		if (arguments.length==3)
		{
			writeln("<embed type='application/x-spt' hidden=true");
			writeln(" width=10 height=10 button=push target='" + plugInName2 + "'");
			writeln(" script='" + spt + "' immediate=1>");
		}
		writeln("</body></html>");

		close();
	}
	else 
	{
		eval('document.' + plugInName + '.executeScript("' + spt + '")');
		if (arguments.length==3)
		{
			eval('document.' + plugInName2 + '.executeScript("' + spt + '")');
		}
	}
}

