// Amira Abdel-Rahman
// (c) Massachusetts Institute of Technology 2020

//BASED ON https://github.com/jonhiller/Voxelyze


var DBL_EPSILONx24 =5.328e-15; //DBL_EPSILON*24
var DISCARD_ANGLE_RAD= 1e-7; //Anything less than this angle can be considered 0
var SMALL_ANGLE_RAD= 1.732e-2; //Angles less than this get small angle approximations. To get: Root solve atan(t)/t-1+MAX_ERROR_PERCENT. From: MAX_ERROR_PERCENT = (t-atan(t))/t 
var SMALL_ANGLE_W =0.9999625; //quaternion W value corresponding to a SMALL_ANGLE_RAD. To calculate, cos(SMALL_ANGLE_RAD*0.5).
var W_THRESH_ACOS2SQRT= 0.9988; //Threshhold of w above which we can approximate acos(w) with sqrt(2-2w). To get: Root solve 1-sqrt(2-2wt)/acos(wt) - MAX_ERROR_PERCENT. From MAX_ERROR_PERCENT = (acos(wt)-sqrt(2-2wt))/acos(wt)
var X_AXIS = 0; 			//!< X Axis
var Y_AXIS = 1; 			//!< Y Axis
var Z_AXIS = 2; 		    //!< Z Axis
var currentTime=0;
var maxStrain=0;




function simulateParallel(setup,numTimeSteps,dt,static=true,saveInterval=10){
	// var instuctionsDiv=document.getElementById("footer2").innerHTML;
	initialize(setup);
    for(var i=0;i<numTimeSteps;i++){
        var t0 = performance.now();
        doTimeStep(setup,dt,static,i,saveInterval);
        var t1 = performance.now();
		console.log("TimeStep "+i+" took " + (t1 - t0) + " milliseconds.");
		
		// document.getElementById("footer2").innerHTML = "Timestep "+i +" out of "+ numTimeSteps+".";
	}
	// updateColors();
}

function initialize(setup){
	
	// pre-calculate current position
	var voxCount = setup.nodes.length;
	for(var i=0;i<voxCount;i++){
		setup.nodes[i].currPosition=new THREE.Vector3(setup.nodes[i].position.x,setup.nodes[i].position.y,setup.nodes[i].position.z);
		setup.nodes[i].orient= new THREE.Quaternion();
        setup.nodes[i].linMom=new THREE.Vector3(0,0,0);
        setup.nodes[i].angMom=new THREE.Vector3(0,0,0);
        setup.nodes[i].intForce=new THREE.Vector3(0,0,0);
        setup.nodes[i].intMoment=new THREE.Vector3(0,0,0);
		setup.nodes[i].moment={ x: 0, y: 0,z:0 };
		setup.nodes[i].displacement={ x: 0, y: 0,z:0 };
		//for dynamic simulations
		setup.nodes[i].posTimeSteps=[];
		setup.nodes[i].angTimeSteps=[];
		setup.nodes[i].nomSize=1.0;
		setup.nodes[i].massInverse=8e-6;
		setup.nodes[i].mass=1/8e-6;
		setup.nodes[i].FloorStaticFriction=false;
	} 
	// pre-calculate the axis
	var linkCount = setup.edges.length;
	for(var i=0;i<linkCount;i++){
		var node1=setup.nodes[setup.edges[i].source];
		var node2=setup.nodes[setup.edges[i].target];
		var pVNeg=new THREE.Vector3(node1.position.x,node1.position.y,node1.position.z);
		var pVPos=new THREE.Vector3(node2.position.x,node2.position.y,node2.position.z);
		var axis=pVPos.clone().sub(pVNeg).normalize();
		setup.edges[i].axis=axis.clone();


		setup.edges[i].currentRestLength=0;
        setup.edges[i].pos2= new THREE.Vector3(0,0,0);
        setup.edges[i].angle1v= new THREE.Vector3(0,0,0);
        setup.edges[i].angle2v= new THREE.Vector3(0,0,0);
        setup.edges[i].angle1=new THREE.Quaternion();
        setup.edges[i].angle2=new THREE.Quaternion();
        setup.edges[i].currentTransverseArea=0;
		setup.edges[i].currentTransverseStrainSum=0;
		//todo update stresses
		setup.edges[i].stressTimeSteps=[];
		
	} 
	
}

function doTimeStep(setup,dt,static=true,currentTimeStep,saveInterval){
	if (dt==0) 
		return true;
	else if (dt<0) 
		dt = recommendedTimeStep();

	// if (collisions) updateCollisions();
	

	var collisions=false;

	//Euler integration:
	var Diverged = false;
	var linkCount = setup.edges.length;
	for(var i=0;i<linkCount;i++){
        updateForces(setup,setup.edges[i],setup.nodes[setup.edges[i].source],setup.nodes[setup.edges[i].target],static);
        // todo: update forces and whatever
		if (axialStrain(setup.edges[i]) > 100) {
			Diverged = true; //catch divergent condition! (if any thread sets true we will fail, so don't need mutex...
		}

	}
    if (Diverged){
		console.log("Divergedd!!!!!")
		return false;

	}
	var voxCount = setup.nodes.length;

	for(var i=0;i<voxCount;i++){
		timeStep(dt,setup.nodes[i],static,currentTimeStep);
		if(!static&& currentTimeStep%saveInterval==0){
			setup.nodes[i].posTimeSteps.push(setup.nodes[i].displacement);
			setup.nodes[i].angTimeSteps.push(setup.nodes[i].angle);

		}
		// todo: update linMom,angMom, orient and whatever
	} 

	
	currentTime += dt;
	return true;

}

function updateForces(setup,edge,node1,node2,static=true){
	
	var pVNeg=new THREE.Vector3(node1.position.x,node1.position.y,node1.position.z);
	var pVPos=new THREE.Vector3(node2.position.x,node2.position.y,node2.position.z);
    var currentRestLength=pVPos.clone().sub(pVNeg).length();
	edge.currentRestLength=currentRestLength; //todo make sure updated
	

	pVNeg=node1.currPosition.clone();
	pVPos=node2.currPosition.clone();

	// Vec3D<double> three
	var oldPos2 = edge.pos2.clone();//??
	var oldAngle1v = edge.angle1v.clone();
	var oldAngle2v = edge.angle2v.clone(); //remember the positions/angles from last timestep to calculate velocity
	// var oldAngle1v=new THREE.Vector3(node1.angle.x,node1.angle.y,node1.angle.z);//?
	// var oldAngle2v=new THREE.Vector3(node2.angle.x,node2.angle.y,node2.angle.z); //??

	totalRot= orientLink( edge,node1,node2); //sets pos2, angle1, angle2 /*restLength*/

	var dPos2=edge.pos2.clone().sub(oldPos2).multiplyScalar(0.5);
	var dAngle1=edge.angle1v.clone().sub(oldAngle1v).multiplyScalar(0.5);
	var dAngle2=edge.angle2v.clone().sub(oldAngle2v).multiplyScalar(0.5);

	
	
	
	//if volume effects...
    //if (!mat->isXyzIndependent() || currentTransverseStrainSum != 0) 
    //updateTransverseInfo(); //currentTransverseStrainSum != 0 catches when we disable poissons mid-simulation
	
	
	var _stress=updateStrain((edge.pos2.x/edge.currentRestLength),edge.stiffness);
	// var _stress=updateStrain(1.0);
	edge.stress = _stress;
	if(!static){
		edge.stressTimeSteps.push(_stress);
	}

	// console.log("Stress:"+edge.stress)
	if(setup.viz.minStress>edge.stress){
		setup.viz.minStress=edge.stress;
	}else if (setup.viz.maxStress<edge.stress){
		setup.viz.maxStress=edge.stress;
	}

	// if (isFailed()){forceNeg = forcePos = momentNeg = momentPos = Vec3D<double>(0,0,0); return;}

	// var b1=mat->_b1, b2=mat->_b2, b3=mat->_b3, a2=mat->_a2; //local copies //todo get from where i had
	
	var l   = currentRestLength;//??
	var rho = edge.density;
	var A = edge.area;
	var E = edge.stiffness;// youngs modulus
	var G=1.0;//todo shear_modulus
	var ixx = 1.0;//todo section ixx
	var I=1.0;
	var iyy = 1.0;//todo section.iyy//
	// var l0=length.dataSync();
	var J=1.0;//todo check
	// var l02 = l0 * l0;
	// var l03 = l0 * l0 * l0;
	var b1= 12*E*I/(l*l*l);
	var b2= 6*E*I/(l*l);
	var b3= 2*E*I/(l);
	var a1= E*A/l;
	var a2= G*J/l;
	var nu=0;

	// var b1= 5e6;
	// var b2= 1.25e7;
	// var b3= 2.08333e+07;
	// var a1= E*A/l;
	// var a2= 1.04167e+07;

	var E=1000000;
	var E=edge.stiffness;
	var L = 5;
	var a1 = E*L; //EA/L : Units of N/m
	var a2 = E * L*L*L / (12.0*(1+nu)); //GJ/L : Units of N-m
	var b1 = E*L; //12EI/L^3 : Units of N/m
	var b2 = E*L*L/2.0; //6EI/L^2 : Units of N (or N-m/m: torque related to linear distance)
	var b3 = E*L*L*L/6.0; //2EI/L : Units of N-m
	// console.log("currentRestLength:"+currentRestLength);
	// console.log("b1:"+b1/10e6);
	// console.log("b2:"+b2/10e7);
	// console.log("b3:"+b3/10e7);
	// console.log("a2:"+a2/10e7);
	// var b1= 5e6;
	// var b2= 1.25e7;
	// var b3= 2.08333e+07;
	// var a1= E*A/l;
	// var a2= 1.04167e+07;

	var currentTransverseArea=25.0;// todo ?? later change
	var currentTransverseArea=edge.area;


	//Beam equations. All relevant terms are here, even though some are zero for small angle and others are zero for large angle (profiled as negligible performance penalty)
	var forceNeg = new THREE.Vector3 (	_stress*currentTransverseArea, //currentA1*pos2.x,
								b1*edge.pos2.y - b2*(edge.angle1v.z + edge.angle2v.z),
								b1*edge.pos2.z + b2*(edge.angle1v.y + edge.angle2v.y)); //Use Curstress instead of -a1*Pos2.x to account for non-linear deformation 
	var forcePos = forceNeg.clone().negate();

	var momentNeg = new THREE.Vector3 (	a2*(edge.angle2v.x - edge.angle1v.x),
								-b2*edge.pos2.z - b3*(2*edge.angle1v.y + edge.angle2v.y),
								b2*edge.pos2.y - b3*(2*edge.angle1v.z + edge.angle2v.z));
	var momentPos = new THREE.Vector3 (	a2*(edge.angle1v.x - edge.angle2v.x),
								-b2*edge.pos2.z - b3*(edge.angle1v.y + 2*edge.angle2v.y),
								b2*edge.pos2.y - b3*(edge.angle1v.z + 2*edge.angle2v.z));
	
								
	// //local damping:
	// if (isLocalVelocityValid()){ //if we don't have the basis for a good damping calculation, don't do any damping.
	// 	float sqA1=mat->_sqA1, sqA2xIp=mat->_sqA2xIp,sqB1=mat->_sqB1, sqB2xFMp=mat->_sqB2xFMp, sqB3xIp=mat->_sqB3xIp;
	// 	Vec3D<double> posCalc(	sqA1*dPos2.x,
	// 							sqB1*dPos2.y - sqB2xFMp*(dAngle1.z+dAngle2.z),
	// 							sqB1*dPos2.z + sqB2xFMp*(dAngle1.y+dAngle2.y));

	// 	forceNeg += pVNeg->dampingMultiplier()*posCalc;
	// 	forcePos -= pVPos->dampingMultiplier()*posCalc;

	// 	momentNeg -= 0.5*pVNeg->dampingMultiplier()*Vec3D<>(	-sqA2xIp*(dAngle2.x - dAngle1.x),
	// 															sqB2xFMp*dPos2.z + sqB3xIp*(2*dAngle1.y + dAngle2.y),
	// 															-sqB2xFMp*dPos2.y + sqB3xIp*(2*dAngle1.z + dAngle2.z));
	// 	momentPos -= 0.5*pVPos->dampingMultiplier()*Vec3D<>(	sqA2xIp*(dAngle2.x - dAngle1.x),
	// 															sqB2xFMp*dPos2.z + sqB3xIp*(dAngle1.y + 2*dAngle2.y),
	// 															-sqB2xFMp*dPos2.y + sqB3xIp*(dAngle1.z + 2*dAngle2.z));

	// }
	// else setBoolState(LOCAL_VELOCITY_VALID, true); //we're good for next go-around unless something changes

    //	transform forces and moments to local voxel coordinates
	var smallAngle=false;//?? todo check
	var forceNeg,momentNeg,forcePos,momentPos;

	
	
	if (!smallAngle){//?? chech
		forceNeg = RotateVec3DInv(edge.angle1,forceNeg);
		momentNeg = RotateVec3DInv(edge.angle1,momentNeg);
	}
	forcePos = RotateVec3DInv(edge.angle2,forcePos);
	momentPos = RotateVec3DInv(edge.angle2,momentPos);
	

	forceNeg =toAxisOriginalVector3(forceNeg,edge.axis);
	forcePos =toAxisOriginalVector3(forcePos,edge.axis);
	momentNeg=toAxisOriginalQuat(momentNeg,edge.axis);
	momentPos=toAxisOriginalQuat(momentPos,edge.axis);
	
	

	node1.intForce.add(forceNeg.clone());
	node2.intForce.add(forcePos.clone());
	node1.intMoment.add(momentNeg.clone());
	node2.intMoment.add(momentPos.clone());

	// assert(!(forceNeg.x != forceNeg.x) || !(forceNeg.y != forceNeg.y) || !(forceNeg.z != forceNeg.z)); //assert non QNAN
	// assert(!(forcePos.x != forcePos.x) || !(forcePos.y != forcePos.y) || !(forcePos.z != forcePos.z)); //assert non QNAN


}

function orientLink( edge,node1,node2){ //updates pos2, angle1, angle2, and smallAngle //Quat3D<double> /*double restLength*/

    var pVPos=node2.currPosition.clone();
	var pVNeg=node1.currPosition.clone();
	

    
	var currentRestLength=edge.currentRestLength;
	// var currentRestLength=0;
	
	
	var pos2 = toAxisXVector3(pVPos.clone().sub(pVNeg),edge.axis); //digit truncation happens here...

	

	// pos2.x = Math.round(pos2.x * 1e4) / 1e4; 
	
	var angle1 = toAxisXQuat(node1.orient,edge.axis);
	var angle2 = toAxisXQuat(node2.orient,edge.axis);


	var totalRot = angle1.conjugate(); //keep track of the total rotation of this bond (after toAxisX()) //Quat3D<double>
	pos2 = RotateVec3D(totalRot,pos2);
	angle2 = totalRot.clone().multiply(angle2);
	angle1 = new THREE.Quaternion(); //zero for now...


	//small angle approximation?
	// var SmallTurn =  ((Math.abs(pos2.z)+Math.abs(pos2.y))/pos2.x);
	// var ExtendPerc = (Math.abs(1-pos2.x/currentRestLength));
	// if (!smallAngle /*&& angle2.IsSmallAngle()*/ && SmallTurn < SA_BOND_BEND_RAD && ExtendPerc < SA_BOND_EXT_PERC){
	// 	smallAngle = true;
	// 	setBoolState(LOCAL_VELOCITY_VALID, false);
	// }
	// else if (smallAngle && (/*!angle2.IsSmallishAngle() || */SmallTurn > HYSTERESIS_FACTOR*SA_BOND_BEND_RAD || ExtendPerc > HYSTERESIS_FACTOR*SA_BOND_EXT_PERC)){
	// 	smallAngle = false;
	// 	setBoolState(LOCAL_VELOCITY_VALID, false);
    // }
    
    var smallAngle=true; //todo later remove

	if (smallAngle)	{ //Align so Angle1 is all zeros
		pos2.x -= currentRestLength; //only valid for small angles
	}
	else { //Large angle. Align so that Pos2.y, Pos2.z are zero.
		FromAngleToPosX(angle1,pos2); //get the angle to align Pos2 with the X axis
		totalRot = angle1.clone().multiply(totalRot) ; //update our total rotation to reflect this
		angle2 = angle1.clone().multiply(  angle2); //rotate angle2
		pos2 = new THREE.Vector3(pos2.length() - currentRestLength, 0, 0); 
	}

	
	angle1v = ToRotationVector(angle1);
	angle2v = ToRotationVector(angle2);

	// assert(!(angle1v.x != angle1v.x) || !(angle1v.y != angle1v.y) || !(angle1v.z != angle1v.z)); //assert non QNAN
	// assert(!(angle2v.x != angle2v.x) || !(angle2v.y != angle2v.y) || !(angle2v.z != angle2v.z)); //assert non QNAN

    edge.pos2=pos2.clone();
    edge.angle1v=angle1v.clone();
    edge.angle2v=angle2v.clone();
    edge.angle1=angle1.clone();
	edge.angle2=angle2.clone();
	
	

	return totalRot;
}

function RotateVec3D(a, f)  { 
	var fx=f.x, fy=f.y, fz=f.z;
	var tw = fx*a.x + fy*a.y + fz*a.z;
	var tx = fx*a.w - fy*a.z + fz*a.y;
	var ty = fx*a.z + fy*a.w - fz*a.x;
	var tz = -fx*a.y + fy*a.x + fz*a.w;

	return new THREE.Vector3(a.w*tx + a.x*tw + a.y*tz - a.z*ty, a.w*ty - a.x*tz + a.y*tw + a.z*tx, a.w*tz + a.x*ty - a.y*tx + a.z*tw);
} //!< Returns a vector representing the specified vector "f" rotated by this quaternion. @param[in] f The vector to transform.


function RotateVec3DInv(a, f) { 
    var fx=f.x, fy=f.y, fz=f.z;
    var tw = a.x*fx + a.y*fy + a.z*fz;
    var tx = a.w*fx - a.y*fz + a.z*fy;
    var ty = a.w*fy + a.x*fz - a.z*fx;
    var tz = a.w*fz - a.x*fy + a.y*fx;
    return new THREE.Vector3(tw*a.x + tx*a.w + ty*a.z - tz*a.y, tw*a.y - tx*a.z + ty*a.w + tz*a.x, tw*a.z + tx*a.y - ty*a.x + tz*a.w);	
} //!< Returns a vector representing the specified vector "f" rotated by the inverse of this quaternion. This is the opposite of RotateVec3D. @param[in] f The vector to transform.

function toAxisOriginalVector3(pV,axis){
	// switch (axis){
	// 	case Y_AXIS:{
	// 		var tmp = pV.y; 
	// 		pV.y=pV.x; 
	// 		pV.x = -tmp; 
	// 		break;
	// 	} 
	// 	case Z_AXIS: {
	// 		var tmp = pV.z; 
	// 		pV.z=pV.x; 
	// 		pV.x = -tmp;
	// 		 break;
	// 	} 
	// 	default: 
	// 		break;
	// }
	// if(axis.equals(new THREE.Vector3(0,1,0))){
	// 	var tmp = pV.y; 
	// 	pV.y=pV.x; 
	// 	pV.x = -tmp;  

	// }else if(axis.equals(new THREE.Vector3(0,0,1))){
	// 	var tmp = pV.z; 
	// 	pV.z=pV.x; 
	// 	pV.x = -tmp; 

	// }

	var vector=axis.clone();
	vector.normalize();
	var xaxis=new THREE.Vector3(1,0,0);
	var geometry = new THREE.BoxGeometry( 1, 1, 1 );
	var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
	var cube = new THREE.Mesh( geometry, material );
	var quaternion = new THREE.Quaternion(); // create one and reuse it
	quaternion.setFromUnitVectors(  xaxis,vector );
	cube.applyQuaternion( quaternion );
	pV.applyEuler(cube.rotation);


	return pV;
}

function toAxisOriginalQuat(pQ,axis){
	// switch (axis){
	// 	case Y_AXIS: {
	// 		var tmp = pQ.y; 
	// 		pQ.y=pQ.x; 
	// 		pQ.x = -tmp; 
	// 		break;
	// 	} 
	// 	case Z_AXIS: {
	// 		var tmp = pQ.z; 
	// 		pQ.z=pQ.x; 
    //         pQ.x = -tmp; 
    //         break;
	// 	} 
	// 	default: 
	// 		break;
	// }
	// if(axis.equals(new THREE.Vector3(0,1,0))){
	// 	var tmp = pQ.y; 
	// 	pQ.y=pQ.x; 
	// 	pQ.x = -tmp; 

	// }else if(axis.equals(new THREE.Vector3(0,0,1))){
	// 	var tmp = pQ.z; 
	// 	pQ.z=pQ.x; 
	// 	pQ.x = -tmp; 

	// }

	var v=new THREE.Vector3(pQ.x,pQ.y,pQ.z);
	var vector=axis.clone();
	vector.normalize();
	var xaxis=new THREE.Vector3(1,0,0);
	var geometry = new THREE.BoxGeometry( 1, 1, 1 );
	var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
	var cube = new THREE.Mesh( geometry, material );
	var quaternion = new THREE.Quaternion(); // create one and reuse it
	// quaternion.setFromUnitVectors( vector, xaxis );
	quaternion.setFromUnitVectors( xaxis, vector ); //amira changed to see 3 march 2020
	cube.applyQuaternion( quaternion );
	v.applyEuler(cube.rotation);

	return new THREE.Quaternion(v.x,v.y,v.z,pQ.w);

}

function toAxisXVector3(v,axis){ //TODO CHANGE

	// var vector=new THREE.Vector3(1,0,0);

	// switch (axis){
	// 	case Y_AXIS: 
	// 		return new THREE.Vector3(v.y, -v.x, v.z); 
	// 		break;
	// 	case Z_AXIS: 
	// 		return new THREE.Vector3(v.z, v.y, -v.x); 
	// 		break;
	// 	default: 
	// 		
	// 		return v;
	//		break;
	// }

	// switch (axis){
	// 	case Y_AXIS: 
	// 		vector=new THREE.Vector3(0,1,0);
	// 		break;
	// 	case Z_AXIS: 
	// 		vector=new THREE.Vector3(0,0,1);
	// 		break;
	// 	default: 
	// 		vector=new THREE.Vector3(1,0,0);
	// 		break;
	// }

	var vector=axis.clone();
	vector.normalize();
	var xaxis=new THREE.Vector3(1,0,0);
	var geometry = new THREE.BoxGeometry( 1, 1, 1 );
	var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
	var cube = new THREE.Mesh( geometry, material );
	var quaternion = new THREE.Quaternion(); // create one and reuse it
	quaternion.setFromUnitVectors( vector, xaxis );
	cube.applyQuaternion( quaternion );
	v.applyEuler(cube.rotation);
	// var res=6;
	// v=new THREE.Vector3( parseFloat(v.x.toFixed(res)),parseFloat(v.y.toFixed(res)),parseFloat(v.z.toFixed(res)))
	
	
	
	return v.clone();

} //transforms a vec3D in the original orientation of the bond to that as if the bond was in +X direction

function  toAxisXQuat(q,axis){
	// var vector=new THREE.Vector3(1,0,0);
	// switch (axis){
	// 	case Y_AXIS: 
	// 		return new THREE.Quaternion( q.y, -q.x, q.z,q.w); 
	// 	case Z_AXIS: 
	// 		return new THREE.Quaternion( q.z, q.y, -q.x,q.w); 
	// 	default: 
	// 		return q;
	// }
	
	// switch (axis){
	// 	case Y_AXIS: 
	// 		vector=new THREE.Vector3(0,1,0);
	// 		break;
	// 	case Z_AXIS: 
	// 		vector=new THREE.Vector3(0,0,1);
	// 		break;
	// 	default: 
	// 		vector=new THREE.Vector3(1,0,0);
	// 		break;
	// }
	var v=new THREE.Vector3(q.x,q.y,q.z);
	var vector=axis.clone();
	vector.normalize();
	var xaxis=new THREE.Vector3(1,0,0);
	var geometry = new THREE.BoxGeometry( 1, 1, 1 );
	var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
	var cube = new THREE.Mesh( geometry, material );
	var quaternion = new THREE.Quaternion(); // create one and reuse it
	quaternion.setFromUnitVectors( vector, xaxis );
	cube.applyQuaternion( quaternion );
	v.applyEuler(cube.rotation);

	return new THREE.Quaternion(v.x,v.y,v.z,q.w);

} //transforms a vec3D in the original orientation of the bond to that as if the bond was in +X direction


//const Quat3D Conjugate() const {return Quat3D(w, -x, -y, -z);} //!< Returns a quaternion that is the conjugate of this quaternion. This quaternion is not modified.
function ToRotationVector(a)  {
	if (a.w >= 1.0 || a.w <= -1.0) {
		return new THREE.Vector3(0,0,0);
	}
	var squareLength = 1.0-a.w*a.w; //because x*x + y*y + z*z + w*w = 1.0, but more susceptible to w noise (when 
	var SLTHRESH_ACOS2SQRT= 2.4e-3; //SquareLength threshhold for when we can use square root optimization for acos. From SquareLength = 1-w*w. (calculate according to 1.0-W_THRESH_ACOS2SQRT*W_THRESH_ACOS2SQRT

	if (squareLength < SLTHRESH_ACOS2SQRT) //???????
		return new THREE.Vector3(a.x, a.y, a.z).multiplyScalar(2.0*Math.sqrt((2-2*a.w)/squareLength)); //acos(w) = sqrt(2*(1-x)) for w close to 1. for w=0.001, error is 1.317e-6
	else 
		return new THREE.Vector3(a.x, a.y, a.z).multiplyScalar(2.0*Math.acos(a.w)/Math.sqrt(squareLength));
} //!< Returns a rotation vector representing this quaternion rotation. Adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/

function FromRotationVector( VecIn) { 
	var q=new THREE.Quaternion();
	var theta = VecIn.clone().divideScalar(2.0);
	var s, thetaMag2 = theta.length()*theta.length();
	if (thetaMag2*thetaMag2 < DBL_EPSILONx24 ){ //if the 4th taylor expansion term is negligible
		q.w=1.0 - 0.5*thetaMag2;
		s=1.0 - thetaMag2 / 6.0;
	}
	else {
		var thetaMag = Math.sqrt(thetaMag2);
		q.w=Math.cos(thetaMag);
		s=Math.sin(thetaMag) / thetaMag;
	}
	q.x=theta.x*s;
	q.y=theta.y*s;
	q.z=theta.z*s;
	return q;
} //!< Overwrites this quaternion with values from the specified rotation vector. Adapted from http://physicsforgames.blogspot.com/2010/02/quaternions.html.  Note: function changes this quaternion. @param[in] VecIn A rotation vector to calculate this quaternion from.


function FromAngleToPosX(a, RotateFrom){ //highly optimized at the expense of readability
	if (new THREE.Vector3(0,0,0).equals(RotateFrom)) 
		return; //leave off if it slows down too much!!

    //Catch and handle small angle:
    var YoverX = RotateFrom.y/RotateFrom.x;
    var ZoverX = RotateFrom.z/RotateFrom.x;
    if (YoverX<SMALL_ANGLE_RAD && YoverX>-SMALL_ANGLE_RAD && ZoverX<SMALL_ANGLE_RAD && ZoverX>-SMALL_ANGLE_RAD){ //??? //Intercept small angle and zero angle
		a.x=0; 
		a.y=0.5*ZoverX; 
		a.z=-0.5*YoverX;
        a.w = 1+0.5*(-a.y*a.y-a.z*a.z); //w=sqrt(1-x*x-y*y), small angle sqrt(1+x) ~= 1+x/2 at x near zero.
        return a;
    }

    //more accurate non-small angle:
    var RotFromNorm = RotateFrom.clone();
    RotFromNorm.normalize(); //Normalize the input...

    var theta = Math.acos(RotFromNorm.x); //because RotFromNorm is normalized, 1,0,0 is normalized, and A.B = |A||B|cos(theta) = cos(theta)
    if (theta > Math.PI-DISCARD_ANGLE_RAD) {//??????
		a.w=0; 
		a.x=0; 
		a.y=1; 
		a.z=0; 
		return a;
	} //180 degree rotation (about y axis, since the vector must be pointing in -x direction

    var AxisMagInv = 1.0/Math.sqrt(RotFromNorm.z*RotFromNorm.z+RotFromNorm.y*RotFromNorm.y);
    //Here theta is the angle, axis is RotFromNorm.Cross(Vec3D(1,0,0)) = Vec3D(0, RotFromNorm.z/AxisMagInv, -RotFromNorm.y/AxisMagInv), which is still normalized. (super rolled together)
    var aa = 0.5*theta;
    var s = Math.sin(a);
	a.w=Math.cos(aa); 
	a.x=0; 
	a.y=RotFromNorm.z*AxisMagInv*s; 
	a.z = -RotFromNorm.y*AxisMagInv*s; //angle axis function, reduced
	return a;

} //!< Overwrites this quaternion with the calculated rotation that would transform the specified RotateFrom vector to point in the positve X direction. Note: function changes this quaternion.  @param[in] RotateFrom An arbitrary direction vector. Does not need to be normalized.

function axialStrain()  {
	return strain;
} //!< returns the current overall axial strain (unitless) between the two voxels.

function axialStrain( positiveEnd) {
	//strainRatio = pVPos->material()->E/pVNeg->material()->E;
	var strainRatio=1.0;
	return positiveEnd ? 2.0 *strain*strainRatio/(1.0+strainRatio) : 2.0*strain/(1.0+strainRatio);
}

function updateStrain( axialStrain,E){ //?from where strain
	strain = axialStrain; //redundant?
	var currentTransverseStrainSum=0.0; //??? todo
    var linear=true;
    // var maxStrain=100000000000000000000;//?? todo later change
	if (linear){
		if (axialStrain > maxStrain) 
			maxStrain = axialStrain; //remember this maximum for easy reference

		return stress(axialStrain,E);
	}
	else {
		var returnStress;

		if (axialStrain > maxStrain){ //if new territory on the stress/strain curve
			maxStrain = axialStrain; //remember this maximum for easy reference
			returnStress = stress(axialStrain,E); //??currentTransverseStrainSum
			
			if (nu != 0.0) 
				strainOffset = maxStrain-stress(axialStrain,E)/(_eHat*(1-nu)); //precalculate strain offset for when we back off
			else strainOffset = maxStrain-returnStress/E; //precalculate strain offset for when we back off

		}
		else { //backed off a non-linear material, therefore in linear region.
			var relativeStrain = axialStrain-strainOffset; // treat the material as linear with a strain offset according to the maximum plastic deformation
			
			if (nu != 0.0) 
				returnStress = stress(relativeStrain,E);
			else 
				returnStress = E*relativeStrain;
		}
		return returnStress;

	}

}

function stress( strain , E ){//,transverseStrainSum, forceLinear){
	//reference: http://www.colorado.edu/engineering/CAS/courses.d/Structures.d/IAST.Lect05.d/IAST.Lect05.pdf page 10
	//if (isFailed(strain)) return 0.0f; //if a failure point is set and exceeded, we've broken!
	// var E =setup.edges[0].stiffness; //todo change later to material ??
	// var E=1000000;//todo change later to material ??
	// var scaleFactor=1;
    return E*strain;

	// if (strain <= strainData[1] || linear || forceLinear){ //for compression/first segment and linear materials (forced or otherwise), simple calculation
        
        // if (nu==0.0) return E*strain;
		// else return _eHat*((1-nu)*strain + nu*transverseStrainSum); 
		//else return eHat()*((1-nu)*strain + nu*transverseStrainSum); 
	// }

	// //the non-linear feature with non-zero poissons ratio is currently experimental
	// int DataCount = modelDataPoints();
	// for (int i=2; i<DataCount; i++){ //go through each segment in the material model (skipping the first segment because it has already been handled.
	// 	if (strain <= strainData[i] || i==DataCount-1){ //if in the segment ending with this point (or if this is the last point extrapolate out) 
	// 		float Perc = (strain-strainData[i-1])/(strainData[i]-strainData[i-1]);
	// 		float basicStress = stressData[i-1] + Perc*(stressData[i]-stressData[i-1]);
	// 		if (nu==0.0f) return basicStress;
	// 		else { //accounting for volumetric effects
	// 			float modulus = (stressData[i]-stressData[i-1])/(strainData[i]-strainData[i-1]);
	// 			float modulusHat = modulus/((1-2*nu)*(1+nu));
	// 			float effectiveStrain = basicStress/modulus; //this is the strain at which a simple linear stress strain line would hit this point at the definied modulus
	// 			float effectiveTransverseStrainSum = transverseStrainSum*(effectiveStrain/strain);
	// 			return modulusHat*((1-nu)*effectiveStrain + nu*effectiveTransverseStrainSum);
	// 		}
	// 	}
	// }

	// assert(false); //should never reach this point
	// return 0.0f;
}

function updateTransverseInfo(edge){
	// currentTransverseArea = 0.5*(pVNeg->transverseArea(edge.axis)+pVPos->transverseArea(edge.axis));
    // currentTransverseStrainSum = 0.5*(pVNeg->transverseStrainSum(edge.axis)+pVPos->transverseStrainSum(edge.axis));
    edge.currentTransverseArea = 1; //or 0
	edge.currentTransverseStrainSum = 1;//or 0

}

function transverseArea( axis){
	var size = 1.0;//??(float)mat->nominalSize();
    //if (mat->poissonsRatio() == 0) return size*size;
    if (true) return size*size;

	// var psVec = poissonsStrain();

	// switch (axis){
	// case X_AXIS: return (float)(size*size*(1+psVec.y)*(1+psVec.z));
	// case Y_AXIS: return (float)(size*size*(1+psVec.x)*(1+psVec.z));
	// case Z_AXIS: return (float)(size*size*(1+psVec.x)*(1+psVec.y));
	// default: return size*size;
	// }
}

//http://klas-physics.googlecode.com/svn/trunk/src/general/Integrator.cpp (reference)
function timeStep( dt,node,static=true,currentTimeStep){
	var previousDt = dt;
	var linMom=node.linMom.clone();
    var angMom=node.angMom.clone();
    var orient=node.orient.clone();
	var pos=new THREE.Vector3(node.currPosition.x,node.currPosition.y,node.currPosition.z);
	if (dt == 0.0) 
		return;

	var isTrue = (currentValue) => currentValue ==true;


	if (node.restrained_degrees_of_freedom.every(isTrue)){
		// pos = originalPosition() + ext->translation();
		// orient = ext->rotationQuat();
		// haltMotion();
		pos=new THREE.Vector3(node.position.x,node.position.y,node.position.z);
		node.currPosition=pos.clone();
		linMom = new THREE.Vector3(0,0,0);
		angMom = new THREE.Vector3(0,0,0);
		node.displacement={x:0,y:0,z:0};

		node.orient=orient.clone();
		node.linMom=linMom.clone();
		node.angMom=angMom.clone();
		return;
	}

	/////////////////////////
	var gravity=true;
	var isFloorEnabled=true;
	// node.FloorStaticFriction=false;

	//Translation
	var curForce = force(node,static,currentTimeStep);
	

	//add gravity
	var grav=-node.mass*9.80665*10.0;
	if(gravity&&!static){
		curForce.y+=grav;

	}

	var fricForce = curForce.clone();
	if (isFloorEnabled) {
		curForce=floorForce(node,dt, curForce).multiplyScalar(1.0);
	}
	fricForce = curForce.clone().sub(fricForce);
	// console.log(fricForce);



	linMom.add(curForce).multiplyScalar(dt);
	var translate=linMom.clone().multiplyScalar(dt*node.massInverse);//??massInverse

	

	//	we need to check for friction conditions here (after calculating the translation) and stop things accordingly
	if (isFloorEnabled && floorPenetration(node) >= 0 &&!static){ //we must catch a slowing voxel here since it all boils down to needing access to the dt of this timestep.
		var work = fricForce.x*translate.x + fricForce.z*translate.z; //F dot disp
		var hKe = 0.5*node.massInverse*(linMom.x*linMom.x + linMom.z*linMom.z); //horizontal kinetic energy
		
		if((hKe + work) <= 0) {
			node.FloorStaticFriction=true; //this checks for a change of direction according to the work-energy principle
			// console.log("dvdfvfdbvd");
		}
		
		if(node.FloorStaticFriction){ 
			//if we're in a state of static friction, zero out all horizontal motion
			
			console.log("hereeee");
			linMom.x = 0;
			linMom.z = 0;
			translate.x = 0
			translate.z = 0;
		}
	}
	else {
		
		node.FloorStaticFriction=false;
	}

	///////////////////////////////////////////////



	pos.add(translate);
	node.currPosition=pos.clone();
	node.displacement={
		x:translate.x+node.displacement.x,
		y:translate.y+node.displacement.y,
		z:translate.z+node.displacement.z};
	
	// pos += translate;

	//Rotation
	var curMoment = moment(node); 
	angMom.add(curMoment*dt);

	var momentInertiaInverse=1.0;//todo ?? later change
	orient.multiply(FromRotationVector(angMom.clone().multiplyScalar((dt*momentInertiaInverse)))); //update the orientation //momentInertiaInverse

	node.orient=orient.clone();
	
	var eulerOrder = "ZYX"; //TODO SEE IF CORRECT
	var eul = new THREE.Euler().setFromQuaternion( orient, eulerOrder ); 

	node.angle={
		x:eul.x,
		y:eul.y,
		z:eul.z};


	node.linMom=linMom.clone();
	node.angMom=angMom.clone();
	
	// if (ext){//?? todo fix 
	// 	var size = 1;//change
	// 	if (ext->isFixed(X_TRANSLATE)) {pos.x = ix*size + ext->translation().x; linMom.x=0;}
	// 	if (ext->isFixed(Y_TRANSLATE)) {pos.y = iy*size + ext->translation().y; linMom.y=0;}
	// 	if (ext->isFixed(Z_TRANSLATE)) {pos.z = iz*size + ext->translation().z; linMom.z=0;}
	// 	if (ext->isFixedAnyRotation()){ //if any rotation fixed, all are fixed
	// 		if (ext->isFixedAllRotation()){
	// 			orient = ext->rotationQuat();
	// 			angMom = Vec3D<double>();
	// 		}
	// 		else { //partial fixes: slow!
	// 			Vec3D<double> tmpRotVec = orient.ToRotationVector();
	// 			if (ext->isFixed(X_ROTATE)){ tmpRotVec.x=0; angMom.x=0;}
	// 			if (ext->isFixed(Y_ROTATE)){ tmpRotVec.y=0; angMom.y=0;}
	// 			if (ext->isFixed(Z_ROTATE)){ tmpRotVec.z=0; angMom.z=0;}
	// 			orient.FromRotationVector(tmpRotVec);
	// 		}
	// 	}
	// }


	// poissonsStrainInvalid = true;
}



function force(node,static=true,currentTimeStep) {
	//forces from internal bonds
	var totalForce=new THREE.Vector3(0,0,0);
	//new THREE.Vector3(node.force.x,node.force.y,node.force.z);
	// todo 
	
	totalForce.add(node.intForce);

	// for (int i=0; i<6; i++){ 
	// 	if (links[i]) totalForce += links[i]->force(isNegative((linkDirection)i)); //total force in LCS
	// }
	totalForce = RotateVec3D(node.orient,totalForce); //from local to global coordinates

	//assert(!(totalForce.x != totalForce.x) || !(totalForce.y != totalForce.y) || !(totalForce.z != totalForce.z)); //assert non QNAN

	//other forces
	if(static){
		totalForce.add(new THREE.Vector3(node.force.x,node.force.y,node.force.z));
	// }else if(currentTimeStep<50){
	// 	totalForce.add(new THREE.Vector3(node.force.x,node.force.y,node.force.z));
	}else{
		// var ex=0.1;
		// if(node.force.y!=0){
		// 	var f=400*Math.sin(currentTimeStep*ex);
		// 	totalForce.add(new THREE.Vector3(0,f,0));
			
		// }
		var ff=new THREE.Vector3(node.force.x,node.force.y,node.force.z);
		if(ff.length()>0){
			// var x=node.position.z;
			// var t=currentTimeStep;
			// var wave=getForce(x,t);
			// totalForce.add(new THREE.Vector3(0,wave,0));
			var t=currentTimeStep;
			totalForce.add(getForce(node.currPosition,currentTimeStep));

		}
		
	}
	

	// if (externalExists()) totalForce += external()->force(); //external forces
	// totalForce -= velocity()*mat->globalDampingTranslateC(); //global damping f-cv
	// totalForce.z += mat->gravityForce(); //gravity, according to f=mg

	// if (isCollisionsEnabled()){
	// 	for (std::vector<CVX_Collision*>::iterator it=colWatch->begin(); it!=colWatch->end(); it++){
	// 		totalForce -= (*it)->contactForce(this);
	// 	}
	// }
	//todo make internal forces 0 again
	node.intForce=new THREE.Vector3(0,0,0);
	// node.force.x=0;
	// node.force.y=0;
	// node.force.z=0;
	return totalForce;
}

function floorForce(node, dt,  pTotalForce){
    var CurPenetration = floorPenetration(node); //for now use the average.
    

    var muStatic=0.2;
    var muKinetic=0.01;//normal force = 1e3*0.001
    var zetaGlobal=1.0;
    // pMat1->setInternalDamping(1.0);
	// pMat1->setGlobalDamping(0.2f);
    // pMat1->setStaticFriction(1.0f);
	// pMat1->setKineticFriction(0.1f); //normal force = 1e3*0.001
	// pMat1->setGlobalDamping(1.0f);

	if (CurPenetration>=0){ 
		
		var vel = velocity(node);
		var horizontalVel= new THREE.Vector3(vel.x, 0, vel.z);
		// console.log(CurPenetration);
		var normalForce = penetrationStiffness(node)*CurPenetration;
        
		pTotalForce.y += normalForce - collisionDampingTranslateC(node)*vel.y; //in the z direction: k*x-C*v - spring and damping

		if (node.FloorStaticFriction){ //If this voxel is currently in static friction mode (no lateral motion) 
			// assert(horizontalVel.Length2() == 0);
			var surfaceForceSq = (pTotalForce.x*pTotalForce.x + pTotalForce.z*pTotalForce.z); //use squares to avoid a square root
			var frictionForceSq = (muStatic*normalForce)*(muStatic*normalForce);
		
			if (surfaceForceSq > frictionForceSq) {
                node.FloorStaticFriction=false; //if we're breaking static friction, leave the forces as they currently have been calculated to initiate motion this time step
            }
		}
        else { //even if we just transitioned don't process here or else with a complete lack of momentum it'll just go back to static friction
            pTotalForce.sub(horizontalVel.normalize().multiplyScalar(muKinetic*normalForce*1.0))//add a friction force opposing velocity according to the normal force and the kinetic coefficient of friction
            //pTotalForce -=  muKinetic*normalForce*horizontalVel.Normalized(); 
        }
	}
	else {
        node.FloorStaticFriction=false;
    }
    return pTotalForce;

}

function floorPenetration(node) {
	var floor=-3.5;
	if(node.currPosition.y-floor<0){
		
		return -(node.currPosition.y-floor);
	}else{
		return 0;
	}
} //!< Returns the interference (in meters) between the collision envelope of this voxel and the floor at Z=0. Positive numbers correspond to interference. If the voxel is not touching the floor 0 is returned.

function velocity(node) {
    return node.linMom.clone().multiplyScalar(node.massInverse);
} //!< Returns the 3D velocity of this voxel in m/s (GCS)
function penetrationStiffness(node)  {
	var E=10000000;
    return (2*E*node.nomSize);
} //!< returns the stiffness with which this voxel will resist penetration. This is calculated according to E*A/L with L = voxelSize/2.
function collisionDampingTranslateC(node)  {
	var E=10000000;
    var _2xSqMxExS = (2.0*Math.sqrt(node.mass*E*node.nomSize));
    var zetaCollision=0.01;
    return zetaCollision*_2xSqMxExS;
} //!< Returns the global material damping coefficient (translation)
function collisionDampingRotateC(node)  {
	var zetaCollision=1.0;
    var _momentInertia = (node.mass*node.nomSize*node.nomSize / 6.0); //simple 1D approx
    var _2xSqIxExSxSxS = (2.0*Math.sqrt(_momentInertia*E*node.nomSize*node.nomSize*node.nomSize));
    return zetaCollision*_2xSqIxExSxSxS;
} //!< Returns the global material damping coefficient (rotation)


function moment(node) {
	//moments from internal bonds
	var totalMoment=new THREE.Vector3(0,0,0);
	// for (int i=0; i<6; i++){ 
	// 	if (links[i]) totalMoment += links[i]->moment(isNegative((linkDirection)i)); //total force in LCS
	// }
	totalMoment.add(node.intMoment);
	totalMoment = RotateVec3D(node.orient,totalMoment);
	
	totalMoment.add(new THREE.Vector3(node.moment.x,node.moment.y,node.moment.z));

	//other moments
	// if (externalExists()) totalMoment += external()->moment(); //external moments
	// totalMoment -= angularVelocity()*mat->globalDampingRotateC(); //global damping
	node.intMoment=new THREE.Vector3(0,0,0);
	return totalMoment;
}

////////////////////////////////////////////////////////////////////////////////////////////////

//void haltMotion(){linMom = angMom = Vec3D<>(0,0,0);} //!< Halts all momentum of this block. Unless fixed the voxel will continue to move in subsequent timesteps.

function recommendedTimeStep()  {
	// //find the largest natural frequency (Math.sqrt(k/m)) that anything in the simulation will experience, then multiply by 2*pi and invert to get the optimally largest timestep that should retain stability
	// float MaxFreq2 = 0.0f; //maximum frequency in the simulation in rad/sec

	// for (std::vector<CVX_Link*>::const_iterator it=linksList.begin(); it != linksList.end(); it++){ //for each link
	// 	CVX_Link* pL = (*it);
	// 	//axial
	// 	float m1 = pL->pVNeg->mat->mass(),  m2 = pL->pVPos->mat->mass();
	// 	float thisMaxFreq2 = pL->axialStiffness()/(m1<m2?m1:m2);
	// 	if (thisMaxFreq2 > MaxFreq2) MaxFreq2 = thisMaxFreq2;

	// 	//rotational will always be less than or equal
	// }


	// if (MaxFreq2 <= 0.0f){ //didn't find anything (i.e no links) check for individual voxelss
	// 	for (std::vector<CVX_Voxel*>::const_iterator it=voxelsList.begin(); it != voxelsList.end(); it++){ //for each link
	// 		float thisMaxFreq2 = (*it)->mat->youngsModulus()*(*it)->mat->nomSize/(*it)->mat->mass(); 
	// 		if (thisMaxFreq2 > MaxFreq2) MaxFreq2 = thisMaxFreq2;
	// 	}
	// }
	
	// if (MaxFreq2 <= 0.0f) return 0.0f;
	// else return 1.0f/(6.283185f*sqrt(MaxFreq2)); //the optimal timestep is to advance one radian of the highest natural frequency
}

function isXyzIndependent()  {return nu==0.0;} //!< Returns true if poisson's ratio is zero - i.e. deformations in each dimension are independent of those in other dimensions.


function isFailed(edge) {
	// return mat->isFailed(maxStrain);
}

// function isLocalVelocityValid()  {return boolStates & LOCAL_VELOCITY_VALID ? true : false;} //

// function dampingMultiplier() {return 2*mat->_sqrtMass*mat->zetaInternal/previousDt;} //!< Returns the damping multiplier for this voxel. This would normally be called only internally for the internal damping calculations.

// function setBoolState(linkFlags flag, bool set=true) {set ? boolStates |= (int)flag : boolStates &= ~(int)flag;}

// function setFixedAll(bool fixed=true) {fixed?setDisplacementAll():clearDisplacementAll();} //!< Sets all 6 degrees of freedom to either fixed or free depending on the value of fixed. Either way, sets all displacements to zero. @param[in] fixed Whether all degrees of freedom should be fixed (true) or free (false).

// function setDisplacement(dofComponent dof, double displacement=0.0); //!< Fixes the specified degree of freedom and applies the prescribed displacement if specified. @param[in] dof the degree of freedom in question. @param[in] displacement The displacement in meters (translational dofs) or radians (rotational dofs) to apply. Large fixed displacements may cause instability.
// function setDisplacementAll(const Vec3D<double>& translation = Vec3D<double>(0,0,0), const Vec3D<double>& rotation = Vec3D<double>(0,0,0)); //!< Fixes the all degrees of freedom and applies the specified translation and rotation. @param[in] translation The translation in meters from this voxel's nominal position to fix it at. @param[in] rotation The rotation (in the form of a rotation vector) from this voxel's nominal orientation to fix it at.

function transverseStrainSum(axis){
	// if (mat->poissonsRatio() == 0) return 0;
	
	// Vec3D<float> psVec = poissonsStrain();

	// switch (axis){
	// case CVX_Link::X_AXIS: return psVec.y+psVec.z;
	// case CVX_Link::Y_AXIS: return psVec.x+psVec.z;
	// case CVX_Link::Z_AXIS: return psVec.x+psVec.y;
	// default: return 0.0f;
	// }

}

function transverseStrainSum(axis){
	// if (mat->poissonsRatio() == 0) return 0;
	
	// Vec3D<float> psVec = poissonsStrain();

	// switch (axis){
	// case CVX_Link::X_AXIS: return psVec.y+psVec.z;
	// case CVX_Link::Y_AXIS: return psVec.x+psVec.z;
	// case CVX_Link::Z_AXIS: return psVec.x+psVec.y;
	// default: return 0.0f;
	// }

}

function poissonsStrain(node){
	// if (poissonsStrainInvalid){
	// 	pStrain = strain(true);
	// 	poissonsStrainInvalid = false;
	// }
	// return pStrain;
}

function strain( poissonsStrain) {
	//if no connections in the positive and negative directions of a particular axis, strain is zero
	//if one connection in positive or negative direction of a particular axis, strain is that strain - ?? and force or constraint?
	//if connections in both the positive and negative directions of a particular axis, strain is the average. 
	
	// Vec3D<float> intStrRet(0,0,0); //intermediate strain return value. axes according to linkAxis enum
	// int numBondAxis[3] = {0}; //number of bonds in this axis (0,1,2). axes according to linkAxis enum
	// bool tension[3] = {false};
	// for (int i=0; i<6; i++){ //cycle through link directions
	// 	if (links[i]){
	// 		int axis = toAxis((linkDirection)i);
	// 		intStrRet[axis] += links[i]->axialStrain(isNegative((linkDirection)i));
	// 		numBondAxis[axis]++;
	// 	}
	// }
	// for (int i=0; i<3; i++){ //cycle through axes
	// 	if (numBondAxis[i]==2) intStrRet[i] *= 0.5f; //average
	// 	if (poissonsStrain){
	// 		tension[i] = ((numBondAxis[i]==2) || (ext && (numBondAxis[i]==1 && (ext->isFixed((dofComponent)(1<<i)) || ext->force()[i] != 0)))); //if both sides pulling, or just one side and a fixed or forced voxel...
	// 	}

	// }

	// if (poissonsStrain){
	// 	if (!(tension[0] && tension[1] && tension[2])){ //if at least one isn't in tension
	// 		float add = 0;
	// 		for (int i=0; i<3; i++) if (tension[i]) add+=intStrRet[i];
	// 		float value = pow( 1.0f + add, -mat->poissonsRatio()) - 1.0f;
	// 		for (int i=0; i<3; i++) if (!tension[i]) intStrRet[i]=value;
	// 	}
	// }

	// return intStrRet;
}