From ccf02ac9bd67d4d49ad8264ae7fc6098e361ff4b Mon Sep 17 00:00:00 2001 From: "apatrick@google.com" Date: Thu, 12 Nov 2009 19:56:24 +0000 Subject: Made all line endings consistently LF and added svn:eol-style=LF property to files with these names / extensions. c cc h mm txt idl py js html css gyp gypi xml shader json htm README DEPS git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31811 0039d316-1c4b-4281-b951-d872f2087c98 --- o3d/samples/GoogleIO-2009/assets/style.css | 16 +- o3d/samples/bitmap-draw-image.html | 492 ++-- o3d/samples/box2d-3d/third_party/box2d/box2d.js | 2074 ++++++++-------- o3d/samples/manipulators/translate1.html | 546 ++--- o3d/samples/o3djs/manipulators.js | 1924 +++++++-------- o3d/samples/o3djs/performance.js | 404 +-- o3d/samples/o3djs/texture.js | 478 ++-- o3d/samples/siteswap/animation.js | 396 +-- o3d/samples/siteswap/math.js | 2984 +++++++++++------------ o3d/samples/siteswap/siteswap.html | 646 ++--- o3d/samples/siteswap/siteswap.js | 830 +++---- 11 files changed, 5395 insertions(+), 5395 deletions(-) (limited to 'o3d/samples') diff --git a/o3d/samples/GoogleIO-2009/assets/style.css b/o3d/samples/GoogleIO-2009/assets/style.css index 71fc441..2c4a145 100644 --- a/o3d/samples/GoogleIO-2009/assets/style.css +++ b/o3d/samples/GoogleIO-2009/assets/style.css @@ -1,8 +1,8 @@ -html, body { - height: 100%; - margin: 0; - padding: 0; - border: none; - font-family: Arial, sans-serif; -} - +html, body { + height: 100%; + margin: 0; + padding: 0; + border: none; + font-family: Arial, sans-serif; +} + diff --git a/o3d/samples/bitmap-draw-image.html b/o3d/samples/bitmap-draw-image.html index 0a05b73..e4daea6 100644 --- a/o3d/samples/bitmap-draw-image.html +++ b/o3d/samples/bitmap-draw-image.html @@ -1,247 +1,247 @@ - - - - - - - - -Bitmap Draw Image Demo - - - - - -

Bitmap Draw Image Demo

-This tutorial shows how to create bitmaps and how to draw images -on both bitmaps and texture mipmaps. -
-Scroll wheel to see different mipmaps. -
- -
- - + + + + + + + + +Bitmap Draw Image Demo + + + + + +

Bitmap Draw Image Demo

+This tutorial shows how to create bitmaps and how to draw images +on both bitmaps and texture mipmaps. +
+Scroll wheel to see different mipmaps. +
+ +
+ + \ No newline at end of file diff --git a/o3d/samples/box2d-3d/third_party/box2d/box2d.js b/o3d/samples/box2d-3d/third_party/box2d/box2d.js index e55e449..8b84f83 100644 --- a/o3d/samples/box2d-3d/third_party/box2d/box2d.js +++ b/o3d/samples/box2d-3d/third_party/box2d/box2d.js @@ -1,1037 +1,1037 @@ -/* - * Box2Djs (port of Box2DFlash 1.4.3.1) - http://box2d-js.sourceforge.net/ - * Single-filed and jsmined ( http://code.google.com/p/jsmin-php/ ) by Mr.doob - */ - -var b2Settings=Class.create();b2Settings.prototype={initialize:function(){}} -b2Settings.USHRT_MAX=0x0000ffff;b2Settings.b2_pi=Math.PI;b2Settings.b2_massUnitsPerKilogram=1.0;b2Settings.b2_timeUnitsPerSecond=1.0;b2Settings.b2_lengthUnitsPerMeter=30.0;b2Settings.b2_maxManifoldPoints=2;b2Settings.b2_maxShapesPerBody=64;b2Settings.b2_maxPolyVertices=8;b2Settings.b2_maxProxies=1024;b2Settings.b2_maxPairs=8*b2Settings.b2_maxProxies;b2Settings.b2_linearSlop=0.005*b2Settings.b2_lengthUnitsPerMeter;b2Settings.b2_angularSlop=2.0/180.0*b2Settings.b2_pi;b2Settings.b2_velocityThreshold=1.0*b2Settings.b2_lengthUnitsPerMeter/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_maxLinearCorrection=0.2*b2Settings.b2_lengthUnitsPerMeter;b2Settings.b2_maxAngularCorrection=8.0/180.0*b2Settings.b2_pi;b2Settings.b2_contactBaumgarte=0.2;b2Settings.b2_timeToSleep=0.5*b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_linearSleepTolerance=0.01*b2Settings.b2_lengthUnitsPerMeter/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_angularSleepTolerance=2.0/180.0/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2Assert=function(a) -{if(!a){var nullVec;nullVec.x++;}}; -var b2Vec2=Class.create();b2Vec2.prototype={initialize:function(x_,y_){this.x=x_;this.y=y_;},SetZero:function(){this.x=0.0;this.y=0.0;},Set:function(x_,y_){this.x=x_;this.y=y_;},SetV:function(v){this.x=v.x;this.y=v.y;},Negative:function(){return new b2Vec2(-this.x,-this.y);},Copy:function(){return new b2Vec2(this.x,this.y);},Add:function(v) -{this.x+=v.x;this.y+=v.y;},Subtract:function(v) -{this.x-=v.x;this.y-=v.y;},Multiply:function(a) -{this.x*=a;this.y*=a;},MulM:function(A) -{var tX=this.x;this.x=A.col1.x*tX+A.col2.x*this.y;this.y=A.col1.y*tX+A.col2.y*this.y;},MulTM:function(A) -{var tX=b2Math.b2Dot(this,A.col1);this.y=b2Math.b2Dot(this,A.col2);this.x=tX;},CrossVF:function(s) -{var tX=this.x;this.x=s*this.y;this.y=-s*tX;},CrossFV:function(s) -{var tX=this.x;this.x=-s*this.y;this.y=s*tX;},MinV:function(b) -{this.x=this.xb.x?this.x:b.x;this.y=this.y>b.y?this.y:b.y;},Abs:function() -{this.x=Math.abs(this.x);this.y=Math.abs(this.y);},Length:function() -{return Math.sqrt(this.x*this.x+this.y*this.y);},Normalize:function() -{var length=this.Length();if(length0.0?a:-a;};b2Math.b2AbsV=function(a) -{var b=new b2Vec2(b2Math.b2Abs(a.x),b2Math.b2Abs(a.y));return b;};b2Math.b2AbsM=function(A) -{var B=new b2Mat22(0,b2Math.b2AbsV(A.col1),b2Math.b2AbsV(A.col2));return B;};b2Math.b2Min=function(a,b) -{return ab?a:b;};b2Math.b2MaxV=function(a,b) -{var c=new b2Vec2(b2Math.b2Max(a.x,b.x),b2Math.b2Max(a.y,b.y));return c;};b2Math.b2Clamp=function(a,low,high) -{return b2Math.b2Max(low,b2Math.b2Min(a,high));};b2Math.b2ClampV=function(a,low,high) -{return b2Math.b2MaxV(low,b2Math.b2MinV(a,high));};b2Math.b2Swap=function(a,b) -{var tmp=a[0];a[0]=b[0];b[0]=tmp;};b2Math.b2Random=function() -{return Math.random()*2-1;};b2Math.b2NextPowerOfTwo=function(x) -{x|=(x>>1)&0x7FFFFFFF;x|=(x>>2)&0x3FFFFFFF;x|=(x>>4)&0x0FFFFFFF;x|=(x>>8)&0x00FFFFFF;x|=(x>>16)&0x0000FFFF;return x+1;};b2Math.b2IsPowerOfTwo=function(x) -{var result=x>0&&(x&(x-1))==0;return result;};b2Math.tempVec2=new b2Vec2();b2Math.tempVec3=new b2Vec2();b2Math.tempVec4=new b2Vec2();b2Math.tempVec5=new b2Vec2();b2Math.tempMat=new b2Mat22(); -var b2AABB=Class.create();b2AABB.prototype={IsValid:function(){var dX=this.maxVertex.x;var dY=this.maxVertex.y;dX=this.maxVertex.x;dY=this.maxVertex.y;dX-=this.minVertex.x;dY-=this.minVertex.y;var valid=dX>=0.0&&dY>=0.0;valid=valid&&this.minVertex.IsValid()&&this.maxVertex.IsValid();return valid;},minVertex:new b2Vec2(),maxVertex:new b2Vec2(),initialize:function(){this.minVertex=new b2Vec2();this.maxVertex=new b2Vec2();}}; -var b2Bound=Class.create();b2Bound.prototype={IsLower:function(){return(this.value&1)==0;},IsUpper:function(){return(this.value&1)==1;},Swap:function(b){var tempValue=this.value;var tempProxyId=this.proxyId;var tempStabbingCount=this.stabbingCount;this.value=b.value;this.proxyId=b.proxyId;this.stabbingCount=b.stabbingCount;b.value=tempValue;b.proxyId=tempProxyId;b.stabbingCount=tempStabbingCount;},value:0,proxyId:0,stabbingCount:0,initialize:function(){}} - -var b2BoundValues=Class.create();b2BoundValues.prototype={lowerValues:[0,0],upperValues:[0,0],initialize:function(){this.lowerValues=[0,0];this.upperValues=[0,0];}} - -var b2Pair=Class.create();b2Pair.prototype={SetBuffered:function(){this.status|=b2Pair.e_pairBuffered;},ClearBuffered:function(){this.status&=~b2Pair.e_pairBuffered;},IsBuffered:function(){return(this.status&b2Pair.e_pairBuffered)==b2Pair.e_pairBuffered;},SetRemoved:function(){this.status|=b2Pair.e_pairRemoved;},ClearRemoved:function(){this.status&=~b2Pair.e_pairRemoved;},IsRemoved:function(){return(this.status&b2Pair.e_pairRemoved)==b2Pair.e_pairRemoved;},SetFinal:function(){this.status|=b2Pair.e_pairFinal;},IsFinal:function(){return(this.status&b2Pair.e_pairFinal)==b2Pair.e_pairFinal;},userData:null,proxyId1:0,proxyId2:0,next:0,status:0,initialize:function(){}};b2Pair.b2_nullPair=b2Settings.USHRT_MAX;b2Pair.b2_nullProxy=b2Settings.USHRT_MAX;b2Pair.b2_tableCapacity=b2Settings.b2_maxPairs;b2Pair.b2_tableMask=b2Pair.b2_tableCapacity-1;b2Pair.e_pairBuffered=0x0001;b2Pair.e_pairRemoved=0x0002;b2Pair.e_pairFinal=0x0004; -var b2PairCallback=Class.create();b2PairCallback.prototype={PairAdded:function(proxyUserData1,proxyUserData2){return null},PairRemoved:function(proxyUserData1,proxyUserData2,pairUserData){},initialize:function(){}}; -var b2BufferedPair=Class.create();b2BufferedPair.prototype={proxyId1:0,proxyId2:0,initialize:function(){}} - -var b2PairManager=Class.create();b2PairManager.prototype={initialize:function(){var i=0;this.m_hashTable=new Array(b2Pair.b2_tableCapacity);for(i=0;iproxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} -var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;var pair=pair=this.FindHash(proxyId1,proxyId2,hash);if(pair!=null) -{return pair;} -var pIndex=this.m_freePair;pair=this.m_pairs[pIndex];this.m_freePair=pair.next;pair.proxyId1=proxyId1;pair.proxyId2=proxyId2;pair.status=0;pair.userData=null;pair.next=this.m_hashTable[hash];this.m_hashTable[hash]=pIndex;++this.m_pairCount;return pair;},RemovePair:function(proxyId1,proxyId2){if(proxyId1>proxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} -var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;var node=this.m_hashTable[hash];var pNode=null;while(node!=b2Pair.b2_nullPair) -{if(b2PairManager.Equals(this.m_pairs[node],proxyId1,proxyId2)) -{var index=node;if(pNode){pNode.next=this.m_pairs[node].next;} -else{this.m_hashTable[hash]=this.m_pairs[node].next;} -var pair=this.m_pairs[index];var userData=pair.userData;pair.next=this.m_freePair;pair.proxyId1=b2Pair.b2_nullProxy;pair.proxyId2=b2Pair.b2_nullProxy;pair.userData=null;pair.status=0;this.m_freePair=index;--this.m_pairCount;return userData;} -else -{pNode=this.m_pairs[node];node=pNode.next;}} -return null;},Find:function(proxyId1,proxyId2){if(proxyId1>proxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} -var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;return this.FindHash(proxyId1,proxyId2,hash);},FindHash:function(proxyId1,proxyId2,hash){var index=this.m_hashTable[hash];while(index!=b2Pair.b2_nullPair&&b2PairManager.Equals(this.m_pairs[index],proxyId1,proxyId2)==false) -{index=this.m_pairs[index].next;} -if(index==b2Pair.b2_nullPair) -{return null;} -return this.m_pairs[index];},ValidateBuffer:function(){},ValidateTable:function(){},m_broadPhase:null,m_callback:null,m_pairs:null,m_freePair:0,m_pairCount:0,m_pairBuffer:null,m_pairBufferCount:0,m_hashTable:null};b2PairManager.Hash=function(proxyId1,proxyId2) -{var key=((proxyId2<<16)&0xffff0000)|proxyId1;key=~key+((key<<15)&0xFFFF8000);key=key^((key>>12)&0x000fffff);key=key+((key<<2)&0xFFFFFFFC);key=key^((key>>4)&0x0fffffff);key=key*2057;key=key^((key>>16)&0x0000ffff);return key;};b2PairManager.Equals=function(pair,proxyId1,proxyId2) -{return(pair.proxyId1==proxyId1&&pair.proxyId2==proxyId2);};b2PairManager.EqualsPair=function(pair1,pair2) -{return pair1.proxyId1==pair2.proxyId1&&pair1.proxyId2==pair2.proxyId2;}; -var b2BroadPhase=Class.create();b2BroadPhase.prototype={initialize:function(worldAABB,callback){this.m_pairManager=new b2PairManager();this.m_proxyPool=new Array(b2Settings.b2_maxPairs);this.m_bounds=new Array(2*b2Settings.b2_maxProxies);this.m_queryResults=new Array(b2Settings.b2_maxProxies);this.m_quantizationFactor=new b2Vec2();var i=0;this.m_pairManager.Initialize(this,callback);this.m_worldAABB=worldAABB;this.m_proxyCount=0;for(i=0;i0&&lowerValue0) -{index=upperIndex;while(index0) -{index=lowerIndex;while(index0&&upperValuebounds[p2.upperBounds[axis]].value) -return false;if(bounds[p1.upperBounds[axis]].valuebounds[p.upperBounds[axis]].value) -return false;if(b.upperValues[axis]0) -{var i=lowerQuery-1;var s=bounds[i].stabbingCount;while(s) -{if(bounds[i].IsLower()) -{var proxy=this.m_proxyPool[bounds[i].proxyId];if(lowerQuery<=proxy.upperBounds[axis]) -{this.IncrementOverlapCount(bounds[i].proxyId);--s;}} ---i;}} -lowerQueryOut[0]=lowerQuery;upperQueryOut[0]=upperQuery;},IncrementOverlapCount:function(proxyId){var proxy=this.m_proxyPool[proxyId];if(proxy.timeStampvalue) -{high=mid-1;} -else if(bounds[mid].value0.0) -{vOut[numOut].id=vIn[0].id;} -else -{vOut[numOut].id=vIn[1].id;} -++numOut;} -return numOut;};b2Collision.EdgeSeparation=function(poly1,edge1,poly2) -{var vert1s=poly1.m_vertices;var count2=poly2.m_vertexCount;var vert2s=poly2.m_vertices;var normalX=poly1.m_normals[edge1].x;var normalY=poly1.m_normals[edge1].y;var tX=normalX;var tMat=poly1.m_R;normalX=tMat.col1.x*tX+tMat.col2.x*normalY;normalY=tMat.col1.y*tX+tMat.col2.y*normalY;var normalLocal2X=normalX;var normalLocal2Y=normalY;tMat=poly2.m_R;tX=normalLocal2X*tMat.col1.x+normalLocal2Y*tMat.col1.y;normalLocal2Y=normalLocal2X*tMat.col2.x+normalLocal2Y*tMat.col2.y;normalLocal2X=tX;var vertexIndex2=0;var minDot=Number.MAX_VALUE;for(var i=0;imaxDot) -{maxDot=dot;edge=i;}} -var s=b2Collision.EdgeSeparation(poly1,edge,poly2);if(s>0.0&&conservative==false) -{return s;} -var prevEdge=edge-1>=0?edge-1:count1-1;var sPrev=b2Collision.EdgeSeparation(poly1,prevEdge,poly2);if(sPrev>0.0&&conservative==false) -{return sPrev;} -var nextEdge=edge+10.0&&conservative==false) -{return sNext;} -var bestEdge=0;var bestSeparation;var increment=0;if(sPrev>s&&sPrev>sNext) -{increment=-1;bestEdge=prevEdge;bestSeparation=sPrev;} -else if(sNext>s) -{increment=1;bestEdge=nextEdge;bestSeparation=sNext;} -else -{edgeIndex[0]=edge;return s;} -while(true) -{if(increment==-1) -edge=bestEdge-1>=0?bestEdge-1:count1-1;else -edge=bestEdge+10.0&&conservative==false) -{return s;} -if(s>bestSeparation) -{bestEdge=edge;bestSeparation=s;} -else -{break;}} -edgeIndex[0]=bestEdge;return bestSeparation;};b2Collision.FindIncidentEdge=function(c,poly1,edge1,poly2) -{var count1=poly1.m_vertexCount;var vert1s=poly1.m_vertices;var count2=poly2.m_vertexCount;var vert2s=poly2.m_vertices;var vertex11=edge1;var vertex12=edge1+1==count1?0:edge1+1;var tVec=vert1s[vertex12];var normal1Local1X=tVec.x;var normal1Local1Y=tVec.y;tVec=vert1s[vertex11];normal1Local1X-=tVec.x;normal1Local1Y-=tVec.y;var tX=normal1Local1X;normal1Local1X=normal1Local1Y;normal1Local1Y=-tX;var invLength=1.0/Math.sqrt(normal1Local1X*normal1Local1X+normal1Local1Y*normal1Local1Y);normal1Local1X*=invLength;normal1Local1Y*=invLength;var normal1X=normal1Local1X;var normal1Y=normal1Local1Y;tX=normal1X;var tMat=poly1.m_R;normal1X=tMat.col1.x*tX+tMat.col2.x*normal1Y;normal1Y=tMat.col1.y*tX+tMat.col2.y*normal1Y;var normal1Local2X=normal1X;var normal1Local2Y=normal1Y;tMat=poly2.m_R;tX=normal1Local2X*tMat.col1.x+normal1Local2Y*tMat.col1.y;normal1Local2Y=normal1Local2X*tMat.col2.x+normal1Local2Y*tMat.col2.y;normal1Local2X=tX;var vertex21=0;var vertex22=0;var minDot=Number.MAX_VALUE;for(var i=0;i0.0&&conservative==false) -return;var edgeB=0;var edgeBOut=[edgeB];var separationB=b2Collision.FindMaxSeparation(edgeBOut,polyB,polyA,conservative);edgeB=edgeBOut[0];if(separationB>0.0&&conservative==false) -return;var poly1;var poly2;var edge1=0;var flip=0;var k_relativeTol=0.98;var k_absoluteTol=0.001;if(separationB>k_relativeTol*separationA+k_absoluteTol) -{poly1=polyB;poly2=polyA;edge1=edgeB;flip=1;} -else -{poly1=polyA;poly2=polyB;edge1=edgeA;flip=0;} -var incidentEdge=[new ClipVertex(),new ClipVertex()];b2Collision.FindIncidentEdge(incidentEdge,poly1,edge1,poly2);var count1=poly1.m_vertexCount;var vert1s=poly1.m_vertices;var v11=vert1s[edge1];var v12=edge1+1radiusSum*radiusSum&&conservative==false) -{return;} -var separation;if(distSqrradius) -{return;} -if(s>separation) -{separation=s;normalIndex=i;}} -if(separationradius) -{return;} -manifold.pointCount=1;manifold.normal.Set(tMat.col1.x*dX+tMat.col2.x*dY,tMat.col1.y*dX+tMat.col2.y*dY);tPoint=manifold.points[0];tPoint.id.features.incidentEdge=b2Collision.b2_nullFeature;tPoint.id.features.incidentVertex=vertIndex1;tPoint.id.features.referenceFace=b2Collision.b2_nullFeature;tPoint.id.features.flip=0;tPoint.position.x=circle.m_position.x-radius*manifold.normal.x;tPoint.position.y=circle.m_position.y-radius*manifold.normal.y;tPoint.separation=dist-radius;return;} -var u=(xLocalX-poly.m_vertices[vertIndex1].x)*eX+(xLocalY-poly.m_vertices[vertIndex1].y)*eY;tPoint=manifold.points[0];tPoint.id.features.incidentEdge=b2Collision.b2_nullFeature;tPoint.id.features.incidentVertex=b2Collision.b2_nullFeature;tPoint.id.features.referenceFace=b2Collision.b2_nullFeature;tPoint.id.features.flip=0;var pX,pY;if(u<=0.0) -{pX=poly.m_vertices[vertIndex1].x;pY=poly.m_vertices[vertIndex1].y;tPoint.id.features.incidentVertex=vertIndex1;} -else if(u>=length) -{pX=poly.m_vertices[vertIndex2].x;pY=poly.m_vertices[vertIndex2].y;tPoint.id.features.incidentVertex=vertIndex2;} -else -{pX=eX*u+poly.m_vertices[vertIndex1].x;pY=eY*u+poly.m_vertices[vertIndex1].y;tPoint.id.features.incidentEdge=vertIndex1;} -dX=xLocalX-pX;dY=xLocalY-pY;dist=Math.sqrt(dX*dX+dY*dY);dX/=dist;dY/=dist;if(dist>radius) -{return;} -manifold.pointCount=1;manifold.normal.Set(tMat.col1.x*dX+tMat.col2.x*dY,tMat.col1.y*dX+tMat.col2.y*dY);tPoint.position.x=circle.m_position.x-radius*manifold.normal.x;tPoint.position.y=circle.m_position.y-radius*manifold.normal.y;tPoint.separation=dist-radius;};b2Collision.b2TestOverlap=function(a,b) -{var t1=b.minVertex;var t2=a.maxVertex;var d1X=t1.x-t2.x;var d1Y=t1.y-t2.y;t1=a.minVertex;t2=b.maxVertex;var d2X=t1.x-t2.x;var d2Y=t1.y-t2.y;if(d1X>0.0||d1Y>0.0) -return false;if(d2X>0.0||d2Y>0.0) -return false;return true;}; -var Features=Class.create();Features.prototype={set_referenceFace:function(value){this._referenceFace=value;this._m_id._key=(this._m_id._key&0xffffff00)|(this._referenceFace&0x000000ff)},get_referenceFace:function(){return this._referenceFace;},_referenceFace:0,set_incidentEdge:function(value){this._incidentEdge=value;this._m_id._key=(this._m_id._key&0xffff00ff)|((this._incidentEdge<<8)&0x0000ff00)},get_incidentEdge:function(){return this._incidentEdge;},_incidentEdge:0,set_incidentVertex:function(value){this._incidentVertex=value;this._m_id._key=(this._m_id._key&0xff00ffff)|((this._incidentVertex<<16)&0x00ff0000)},get_incidentVertex:function(){return this._incidentVertex;},_incidentVertex:0,set_flip:function(value){this._flip=value;this._m_id._key=(this._m_id._key&0x00ffffff)|((this._flip<<24)&0xff000000)},get_flip:function(){return this._flip;},_flip:0,_m_id:null,initialize:function(){}}; -var b2ContactID=Class.create();b2ContactID.prototype={initialize:function(){this.features=new Features();this.features._m_id=this;},Set:function(id){this.set_key(id._key);},Copy:function(){var id=new b2ContactID();id.set_key(this._key);return id;},get_key:function(){return this._key;},set_key:function(value){this._key=value;this.features._referenceFace=this._key&0x000000ff;this.features._incidentEdge=((this._key&0x0000ff00)>>8)&0x000000ff;this.features._incidentVertex=((this._key&0x00ff0000)>>16)&0x000000ff;this.features._flip=((this._key&0xff000000)>>24)&0x000000ff;},features:new Features(),_key:0}; -var b2ContactPoint=Class.create();b2ContactPoint.prototype={position:new b2Vec2(),separation:null,normalImpulse:null,tangentImpulse:null,id:new b2ContactID(),initialize:function(){this.position=new b2Vec2();this.id=new b2ContactID();}};var b2Distance=Class.create();b2Distance.prototype={initialize:function(){}};b2Distance.ProcessTwo=function(p1Out,p2Out,p1s,p2s,points) -{var rX=-points[1].x;var rY=-points[1].y;var dX=points[0].x-points[1].x;var dY=points[0].y-points[1].y;var length=Math.sqrt(dX*dX+dY*dY);dX/=length;dY/=length;var lambda=rX*dX+rY*dY;if(lambda<=0.0||length=0.0&&ud>=0.0) -{var lambda=un/(un+ud);p1Out.x=p1s[1].x+lambda*(p1s[2].x-p1s[1].x);p1Out.y=p1s[1].y+lambda*(p1s[2].y-p1s[1].y);p2Out.x=p2s[1].x+lambda*(p2s[2].x-p2s[1].x);p2Out.y=p2s[1].y+lambda*(p2s[2].y-p2s[1].y);p1s[0].SetV(p1s[2]);p2s[0].SetV(p2s[2]);points[0].SetV(points[2]);return 2;} -var vb=n*(cX*aY-cY*aX);if(vb<=0.0&&tn>=0.0&&td>=0.0) -{var lambda=tn/(tn+td);p1Out.x=p1s[0].x+lambda*(p1s[2].x-p1s[0].x);p1Out.y=p1s[0].y+lambda*(p1s[2].y-p1s[0].y);p2Out.x=p2s[0].x+lambda*(p2s[2].x-p2s[0].x);p2Out.y=p2s[0].y+lambda*(p2s[2].y-p2s[0].y);p1s[1].SetV(p1s[2]);p2s[1].SetV(p2s[2]);points[1].SetV(points[2]);return 2;} -var denom=va+vb+vc;denom=1.0/denom;var u=va*denom;var v=vb*denom;var w=1.0-u-v;p1Out.x=u*p1s[0].x+v*p1s[1].x+w*p1s[2].x;p1Out.y=u*p1s[0].y+v*p1s[1].y+w*p1s[2].y;p2Out.x=u*p2s[0].x+v*p2s[1].x+w*p2s[2].x;p2Out.y=u*p2s[0].y+v*p2s[1].y+w*p2s[2].y;return 3;};b2Distance.InPoinsts=function(w,points,pointCount) -{for(var i=0;i0.0) -{return false;}} -return true;},initialize:function(def,body,newOrigin){this.m_R=new b2Mat22();this.m_position=new b2Vec2();this.m_userData=def.userData;this.m_friction=def.friction;this.m_restitution=def.restitution;this.m_body=body;this.m_proxyId=b2Pair.b2_nullProxy;this.m_maxRadius=0.0;this.m_categoryBits=def.categoryBits;this.m_maskBits=def.maskBits;this.m_groupIndex=def.groupIndex;this.syncAABB=new b2AABB();this.syncMat=new b2Mat22();this.m_localCentroid=new b2Vec2();this.m_localOBB=new b2OBB();var i=0;var hX;var hY;var tVec;var aabb=new b2AABB();this.m_vertices=new Array(b2Settings.b2_maxPolyVertices);this.m_coreVertices=new Array(b2Settings.b2_maxPolyVertices);this.m_normals=new Array(b2Settings.b2_maxPolyVertices);this.m_type=b2Shape.e_polyShape;var localR=new b2Mat22(def.localRotation);if(def.type==b2Shape.e_boxShape) -{this.m_localCentroid.x=def.localPosition.x-newOrigin.x;this.m_localCentroid.y=def.localPosition.y-newOrigin.y;var box=def;this.m_vertexCount=4;hX=box.extents.x;hY=box.extents.y;var hcX=Math.max(0.0,hX-2.0*b2Settings.b2_linearSlop);var hcY=Math.max(0.0,hY-2.0*b2Settings.b2_linearSlop);tVec=this.m_vertices[0]=new b2Vec2();tVec.x=localR.col1.x*hX+localR.col2.x*hY;tVec.y=localR.col1.y*hX+localR.col2.y*hY;tVec=this.m_vertices[1]=new b2Vec2();tVec.x=localR.col1.x*-hX+localR.col2.x*hY;tVec.y=localR.col1.y*-hX+localR.col2.y*hY;tVec=this.m_vertices[2]=new b2Vec2();tVec.x=localR.col1.x*-hX+localR.col2.x*-hY;tVec.y=localR.col1.y*-hX+localR.col2.y*-hY;tVec=this.m_vertices[3]=new b2Vec2();tVec.x=localR.col1.x*hX+localR.col2.x*-hY;tVec.y=localR.col1.y*hX+localR.col2.y*-hY;tVec=this.m_coreVertices[0]=new b2Vec2();tVec.x=localR.col1.x*hcX+localR.col2.x*hcY;tVec.y=localR.col1.y*hcX+localR.col2.y*hcY;tVec=this.m_coreVertices[1]=new b2Vec2();tVec.x=localR.col1.x*-hcX+localR.col2.x*hcY;tVec.y=localR.col1.y*-hcX+localR.col2.y*hcY;tVec=this.m_coreVertices[2]=new b2Vec2();tVec.x=localR.col1.x*-hcX+localR.col2.x*-hcY;tVec.y=localR.col1.y*-hcX+localR.col2.y*-hcY;tVec=this.m_coreVertices[3]=new b2Vec2();tVec.x=localR.col1.x*hcX+localR.col2.x*-hcY;tVec.y=localR.col1.y*hcX+localR.col2.y*-hcY;} -else -{var poly=def;this.m_vertexCount=poly.vertexCount;b2Shape.PolyCentroid(poly.vertices,poly.vertexCount,b2PolyShape.tempVec);var centroidX=b2PolyShape.tempVec.x;var centroidY=b2PolyShape.tempVec.y;this.m_localCentroid.x=def.localPosition.x+(localR.col1.x*centroidX+localR.col2.x*centroidY)-newOrigin.x;this.m_localCentroid.y=def.localPosition.y+(localR.col1.y*centroidX+localR.col2.y*centroidY)-newOrigin.y;for(i=0;iNumber.MIN_VALUE) -{uX*=1.0/length;uY*=1.0/length;} -this.m_coreVertices[i].x=this.m_vertices[i].x-2.0*b2Settings.b2_linearSlop*uX;this.m_coreVertices[i].y=this.m_vertices[i].y-2.0*b2Settings.b2_linearSlop*uY;}} -var minVertexX=Number.MAX_VALUE;var minVertexY=Number.MAX_VALUE;var maxVertexX=-Number.MAX_VALUE;var maxVertexY=-Number.MAX_VALUE;this.m_maxRadius=0.0;for(i=0;ibestValue) -{bestIndex=i;bestValue=value;}} -out.Set(this.m_position.x+(this.m_R.col1.x*this.m_coreVertices[bestIndex].x+this.m_R.col2.x*this.m_coreVertices[bestIndex].y),this.m_position.y+(this.m_R.col1.y*this.m_coreVertices[bestIndex].x+this.m_R.col2.y*this.m_coreVertices[bestIndex].y));},m_localCentroid:new b2Vec2(),m_localOBB:new b2OBB(),m_vertices:null,m_coreVertices:null,m_vertexCount:0,m_normals:null});b2PolyShape.tempVec=new b2Vec2();b2PolyShape.tAbsR=new b2Mat22(); -var b2Body=Class.create();b2Body.prototype={SetOriginPosition:function(position,rotation){if(this.IsFrozen()) -{return;} -this.m_rotation=rotation;this.m_R.Set(this.m_rotation);this.m_position=b2Math.AddVV(position,b2Math.b2MulMV(this.m_R,this.m_center));this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;for(var s=this.m_shapeList;s!=null;s=s.m_next) -{s.Synchronize(this.m_position,this.m_R,this.m_position,this.m_R);} -this.m_world.m_broadPhase.Commit();},GetOriginPosition:function(){return b2Math.SubtractVV(this.m_position,b2Math.b2MulMV(this.m_R,this.m_center));},SetCenterPosition:function(position,rotation){if(this.IsFrozen()) -{return;} -this.m_rotation=rotation;this.m_R.Set(this.m_rotation);this.m_position.SetV(position);this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;for(var s=this.m_shapeList;s!=null;s=s.m_next) -{s.Synchronize(this.m_position,this.m_R,this.m_position,this.m_R);} -this.m_world.m_broadPhase.Commit();},GetCenterPosition:function(){return this.m_position;},GetRotation:function(){return this.m_rotation;},GetRotationMatrix:function(){return this.m_R;},SetLinearVelocity:function(v){this.m_linearVelocity.SetV(v);},GetLinearVelocity:function(){return this.m_linearVelocity;},SetAngularVelocity:function(w){this.m_angularVelocity=w;},GetAngularVelocity:function(){return this.m_angularVelocity;},ApplyForce:function(force,point) -{if(this.IsSleeping()==false) -{this.m_force.Add(force);this.m_torque+=b2Math.b2CrossVV(b2Math.SubtractVV(point,this.m_position),force);}},ApplyTorque:function(torque) -{if(this.IsSleeping()==false) -{this.m_torque+=torque;}},ApplyImpulse:function(impulse,point) -{if(this.IsSleeping()==false) -{this.m_linearVelocity.Add(b2Math.MulFV(this.m_invMass,impulse));this.m_angularVelocity+=(this.m_invI*b2Math.b2CrossVV(b2Math.SubtractVV(point,this.m_position),impulse));}},GetMass:function(){return this.m_mass;},GetInertia:function(){return this.m_I;},GetWorldPoint:function(localPoint){return b2Math.AddVV(this.m_position,b2Math.b2MulMV(this.m_R,localPoint));},GetWorldVector:function(localVector){return b2Math.b2MulMV(this.m_R,localVector);},GetLocalPoint:function(worldPoint){return b2Math.b2MulTMV(this.m_R,b2Math.SubtractVV(worldPoint,this.m_position));},GetLocalVector:function(worldVector){return b2Math.b2MulTMV(this.m_R,worldVector);},IsStatic:function(){return(this.m_flags&b2Body.e_staticFlag)==b2Body.e_staticFlag;},IsFrozen:function() -{return(this.m_flags&b2Body.e_frozenFlag)==b2Body.e_frozenFlag;},IsSleeping:function(){return(this.m_flags&b2Body.e_sleepFlag)==b2Body.e_sleepFlag;},AllowSleeping:function(flag) -{if(flag) -{this.m_flags|=b2Body.e_allowSleepFlag;} -else -{this.m_flags&=~b2Body.e_allowSleepFlag;this.WakeUp();}},WakeUp:function(){this.m_flags&=~b2Body.e_sleepFlag;this.m_sleepTime=0.0;},GetShapeList:function(){return this.m_shapeList;},GetContactList:function() -{return this.m_contactList;},GetJointList:function() -{return this.m_jointList;},GetNext:function(){return this.m_next;},GetUserData:function(){return this.m_userData;},initialize:function(bd,world){this.sMat0=new b2Mat22();this.m_position=new b2Vec2();this.m_R=new b2Mat22(0);this.m_position0=new b2Vec2();var i=0;var sd;var massData;this.m_flags=0;this.m_position.SetV(bd.position);this.m_rotation=bd.rotation;this.m_R.Set(this.m_rotation);this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;this.m_world=world;this.m_linearDamping=b2Math.b2Clamp(1.0-bd.linearDamping,0.0,1.0);this.m_angularDamping=b2Math.b2Clamp(1.0-bd.angularDamping,0.0,1.0);this.m_force=new b2Vec2(0.0,0.0);this.m_torque=0.0;this.m_mass=0.0;var massDatas=new Array(b2Settings.b2_maxShapesPerBody);for(i=0;i0.0) -{this.m_center.Multiply(1.0/this.m_mass);this.m_position.Add(b2Math.b2MulMV(this.m_R,this.m_center));} -else -{this.m_flags|=b2Body.e_staticFlag;} -this.m_I=0.0;for(i=0;i0.0) -{this.m_invMass=1.0/this.m_mass;} -else -{this.m_invMass=0.0;} -if(this.m_I>0.0&&bd.preventRotation==false) -{this.m_invI=1.0/this.m_I;} -else -{this.m_I=0.0;this.m_invI=0.0;} -this.m_linearVelocity=b2Math.AddVV(bd.linearVelocity,b2Math.b2CrossFV(bd.angularVelocity,this.m_center));this.m_angularVelocity=bd.angularVelocity;this.m_jointList=null;this.m_contactList=null;this.m_prev=null;this.m_next=null;this.m_shapeList=null;for(i=0;i0;} -var collide=(shape1.m_maskBits&shape2.m_categoryBits)!=0&&(shape1.m_categoryBits&shape2.m_maskBits)!=0;return collide;},initialize:function(){}};b2CollisionFilter.b2_defaultFilter=new b2CollisionFilter; -var b2Island=Class.create();b2Island.prototype={initialize:function(bodyCapacity,contactCapacity,jointCapacity,allocator) -{var i=0;this.m_bodyCapacity=bodyCapacity;this.m_contactCapacity=contactCapacity;this.m_jointCapacity=jointCapacity;this.m_bodyCount=0;this.m_contactCount=0;this.m_jointCount=0;this.m_bodies=new Array(bodyCapacity);for(i=0;iangTolSqr||b2Math.b2Dot(b.m_linearVelocity,b.m_linearVelocity)>linTolSqr) -{b.m_sleepTime=0.0;minSleepTime=0.0;} -else -{b.m_sleepTime+=dt;minSleepTime=b2Math.b2Min(minSleepTime,b.m_sleepTime);}} -if(minSleepTime>=b2Settings.b2_timeToSleep) -{for(i=0;i0) -{contact.m_shape1.m_body.WakeUp();contact.m_shape2.m_body.WakeUp();} -var type1=contact.m_shape1.m_type;var type2=contact.m_shape2.m_type;var destroyFcn=b2Contact.s_registers[type1][type2].destroyFcn;destroyFcn(contact,allocator);};b2Contact.s_registers=null;b2Contact.s_initialized=false; -var b2ContactConstraint=Class.create();b2ContactConstraint.prototype={initialize:function(){this.normal=new b2Vec2();this.points=new Array(b2Settings.b2_maxManifoldPoints);for(var i=0;i0.0) -{ccp.velocityBias=-60.0*ccp.separation;} -var tX=v2X+(-w2*r2Y)-v1X-(-w1*r1Y);var tY=v2Y+(w2*r2X)-v1Y-(w1*r1X);var vRel=c.normal.x*tX+c.normal.y*tY;if(vRel<-b2Settings.b2_velocityThreshold) -{ccp.velocityBias+=-c.restitution*vRel;}} -++count;}}},PreSolve:function(){var tVec;var tVec2;var tMat;for(var i=0;i=-b2Settings.b2_linearSlop;},PostSolve:function(){for(var i=0;i0) -{this.m_manifoldCount=1;} -else -{this.m_manifoldCount=0;}},GetManifolds:function() -{return this.m_manifold;},m_manifold:[new b2Manifold()]});b2CircleContact.Create=function(shape1,shape2,allocator){return new b2CircleContact(shape1,shape2);};b2CircleContact.Destroy=function(contact,allocator){}; -var b2Conservative=Class.create();b2Conservative.prototype={initialize:function(){}} -b2Conservative.R1=new b2Mat22();b2Conservative.R2=new b2Mat22();b2Conservative.x1=new b2Vec2();b2Conservative.x2=new b2Vec2();b2Conservative.Conservative=function(shape1,shape2){var body1=shape1.GetBody();var body2=shape2.GetBody();var v1X=body1.m_position.x-body1.m_position0.x;var v1Y=body1.m_position.y-body1.m_position0.y;var omega1=body1.m_rotation-body1.m_rotation0;var v2X=body2.m_position.x-body2.m_position0.x;var v2Y=body2.m_position.y-body2.m_position0.y;var omega2=body2.m_rotation-body2.m_rotation0;var r1=shape1.GetMaxRadius();var r2=shape2.GetMaxRadius();var p1StartX=body1.m_position0.x;var p1StartY=body1.m_position0.y;var a1Start=body1.m_rotation0;var p2StartX=body2.m_position0.x;var p2StartY=body2.m_position0.y;var a2Start=body2.m_rotation0;var p1X=p1StartX;var p1Y=p1StartY;var a1=a1Start;var p2X=p2StartX;var p2Y=p2StartY;var a2=a2Start;b2Conservative.R1.Set(a1);b2Conservative.R2.Set(a2);shape1.QuickSync(p1,b2Conservative.R1);shape2.QuickSync(p2,b2Conservative.R2);var s1=0.0;var maxIterations=10;var dX;var dY;var invRelativeVelocity=0.0;var hit=true;for(var iter=0;iterFLT_EPSILON) -{d*=b2_linearSlop/length;} -if(body1.IsStatic()) -{body1.m_position.x=p1X;body1.m_position.y=p1Y;} -else -{body1.m_position.x=p1X-dX;body1.m_position.y=p1Y-dY;} -body1.m_rotation=a1;body1.m_R.Set(a1);body1.QuickSyncShapes();if(body2.IsStatic()) -{body2.m_position.x=p2X;body2.m_position.y=p2Y;} -else -{body2.m_position.x=p2X+dX;body2.m_position.y=p2Y+dY;} -body2.m_position.x=p2X+dX;body2.m_position.y=p2Y+dY;body2.m_rotation=a2;body2.m_R.Set(a2);body2.QuickSyncShapes();return true;} -shape1.QuickSync(body1.m_position,body1.m_R);shape2.QuickSync(body2.m_position,body2.m_R);return false;}; -var b2NullContact=Class.create();Object.extend(b2NullContact.prototype,b2Contact.prototype);Object.extend(b2NullContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} -this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;},Evaluate:function(){},GetManifolds:function(){return null;}}); -var b2PolyAndCircleContact=Class.create();Object.extend(b2PolyAndCircleContact.prototype,b2Contact.prototype);Object.extend(b2PolyAndCircleContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} -this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;this.m_manifold=[new b2Manifold()];b2Settings.b2Assert(this.m_shape1.m_type==b2Shape.e_polyShape);b2Settings.b2Assert(this.m_shape2.m_type==b2Shape.e_circleShape);this.m_manifold[0].pointCount=0;this.m_manifold[0].points[0].normalImpulse=0.0;this.m_manifold[0].points[0].tangentImpulse=0.0;},Evaluate:function(){b2Collision.b2CollidePolyAndCircle(this.m_manifold[0],this.m_shape1,this.m_shape2,false);if(this.m_manifold[0].pointCount>0) -{this.m_manifoldCount=1;} -else -{this.m_manifoldCount=0;}},GetManifolds:function() -{return this.m_manifold;},m_manifold:[new b2Manifold()]}) -b2PolyAndCircleContact.Create=function(shape1,shape2,allocator){return new b2PolyAndCircleContact(shape1,shape2);};b2PolyAndCircleContact.Destroy=function(contact,allocator){}; -var b2PolyContact=Class.create();Object.extend(b2PolyContact.prototype,b2Contact.prototype);Object.extend(b2PolyContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} -this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;this.m0=new b2Manifold();this.m_manifold=[new b2Manifold()];this.m_manifold[0].pointCount=0;},m0:new b2Manifold(),Evaluate:function(){var tMani=this.m_manifold[0];var tPoints=this.m0.points;for(var k=0;k0) -{var match=[false,false];for(var i=0;i0) -{var body1=c.m_shape1.m_body;var body2=c.m_shape2.m_body;var node1=c.m_node1;var node2=c.m_node2;body1.WakeUp();body2.WakeUp();if(node1.prev) -{node1.prev.next=node1.next;} -if(node1.next) -{node1.next.prev=node1.prev;} -if(node1==body1.m_contactList) -{body1.m_contactList=node1.next;} -node1.prev=null;node1.next=null;if(node2.prev) -{node2.prev.next=node2.next;} -if(node2.next) -{node2.next.prev=node2.prev;} -if(node2==body2.m_contactList) -{body2.m_contactList=node2.next;} -node2.prev=null;node2.next=null;} -b2Contact.Destroy(c,this.m_world.m_blockAllocator);--this.m_world.m_contactCount;},CleanContactList:function() -{var c=this.m_world.m_contactList;while(c!=null) -{var c0=c;c=c.m_next;if(c0.m_flags&b2Contact.e_destroyFlag) -{this.DestroyContact(c0);c0=null;}}},Collide:function() -{var body1;var body2;var node1;var node2;for(var c=this.m_world.m_contactList;c!=null;c=c.m_next) -{if(c.m_shape1.m_body.IsSleeping()&&c.m_shape2.m_body.IsSleeping()) -{continue;} -var oldCount=c.GetManifoldCount();c.Evaluate();var newCount=c.GetManifoldCount();if(oldCount==0&&newCount>0) -{body1=c.m_shape1.m_body;body2=c.m_shape2.m_body;node1=c.m_node1;node2=c.m_node2;node1.contact=c;node1.other=body2;node1.prev=null;node1.next=body1.m_contactList;if(node1.next!=null) -{node1.next.prev=c.m_node1;} -body1.m_contactList=c.m_node1;node2.contact=c;node2.other=body1;node2.prev=null;node2.next=body2.m_contactList;if(node2.next!=null) -{node2.next.prev=node2;} -body2.m_contactList=node2;} -else if(oldCount>0&&newCount==0) -{body1=c.m_shape1.m_body;body2=c.m_shape2.m_body;node1=c.m_node1;node2=c.m_node2;if(node1.prev) -{node1.prev.next=node1.next;} -if(node1.next) -{node1.next.prev=node1.prev;} -if(node1==body1.m_contactList) -{body1.m_contactList=node1.next;} -node1.prev=null;node1.next=null;if(node2.prev) -{node2.prev.next=node2.next;} -if(node2.next) -{node2.next.prev=node2.prev;} -if(node2==body2.m_contactList) -{body2.m_contactList=node2.next;} -node2.prev=null;node2.next=null;}}},m_world:null,m_nullContact:new b2NullContact(),m_destroyImmediate:null}); -var b2World=Class.create();b2World.prototype={initialize:function(worldAABB,gravity,doSleep){this.step=new b2TimeStep();this.m_contactManager=new b2ContactManager();this.m_listener=null;this.m_filter=b2CollisionFilter.b2_defaultFilter;this.m_bodyList=null;this.m_contactList=null;this.m_jointList=null;this.m_bodyCount=0;this.m_contactCount=0;this.m_jointCount=0;this.m_bodyDestroyList=null;this.m_allowSleep=doSleep;this.m_gravity=gravity;this.m_contactManager.m_world=this;this.m_broadPhase=new b2BroadPhase(worldAABB,this.m_contactManager);var bd=new b2BodyDef();this.m_groundBody=this.CreateBody(bd);},SetListener:function(listener){this.m_listener=listener;},SetFilter:function(filter){this.m_filter=filter;},CreateBody:function(def){var b=new b2Body(def,this);b.m_prev=null;b.m_next=this.m_bodyList;if(this.m_bodyList) -{this.m_bodyList.m_prev=b;} -this.m_bodyList=b;++this.m_bodyCount;return b;},DestroyBody:function(b) -{if(b.m_flags&b2Body.e_destroyFlag) -{return;} -if(b.m_prev) -{b.m_prev.m_next=b.m_next;} -if(b.m_next) -{b.m_next.m_prev=b.m_prev;} -if(b==this.m_bodyList) -{this.m_bodyList=b.m_next;} -b.m_flags|=b2Body.e_destroyFlag;--this.m_bodyCount;b.m_prev=null;b.m_next=this.m_bodyDestroyList;this.m_bodyDestroyList=b;},CleanBodyList:function() -{this.m_contactManager.m_destroyImmediate=true;var b=this.m_bodyDestroyList;while(b) -{var b0=b;b=b.m_next;var jn=b0.m_jointList;while(jn) -{var jn0=jn;jn=jn.next;if(this.m_listener) -{this.m_listener.NotifyJointDestroyed(jn0.joint);} -this.DestroyJoint(jn0.joint);} -b0.Destroy();} -this.m_bodyDestroyList=null;this.m_contactManager.m_destroyImmediate=false;},CreateJoint:function(def){var j=b2Joint.Create(def,this.m_blockAllocator);j.m_prev=null;j.m_next=this.m_jointList;if(this.m_jointList) -{this.m_jointList.m_prev=j;} -this.m_jointList=j;++this.m_jointCount;j.m_node1.joint=j;j.m_node1.other=j.m_body2;j.m_node1.prev=null;j.m_node1.next=j.m_body1.m_jointList;if(j.m_body1.m_jointList)j.m_body1.m_jointList.prev=j.m_node1;j.m_body1.m_jointList=j.m_node1;j.m_node2.joint=j;j.m_node2.other=j.m_body1;j.m_node2.prev=null;j.m_node2.next=j.m_body2.m_jointList;if(j.m_body2.m_jointList)j.m_body2.m_jointList.prev=j.m_node2;j.m_body2.m_jointList=j.m_node2;if(def.collideConnected==false) -{var b=def.body1.m_shapeCount0.0) -{this.step.inv_dt=1.0/dt;} -else -{this.step.inv_dt=0.0;} -this.m_positionIterationCount=0;this.m_contactManager.CleanContactList();this.CleanBodyList();this.m_contactManager.Collide();var island=new b2Island(this.m_bodyCount,this.m_contactCount,this.m_jointCount,this.m_stackAllocator);for(b=this.m_bodyList;b!=null;b=b.m_next) -{b.m_flags&=~b2Body.e_islandFlag;} -for(var c=this.m_contactList;c!=null;c=c.m_next) -{c.m_flags&=~b2Contact.e_islandFlag;} -for(var j=this.m_jointList;j!=null;j=j.m_next) -{j.m_islandFlag=false;} -var stackSize=this.m_bodyCount;var stack=new Array(this.m_bodyCount);for(var k=0;k0) -{b=stack[--stackCount];island.AddBody(b);b.m_flags&=~b2Body.e_sleepFlag;if(b.m_flags&b2Body.e_staticFlag) -{continue;} -for(var cn=b.m_contactList;cn!=null;cn=cn.next) -{if(cn.contact.m_flags&b2Contact.e_islandFlag) -{continue;} -island.AddContact(cn.contact);cn.contact.m_flags|=b2Contact.e_islandFlag;other=cn.other;if(other.m_flags&b2Body.e_islandFlag) -{continue;} -stack[stackCount++]=other;other.m_flags|=b2Body.e_islandFlag;} -for(var jn=b.m_jointList;jn!=null;jn=jn.next) -{if(jn.joint.m_islandFlag==true) -{continue;} -island.AddJoint(jn.joint);jn.joint.m_islandFlag=true;other=jn.other;if(other.m_flags&b2Body.e_islandFlag) -{continue;} -stack[stackCount++]=other;other.m_flags|=b2Body.e_islandFlag;}} -island.Solve(this.step,this.m_gravity);this.m_positionIterationCount=b2Math.b2Max(this.m_positionIterationCount,b2Island.m_positionIterationCount);if(this.m_allowSleep) -{island.UpdateSleep(dt);} -for(var i=0;ib2Settings.b2_linearSlop) -{this.m_u.Multiply(1.0/length);} -else -{this.m_u.SetZero();} -var cr1u=(r1X*this.m_u.y-r1Y*this.m_u.x);var cr2u=(r2X*this.m_u.y-r2Y*this.m_u.x);this.m_mass=this.m_body1.m_invMass+this.m_body1.m_invI*cr1u*cr1u+this.m_body2.m_invMass+this.m_body2.m_invI*cr2u*cr2u;this.m_mass=1.0/this.m_mass;if(b2World.s_enableWarmStarting) -{var PX=this.m_impulse*this.m_u.x;var PY=this.m_impulse*this.m_u.y;this.m_body1.m_linearVelocity.x-=this.m_body1.m_invMass*PX;this.m_body1.m_linearVelocity.y-=this.m_body1.m_invMass*PY;this.m_body1.m_angularVelocity-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_linearVelocity.x+=this.m_body2.m_invMass*PX;this.m_body2.m_linearVelocity.y+=this.m_body2.m_invMass*PY;this.m_body2.m_angularVelocity+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);} -else -{this.m_impulse=0.0;}},SolveVelocityConstraints:function(step){var tMat;tMat=this.m_body1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=this.m_body2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var v1X=this.m_body1.m_linearVelocity.x+(-this.m_body1.m_angularVelocity*r1Y);var v1Y=this.m_body1.m_linearVelocity.y+(this.m_body1.m_angularVelocity*r1X);var v2X=this.m_body2.m_linearVelocity.x+(-this.m_body2.m_angularVelocity*r2Y);var v2Y=this.m_body2.m_linearVelocity.y+(this.m_body2.m_angularVelocity*r2X);var Cdot=(this.m_u.x*(v2X-v1X)+this.m_u.y*(v2Y-v1Y));var impulse=-this.m_mass*Cdot;this.m_impulse+=impulse;var PX=impulse*this.m_u.x;var PY=impulse*this.m_u.y;this.m_body1.m_linearVelocity.x-=this.m_body1.m_invMass*PX;this.m_body1.m_linearVelocity.y-=this.m_body1.m_invMass*PY;this.m_body1.m_angularVelocity-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_linearVelocity.x+=this.m_body2.m_invMass*PX;this.m_body2.m_linearVelocity.y+=this.m_body2.m_invMass*PY;this.m_body2.m_angularVelocity+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);},SolvePositionConstraints:function(){var tMat;tMat=this.m_body1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=this.m_body2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var dX=this.m_body2.m_position.x+r2X-this.m_body1.m_position.x-r1X;var dY=this.m_body2.m_position.y+r2Y-this.m_body1.m_position.y-r1Y;var length=Math.sqrt(dX*dX+dY*dY);dX/=length;dY/=length;var C=length-this.m_length;C=b2Math.b2Clamp(C,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);var impulse=-this.m_mass*C;this.m_u.Set(dX,dY);var PX=impulse*this.m_u.x;var PY=impulse*this.m_u.y;this.m_body1.m_position.x-=this.m_body1.m_invMass*PX;this.m_body1.m_position.y-=this.m_body1.m_invMass*PY;this.m_body1.m_rotation-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_position.x+=this.m_body2.m_invMass*PX;this.m_body2.m_position.y+=this.m_body2.m_invMass*PY;this.m_body2.m_rotation+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);this.m_body1.m_R.Set(this.m_body1.m_rotation);this.m_body2.m_R.Set(this.m_body2.m_rotation);return b2Math.b2Abs(C)step.dt*this.m_maxForce) -{this.m_impulse.Multiply(step.dt*this.m_maxForce/length);} -impulseX=this.m_impulse.x-oldImpulseX;impulseY=this.m_impulse.y-oldImpulseY;body.m_linearVelocity.x+=body.m_invMass*impulseX;body.m_linearVelocity.y+=body.m_invMass*impulseY;body.m_angularVelocity+=body.m_invI*(rX*impulseY-rY*impulseX);},SolvePositionConstraints:function(){return true;},m_localAnchor:new b2Vec2(),m_target:new b2Vec2(),m_impulse:new b2Vec2(),m_ptpMass:new b2Mat22(),m_C:new b2Vec2(),m_maxForce:null,m_beta:null,m_gamma:null}); -var b2MouseJointDef=Class.create();Object.extend(b2MouseJointDef.prototype,b2JointDef.prototype);Object.extend(b2MouseJointDef.prototype,{initialize:function() -{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.target=new b2Vec2();this.type=b2Joint.e_mouseJoint;this.maxForce=0.0;this.frequencyHz=5.0;this.dampingRatio=0.7;this.timeStep=1.0/60.0;},target:new b2Vec2(),maxForce:null,frequencyHz:null,dampingRatio:null,timeStep:null}); -var b2PrismaticJoint=Class.create();Object.extend(b2PrismaticJoint.prototype,b2Joint.prototype);Object.extend(b2PrismaticJoint.prototype,{GetAnchor1:function(){var b1=this.m_body1;var tVec=new b2Vec2();tVec.SetV(this.m_localAnchor1);tVec.MulM(b1.m_R);tVec.Add(b1.m_position);return tVec;},GetAnchor2:function(){var b2=this.m_body2;var tVec=new b2Vec2();tVec.SetV(this.m_localAnchor2);tVec.MulM(b2.m_R);tVec.Add(b2.m_position);return tVec;},GetJointTranslation:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var translation=ax1X*dX+ax1Y*dY;return translation;},GetJointSpeed:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var v1=b1.m_linearVelocity;var v2=b2.m_linearVelocity;var w1=b1.m_angularVelocity;var w2=b2.m_angularVelocity;var speed=(dX*(-w1*ax1Y)+dY*(w1*ax1X))+(ax1X*(((v2.x+(-w2*r2Y))-v1.x)-(-w1*r1Y))+ax1Y*(((v2.y+(w2*r2X))-v1.y)-(w1*r1X)));return speed;},GetMotorForce:function(invTimeStep){return invTimeStep*this.m_motorImpulse;},SetMotorSpeed:function(speed) -{this.m_motorSpeed=speed;},SetMotorForce:function(force) -{this.m_maxMotorForce=force;},GetReactionForce:function(invTimeStep) -{var tImp=invTimeStep*this.m_limitImpulse;var tMat;tMat=this.m_body1.m_R;var ax1X=tImp*(tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y);var ax1Y=tImp*(tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y);var ay1X=tImp*(tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y);var ay1Y=tImp*(tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y);return new b2Vec2(ax1X+ay1X,ax1Y+ay1Y);},GetReactionTorque:function(invTimeStep) -{return invTimeStep*this.m_angularImpulse;},initialize:function(def){this.m_node1=new b2JointNode();this.m_node2=new b2JointNode();this.m_type=def.type;this.m_prev=null;this.m_next=null;this.m_body1=def.body1;this.m_body2=def.body2;this.m_collideConnected=def.collideConnected;this.m_islandFlag=false;this.m_userData=def.userData;this.m_localAnchor1=new b2Vec2();this.m_localAnchor2=new b2Vec2();this.m_localXAxis1=new b2Vec2();this.m_localYAxis1=new b2Vec2();this.m_linearJacobian=new b2Jacobian();this.m_motorJacobian=new b2Jacobian();var tMat;var tX;var tY;tMat=this.m_body1.m_R;tX=(def.anchorPoint.x-this.m_body1.m_position.x);tY=(def.anchorPoint.y-this.m_body1.m_position.y);this.m_localAnchor1.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));tMat=this.m_body2.m_R;tX=(def.anchorPoint.x-this.m_body2.m_position.x);tY=(def.anchorPoint.y-this.m_body2.m_position.y);this.m_localAnchor2.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));tMat=this.m_body1.m_R;tX=def.axis.x;tY=def.axis.y;this.m_localXAxis1.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));this.m_localYAxis1.x=-this.m_localXAxis1.y;this.m_localYAxis1.y=this.m_localXAxis1.x;this.m_initialAngle=this.m_body2.m_rotation-this.m_body1.m_rotation;this.m_linearJacobian.SetZero();this.m_linearMass=0.0;this.m_linearImpulse=0.0;this.m_angularMass=0.0;this.m_angularImpulse=0.0;this.m_motorJacobian.SetZero();this.m_motorMass=0.0;this.m_motorImpulse=0.0;this.m_limitImpulse=0.0;this.m_limitPositionImpulse=0.0;this.m_lowerTranslation=def.lowerTranslation;this.m_upperTranslation=def.upperTranslation;this.m_maxMotorForce=def.motorForce;this.m_motorSpeed=def.motorSpeed;this.m_enableLimit=def.enableLimit;this.m_enableMotor=def.enableMotor;},PrepareVelocitySolver:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;tMat=b1.m_R;var ay1X=tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y;var ay1Y=tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y;var eX=b2.m_position.x+r2X-b1.m_position.x;var eY=b2.m_position.y+r2Y-b1.m_position.y;this.m_linearJacobian.linear1.x=-ay1X;this.m_linearJacobian.linear1.y=-ay1Y;this.m_linearJacobian.linear2.x=ay1X;this.m_linearJacobian.linear2.y=ay1Y;this.m_linearJacobian.angular1=-(eX*ay1Y-eY*ay1X);this.m_linearJacobian.angular2=r2X*ay1Y-r2Y*ay1X;this.m_linearMass=invMass1+invI1*this.m_linearJacobian.angular1*this.m_linearJacobian.angular1+ -invMass2+invI2*this.m_linearJacobian.angular2*this.m_linearJacobian.angular2;this.m_linearMass=1.0/this.m_linearMass;this.m_angularMass=1.0/(invI1+invI2);if(this.m_enableLimit||this.m_enableMotor) -{tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;this.m_motorJacobian.linear1.x=-ax1X;this.m_motorJacobian.linear1.y=-ax1Y;this.m_motorJacobian.linear2.x=ax1X;this.m_motorJacobian.linear2.y=ax1Y;this.m_motorJacobian.angular1=-(eX*ax1Y-eY*ax1X);this.m_motorJacobian.angular2=r2X*ax1Y-r2Y*ax1X;this.m_motorMass=invMass1+invI1*this.m_motorJacobian.angular1*this.m_motorJacobian.angular1+ -invMass2+invI2*this.m_motorJacobian.angular2*this.m_motorJacobian.angular2;this.m_motorMass=1.0/this.m_motorMass;if(this.m_enableLimit) -{var dX=eX-r1X;var dY=eY-r1Y;var jointTranslation=ax1X*dX+ax1Y*dY;if(b2Math.b2Abs(this.m_upperTranslation-this.m_lowerTranslation)<2.0*b2Settings.b2_linearSlop) -{this.m_limitState=b2Joint.e_equalLimits;} -else if(jointTranslation<=this.m_lowerTranslation) -{if(this.m_limitState!=b2Joint.e_atLowerLimit) -{this.m_limitImpulse=0.0;} -this.m_limitState=b2Joint.e_atLowerLimit;} -else if(jointTranslation>=this.m_upperTranslation) -{if(this.m_limitState!=b2Joint.e_atUpperLimit) -{this.m_limitImpulse=0.0;} -this.m_limitState=b2Joint.e_atUpperLimit;} -else -{this.m_limitState=b2Joint.e_inactiveLimit;this.m_limitImpulse=0.0;}}} -if(this.m_enableMotor==false) -{this.m_motorImpulse=0.0;} -if(this.m_enableLimit==false) -{this.m_limitImpulse=0.0;} -if(b2World.s_enableWarmStarting) -{var P1X=this.m_linearImpulse*this.m_linearJacobian.linear1.x+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear1.x;var P1Y=this.m_linearImpulse*this.m_linearJacobian.linear1.y+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear1.y;var P2X=this.m_linearImpulse*this.m_linearJacobian.linear2.x+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear2.x;var P2Y=this.m_linearImpulse*this.m_linearJacobian.linear2.y+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear2.y;var L1=this.m_linearImpulse*this.m_linearJacobian.angular1-this.m_angularImpulse+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.angular1;var L2=this.m_linearImpulse*this.m_linearJacobian.angular2+this.m_angularImpulse+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.angular2;b1.m_linearVelocity.x+=invMass1*P1X;b1.m_linearVelocity.y+=invMass1*P1Y;b1.m_angularVelocity+=invI1*L1;b2.m_linearVelocity.x+=invMass2*P2X;b2.m_linearVelocity.y+=invMass2*P2Y;b2.m_angularVelocity+=invI2*L2;} -else -{this.m_linearImpulse=0.0;this.m_angularImpulse=0.0;this.m_limitImpulse=0.0;this.m_motorImpulse=0.0;} -this.m_limitPositionImpulse=0.0;},SolveVelocityConstraints:function(step){var b1=this.m_body1;var b2=this.m_body2;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;var oldLimitImpulse;var linearCdot=this.m_linearJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity);var linearImpulse=-this.m_linearMass*linearCdot;this.m_linearImpulse+=linearImpulse;b1.m_linearVelocity.x+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.y;b1.m_angularVelocity+=invI1*linearImpulse*this.m_linearJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.y;b2.m_angularVelocity+=invI2*linearImpulse*this.m_linearJacobian.angular2;var angularCdot=b2.m_angularVelocity-b1.m_angularVelocity;var angularImpulse=-this.m_angularMass*angularCdot;this.m_angularImpulse+=angularImpulse;b1.m_angularVelocity-=invI1*angularImpulse;b2.m_angularVelocity+=invI2*angularImpulse;if(this.m_enableMotor&&this.m_limitState!=b2Joint.e_equalLimits) -{var motorCdot=this.m_motorJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity)-this.m_motorSpeed;var motorImpulse=-this.m_motorMass*motorCdot;var oldMotorImpulse=this.m_motorImpulse;this.m_motorImpulse=b2Math.b2Clamp(this.m_motorImpulse+motorImpulse,-step.dt*this.m_maxMotorForce,step.dt*this.m_maxMotorForce);motorImpulse=this.m_motorImpulse-oldMotorImpulse;b1.m_linearVelocity.x+=(invMass1*motorImpulse)*this.m_motorJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*motorImpulse)*this.m_motorJacobian.linear1.y;b1.m_angularVelocity+=invI1*motorImpulse*this.m_motorJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*motorImpulse)*this.m_motorJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*motorImpulse)*this.m_motorJacobian.linear2.y;b2.m_angularVelocity+=invI2*motorImpulse*this.m_motorJacobian.angular2;} -if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) -{var limitCdot=this.m_motorJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity);var limitImpulse=-this.m_motorMass*limitCdot;if(this.m_limitState==b2Joint.e_equalLimits) -{this.m_limitImpulse+=limitImpulse;} -else if(this.m_limitState==b2Joint.e_atLowerLimit) -{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Max(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} -else if(this.m_limitState==b2Joint.e_atUpperLimit) -{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Min(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} -b1.m_linearVelocity.x+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.y;b1.m_angularVelocity+=invI1*limitImpulse*this.m_motorJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.y;b2.m_angularVelocity+=invI2*limitImpulse*this.m_motorJacobian.angular2;}},SolvePositionConstraints:function(){var limitC;var oldLimitImpulse;var b1=this.m_body1;var b2=this.m_body2;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ay1X=tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y;var ay1Y=tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y;var linearC=ay1X*dX+ay1Y*dY;linearC=b2Math.b2Clamp(linearC,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);var linearImpulse=-this.m_linearMass*linearC;b1.m_position.x+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.x;b1.m_position.y+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.y;b1.m_rotation+=invI1*linearImpulse*this.m_linearJacobian.angular1;b2.m_position.x+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.x;b2.m_position.y+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.y;b2.m_rotation+=invI2*linearImpulse*this.m_linearJacobian.angular2;var positionError=b2Math.b2Abs(linearC);var angularC=b2.m_rotation-b1.m_rotation-this.m_initialAngle;angularC=b2Math.b2Clamp(angularC,-b2Settings.b2_maxAngularCorrection,b2Settings.b2_maxAngularCorrection);var angularImpulse=-this.m_angularMass*angularC;b1.m_rotation-=b1.m_invI*angularImpulse;b1.m_R.Set(b1.m_rotation);b2.m_rotation+=b2.m_invI*angularImpulse;b2.m_R.Set(b2.m_rotation);var angularError=b2Math.b2Abs(angularC);if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) -{tMat=b1.m_R;r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;p1X=b1.m_position.x+r1X;p1Y=b1.m_position.y+r1Y;p2X=b2.m_position.x+r2X;p2Y=b2.m_position.y+r2Y;dX=p2X-p1X;dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var translation=(ax1X*dX+ax1Y*dY);var limitImpulse=0.0;if(this.m_limitState==b2Joint.e_equalLimits) -{limitC=b2Math.b2Clamp(translation,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);limitImpulse=-this.m_motorMass*limitC;positionError=b2Math.b2Max(positionError,b2Math.b2Abs(angularC));} -else if(this.m_limitState==b2Joint.e_atLowerLimit) -{limitC=translation-this.m_lowerTranslation;positionError=b2Math.b2Max(positionError,-limitC);limitC=b2Math.b2Clamp(limitC+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Max(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} -else if(this.m_limitState==b2Joint.e_atUpperLimit) -{limitC=translation-this.m_upperTranslation;positionError=b2Math.b2Max(positionError,limitC);limitC=b2Math.b2Clamp(limitC-b2Settings.b2_linearSlop,0.0,b2Settings.b2_maxLinearCorrection);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Min(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} -b1.m_position.x+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.x;b1.m_position.y+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.y;b1.m_rotation+=invI1*limitImpulse*this.m_motorJacobian.angular1;b1.m_R.Set(b1.m_rotation);b2.m_position.x+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.x;b2.m_position.y+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.y;b2.m_rotation+=invI2*limitImpulse*this.m_motorJacobian.angular2;b2.m_R.Set(b2.m_rotation);} -return positionError<=b2Settings.b2_linearSlop&&angularError<=b2Settings.b2_angularSlop;},m_localAnchor1:new b2Vec2(),m_localAnchor2:new b2Vec2(),m_localXAxis1:new b2Vec2(),m_localYAxis1:new b2Vec2(),m_initialAngle:null,m_linearJacobian:new b2Jacobian(),m_linearMass:null,m_linearImpulse:null,m_angularMass:null,m_angularImpulse:null,m_motorJacobian:new b2Jacobian(),m_motorMass:null,m_motorImpulse:null,m_limitImpulse:null,m_limitPositionImpulse:null,m_lowerTranslation:null,m_upperTranslation:null,m_maxMotorForce:null,m_motorSpeed:null,m_enableLimit:null,m_enableMotor:null,m_limitState:0}); -var b2PrismaticJointDef=Class.create();Object.extend(b2PrismaticJointDef.prototype,b2JointDef.prototype);Object.extend(b2PrismaticJointDef.prototype,{initialize:function() -{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.type=b2Joint.e_prismaticJoint;this.anchorPoint=new b2Vec2(0.0,0.0);this.axis=new b2Vec2(0.0,0.0);this.lowerTranslation=0.0;this.upperTranslation=0.0;this.motorForce=0.0;this.motorSpeed=0.0;this.enableLimit=false;this.enableMotor=false;},anchorPoint:null,axis:null,lowerTranslation:null,upperTranslation:null,motorForce:null,motorSpeed:null,enableLimit:null,enableMotor:null}); -var b2PulleyJoint=Class.create();Object.extend(b2PulleyJoint.prototype,b2Joint.prototype);Object.extend(b2PulleyJoint.prototype,{GetAnchor1:function(){var tMat=this.m_body1.m_R;return new b2Vec2(this.m_body1.m_position.x+(tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y),this.m_body1.m_position.y+(tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y));},GetAnchor2:function(){var tMat=this.m_body2.m_R;return new b2Vec2(this.m_body2.m_position.x+(tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y),this.m_body2.m_position.y+(tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y));},GetGroundPoint1:function(){return new b2Vec2(this.m_ground.m_position.x+this.m_groundAnchor1.x,this.m_ground.m_position.y+this.m_groundAnchor1.y);},GetGroundPoint2:function(){return new b2Vec2(this.m_ground.m_position.x+this.m_groundAnchor2.x,this.m_ground.m_position.y+this.m_groundAnchor2.y);},GetReactionForce:function(invTimeStep){return new b2Vec2();},GetReactionTorque:function(invTimeStep){return 0.0;},GetLength1:function(){var tMat;tMat=this.m_body1.m_R;var pX=this.m_body1.m_position.x+(tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y);var pY=this.m_body1.m_position.y+(tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y);var dX=pX-(this.m_ground.m_position.x+this.m_groundAnchor1.x);var dY=pY-(this.m_ground.m_position.y+this.m_groundAnchor1.y);return Math.sqrt(dX*dX+dY*dY);},GetLength2:function(){var tMat;tMat=this.m_body2.m_R;var pX=this.m_body2.m_position.x+(tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y);var pY=this.m_body2.m_position.y+(tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y);var dX=pX-(this.m_ground.m_position.x+this.m_groundAnchor2.x);var dY=pY-(this.m_ground.m_position.y+this.m_groundAnchor2.y);return Math.sqrt(dX*dX+dY*dY);},GetRatio:function(){return this.m_ratio;},initialize:function(def){this.m_node1=new b2JointNode();this.m_node2=new b2JointNode();this.m_type=def.type;this.m_prev=null;this.m_next=null;this.m_body1=def.body1;this.m_body2=def.body2;this.m_collideConnected=def.collideConnected;this.m_islandFlag=false;this.m_userData=def.userData;this.m_groundAnchor1=new b2Vec2();this.m_groundAnchor2=new b2Vec2();this.m_localAnchor1=new b2Vec2();this.m_localAnchor2=new b2Vec2();this.m_u1=new b2Vec2();this.m_u2=new b2Vec2();var tMat;var tX;var tY;this.m_ground=this.m_body1.m_world.m_groundBody;this.m_groundAnchor1.x=def.groundPoint1.x-this.m_ground.m_position.x;this.m_groundAnchor1.y=def.groundPoint1.y-this.m_ground.m_position.y;this.m_groundAnchor2.x=def.groundPoint2.x-this.m_ground.m_position.x;this.m_groundAnchor2.y=def.groundPoint2.y-this.m_ground.m_position.y;tMat=this.m_body1.m_R;tX=def.anchorPoint1.x-this.m_body1.m_position.x;tY=def.anchorPoint1.y-this.m_body1.m_position.y;this.m_localAnchor1.x=tX*tMat.col1.x+tY*tMat.col1.y;this.m_localAnchor1.y=tX*tMat.col2.x+tY*tMat.col2.y;tMat=this.m_body2.m_R;tX=def.anchorPoint2.x-this.m_body2.m_position.x;tY=def.anchorPoint2.y-this.m_body2.m_position.y;this.m_localAnchor2.x=tX*tMat.col1.x+tY*tMat.col1.y;this.m_localAnchor2.y=tX*tMat.col2.x+tY*tMat.col2.y;this.m_ratio=def.ratio;tX=def.groundPoint1.x-def.anchorPoint1.x;tY=def.groundPoint1.y-def.anchorPoint1.y;var d1Len=Math.sqrt(tX*tX+tY*tY);tX=def.groundPoint2.x-def.anchorPoint2.x;tY=def.groundPoint2.y-def.anchorPoint2.y;var d2Len=Math.sqrt(tX*tX+tY*tY);var length1=b2Math.b2Max(0.5*b2PulleyJoint.b2_minPulleyLength,d1Len);var length2=b2Math.b2Max(0.5*b2PulleyJoint.b2_minPulleyLength,d2Len);this.m_constant=length1+this.m_ratio*length2;this.m_maxLength1=b2Math.b2Clamp(def.maxLength1,length1,this.m_constant-this.m_ratio*b2PulleyJoint.b2_minPulleyLength);this.m_maxLength2=b2Math.b2Clamp(def.maxLength2,length2,(this.m_constant-b2PulleyJoint.b2_minPulleyLength)/this.m_ratio);this.m_pulleyImpulse=0.0;this.m_limitImpulse1=0.0;this.m_limitImpulse2=0.0;},PrepareVelocitySolver:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var s1X=this.m_ground.m_position.x+this.m_groundAnchor1.x;var s1Y=this.m_ground.m_position.y+this.m_groundAnchor1.y;var s2X=this.m_ground.m_position.x+this.m_groundAnchor2.x;var s2Y=this.m_ground.m_position.y+this.m_groundAnchor2.y;this.m_u1.Set(p1X-s1X,p1Y-s1Y);this.m_u2.Set(p2X-s2X,p2Y-s2Y);var length1=this.m_u1.Length();var length2=this.m_u2.Length();if(length1>b2Settings.b2_linearSlop) -{this.m_u1.Multiply(1.0/length1);} -else -{this.m_u1.SetZero();} -if(length2>b2Settings.b2_linearSlop) -{this.m_u2.Multiply(1.0/length2);} -else -{this.m_u2.SetZero();} -if(length1b2Settings.b2_linearSlop) -{this.m_u1.Multiply(1.0/length1);} -else -{this.m_u1.SetZero();} -if(length2>b2Settings.b2_linearSlop) -{this.m_u2.Multiply(1.0/length2);} -else -{this.m_u2.SetZero();} -C=this.m_constant-length1-this.m_ratio*length2;linearError=b2Math.b2Max(linearError,Math.abs(C));C=b2Math.b2Clamp(C,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);impulse=-this.m_pulleyMass*C;p1X=-impulse*this.m_u1.x;p1Y=-impulse*this.m_u1.y;p2X=-this.m_ratio*impulse*this.m_u2.x;p2Y=-this.m_ratio*impulse*this.m_u2.y;b1.m_position.x+=b1.m_invMass*p1X;b1.m_position.y+=b1.m_invMass*p1Y;b1.m_rotation+=b1.m_invI*(r1X*p1Y-r1Y*p1X);b2.m_position.x+=b2.m_invMass*p2X;b2.m_position.y+=b2.m_invMass*p2Y;b2.m_rotation+=b2.m_invI*(r2X*p2Y-r2Y*p2X);b1.m_R.Set(b1.m_rotation);b2.m_R.Set(b2.m_rotation);} -if(this.m_limitState1==b2Joint.e_atUpperLimit) -{tMat=b1.m_R;r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;p1X=b1.m_position.x+r1X;p1Y=b1.m_position.y+r1Y;this.m_u1.Set(p1X-s1X,p1Y-s1Y);length1=this.m_u1.Length();if(length1>b2Settings.b2_linearSlop) -{this.m_u1.x*=1.0/length1;this.m_u1.y*=1.0/length1;} -else -{this.m_u1.SetZero();} -C=this.m_maxLength1-length1;linearError=b2Math.b2Max(linearError,-C);C=b2Math.b2Clamp(C+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);impulse=-this.m_limitMass1*C;oldLimitPositionImpulse=this.m_limitPositionImpulse1;this.m_limitPositionImpulse1=b2Math.b2Max(0.0,this.m_limitPositionImpulse1+impulse);impulse=this.m_limitPositionImpulse1-oldLimitPositionImpulse;p1X=-impulse*this.m_u1.x;p1Y=-impulse*this.m_u1.y;b1.m_position.x+=b1.m_invMass*p1X;b1.m_position.y+=b1.m_invMass*p1Y;b1.m_rotation+=b1.m_invI*(r1X*p1Y-r1Y*p1X);b1.m_R.Set(b1.m_rotation);} -if(this.m_limitState2==b2Joint.e_atUpperLimit) -{tMat=b2.m_R;r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;p2X=b2.m_position.x+r2X;p2Y=b2.m_position.y+r2Y;this.m_u2.Set(p2X-s2X,p2Y-s2Y);length2=this.m_u2.Length();if(length2>b2Settings.b2_linearSlop) -{this.m_u2.x*=1.0/length2;this.m_u2.y*=1.0/length2;} -else -{this.m_u2.SetZero();} -C=this.m_maxLength2-length2;linearError=b2Math.b2Max(linearError,-C);C=b2Math.b2Clamp(C+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);impulse=-this.m_limitMass2*C;oldLimitPositionImpulse=this.m_limitPositionImpulse2;this.m_limitPositionImpulse2=b2Math.b2Max(0.0,this.m_limitPositionImpulse2+impulse);impulse=this.m_limitPositionImpulse2-oldLimitPositionImpulse;p2X=-impulse*this.m_u2.x;p2Y=-impulse*this.m_u2.y;b2.m_position.x+=b2.m_invMass*p2X;b2.m_position.y+=b2.m_invMass*p2Y;b2.m_rotation+=b2.m_invI*(r2X*p2Y-r2Y*p2X);b2.m_R.Set(b2.m_rotation);} -return linearError=this.m_upperAngle) -{if(this.m_limitState!=b2Joint.e_atUpperLimit) -{this.m_limitImpulse=0.0;} -this.m_limitState=b2Joint.e_atUpperLimit;} -else -{this.m_limitState=b2Joint.e_inactiveLimit;this.m_limitImpulse=0.0;}} -else -{this.m_limitImpulse=0.0;} -if(b2World.s_enableWarmStarting) -{b1.m_linearVelocity.x-=invMass1*this.m_ptpImpulse.x;b1.m_linearVelocity.y-=invMass1*this.m_ptpImpulse.y;b1.m_angularVelocity-=invI1*((r1X*this.m_ptpImpulse.y-r1Y*this.m_ptpImpulse.x)+this.m_motorImpulse+this.m_limitImpulse);b2.m_linearVelocity.x+=invMass2*this.m_ptpImpulse.x;b2.m_linearVelocity.y+=invMass2*this.m_ptpImpulse.y;b2.m_angularVelocity+=invI2*((r2X*this.m_ptpImpulse.y-r2Y*this.m_ptpImpulse.x)+this.m_motorImpulse+this.m_limitImpulse);} -else{this.m_ptpImpulse.SetZero();this.m_motorImpulse=0.0;this.m_limitImpulse=0.0;} -this.m_limitPositionImpulse=0.0;},SolveVelocityConstraints:function(step){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var oldLimitImpulse;var ptpCdotX=b2.m_linearVelocity.x+(-b2.m_angularVelocity*r2Y)-b1.m_linearVelocity.x-(-b1.m_angularVelocity*r1Y);var ptpCdotY=b2.m_linearVelocity.y+(b2.m_angularVelocity*r2X)-b1.m_linearVelocity.y-(b1.m_angularVelocity*r1X);var ptpImpulseX=-(this.m_ptpMass.col1.x*ptpCdotX+this.m_ptpMass.col2.x*ptpCdotY);var ptpImpulseY=-(this.m_ptpMass.col1.y*ptpCdotX+this.m_ptpMass.col2.y*ptpCdotY);this.m_ptpImpulse.x+=ptpImpulseX;this.m_ptpImpulse.y+=ptpImpulseY;b1.m_linearVelocity.x-=b1.m_invMass*ptpImpulseX;b1.m_linearVelocity.y-=b1.m_invMass*ptpImpulseY;b1.m_angularVelocity-=b1.m_invI*(r1X*ptpImpulseY-r1Y*ptpImpulseX);b2.m_linearVelocity.x+=b2.m_invMass*ptpImpulseX;b2.m_linearVelocity.y+=b2.m_invMass*ptpImpulseY;b2.m_angularVelocity+=b2.m_invI*(r2X*ptpImpulseY-r2Y*ptpImpulseX);if(this.m_enableMotor&&this.m_limitState!=b2Joint.e_equalLimits) -{var motorCdot=b2.m_angularVelocity-b1.m_angularVelocity-this.m_motorSpeed;var motorImpulse=-this.m_motorMass*motorCdot;var oldMotorImpulse=this.m_motorImpulse;this.m_motorImpulse=b2Math.b2Clamp(this.m_motorImpulse+motorImpulse,-step.dt*this.m_maxMotorTorque,step.dt*this.m_maxMotorTorque);motorImpulse=this.m_motorImpulse-oldMotorImpulse;b1.m_angularVelocity-=b1.m_invI*motorImpulse;b2.m_angularVelocity+=b2.m_invI*motorImpulse;} -if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) -{var limitCdot=b2.m_angularVelocity-b1.m_angularVelocity;var limitImpulse=-this.m_motorMass*limitCdot;if(this.m_limitState==b2Joint.e_equalLimits) -{this.m_limitImpulse+=limitImpulse;} -else if(this.m_limitState==b2Joint.e_atLowerLimit) -{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Max(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} -else if(this.m_limitState==b2Joint.e_atUpperLimit) -{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Min(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} -b1.m_angularVelocity-=b1.m_invI*limitImpulse;b2.m_angularVelocity+=b2.m_invI*limitImpulse;}},SolvePositionConstraints:function(){var oldLimitImpulse;var limitC;var b1=this.m_body1;var b2=this.m_body2;var positionError=0.0;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var ptpCX=p2X-p1X;var ptpCY=p2Y-p1Y;positionError=Math.sqrt(ptpCX*ptpCX+ptpCY*ptpCY);var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;this.K1.col1.x=invMass1+invMass2;this.K1.col2.x=0.0;this.K1.col1.y=0.0;this.K1.col2.y=invMass1+invMass2;this.K2.col1.x=invI1*r1Y*r1Y;this.K2.col2.x=-invI1*r1X*r1Y;this.K2.col1.y=-invI1*r1X*r1Y;this.K2.col2.y=invI1*r1X*r1X;this.K3.col1.x=invI2*r2Y*r2Y;this.K3.col2.x=-invI2*r2X*r2Y;this.K3.col1.y=-invI2*r2X*r2Y;this.K3.col2.y=invI2*r2X*r2X;this.K.SetM(this.K1);this.K.AddM(this.K2);this.K.AddM(this.K3);this.K.Solve(b2RevoluteJoint.tImpulse,-ptpCX,-ptpCY);var impulseX=b2RevoluteJoint.tImpulse.x;var impulseY=b2RevoluteJoint.tImpulse.y;b1.m_position.x-=b1.m_invMass*impulseX;b1.m_position.y-=b1.m_invMass*impulseY;b1.m_rotation-=b1.m_invI*(r1X*impulseY-r1Y*impulseX);b1.m_R.Set(b1.m_rotation);b2.m_position.x+=b2.m_invMass*impulseX;b2.m_position.y+=b2.m_invMass*impulseY;b2.m_rotation+=b2.m_invI*(r2X*impulseY-r2Y*impulseX);b2.m_R.Set(b2.m_rotation);var angularError=0.0;if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) -{var angle=b2.m_rotation-b1.m_rotation-this.m_intialAngle;var limitImpulse=0.0;if(this.m_limitState==b2Joint.e_equalLimits) -{limitC=b2Math.b2Clamp(angle,-b2Settings.b2_maxAngularCorrection,b2Settings.b2_maxAngularCorrection);limitImpulse=-this.m_motorMass*limitC;angularError=b2Math.b2Abs(limitC);} -else if(this.m_limitState==b2Joint.e_atLowerLimit) -{limitC=angle-this.m_lowerAngle;angularError=b2Math.b2Max(0.0,-limitC);limitC=b2Math.b2Clamp(limitC+b2Settings.b2_angularSlop,-b2Settings.b2_maxAngularCorrection,0.0);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Max(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} -else if(this.m_limitState==b2Joint.e_atUpperLimit) -{limitC=angle-this.m_upperAngle;angularError=b2Math.b2Max(0.0,limitC);limitC=b2Math.b2Clamp(limitC-b2Settings.b2_angularSlop,0.0,b2Settings.b2_maxAngularCorrection);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Min(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} -b1.m_rotation-=b1.m_invI*limitImpulse;b1.m_R.Set(b1.m_rotation);b2.m_rotation+=b2.m_invI*limitImpulse;b2.m_R.Set(b2.m_rotation);} -return positionError<=b2Settings.b2_linearSlop&&angularError<=b2Settings.b2_angularSlop;},m_localAnchor1:new b2Vec2(),m_localAnchor2:new b2Vec2(),m_ptpImpulse:new b2Vec2(),m_motorImpulse:null,m_limitImpulse:null,m_limitPositionImpulse:null,m_ptpMass:new b2Mat22(),m_motorMass:null,m_intialAngle:null,m_lowerAngle:null,m_upperAngle:null,m_maxMotorTorque:null,m_motorSpeed:null,m_enableLimit:null,m_enableMotor:null,m_limitState:0});b2RevoluteJoint.tImpulse=new b2Vec2(); -var b2RevoluteJointDef=Class.create();Object.extend(b2RevoluteJointDef.prototype,b2JointDef.prototype);Object.extend(b2RevoluteJointDef.prototype,{initialize:function() -{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.type=b2Joint.e_revoluteJoint;this.anchorPoint=new b2Vec2(0.0,0.0);this.lowerAngle=0.0;this.upperAngle=0.0;this.motorTorque=0.0;this.motorSpeed=0.0;this.enableLimit=false;this.enableMotor=false;},anchorPoint:null,lowerAngle:null,upperAngle:null,motorTorque:null,motorSpeed:null,enableLimit:null,enableMotor:null}); +/* + * Box2Djs (port of Box2DFlash 1.4.3.1) - http://box2d-js.sourceforge.net/ + * Single-filed and jsmined ( http://code.google.com/p/jsmin-php/ ) by Mr.doob + */ + +var b2Settings=Class.create();b2Settings.prototype={initialize:function(){}} +b2Settings.USHRT_MAX=0x0000ffff;b2Settings.b2_pi=Math.PI;b2Settings.b2_massUnitsPerKilogram=1.0;b2Settings.b2_timeUnitsPerSecond=1.0;b2Settings.b2_lengthUnitsPerMeter=30.0;b2Settings.b2_maxManifoldPoints=2;b2Settings.b2_maxShapesPerBody=64;b2Settings.b2_maxPolyVertices=8;b2Settings.b2_maxProxies=1024;b2Settings.b2_maxPairs=8*b2Settings.b2_maxProxies;b2Settings.b2_linearSlop=0.005*b2Settings.b2_lengthUnitsPerMeter;b2Settings.b2_angularSlop=2.0/180.0*b2Settings.b2_pi;b2Settings.b2_velocityThreshold=1.0*b2Settings.b2_lengthUnitsPerMeter/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_maxLinearCorrection=0.2*b2Settings.b2_lengthUnitsPerMeter;b2Settings.b2_maxAngularCorrection=8.0/180.0*b2Settings.b2_pi;b2Settings.b2_contactBaumgarte=0.2;b2Settings.b2_timeToSleep=0.5*b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_linearSleepTolerance=0.01*b2Settings.b2_lengthUnitsPerMeter/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2_angularSleepTolerance=2.0/180.0/b2Settings.b2_timeUnitsPerSecond;b2Settings.b2Assert=function(a) +{if(!a){var nullVec;nullVec.x++;}}; +var b2Vec2=Class.create();b2Vec2.prototype={initialize:function(x_,y_){this.x=x_;this.y=y_;},SetZero:function(){this.x=0.0;this.y=0.0;},Set:function(x_,y_){this.x=x_;this.y=y_;},SetV:function(v){this.x=v.x;this.y=v.y;},Negative:function(){return new b2Vec2(-this.x,-this.y);},Copy:function(){return new b2Vec2(this.x,this.y);},Add:function(v) +{this.x+=v.x;this.y+=v.y;},Subtract:function(v) +{this.x-=v.x;this.y-=v.y;},Multiply:function(a) +{this.x*=a;this.y*=a;},MulM:function(A) +{var tX=this.x;this.x=A.col1.x*tX+A.col2.x*this.y;this.y=A.col1.y*tX+A.col2.y*this.y;},MulTM:function(A) +{var tX=b2Math.b2Dot(this,A.col1);this.y=b2Math.b2Dot(this,A.col2);this.x=tX;},CrossVF:function(s) +{var tX=this.x;this.x=s*this.y;this.y=-s*tX;},CrossFV:function(s) +{var tX=this.x;this.x=-s*this.y;this.y=s*tX;},MinV:function(b) +{this.x=this.xb.x?this.x:b.x;this.y=this.y>b.y?this.y:b.y;},Abs:function() +{this.x=Math.abs(this.x);this.y=Math.abs(this.y);},Length:function() +{return Math.sqrt(this.x*this.x+this.y*this.y);},Normalize:function() +{var length=this.Length();if(length0.0?a:-a;};b2Math.b2AbsV=function(a) +{var b=new b2Vec2(b2Math.b2Abs(a.x),b2Math.b2Abs(a.y));return b;};b2Math.b2AbsM=function(A) +{var B=new b2Mat22(0,b2Math.b2AbsV(A.col1),b2Math.b2AbsV(A.col2));return B;};b2Math.b2Min=function(a,b) +{return ab?a:b;};b2Math.b2MaxV=function(a,b) +{var c=new b2Vec2(b2Math.b2Max(a.x,b.x),b2Math.b2Max(a.y,b.y));return c;};b2Math.b2Clamp=function(a,low,high) +{return b2Math.b2Max(low,b2Math.b2Min(a,high));};b2Math.b2ClampV=function(a,low,high) +{return b2Math.b2MaxV(low,b2Math.b2MinV(a,high));};b2Math.b2Swap=function(a,b) +{var tmp=a[0];a[0]=b[0];b[0]=tmp;};b2Math.b2Random=function() +{return Math.random()*2-1;};b2Math.b2NextPowerOfTwo=function(x) +{x|=(x>>1)&0x7FFFFFFF;x|=(x>>2)&0x3FFFFFFF;x|=(x>>4)&0x0FFFFFFF;x|=(x>>8)&0x00FFFFFF;x|=(x>>16)&0x0000FFFF;return x+1;};b2Math.b2IsPowerOfTwo=function(x) +{var result=x>0&&(x&(x-1))==0;return result;};b2Math.tempVec2=new b2Vec2();b2Math.tempVec3=new b2Vec2();b2Math.tempVec4=new b2Vec2();b2Math.tempVec5=new b2Vec2();b2Math.tempMat=new b2Mat22(); +var b2AABB=Class.create();b2AABB.prototype={IsValid:function(){var dX=this.maxVertex.x;var dY=this.maxVertex.y;dX=this.maxVertex.x;dY=this.maxVertex.y;dX-=this.minVertex.x;dY-=this.minVertex.y;var valid=dX>=0.0&&dY>=0.0;valid=valid&&this.minVertex.IsValid()&&this.maxVertex.IsValid();return valid;},minVertex:new b2Vec2(),maxVertex:new b2Vec2(),initialize:function(){this.minVertex=new b2Vec2();this.maxVertex=new b2Vec2();}}; +var b2Bound=Class.create();b2Bound.prototype={IsLower:function(){return(this.value&1)==0;},IsUpper:function(){return(this.value&1)==1;},Swap:function(b){var tempValue=this.value;var tempProxyId=this.proxyId;var tempStabbingCount=this.stabbingCount;this.value=b.value;this.proxyId=b.proxyId;this.stabbingCount=b.stabbingCount;b.value=tempValue;b.proxyId=tempProxyId;b.stabbingCount=tempStabbingCount;},value:0,proxyId:0,stabbingCount:0,initialize:function(){}} + +var b2BoundValues=Class.create();b2BoundValues.prototype={lowerValues:[0,0],upperValues:[0,0],initialize:function(){this.lowerValues=[0,0];this.upperValues=[0,0];}} + +var b2Pair=Class.create();b2Pair.prototype={SetBuffered:function(){this.status|=b2Pair.e_pairBuffered;},ClearBuffered:function(){this.status&=~b2Pair.e_pairBuffered;},IsBuffered:function(){return(this.status&b2Pair.e_pairBuffered)==b2Pair.e_pairBuffered;},SetRemoved:function(){this.status|=b2Pair.e_pairRemoved;},ClearRemoved:function(){this.status&=~b2Pair.e_pairRemoved;},IsRemoved:function(){return(this.status&b2Pair.e_pairRemoved)==b2Pair.e_pairRemoved;},SetFinal:function(){this.status|=b2Pair.e_pairFinal;},IsFinal:function(){return(this.status&b2Pair.e_pairFinal)==b2Pair.e_pairFinal;},userData:null,proxyId1:0,proxyId2:0,next:0,status:0,initialize:function(){}};b2Pair.b2_nullPair=b2Settings.USHRT_MAX;b2Pair.b2_nullProxy=b2Settings.USHRT_MAX;b2Pair.b2_tableCapacity=b2Settings.b2_maxPairs;b2Pair.b2_tableMask=b2Pair.b2_tableCapacity-1;b2Pair.e_pairBuffered=0x0001;b2Pair.e_pairRemoved=0x0002;b2Pair.e_pairFinal=0x0004; +var b2PairCallback=Class.create();b2PairCallback.prototype={PairAdded:function(proxyUserData1,proxyUserData2){return null},PairRemoved:function(proxyUserData1,proxyUserData2,pairUserData){},initialize:function(){}}; +var b2BufferedPair=Class.create();b2BufferedPair.prototype={proxyId1:0,proxyId2:0,initialize:function(){}} + +var b2PairManager=Class.create();b2PairManager.prototype={initialize:function(){var i=0;this.m_hashTable=new Array(b2Pair.b2_tableCapacity);for(i=0;iproxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} +var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;var pair=pair=this.FindHash(proxyId1,proxyId2,hash);if(pair!=null) +{return pair;} +var pIndex=this.m_freePair;pair=this.m_pairs[pIndex];this.m_freePair=pair.next;pair.proxyId1=proxyId1;pair.proxyId2=proxyId2;pair.status=0;pair.userData=null;pair.next=this.m_hashTable[hash];this.m_hashTable[hash]=pIndex;++this.m_pairCount;return pair;},RemovePair:function(proxyId1,proxyId2){if(proxyId1>proxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} +var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;var node=this.m_hashTable[hash];var pNode=null;while(node!=b2Pair.b2_nullPair) +{if(b2PairManager.Equals(this.m_pairs[node],proxyId1,proxyId2)) +{var index=node;if(pNode){pNode.next=this.m_pairs[node].next;} +else{this.m_hashTable[hash]=this.m_pairs[node].next;} +var pair=this.m_pairs[index];var userData=pair.userData;pair.next=this.m_freePair;pair.proxyId1=b2Pair.b2_nullProxy;pair.proxyId2=b2Pair.b2_nullProxy;pair.userData=null;pair.status=0;this.m_freePair=index;--this.m_pairCount;return userData;} +else +{pNode=this.m_pairs[node];node=pNode.next;}} +return null;},Find:function(proxyId1,proxyId2){if(proxyId1>proxyId2){var temp=proxyId1;proxyId1=proxyId2;proxyId2=temp;} +var hash=b2PairManager.Hash(proxyId1,proxyId2)&b2Pair.b2_tableMask;return this.FindHash(proxyId1,proxyId2,hash);},FindHash:function(proxyId1,proxyId2,hash){var index=this.m_hashTable[hash];while(index!=b2Pair.b2_nullPair&&b2PairManager.Equals(this.m_pairs[index],proxyId1,proxyId2)==false) +{index=this.m_pairs[index].next;} +if(index==b2Pair.b2_nullPair) +{return null;} +return this.m_pairs[index];},ValidateBuffer:function(){},ValidateTable:function(){},m_broadPhase:null,m_callback:null,m_pairs:null,m_freePair:0,m_pairCount:0,m_pairBuffer:null,m_pairBufferCount:0,m_hashTable:null};b2PairManager.Hash=function(proxyId1,proxyId2) +{var key=((proxyId2<<16)&0xffff0000)|proxyId1;key=~key+((key<<15)&0xFFFF8000);key=key^((key>>12)&0x000fffff);key=key+((key<<2)&0xFFFFFFFC);key=key^((key>>4)&0x0fffffff);key=key*2057;key=key^((key>>16)&0x0000ffff);return key;};b2PairManager.Equals=function(pair,proxyId1,proxyId2) +{return(pair.proxyId1==proxyId1&&pair.proxyId2==proxyId2);};b2PairManager.EqualsPair=function(pair1,pair2) +{return pair1.proxyId1==pair2.proxyId1&&pair1.proxyId2==pair2.proxyId2;}; +var b2BroadPhase=Class.create();b2BroadPhase.prototype={initialize:function(worldAABB,callback){this.m_pairManager=new b2PairManager();this.m_proxyPool=new Array(b2Settings.b2_maxPairs);this.m_bounds=new Array(2*b2Settings.b2_maxProxies);this.m_queryResults=new Array(b2Settings.b2_maxProxies);this.m_quantizationFactor=new b2Vec2();var i=0;this.m_pairManager.Initialize(this,callback);this.m_worldAABB=worldAABB;this.m_proxyCount=0;for(i=0;i0&&lowerValue0) +{index=upperIndex;while(index0) +{index=lowerIndex;while(index0&&upperValuebounds[p2.upperBounds[axis]].value) +return false;if(bounds[p1.upperBounds[axis]].valuebounds[p.upperBounds[axis]].value) +return false;if(b.upperValues[axis]0) +{var i=lowerQuery-1;var s=bounds[i].stabbingCount;while(s) +{if(bounds[i].IsLower()) +{var proxy=this.m_proxyPool[bounds[i].proxyId];if(lowerQuery<=proxy.upperBounds[axis]) +{this.IncrementOverlapCount(bounds[i].proxyId);--s;}} +--i;}} +lowerQueryOut[0]=lowerQuery;upperQueryOut[0]=upperQuery;},IncrementOverlapCount:function(proxyId){var proxy=this.m_proxyPool[proxyId];if(proxy.timeStampvalue) +{high=mid-1;} +else if(bounds[mid].value0.0) +{vOut[numOut].id=vIn[0].id;} +else +{vOut[numOut].id=vIn[1].id;} +++numOut;} +return numOut;};b2Collision.EdgeSeparation=function(poly1,edge1,poly2) +{var vert1s=poly1.m_vertices;var count2=poly2.m_vertexCount;var vert2s=poly2.m_vertices;var normalX=poly1.m_normals[edge1].x;var normalY=poly1.m_normals[edge1].y;var tX=normalX;var tMat=poly1.m_R;normalX=tMat.col1.x*tX+tMat.col2.x*normalY;normalY=tMat.col1.y*tX+tMat.col2.y*normalY;var normalLocal2X=normalX;var normalLocal2Y=normalY;tMat=poly2.m_R;tX=normalLocal2X*tMat.col1.x+normalLocal2Y*tMat.col1.y;normalLocal2Y=normalLocal2X*tMat.col2.x+normalLocal2Y*tMat.col2.y;normalLocal2X=tX;var vertexIndex2=0;var minDot=Number.MAX_VALUE;for(var i=0;imaxDot) +{maxDot=dot;edge=i;}} +var s=b2Collision.EdgeSeparation(poly1,edge,poly2);if(s>0.0&&conservative==false) +{return s;} +var prevEdge=edge-1>=0?edge-1:count1-1;var sPrev=b2Collision.EdgeSeparation(poly1,prevEdge,poly2);if(sPrev>0.0&&conservative==false) +{return sPrev;} +var nextEdge=edge+10.0&&conservative==false) +{return sNext;} +var bestEdge=0;var bestSeparation;var increment=0;if(sPrev>s&&sPrev>sNext) +{increment=-1;bestEdge=prevEdge;bestSeparation=sPrev;} +else if(sNext>s) +{increment=1;bestEdge=nextEdge;bestSeparation=sNext;} +else +{edgeIndex[0]=edge;return s;} +while(true) +{if(increment==-1) +edge=bestEdge-1>=0?bestEdge-1:count1-1;else +edge=bestEdge+10.0&&conservative==false) +{return s;} +if(s>bestSeparation) +{bestEdge=edge;bestSeparation=s;} +else +{break;}} +edgeIndex[0]=bestEdge;return bestSeparation;};b2Collision.FindIncidentEdge=function(c,poly1,edge1,poly2) +{var count1=poly1.m_vertexCount;var vert1s=poly1.m_vertices;var count2=poly2.m_vertexCount;var vert2s=poly2.m_vertices;var vertex11=edge1;var vertex12=edge1+1==count1?0:edge1+1;var tVec=vert1s[vertex12];var normal1Local1X=tVec.x;var normal1Local1Y=tVec.y;tVec=vert1s[vertex11];normal1Local1X-=tVec.x;normal1Local1Y-=tVec.y;var tX=normal1Local1X;normal1Local1X=normal1Local1Y;normal1Local1Y=-tX;var invLength=1.0/Math.sqrt(normal1Local1X*normal1Local1X+normal1Local1Y*normal1Local1Y);normal1Local1X*=invLength;normal1Local1Y*=invLength;var normal1X=normal1Local1X;var normal1Y=normal1Local1Y;tX=normal1X;var tMat=poly1.m_R;normal1X=tMat.col1.x*tX+tMat.col2.x*normal1Y;normal1Y=tMat.col1.y*tX+tMat.col2.y*normal1Y;var normal1Local2X=normal1X;var normal1Local2Y=normal1Y;tMat=poly2.m_R;tX=normal1Local2X*tMat.col1.x+normal1Local2Y*tMat.col1.y;normal1Local2Y=normal1Local2X*tMat.col2.x+normal1Local2Y*tMat.col2.y;normal1Local2X=tX;var vertex21=0;var vertex22=0;var minDot=Number.MAX_VALUE;for(var i=0;i0.0&&conservative==false) +return;var edgeB=0;var edgeBOut=[edgeB];var separationB=b2Collision.FindMaxSeparation(edgeBOut,polyB,polyA,conservative);edgeB=edgeBOut[0];if(separationB>0.0&&conservative==false) +return;var poly1;var poly2;var edge1=0;var flip=0;var k_relativeTol=0.98;var k_absoluteTol=0.001;if(separationB>k_relativeTol*separationA+k_absoluteTol) +{poly1=polyB;poly2=polyA;edge1=edgeB;flip=1;} +else +{poly1=polyA;poly2=polyB;edge1=edgeA;flip=0;} +var incidentEdge=[new ClipVertex(),new ClipVertex()];b2Collision.FindIncidentEdge(incidentEdge,poly1,edge1,poly2);var count1=poly1.m_vertexCount;var vert1s=poly1.m_vertices;var v11=vert1s[edge1];var v12=edge1+1radiusSum*radiusSum&&conservative==false) +{return;} +var separation;if(distSqrradius) +{return;} +if(s>separation) +{separation=s;normalIndex=i;}} +if(separationradius) +{return;} +manifold.pointCount=1;manifold.normal.Set(tMat.col1.x*dX+tMat.col2.x*dY,tMat.col1.y*dX+tMat.col2.y*dY);tPoint=manifold.points[0];tPoint.id.features.incidentEdge=b2Collision.b2_nullFeature;tPoint.id.features.incidentVertex=vertIndex1;tPoint.id.features.referenceFace=b2Collision.b2_nullFeature;tPoint.id.features.flip=0;tPoint.position.x=circle.m_position.x-radius*manifold.normal.x;tPoint.position.y=circle.m_position.y-radius*manifold.normal.y;tPoint.separation=dist-radius;return;} +var u=(xLocalX-poly.m_vertices[vertIndex1].x)*eX+(xLocalY-poly.m_vertices[vertIndex1].y)*eY;tPoint=manifold.points[0];tPoint.id.features.incidentEdge=b2Collision.b2_nullFeature;tPoint.id.features.incidentVertex=b2Collision.b2_nullFeature;tPoint.id.features.referenceFace=b2Collision.b2_nullFeature;tPoint.id.features.flip=0;var pX,pY;if(u<=0.0) +{pX=poly.m_vertices[vertIndex1].x;pY=poly.m_vertices[vertIndex1].y;tPoint.id.features.incidentVertex=vertIndex1;} +else if(u>=length) +{pX=poly.m_vertices[vertIndex2].x;pY=poly.m_vertices[vertIndex2].y;tPoint.id.features.incidentVertex=vertIndex2;} +else +{pX=eX*u+poly.m_vertices[vertIndex1].x;pY=eY*u+poly.m_vertices[vertIndex1].y;tPoint.id.features.incidentEdge=vertIndex1;} +dX=xLocalX-pX;dY=xLocalY-pY;dist=Math.sqrt(dX*dX+dY*dY);dX/=dist;dY/=dist;if(dist>radius) +{return;} +manifold.pointCount=1;manifold.normal.Set(tMat.col1.x*dX+tMat.col2.x*dY,tMat.col1.y*dX+tMat.col2.y*dY);tPoint.position.x=circle.m_position.x-radius*manifold.normal.x;tPoint.position.y=circle.m_position.y-radius*manifold.normal.y;tPoint.separation=dist-radius;};b2Collision.b2TestOverlap=function(a,b) +{var t1=b.minVertex;var t2=a.maxVertex;var d1X=t1.x-t2.x;var d1Y=t1.y-t2.y;t1=a.minVertex;t2=b.maxVertex;var d2X=t1.x-t2.x;var d2Y=t1.y-t2.y;if(d1X>0.0||d1Y>0.0) +return false;if(d2X>0.0||d2Y>0.0) +return false;return true;}; +var Features=Class.create();Features.prototype={set_referenceFace:function(value){this._referenceFace=value;this._m_id._key=(this._m_id._key&0xffffff00)|(this._referenceFace&0x000000ff)},get_referenceFace:function(){return this._referenceFace;},_referenceFace:0,set_incidentEdge:function(value){this._incidentEdge=value;this._m_id._key=(this._m_id._key&0xffff00ff)|((this._incidentEdge<<8)&0x0000ff00)},get_incidentEdge:function(){return this._incidentEdge;},_incidentEdge:0,set_incidentVertex:function(value){this._incidentVertex=value;this._m_id._key=(this._m_id._key&0xff00ffff)|((this._incidentVertex<<16)&0x00ff0000)},get_incidentVertex:function(){return this._incidentVertex;},_incidentVertex:0,set_flip:function(value){this._flip=value;this._m_id._key=(this._m_id._key&0x00ffffff)|((this._flip<<24)&0xff000000)},get_flip:function(){return this._flip;},_flip:0,_m_id:null,initialize:function(){}}; +var b2ContactID=Class.create();b2ContactID.prototype={initialize:function(){this.features=new Features();this.features._m_id=this;},Set:function(id){this.set_key(id._key);},Copy:function(){var id=new b2ContactID();id.set_key(this._key);return id;},get_key:function(){return this._key;},set_key:function(value){this._key=value;this.features._referenceFace=this._key&0x000000ff;this.features._incidentEdge=((this._key&0x0000ff00)>>8)&0x000000ff;this.features._incidentVertex=((this._key&0x00ff0000)>>16)&0x000000ff;this.features._flip=((this._key&0xff000000)>>24)&0x000000ff;},features:new Features(),_key:0}; +var b2ContactPoint=Class.create();b2ContactPoint.prototype={position:new b2Vec2(),separation:null,normalImpulse:null,tangentImpulse:null,id:new b2ContactID(),initialize:function(){this.position=new b2Vec2();this.id=new b2ContactID();}};var b2Distance=Class.create();b2Distance.prototype={initialize:function(){}};b2Distance.ProcessTwo=function(p1Out,p2Out,p1s,p2s,points) +{var rX=-points[1].x;var rY=-points[1].y;var dX=points[0].x-points[1].x;var dY=points[0].y-points[1].y;var length=Math.sqrt(dX*dX+dY*dY);dX/=length;dY/=length;var lambda=rX*dX+rY*dY;if(lambda<=0.0||length=0.0&&ud>=0.0) +{var lambda=un/(un+ud);p1Out.x=p1s[1].x+lambda*(p1s[2].x-p1s[1].x);p1Out.y=p1s[1].y+lambda*(p1s[2].y-p1s[1].y);p2Out.x=p2s[1].x+lambda*(p2s[2].x-p2s[1].x);p2Out.y=p2s[1].y+lambda*(p2s[2].y-p2s[1].y);p1s[0].SetV(p1s[2]);p2s[0].SetV(p2s[2]);points[0].SetV(points[2]);return 2;} +var vb=n*(cX*aY-cY*aX);if(vb<=0.0&&tn>=0.0&&td>=0.0) +{var lambda=tn/(tn+td);p1Out.x=p1s[0].x+lambda*(p1s[2].x-p1s[0].x);p1Out.y=p1s[0].y+lambda*(p1s[2].y-p1s[0].y);p2Out.x=p2s[0].x+lambda*(p2s[2].x-p2s[0].x);p2Out.y=p2s[0].y+lambda*(p2s[2].y-p2s[0].y);p1s[1].SetV(p1s[2]);p2s[1].SetV(p2s[2]);points[1].SetV(points[2]);return 2;} +var denom=va+vb+vc;denom=1.0/denom;var u=va*denom;var v=vb*denom;var w=1.0-u-v;p1Out.x=u*p1s[0].x+v*p1s[1].x+w*p1s[2].x;p1Out.y=u*p1s[0].y+v*p1s[1].y+w*p1s[2].y;p2Out.x=u*p2s[0].x+v*p2s[1].x+w*p2s[2].x;p2Out.y=u*p2s[0].y+v*p2s[1].y+w*p2s[2].y;return 3;};b2Distance.InPoinsts=function(w,points,pointCount) +{for(var i=0;i0.0) +{return false;}} +return true;},initialize:function(def,body,newOrigin){this.m_R=new b2Mat22();this.m_position=new b2Vec2();this.m_userData=def.userData;this.m_friction=def.friction;this.m_restitution=def.restitution;this.m_body=body;this.m_proxyId=b2Pair.b2_nullProxy;this.m_maxRadius=0.0;this.m_categoryBits=def.categoryBits;this.m_maskBits=def.maskBits;this.m_groupIndex=def.groupIndex;this.syncAABB=new b2AABB();this.syncMat=new b2Mat22();this.m_localCentroid=new b2Vec2();this.m_localOBB=new b2OBB();var i=0;var hX;var hY;var tVec;var aabb=new b2AABB();this.m_vertices=new Array(b2Settings.b2_maxPolyVertices);this.m_coreVertices=new Array(b2Settings.b2_maxPolyVertices);this.m_normals=new Array(b2Settings.b2_maxPolyVertices);this.m_type=b2Shape.e_polyShape;var localR=new b2Mat22(def.localRotation);if(def.type==b2Shape.e_boxShape) +{this.m_localCentroid.x=def.localPosition.x-newOrigin.x;this.m_localCentroid.y=def.localPosition.y-newOrigin.y;var box=def;this.m_vertexCount=4;hX=box.extents.x;hY=box.extents.y;var hcX=Math.max(0.0,hX-2.0*b2Settings.b2_linearSlop);var hcY=Math.max(0.0,hY-2.0*b2Settings.b2_linearSlop);tVec=this.m_vertices[0]=new b2Vec2();tVec.x=localR.col1.x*hX+localR.col2.x*hY;tVec.y=localR.col1.y*hX+localR.col2.y*hY;tVec=this.m_vertices[1]=new b2Vec2();tVec.x=localR.col1.x*-hX+localR.col2.x*hY;tVec.y=localR.col1.y*-hX+localR.col2.y*hY;tVec=this.m_vertices[2]=new b2Vec2();tVec.x=localR.col1.x*-hX+localR.col2.x*-hY;tVec.y=localR.col1.y*-hX+localR.col2.y*-hY;tVec=this.m_vertices[3]=new b2Vec2();tVec.x=localR.col1.x*hX+localR.col2.x*-hY;tVec.y=localR.col1.y*hX+localR.col2.y*-hY;tVec=this.m_coreVertices[0]=new b2Vec2();tVec.x=localR.col1.x*hcX+localR.col2.x*hcY;tVec.y=localR.col1.y*hcX+localR.col2.y*hcY;tVec=this.m_coreVertices[1]=new b2Vec2();tVec.x=localR.col1.x*-hcX+localR.col2.x*hcY;tVec.y=localR.col1.y*-hcX+localR.col2.y*hcY;tVec=this.m_coreVertices[2]=new b2Vec2();tVec.x=localR.col1.x*-hcX+localR.col2.x*-hcY;tVec.y=localR.col1.y*-hcX+localR.col2.y*-hcY;tVec=this.m_coreVertices[3]=new b2Vec2();tVec.x=localR.col1.x*hcX+localR.col2.x*-hcY;tVec.y=localR.col1.y*hcX+localR.col2.y*-hcY;} +else +{var poly=def;this.m_vertexCount=poly.vertexCount;b2Shape.PolyCentroid(poly.vertices,poly.vertexCount,b2PolyShape.tempVec);var centroidX=b2PolyShape.tempVec.x;var centroidY=b2PolyShape.tempVec.y;this.m_localCentroid.x=def.localPosition.x+(localR.col1.x*centroidX+localR.col2.x*centroidY)-newOrigin.x;this.m_localCentroid.y=def.localPosition.y+(localR.col1.y*centroidX+localR.col2.y*centroidY)-newOrigin.y;for(i=0;iNumber.MIN_VALUE) +{uX*=1.0/length;uY*=1.0/length;} +this.m_coreVertices[i].x=this.m_vertices[i].x-2.0*b2Settings.b2_linearSlop*uX;this.m_coreVertices[i].y=this.m_vertices[i].y-2.0*b2Settings.b2_linearSlop*uY;}} +var minVertexX=Number.MAX_VALUE;var minVertexY=Number.MAX_VALUE;var maxVertexX=-Number.MAX_VALUE;var maxVertexY=-Number.MAX_VALUE;this.m_maxRadius=0.0;for(i=0;ibestValue) +{bestIndex=i;bestValue=value;}} +out.Set(this.m_position.x+(this.m_R.col1.x*this.m_coreVertices[bestIndex].x+this.m_R.col2.x*this.m_coreVertices[bestIndex].y),this.m_position.y+(this.m_R.col1.y*this.m_coreVertices[bestIndex].x+this.m_R.col2.y*this.m_coreVertices[bestIndex].y));},m_localCentroid:new b2Vec2(),m_localOBB:new b2OBB(),m_vertices:null,m_coreVertices:null,m_vertexCount:0,m_normals:null});b2PolyShape.tempVec=new b2Vec2();b2PolyShape.tAbsR=new b2Mat22(); +var b2Body=Class.create();b2Body.prototype={SetOriginPosition:function(position,rotation){if(this.IsFrozen()) +{return;} +this.m_rotation=rotation;this.m_R.Set(this.m_rotation);this.m_position=b2Math.AddVV(position,b2Math.b2MulMV(this.m_R,this.m_center));this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;for(var s=this.m_shapeList;s!=null;s=s.m_next) +{s.Synchronize(this.m_position,this.m_R,this.m_position,this.m_R);} +this.m_world.m_broadPhase.Commit();},GetOriginPosition:function(){return b2Math.SubtractVV(this.m_position,b2Math.b2MulMV(this.m_R,this.m_center));},SetCenterPosition:function(position,rotation){if(this.IsFrozen()) +{return;} +this.m_rotation=rotation;this.m_R.Set(this.m_rotation);this.m_position.SetV(position);this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;for(var s=this.m_shapeList;s!=null;s=s.m_next) +{s.Synchronize(this.m_position,this.m_R,this.m_position,this.m_R);} +this.m_world.m_broadPhase.Commit();},GetCenterPosition:function(){return this.m_position;},GetRotation:function(){return this.m_rotation;},GetRotationMatrix:function(){return this.m_R;},SetLinearVelocity:function(v){this.m_linearVelocity.SetV(v);},GetLinearVelocity:function(){return this.m_linearVelocity;},SetAngularVelocity:function(w){this.m_angularVelocity=w;},GetAngularVelocity:function(){return this.m_angularVelocity;},ApplyForce:function(force,point) +{if(this.IsSleeping()==false) +{this.m_force.Add(force);this.m_torque+=b2Math.b2CrossVV(b2Math.SubtractVV(point,this.m_position),force);}},ApplyTorque:function(torque) +{if(this.IsSleeping()==false) +{this.m_torque+=torque;}},ApplyImpulse:function(impulse,point) +{if(this.IsSleeping()==false) +{this.m_linearVelocity.Add(b2Math.MulFV(this.m_invMass,impulse));this.m_angularVelocity+=(this.m_invI*b2Math.b2CrossVV(b2Math.SubtractVV(point,this.m_position),impulse));}},GetMass:function(){return this.m_mass;},GetInertia:function(){return this.m_I;},GetWorldPoint:function(localPoint){return b2Math.AddVV(this.m_position,b2Math.b2MulMV(this.m_R,localPoint));},GetWorldVector:function(localVector){return b2Math.b2MulMV(this.m_R,localVector);},GetLocalPoint:function(worldPoint){return b2Math.b2MulTMV(this.m_R,b2Math.SubtractVV(worldPoint,this.m_position));},GetLocalVector:function(worldVector){return b2Math.b2MulTMV(this.m_R,worldVector);},IsStatic:function(){return(this.m_flags&b2Body.e_staticFlag)==b2Body.e_staticFlag;},IsFrozen:function() +{return(this.m_flags&b2Body.e_frozenFlag)==b2Body.e_frozenFlag;},IsSleeping:function(){return(this.m_flags&b2Body.e_sleepFlag)==b2Body.e_sleepFlag;},AllowSleeping:function(flag) +{if(flag) +{this.m_flags|=b2Body.e_allowSleepFlag;} +else +{this.m_flags&=~b2Body.e_allowSleepFlag;this.WakeUp();}},WakeUp:function(){this.m_flags&=~b2Body.e_sleepFlag;this.m_sleepTime=0.0;},GetShapeList:function(){return this.m_shapeList;},GetContactList:function() +{return this.m_contactList;},GetJointList:function() +{return this.m_jointList;},GetNext:function(){return this.m_next;},GetUserData:function(){return this.m_userData;},initialize:function(bd,world){this.sMat0=new b2Mat22();this.m_position=new b2Vec2();this.m_R=new b2Mat22(0);this.m_position0=new b2Vec2();var i=0;var sd;var massData;this.m_flags=0;this.m_position.SetV(bd.position);this.m_rotation=bd.rotation;this.m_R.Set(this.m_rotation);this.m_position0.SetV(this.m_position);this.m_rotation0=this.m_rotation;this.m_world=world;this.m_linearDamping=b2Math.b2Clamp(1.0-bd.linearDamping,0.0,1.0);this.m_angularDamping=b2Math.b2Clamp(1.0-bd.angularDamping,0.0,1.0);this.m_force=new b2Vec2(0.0,0.0);this.m_torque=0.0;this.m_mass=0.0;var massDatas=new Array(b2Settings.b2_maxShapesPerBody);for(i=0;i0.0) +{this.m_center.Multiply(1.0/this.m_mass);this.m_position.Add(b2Math.b2MulMV(this.m_R,this.m_center));} +else +{this.m_flags|=b2Body.e_staticFlag;} +this.m_I=0.0;for(i=0;i0.0) +{this.m_invMass=1.0/this.m_mass;} +else +{this.m_invMass=0.0;} +if(this.m_I>0.0&&bd.preventRotation==false) +{this.m_invI=1.0/this.m_I;} +else +{this.m_I=0.0;this.m_invI=0.0;} +this.m_linearVelocity=b2Math.AddVV(bd.linearVelocity,b2Math.b2CrossFV(bd.angularVelocity,this.m_center));this.m_angularVelocity=bd.angularVelocity;this.m_jointList=null;this.m_contactList=null;this.m_prev=null;this.m_next=null;this.m_shapeList=null;for(i=0;i0;} +var collide=(shape1.m_maskBits&shape2.m_categoryBits)!=0&&(shape1.m_categoryBits&shape2.m_maskBits)!=0;return collide;},initialize:function(){}};b2CollisionFilter.b2_defaultFilter=new b2CollisionFilter; +var b2Island=Class.create();b2Island.prototype={initialize:function(bodyCapacity,contactCapacity,jointCapacity,allocator) +{var i=0;this.m_bodyCapacity=bodyCapacity;this.m_contactCapacity=contactCapacity;this.m_jointCapacity=jointCapacity;this.m_bodyCount=0;this.m_contactCount=0;this.m_jointCount=0;this.m_bodies=new Array(bodyCapacity);for(i=0;iangTolSqr||b2Math.b2Dot(b.m_linearVelocity,b.m_linearVelocity)>linTolSqr) +{b.m_sleepTime=0.0;minSleepTime=0.0;} +else +{b.m_sleepTime+=dt;minSleepTime=b2Math.b2Min(minSleepTime,b.m_sleepTime);}} +if(minSleepTime>=b2Settings.b2_timeToSleep) +{for(i=0;i0) +{contact.m_shape1.m_body.WakeUp();contact.m_shape2.m_body.WakeUp();} +var type1=contact.m_shape1.m_type;var type2=contact.m_shape2.m_type;var destroyFcn=b2Contact.s_registers[type1][type2].destroyFcn;destroyFcn(contact,allocator);};b2Contact.s_registers=null;b2Contact.s_initialized=false; +var b2ContactConstraint=Class.create();b2ContactConstraint.prototype={initialize:function(){this.normal=new b2Vec2();this.points=new Array(b2Settings.b2_maxManifoldPoints);for(var i=0;i0.0) +{ccp.velocityBias=-60.0*ccp.separation;} +var tX=v2X+(-w2*r2Y)-v1X-(-w1*r1Y);var tY=v2Y+(w2*r2X)-v1Y-(w1*r1X);var vRel=c.normal.x*tX+c.normal.y*tY;if(vRel<-b2Settings.b2_velocityThreshold) +{ccp.velocityBias+=-c.restitution*vRel;}} +++count;}}},PreSolve:function(){var tVec;var tVec2;var tMat;for(var i=0;i=-b2Settings.b2_linearSlop;},PostSolve:function(){for(var i=0;i0) +{this.m_manifoldCount=1;} +else +{this.m_manifoldCount=0;}},GetManifolds:function() +{return this.m_manifold;},m_manifold:[new b2Manifold()]});b2CircleContact.Create=function(shape1,shape2,allocator){return new b2CircleContact(shape1,shape2);};b2CircleContact.Destroy=function(contact,allocator){}; +var b2Conservative=Class.create();b2Conservative.prototype={initialize:function(){}} +b2Conservative.R1=new b2Mat22();b2Conservative.R2=new b2Mat22();b2Conservative.x1=new b2Vec2();b2Conservative.x2=new b2Vec2();b2Conservative.Conservative=function(shape1,shape2){var body1=shape1.GetBody();var body2=shape2.GetBody();var v1X=body1.m_position.x-body1.m_position0.x;var v1Y=body1.m_position.y-body1.m_position0.y;var omega1=body1.m_rotation-body1.m_rotation0;var v2X=body2.m_position.x-body2.m_position0.x;var v2Y=body2.m_position.y-body2.m_position0.y;var omega2=body2.m_rotation-body2.m_rotation0;var r1=shape1.GetMaxRadius();var r2=shape2.GetMaxRadius();var p1StartX=body1.m_position0.x;var p1StartY=body1.m_position0.y;var a1Start=body1.m_rotation0;var p2StartX=body2.m_position0.x;var p2StartY=body2.m_position0.y;var a2Start=body2.m_rotation0;var p1X=p1StartX;var p1Y=p1StartY;var a1=a1Start;var p2X=p2StartX;var p2Y=p2StartY;var a2=a2Start;b2Conservative.R1.Set(a1);b2Conservative.R2.Set(a2);shape1.QuickSync(p1,b2Conservative.R1);shape2.QuickSync(p2,b2Conservative.R2);var s1=0.0;var maxIterations=10;var dX;var dY;var invRelativeVelocity=0.0;var hit=true;for(var iter=0;iterFLT_EPSILON) +{d*=b2_linearSlop/length;} +if(body1.IsStatic()) +{body1.m_position.x=p1X;body1.m_position.y=p1Y;} +else +{body1.m_position.x=p1X-dX;body1.m_position.y=p1Y-dY;} +body1.m_rotation=a1;body1.m_R.Set(a1);body1.QuickSyncShapes();if(body2.IsStatic()) +{body2.m_position.x=p2X;body2.m_position.y=p2Y;} +else +{body2.m_position.x=p2X+dX;body2.m_position.y=p2Y+dY;} +body2.m_position.x=p2X+dX;body2.m_position.y=p2Y+dY;body2.m_rotation=a2;body2.m_R.Set(a2);body2.QuickSyncShapes();return true;} +shape1.QuickSync(body1.m_position,body1.m_R);shape2.QuickSync(body2.m_position,body2.m_R);return false;}; +var b2NullContact=Class.create();Object.extend(b2NullContact.prototype,b2Contact.prototype);Object.extend(b2NullContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} +this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;},Evaluate:function(){},GetManifolds:function(){return null;}}); +var b2PolyAndCircleContact=Class.create();Object.extend(b2PolyAndCircleContact.prototype,b2Contact.prototype);Object.extend(b2PolyAndCircleContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} +this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;this.m_manifold=[new b2Manifold()];b2Settings.b2Assert(this.m_shape1.m_type==b2Shape.e_polyShape);b2Settings.b2Assert(this.m_shape2.m_type==b2Shape.e_circleShape);this.m_manifold[0].pointCount=0;this.m_manifold[0].points[0].normalImpulse=0.0;this.m_manifold[0].points[0].tangentImpulse=0.0;},Evaluate:function(){b2Collision.b2CollidePolyAndCircle(this.m_manifold[0],this.m_shape1,this.m_shape2,false);if(this.m_manifold[0].pointCount>0) +{this.m_manifoldCount=1;} +else +{this.m_manifoldCount=0;}},GetManifolds:function() +{return this.m_manifold;},m_manifold:[new b2Manifold()]}) +b2PolyAndCircleContact.Create=function(shape1,shape2,allocator){return new b2PolyAndCircleContact(shape1,shape2);};b2PolyAndCircleContact.Destroy=function(contact,allocator){}; +var b2PolyContact=Class.create();Object.extend(b2PolyContact.prototype,b2Contact.prototype);Object.extend(b2PolyContact.prototype,{initialize:function(s1,s2){this.m_node1=new b2ContactNode();this.m_node2=new b2ContactNode();this.m_flags=0;if(!s1||!s2){this.m_shape1=null;this.m_shape2=null;return;} +this.m_shape1=s1;this.m_shape2=s2;this.m_manifoldCount=0;this.m_friction=Math.sqrt(this.m_shape1.m_friction*this.m_shape2.m_friction);this.m_restitution=b2Math.b2Max(this.m_shape1.m_restitution,this.m_shape2.m_restitution);this.m_prev=null;this.m_next=null;this.m_node1.contact=null;this.m_node1.prev=null;this.m_node1.next=null;this.m_node1.other=null;this.m_node2.contact=null;this.m_node2.prev=null;this.m_node2.next=null;this.m_node2.other=null;this.m0=new b2Manifold();this.m_manifold=[new b2Manifold()];this.m_manifold[0].pointCount=0;},m0:new b2Manifold(),Evaluate:function(){var tMani=this.m_manifold[0];var tPoints=this.m0.points;for(var k=0;k0) +{var match=[false,false];for(var i=0;i0) +{var body1=c.m_shape1.m_body;var body2=c.m_shape2.m_body;var node1=c.m_node1;var node2=c.m_node2;body1.WakeUp();body2.WakeUp();if(node1.prev) +{node1.prev.next=node1.next;} +if(node1.next) +{node1.next.prev=node1.prev;} +if(node1==body1.m_contactList) +{body1.m_contactList=node1.next;} +node1.prev=null;node1.next=null;if(node2.prev) +{node2.prev.next=node2.next;} +if(node2.next) +{node2.next.prev=node2.prev;} +if(node2==body2.m_contactList) +{body2.m_contactList=node2.next;} +node2.prev=null;node2.next=null;} +b2Contact.Destroy(c,this.m_world.m_blockAllocator);--this.m_world.m_contactCount;},CleanContactList:function() +{var c=this.m_world.m_contactList;while(c!=null) +{var c0=c;c=c.m_next;if(c0.m_flags&b2Contact.e_destroyFlag) +{this.DestroyContact(c0);c0=null;}}},Collide:function() +{var body1;var body2;var node1;var node2;for(var c=this.m_world.m_contactList;c!=null;c=c.m_next) +{if(c.m_shape1.m_body.IsSleeping()&&c.m_shape2.m_body.IsSleeping()) +{continue;} +var oldCount=c.GetManifoldCount();c.Evaluate();var newCount=c.GetManifoldCount();if(oldCount==0&&newCount>0) +{body1=c.m_shape1.m_body;body2=c.m_shape2.m_body;node1=c.m_node1;node2=c.m_node2;node1.contact=c;node1.other=body2;node1.prev=null;node1.next=body1.m_contactList;if(node1.next!=null) +{node1.next.prev=c.m_node1;} +body1.m_contactList=c.m_node1;node2.contact=c;node2.other=body1;node2.prev=null;node2.next=body2.m_contactList;if(node2.next!=null) +{node2.next.prev=node2;} +body2.m_contactList=node2;} +else if(oldCount>0&&newCount==0) +{body1=c.m_shape1.m_body;body2=c.m_shape2.m_body;node1=c.m_node1;node2=c.m_node2;if(node1.prev) +{node1.prev.next=node1.next;} +if(node1.next) +{node1.next.prev=node1.prev;} +if(node1==body1.m_contactList) +{body1.m_contactList=node1.next;} +node1.prev=null;node1.next=null;if(node2.prev) +{node2.prev.next=node2.next;} +if(node2.next) +{node2.next.prev=node2.prev;} +if(node2==body2.m_contactList) +{body2.m_contactList=node2.next;} +node2.prev=null;node2.next=null;}}},m_world:null,m_nullContact:new b2NullContact(),m_destroyImmediate:null}); +var b2World=Class.create();b2World.prototype={initialize:function(worldAABB,gravity,doSleep){this.step=new b2TimeStep();this.m_contactManager=new b2ContactManager();this.m_listener=null;this.m_filter=b2CollisionFilter.b2_defaultFilter;this.m_bodyList=null;this.m_contactList=null;this.m_jointList=null;this.m_bodyCount=0;this.m_contactCount=0;this.m_jointCount=0;this.m_bodyDestroyList=null;this.m_allowSleep=doSleep;this.m_gravity=gravity;this.m_contactManager.m_world=this;this.m_broadPhase=new b2BroadPhase(worldAABB,this.m_contactManager);var bd=new b2BodyDef();this.m_groundBody=this.CreateBody(bd);},SetListener:function(listener){this.m_listener=listener;},SetFilter:function(filter){this.m_filter=filter;},CreateBody:function(def){var b=new b2Body(def,this);b.m_prev=null;b.m_next=this.m_bodyList;if(this.m_bodyList) +{this.m_bodyList.m_prev=b;} +this.m_bodyList=b;++this.m_bodyCount;return b;},DestroyBody:function(b) +{if(b.m_flags&b2Body.e_destroyFlag) +{return;} +if(b.m_prev) +{b.m_prev.m_next=b.m_next;} +if(b.m_next) +{b.m_next.m_prev=b.m_prev;} +if(b==this.m_bodyList) +{this.m_bodyList=b.m_next;} +b.m_flags|=b2Body.e_destroyFlag;--this.m_bodyCount;b.m_prev=null;b.m_next=this.m_bodyDestroyList;this.m_bodyDestroyList=b;},CleanBodyList:function() +{this.m_contactManager.m_destroyImmediate=true;var b=this.m_bodyDestroyList;while(b) +{var b0=b;b=b.m_next;var jn=b0.m_jointList;while(jn) +{var jn0=jn;jn=jn.next;if(this.m_listener) +{this.m_listener.NotifyJointDestroyed(jn0.joint);} +this.DestroyJoint(jn0.joint);} +b0.Destroy();} +this.m_bodyDestroyList=null;this.m_contactManager.m_destroyImmediate=false;},CreateJoint:function(def){var j=b2Joint.Create(def,this.m_blockAllocator);j.m_prev=null;j.m_next=this.m_jointList;if(this.m_jointList) +{this.m_jointList.m_prev=j;} +this.m_jointList=j;++this.m_jointCount;j.m_node1.joint=j;j.m_node1.other=j.m_body2;j.m_node1.prev=null;j.m_node1.next=j.m_body1.m_jointList;if(j.m_body1.m_jointList)j.m_body1.m_jointList.prev=j.m_node1;j.m_body1.m_jointList=j.m_node1;j.m_node2.joint=j;j.m_node2.other=j.m_body1;j.m_node2.prev=null;j.m_node2.next=j.m_body2.m_jointList;if(j.m_body2.m_jointList)j.m_body2.m_jointList.prev=j.m_node2;j.m_body2.m_jointList=j.m_node2;if(def.collideConnected==false) +{var b=def.body1.m_shapeCount0.0) +{this.step.inv_dt=1.0/dt;} +else +{this.step.inv_dt=0.0;} +this.m_positionIterationCount=0;this.m_contactManager.CleanContactList();this.CleanBodyList();this.m_contactManager.Collide();var island=new b2Island(this.m_bodyCount,this.m_contactCount,this.m_jointCount,this.m_stackAllocator);for(b=this.m_bodyList;b!=null;b=b.m_next) +{b.m_flags&=~b2Body.e_islandFlag;} +for(var c=this.m_contactList;c!=null;c=c.m_next) +{c.m_flags&=~b2Contact.e_islandFlag;} +for(var j=this.m_jointList;j!=null;j=j.m_next) +{j.m_islandFlag=false;} +var stackSize=this.m_bodyCount;var stack=new Array(this.m_bodyCount);for(var k=0;k0) +{b=stack[--stackCount];island.AddBody(b);b.m_flags&=~b2Body.e_sleepFlag;if(b.m_flags&b2Body.e_staticFlag) +{continue;} +for(var cn=b.m_contactList;cn!=null;cn=cn.next) +{if(cn.contact.m_flags&b2Contact.e_islandFlag) +{continue;} +island.AddContact(cn.contact);cn.contact.m_flags|=b2Contact.e_islandFlag;other=cn.other;if(other.m_flags&b2Body.e_islandFlag) +{continue;} +stack[stackCount++]=other;other.m_flags|=b2Body.e_islandFlag;} +for(var jn=b.m_jointList;jn!=null;jn=jn.next) +{if(jn.joint.m_islandFlag==true) +{continue;} +island.AddJoint(jn.joint);jn.joint.m_islandFlag=true;other=jn.other;if(other.m_flags&b2Body.e_islandFlag) +{continue;} +stack[stackCount++]=other;other.m_flags|=b2Body.e_islandFlag;}} +island.Solve(this.step,this.m_gravity);this.m_positionIterationCount=b2Math.b2Max(this.m_positionIterationCount,b2Island.m_positionIterationCount);if(this.m_allowSleep) +{island.UpdateSleep(dt);} +for(var i=0;ib2Settings.b2_linearSlop) +{this.m_u.Multiply(1.0/length);} +else +{this.m_u.SetZero();} +var cr1u=(r1X*this.m_u.y-r1Y*this.m_u.x);var cr2u=(r2X*this.m_u.y-r2Y*this.m_u.x);this.m_mass=this.m_body1.m_invMass+this.m_body1.m_invI*cr1u*cr1u+this.m_body2.m_invMass+this.m_body2.m_invI*cr2u*cr2u;this.m_mass=1.0/this.m_mass;if(b2World.s_enableWarmStarting) +{var PX=this.m_impulse*this.m_u.x;var PY=this.m_impulse*this.m_u.y;this.m_body1.m_linearVelocity.x-=this.m_body1.m_invMass*PX;this.m_body1.m_linearVelocity.y-=this.m_body1.m_invMass*PY;this.m_body1.m_angularVelocity-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_linearVelocity.x+=this.m_body2.m_invMass*PX;this.m_body2.m_linearVelocity.y+=this.m_body2.m_invMass*PY;this.m_body2.m_angularVelocity+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);} +else +{this.m_impulse=0.0;}},SolveVelocityConstraints:function(step){var tMat;tMat=this.m_body1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=this.m_body2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var v1X=this.m_body1.m_linearVelocity.x+(-this.m_body1.m_angularVelocity*r1Y);var v1Y=this.m_body1.m_linearVelocity.y+(this.m_body1.m_angularVelocity*r1X);var v2X=this.m_body2.m_linearVelocity.x+(-this.m_body2.m_angularVelocity*r2Y);var v2Y=this.m_body2.m_linearVelocity.y+(this.m_body2.m_angularVelocity*r2X);var Cdot=(this.m_u.x*(v2X-v1X)+this.m_u.y*(v2Y-v1Y));var impulse=-this.m_mass*Cdot;this.m_impulse+=impulse;var PX=impulse*this.m_u.x;var PY=impulse*this.m_u.y;this.m_body1.m_linearVelocity.x-=this.m_body1.m_invMass*PX;this.m_body1.m_linearVelocity.y-=this.m_body1.m_invMass*PY;this.m_body1.m_angularVelocity-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_linearVelocity.x+=this.m_body2.m_invMass*PX;this.m_body2.m_linearVelocity.y+=this.m_body2.m_invMass*PY;this.m_body2.m_angularVelocity+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);},SolvePositionConstraints:function(){var tMat;tMat=this.m_body1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=this.m_body2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var dX=this.m_body2.m_position.x+r2X-this.m_body1.m_position.x-r1X;var dY=this.m_body2.m_position.y+r2Y-this.m_body1.m_position.y-r1Y;var length=Math.sqrt(dX*dX+dY*dY);dX/=length;dY/=length;var C=length-this.m_length;C=b2Math.b2Clamp(C,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);var impulse=-this.m_mass*C;this.m_u.Set(dX,dY);var PX=impulse*this.m_u.x;var PY=impulse*this.m_u.y;this.m_body1.m_position.x-=this.m_body1.m_invMass*PX;this.m_body1.m_position.y-=this.m_body1.m_invMass*PY;this.m_body1.m_rotation-=this.m_body1.m_invI*(r1X*PY-r1Y*PX);this.m_body2.m_position.x+=this.m_body2.m_invMass*PX;this.m_body2.m_position.y+=this.m_body2.m_invMass*PY;this.m_body2.m_rotation+=this.m_body2.m_invI*(r2X*PY-r2Y*PX);this.m_body1.m_R.Set(this.m_body1.m_rotation);this.m_body2.m_R.Set(this.m_body2.m_rotation);return b2Math.b2Abs(C)step.dt*this.m_maxForce) +{this.m_impulse.Multiply(step.dt*this.m_maxForce/length);} +impulseX=this.m_impulse.x-oldImpulseX;impulseY=this.m_impulse.y-oldImpulseY;body.m_linearVelocity.x+=body.m_invMass*impulseX;body.m_linearVelocity.y+=body.m_invMass*impulseY;body.m_angularVelocity+=body.m_invI*(rX*impulseY-rY*impulseX);},SolvePositionConstraints:function(){return true;},m_localAnchor:new b2Vec2(),m_target:new b2Vec2(),m_impulse:new b2Vec2(),m_ptpMass:new b2Mat22(),m_C:new b2Vec2(),m_maxForce:null,m_beta:null,m_gamma:null}); +var b2MouseJointDef=Class.create();Object.extend(b2MouseJointDef.prototype,b2JointDef.prototype);Object.extend(b2MouseJointDef.prototype,{initialize:function() +{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.target=new b2Vec2();this.type=b2Joint.e_mouseJoint;this.maxForce=0.0;this.frequencyHz=5.0;this.dampingRatio=0.7;this.timeStep=1.0/60.0;},target:new b2Vec2(),maxForce:null,frequencyHz:null,dampingRatio:null,timeStep:null}); +var b2PrismaticJoint=Class.create();Object.extend(b2PrismaticJoint.prototype,b2Joint.prototype);Object.extend(b2PrismaticJoint.prototype,{GetAnchor1:function(){var b1=this.m_body1;var tVec=new b2Vec2();tVec.SetV(this.m_localAnchor1);tVec.MulM(b1.m_R);tVec.Add(b1.m_position);return tVec;},GetAnchor2:function(){var b2=this.m_body2;var tVec=new b2Vec2();tVec.SetV(this.m_localAnchor2);tVec.MulM(b2.m_R);tVec.Add(b2.m_position);return tVec;},GetJointTranslation:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var translation=ax1X*dX+ax1Y*dY;return translation;},GetJointSpeed:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var v1=b1.m_linearVelocity;var v2=b2.m_linearVelocity;var w1=b1.m_angularVelocity;var w2=b2.m_angularVelocity;var speed=(dX*(-w1*ax1Y)+dY*(w1*ax1X))+(ax1X*(((v2.x+(-w2*r2Y))-v1.x)-(-w1*r1Y))+ax1Y*(((v2.y+(w2*r2X))-v1.y)-(w1*r1X)));return speed;},GetMotorForce:function(invTimeStep){return invTimeStep*this.m_motorImpulse;},SetMotorSpeed:function(speed) +{this.m_motorSpeed=speed;},SetMotorForce:function(force) +{this.m_maxMotorForce=force;},GetReactionForce:function(invTimeStep) +{var tImp=invTimeStep*this.m_limitImpulse;var tMat;tMat=this.m_body1.m_R;var ax1X=tImp*(tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y);var ax1Y=tImp*(tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y);var ay1X=tImp*(tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y);var ay1Y=tImp*(tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y);return new b2Vec2(ax1X+ay1X,ax1Y+ay1Y);},GetReactionTorque:function(invTimeStep) +{return invTimeStep*this.m_angularImpulse;},initialize:function(def){this.m_node1=new b2JointNode();this.m_node2=new b2JointNode();this.m_type=def.type;this.m_prev=null;this.m_next=null;this.m_body1=def.body1;this.m_body2=def.body2;this.m_collideConnected=def.collideConnected;this.m_islandFlag=false;this.m_userData=def.userData;this.m_localAnchor1=new b2Vec2();this.m_localAnchor2=new b2Vec2();this.m_localXAxis1=new b2Vec2();this.m_localYAxis1=new b2Vec2();this.m_linearJacobian=new b2Jacobian();this.m_motorJacobian=new b2Jacobian();var tMat;var tX;var tY;tMat=this.m_body1.m_R;tX=(def.anchorPoint.x-this.m_body1.m_position.x);tY=(def.anchorPoint.y-this.m_body1.m_position.y);this.m_localAnchor1.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));tMat=this.m_body2.m_R;tX=(def.anchorPoint.x-this.m_body2.m_position.x);tY=(def.anchorPoint.y-this.m_body2.m_position.y);this.m_localAnchor2.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));tMat=this.m_body1.m_R;tX=def.axis.x;tY=def.axis.y;this.m_localXAxis1.Set((tX*tMat.col1.x+tY*tMat.col1.y),(tX*tMat.col2.x+tY*tMat.col2.y));this.m_localYAxis1.x=-this.m_localXAxis1.y;this.m_localYAxis1.y=this.m_localXAxis1.x;this.m_initialAngle=this.m_body2.m_rotation-this.m_body1.m_rotation;this.m_linearJacobian.SetZero();this.m_linearMass=0.0;this.m_linearImpulse=0.0;this.m_angularMass=0.0;this.m_angularImpulse=0.0;this.m_motorJacobian.SetZero();this.m_motorMass=0.0;this.m_motorImpulse=0.0;this.m_limitImpulse=0.0;this.m_limitPositionImpulse=0.0;this.m_lowerTranslation=def.lowerTranslation;this.m_upperTranslation=def.upperTranslation;this.m_maxMotorForce=def.motorForce;this.m_motorSpeed=def.motorSpeed;this.m_enableLimit=def.enableLimit;this.m_enableMotor=def.enableMotor;},PrepareVelocitySolver:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;tMat=b1.m_R;var ay1X=tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y;var ay1Y=tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y;var eX=b2.m_position.x+r2X-b1.m_position.x;var eY=b2.m_position.y+r2Y-b1.m_position.y;this.m_linearJacobian.linear1.x=-ay1X;this.m_linearJacobian.linear1.y=-ay1Y;this.m_linearJacobian.linear2.x=ay1X;this.m_linearJacobian.linear2.y=ay1Y;this.m_linearJacobian.angular1=-(eX*ay1Y-eY*ay1X);this.m_linearJacobian.angular2=r2X*ay1Y-r2Y*ay1X;this.m_linearMass=invMass1+invI1*this.m_linearJacobian.angular1*this.m_linearJacobian.angular1+ +invMass2+invI2*this.m_linearJacobian.angular2*this.m_linearJacobian.angular2;this.m_linearMass=1.0/this.m_linearMass;this.m_angularMass=1.0/(invI1+invI2);if(this.m_enableLimit||this.m_enableMotor) +{tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;this.m_motorJacobian.linear1.x=-ax1X;this.m_motorJacobian.linear1.y=-ax1Y;this.m_motorJacobian.linear2.x=ax1X;this.m_motorJacobian.linear2.y=ax1Y;this.m_motorJacobian.angular1=-(eX*ax1Y-eY*ax1X);this.m_motorJacobian.angular2=r2X*ax1Y-r2Y*ax1X;this.m_motorMass=invMass1+invI1*this.m_motorJacobian.angular1*this.m_motorJacobian.angular1+ +invMass2+invI2*this.m_motorJacobian.angular2*this.m_motorJacobian.angular2;this.m_motorMass=1.0/this.m_motorMass;if(this.m_enableLimit) +{var dX=eX-r1X;var dY=eY-r1Y;var jointTranslation=ax1X*dX+ax1Y*dY;if(b2Math.b2Abs(this.m_upperTranslation-this.m_lowerTranslation)<2.0*b2Settings.b2_linearSlop) +{this.m_limitState=b2Joint.e_equalLimits;} +else if(jointTranslation<=this.m_lowerTranslation) +{if(this.m_limitState!=b2Joint.e_atLowerLimit) +{this.m_limitImpulse=0.0;} +this.m_limitState=b2Joint.e_atLowerLimit;} +else if(jointTranslation>=this.m_upperTranslation) +{if(this.m_limitState!=b2Joint.e_atUpperLimit) +{this.m_limitImpulse=0.0;} +this.m_limitState=b2Joint.e_atUpperLimit;} +else +{this.m_limitState=b2Joint.e_inactiveLimit;this.m_limitImpulse=0.0;}}} +if(this.m_enableMotor==false) +{this.m_motorImpulse=0.0;} +if(this.m_enableLimit==false) +{this.m_limitImpulse=0.0;} +if(b2World.s_enableWarmStarting) +{var P1X=this.m_linearImpulse*this.m_linearJacobian.linear1.x+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear1.x;var P1Y=this.m_linearImpulse*this.m_linearJacobian.linear1.y+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear1.y;var P2X=this.m_linearImpulse*this.m_linearJacobian.linear2.x+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear2.x;var P2Y=this.m_linearImpulse*this.m_linearJacobian.linear2.y+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.linear2.y;var L1=this.m_linearImpulse*this.m_linearJacobian.angular1-this.m_angularImpulse+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.angular1;var L2=this.m_linearImpulse*this.m_linearJacobian.angular2+this.m_angularImpulse+(this.m_motorImpulse+this.m_limitImpulse)*this.m_motorJacobian.angular2;b1.m_linearVelocity.x+=invMass1*P1X;b1.m_linearVelocity.y+=invMass1*P1Y;b1.m_angularVelocity+=invI1*L1;b2.m_linearVelocity.x+=invMass2*P2X;b2.m_linearVelocity.y+=invMass2*P2Y;b2.m_angularVelocity+=invI2*L2;} +else +{this.m_linearImpulse=0.0;this.m_angularImpulse=0.0;this.m_limitImpulse=0.0;this.m_motorImpulse=0.0;} +this.m_limitPositionImpulse=0.0;},SolveVelocityConstraints:function(step){var b1=this.m_body1;var b2=this.m_body2;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;var oldLimitImpulse;var linearCdot=this.m_linearJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity);var linearImpulse=-this.m_linearMass*linearCdot;this.m_linearImpulse+=linearImpulse;b1.m_linearVelocity.x+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.y;b1.m_angularVelocity+=invI1*linearImpulse*this.m_linearJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.y;b2.m_angularVelocity+=invI2*linearImpulse*this.m_linearJacobian.angular2;var angularCdot=b2.m_angularVelocity-b1.m_angularVelocity;var angularImpulse=-this.m_angularMass*angularCdot;this.m_angularImpulse+=angularImpulse;b1.m_angularVelocity-=invI1*angularImpulse;b2.m_angularVelocity+=invI2*angularImpulse;if(this.m_enableMotor&&this.m_limitState!=b2Joint.e_equalLimits) +{var motorCdot=this.m_motorJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity)-this.m_motorSpeed;var motorImpulse=-this.m_motorMass*motorCdot;var oldMotorImpulse=this.m_motorImpulse;this.m_motorImpulse=b2Math.b2Clamp(this.m_motorImpulse+motorImpulse,-step.dt*this.m_maxMotorForce,step.dt*this.m_maxMotorForce);motorImpulse=this.m_motorImpulse-oldMotorImpulse;b1.m_linearVelocity.x+=(invMass1*motorImpulse)*this.m_motorJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*motorImpulse)*this.m_motorJacobian.linear1.y;b1.m_angularVelocity+=invI1*motorImpulse*this.m_motorJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*motorImpulse)*this.m_motorJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*motorImpulse)*this.m_motorJacobian.linear2.y;b2.m_angularVelocity+=invI2*motorImpulse*this.m_motorJacobian.angular2;} +if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) +{var limitCdot=this.m_motorJacobian.Compute(b1.m_linearVelocity,b1.m_angularVelocity,b2.m_linearVelocity,b2.m_angularVelocity);var limitImpulse=-this.m_motorMass*limitCdot;if(this.m_limitState==b2Joint.e_equalLimits) +{this.m_limitImpulse+=limitImpulse;} +else if(this.m_limitState==b2Joint.e_atLowerLimit) +{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Max(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} +else if(this.m_limitState==b2Joint.e_atUpperLimit) +{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Min(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} +b1.m_linearVelocity.x+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.x;b1.m_linearVelocity.y+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.y;b1.m_angularVelocity+=invI1*limitImpulse*this.m_motorJacobian.angular1;b2.m_linearVelocity.x+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.x;b2.m_linearVelocity.y+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.y;b2.m_angularVelocity+=invI2*limitImpulse*this.m_motorJacobian.angular2;}},SolvePositionConstraints:function(){var limitC;var oldLimitImpulse;var b1=this.m_body1;var b2=this.m_body2;var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var dX=p2X-p1X;var dY=p2Y-p1Y;tMat=b1.m_R;var ay1X=tMat.col1.x*this.m_localYAxis1.x+tMat.col2.x*this.m_localYAxis1.y;var ay1Y=tMat.col1.y*this.m_localYAxis1.x+tMat.col2.y*this.m_localYAxis1.y;var linearC=ay1X*dX+ay1Y*dY;linearC=b2Math.b2Clamp(linearC,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);var linearImpulse=-this.m_linearMass*linearC;b1.m_position.x+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.x;b1.m_position.y+=(invMass1*linearImpulse)*this.m_linearJacobian.linear1.y;b1.m_rotation+=invI1*linearImpulse*this.m_linearJacobian.angular1;b2.m_position.x+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.x;b2.m_position.y+=(invMass2*linearImpulse)*this.m_linearJacobian.linear2.y;b2.m_rotation+=invI2*linearImpulse*this.m_linearJacobian.angular2;var positionError=b2Math.b2Abs(linearC);var angularC=b2.m_rotation-b1.m_rotation-this.m_initialAngle;angularC=b2Math.b2Clamp(angularC,-b2Settings.b2_maxAngularCorrection,b2Settings.b2_maxAngularCorrection);var angularImpulse=-this.m_angularMass*angularC;b1.m_rotation-=b1.m_invI*angularImpulse;b1.m_R.Set(b1.m_rotation);b2.m_rotation+=b2.m_invI*angularImpulse;b2.m_R.Set(b2.m_rotation);var angularError=b2Math.b2Abs(angularC);if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) +{tMat=b1.m_R;r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;p1X=b1.m_position.x+r1X;p1Y=b1.m_position.y+r1Y;p2X=b2.m_position.x+r2X;p2Y=b2.m_position.y+r2Y;dX=p2X-p1X;dY=p2Y-p1Y;tMat=b1.m_R;var ax1X=tMat.col1.x*this.m_localXAxis1.x+tMat.col2.x*this.m_localXAxis1.y;var ax1Y=tMat.col1.y*this.m_localXAxis1.x+tMat.col2.y*this.m_localXAxis1.y;var translation=(ax1X*dX+ax1Y*dY);var limitImpulse=0.0;if(this.m_limitState==b2Joint.e_equalLimits) +{limitC=b2Math.b2Clamp(translation,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);limitImpulse=-this.m_motorMass*limitC;positionError=b2Math.b2Max(positionError,b2Math.b2Abs(angularC));} +else if(this.m_limitState==b2Joint.e_atLowerLimit) +{limitC=translation-this.m_lowerTranslation;positionError=b2Math.b2Max(positionError,-limitC);limitC=b2Math.b2Clamp(limitC+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Max(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} +else if(this.m_limitState==b2Joint.e_atUpperLimit) +{limitC=translation-this.m_upperTranslation;positionError=b2Math.b2Max(positionError,limitC);limitC=b2Math.b2Clamp(limitC-b2Settings.b2_linearSlop,0.0,b2Settings.b2_maxLinearCorrection);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Min(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} +b1.m_position.x+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.x;b1.m_position.y+=(invMass1*limitImpulse)*this.m_motorJacobian.linear1.y;b1.m_rotation+=invI1*limitImpulse*this.m_motorJacobian.angular1;b1.m_R.Set(b1.m_rotation);b2.m_position.x+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.x;b2.m_position.y+=(invMass2*limitImpulse)*this.m_motorJacobian.linear2.y;b2.m_rotation+=invI2*limitImpulse*this.m_motorJacobian.angular2;b2.m_R.Set(b2.m_rotation);} +return positionError<=b2Settings.b2_linearSlop&&angularError<=b2Settings.b2_angularSlop;},m_localAnchor1:new b2Vec2(),m_localAnchor2:new b2Vec2(),m_localXAxis1:new b2Vec2(),m_localYAxis1:new b2Vec2(),m_initialAngle:null,m_linearJacobian:new b2Jacobian(),m_linearMass:null,m_linearImpulse:null,m_angularMass:null,m_angularImpulse:null,m_motorJacobian:new b2Jacobian(),m_motorMass:null,m_motorImpulse:null,m_limitImpulse:null,m_limitPositionImpulse:null,m_lowerTranslation:null,m_upperTranslation:null,m_maxMotorForce:null,m_motorSpeed:null,m_enableLimit:null,m_enableMotor:null,m_limitState:0}); +var b2PrismaticJointDef=Class.create();Object.extend(b2PrismaticJointDef.prototype,b2JointDef.prototype);Object.extend(b2PrismaticJointDef.prototype,{initialize:function() +{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.type=b2Joint.e_prismaticJoint;this.anchorPoint=new b2Vec2(0.0,0.0);this.axis=new b2Vec2(0.0,0.0);this.lowerTranslation=0.0;this.upperTranslation=0.0;this.motorForce=0.0;this.motorSpeed=0.0;this.enableLimit=false;this.enableMotor=false;},anchorPoint:null,axis:null,lowerTranslation:null,upperTranslation:null,motorForce:null,motorSpeed:null,enableLimit:null,enableMotor:null}); +var b2PulleyJoint=Class.create();Object.extend(b2PulleyJoint.prototype,b2Joint.prototype);Object.extend(b2PulleyJoint.prototype,{GetAnchor1:function(){var tMat=this.m_body1.m_R;return new b2Vec2(this.m_body1.m_position.x+(tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y),this.m_body1.m_position.y+(tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y));},GetAnchor2:function(){var tMat=this.m_body2.m_R;return new b2Vec2(this.m_body2.m_position.x+(tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y),this.m_body2.m_position.y+(tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y));},GetGroundPoint1:function(){return new b2Vec2(this.m_ground.m_position.x+this.m_groundAnchor1.x,this.m_ground.m_position.y+this.m_groundAnchor1.y);},GetGroundPoint2:function(){return new b2Vec2(this.m_ground.m_position.x+this.m_groundAnchor2.x,this.m_ground.m_position.y+this.m_groundAnchor2.y);},GetReactionForce:function(invTimeStep){return new b2Vec2();},GetReactionTorque:function(invTimeStep){return 0.0;},GetLength1:function(){var tMat;tMat=this.m_body1.m_R;var pX=this.m_body1.m_position.x+(tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y);var pY=this.m_body1.m_position.y+(tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y);var dX=pX-(this.m_ground.m_position.x+this.m_groundAnchor1.x);var dY=pY-(this.m_ground.m_position.y+this.m_groundAnchor1.y);return Math.sqrt(dX*dX+dY*dY);},GetLength2:function(){var tMat;tMat=this.m_body2.m_R;var pX=this.m_body2.m_position.x+(tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y);var pY=this.m_body2.m_position.y+(tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y);var dX=pX-(this.m_ground.m_position.x+this.m_groundAnchor2.x);var dY=pY-(this.m_ground.m_position.y+this.m_groundAnchor2.y);return Math.sqrt(dX*dX+dY*dY);},GetRatio:function(){return this.m_ratio;},initialize:function(def){this.m_node1=new b2JointNode();this.m_node2=new b2JointNode();this.m_type=def.type;this.m_prev=null;this.m_next=null;this.m_body1=def.body1;this.m_body2=def.body2;this.m_collideConnected=def.collideConnected;this.m_islandFlag=false;this.m_userData=def.userData;this.m_groundAnchor1=new b2Vec2();this.m_groundAnchor2=new b2Vec2();this.m_localAnchor1=new b2Vec2();this.m_localAnchor2=new b2Vec2();this.m_u1=new b2Vec2();this.m_u2=new b2Vec2();var tMat;var tX;var tY;this.m_ground=this.m_body1.m_world.m_groundBody;this.m_groundAnchor1.x=def.groundPoint1.x-this.m_ground.m_position.x;this.m_groundAnchor1.y=def.groundPoint1.y-this.m_ground.m_position.y;this.m_groundAnchor2.x=def.groundPoint2.x-this.m_ground.m_position.x;this.m_groundAnchor2.y=def.groundPoint2.y-this.m_ground.m_position.y;tMat=this.m_body1.m_R;tX=def.anchorPoint1.x-this.m_body1.m_position.x;tY=def.anchorPoint1.y-this.m_body1.m_position.y;this.m_localAnchor1.x=tX*tMat.col1.x+tY*tMat.col1.y;this.m_localAnchor1.y=tX*tMat.col2.x+tY*tMat.col2.y;tMat=this.m_body2.m_R;tX=def.anchorPoint2.x-this.m_body2.m_position.x;tY=def.anchorPoint2.y-this.m_body2.m_position.y;this.m_localAnchor2.x=tX*tMat.col1.x+tY*tMat.col1.y;this.m_localAnchor2.y=tX*tMat.col2.x+tY*tMat.col2.y;this.m_ratio=def.ratio;tX=def.groundPoint1.x-def.anchorPoint1.x;tY=def.groundPoint1.y-def.anchorPoint1.y;var d1Len=Math.sqrt(tX*tX+tY*tY);tX=def.groundPoint2.x-def.anchorPoint2.x;tY=def.groundPoint2.y-def.anchorPoint2.y;var d2Len=Math.sqrt(tX*tX+tY*tY);var length1=b2Math.b2Max(0.5*b2PulleyJoint.b2_minPulleyLength,d1Len);var length2=b2Math.b2Max(0.5*b2PulleyJoint.b2_minPulleyLength,d2Len);this.m_constant=length1+this.m_ratio*length2;this.m_maxLength1=b2Math.b2Clamp(def.maxLength1,length1,this.m_constant-this.m_ratio*b2PulleyJoint.b2_minPulleyLength);this.m_maxLength2=b2Math.b2Clamp(def.maxLength2,length2,(this.m_constant-b2PulleyJoint.b2_minPulleyLength)/this.m_ratio);this.m_pulleyImpulse=0.0;this.m_limitImpulse1=0.0;this.m_limitImpulse2=0.0;},PrepareVelocitySolver:function(){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var s1X=this.m_ground.m_position.x+this.m_groundAnchor1.x;var s1Y=this.m_ground.m_position.y+this.m_groundAnchor1.y;var s2X=this.m_ground.m_position.x+this.m_groundAnchor2.x;var s2Y=this.m_ground.m_position.y+this.m_groundAnchor2.y;this.m_u1.Set(p1X-s1X,p1Y-s1Y);this.m_u2.Set(p2X-s2X,p2Y-s2Y);var length1=this.m_u1.Length();var length2=this.m_u2.Length();if(length1>b2Settings.b2_linearSlop) +{this.m_u1.Multiply(1.0/length1);} +else +{this.m_u1.SetZero();} +if(length2>b2Settings.b2_linearSlop) +{this.m_u2.Multiply(1.0/length2);} +else +{this.m_u2.SetZero();} +if(length1b2Settings.b2_linearSlop) +{this.m_u1.Multiply(1.0/length1);} +else +{this.m_u1.SetZero();} +if(length2>b2Settings.b2_linearSlop) +{this.m_u2.Multiply(1.0/length2);} +else +{this.m_u2.SetZero();} +C=this.m_constant-length1-this.m_ratio*length2;linearError=b2Math.b2Max(linearError,Math.abs(C));C=b2Math.b2Clamp(C,-b2Settings.b2_maxLinearCorrection,b2Settings.b2_maxLinearCorrection);impulse=-this.m_pulleyMass*C;p1X=-impulse*this.m_u1.x;p1Y=-impulse*this.m_u1.y;p2X=-this.m_ratio*impulse*this.m_u2.x;p2Y=-this.m_ratio*impulse*this.m_u2.y;b1.m_position.x+=b1.m_invMass*p1X;b1.m_position.y+=b1.m_invMass*p1Y;b1.m_rotation+=b1.m_invI*(r1X*p1Y-r1Y*p1X);b2.m_position.x+=b2.m_invMass*p2X;b2.m_position.y+=b2.m_invMass*p2Y;b2.m_rotation+=b2.m_invI*(r2X*p2Y-r2Y*p2X);b1.m_R.Set(b1.m_rotation);b2.m_R.Set(b2.m_rotation);} +if(this.m_limitState1==b2Joint.e_atUpperLimit) +{tMat=b1.m_R;r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;p1X=b1.m_position.x+r1X;p1Y=b1.m_position.y+r1Y;this.m_u1.Set(p1X-s1X,p1Y-s1Y);length1=this.m_u1.Length();if(length1>b2Settings.b2_linearSlop) +{this.m_u1.x*=1.0/length1;this.m_u1.y*=1.0/length1;} +else +{this.m_u1.SetZero();} +C=this.m_maxLength1-length1;linearError=b2Math.b2Max(linearError,-C);C=b2Math.b2Clamp(C+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);impulse=-this.m_limitMass1*C;oldLimitPositionImpulse=this.m_limitPositionImpulse1;this.m_limitPositionImpulse1=b2Math.b2Max(0.0,this.m_limitPositionImpulse1+impulse);impulse=this.m_limitPositionImpulse1-oldLimitPositionImpulse;p1X=-impulse*this.m_u1.x;p1Y=-impulse*this.m_u1.y;b1.m_position.x+=b1.m_invMass*p1X;b1.m_position.y+=b1.m_invMass*p1Y;b1.m_rotation+=b1.m_invI*(r1X*p1Y-r1Y*p1X);b1.m_R.Set(b1.m_rotation);} +if(this.m_limitState2==b2Joint.e_atUpperLimit) +{tMat=b2.m_R;r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;p2X=b2.m_position.x+r2X;p2Y=b2.m_position.y+r2Y;this.m_u2.Set(p2X-s2X,p2Y-s2Y);length2=this.m_u2.Length();if(length2>b2Settings.b2_linearSlop) +{this.m_u2.x*=1.0/length2;this.m_u2.y*=1.0/length2;} +else +{this.m_u2.SetZero();} +C=this.m_maxLength2-length2;linearError=b2Math.b2Max(linearError,-C);C=b2Math.b2Clamp(C+b2Settings.b2_linearSlop,-b2Settings.b2_maxLinearCorrection,0.0);impulse=-this.m_limitMass2*C;oldLimitPositionImpulse=this.m_limitPositionImpulse2;this.m_limitPositionImpulse2=b2Math.b2Max(0.0,this.m_limitPositionImpulse2+impulse);impulse=this.m_limitPositionImpulse2-oldLimitPositionImpulse;p2X=-impulse*this.m_u2.x;p2Y=-impulse*this.m_u2.y;b2.m_position.x+=b2.m_invMass*p2X;b2.m_position.y+=b2.m_invMass*p2Y;b2.m_rotation+=b2.m_invI*(r2X*p2Y-r2Y*p2X);b2.m_R.Set(b2.m_rotation);} +return linearError=this.m_upperAngle) +{if(this.m_limitState!=b2Joint.e_atUpperLimit) +{this.m_limitImpulse=0.0;} +this.m_limitState=b2Joint.e_atUpperLimit;} +else +{this.m_limitState=b2Joint.e_inactiveLimit;this.m_limitImpulse=0.0;}} +else +{this.m_limitImpulse=0.0;} +if(b2World.s_enableWarmStarting) +{b1.m_linearVelocity.x-=invMass1*this.m_ptpImpulse.x;b1.m_linearVelocity.y-=invMass1*this.m_ptpImpulse.y;b1.m_angularVelocity-=invI1*((r1X*this.m_ptpImpulse.y-r1Y*this.m_ptpImpulse.x)+this.m_motorImpulse+this.m_limitImpulse);b2.m_linearVelocity.x+=invMass2*this.m_ptpImpulse.x;b2.m_linearVelocity.y+=invMass2*this.m_ptpImpulse.y;b2.m_angularVelocity+=invI2*((r2X*this.m_ptpImpulse.y-r2Y*this.m_ptpImpulse.x)+this.m_motorImpulse+this.m_limitImpulse);} +else{this.m_ptpImpulse.SetZero();this.m_motorImpulse=0.0;this.m_limitImpulse=0.0;} +this.m_limitPositionImpulse=0.0;},SolveVelocityConstraints:function(step){var b1=this.m_body1;var b2=this.m_body2;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var oldLimitImpulse;var ptpCdotX=b2.m_linearVelocity.x+(-b2.m_angularVelocity*r2Y)-b1.m_linearVelocity.x-(-b1.m_angularVelocity*r1Y);var ptpCdotY=b2.m_linearVelocity.y+(b2.m_angularVelocity*r2X)-b1.m_linearVelocity.y-(b1.m_angularVelocity*r1X);var ptpImpulseX=-(this.m_ptpMass.col1.x*ptpCdotX+this.m_ptpMass.col2.x*ptpCdotY);var ptpImpulseY=-(this.m_ptpMass.col1.y*ptpCdotX+this.m_ptpMass.col2.y*ptpCdotY);this.m_ptpImpulse.x+=ptpImpulseX;this.m_ptpImpulse.y+=ptpImpulseY;b1.m_linearVelocity.x-=b1.m_invMass*ptpImpulseX;b1.m_linearVelocity.y-=b1.m_invMass*ptpImpulseY;b1.m_angularVelocity-=b1.m_invI*(r1X*ptpImpulseY-r1Y*ptpImpulseX);b2.m_linearVelocity.x+=b2.m_invMass*ptpImpulseX;b2.m_linearVelocity.y+=b2.m_invMass*ptpImpulseY;b2.m_angularVelocity+=b2.m_invI*(r2X*ptpImpulseY-r2Y*ptpImpulseX);if(this.m_enableMotor&&this.m_limitState!=b2Joint.e_equalLimits) +{var motorCdot=b2.m_angularVelocity-b1.m_angularVelocity-this.m_motorSpeed;var motorImpulse=-this.m_motorMass*motorCdot;var oldMotorImpulse=this.m_motorImpulse;this.m_motorImpulse=b2Math.b2Clamp(this.m_motorImpulse+motorImpulse,-step.dt*this.m_maxMotorTorque,step.dt*this.m_maxMotorTorque);motorImpulse=this.m_motorImpulse-oldMotorImpulse;b1.m_angularVelocity-=b1.m_invI*motorImpulse;b2.m_angularVelocity+=b2.m_invI*motorImpulse;} +if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) +{var limitCdot=b2.m_angularVelocity-b1.m_angularVelocity;var limitImpulse=-this.m_motorMass*limitCdot;if(this.m_limitState==b2Joint.e_equalLimits) +{this.m_limitImpulse+=limitImpulse;} +else if(this.m_limitState==b2Joint.e_atLowerLimit) +{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Max(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} +else if(this.m_limitState==b2Joint.e_atUpperLimit) +{oldLimitImpulse=this.m_limitImpulse;this.m_limitImpulse=b2Math.b2Min(this.m_limitImpulse+limitImpulse,0.0);limitImpulse=this.m_limitImpulse-oldLimitImpulse;} +b1.m_angularVelocity-=b1.m_invI*limitImpulse;b2.m_angularVelocity+=b2.m_invI*limitImpulse;}},SolvePositionConstraints:function(){var oldLimitImpulse;var limitC;var b1=this.m_body1;var b2=this.m_body2;var positionError=0.0;var tMat;tMat=b1.m_R;var r1X=tMat.col1.x*this.m_localAnchor1.x+tMat.col2.x*this.m_localAnchor1.y;var r1Y=tMat.col1.y*this.m_localAnchor1.x+tMat.col2.y*this.m_localAnchor1.y;tMat=b2.m_R;var r2X=tMat.col1.x*this.m_localAnchor2.x+tMat.col2.x*this.m_localAnchor2.y;var r2Y=tMat.col1.y*this.m_localAnchor2.x+tMat.col2.y*this.m_localAnchor2.y;var p1X=b1.m_position.x+r1X;var p1Y=b1.m_position.y+r1Y;var p2X=b2.m_position.x+r2X;var p2Y=b2.m_position.y+r2Y;var ptpCX=p2X-p1X;var ptpCY=p2Y-p1Y;positionError=Math.sqrt(ptpCX*ptpCX+ptpCY*ptpCY);var invMass1=b1.m_invMass;var invMass2=b2.m_invMass;var invI1=b1.m_invI;var invI2=b2.m_invI;this.K1.col1.x=invMass1+invMass2;this.K1.col2.x=0.0;this.K1.col1.y=0.0;this.K1.col2.y=invMass1+invMass2;this.K2.col1.x=invI1*r1Y*r1Y;this.K2.col2.x=-invI1*r1X*r1Y;this.K2.col1.y=-invI1*r1X*r1Y;this.K2.col2.y=invI1*r1X*r1X;this.K3.col1.x=invI2*r2Y*r2Y;this.K3.col2.x=-invI2*r2X*r2Y;this.K3.col1.y=-invI2*r2X*r2Y;this.K3.col2.y=invI2*r2X*r2X;this.K.SetM(this.K1);this.K.AddM(this.K2);this.K.AddM(this.K3);this.K.Solve(b2RevoluteJoint.tImpulse,-ptpCX,-ptpCY);var impulseX=b2RevoluteJoint.tImpulse.x;var impulseY=b2RevoluteJoint.tImpulse.y;b1.m_position.x-=b1.m_invMass*impulseX;b1.m_position.y-=b1.m_invMass*impulseY;b1.m_rotation-=b1.m_invI*(r1X*impulseY-r1Y*impulseX);b1.m_R.Set(b1.m_rotation);b2.m_position.x+=b2.m_invMass*impulseX;b2.m_position.y+=b2.m_invMass*impulseY;b2.m_rotation+=b2.m_invI*(r2X*impulseY-r2Y*impulseX);b2.m_R.Set(b2.m_rotation);var angularError=0.0;if(this.m_enableLimit&&this.m_limitState!=b2Joint.e_inactiveLimit) +{var angle=b2.m_rotation-b1.m_rotation-this.m_intialAngle;var limitImpulse=0.0;if(this.m_limitState==b2Joint.e_equalLimits) +{limitC=b2Math.b2Clamp(angle,-b2Settings.b2_maxAngularCorrection,b2Settings.b2_maxAngularCorrection);limitImpulse=-this.m_motorMass*limitC;angularError=b2Math.b2Abs(limitC);} +else if(this.m_limitState==b2Joint.e_atLowerLimit) +{limitC=angle-this.m_lowerAngle;angularError=b2Math.b2Max(0.0,-limitC);limitC=b2Math.b2Clamp(limitC+b2Settings.b2_angularSlop,-b2Settings.b2_maxAngularCorrection,0.0);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Max(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} +else if(this.m_limitState==b2Joint.e_atUpperLimit) +{limitC=angle-this.m_upperAngle;angularError=b2Math.b2Max(0.0,limitC);limitC=b2Math.b2Clamp(limitC-b2Settings.b2_angularSlop,0.0,b2Settings.b2_maxAngularCorrection);limitImpulse=-this.m_motorMass*limitC;oldLimitImpulse=this.m_limitPositionImpulse;this.m_limitPositionImpulse=b2Math.b2Min(this.m_limitPositionImpulse+limitImpulse,0.0);limitImpulse=this.m_limitPositionImpulse-oldLimitImpulse;} +b1.m_rotation-=b1.m_invI*limitImpulse;b1.m_R.Set(b1.m_rotation);b2.m_rotation+=b2.m_invI*limitImpulse;b2.m_R.Set(b2.m_rotation);} +return positionError<=b2Settings.b2_linearSlop&&angularError<=b2Settings.b2_angularSlop;},m_localAnchor1:new b2Vec2(),m_localAnchor2:new b2Vec2(),m_ptpImpulse:new b2Vec2(),m_motorImpulse:null,m_limitImpulse:null,m_limitPositionImpulse:null,m_ptpMass:new b2Mat22(),m_motorMass:null,m_intialAngle:null,m_lowerAngle:null,m_upperAngle:null,m_maxMotorTorque:null,m_motorSpeed:null,m_enableLimit:null,m_enableMotor:null,m_limitState:0});b2RevoluteJoint.tImpulse=new b2Vec2(); +var b2RevoluteJointDef=Class.create();Object.extend(b2RevoluteJointDef.prototype,b2JointDef.prototype);Object.extend(b2RevoluteJointDef.prototype,{initialize:function() +{this.type=b2Joint.e_unknownJoint;this.userData=null;this.body1=null;this.body2=null;this.collideConnected=false;this.type=b2Joint.e_revoluteJoint;this.anchorPoint=new b2Vec2(0.0,0.0);this.lowerAngle=0.0;this.upperAngle=0.0;this.motorTorque=0.0;this.motorSpeed=0.0;this.enableLimit=false;this.enableMotor=false;},anchorPoint:null,lowerAngle:null,upperAngle:null,motorTorque:null,motorSpeed:null,enableLimit:null,enableMotor:null}); diff --git a/o3d/samples/manipulators/translate1.html b/o3d/samples/manipulators/translate1.html index dd3eadb..7debeae 100644 --- a/o3d/samples/manipulators/translate1.html +++ b/o3d/samples/manipulators/translate1.html @@ -1,273 +1,273 @@ - - - - - - - - -Translate1 Manipulator - - - - - -

Translate1 Manipulator Sample

-This example shows how to move objects around the scene using the -Translate1 manipulator. -
- -
- - - + + + + + + + + +Translate1 Manipulator + + + + + +

Translate1 Manipulator Sample

+This example shows how to move objects around the scene using the +Translate1 manipulator. +
+ +
+ + + diff --git a/o3d/samples/o3djs/manipulators.js b/o3d/samples/o3djs/manipulators.js index e1ddce6..cade671 100644 --- a/o3d/samples/o3djs/manipulators.js +++ b/o3d/samples/o3djs/manipulators.js @@ -1,962 +1,962 @@ -/* - * Copyright 2009, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -/** - * @fileoverview This file contains classes that implement several - * forms of 2D and 3D manipulation. - */ - -o3djs.provide('o3djs.manipulators'); - -o3djs.require('o3djs.material'); - -o3djs.require('o3djs.math'); - -o3djs.require('o3djs.picking'); - -o3djs.require('o3djs.primitives'); - -o3djs.require('o3djs.quaternions'); - -/** - * A module implementing several forms of 2D and 3D manipulation. - * @namespace - */ -o3djs.manipulators = o3djs.manipulators || {}; - -/** - * Creates a new manipulator manager, which maintains multiple - * manipulators in the same scene. The manager is implicitly - * associated with a particular O3D client via the Pack which is - * passed in, although multiple managers can be created for a given - * client. The manipulators are positioned in world coordinates and - * are placed in the scene graph underneath the parent transform which - * is passed in. - * @param {!o3d.Pack} pack Pack in which manipulators' geometry and - * materials will be created. - * @param {!o3d.DrawList} drawList The draw list against which - * internal materials are created. - * @param {!o3d.Transform} parentTransform The parent transform under - * which the manipulators' geometry should be parented. - * @param {!o3d.RenderNode} parentRenderNode The parent render node - * under which the manipulators' draw elements should be placed. - * @param {number} renderNodePriority The priority that the - * manipulators' geometry should use for rendering. - * @return {!o3djs.manipulators.Manager} The created manipulator - * manager. - */ -o3djs.manipulators.createManager = function(pack, - drawList, - parentTransform, - parentRenderNode, - renderNodePriority) { - return new o3djs.manipulators.Manager(pack, - drawList, - parentTransform, - parentRenderNode, - renderNodePriority); -} - -// -// Some linear algebra classes. -// TODO(kbr): find a better home for these. -// - -/** - * Creates a new Line object, which implements projection and - * closest-point operations. - * @constructor - * @private - * @param {o3djs.math.Vector3} opt_direction The direction of the - * line. Does not need to be normalized but must not be the zero - * vector. Defaults to [1, 0, 0] if not specified. - * @param {o3djs.math.Vector3} opt_point A point through which the - * line goes. Defaults to [0, 0, 0] if not specified. - */ -o3djs.manipulators.Line_ = function(opt_direction, - opt_point) { - if (opt_direction) { - this.direction_ = o3djs.math.copyVector(opt_direction); - } else { - this.direction_ = [1, 0, 0]; - } - - if (opt_point) { - this.point_ = o3djs.math.copyVector(opt_point); - } else { - this.point_ = [0, 0, 0]; - } - - // Helper for computing projections along the line - this.alongVec_ = [0, 0, 0]; - this.recalc_(); -} - -/** - * Sets the direction of this line. - * @private - * @param {!o3djs.math.Vector3} direction The new direction of the - * line. Does not need to be normalized but must not be the zero - * vector. - */ -o3djs.manipulators.Line_.prototype.setDirection = function(direction) { - this.direction_ = o3djs.math.copyVector(direction); - this.recalc_(); -} - -/** - * Gets the direction of this line. - * @private - * @return {!o3djs.math.Vector3} The direction of the line. - */ -o3djs.manipulators.Line_.prototype.getDirection = function() { - return this.direction_; -} - -/** - * Sets one point through which this line travels. - * @private - * @param {!o3djs.math.Vector3} point A point which through the line - * will travel. - */ -o3djs.manipulators.Line_.prototype.setPoint = function(point) { - this.point_ = o3djs.math.copyVector(point); - this.recalc_(); -} - -/** - * Gets one point through which this line travels. - * @private - * @return {!o3djs.math.Vector3} A point which through the line - * travels. - */ -o3djs.manipulators.Line_.prototype.getPoint = function() { - return this.point_; -} - -/** - * Projects a point onto the line. - * @private - * @param {!o3djs.math.Vector3} point Point to be projected. - * @return {!o3djs.math.Vector3} Point on the line closest to the - * passed point. - */ -o3djs.manipulators.Line_.prototype.projectPoint = function(point) { - var dotp = o3djs.math.dot(this.direction_, point); - return o3djs.math.addVector(this.alongVec_, - o3djs.math.mulScalarVector(dotp, - this.direction_)); -} - -o3djs.manipulators.EPSILON = 0.00001; -o3djs.manipulators.X_AXIS = [1, 0, 0]; - - -/** - * Returns the closest point on this line to the given ray, which is - * specified by start and end points. If the ray is parallel to the - * line, returns null. - * @private - * @param {!o3djs.math.Vector3} startPoint Start point of ray. - * @param {!o3djs.math.Vector3} endPoint End point of ray. - * @return {o3djs.math.Vector3} The closest point on the line to the - * ray, or null if the ray is parallel to the line. - */ -o3djs.manipulators.Line_.prototype.closestPointToRay = function(startPoint, - endPoint) { - // Consider a two-sided line and a one-sided ray, both in in 3D - // space, and assume they are not parallel. Their parametric - // formulation is: - // - // p1 = point + t * dir - // p2 = raystart + u * raydir - // - // Here t and u are scalar parameter values, and the other values - // are three-dimensional vectors. p1 and p2 are arbitrary points on - // the line and ray, respectively. - // - // At the points cp1 and cp2 on these two lines where the line and - // the ray are closest together, the line segment between cp1 and - // cp2 is perpendicular to both of the lines. - // - // We can therefore write the following equations: - // - // dot( dir, (cp2 - cp1)) = 0 - // dot(raydir, (cp2 - cp1)) = 0 - // - // Define t' and u' as the parameter values for cp1 and cp2, - // respectively. Expanding, these equations become - // - // dot( dir, ((raystart + u' * raydir) - (point + t' * dir))) = 0 - // dot(raydir, ((raystart + u' * raydir) - (point + t' * dir))) = 0 - // - // With some reshuffling, these can be expressed in vector/matrix - // form: - // - // [ dot( dir, raystart) - dot( dir, point) ] - // [ dot(raydir, raystart) - dot(raydir, point) ] + (continued) - // - // [ -dot( dir, dir) dot( dir, raydir) ] [ t' ] [0] - // [ -dot(raydir, dir) dot(raydir, raydir) ] * [ u' ] = [0] - // - // u' is the parameter for the world space ray being cast into the - // screen. We can deduce whether the starting point of the ray is - // actually the closest point to the infinite 3D line by whether the - // value of u' is less than zero. - var rayDirection = o3djs.math.subVector(endPoint, startPoint); - var ddrd = o3djs.math.dot(this.direction_, rayDirection); - var A = [[-o3djs.math.lengthSquared(this.direction_), ddrd], - [ddrd, -o3djs.math.lengthSquared(rayDirection)]]; - var det = o3djs.math.det2(A); - if (Math.abs(det) < o3djs.manipulators.EPSILON) { - return null; - } - var Ainv = o3djs.math.inverse2(A); - var b = [o3djs.math.dot(this.point_, this.direction_) - - o3djs.math.dot(startPoint, this.direction_), - o3djs.math.dot(startPoint, rayDirection) - - o3djs.math.dot(this.point_, rayDirection)]; - var x = o3djs.math.mulMatrixVector(Ainv, b); - if (x[1] < 0) { - // Means that start point is closest point to this line - return startPoint; - } else { - return o3djs.math.addVector(this.point_, - o3djs.math.mulScalarVector( - x[0], - this.direction_)); - } -} - -/** - * Performs internal recalculations when the parameters of the line change. - * @private - */ -o3djs.manipulators.Line_.prototype.recalc_ = function() { - var denom = o3djs.math.lengthSquared(this.direction_); - if (denom == 0.0) { - throw 'Line_.recalc: ERROR: direction was the zero vector (not allowed)'; - } - this.alongVec_ = - o3djs.math.subVector(this.point_, - o3djs.math.mulScalarVector( - o3djs.math.dot(this.point_, - this.direction_), - this.direction_)); -} - -o3djs.manipulators.DEFAULT_COLOR = [0.8, 0.8, 0.8, 1.0]; -o3djs.manipulators.HIGHLIGHTED_COLOR = [0.9, 0.9, 0.0, 1.0]; - -/** - * Constructs a new manipulator manager. Do not call this directly; - * use o3djs.manipulators.createManager instead. - * @constructor - * @param {!o3d.Pack} pack Pack in which manipulators' geometry and - * materials will be created. - * @param {!o3d.DrawList} drawList The draw list against which - * internal materials are created. - * @param {!o3d.Transform} parentTransform The parent transform under - * which the manipulators' geometry should be parented. - * @param {!o3d.RenderNode} parentRenderNode The parent render node - * under which the manipulators' draw elements should be placed. - * @param {number} renderNodePriority The priority that the - * manipulators' geometry should use for rendering. - */ -o3djs.manipulators.Manager = function(pack, - drawList, - parentTransform, - parentRenderNode, - renderNodePriority) { - this.pack = pack; - this.drawList = drawList; - this.parentTransform = parentTransform; - this.parentRenderNode = parentRenderNode; - this.renderNodePriority = renderNodePriority; - - this.lightPosition = [10, 10, 10]; - - // Create the default and highlighted materials. - this.defaultMaterial = - this.createPhongMaterial_(o3djs.manipulators.DEFAULT_COLOR); - this.highlightedMaterial = - this.createPhongMaterial_(o3djs.manipulators.HIGHLIGHTED_COLOR); - - // This is a map from the manip's parent Transform clientId to the manip. - this.manipsByClientId = []; - - // Presumably we need a TransformInfo for the parentTransform. - this.transformInfo = - o3djs.picking.createTransformInfo(this.parentTransform, null); - - /** - * The currently-highlighted manipulator. - * @type {o3djs.manipulators.Manip} - */ - this.highlightedManip = null; - - /** - * The manipulator currently being dragged. - * @private - * @type {o3djs.manipulators.Manip} - */ - this.draggedManip_ = null; -} - -/** - * Creates a phong material based on the given single color. - * @private - * @param {!o3djs.math.Vector4} baseColor A vector with 4 entries, the - * R,G,B, and A components of a color. - * @return {!o3d.Material} A phong material whose overall pigment is baseColor. - */ -o3djs.manipulators.Manager.prototype.createPhongMaterial_ = - function(baseColor) { - // Create a new, empty Material object. - var material = this.pack.createObject('Material'); - - o3djs.effect.attachStandardShader( - this.pack, material, this.lightPosition, 'phong'); - - material.drawList = this.drawList; - - // Assign parameters to the phong material. - material.getParam('emissive').value = [0, 0, 0, 1]; - material.getParam('ambient').value = - o3djs.math.mulScalarVector(0.1, baseColor); - material.getParam('diffuse').value = baseColor; - material.getParam('specular').value = [.2, .2, .2, 1]; - material.getParam('shininess').value = 20; - - return material; -} - -/** - * Creates a new Translate1 manipulator. A Translate1 moves along the - * X axis in its local coordinate system. - * @return {!o3djs.manipulators.Translate1} A new Translate1 manipulator. - */ -o3djs.manipulators.Manager.prototype.createTranslate1 = function() { - var manip = new o3djs.manipulators.Translate1(this); - this.add_(manip); - return manip; -} - -/** - * Adds a manipulator to this manager's set. - * @private - * @param {!o3djs.manipulators.Manip} manip The manipulator to add. - */ -o3djs.manipulators.Manager.prototype.add_ = function(manip) { - // Generate draw elements for the manipulator's transform - manip.getTransform().createDrawElements(this.pack, null); - // Add the manipulator's transform to the parent transform - manip.getBaseTransform_().parent = this.parentTransform; - // Add the manipulator into our managed list - this.manipsByClientId[manip.getTransform().clientId] = manip; -} - -/** - * Event handler for multiple kinds of mouse events. - * @private - * @param {number} x The x coordinate of the mouse event. - * @param {number} y The y coordinate of the mouse event. - * @param {!o3djs.math.Matrix4} view The current view matrix. - * @param {!o3djs.math.Matrix4} projection The current projection matrix. - * @param {number} width The width of the viewport. - * @param {number} height The height of the viewport. - * @param {!function(!o3djs.manipulators.Manager, - * o3djs.picking.PickInfo, o3djs.manipulators.Manip): void} func - * Callback function. Always receives the manager as argument; if - * a manipulator was picked, receives non-null PickInfo and Manip - * arguments, otherwise receives null for both of these arguments. - */ -o3djs.manipulators.Manager.prototype.handleMouse_ = function(x, - y, - view, - projection, - width, - height, - func) { - this.transformInfo.update(); - - // Create the world ray - var worldRay = - o3djs.picking.clientPositionToWorldRayEx(x, y, - view, projection, - width, height); - - // Pick against all of the manipulators' geometry - var pickResult = this.transformInfo.pick(worldRay); - if (pickResult != null) { - // Find which manipulator we picked. - // NOTE this assumes some things about the transform graph - // structure of the manipulators. - var manip = - this.manipsByClientId[pickResult.shapeInfo.parent.transform.clientId]; - func(this, pickResult, manip); - } else { - func(this, null, null); - } -} - -/** - * Callback handling the mouse-down event on a manipulator. - * @private - * @param {!o3djs.manipulators.Manager} manager The manipulator - * manager owning the given manipulator. - * @param {o3djs.picking.PickInfo} pickResult The picking information - * associated with the mouse-down event. - * @param {o3djs.manipulators.Manip} manip The manipulator to be - * selected. - */ -o3djs.manipulators.mouseDownCallback_ = function(manager, - pickResult, - manip) { - if (manip != null) { - manager.draggedManip_ = manip; - manip.makeActive(pickResult); - } -} - -/** - * Callback handling the mouse-over event on a manipulator. - * @private - * @param {!o3djs.manipulators.Manager} manager The manipulator - * manager owning the given manipulator. - * @param {o3djs.picking.PickInfo} pickResult The picking information - * associated with the mouse-over event. - * @param {o3djs.manipulators.Manip} manip The manipulator to be - * highlighted. - */ -o3djs.manipulators.hoverCallback_ = function(manager, - pickResult, - manip) { - if (manager.highlightedManip != null && - manager.highlightedManip != manip) { - // Un-highlight the previously highlighted manipulator - manager.highlightedManip.clearHighlight(); - manager.highlightedManip = null; - } - - if (manip != null) { - manip.highlight(pickResult); - manager.highlightedManip = manip; - } -} - -/** - * Method which should be called by end user code upon receiving a - * mouse-down event. - * @param {number} x The x coordinate of the mouse event. - * @param {number} y The y coordinate of the mouse event. - * @param {!o3djs.math.Matrix4} view The current view matrix. - * @param {!o3djs.math.Matrix4} projection The current projection matrix. - * @param {number} width The width of the viewport. - * @param {number} height The height of the viewport. - */ -o3djs.manipulators.Manager.prototype.mousedown = function(x, - y, - view, - projection, - width, - height) { - this.handleMouse_(x, y, view, projection, width, height, - o3djs.manipulators.mouseDownCallback_); -} - -/** - * Method which should be called by end user code upon receiving a - * mouse motion event. - * @param {number} x The x coordinate of the mouse event. - * @param {number} y The y coordinate of the mouse event. - * @param {!o3djs.math.Matrix4} view The current view matrix. - * @param {!o3djs.math.Matrix4} projection The current projection matrix. - * @param {number} width The width of the viewport. - * @param {number} height The height of the viewport. - */ -o3djs.manipulators.Manager.prototype.mousemove = function(x, - y, - view, - projection, - width, - height) { - if (this.draggedManip_ != null) { - var worldRay = - o3djs.picking.clientPositionToWorldRayEx(x, y, - view, projection, - width, height); - this.draggedManip_.drag(worldRay.near, worldRay.far); - } else { - this.handleMouse_(x, y, view, projection, width, height, - o3djs.manipulators.hoverCallback_); - } -} - -/** - * Method which should be called by end user code upon receiving a - * mouse-up event. - */ -o3djs.manipulators.Manager.prototype.mouseup = function() { - if (this.draggedManip_ != null) { - this.draggedManip_.makeInactive(); - this.draggedManip_ = null; - } -} - -/** - * Method which should be called by end user code, typically in - * response to mouse move events, to update the transforms of - * manipulators which might have been moved either because of - * manipulators further up the hierarchy, or programmatic changes to - * transforms. - */ -o3djs.manipulators.Manager.prototype.updateInactiveManipulators = function() { - for (var ii in this.manipsByClientId) { - var manip = this.manipsByClientId[ii]; - if (!manip.isActive()) { - manip.updateBaseTransformFromAttachedTransform_(); - } - } -} - -/** - * Base class for all manipulators. - * @constructor - * @param {!o3djs.manipulators.Manager} manager The manager of this - * manipulator. - */ -o3djs.manipulators.Manip = function(manager) { - this.manager_ = manager; - var pack = manager.pack; - // This transform holds the local transformation of the manipulator, - // which is either applied to the transform to which it is attached, - // or (see below) consumed by the user in the manipulator's - // callbacks. After each interaction, if there is an attached - // transform, this local transform is added in to it and reset to - // the identity. - // TODO(kbr): add support for user callbacks on manipulators. - this.localTransform_ = pack.createObject('Transform'); - - // This transform provides an offset, if desired, between the - // manipulator's geometry and the transform (and, implicitly, the - // shape) to which it is attached. This allows the manipulator to be - // easily placed below an object, for example. - this.offsetTransform_ = pack.createObject('Transform'); - - // This transform is the one which is actually parented to the - // manager's parentTransform. It is used to place the manipulator in - // world space, regardless of the world space location of the - // parentTransform supplied to the manager. If this manipulator is - // attached to a given transform, then upon completion of a - // particular drag interaction, this transform is adjusted to take - // into account the attached transform's new value. - this.baseTransform_ = pack.createObject('Transform'); - - // Hook up these transforms - this.localTransform_.parent = this.offsetTransform_; - this.offsetTransform_.parent = this.baseTransform_; - - // This is the transform in the scene graph to which this - // manipulator is conceptually "attached", and whose local transform - // we are modifying. - this.attachedTransform_ = null; - - this.active_ = false; -} - -/** - * Adds shapes to the internal transform of this manipulator. - * @private - * @param {!Array.} shapes Array of shapes to add. - */ -o3djs.manipulators.Manip.prototype.addShapes_ = function(shapes) { - for (var ii = 0; ii < shapes.length; ii++) { - this.localTransform_.addShape(shapes[ii]); - } -} - -/** - * Returns the "base" transform of this manipulator, which places the - * origin of the manipulator at the local origin of the attached - * transform. - * @private - * @return {!o3d.Transform} The base transform of this manipulator. - */ -o3djs.manipulators.Manip.prototype.getBaseTransform_ = function() { - return this.baseTransform_; -} - -/** - * Returns the "offset" transform of this manipulator, which allows - * the manipulator's geometry to be moved or rotated with respect to - * the local origin of the attached transform. - * @return {!o3d.Transform} The offset transform of this manipulator. - */ -o3djs.manipulators.Manip.prototype.getOffsetTransform = function() { - return this.offsetTransform_; -} - -/** - * Returns the local transform of this manipulator, which contains the - * changes that have been made in response to the current drag - * operation. Upon completion of the drag, this transform's effects - * are composed in to the attached transform, and this transform is - * reset to the identity. - * @return {!o3d.Transform} The local transform of this manipulator. - */ -o3djs.manipulators.Manip.prototype.getTransform = function() { - return this.localTransform_; -} - -/** - * Sets the translation component of the offset transform. This is - * useful for moving the manipulator's geometry with respect to the - * local origin of the attached transform. - * @param {!o3djs.math.Vector3} translation The offset translation for - * this manipulator. - */ -o3djs.manipulators.Manip.prototype.setOffsetTranslation = - function(translation) { - this.getOffsetTransform().localMatrix = - o3djs.math.matrix4.setTranslation(this.getOffsetTransform().localMatrix, - translation); - -} - -/** - * Sets the rotation component of the offset transform. This is useful - * for orienting the manipulator's geometry with respect to the local - * origin of the attached transform. - * @param {!o3djs.quaternions.Quaternion} quaternion The offset - * rotation for this manipulator. - */ -o3djs.manipulators.Manip.prototype.setOffsetRotation = function(quaternion) { - var rot = o3djs.quaternions.quaternionToRotation(quaternion); - this.getOffsetTransform().localMatrix = - o3djs.math.matrix4.setUpper3x3(this.getOffsetTransform().localMatrix, - rot); - -} - -/** - * Explicitly sets the local translation of this manipulator. - * (TODO(kbr): it is not clear that this capability should be in the - * API.) - * @param {!o3djs.math.Vector3} translation The local translation for - * this manipulator. - */ -o3djs.manipulators.Manip.prototype.setTranslation = function(translation) { - this.getTransform().localMatrix = - o3djs.math.matrix4.setTranslation(this.getTransform().localMatrix, - translation); - -} - -/** - * Explicitly sets the local rotation of this manipulator. (TODO(kbr): - * it is not clear that this capability should be in the API.) - * @param {!o3djs.quaternions.Quaternion} quaternion The local - * rotation for this manipulator. - */ -o3djs.manipulators.Manip.prototype.setRotation = function(quaternion) { - var rot = o3djs.quaternions.quaternionToRotation(quaternion); - this.getTransform().localMatrix = - o3djs.math.matrix4.setUpper3x3(this.getTransform().localMatrix, - rot); - -} - -/** - * Attaches this manipulator to the given transform. Interactions with - * the manipulator will cause this transform's local matrix to be - * modified appropriately. - * @param {!o3d.Transform} transform The transform to which this - * manipulator should be attached. - */ -o3djs.manipulators.Manip.prototype.attachTo = function(transform) { - this.attachedTransform_ = transform; - // Update our base transform to place the manipulator at exactly the - // location of the attached transform. - this.updateBaseTransformFromAttachedTransform_(); -} - -/** - * Highlights this manipulator according to the given pick result. - * @param {o3djs.picking.PickInfo} pickResult The pick result which - * caused this manipulator to become highlighted. - */ -o3djs.manipulators.Manip.prototype.highlight = function(pickResult) { -} - -/** - * Clears any highlight for this manipulator. - */ -o3djs.manipulators.Manip.prototype.clearHighlight = function() { -} - -/** - * Activates this manipulator according to the given pick result. In - * complex manipulators, picking different portions of the manipulator - * may result in different forms of interaction. - * @param {o3djs.picking.PickInfo} pickResult The pick result which - * caused this manipulator to become active. - */ -o3djs.manipulators.Manip.prototype.makeActive = function(pickResult) { - this.active_ = true; -} - -/** - * Deactivates this manipulator. - */ -o3djs.manipulators.Manip.prototype.makeInactive = function() { - this.active_ = false; -} - -/** - * Drags this manipulator according to the world-space ray specified - * by startPoint and endPoint. makeActive must already have been - * called with the initial pick result causing this manipulator to - * become active. This method exists only to be overridden. - * @param {!o3djs.math.Vector3} startPoint Start point of the - * world-space ray through the current mouse position. - * @param {!o3djs.math.Vector3} endPoint End point of the world-space - * ray through the current mouse position. - */ -o3djs.manipulators.Manip.prototype.drag = function(startPoint, - endPoint) { -} - -/** - * Indicates whether this manipulator is active. - * @return {boolean} Whether this manipulator is active. - */ -o3djs.manipulators.Manip.prototype.isActive = function() { - return this.active_; -} - -/** - * Updates the base transform of this manipulator from the state of - * its attached transform, resetting the local transform of this - * manipulator to the identity. - * @private - */ -o3djs.manipulators.Manip.prototype.updateBaseTransformFromAttachedTransform_ = - function() { - if (this.attachedTransform_ != null) { - var attWorld = this.attachedTransform_.worldMatrix; - var parWorld = this.manager_.parentTransform.worldMatrix; - var parWorldInv = o3djs.math.matrix4.inverse(parWorld); - this.baseTransform_.localMatrix = - o3djs.math.matrix4.mul(attWorld, parWorldInv); - // Reset the manipulator's local matrix to the identity. - this.localTransform_.localMatrix = o3djs.math.matrix4.identity(); - } -} - -/** - * Updates this manipulator's attached transform based on the values - * in the local transform. - * @private - */ -o3djs.manipulators.Manip.prototype.updateAttachedTransformFromLocalTransform_ = - function() { - if (this.attachedTransform_ != null) { - // Compute the composition of the base and local transforms. - // The offset transform is skipped except for transforming the - // local matrix's translation by the rotation component of the - // offset transform. - var base = this.baseTransform_.worldMatrix; - var local = this.localTransform_.localMatrix; - var xlate = o3djs.math.matrix4.getTranslation(local); - var transformedXlate = - o3djs.math.matrix4.transformDirection( - this.offsetTransform_.localMatrix, - o3djs.math.matrix4.getTranslation(local)); - o3djs.math.matrix4.setTranslation(local, transformedXlate); - var totalMat = o3djs.math.matrix4.mul(base, local); - - // Set this into the attached transform, taking into account its - // parent's transform, if any. - // Note that we can not query the parent's transform directly, so - // we compute it using a little trick. - var attWorld = this.attachedTransform_.worldMatrix; - var attLocal = this.attachedTransform_.localMatrix; - var attParentMat = - o3djs.math.matrix4.mul(attWorld, - o3djs.math.matrix4.inverse(attLocal)); - // Now we can take the inverse of this matrix - var attParentMatInv = o3djs.math.matrix4.inverse(attParentMat); - totalMat = o3djs.math.matrix4.mul(attParentMatInv, totalMat); - this.attachedTransform_.localMatrix = totalMat; - } -} - -/** - * Sets the material of the given shape's draw elements. - * @private - */ -o3djs.manipulators.Manip.prototype.setMaterial_ = function(shape, material) { - var elements = shape.elements; - for (var ii = 0; ii < elements.length; ii++) { - var drawElements = elements[ii].drawElements; - for (var jj = 0; jj < drawElements.length; jj++) { - drawElements[jj].material = material; - } - } -} - -/** - * Sets the materials of the given shapes' draw elements. - * @private - */ -o3djs.manipulators.Manip.prototype.setMaterials_ = function(shapes, material) { - for (var ii = 0; ii < shapes.length; ii++) { - this.setMaterial_(shapes[ii], material); - } -} - -/** - * A manipulator allowing an object to be dragged along a line. - * @constructor - * @extends {o3djs.manipulators.Manip} - * @param {!o3djs.manipulators.Manager} manager The manager for the - * new Translate1 manipulator. - */ -o3djs.manipulators.Translate1 = function(manager) { - o3djs.manipulators.Manip.call(this, manager); - - var pack = manager.pack; - var material = manager.defaultMaterial; - - var shape = manager.translate1Shape_; - if (!shape) { - // Create the geometry for the manipulator, which looks like a - // two-way arrow going from (-1, 0, 0) to (1, 0, 0). - var matrix4 = o3djs.math.matrix4; - var zRot = matrix4.rotationZ(Math.PI / 2); - - var verts = o3djs.primitives.createTruncatedConeVertices( - 0.15, // Bottom radius. - 0.0, // Top radius. - 0.3, // Height. - 4, // Number of radial subdivisions. - 1, // Number of vertical subdivisions. - matrix4.mul(matrix4.translation([0, 0.85, 0]), zRot)); - - verts.append(o3djs.primitives.createCylinderVertices( - 0.06, // Radius. - 1.4, // Height. - 4, // Number of radial subdivisions. - 1, // Number of vertical subdivisions. - zRot)); - - verts.append(o3djs.primitives.createTruncatedConeVertices( - 0.0, // Bottom radius. - 0.15, // Top radius. - 0.3, // Height. - 4, // Number of radial subdivisions. - 1, // Number of vertical subdivisions. - matrix4.mul(matrix4.translation([0, -0.85, 0]), zRot))); - - shape = verts.createShape(pack, material); - manager.translate1Shape_ = shape; - } - - this.addShapes_([ shape ]); - - this.transformInfo = o3djs.picking.createTransformInfo(this.getTransform(), - manager.transformInfo); - - // Add a parameter to our transform to be able to change the - // material's color for highlighting. - this.colorParam = this.getTransform().createParam('diffuse', 'ParamFloat4'); - this.clearHighlight(); - - /** Dragging state */ - this.dragLine = new o3djs.manipulators.Line_(); -} - -o3djs.base.inherit(o3djs.manipulators.Translate1, o3djs.manipulators.Manip); - -o3djs.manipulators.Translate1.prototype.highlight = function(pickResult) { - // We can use instanced geometry for the entire Translate1 since its - // entire color changes during highlighting. - // TODO(kbr): support custom user geometry and associated callbacks. - this.colorParam.value = o3djs.manipulators.HIGHLIGHTED_COLOR; -} - -o3djs.manipulators.Translate1.prototype.clearHighlight = function() { - this.colorParam.value = o3djs.manipulators.DEFAULT_COLOR; -} - -o3djs.manipulators.Translate1.prototype.makeActive = function(pickResult) { - o3djs.manipulators.Manip.prototype.makeActive.call(this, pickResult); - this.highlight(pickResult); - var worldMatrix = this.getTransform().worldMatrix; - this.dragLine.setDirection( - o3djs.math.matrix4.transformDirection(worldMatrix, - o3djs.manipulators.X_AXIS)); - this.dragLine.setPoint(pickResult.worldIntersectionPosition); -} - -o3djs.manipulators.Translate1.prototype.makeInactive = function() { - o3djs.manipulators.Manip.prototype.makeInactive.call(this); - this.clearHighlight(); - this.updateAttachedTransformFromLocalTransform_(); - this.updateBaseTransformFromAttachedTransform_(); -} - -o3djs.manipulators.Translate1.prototype.drag = function(startPoint, - endPoint) { - // Algorithm: Find closest point of ray to dragLine. Subtract this - // point from the line's point to find difference vector; transform - // from world to local coordinates to find new local offset of - // manipulator. - var closestPoint = this.dragLine.closestPointToRay(startPoint, endPoint); - if (closestPoint == null) { - // Drag axis is parallel to ray. Punt. - return; - } - // Need to do a world-to-local transformation on the difference vector. - // Note that we also incorporate the translation portion of the matrix. - var diffVector = - o3djs.math.subVector(closestPoint, this.dragLine.getPoint()); - var worldToLocal = - o3djs.math.matrix4.inverse(this.getTransform().worldMatrix); - this.getTransform().localMatrix = - o3djs.math.matrix4.setTranslation( - this.getTransform().localMatrix, - o3djs.math.matrix4.transformDirection(worldToLocal, - diffVector)); - this.updateAttachedTransformFromLocalTransform_(); -} +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @fileoverview This file contains classes that implement several + * forms of 2D and 3D manipulation. + */ + +o3djs.provide('o3djs.manipulators'); + +o3djs.require('o3djs.material'); + +o3djs.require('o3djs.math'); + +o3djs.require('o3djs.picking'); + +o3djs.require('o3djs.primitives'); + +o3djs.require('o3djs.quaternions'); + +/** + * A module implementing several forms of 2D and 3D manipulation. + * @namespace + */ +o3djs.manipulators = o3djs.manipulators || {}; + +/** + * Creates a new manipulator manager, which maintains multiple + * manipulators in the same scene. The manager is implicitly + * associated with a particular O3D client via the Pack which is + * passed in, although multiple managers can be created for a given + * client. The manipulators are positioned in world coordinates and + * are placed in the scene graph underneath the parent transform which + * is passed in. + * @param {!o3d.Pack} pack Pack in which manipulators' geometry and + * materials will be created. + * @param {!o3d.DrawList} drawList The draw list against which + * internal materials are created. + * @param {!o3d.Transform} parentTransform The parent transform under + * which the manipulators' geometry should be parented. + * @param {!o3d.RenderNode} parentRenderNode The parent render node + * under which the manipulators' draw elements should be placed. + * @param {number} renderNodePriority The priority that the + * manipulators' geometry should use for rendering. + * @return {!o3djs.manipulators.Manager} The created manipulator + * manager. + */ +o3djs.manipulators.createManager = function(pack, + drawList, + parentTransform, + parentRenderNode, + renderNodePriority) { + return new o3djs.manipulators.Manager(pack, + drawList, + parentTransform, + parentRenderNode, + renderNodePriority); +} + +// +// Some linear algebra classes. +// TODO(kbr): find a better home for these. +// + +/** + * Creates a new Line object, which implements projection and + * closest-point operations. + * @constructor + * @private + * @param {o3djs.math.Vector3} opt_direction The direction of the + * line. Does not need to be normalized but must not be the zero + * vector. Defaults to [1, 0, 0] if not specified. + * @param {o3djs.math.Vector3} opt_point A point through which the + * line goes. Defaults to [0, 0, 0] if not specified. + */ +o3djs.manipulators.Line_ = function(opt_direction, + opt_point) { + if (opt_direction) { + this.direction_ = o3djs.math.copyVector(opt_direction); + } else { + this.direction_ = [1, 0, 0]; + } + + if (opt_point) { + this.point_ = o3djs.math.copyVector(opt_point); + } else { + this.point_ = [0, 0, 0]; + } + + // Helper for computing projections along the line + this.alongVec_ = [0, 0, 0]; + this.recalc_(); +} + +/** + * Sets the direction of this line. + * @private + * @param {!o3djs.math.Vector3} direction The new direction of the + * line. Does not need to be normalized but must not be the zero + * vector. + */ +o3djs.manipulators.Line_.prototype.setDirection = function(direction) { + this.direction_ = o3djs.math.copyVector(direction); + this.recalc_(); +} + +/** + * Gets the direction of this line. + * @private + * @return {!o3djs.math.Vector3} The direction of the line. + */ +o3djs.manipulators.Line_.prototype.getDirection = function() { + return this.direction_; +} + +/** + * Sets one point through which this line travels. + * @private + * @param {!o3djs.math.Vector3} point A point which through the line + * will travel. + */ +o3djs.manipulators.Line_.prototype.setPoint = function(point) { + this.point_ = o3djs.math.copyVector(point); + this.recalc_(); +} + +/** + * Gets one point through which this line travels. + * @private + * @return {!o3djs.math.Vector3} A point which through the line + * travels. + */ +o3djs.manipulators.Line_.prototype.getPoint = function() { + return this.point_; +} + +/** + * Projects a point onto the line. + * @private + * @param {!o3djs.math.Vector3} point Point to be projected. + * @return {!o3djs.math.Vector3} Point on the line closest to the + * passed point. + */ +o3djs.manipulators.Line_.prototype.projectPoint = function(point) { + var dotp = o3djs.math.dot(this.direction_, point); + return o3djs.math.addVector(this.alongVec_, + o3djs.math.mulScalarVector(dotp, + this.direction_)); +} + +o3djs.manipulators.EPSILON = 0.00001; +o3djs.manipulators.X_AXIS = [1, 0, 0]; + + +/** + * Returns the closest point on this line to the given ray, which is + * specified by start and end points. If the ray is parallel to the + * line, returns null. + * @private + * @param {!o3djs.math.Vector3} startPoint Start point of ray. + * @param {!o3djs.math.Vector3} endPoint End point of ray. + * @return {o3djs.math.Vector3} The closest point on the line to the + * ray, or null if the ray is parallel to the line. + */ +o3djs.manipulators.Line_.prototype.closestPointToRay = function(startPoint, + endPoint) { + // Consider a two-sided line and a one-sided ray, both in in 3D + // space, and assume they are not parallel. Their parametric + // formulation is: + // + // p1 = point + t * dir + // p2 = raystart + u * raydir + // + // Here t and u are scalar parameter values, and the other values + // are three-dimensional vectors. p1 and p2 are arbitrary points on + // the line and ray, respectively. + // + // At the points cp1 and cp2 on these two lines where the line and + // the ray are closest together, the line segment between cp1 and + // cp2 is perpendicular to both of the lines. + // + // We can therefore write the following equations: + // + // dot( dir, (cp2 - cp1)) = 0 + // dot(raydir, (cp2 - cp1)) = 0 + // + // Define t' and u' as the parameter values for cp1 and cp2, + // respectively. Expanding, these equations become + // + // dot( dir, ((raystart + u' * raydir) - (point + t' * dir))) = 0 + // dot(raydir, ((raystart + u' * raydir) - (point + t' * dir))) = 0 + // + // With some reshuffling, these can be expressed in vector/matrix + // form: + // + // [ dot( dir, raystart) - dot( dir, point) ] + // [ dot(raydir, raystart) - dot(raydir, point) ] + (continued) + // + // [ -dot( dir, dir) dot( dir, raydir) ] [ t' ] [0] + // [ -dot(raydir, dir) dot(raydir, raydir) ] * [ u' ] = [0] + // + // u' is the parameter for the world space ray being cast into the + // screen. We can deduce whether the starting point of the ray is + // actually the closest point to the infinite 3D line by whether the + // value of u' is less than zero. + var rayDirection = o3djs.math.subVector(endPoint, startPoint); + var ddrd = o3djs.math.dot(this.direction_, rayDirection); + var A = [[-o3djs.math.lengthSquared(this.direction_), ddrd], + [ddrd, -o3djs.math.lengthSquared(rayDirection)]]; + var det = o3djs.math.det2(A); + if (Math.abs(det) < o3djs.manipulators.EPSILON) { + return null; + } + var Ainv = o3djs.math.inverse2(A); + var b = [o3djs.math.dot(this.point_, this.direction_) - + o3djs.math.dot(startPoint, this.direction_), + o3djs.math.dot(startPoint, rayDirection) - + o3djs.math.dot(this.point_, rayDirection)]; + var x = o3djs.math.mulMatrixVector(Ainv, b); + if (x[1] < 0) { + // Means that start point is closest point to this line + return startPoint; + } else { + return o3djs.math.addVector(this.point_, + o3djs.math.mulScalarVector( + x[0], + this.direction_)); + } +} + +/** + * Performs internal recalculations when the parameters of the line change. + * @private + */ +o3djs.manipulators.Line_.prototype.recalc_ = function() { + var denom = o3djs.math.lengthSquared(this.direction_); + if (denom == 0.0) { + throw 'Line_.recalc: ERROR: direction was the zero vector (not allowed)'; + } + this.alongVec_ = + o3djs.math.subVector(this.point_, + o3djs.math.mulScalarVector( + o3djs.math.dot(this.point_, + this.direction_), + this.direction_)); +} + +o3djs.manipulators.DEFAULT_COLOR = [0.8, 0.8, 0.8, 1.0]; +o3djs.manipulators.HIGHLIGHTED_COLOR = [0.9, 0.9, 0.0, 1.0]; + +/** + * Constructs a new manipulator manager. Do not call this directly; + * use o3djs.manipulators.createManager instead. + * @constructor + * @param {!o3d.Pack} pack Pack in which manipulators' geometry and + * materials will be created. + * @param {!o3d.DrawList} drawList The draw list against which + * internal materials are created. + * @param {!o3d.Transform} parentTransform The parent transform under + * which the manipulators' geometry should be parented. + * @param {!o3d.RenderNode} parentRenderNode The parent render node + * under which the manipulators' draw elements should be placed. + * @param {number} renderNodePriority The priority that the + * manipulators' geometry should use for rendering. + */ +o3djs.manipulators.Manager = function(pack, + drawList, + parentTransform, + parentRenderNode, + renderNodePriority) { + this.pack = pack; + this.drawList = drawList; + this.parentTransform = parentTransform; + this.parentRenderNode = parentRenderNode; + this.renderNodePriority = renderNodePriority; + + this.lightPosition = [10, 10, 10]; + + // Create the default and highlighted materials. + this.defaultMaterial = + this.createPhongMaterial_(o3djs.manipulators.DEFAULT_COLOR); + this.highlightedMaterial = + this.createPhongMaterial_(o3djs.manipulators.HIGHLIGHTED_COLOR); + + // This is a map from the manip's parent Transform clientId to the manip. + this.manipsByClientId = []; + + // Presumably we need a TransformInfo for the parentTransform. + this.transformInfo = + o3djs.picking.createTransformInfo(this.parentTransform, null); + + /** + * The currently-highlighted manipulator. + * @type {o3djs.manipulators.Manip} + */ + this.highlightedManip = null; + + /** + * The manipulator currently being dragged. + * @private + * @type {o3djs.manipulators.Manip} + */ + this.draggedManip_ = null; +} + +/** + * Creates a phong material based on the given single color. + * @private + * @param {!o3djs.math.Vector4} baseColor A vector with 4 entries, the + * R,G,B, and A components of a color. + * @return {!o3d.Material} A phong material whose overall pigment is baseColor. + */ +o3djs.manipulators.Manager.prototype.createPhongMaterial_ = + function(baseColor) { + // Create a new, empty Material object. + var material = this.pack.createObject('Material'); + + o3djs.effect.attachStandardShader( + this.pack, material, this.lightPosition, 'phong'); + + material.drawList = this.drawList; + + // Assign parameters to the phong material. + material.getParam('emissive').value = [0, 0, 0, 1]; + material.getParam('ambient').value = + o3djs.math.mulScalarVector(0.1, baseColor); + material.getParam('diffuse').value = baseColor; + material.getParam('specular').value = [.2, .2, .2, 1]; + material.getParam('shininess').value = 20; + + return material; +} + +/** + * Creates a new Translate1 manipulator. A Translate1 moves along the + * X axis in its local coordinate system. + * @return {!o3djs.manipulators.Translate1} A new Translate1 manipulator. + */ +o3djs.manipulators.Manager.prototype.createTranslate1 = function() { + var manip = new o3djs.manipulators.Translate1(this); + this.add_(manip); + return manip; +} + +/** + * Adds a manipulator to this manager's set. + * @private + * @param {!o3djs.manipulators.Manip} manip The manipulator to add. + */ +o3djs.manipulators.Manager.prototype.add_ = function(manip) { + // Generate draw elements for the manipulator's transform + manip.getTransform().createDrawElements(this.pack, null); + // Add the manipulator's transform to the parent transform + manip.getBaseTransform_().parent = this.parentTransform; + // Add the manipulator into our managed list + this.manipsByClientId[manip.getTransform().clientId] = manip; +} + +/** + * Event handler for multiple kinds of mouse events. + * @private + * @param {number} x The x coordinate of the mouse event. + * @param {number} y The y coordinate of the mouse event. + * @param {!o3djs.math.Matrix4} view The current view matrix. + * @param {!o3djs.math.Matrix4} projection The current projection matrix. + * @param {number} width The width of the viewport. + * @param {number} height The height of the viewport. + * @param {!function(!o3djs.manipulators.Manager, + * o3djs.picking.PickInfo, o3djs.manipulators.Manip): void} func + * Callback function. Always receives the manager as argument; if + * a manipulator was picked, receives non-null PickInfo and Manip + * arguments, otherwise receives null for both of these arguments. + */ +o3djs.manipulators.Manager.prototype.handleMouse_ = function(x, + y, + view, + projection, + width, + height, + func) { + this.transformInfo.update(); + + // Create the world ray + var worldRay = + o3djs.picking.clientPositionToWorldRayEx(x, y, + view, projection, + width, height); + + // Pick against all of the manipulators' geometry + var pickResult = this.transformInfo.pick(worldRay); + if (pickResult != null) { + // Find which manipulator we picked. + // NOTE this assumes some things about the transform graph + // structure of the manipulators. + var manip = + this.manipsByClientId[pickResult.shapeInfo.parent.transform.clientId]; + func(this, pickResult, manip); + } else { + func(this, null, null); + } +} + +/** + * Callback handling the mouse-down event on a manipulator. + * @private + * @param {!o3djs.manipulators.Manager} manager The manipulator + * manager owning the given manipulator. + * @param {o3djs.picking.PickInfo} pickResult The picking information + * associated with the mouse-down event. + * @param {o3djs.manipulators.Manip} manip The manipulator to be + * selected. + */ +o3djs.manipulators.mouseDownCallback_ = function(manager, + pickResult, + manip) { + if (manip != null) { + manager.draggedManip_ = manip; + manip.makeActive(pickResult); + } +} + +/** + * Callback handling the mouse-over event on a manipulator. + * @private + * @param {!o3djs.manipulators.Manager} manager The manipulator + * manager owning the given manipulator. + * @param {o3djs.picking.PickInfo} pickResult The picking information + * associated with the mouse-over event. + * @param {o3djs.manipulators.Manip} manip The manipulator to be + * highlighted. + */ +o3djs.manipulators.hoverCallback_ = function(manager, + pickResult, + manip) { + if (manager.highlightedManip != null && + manager.highlightedManip != manip) { + // Un-highlight the previously highlighted manipulator + manager.highlightedManip.clearHighlight(); + manager.highlightedManip = null; + } + + if (manip != null) { + manip.highlight(pickResult); + manager.highlightedManip = manip; + } +} + +/** + * Method which should be called by end user code upon receiving a + * mouse-down event. + * @param {number} x The x coordinate of the mouse event. + * @param {number} y The y coordinate of the mouse event. + * @param {!o3djs.math.Matrix4} view The current view matrix. + * @param {!o3djs.math.Matrix4} projection The current projection matrix. + * @param {number} width The width of the viewport. + * @param {number} height The height of the viewport. + */ +o3djs.manipulators.Manager.prototype.mousedown = function(x, + y, + view, + projection, + width, + height) { + this.handleMouse_(x, y, view, projection, width, height, + o3djs.manipulators.mouseDownCallback_); +} + +/** + * Method which should be called by end user code upon receiving a + * mouse motion event. + * @param {number} x The x coordinate of the mouse event. + * @param {number} y The y coordinate of the mouse event. + * @param {!o3djs.math.Matrix4} view The current view matrix. + * @param {!o3djs.math.Matrix4} projection The current projection matrix. + * @param {number} width The width of the viewport. + * @param {number} height The height of the viewport. + */ +o3djs.manipulators.Manager.prototype.mousemove = function(x, + y, + view, + projection, + width, + height) { + if (this.draggedManip_ != null) { + var worldRay = + o3djs.picking.clientPositionToWorldRayEx(x, y, + view, projection, + width, height); + this.draggedManip_.drag(worldRay.near, worldRay.far); + } else { + this.handleMouse_(x, y, view, projection, width, height, + o3djs.manipulators.hoverCallback_); + } +} + +/** + * Method which should be called by end user code upon receiving a + * mouse-up event. + */ +o3djs.manipulators.Manager.prototype.mouseup = function() { + if (this.draggedManip_ != null) { + this.draggedManip_.makeInactive(); + this.draggedManip_ = null; + } +} + +/** + * Method which should be called by end user code, typically in + * response to mouse move events, to update the transforms of + * manipulators which might have been moved either because of + * manipulators further up the hierarchy, or programmatic changes to + * transforms. + */ +o3djs.manipulators.Manager.prototype.updateInactiveManipulators = function() { + for (var ii in this.manipsByClientId) { + var manip = this.manipsByClientId[ii]; + if (!manip.isActive()) { + manip.updateBaseTransformFromAttachedTransform_(); + } + } +} + +/** + * Base class for all manipulators. + * @constructor + * @param {!o3djs.manipulators.Manager} manager The manager of this + * manipulator. + */ +o3djs.manipulators.Manip = function(manager) { + this.manager_ = manager; + var pack = manager.pack; + // This transform holds the local transformation of the manipulator, + // which is either applied to the transform to which it is attached, + // or (see below) consumed by the user in the manipulator's + // callbacks. After each interaction, if there is an attached + // transform, this local transform is added in to it and reset to + // the identity. + // TODO(kbr): add support for user callbacks on manipulators. + this.localTransform_ = pack.createObject('Transform'); + + // This transform provides an offset, if desired, between the + // manipulator's geometry and the transform (and, implicitly, the + // shape) to which it is attached. This allows the manipulator to be + // easily placed below an object, for example. + this.offsetTransform_ = pack.createObject('Transform'); + + // This transform is the one which is actually parented to the + // manager's parentTransform. It is used to place the manipulator in + // world space, regardless of the world space location of the + // parentTransform supplied to the manager. If this manipulator is + // attached to a given transform, then upon completion of a + // particular drag interaction, this transform is adjusted to take + // into account the attached transform's new value. + this.baseTransform_ = pack.createObject('Transform'); + + // Hook up these transforms + this.localTransform_.parent = this.offsetTransform_; + this.offsetTransform_.parent = this.baseTransform_; + + // This is the transform in the scene graph to which this + // manipulator is conceptually "attached", and whose local transform + // we are modifying. + this.attachedTransform_ = null; + + this.active_ = false; +} + +/** + * Adds shapes to the internal transform of this manipulator. + * @private + * @param {!Array.} shapes Array of shapes to add. + */ +o3djs.manipulators.Manip.prototype.addShapes_ = function(shapes) { + for (var ii = 0; ii < shapes.length; ii++) { + this.localTransform_.addShape(shapes[ii]); + } +} + +/** + * Returns the "base" transform of this manipulator, which places the + * origin of the manipulator at the local origin of the attached + * transform. + * @private + * @return {!o3d.Transform} The base transform of this manipulator. + */ +o3djs.manipulators.Manip.prototype.getBaseTransform_ = function() { + return this.baseTransform_; +} + +/** + * Returns the "offset" transform of this manipulator, which allows + * the manipulator's geometry to be moved or rotated with respect to + * the local origin of the attached transform. + * @return {!o3d.Transform} The offset transform of this manipulator. + */ +o3djs.manipulators.Manip.prototype.getOffsetTransform = function() { + return this.offsetTransform_; +} + +/** + * Returns the local transform of this manipulator, which contains the + * changes that have been made in response to the current drag + * operation. Upon completion of the drag, this transform's effects + * are composed in to the attached transform, and this transform is + * reset to the identity. + * @return {!o3d.Transform} The local transform of this manipulator. + */ +o3djs.manipulators.Manip.prototype.getTransform = function() { + return this.localTransform_; +} + +/** + * Sets the translation component of the offset transform. This is + * useful for moving the manipulator's geometry with respect to the + * local origin of the attached transform. + * @param {!o3djs.math.Vector3} translation The offset translation for + * this manipulator. + */ +o3djs.manipulators.Manip.prototype.setOffsetTranslation = + function(translation) { + this.getOffsetTransform().localMatrix = + o3djs.math.matrix4.setTranslation(this.getOffsetTransform().localMatrix, + translation); + +} + +/** + * Sets the rotation component of the offset transform. This is useful + * for orienting the manipulator's geometry with respect to the local + * origin of the attached transform. + * @param {!o3djs.quaternions.Quaternion} quaternion The offset + * rotation for this manipulator. + */ +o3djs.manipulators.Manip.prototype.setOffsetRotation = function(quaternion) { + var rot = o3djs.quaternions.quaternionToRotation(quaternion); + this.getOffsetTransform().localMatrix = + o3djs.math.matrix4.setUpper3x3(this.getOffsetTransform().localMatrix, + rot); + +} + +/** + * Explicitly sets the local translation of this manipulator. + * (TODO(kbr): it is not clear that this capability should be in the + * API.) + * @param {!o3djs.math.Vector3} translation The local translation for + * this manipulator. + */ +o3djs.manipulators.Manip.prototype.setTranslation = function(translation) { + this.getTransform().localMatrix = + o3djs.math.matrix4.setTranslation(this.getTransform().localMatrix, + translation); + +} + +/** + * Explicitly sets the local rotation of this manipulator. (TODO(kbr): + * it is not clear that this capability should be in the API.) + * @param {!o3djs.quaternions.Quaternion} quaternion The local + * rotation for this manipulator. + */ +o3djs.manipulators.Manip.prototype.setRotation = function(quaternion) { + var rot = o3djs.quaternions.quaternionToRotation(quaternion); + this.getTransform().localMatrix = + o3djs.math.matrix4.setUpper3x3(this.getTransform().localMatrix, + rot); + +} + +/** + * Attaches this manipulator to the given transform. Interactions with + * the manipulator will cause this transform's local matrix to be + * modified appropriately. + * @param {!o3d.Transform} transform The transform to which this + * manipulator should be attached. + */ +o3djs.manipulators.Manip.prototype.attachTo = function(transform) { + this.attachedTransform_ = transform; + // Update our base transform to place the manipulator at exactly the + // location of the attached transform. + this.updateBaseTransformFromAttachedTransform_(); +} + +/** + * Highlights this manipulator according to the given pick result. + * @param {o3djs.picking.PickInfo} pickResult The pick result which + * caused this manipulator to become highlighted. + */ +o3djs.manipulators.Manip.prototype.highlight = function(pickResult) { +} + +/** + * Clears any highlight for this manipulator. + */ +o3djs.manipulators.Manip.prototype.clearHighlight = function() { +} + +/** + * Activates this manipulator according to the given pick result. In + * complex manipulators, picking different portions of the manipulator + * may result in different forms of interaction. + * @param {o3djs.picking.PickInfo} pickResult The pick result which + * caused this manipulator to become active. + */ +o3djs.manipulators.Manip.prototype.makeActive = function(pickResult) { + this.active_ = true; +} + +/** + * Deactivates this manipulator. + */ +o3djs.manipulators.Manip.prototype.makeInactive = function() { + this.active_ = false; +} + +/** + * Drags this manipulator according to the world-space ray specified + * by startPoint and endPoint. makeActive must already have been + * called with the initial pick result causing this manipulator to + * become active. This method exists only to be overridden. + * @param {!o3djs.math.Vector3} startPoint Start point of the + * world-space ray through the current mouse position. + * @param {!o3djs.math.Vector3} endPoint End point of the world-space + * ray through the current mouse position. + */ +o3djs.manipulators.Manip.prototype.drag = function(startPoint, + endPoint) { +} + +/** + * Indicates whether this manipulator is active. + * @return {boolean} Whether this manipulator is active. + */ +o3djs.manipulators.Manip.prototype.isActive = function() { + return this.active_; +} + +/** + * Updates the base transform of this manipulator from the state of + * its attached transform, resetting the local transform of this + * manipulator to the identity. + * @private + */ +o3djs.manipulators.Manip.prototype.updateBaseTransformFromAttachedTransform_ = + function() { + if (this.attachedTransform_ != null) { + var attWorld = this.attachedTransform_.worldMatrix; + var parWorld = this.manager_.parentTransform.worldMatrix; + var parWorldInv = o3djs.math.matrix4.inverse(parWorld); + this.baseTransform_.localMatrix = + o3djs.math.matrix4.mul(attWorld, parWorldInv); + // Reset the manipulator's local matrix to the identity. + this.localTransform_.localMatrix = o3djs.math.matrix4.identity(); + } +} + +/** + * Updates this manipulator's attached transform based on the values + * in the local transform. + * @private + */ +o3djs.manipulators.Manip.prototype.updateAttachedTransformFromLocalTransform_ = + function() { + if (this.attachedTransform_ != null) { + // Compute the composition of the base and local transforms. + // The offset transform is skipped except for transforming the + // local matrix's translation by the rotation component of the + // offset transform. + var base = this.baseTransform_.worldMatrix; + var local = this.localTransform_.localMatrix; + var xlate = o3djs.math.matrix4.getTranslation(local); + var transformedXlate = + o3djs.math.matrix4.transformDirection( + this.offsetTransform_.localMatrix, + o3djs.math.matrix4.getTranslation(local)); + o3djs.math.matrix4.setTranslation(local, transformedXlate); + var totalMat = o3djs.math.matrix4.mul(base, local); + + // Set this into the attached transform, taking into account its + // parent's transform, if any. + // Note that we can not query the parent's transform directly, so + // we compute it using a little trick. + var attWorld = this.attachedTransform_.worldMatrix; + var attLocal = this.attachedTransform_.localMatrix; + var attParentMat = + o3djs.math.matrix4.mul(attWorld, + o3djs.math.matrix4.inverse(attLocal)); + // Now we can take the inverse of this matrix + var attParentMatInv = o3djs.math.matrix4.inverse(attParentMat); + totalMat = o3djs.math.matrix4.mul(attParentMatInv, totalMat); + this.attachedTransform_.localMatrix = totalMat; + } +} + +/** + * Sets the material of the given shape's draw elements. + * @private + */ +o3djs.manipulators.Manip.prototype.setMaterial_ = function(shape, material) { + var elements = shape.elements; + for (var ii = 0; ii < elements.length; ii++) { + var drawElements = elements[ii].drawElements; + for (var jj = 0; jj < drawElements.length; jj++) { + drawElements[jj].material = material; + } + } +} + +/** + * Sets the materials of the given shapes' draw elements. + * @private + */ +o3djs.manipulators.Manip.prototype.setMaterials_ = function(shapes, material) { + for (var ii = 0; ii < shapes.length; ii++) { + this.setMaterial_(shapes[ii], material); + } +} + +/** + * A manipulator allowing an object to be dragged along a line. + * @constructor + * @extends {o3djs.manipulators.Manip} + * @param {!o3djs.manipulators.Manager} manager The manager for the + * new Translate1 manipulator. + */ +o3djs.manipulators.Translate1 = function(manager) { + o3djs.manipulators.Manip.call(this, manager); + + var pack = manager.pack; + var material = manager.defaultMaterial; + + var shape = manager.translate1Shape_; + if (!shape) { + // Create the geometry for the manipulator, which looks like a + // two-way arrow going from (-1, 0, 0) to (1, 0, 0). + var matrix4 = o3djs.math.matrix4; + var zRot = matrix4.rotationZ(Math.PI / 2); + + var verts = o3djs.primitives.createTruncatedConeVertices( + 0.15, // Bottom radius. + 0.0, // Top radius. + 0.3, // Height. + 4, // Number of radial subdivisions. + 1, // Number of vertical subdivisions. + matrix4.mul(matrix4.translation([0, 0.85, 0]), zRot)); + + verts.append(o3djs.primitives.createCylinderVertices( + 0.06, // Radius. + 1.4, // Height. + 4, // Number of radial subdivisions. + 1, // Number of vertical subdivisions. + zRot)); + + verts.append(o3djs.primitives.createTruncatedConeVertices( + 0.0, // Bottom radius. + 0.15, // Top radius. + 0.3, // Height. + 4, // Number of radial subdivisions. + 1, // Number of vertical subdivisions. + matrix4.mul(matrix4.translation([0, -0.85, 0]), zRot))); + + shape = verts.createShape(pack, material); + manager.translate1Shape_ = shape; + } + + this.addShapes_([ shape ]); + + this.transformInfo = o3djs.picking.createTransformInfo(this.getTransform(), + manager.transformInfo); + + // Add a parameter to our transform to be able to change the + // material's color for highlighting. + this.colorParam = this.getTransform().createParam('diffuse', 'ParamFloat4'); + this.clearHighlight(); + + /** Dragging state */ + this.dragLine = new o3djs.manipulators.Line_(); +} + +o3djs.base.inherit(o3djs.manipulators.Translate1, o3djs.manipulators.Manip); + +o3djs.manipulators.Translate1.prototype.highlight = function(pickResult) { + // We can use instanced geometry for the entire Translate1 since its + // entire color changes during highlighting. + // TODO(kbr): support custom user geometry and associated callbacks. + this.colorParam.value = o3djs.manipulators.HIGHLIGHTED_COLOR; +} + +o3djs.manipulators.Translate1.prototype.clearHighlight = function() { + this.colorParam.value = o3djs.manipulators.DEFAULT_COLOR; +} + +o3djs.manipulators.Translate1.prototype.makeActive = function(pickResult) { + o3djs.manipulators.Manip.prototype.makeActive.call(this, pickResult); + this.highlight(pickResult); + var worldMatrix = this.getTransform().worldMatrix; + this.dragLine.setDirection( + o3djs.math.matrix4.transformDirection(worldMatrix, + o3djs.manipulators.X_AXIS)); + this.dragLine.setPoint(pickResult.worldIntersectionPosition); +} + +o3djs.manipulators.Translate1.prototype.makeInactive = function() { + o3djs.manipulators.Manip.prototype.makeInactive.call(this); + this.clearHighlight(); + this.updateAttachedTransformFromLocalTransform_(); + this.updateBaseTransformFromAttachedTransform_(); +} + +o3djs.manipulators.Translate1.prototype.drag = function(startPoint, + endPoint) { + // Algorithm: Find closest point of ray to dragLine. Subtract this + // point from the line's point to find difference vector; transform + // from world to local coordinates to find new local offset of + // manipulator. + var closestPoint = this.dragLine.closestPointToRay(startPoint, endPoint); + if (closestPoint == null) { + // Drag axis is parallel to ray. Punt. + return; + } + // Need to do a world-to-local transformation on the difference vector. + // Note that we also incorporate the translation portion of the matrix. + var diffVector = + o3djs.math.subVector(closestPoint, this.dragLine.getPoint()); + var worldToLocal = + o3djs.math.matrix4.inverse(this.getTransform().worldMatrix); + this.getTransform().localMatrix = + o3djs.math.matrix4.setTranslation( + this.getTransform().localMatrix, + o3djs.math.matrix4.transformDirection(worldToLocal, + diffVector)); + this.updateAttachedTransformFromLocalTransform_(); +} diff --git a/o3d/samples/o3djs/performance.js b/o3d/samples/o3djs/performance.js index 92865e5..c5faeaa 100644 --- a/o3d/samples/o3djs/performance.js +++ b/o3d/samples/o3djs/performance.js @@ -1,202 +1,202 @@ -/* - * Copyright 2009, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -/** - * @fileoverview This file contains a utility that helps adjust rendering - * quality [or any other setting, really] based on rendering performance. - * - */ - -o3djs.provide('o3djs.performance'); - -/** - * A Module to help with adjusting performance. - * @namespace - */ -o3djs.performance = o3djs.performance || {}; - -/** - * Creates a utility that monitors performance [in terms of FPS] and helps to - * adjust the rendered scene accordingly. - * @param {number} targetFPSMin the minimum acceptable frame rate; if we're - * under this, try to decrease quality to improve performance. - * @param {number} targetFPSMax if we're over this, try to increase quality. - * @param {!function(): void} increaseQuality a function to increase - * quality because we're rendering at high-enough FPS to afford it. - * @param {!function(): void} decreaseQuality a function to decrease - * quality to try to raise our rendering speed. - * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. - * @return {!o3djs.performance.PerformanceMonitor} The created - * PerformanceMonitor. - */ -o3djs.performance.createPerformanceMonitor = function( - targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { - return new o3djs.performance.PerformanceMonitor(targetFPSMin, targetFPSMax, - increaseQuality, decreaseQuality, opt_options); -}; - -/** - * A class that monitors performance [in terms of FPS] and helps to adjust the - * rendered scene accordingly. - * @constructor - * @param {number} targetFPSMin the minimum acceptable frame rate; if we're - * under this, try to decrease quality to improve performance. - * @param {number} targetFPSMax if we're over this, try to increase quality. - * @param {function(): void} increaseQuality a function to increase - * quality/lower FPS. - * @param {function(): void} decreaseQuality a function to decrease - * quality/raise FPS. - * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. - */ -o3djs.performance.PerformanceMonitor = function( - targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { - opt_options = opt_options || {}; - - /** - * A function to increase quality/lower FPS. - * @type {function(): void} - */ - this.increaseQuality = increaseQuality; - - /** - * A function to decrease quality/raise FPS. - * @type {function(): void} - */ - this.decreaseQuality = decreaseQuality; - - /** - * The mean time taken per frame so far, in seconds. This is only valid once - * we've collected at least minSamples samples. - * @type {number} - */ - this.meanFrameTime = 0; - - /** - * The number of samples we've collected so far, when that number is less than - * or equal to this.damping. After that point, we no longer update - * this.sampleCount, so it will clip at this.damping. - * - * @type {number} - */ - this.sampleCount = 0; - - /** - * The minimum number of samples to collect before trying to adjust quality. - * - * @type {number} - */ - this.minSamples = opt_options.opt_minSamples || 60; - - /** - * A number that controls the rate at which the effects of any given sample - * fade away. Higher is slower, but also means that each individual sample - * counts for less at its most-influential. Damping defaults to 120; anywhere - * between 60 and 600 are probably reasonable values, depending on your needs, - * but the number must be no less than minSamples. - * - * @type {number} - */ - this.damping = opt_options.opt_damping || 120; - - /** - * The minimum number of samples to take in between adjustments, to cut down - * on overshoot. It defaults to 2 * minSamples. - * - * @type {number} - */ - this.delayCycles = opt_options.opt_delayCycles || 2 * this.minSamples; - - this.targetFrameTimeMax_ = 1 / targetFPSMin; - this.targetFrameTimeMin_ = 1 / targetFPSMax; - this.scaleInput_ = 1 / this.minSamples; - this.scaleMean_ = 1; - this.delayCyclesLeft_ = 0; - if (this.damping < this.minSamples) { - throw Error('Damping must be at least minSamples.'); - } -}; - -/** - * Options for the PerformanceMonitor. - * - * opt_minSamples is the minimum number of samples to take before making any - * performance adjustments. - * opt_damping is a number that controls the rate at which the effects of any - * given sample fade away. Higher is slower, but also means that each - * individual sample counts for less at its most-influential. Damping - * defaults to 120; anywhere between 60 and 600 are probably reasonable values, - * depending on your needs, but the number must be no less than minSamples. - * opt_delayCycles is the minimum number of samples to take in between - * adjustments, to cut down on overshoot. It defaults to 2 * opt_minSamples. - * - * @type {{ - * opt_minSamples: number, - * opt_damping: number, - * opt_delayCycles: number - * }} - */ -o3djs.performance.PerformanceMonitor.Options = goog.typedef; - -/** - * Call this once per frame with the elapsed time since the last call, and it - * will attempt to adjust your rendering quality as needed. - * - * @param {number} seconds the elapsed time since the last frame was rendered, - * in seconds. - */ -o3djs.performance.PerformanceMonitor.prototype.onRender = function(seconds) { - var test = true; - if (this.sampleCount < this.damping) { - if (this.sampleCount >= this.minSamples) { - this.scaleInput_ = 1 / (this.sampleCount + 1); - this.scaleMean_ = this.sampleCount * this.scaleInput_; - } else { - test = false; - } - this.sampleCount += 1; - } - this.meanFrameTime = this.meanFrameTime * this.scaleMean_ + - seconds * this.scaleInput_; - if (this.delayCyclesLeft_ > 0) { - this.delayCyclesLeft_ -= 1; - } else if (test) { - if (this.meanFrameTime < this.targetFrameTimeMin_) { - this.increaseQuality(); - this.delayCyclesLeft_ = this.delayCycles; - } else if (this.meanFrameTime > this.targetFrameTimeMax_) { - this.decreaseQuality(); - this.delayCyclesLeft_ = this.delayCycles; - } - } -}; - - +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @fileoverview This file contains a utility that helps adjust rendering + * quality [or any other setting, really] based on rendering performance. + * + */ + +o3djs.provide('o3djs.performance'); + +/** + * A Module to help with adjusting performance. + * @namespace + */ +o3djs.performance = o3djs.performance || {}; + +/** + * Creates a utility that monitors performance [in terms of FPS] and helps to + * adjust the rendered scene accordingly. + * @param {number} targetFPSMin the minimum acceptable frame rate; if we're + * under this, try to decrease quality to improve performance. + * @param {number} targetFPSMax if we're over this, try to increase quality. + * @param {!function(): void} increaseQuality a function to increase + * quality because we're rendering at high-enough FPS to afford it. + * @param {!function(): void} decreaseQuality a function to decrease + * quality to try to raise our rendering speed. + * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. + * @return {!o3djs.performance.PerformanceMonitor} The created + * PerformanceMonitor. + */ +o3djs.performance.createPerformanceMonitor = function( + targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { + return new o3djs.performance.PerformanceMonitor(targetFPSMin, targetFPSMax, + increaseQuality, decreaseQuality, opt_options); +}; + +/** + * A class that monitors performance [in terms of FPS] and helps to adjust the + * rendered scene accordingly. + * @constructor + * @param {number} targetFPSMin the minimum acceptable frame rate; if we're + * under this, try to decrease quality to improve performance. + * @param {number} targetFPSMax if we're over this, try to increase quality. + * @param {function(): void} increaseQuality a function to increase + * quality/lower FPS. + * @param {function(): void} decreaseQuality a function to decrease + * quality/raise FPS. + * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. + */ +o3djs.performance.PerformanceMonitor = function( + targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { + opt_options = opt_options || {}; + + /** + * A function to increase quality/lower FPS. + * @type {function(): void} + */ + this.increaseQuality = increaseQuality; + + /** + * A function to decrease quality/raise FPS. + * @type {function(): void} + */ + this.decreaseQuality = decreaseQuality; + + /** + * The mean time taken per frame so far, in seconds. This is only valid once + * we've collected at least minSamples samples. + * @type {number} + */ + this.meanFrameTime = 0; + + /** + * The number of samples we've collected so far, when that number is less than + * or equal to this.damping. After that point, we no longer update + * this.sampleCount, so it will clip at this.damping. + * + * @type {number} + */ + this.sampleCount = 0; + + /** + * The minimum number of samples to collect before trying to adjust quality. + * + * @type {number} + */ + this.minSamples = opt_options.opt_minSamples || 60; + + /** + * A number that controls the rate at which the effects of any given sample + * fade away. Higher is slower, but also means that each individual sample + * counts for less at its most-influential. Damping defaults to 120; anywhere + * between 60 and 600 are probably reasonable values, depending on your needs, + * but the number must be no less than minSamples. + * + * @type {number} + */ + this.damping = opt_options.opt_damping || 120; + + /** + * The minimum number of samples to take in between adjustments, to cut down + * on overshoot. It defaults to 2 * minSamples. + * + * @type {number} + */ + this.delayCycles = opt_options.opt_delayCycles || 2 * this.minSamples; + + this.targetFrameTimeMax_ = 1 / targetFPSMin; + this.targetFrameTimeMin_ = 1 / targetFPSMax; + this.scaleInput_ = 1 / this.minSamples; + this.scaleMean_ = 1; + this.delayCyclesLeft_ = 0; + if (this.damping < this.minSamples) { + throw Error('Damping must be at least minSamples.'); + } +}; + +/** + * Options for the PerformanceMonitor. + * + * opt_minSamples is the minimum number of samples to take before making any + * performance adjustments. + * opt_damping is a number that controls the rate at which the effects of any + * given sample fade away. Higher is slower, but also means that each + * individual sample counts for less at its most-influential. Damping + * defaults to 120; anywhere between 60 and 600 are probably reasonable values, + * depending on your needs, but the number must be no less than minSamples. + * opt_delayCycles is the minimum number of samples to take in between + * adjustments, to cut down on overshoot. It defaults to 2 * opt_minSamples. + * + * @type {{ + * opt_minSamples: number, + * opt_damping: number, + * opt_delayCycles: number + * }} + */ +o3djs.performance.PerformanceMonitor.Options = goog.typedef; + +/** + * Call this once per frame with the elapsed time since the last call, and it + * will attempt to adjust your rendering quality as needed. + * + * @param {number} seconds the elapsed time since the last frame was rendered, + * in seconds. + */ +o3djs.performance.PerformanceMonitor.prototype.onRender = function(seconds) { + var test = true; + if (this.sampleCount < this.damping) { + if (this.sampleCount >= this.minSamples) { + this.scaleInput_ = 1 / (this.sampleCount + 1); + this.scaleMean_ = this.sampleCount * this.scaleInput_; + } else { + test = false; + } + this.sampleCount += 1; + } + this.meanFrameTime = this.meanFrameTime * this.scaleMean_ + + seconds * this.scaleInput_; + if (this.delayCyclesLeft_ > 0) { + this.delayCyclesLeft_ -= 1; + } else if (test) { + if (this.meanFrameTime < this.targetFrameTimeMin_) { + this.increaseQuality(); + this.delayCyclesLeft_ = this.delayCycles; + } else if (this.meanFrameTime > this.targetFrameTimeMax_) { + this.decreaseQuality(); + this.delayCyclesLeft_ = this.delayCycles; + } + } +}; + + diff --git a/o3d/samples/o3djs/texture.js b/o3d/samples/o3djs/texture.js index 33678b2..5041dde 100644 --- a/o3d/samples/o3djs/texture.js +++ b/o3d/samples/o3djs/texture.js @@ -1,239 +1,239 @@ -/* - * Copyright 2009, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -/** - * @fileoverview This file contains functions helping to manipulate and manage - * textures. - */ - -o3djs.provide('o3djs.texture'); - -/** - * A Module for bitmaps. - * @namespace - */ -o3djs.texture = o3djs.texture || {}; - -/** - * The maximum dimension of a texture. - * @type {number} - */ -o3djs.texture.MAX_TEXTURE_DIMENSION = 2048; - -/** - * Computes the maximum number of levels of mips a given width and height could - * use. - * @param {number} width Width of texture. - * @param {number} height Height of texture. - * @return {number} The maximum number of levels for the given width and height. - */ -o3djs.texture.computeNumLevels = function(width, height) { - if (width == 0 || height == 0) { - return 0; - } - var max = Math.max(width, height); - var levels = 0; - while (max > 0) { - ++levels; - max = max >> 1; - } - return levels; -}; - -/** - * Creates a texture from a RawData object. - * @param {!o3d.Pack} pack The pack to create the texture in. - * @param {!o3d.RawData} rawData The raw data to create the texture from. - * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips - * can not be generated for DXT textures although they will be loaded if they - * exist in the RawData. - * @param {boolean} opt_flip Whether or not to flip the texture. Most DCC tools - * Like Maya, Max, etc expect the textures to be flipped. Note that only - * 2D (image) textures will be flipped. Cube textures will not be flipped. - * Default = true. - * @param {number} opt_maxWidth The maximum width of the texture. If the RawData - * is larger than this size it will be scaled down to this size. Note that - * DXT format textures can not be scaled. Default = 2048. - * @param {number} opt_maxHeight The maximum width of the texture. If the - * RawData is larger than this size it will be scaled down to this size. Note - * that DXT format textures can not be scaled. Default = 2048. - * @return {!o3d.Texture} The created texture. - */ -o3djs.texture.createTextureFromRawData = function( - pack, - rawData, - opt_generateMips, - opt_flip, - opt_maxWidth, - opt_maxHeight) { - // Make a bitmaps from the raw data. - var bitmaps = pack.createBitmapsFromRawData(rawData); - if (opt_flip || typeof opt_flip === 'undefined') { - for (var ii = 0; ii < bitmaps.length; ++ii) { - var bitmap = bitmaps[ii]; - if (bitmap.semantic == o3djs.base.o3d.Bitmap.IMAGE) { - bitmaps[ii].flipVertically(); - } - } - } - - // Create a texture from the bitmaps. - var texture = o3djs.texture.createTextureFromBitmaps( - pack, bitmaps, opt_generateMips); - - // Delete the bitmaps. - for (var ii = 0; ii < bitmaps.length; ++ii) { - pack.removeObject(bitmaps[ii]); - } - - return texture; -}; - -/** - * Returns whether or not a given texture format can be scaled. - * @param {!o3d.Texture.Format} format The format to check. - * @return {boolean} True if you can scale and make mips for the given format. - */ -o3djs.texture.canMakeMipsAndScale = function(format) { - switch (format) { - case o3djs.base.o3d.Texture.XRGB8: - case o3djs.base.o3d.Texture.ARGB8: - case o3djs.base.o3d.Texture.ABGR16F: - case o3djs.base.o3d.Texture.R32F: - case o3djs.base.o3d.Texture.ABGR32F: - return true; - case o3djs.base.o3d.Texture.DXT1: - case o3djs.base.o3d.Texture.DXT3: - case o3djs.base.o3d.Texture.DXT5: - return false; - } - return false; -}; - -/** - * Creates a Texture from an array of bitmaps. - * @param {!o3d.Pack} pack The pack to create the texture in. - * @param {!Array.} bitmaps An array of bitmaps to create the - * texture from. For a 2D texture this would be 1 bitmap. For a cubemap this - * would be 6 bitmaps. - * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips - * can not be generated for DXT textures although they will be loaded if they - * exist in the RawData. Default = true. - * @return {!o3d.Texture} The created texture. - */ -o3djs.texture.createTextureFromBitmaps = function( - pack, - bitmaps, - opt_generateMips) { - - if (bitmaps.length == 0) { - throw 'no bitmaps'; - } - - var srcWidth = bitmaps[0].width; - var srcHeight = bitmaps[0].height; - var format = bitmaps[0].format; - var mipMaps = bitmaps[0].numMipmaps; - var maxMips = o3djs.texture.computeNumLevels(srcWidth, srcHeight); - var targetMips = mipMaps; - var dstWidth = srcWidth; - var dstHeight = srcHeight; - if ((typeof opt_generateMips === 'undefined' || opt_generateMips) && - o3djs.texture.canMakeMipsAndScale(format) && - mipMaps == 1 && maxMips > 1) { - targetMips = maxMips; - } - - // Check that all the bitmaps are the same size and make mips - for (var ii = 0; ii < bitmaps.length; ++ii) { - var bitmap = bitmaps[ii]; - if (bitmap.width != srcWidth || - bitmap.height != srcHeight || - bitmap.format != format || - bitmap.numMipmaps != mipMaps) { - throw 'bitmaps must all be the same width, height, mips and format'; - } - if (targetMips != mipMaps) { - bitmap.generateMips(0, targetMips - 1); - } - } - - var levels = bitmap.numMipmaps > 1 ? bitmap.numMipmaps : - o3djs.texture.computeNumLevels(dstWidth, dstHeight); - var texture; - if (bitmaps.length == 6 && - bitmaps[0].semantic != o3djs.base.o3d.Bitmap.SLICE) { - if (srcWidth != srcHeight || - srcWidth != dstWidth || - srcHeight != dstHeight) { - throw 'Cubemaps must be square'; - } - texture = pack.createTextureCUBE(dstWidth, format, targetMips, false); - for (var ii = 0; ii < 6; ++ii) { - texture.setFromBitmap( - /** @type {o3d.TextureCUBE.CubeFace} */ (ii), - bitmaps[ii]); - } - } else if (bitmaps.length == 1) { - texture = pack.createTexture2D( - dstWidth, dstHeight, format, targetMips, false); - texture.setFromBitmap(bitmaps[0]); - } - - return /** @type{!o3d.Texture} */ (texture); -}; - -/** - * Creates a TextureCUBE from 6 bitmaps. The bitmaps do not have to be the same - * size thought they do have to be the same format. - * - * @param {!o3d.Pack} pack The pack to create the texture in. - * @param {number} edgeLength The size of the cubemap. - * @param {!Array.} bitmaps An array of 6 bitmaps in the order - * FACE_POSITIVE_X, FACE_NEGATIVE_X, FACE_POSITIVE_Y, FACE_NEGATIVE_Y, - * FACE_POSITIVE_Z, FACE_NEGATIVE_Z. - * @return {!o3d.Texture} The created texture. - */ -o3djs.texture.createCubeTextureFrom6Bitmaps = function( - pack, edgeLength, bitmaps) { - var numMips = o3djs.texture.computeNumLevels(edgeLength, edgeLength); - var texture = pack.createTextureCUBE( - edgeLength, bitmaps[0].format, numMips, false); - for (var ii = 0; ii < 6; ++ii) { - var bitmap = bitmaps[ii]; - texture.drawImage(bitmap, 0, 0, 0, bitmap.width, bitmap.height, - ii, 0, 0, edgeLength, edgeLength); - } - texture.generateMips(0, numMips - 1); - return texture; -}; - +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @fileoverview This file contains functions helping to manipulate and manage + * textures. + */ + +o3djs.provide('o3djs.texture'); + +/** + * A Module for bitmaps. + * @namespace + */ +o3djs.texture = o3djs.texture || {}; + +/** + * The maximum dimension of a texture. + * @type {number} + */ +o3djs.texture.MAX_TEXTURE_DIMENSION = 2048; + +/** + * Computes the maximum number of levels of mips a given width and height could + * use. + * @param {number} width Width of texture. + * @param {number} height Height of texture. + * @return {number} The maximum number of levels for the given width and height. + */ +o3djs.texture.computeNumLevels = function(width, height) { + if (width == 0 || height == 0) { + return 0; + } + var max = Math.max(width, height); + var levels = 0; + while (max > 0) { + ++levels; + max = max >> 1; + } + return levels; +}; + +/** + * Creates a texture from a RawData object. + * @param {!o3d.Pack} pack The pack to create the texture in. + * @param {!o3d.RawData} rawData The raw data to create the texture from. + * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips + * can not be generated for DXT textures although they will be loaded if they + * exist in the RawData. + * @param {boolean} opt_flip Whether or not to flip the texture. Most DCC tools + * Like Maya, Max, etc expect the textures to be flipped. Note that only + * 2D (image) textures will be flipped. Cube textures will not be flipped. + * Default = true. + * @param {number} opt_maxWidth The maximum width of the texture. If the RawData + * is larger than this size it will be scaled down to this size. Note that + * DXT format textures can not be scaled. Default = 2048. + * @param {number} opt_maxHeight The maximum width of the texture. If the + * RawData is larger than this size it will be scaled down to this size. Note + * that DXT format textures can not be scaled. Default = 2048. + * @return {!o3d.Texture} The created texture. + */ +o3djs.texture.createTextureFromRawData = function( + pack, + rawData, + opt_generateMips, + opt_flip, + opt_maxWidth, + opt_maxHeight) { + // Make a bitmaps from the raw data. + var bitmaps = pack.createBitmapsFromRawData(rawData); + if (opt_flip || typeof opt_flip === 'undefined') { + for (var ii = 0; ii < bitmaps.length; ++ii) { + var bitmap = bitmaps[ii]; + if (bitmap.semantic == o3djs.base.o3d.Bitmap.IMAGE) { + bitmaps[ii].flipVertically(); + } + } + } + + // Create a texture from the bitmaps. + var texture = o3djs.texture.createTextureFromBitmaps( + pack, bitmaps, opt_generateMips); + + // Delete the bitmaps. + for (var ii = 0; ii < bitmaps.length; ++ii) { + pack.removeObject(bitmaps[ii]); + } + + return texture; +}; + +/** + * Returns whether or not a given texture format can be scaled. + * @param {!o3d.Texture.Format} format The format to check. + * @return {boolean} True if you can scale and make mips for the given format. + */ +o3djs.texture.canMakeMipsAndScale = function(format) { + switch (format) { + case o3djs.base.o3d.Texture.XRGB8: + case o3djs.base.o3d.Texture.ARGB8: + case o3djs.base.o3d.Texture.ABGR16F: + case o3djs.base.o3d.Texture.R32F: + case o3djs.base.o3d.Texture.ABGR32F: + return true; + case o3djs.base.o3d.Texture.DXT1: + case o3djs.base.o3d.Texture.DXT3: + case o3djs.base.o3d.Texture.DXT5: + return false; + } + return false; +}; + +/** + * Creates a Texture from an array of bitmaps. + * @param {!o3d.Pack} pack The pack to create the texture in. + * @param {!Array.} bitmaps An array of bitmaps to create the + * texture from. For a 2D texture this would be 1 bitmap. For a cubemap this + * would be 6 bitmaps. + * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips + * can not be generated for DXT textures although they will be loaded if they + * exist in the RawData. Default = true. + * @return {!o3d.Texture} The created texture. + */ +o3djs.texture.createTextureFromBitmaps = function( + pack, + bitmaps, + opt_generateMips) { + + if (bitmaps.length == 0) { + throw 'no bitmaps'; + } + + var srcWidth = bitmaps[0].width; + var srcHeight = bitmaps[0].height; + var format = bitmaps[0].format; + var mipMaps = bitmaps[0].numMipmaps; + var maxMips = o3djs.texture.computeNumLevels(srcWidth, srcHeight); + var targetMips = mipMaps; + var dstWidth = srcWidth; + var dstHeight = srcHeight; + if ((typeof opt_generateMips === 'undefined' || opt_generateMips) && + o3djs.texture.canMakeMipsAndScale(format) && + mipMaps == 1 && maxMips > 1) { + targetMips = maxMips; + } + + // Check that all the bitmaps are the same size and make mips + for (var ii = 0; ii < bitmaps.length; ++ii) { + var bitmap = bitmaps[ii]; + if (bitmap.width != srcWidth || + bitmap.height != srcHeight || + bitmap.format != format || + bitmap.numMipmaps != mipMaps) { + throw 'bitmaps must all be the same width, height, mips and format'; + } + if (targetMips != mipMaps) { + bitmap.generateMips(0, targetMips - 1); + } + } + + var levels = bitmap.numMipmaps > 1 ? bitmap.numMipmaps : + o3djs.texture.computeNumLevels(dstWidth, dstHeight); + var texture; + if (bitmaps.length == 6 && + bitmaps[0].semantic != o3djs.base.o3d.Bitmap.SLICE) { + if (srcWidth != srcHeight || + srcWidth != dstWidth || + srcHeight != dstHeight) { + throw 'Cubemaps must be square'; + } + texture = pack.createTextureCUBE(dstWidth, format, targetMips, false); + for (var ii = 0; ii < 6; ++ii) { + texture.setFromBitmap( + /** @type {o3d.TextureCUBE.CubeFace} */ (ii), + bitmaps[ii]); + } + } else if (bitmaps.length == 1) { + texture = pack.createTexture2D( + dstWidth, dstHeight, format, targetMips, false); + texture.setFromBitmap(bitmaps[0]); + } + + return /** @type{!o3d.Texture} */ (texture); +}; + +/** + * Creates a TextureCUBE from 6 bitmaps. The bitmaps do not have to be the same + * size thought they do have to be the same format. + * + * @param {!o3d.Pack} pack The pack to create the texture in. + * @param {number} edgeLength The size of the cubemap. + * @param {!Array.} bitmaps An array of 6 bitmaps in the order + * FACE_POSITIVE_X, FACE_NEGATIVE_X, FACE_POSITIVE_Y, FACE_NEGATIVE_Y, + * FACE_POSITIVE_Z, FACE_NEGATIVE_Z. + * @return {!o3d.Texture} The created texture. + */ +o3djs.texture.createCubeTextureFrom6Bitmaps = function( + pack, edgeLength, bitmaps) { + var numMips = o3djs.texture.computeNumLevels(edgeLength, edgeLength); + var texture = pack.createTextureCUBE( + edgeLength, bitmaps[0].format, numMips, false); + for (var ii = 0; ii < 6; ++ii) { + var bitmap = bitmaps[ii]; + texture.drawImage(bitmap, 0, 0, 0, bitmap.width, bitmap.height, + ii, 0, 0, edgeLength, edgeLength); + } + texture.generateMips(0, numMips - 1); + return texture; +}; + diff --git a/o3d/samples/siteswap/animation.js b/o3d/samples/siteswap/animation.js index eb20cc8..c9aa80a 100644 --- a/o3d/samples/siteswap/animation.js +++ b/o3d/samples/siteswap/animation.js @@ -1,198 +1,198 @@ -// @@REWRITE(insert js-copyright) -// @@REWRITE(delete-start) -// Copyright 2009 Google Inc. All Rights Reserved -// @@REWRITE(delete-end) - -/** - * This file contains the animation-management code for the siteswap animator. - * This is encapsulated in the EventQueue and QueueEvent classes, the event - * handler, and startAnimation, the main external interface to the animation. - */ - -/** - * A record, held in the EventQueue, that describes the curve that should be - * given to a shape at a time. - * @constructor - * @param {!number} time base time at which the event occurs/the curve starts. - * @param {!o3d.Shape} shape the shape to be updated. - * @param {Curve} curve the path for the shape to follow. - */ -function QueueEvent(time, shape, curve) { - this.time = time; - this.shape = shape; - this.curve = curve; - return this; -} - -/** - * A circular queue of events that will happen during the course of an animation - * that's duration beats long. The queue is ordered by the time each curve - * starts. Note that a curve may start after it ends, since time loops - * endlessly. The nextEvent field is the index of the next event to occur; it - * keeps track of how far we've gotten in the queue. - * @constructor - * @param {!number} duration the length of the animation in beats. - */ -function EventQueue(duration) { - this.events = []; - this.nextEvent = 0; - this.duration = duration; - this.timeCorrection = 0; // Corrects from queue entry time to real time. - return this; -} - -/** - * Add an event to the queue, inserting into order by its time field. - * A heap-based priority queue would be faster, but likely overkill, as this - * won't ever contain that many items, and isn't likely to be speed-critical. - * @param {!QueueEvent} event the event to add. - */ -EventQueue.prototype.addEvent = function(event) { - var i = 0; - while (i < this.events.length && event.time > this.events[i].time) { - ++i; - } - this.events.splice(i, 0, event); -}; - -/** - * Pull the next event off the queue. - * @return {!QueueEvent} the event. - */ -EventQueue.prototype.shift = function() { - var e = this.events[this.nextEvent]; - if (++this.nextEvent >= this.events.length) { - assert(this.nextEvent > 0); - this.nextEvent = 0; - this.timeCorrection += this.duration; - } - return e; -}; - -/** - * Process all current events, updating all animated objects with their new - * curves, until we find that the next event in the queue is in the future. - * @param {!number} time the current time in beats. This number is an absolute, - * not locked to the range of the duration of the pattern, so getNextTime() - * returns a doctored number to add in the offset from in-pattern time to real - * time. - * @return {!number} the time of the next future event. - */ -EventQueue.prototype.processEvents = function(time) { - while (this.getNextTime() <= time) { - var e = this.shift(); - setParamCurveInfo(e.curve, e.shape, time); - } - return this.getNextTime(); // In case you want to set a callback. -}; - -/** - * Look up the initial curve for a shape [the curve that it'll be starting or in - * the middle of at time 0]. - * @param {!CurveSet} curveSet the complete set of curves for a Shape. - * @return {!Object} the curve and the time at which it would have started. - */ -function getInitialCurveInfo(curveSet) { - var curve = curveSet.getCurveForUnsafeTime(0); - var curveBaseTime; - if (!curve.startTime) { - curveBaseTime = 0; - } else { - // If the curve isn't starting now, it must have wrapped around. - assert(curve.startTime + curve.duration > curveSet.duration); - // So subtract off one wrap so that its startTime is in the right space of - // numbers. We assume here that no curve duration is longer than the - // pattern, which must be guaranteed by the code that generates patterns. - assert(curve.duration <= curveSet.duration); - curveBaseTime = curve.startTime - curveSet.duration; - } - return { curve: curve, curveBaseTime: curveBaseTime }; -} - -/** - * Set up the event queue with a complete pattern starting at time 0. - * @param {!Array.CurveSet} curveSets the curve sets for all shapes. - * @param {!Array.o3d.Shape} shapes all the shapes to animate. - */ -EventQueue.prototype.setUp = function(curveSets, shapes) { - assert(curveSets.length == shapes.length); - for (var i = 0; i < shapes.length; ++i) { - var curveSet = curveSets[i]; - assert(this.duration % curveSet.duration == 0); - var shape = shapes[i]; - var record = getInitialCurveInfo(curveSet); - var curveBaseTime = record.curveBaseTime; - var curve = record.curve; - setParamCurveInfo(curve, shape, curveBaseTime); - do { - curveBaseTime += curve.duration; - curve = curveSet.getCurveForTime(curveBaseTime); - var e = new QueueEvent(curveBaseTime % this.duration, shape, curve); - this.addEvent(e); - } while (curveBaseTime + curve.duration <= this.duration); - } -}; - -/** - * Return the time of the next future event. - * @return {!number} the time. - */ -EventQueue.prototype.getNextTime = function() { - return this.events[this.nextEvent].time + this.timeCorrection; -}; - -/** - * This is the event handler that runs the whole animation. When triggered by - * the counter, it updates the curves on all objects whose curves have expired. - * - * The current time will be some time around when we wanted to be called. It - * might be exact, but it might be a bit off due to floating point error, or a - * lot off due to the system getting bogged down somewhere for a second or - * two. e.g. if we wanted to get a call at time 7, it's likely to be - * something like 7.04, but might even be 11. We then use 7, not 7.04, as the - * start time for each of the curves set, so as to remove clock drift. Since - * the time we wanted to be called is stored in the next item in the queue, we - * can just pull that out and use it. However, if we then find that we're - * setting our callback in the past, we repeat the process until our callback - * is set safely in the future. We may get some visual artifacts, but at - * least we won't drop any events [leading to stuff drifting endlessly off - * into the distance]. - */ -function handler() { - var eventTime = g.eventQueue.getNextTime(); - var trueCurrentTime; - do { - g.counter.removeCallback(eventTime); - eventTime = g.eventQueue.processEvents(eventTime); - g.counter.addCallback(eventTime, handler); - trueCurrentTime = g.counter.count; - } while (eventTime < trueCurrentTime); -} - -/** - * Given a precomputed juggling pattern, this sets up the O3D objects, - * EventQueue, and callback necessary to start an animation, then calls - * updateAnimating to kick it off if enabled. - * - * @param {!number} numBalls the number of balls in the animation. - * @param {!number} numHands the number of hands in the animation. - * @param {!number} duration the length of the full animation cycle in beats. - * @param {!Array.CurveSet} ballCurveSets one CurveSet per ball. - * @param {!Array.CurveSet} handCurveSets one CurveSet per hand. - */ -function startAnimation(numBalls, numHands, duration, ballCurveSets, - handCurveSets) { - g.counter.running = false; - g.counter.reset(); - - setNumBalls(numBalls); - setNumHands(numHands); - - g.eventQueue = new EventQueue(duration); - g.eventQueue.setUp(handCurveSets, g.handShapes); - g.eventQueue.setUp(ballCurveSets, g.ballShapes); - g.counter.addCallback(g.eventQueue.getNextTime(), handler); - - updateAnimating(); -} - +// @@REWRITE(insert js-copyright) +// @@REWRITE(delete-start) +// Copyright 2009 Google Inc. All Rights Reserved +// @@REWRITE(delete-end) + +/** + * This file contains the animation-management code for the siteswap animator. + * This is encapsulated in the EventQueue and QueueEvent classes, the event + * handler, and startAnimation, the main external interface to the animation. + */ + +/** + * A record, held in the EventQueue, that describes the curve that should be + * given to a shape at a time. + * @constructor + * @param {!number} time base time at which the event occurs/the curve starts. + * @param {!o3d.Shape} shape the shape to be updated. + * @param {Curve} curve the path for the shape to follow. + */ +function QueueEvent(time, shape, curve) { + this.time = time; + this.shape = shape; + this.curve = curve; + return this; +} + +/** + * A circular queue of events that will happen during the course of an animation + * that's duration beats long. The queue is ordered by the time each curve + * starts. Note that a curve may start after it ends, since time loops + * endlessly. The nextEvent field is the index of the next event to occur; it + * keeps track of how far we've gotten in the queue. + * @constructor + * @param {!number} duration the length of the animation in beats. + */ +function EventQueue(duration) { + this.events = []; + this.nextEvent = 0; + this.duration = duration; + this.timeCorrection = 0; // Corrects from queue entry time to real time. + return this; +} + +/** + * Add an event to the queue, inserting into order by its time field. + * A heap-based priority queue would be faster, but likely overkill, as this + * won't ever contain that many items, and isn't likely to be speed-critical. + * @param {!QueueEvent} event the event to add. + */ +EventQueue.prototype.addEvent = function(event) { + var i = 0; + while (i < this.events.length && event.time > this.events[i].time) { + ++i; + } + this.events.splice(i, 0, event); +}; + +/** + * Pull the next event off the queue. + * @return {!QueueEvent} the event. + */ +EventQueue.prototype.shift = function() { + var e = this.events[this.nextEvent]; + if (++this.nextEvent >= this.events.length) { + assert(this.nextEvent > 0); + this.nextEvent = 0; + this.timeCorrection += this.duration; + } + return e; +}; + +/** + * Process all current events, updating all animated objects with their new + * curves, until we find that the next event in the queue is in the future. + * @param {!number} time the current time in beats. This number is an absolute, + * not locked to the range of the duration of the pattern, so getNextTime() + * returns a doctored number to add in the offset from in-pattern time to real + * time. + * @return {!number} the time of the next future event. + */ +EventQueue.prototype.processEvents = function(time) { + while (this.getNextTime() <= time) { + var e = this.shift(); + setParamCurveInfo(e.curve, e.shape, time); + } + return this.getNextTime(); // In case you want to set a callback. +}; + +/** + * Look up the initial curve for a shape [the curve that it'll be starting or in + * the middle of at time 0]. + * @param {!CurveSet} curveSet the complete set of curves for a Shape. + * @return {!Object} the curve and the time at which it would have started. + */ +function getInitialCurveInfo(curveSet) { + var curve = curveSet.getCurveForUnsafeTime(0); + var curveBaseTime; + if (!curve.startTime) { + curveBaseTime = 0; + } else { + // If the curve isn't starting now, it must have wrapped around. + assert(curve.startTime + curve.duration > curveSet.duration); + // So subtract off one wrap so that its startTime is in the right space of + // numbers. We assume here that no curve duration is longer than the + // pattern, which must be guaranteed by the code that generates patterns. + assert(curve.duration <= curveSet.duration); + curveBaseTime = curve.startTime - curveSet.duration; + } + return { curve: curve, curveBaseTime: curveBaseTime }; +} + +/** + * Set up the event queue with a complete pattern starting at time 0. + * @param {!Array.CurveSet} curveSets the curve sets for all shapes. + * @param {!Array.o3d.Shape} shapes all the shapes to animate. + */ +EventQueue.prototype.setUp = function(curveSets, shapes) { + assert(curveSets.length == shapes.length); + for (var i = 0; i < shapes.length; ++i) { + var curveSet = curveSets[i]; + assert(this.duration % curveSet.duration == 0); + var shape = shapes[i]; + var record = getInitialCurveInfo(curveSet); + var curveBaseTime = record.curveBaseTime; + var curve = record.curve; + setParamCurveInfo(curve, shape, curveBaseTime); + do { + curveBaseTime += curve.duration; + curve = curveSet.getCurveForTime(curveBaseTime); + var e = new QueueEvent(curveBaseTime % this.duration, shape, curve); + this.addEvent(e); + } while (curveBaseTime + curve.duration <= this.duration); + } +}; + +/** + * Return the time of the next future event. + * @return {!number} the time. + */ +EventQueue.prototype.getNextTime = function() { + return this.events[this.nextEvent].time + this.timeCorrection; +}; + +/** + * This is the event handler that runs the whole animation. When triggered by + * the counter, it updates the curves on all objects whose curves have expired. + * + * The current time will be some time around when we wanted to be called. It + * might be exact, but it might be a bit off due to floating point error, or a + * lot off due to the system getting bogged down somewhere for a second or + * two. e.g. if we wanted to get a call at time 7, it's likely to be + * something like 7.04, but might even be 11. We then use 7, not 7.04, as the + * start time for each of the curves set, so as to remove clock drift. Since + * the time we wanted to be called is stored in the next item in the queue, we + * can just pull that out and use it. However, if we then find that we're + * setting our callback in the past, we repeat the process until our callback + * is set safely in the future. We may get some visual artifacts, but at + * least we won't drop any events [leading to stuff drifting endlessly off + * into the distance]. + */ +function handler() { + var eventTime = g.eventQueue.getNextTime(); + var trueCurrentTime; + do { + g.counter.removeCallback(eventTime); + eventTime = g.eventQueue.processEvents(eventTime); + g.counter.addCallback(eventTime, handler); + trueCurrentTime = g.counter.count; + } while (eventTime < trueCurrentTime); +} + +/** + * Given a precomputed juggling pattern, this sets up the O3D objects, + * EventQueue, and callback necessary to start an animation, then calls + * updateAnimating to kick it off if enabled. + * + * @param {!number} numBalls the number of balls in the animation. + * @param {!number} numHands the number of hands in the animation. + * @param {!number} duration the length of the full animation cycle in beats. + * @param {!Array.CurveSet} ballCurveSets one CurveSet per ball. + * @param {!Array.CurveSet} handCurveSets one CurveSet per hand. + */ +function startAnimation(numBalls, numHands, duration, ballCurveSets, + handCurveSets) { + g.counter.running = false; + g.counter.reset(); + + setNumBalls(numBalls); + setNumHands(numHands); + + g.eventQueue = new EventQueue(duration); + g.eventQueue.setUp(handCurveSets, g.handShapes); + g.eventQueue.setUp(ballCurveSets, g.ballShapes); + g.counter.addCallback(g.eventQueue.getNextTime(), handler); + + updateAnimating(); +} + diff --git a/o3d/samples/siteswap/math.js b/o3d/samples/siteswap/math.js index c5e2f43..e584744 100644 --- a/o3d/samples/siteswap/math.js +++ b/o3d/samples/siteswap/math.js @@ -1,1492 +1,1492 @@ -// @@REWRITE(insert js-copyright) -// @@REWRITE(delete-start) -// Copyright 2009 Google Inc. All Rights Reserved -// @@REWRITE(delete-end) - -/** - * @fileoverview This file contains all the math for the siteswap animator. It - * handles all of the site-swap-related stuff [converting a sequence of integers - * into a more-useful representation of a pattern, pattern validation, etc.] as - * well as all the physics used for the simulation. - */ - -/** - * This is a container class that holds the coefficients of an equation - * describing the motion of an object. - * The basic equation is: - * f(x) := a t^2 + b t + c + d sin (f t) + e cos (f t). - * However, sometimes we LERP between that function and this one: - * g(x) := lA t^2 + lB t + lC - * lerpRate [so far] is always either 1 [LERP from f to g over 1 beat] or -1, - * [LERP from g to f over one beat]. - * - * Just plug in t to evaluate the equation. There's no JavaScript function to - * do this because it's always done on the GPU. - * - * @constructor - */ -EquationCoefficients = function(a, b, c, d, e, f, lA, lB, lC, lerpRate) { - assert(!isNaN(a) && !isNaN(b) && !isNaN(c)); - d = d || 0; - e = e || 0; - f = f || 0; - assert(!isNaN(d) && !isNaN(e) && !isNaN(f)); - lA = lA || 0; - lB = lB || 0; - lC = lC || 0; - assert(!isNaN(lA) && !isNaN(lB) && !isNaN(lC)); - lerpRate = lerpRate || 0; - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - this.lA = lA; - this.lB = lB; - this.lC = lC; - this.lerpRate = lerpRate; -} - -/** - * Create a new equation that's equivalent to this equation's coefficients a-f - * with a LERP to the polynomial portion of the supplied equation. - * @param {!EquationCoefficients} eqn the source of coefficients. - * @param {!number} lerpRate the rate and direction of the LERP; positive for - * "from this equation to the new one" and vice-versa. - * @return {!EquationCoefficients} a new set of coefficients. - */ -EquationCoefficients.prototype.lerpIn = function(eqn, lerpRate) { - assert(!this.lerpRate); - return new EquationCoefficients(this.a, this.b, this.c, this.d, this.e, - this.f, eqn.a, eqn.b, eqn.c, lerpRate); -}; - -/** - * Convert the EquationCoefficients to a string for debugging. - * @return {String} debugging output. - */ -EquationCoefficients.prototype.toString = function() { - return 'F(t) := ' + this.a.toFixed(2) + ' t^2 + ' + this.b.toFixed(2) + - ' t + ' + this.c.toFixed(2) + ' + ' + - this.d.toFixed(2) + ' sin(' + this.f.toFixed(2) + ' t) + ' + - this.e.toFixed(2) + ' cos(' + this.f.toFixed(2) + ' t) + LERP(' + - this.lerpRate.toFixed(2) + ') of ' + - this.lA.toFixed(2) + ' t^2 + ' + this.lB.toFixed(2) + - ' t + ' + this.lC.toFixed(2); -}; - -/** - * A set of equations which describe the motion of an object over time. - * The three equations each supply one dimension of the motion, and the curve is - * valid from startTime to startTime + duration. - * @param {!number} startTime the initial time at which the curve is valid. - * @param {!number} duration how long [in beats] the curve is valid. - * @param {!EquationCoefficients} xEqn the equation for motion in x. - * @param {!EquationCoefficients} yEqn the equation for motion in y. - * @param {!EquationCoefficients} zEqn the equation for motion in z. - * @constructor - */ -Curve = function(startTime, duration, xEqn, yEqn, zEqn) { - this.startTime = startTime; - this.duration = duration; - this.xEqn = xEqn; - this.yEqn = yEqn; - this.zEqn = zEqn; -} - -/** - * Convert the Curve to a string for debugging. - * @return {String} debugging output. - */ -Curve.prototype.toString = function() { - var s = 'startTime: ' + this.startTime + '\n'; - s += 'duration: ' + this.duration + '\n'; - s += this.xEqn + '\n'; - s += this.yEqn + '\n'; - s += this.zEqn + '\n'; - return s; -}; - -/** - * Modify this curve's coefficients to include a LERP to the polynomial - * portion of the supplied curve. - * @param {!Curve} curve the source of coefficients. - * @param {!number} lerpRate the rate and direction of the LERP; positive for - * "from this equation to the new one" and vice-versa. - * @return {!Curve} a new curve. - */ -Curve.prototype.lerpIn = function(curve, lerpRate) { - assert(this.startTime == curve.startTime); - assert(this.duration == curve.duration); - var xEqn = this.xEqn.lerpIn(curve.xEqn, lerpRate); - var yEqn = this.yEqn.lerpIn(curve.yEqn, lerpRate); - var zEqn = this.zEqn.lerpIn(curve.zEqn, lerpRate); - return new Curve(this.startTime, this.duration, xEqn, yEqn, zEqn); -}; - -/** - * Produce a set of polynomial coefficients that describe linear motion between - * two points in 1 dimension. - * @param {!number} startPos the starting position. - * @param {!number} endPos the ending position. - * @param {!number} duration how long the motion takes. - * @return {!EquationCoefficients} the equation for the motion. - */ -Curve.computeLinearCoefficients = function(startPos, endPos, duration) { - return new EquationCoefficients( - 0, (endPos - startPos) / duration, startPos); -} - -var GRAVITY = 1; // Higher means higher throws for the same duration. -/** - * Produce a set of polynomial coefficients that describe parabolic motion - * between two points in 1 dimension. - * @param {!number} startPos the starting position. - * @param {!number} endPos the ending position. - * @param {!number} duration how long the motion takes. - * @return {!EquationCoefficients} the equation for the motion. - */ -Curve.computeParabolicCoefficients = function(startPos, endPos, duration) { - var dY = endPos - startPos; - return new EquationCoefficients(-GRAVITY / 2, - dY / duration + GRAVITY * duration / 2, - startPos); -} - -/** - * Compute the curve taken by a ball given its throw and catch positions, the - * time it was thrown, and how long it stayed in the air. - * - * We use duration rather than throwTime and catchTime because, what - * with the modular arithmetic used in our records, catchTime might be before - * throwTime, and in some representations the pattern could wrap around a few - * times while the ball's in the air. When the parabola computed here is used, - * time must be supplied as an offset from the time of the throw, and must of - * course not wrap at all. That is, these coefficients work for f(0) == - * throwPos, f(duration) == catchPos. - * - * We treat the y axis as vertical and thus affected by gravity. - * - * @param {!EquationCoefficients} throwPos - * @param {!EquationCoefficients} catchPos - * @param {!number} startTime - * @param {!number} duration - * @return {!Curve} - */ -Curve.computeThrowCurve = function(throwPos, catchPos, startTime, duration) { - var xEqn = Curve.computeLinearCoefficients(throwPos.x, catchPos.x, duration); - var yEqn = Curve.computeParabolicCoefficients(throwPos.y, catchPos.y, - duration); - var zEqn = Curve.computeLinearCoefficients(throwPos.z, catchPos.z, duration); - return new Curve(startTime, duration, xEqn, yEqn, zEqn); -} - -/** - * Compute a straight line Curve given start and end positions, the start time, - * and the duration of the motion. - * - * @param {!EquationCoefficients} startPos - * @param {!EquationCoefficients} endPos - * @param {!number} startTime - * @param {!number} duration - * @return {!Curve} - */ -Curve.computeStraightLineCurve = - function(startPos, endPos, startTime, duration) { - var xEqn = Curve.computeLinearCoefficients(startPos.x, endPos.x, duration); - var yEqn = Curve.computeLinearCoefficients(startPos.y, endPos.y, duration); - var zEqn = Curve.computeLinearCoefficients(startPos.z, endPos.z, duration); - return new Curve(startTime, duration, xEqn, yEqn, zEqn); -} - -/** - * Threshold horizontal distance below which computeCircularCurve won't bother - * trying to approximate a circular curve. See the comment above - * computeCircularCurve for more info. - * @type {number} - */ -Curve.EPSILON = .0001; - -/** - * Compute a circular curve, used as an approximation for the motion of a hand - * between a catch and its following throw. - * - * Assumes a lot of stuff about this looking like a "normal" throw: the catch is - * moving roughly the opposite direction as the throw, the throw and catch - * aren't at the same place, and such. Otherwise this looks very odd at best. - * This is used for the height of the curve. - * This produces coefficients for d sin(f t) + e cos(f t) for each of x, y, z. - * It produces a vertical-ish circular curve from the start to the end, going - * down, then up. So if dV [the distance from the start to finish in the x-z - * plane, ignoring y] is less than Curve.EPSILON, it doesn't know which way down - * is, and it bails by returning a straight line instead. - */ -Curve.computeCircularCurve = function(startPos, endPos, startTime, duration) { - var dX = endPos.x - startPos.x; - var dY = endPos.y - startPos.y; - var dZ = endPos.z - startPos.z; - var dV = Math.sqrt(dX * dX + dZ * dZ); - if (dV < Curve.EPSILON) { - return Curve.computeStraightLineCurve(startPos, endPos, startTime, - duration); - } - var negHalfdV = -0.5 * dV; - var negHalfdY = -0.5 * dY; - var f = Math.PI / duration; - var yEqn = new EquationCoefficients( - 0, 0, startPos.y + dY / 2, - negHalfdV, negHalfdY, f); - var ratio = dX / dV; - var xEqn = new EquationCoefficients( - 0, 0, startPos.x + dX / 2, - negHalfdY * ratio, negHalfdV * ratio, f); - ratio = dZ / dV; - var zEqn = new EquationCoefficients( - 0, 0, startPos.z + dZ / 2, - negHalfdY * ratio, negHalfdV * ratio, f); - return new Curve(startTime, duration, xEqn, yEqn, zEqn); -} - -/** - * This is the abstract base class for an object that describes a throw, catch, - * or empty hand [placeholder] in a site-swap pattern. - * @constructor - */ -Descriptor = function() { -} - -/** - * Create an otherwise-identical copy of this descriptor at a given time offset. - * Note that offset may put time past patternLength; the caller will have to fix - * this up manually. - * @param {number} offset how many beats to offset the new descriptor. - * Derived classes must override this function. - */ -Descriptor.prototype.clone = function(offset) { - throw new Error('Unimplemented.'); -}; - -/** - * Generate the Curve implied by this descriptor and the supplied hand - * positions. - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * Derived classes must override this function. - */ -Descriptor.prototype.generateCurve = function(handPositions) { - throw new Error('Unimplemented.'); -}; - -/** - * Adjust the start time of this Descriptor to be in [0, pathLength). - * @param {!number} pathLength the duration of a path, in beats. - * @return {!Descriptor} this. - */ -Descriptor.prototype.fixUpModPathLength = function(pathLength) { - this.time = this.time % pathLength; - return this; -}; - -/** - * This describes a throw in a site-swap pattern. - * @param {!number} throwNum the site-swap number of the throw. - * @param {!number} throwTime the time this throw occurs. - * @param {!number} sourceHand the index of the throwing hand. - * @param {!number} destHand the index of the catching hand. - * @constructor - */ -ThrowDescriptor = function(throwNum, throwTime, sourceHand, destHand) { - this.throwNum = throwNum; - this.sourceHand = sourceHand; - this.destHand = destHand; - this.time = throwTime; -} - -/** - * This is a subclass of Descriptor. - */ -ThrowDescriptor.prototype = new Descriptor(); - -/** - * Set up the constructor, just to be neat. - */ -ThrowDescriptor.prototype.constructor = ThrowDescriptor; - -/** - * We label each Descriptor subclass with a type for debugging. - */ -ThrowDescriptor.prototype.type = 'THROW'; - -/** - * Create an otherwise-identical copy of this descriptor at a given time offset. - * Note that offset may put time past patternLength; the caller will have to fix - * this up manually. - * @param {number} offset how many beats to offset the new descriptor. - * @return {!Descriptor} the new copy. - */ -ThrowDescriptor.prototype.clone = function(offset) { - offset = offset || 0; // Turn null into 0. - return new ThrowDescriptor(this.throwNum, this.time + offset, - this.sourceHand, this.destHand); -}; - -/** - * Convert the ThrowDescriptor to a string for debugging. - * @return {String} debugging output. - */ -ThrowDescriptor.prototype.toString = function() { - return '(' + this.throwNum + ' from hand ' + this.sourceHand + ' to hand ' + - this.destHand + ')'; -}; - -/** - * Generate the Curve implied by this descriptor and the supplied hand - * positions. - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * @return {!Curve} the curve. - */ -ThrowDescriptor.prototype.generateCurve = function(handPositions) { - var startPos = handPositions[this.sourceHand].throwPositions[this.destHand]; - var endPos = handPositions[this.destHand].catchPosition; - return Curve.computeThrowCurve(startPos, endPos, this.time, - this.throwNum - 1); }; - -/** - * This describes a catch in a site-swap pattern. - * @param {!number} hand the index of the catching hand. - * @param {!number} sourceThrowNum the site-swap number of the preceeding throw. - * @param {!number} destThrowNum the site-swap number of the following throw. - * @param {!number} sourceHand the index of the hand throwing the source throw. - * @param {!number} destHand the index of the hand catching the following throw. - * @param {!number} catchTime the time at which the catch occurs. - * @constructor - */ -CarryDescriptor = function(hand, sourceThrowNum, destThrowNum, sourceHand, - destHand, catchTime) { - this.hand = hand; - this.sourceThrowNum = sourceThrowNum; - this.destThrowNum = destThrowNum; - this.sourceHand = sourceHand; - this.destHand = destHand; - this.time = catchTime; -} - -/** - * This is a subclass of Descriptor. - */ -CarryDescriptor.prototype = new Descriptor(); - -/** - * Set up the constructor, just to be neat. - */ -CarryDescriptor.prototype.constructor = CarryDescriptor; - -/** - * We label each Descriptor subclass with a type for debugging. - */ -CarryDescriptor.prototype.type = 'CARRY'; - -/** - * Since this gets pathLength, not patternLength, we'll have to collapse sets - * of CarryDescriptors later, as they may be spread sparsely through the full - * animation and we'll only want them to be distributed over the full pattern - * length. We may have dupes to throw away as well. - * @param {!ThrowDescriptor} inThrowDescriptor - * @param {!ThrowDescriptor} outThrowDescriptor - * @param {!number} pathLength - * @return {!CarryDescriptor} - */ -CarryDescriptor.fromThrowDescriptors = function(inThrowDescriptor, - outThrowDescriptor, pathLength) { - assert(inThrowDescriptor.destHand == outThrowDescriptor.sourceHand); - assert((inThrowDescriptor.time + inThrowDescriptor.throwNum) % - pathLength == outThrowDescriptor.time); - return new CarryDescriptor(inThrowDescriptor.destHand, - inThrowDescriptor.throwNum, outThrowDescriptor.throwNum, - inThrowDescriptor.sourceHand, outThrowDescriptor.destHand, - (outThrowDescriptor.time + pathLength - 1) % pathLength); -}; - -/** - * Create an otherwise-identical copy of this descriptor at a given time offset. - * Note that offset may put time past patternLength; the caller will have to fix - * this up manually. - * @param {number} offset how many beats to offset the new descriptor. - * @return {!Descriptor} the new copy. - */ -CarryDescriptor.prototype.clone = function(offset) { - offset = offset || 0; // Turn null into 0. - return new CarryDescriptor(this.hand, this.sourceThrowNum, - this.destThrowNum, this.sourceHand, this.destHand, this.time + offset); -}; - -/** - * Convert the CarryDescriptor to a string for debugging. - * @return {String} debugging output. - */ -CarryDescriptor.prototype.toString = function() { - return 'time: ' + this.time + ' (hand ' + this.hand + ' catches ' + - this.sourceThrowNum + ' from hand ' + this.sourceHand + ' then throws ' + - this.destThrowNum + ' to hand ' + this.destHand + ')'; -}; - -/** - * Test if this CarryDescriptor is equivalent to another, mod patternLength. - * @param {!CarryDescriptor} cd the other CarryDescriptor. - * @param {!number} patternLength the length of the pattern. - * @return {!bool} - */ -CarryDescriptor.prototype.equalsWithMod = function(cd, patternLength) { - if (!(cd instanceof CarryDescriptor)) { - return false; - } - if (this.hand != cd.hand) { - return false; - } - if (this.sourceThrowNum != cd.sourceThrowNum) { - return false; - } - if (this.destThrowNum != cd.destThrowNum) { - return false; - } - if (this.sourceHand != cd.sourceHand) { - return false; - } - if (this.destHand != cd.destHand) { - return false; - } - if (this.time % patternLength != cd.time % patternLength) { - return false; - } - return true; -}; - -/** - * Generate the Curve implied by this descriptor and the supplied hand - * positions. - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * @return {!Curve} the curve. - */ -CarryDescriptor.prototype.generateCurve = function(handPositions) { - var startPos = handPositions[this.hand].catchPosition; - var endPos = handPositions[this.hand].throwPositions[this.destHand]; - return Curve.computeCircularCurve(startPos, endPos, this.time, 1); -}; - -/** - * This describes a carry of a "1" in a site-swap pattern. - * The flags isThrow and isCatch tell whether this is the actual 1 [isThrow] or - * the carry that receives the handoff [isCatch]. It's legal for both to be - * true, which happens when there are two 1s in a row. - * @param {!number} sourceThrowNum the site-swap number of the prev throw - * [including this one if isCatch]. - * @param {!number} sourceHand the index of the hand throwing sourceThrowNum. - * @param {!number} destThrowNum the site-swap number of the next throw - * [including this one if isThrow]. - * @param {!number} destHand the index of the hand catching destThrowNum. - * @param {!number} hand the index of the hand doing this carry. - * @param {!number} time the time at which the carry starts. - * @param {!bool} isThrow whether this is a 1. - * @param {!bool} isCatch whether this is the carry after a 1. - * @constructor - */ -CarryOneDescriptor = function(sourceThrowNum, sourceHand, destThrowNum, - destHand, hand, time, isThrow, isCatch) { - // It's possible to have !isCatch with sourceThrowNum == 1 temporarily, if we - // just haven't handled that 1 yet [we're doing the throw of this one, and - // will later get to the previous one, due to wraparound], and vice-versa. - assert(isThrow || (sourceThrowNum == 1)); - assert(isCatch || (destThrowNum == 1)); - this.sourceThrowNum = sourceThrowNum; - this.sourceHand = sourceHand; - this.destHand = destHand; - this.destThrowNum = destThrowNum; - this.hand = hand; - this.time = time; - this.isThrow = isThrow; - this.isCatch = isCatch; - return this; -} - -/** - * This is a subclass of Descriptor. - */ -CarryOneDescriptor.prototype = new Descriptor(); - -/** - * Set up the constructor, just to be neat. - */ -CarryOneDescriptor.prototype.constructor = CarryOneDescriptor; - -/** - * We label each Descriptor subclass with a type for debugging. - */ -CarryOneDescriptor.prototype.type = 'CARRY_ONE'; - -/** - * Create a pair of CarryOneDescriptors to describe the carry that is a throw of - * 1. A 1 spends all its time being carried, so these two carries surrounding - * it represent [and therefore don't have] a throw between them. - * Prev and post are generally the ordinary CarryDescriptors surrounding the - * throw of 1 that we're trying to implement. However, they could each [or - * both] independently be CarryOneDescriptors implementing other 1 throws. - * @param {!Descriptor} prev the carry descriptor previous to the 1. - * @param {!Descriptor} post the carry descriptor subsequent to the 1. - * @return {!Array.CarryOneDescriptor} a pair of CarryOneDescriptors. - */ -CarryOneDescriptor.getDescriptorPair = function(prev, post) { - assert(prev instanceof CarryDescriptor || prev instanceof CarryOneDescriptor); - assert(post instanceof CarryDescriptor || post instanceof CarryOneDescriptor); - assert(prev.destHand == post.hand); - assert(prev.hand == post.sourceHand); - var newPrev; - var newPost; - if (prev instanceof CarryOneDescriptor) { - assert(prev.isCatch && !prev.isThrow); - newPrev = prev; - newPrev.isThrow = true; - assert(newPrev.destHand == post.hand); - } else { - newPrev = new CarryOneDescriptor(prev.sourceThrowNum, prev.sourceHand, 1, - post.hand, prev.hand, prev.time, true, false); - } - if (post instanceof CarryOneDescriptor) { - assert(post.isThrow && !post.isCatch); - newPost = post; - newPost.isCatch = true; - assert(newPost.sourceHand == prev.hand); - assert(newPost.sourceThrowNum == 1); - } else { - newPost = new CarryOneDescriptor(1, prev.hand, post.destThrowNum, - post.destHand, post.hand, post.time, false, true); - } - return [newPrev, newPost]; -}; - -/** - * Convert the CarryOneDescriptor to a string for debugging. - * @return {String} debugging output. - */ -CarryOneDescriptor.prototype.toString = function() { - var s; - if (this.isThrow) { - s = 'Hand ' + this.hand + ' catches a ' + this.sourceThrowNum + ' from ' + - this.sourceHand + ' at time ' + this.time + ' and then passes a 1 to ' + - this.destHand + '.'; - } else { - assert(this.isCatch && this.sourceThrowNum == 1); - s = 'Hand ' + this.hand + ' catches a 1 from ' + this.sourceHand + - ' at time ' + this.time + ' and then passes a ' + this.destThrowNum + - ' to ' + this.destHand + '.'; - } - return s; -}; - -/** - * Compute the curve taken by a ball during the carry representing a 1, as long - * as it's not both a catch and a throw of a 1, which is handled elsewhere. - * It's either a LERP from a circular curve [a catch of a throw > 1] to a - * straight line to the handoff point [for isThrow] or a LERP from a straight - * line from the handoff to a circular curve for the next throw > 1 [for - * isCatch]. - * - * @param {!EquationCoefficients} catchPos - * @param {!EquationCoefficients} throwPos - * @param {!EquationCoefficients} handoffPos - * @param {!number} startTime - * @param {!bool} isCatch whether this is the carry after a 1. - * @param {!bool} isThrow whether this is a 1. - * @return {!Curve} - */ -Curve.computeCarryOneCurve = function(catchPos, throwPos, handoffPos, startTime, - isCatch, isThrow) { - assert(!isCatch != !isThrow); - var curve = Curve.computeCircularCurve(catchPos, throwPos, startTime, 1); - var curve2 = Curve.computeStraightLineCurve(handoffPos, handoffPos, - startTime, 1); - return curve.lerpIn(curve2, isThrow ? 1 : -1); -} - -/** - * Compute the curve taken by a ball during the carry representing a 1 that is - * both the catch of one 1 and the immediately-following throw of another 1. - * - * @param {!EquationCoefficients} leadingHandoffPos - * @param {!EquationCoefficients} trailingHandoffPos - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * @param {!number} hand - * @param {!number} time the time at which the first 1's catch takes place. - * @return {!Curve} - */ -Curve.computeConsecutiveCarryOneCurve = function(leadingHandoffPos, - trailingHandoffPos, handPositions, hand, time) { - var curve = Curve.computeStraightLineCurve(leadingHandoffPos, - handPositions[hand].basePosition, time, 1); - var curve2 = - Curve.computeStraightLineCurve(handPositions[hand].basePosition, - trailingHandoffPos, time, 1); - return curve.lerpIn(curve2, 1); -} - -/** - * Generate the Curve implied by this descriptor and the supplied hand - * positions. - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * @return {!Curve} the curve. - */ -CarryOneDescriptor.prototype.generateCurve = function(handPositions) { - var leadingHandoffPos, trailingHandoffPos; - if (this.isCatch) { - var p0 = handPositions[this.hand].basePosition; - var p1 = handPositions[this.sourceHand].basePosition; - handoffPos = leadingHandoffPos = p0.add(p1).scale(0.5); - } - if (this.isThrow) { - var p0 = handPositions[this.hand].basePosition; - var p1 = handPositions[this.destHand].basePosition; - handoffPos = trailingHandoffPos = p0.add(p1).scale(0.5); - } - if (!this.isCatch || !this.isThrow) { - return Curve.computeCarryOneCurve(handPositions[this.hand].catchPosition, - handPositions[this.hand].throwPositions[this.destHand], handoffPos, - this.time, this.isCatch, this.isThrow); - } else { - return Curve.computeConsecutiveCarryOneCurve(leadingHandoffPos, - trailingHandoffPos, handPositions, this.hand, this.time); - } -}; - -/** - * Create an otherwise-identical copy of this descriptor at a given time offset. - * Note that offset may put time past patternLength; the caller will have to fix - * this up manually. - * @param {number} offset how many beats to offset the new descriptor. - * @return {!Descriptor} the new copy. - */ -CarryOneDescriptor.prototype.clone = function(offset) { - offset = offset || 0; // Turn null into 0. - return new CarryOneDescriptor(this.sourceThrowNum, this.sourceHand, - this.destThrowNum, this.destHand, this.hand, this.time + offset, - this.isThrow, this.isCatch); -}; - -/** - * Test if this CarryOneDescriptor is equivalent to another, mod patternLength. - * @param {!CarryOneDescriptor} cd the other CarryOneDescriptor. - * @param {!number} patternLength the length of the pattern. - * @return {!bool} - */ -CarryOneDescriptor.prototype.equalsWithMod = function(cd, patternLength) { - if (!(cd instanceof CarryOneDescriptor)) { - return false; - } - if (this.hand != cd.hand) { - return false; - } - if (this.sourceThrowNum != cd.sourceThrowNum) { - return false; - } - if (this.destThrowNum != cd.destThrowNum) { - return false; - } - if (this.sourceHand != cd.sourceHand) { - return false; - } - if (this.destHand != cd.destHand) { - return false; - } - if (this.isCatch != cd.isCatch) { - return false; - } - if (this.isThrow != cd.isThrow) { - return false; - } - if (this.time % patternLength != cd.time % patternLength) { - return false; - } - return true; -}; - -/** - * This describes an empty hand in a site-swap pattern. - * @param {!Descriptor} cd0 the CarryDescriptor or CarryOneDescriptor describing - * this hand immediately before it was emptied. - * @param {!Descriptor} cd1 the CarryDescriptor or CarryOneDescriptor describing - * this hand immediately after it's done being empty. - * @param {!number} patternLength the length of the pattern. - * @constructor - */ -EmptyHandDescriptor = function(cd0, cd1, patternLength) { - assert(cd0.hand == cd1.hand); - this.hand = cd0.hand; - this.prevThrowDest = cd0.destHand; - this.sourceThrowNum = cd0.destThrowNum; - this.nextCatchSource = cd1.sourceHand; - this.destThrowNum = cd1.sourceThrowNum; - // This code assumes that each CarryDescriptor and CarryOneDescriptor always - // has a duration of 1 beat. If we want to be able to allow long-held balls - // [instead of thrown twos, for example], we'll have to fix that here and a - // number of other places. - this.time = (cd0.time + 1) % patternLength; - this.duration = cd1.time - this.time; - if (this.duration < 0) { - this.duration += patternLength; - assert(this.duration > 0); - } -} - -/** - * This is a subclass of Descriptor. - */ -EmptyHandDescriptor.prototype = new Descriptor(); - -/** - * Set up the constructor, just to be neat. - */ -EmptyHandDescriptor.prototype.constructor = EmptyHandDescriptor; - -/** - * We label each Descriptor subclass with a type for debugging. - */ -EmptyHandDescriptor.prototype.type = 'EMPTY'; - -/** - * Convert the EmptyHandDescriptor to a string for debugging. - * @return {String} debugging output. - */ -EmptyHandDescriptor.prototype.toString = function() { - return 'time: ' + this.time + ' for ' + this.duration + ' (hand ' + - this.hand + ', after throwing a ' + this.sourceThrowNum + ' to hand ' + - this.prevThrowDest + ' then catches a ' + this.destThrowNum + - ' from hand ' + this.nextCatchSource + ')'; -}; - -/** - * Generate the Curve implied by this descriptor and the supplied hand - * positions. - * @param {!Array.HandPositionRecord} handPositions where the hands will be. - * @return {!Curve} the curve. - */ -EmptyHandDescriptor.prototype.generateCurve = function(handPositions) { - var startPos, endPos; - if (this.sourceThrowNum == 1) { - var p0 = handPositions[this.hand].basePosition; - var p1 = handPositions[this.prevThrowDest].basePosition; - startPos = p0.add(p1).scale(0.5); - } else { - startPos = handPositions[this.hand].throwPositions[this.prevThrowDest]; - } - if (this.destThrowNum == 1) { - var p0 = handPositions[this.hand].basePosition; - var p1 = handPositions[this.nextCatchSource].basePosition; - endPos = p0.add(p1).scale(0.5); - } else { - endPos = handPositions[this.hand].catchPosition; - } - // TODO: Replace with a good empty-hand curve. - return Curve.computeStraightLineCurve(startPos, endPos, this.time, - this.duration); -}; - -/** - * A series of descriptors that describes the full path of an object during a - * pattern. - * @param {!Array.Descriptor} descriptors all descriptors for the object. - * @param {!number} pathLength the length of the path in beats. - * @constructor - */ -Path = function(descriptors, pathLength) { - this.descriptors = descriptors; - this.pathLength = pathLength; -} - -/** - * Create a Path representing a ball, filling in the gaps between the throws - * with carry descriptors. Since it's a ball's path, there are no - * EmptyHandDescriptors in the output. - * @param {!Array.ThrowDescriptor} throwDescriptors the ball's part of the - * pattern. - * @param {!number} pathLength the length of the pattern in beats. - * @return {!Path} the ball's full path. - */ -Path.ballPathFromThrowDescriptors = function(throwDescriptors, pathLength) { - return new Path( - Path.createDescriptorList(throwDescriptors, pathLength), pathLength); -}; - -/** - * Create the sequence of ThrowDescriptors, CarryDescriptors, and - * CarryOneDescriptor describing the path of a ball through a pattern. - * A sequence such as (h j k) generally maps to an alternating series of throw - * and carry descriptors [Th Chj Tj Cjk Tk Ck? ...]. However, when j is a 1, - * you remove the throw descriptor and modify the previous and subsequent carry - * descriptors, since the throw descriptor has zero duration and the carry - * descriptors need to take into account the handoff. - * @param {!Array.ThrowDescriptor} throwDescriptors the ball's part of the - * pattern. - * @param {!number} pathLength the length of the pattern in beats. - * @return {!Array.Descriptor} the full set of descriptors for the ball. - */ -Path.createDescriptorList = function(throwDescriptors, pathLength) { - var descriptors = []; - var prevThrow; - for (var index in throwDescriptors) { - var td = throwDescriptors[index]; - if (prevThrow) { - descriptors.push( - CarryDescriptor.fromThrowDescriptors(prevThrow, td, pathLength)); - } // Else it's handled after the loop. - descriptors.push(td); - prevThrow = td; - } - descriptors.push( - CarryDescriptor.fromThrowDescriptors(prevThrow, throwDescriptors[0], - pathLength)); - // Now post-process to take care of throws of 1. It's easier to do it here - // than during construction since we can now assume that the previous and - // subsequent carry descriptors are already in place [modulo pathLength]. - for (var i = 0; i < descriptors.length; ++i) { - var descriptor = descriptors[i]; - if (descriptor instanceof ThrowDescriptor) { - if (descriptor.throwNum == 1) { - var prevIndex = (i + descriptors.length - 1) % descriptors.length; - var postIndex = (i + 1) % descriptors.length; - var replacements = CarryOneDescriptor.getDescriptorPair( - descriptors[prevIndex], descriptors[postIndex]); - descriptors[prevIndex] = replacements[0]; - descriptors[postIndex] = replacements[1]; - descriptors.splice(i, 1); - // We've removed a descriptor from the array, but since we can never - // have 2 ThrowDescriptors in a row, we don't need to decrement i. - } - } - } - return descriptors; -}; - -/** - * Convert the Path to a string for debugging. - * @return {String} debugging output. - */ -Path.prototype.toString = function() { - var ret = 'pathLength is ' + this.pathLength + '; ['; - for (var index in this.descriptors) { - ret += this.descriptors[index].toString(); - } - ret += ']'; - return ret; -}; - -/** - * Create an otherwise-identical copy of this path at a given time offset. - * Note that offset may put time references in the Path past the length of the - * pattern. The caller must fix this up manually. - * @param {number} offset how many beats to offset the new Path. - * @return {!Path} the new copy. - */ -Path.prototype.clone = function(offset) { - offset = offset || 0; // Turn null into 0. - var descriptors = []; - for (var index in this.descriptors) { - descriptors.push(this.descriptors[index].clone(offset)); - } - return new Path(descriptors, this.pathLength); -}; - -/** - * Adjust the start time of all descriptors to be in [0, pathLength) via modular - * arithmetic. Reorder the array such that they're sorted in increasing order - * of time. - * @return {!Path} this. - */ -Path.prototype.fixUpModPathLength = function() { - var splitIndex; - var prevTime = 0; - for (var index in this.descriptors) { - var d = this.descriptors[index]; - d.fixUpModPathLength(this.pathLength); - if (d.time < prevTime) { - assert(null == splitIndex); - splitIndex = index; // From here to the end should move to the start. - } - prevTime = d.time; - } - if (null != splitIndex) { - var temp = this.descriptors.slice(splitIndex); - this.descriptors.length = splitIndex; - this.descriptors = temp.concat(this.descriptors); - } - return this; -}; - -/** - * Take a standard asynch siteswap pattern [expressed as an array of ints] and - * a number of hands, and expand it into a 2D grid of ThrowDescriptors with one - * row per hand. - * Non-asynch patterns are more complicated, since their linear forms aren't - * fully-specified, so we don't handle them here. - * You'll want to expand your pattern to the LCM of numHands and minimal pattern - * length before calling this. - * The basic approach doesn't really work for one-handed patterns. It ends up - * with catches and throws happening at the same time [having removed all - * empty-hand time in between them]. To fix this, we double all throw heights - * and space them out, as if doing a two-handed pattern with all zeroes from the - * other hand. Yes, this points out that the overall approach we're taking is a - * bit odd [since you end up with hands empty for time proportional to the - * number of hands], but you have to make some sort of assumptions to generalize - * siteswaps to N hands, and that's what I chose. - * @param {!Array.number} pattern an asynch siteswap pattern. - * @param {!number} numHands the number of hands. - * @return {!Array.Array.ThrowDescriptor} the expanded pattern. - */ -function expandPattern(pattern, numHands) { - var fullPattern = []; - assert(numHands > 0); - if (numHands == 1) { - numHands = 2; - var temp = []; - for (var i = 0; i < pattern.length; ++i) { - temp[2 * i] = 2 * pattern[i]; - temp[2 * i + 1] = 0; - } - pattern = temp; - } - for (var hand = 0; hand < numHands; ++hand) { - fullPattern[hand] = []; - } - for (var time = 0; time < pattern.length; ++time) { - for (var hand = 0; hand < numHands; ++hand) { - var t; - if (hand == time % numHands) { - t = new ThrowDescriptor(pattern[time], time, hand, - (hand + pattern[time]) % numHands); - } else { - // These are ignored during analysis, so they don't appear in BallPaths. - t = new ThrowDescriptor(0, time, hand, hand); - } - fullPattern[hand].push(t); - } - } - return fullPattern; -} - -// TODO: Wrap the final pattern in a class, then make the remaining few global -// functions be members of that class to clean up the global namespace. - -/** - * Given a valid site-swap for a nonzero number of balls, stored as an expanded - * pattern array-of-arrays, with pattern length the LCM of hands and minimal - * pattern length, produce Paths for all the balls. - * @param {!Array.Array.ThrowDescriptor} pattern a valid pattern. - * @return {!Array.Path} the paths of all the balls. - */ -function generateBallPaths(pattern) { - var numHands = pattern.length; - assert(numHands > 0); - var patternLength = pattern[0].length; - assert(patternLength > 0); - var sum = 0; - for (var hand in pattern) { - for (var time in pattern[hand]) { - sum += pattern[hand][time].throwNum; - } - } - var numBalls = sum / patternLength; - assert(numBalls == Math.round(numBalls)); - assert(numBalls > 0); - - var ballsToAllocate = numBalls; - var ballPaths = []; - // NOTE: The indices of locationsChecked are reversed from those of pattern - // for simplicity of allocation. This might be worth flipping to match. - var locationsChecked = []; - for (var time = 0; time < patternLength && ballsToAllocate; ++time) { - locationsChecked[time] = locationsChecked[time] || []; - for (var hand = 0; hand < numHands && ballsToAllocate; ++hand) { - if (locationsChecked[time][hand]) { - continue; - } - var curThrowDesc = pattern[hand][time]; - var curThrow = curThrowDesc.throwNum; - if (!curThrow) { - assert(curThrow === 0); - continue; - } - var throwDescriptors = []; - var curTime = time; - var curHand = hand; - var wraps = 0; - do { - if (!locationsChecked[curTime]) { - locationsChecked[curTime] = []; - } - assert(!locationsChecked[curTime][curHand]); - locationsChecked[curTime][curHand] = true; - // We copy curThrowDesc here, adding wraps * patternLength, to get - // the true throw time relative to offset. Later we'll add in offset - // when we clone again, then mod by pathLength. - throwDescriptors.push(curThrowDesc.clone(wraps * patternLength)); - var nextThrowTime = curThrow + curTime; - wraps += Math.floor(nextThrowTime / patternLength); - curTime = nextThrowTime % patternLength; - assert(curTime >= time); // Else we'd have covered it earlier. - curHand = curThrowDesc.destHand; - var tempThrowDesc = curThrowDesc; - curThrowDesc = pattern[curHand][curTime]; - curThrow = curThrowDesc.throwNum; - assert(tempThrowDesc.destHand == curThrowDesc.sourceHand); - assert(curThrowDesc.time == - (tempThrowDesc.throwNum + tempThrowDesc.time) % patternLength); - } while (curTime != time || curHand != hand); - var pathLength = wraps * patternLength; - var ballPath = - Path.ballPathFromThrowDescriptors(throwDescriptors, pathLength); - for (var i = 0; i < wraps; ++i) { - var offset = i * patternLength % pathLength; - ballPaths.push(ballPath.clone(offset, pathLength).fixUpModPathLength()); - } - ballsToAllocate -= wraps; - assert(ballsToAllocate >= 0); - } - } - return ballPaths; -} - -/** - * Given an array of ball paths, produce the corresponding set of hand paths. - * @param {!Array.Path} ballPaths the Paths of all the balls in the pattern. - * @param {!number} numHands how many hands to use in the pattern. - * @param {!number} patternLength the length, in beats, of the pattern. - * @return {!Array.Path} the paths of all the hands. - */ -function generateHandPaths(ballPaths, numHands, patternLength) { - assert(numHands > 0); - assert(patternLength > 0); - var handRecords = []; // One record per hand. - for (var idxBR in ballPaths) { - var descriptors = ballPaths[idxBR].descriptors; - for (var idxD in descriptors) { - var descriptor = descriptors[idxD]; - // TODO: Fix likely needed for throws of 1. - if (!(descriptor instanceof ThrowDescriptor)) { - // It's a CarryDescriptor or a CarryOneDescriptor. - var hand = descriptor.hand; - if (!handRecords[hand]) { - handRecords[hand] = []; - } - // TODO: Should we not shorten stuff here if we're going to lengthen - // everything later anyway? Is there a risk of inconsistency due to - // ball paths of different lengths? - var catchTime = descriptor.time % patternLength; - if (!handRecords[hand][catchTime]) { - // We pass in this offset to set the new descriptor's time to - // catchTime, so as to keep it within [0, patternLength). - handRecords[hand][catchTime] = - descriptor.clone(catchTime - descriptor.time); - } else { - assert( - handRecords[hand][catchTime].equalsWithMod( - descriptor, patternLength)); - } - } - } - } - var handPaths = []; - for (var hand in handRecords) { - var outDescriptors = []; - var inDescriptors = handRecords[hand]; - var prevDescriptor = null; - var descriptor; - for (var idxD in inDescriptors) { - descriptor = inDescriptors[idxD]; - assert(descriptor); // Enumeration should skip array holes. - assert(descriptor.hand == hand); - if (prevDescriptor) { - outDescriptors.push(new EmptyHandDescriptor(prevDescriptor, descriptor, - patternLength)); - } - outDescriptors.push(descriptor.clone()); - prevDescriptor = descriptor; - } - // Note that this EmptyHandDescriptor that wraps around the end lives at the - // end of the array, not the beginning, despite the fact that it may be the - // active one at time zero. This is the same behavior as with Paths for - // balls. - descriptor = new EmptyHandDescriptor(prevDescriptor, outDescriptors[0], - patternLength); - if (descriptor.time < outDescriptors[0].time) { - assert(descriptor.time + descriptor.duration == outDescriptors[0].time); - outDescriptors.unshift(descriptor); - } else { - assert(descriptor.time == - outDescriptors[outDescriptors.length - 1].time + 1); - outDescriptors.push(descriptor); - } - handPaths[hand] = - new Path(outDescriptors, patternLength).fixUpModPathLength(); - } - return handPaths; -} - -// NOTE: All this Vector stuff does lots of object allocations. If that's a -// problem for your browser [e.g. IE6], you'd better stick with the embedded V8. -// This code predates the creation of o3djs/math.js; I should probably switch it -// over at some point, but for now it's not worth the trouble. - -/** - * A simple 3-dimensional vector. - * @constructor - */ -Vector = function(x, y, z) { - this.x = x; - this.y = y; - this.z = z; -} - -Vector.prototype.sub = function(v) { - return new Vector(this.x - v.x, this.y - v.y, this.z - v.z); -}; - -Vector.prototype.add = function(v) { - return new Vector(this.x + v.x, this.y + v.y, this.z + v.z); -}; - -Vector.prototype.dot = function(v) { - return this.x * v.x + this.y * v.y + this.z * v.z; -}; - -Vector.prototype.length = function() { - return Math.sqrt(this.dot(this)); -}; - -Vector.prototype.scale = function(s) { - return new Vector(this.x * s, this.y * s, this.z * s); -}; - -Vector.prototype.set = function(v) { - this.x = v.x; - this.y = v.y; - this.z = v.z; -}; - -Vector.prototype.normalize = function() { - var length = this.length(); - assert(length); - this.set(this.scale(1 / length)); - return this; -}; - -/** - * Convert the Vector to a string for debugging. - * @return {String} debugging output. - */ -Vector.prototype.toString = function() { - return '{' + this.x.toFixed(3) + ', ' + this.y.toFixed(3) + ', ' + - this.z.toFixed(3) + '}'; -}; - -/** - * A container class that holds the positions relevant to a hand: where it is - * when it's not doing anything, where it likes to catch balls, and where it - * likes to throw balls to each of the other hands. - * @param {!Vector} basePosition the centroid of throw and catch positions when - * the hand throws to itself. - * @param {!Vector} catchPosition where the hand likes to catch balls. - * @constructor - */ -HandPositionRecord = function(basePosition, catchPosition) { - this.basePosition = basePosition; - this.catchPosition = catchPosition; - this.throwPositions = []; -} - -/** - * Convert the HandPositionRecord to a string for debugging. - * @return {String} debugging output. - */ -HandPositionRecord.prototype.toString = function() { - var s = 'base: ' + this.basePosition.toString() + ';\n'; - s += 'catch: ' + this.catchPosition.toString() + ';\n'; - s += 'throws:\n'; - for (var i = 0; i < this.throwPositions.length; ++i) { - s += '[' + i + '] ' + this.throwPositions[i].toString() + '\n'; - } - return s; -}; - -/** - * Compute all the hand positions used in a pattern given a number of hands and - * a grouping style ["even" for evenly-spaced hands, "pairs" to group them in - * pairs, as with 2-handed jugglers]. - * @param {!number} numHands the number of hands to use. - * @param {!String} style the grouping style. - * @return {!Array.HandPositionRecord} a full set of hand positions. - */ -function computeHandPositions(numHands, style) { - assert(numHands > 0); - var majorRadiusScale = 0.75; - var majorRadius = majorRadiusScale * (numHands - 1); - var throwCatchOffset = 0.45; - var catchRadius = majorRadius + throwCatchOffset; - var handPositionRecords = []; - for (var hand = 0; hand < numHands; ++hand) { - var circleFraction; - if (style == 'even') { - circleFraction = hand / numHands; - } else { - assert(style == 'pairs'); - circleFraction = (hand + Math.floor(hand / 2)) / (1.5 * numHands); - } - var cos = Math.cos(Math.PI * 2 * circleFraction); - var sin = Math.sin(Math.PI * 2 * circleFraction); - var cX = catchRadius * cos; - var cY = 0; - var cZ = catchRadius * sin; - var bX = majorRadius * cos; - var bY = 0; - var bZ = majorRadius * sin; - handPositionRecords[hand] = new HandPositionRecord( - new Vector(bX, bY, bZ), new Vector(cX, cY, cZ)); - } - // Now that we've got all the hands' base and catch positions, we need to - // compute the appropriate throw positions for each hand pair. - for (var source = 0; source < numHands; ++source) { - var throwHand = handPositionRecords[source]; - for (var target = 0; target < numHands; ++target) { - var catchHand = handPositionRecords[target]; - if (throwHand == catchHand) { - var baseV = throwHand.basePosition; - throwHand.throwPositions[target] = - baseV.add(baseV.sub(throwHand.catchPosition)); - } else { - var directionV = - catchHand.catchPosition.sub(throwHand.basePosition).normalize(); - var offsetV = directionV.scale(throwCatchOffset); - throwHand.throwPositions[target] = - throwHand.basePosition.add(offsetV); - } - } - } - return handPositionRecords; -} - -/** - * Convert an array of HandPositionRecord to a string for debugging. - * @param {!Array.HandPositionRecord} positions the positions to display. - * @return {String} debugging output. - */ -function getStringFromHandPositions(positions) { - var s = ''; - for (index in positions) { - s += positions[index].toString(); - } - return s; -} - -/** - * The set of curves an object passes through throughout a full animation cycle. - * @param {!number} duration the length of the animation in beats. - * @param {!Array.Curve} curves the full set of Curves. - * @constructor - */ -CurveSet = function(duration, curves) { - this.duration = duration; - this.curves = curves; -} - -/** - * Looks up what curve is active at a particular time. This is slower than - * getCurveForTime, but can be used even if no Curve starts precisely at - * unsafeTime % this.duration. - * @param {!number} unsafeTime the time at which to check. - * @return {!Curve} the curve active at unsafeTime. - */ -CurveSet.prototype.getCurveForUnsafeTime = function(unsafeTime) { - unsafeTime %= this.duration; - time = Math.floor(unsafeTime); - if (this.curves[time]) { - return this.curves[time]; - } - var curve; - for (var i = time; i >= 0; --i) { - curve = this.curves[i]; - if (curve) { - assert(i + curve.duration >= unsafeTime); - return curve; - } - } - // We must want the last one. There's always a last one, given how we - // construct the CurveSets; they're sparse, but the length gets set by adding - // elements at the end. - curve = this.curves[this.curves.length - 1]; - unsafeTime += this.duration; - assert(curve.startTime <= unsafeTime); - assert(curve.startTime + curve.duration > unsafeTime); - return curve; -}; - -/** - * Looks up what curve is active at a particular time. This is faster than - * getCurveForUnsafeTime, but can only be used if if a Curve starts precisely at - * unsafeTime % this.duration. - * @param {!number} time the time at which to check. - * @return {!Curve} the curve starting at time. - */ -CurveSet.prototype.getCurveForTime = function(time) { - return this.curves[time % this.duration]; -}; - -/** - * Convert the CurveSet to a string for debugging. - * @return {String} debugging output. - */ -CurveSet.prototype.toString = function() { - var s = 'Duration: ' + this.duration + '\n'; - for (var c in this.curves) { - s += this.curves[c].toString(); - } - return s; -}; - -/** - * Namespace object to hold the pure math functions. - * TODO: Consider just rolling these into the Pattern object, when it gets - * created. - */ -var JugglingMath = {}; - -/** - * Computes the greatest common devisor of integers a and b. - * @param {!number} a an integer. - * @param {!number} b an integer. - * @return {!number} the GCD of a and b. - */ -JugglingMath.computeGCD = function(a, b) { - assert(Math.round(a) == a); - assert(Math.round(b) == b); - assert(a >= 0); - assert(b >= 0); - if (!b) { - return a; - } else { - return JugglingMath.computeGCD(b, a % b); - } -} - -/** - * Computes the least common multiple of integers a and b, by making use of the - * fact that LCM(a, b) * GCD(a, b) == a * b. - * @param {!number} a an integer. - * @param {!number} b an integer. - * @return {!number} the LCM of a and b. - */ -JugglingMath.computeLCM = function(a, b) { - assert(Math.round(a) == a); - assert(Math.round(b) == b); - assert(a >= 0); - assert(b >= 0); - var ret = a * b / JugglingMath.computeGCD(a, b); - assert(Math.round(ret) == ret); - return ret; -} - -/** - * Given a Path and a set of hand positions, compute the corresponding set of - * Curves. - * @param {!Path} path the path of an object. - * @param {!Array.HandPositionRecord} handPositions the positions of the hands - * juggling the pattern containing the path. - * @return {!CurveSet} the full set of curves. - */ -CurveSet.getCurveSetFromPath = function(path, handPositions) { - var curves = []; - var pathLength = path.pathLength; - for (var index in path.descriptors) { - var descriptor = path.descriptors[index]; - var curve = descriptor.generateCurve(handPositions); - assert(!curves[curve.startTime]); - assert(curve.startTime < pathLength); - curves[curve.startTime] = curve; - } - return new CurveSet(pathLength, curves); -} - -/** - * Given a set of Paths and a set of hand positions, compute the corresponding - * CurveSets. - * @param {!Array.Path} paths the paths of a number of objects. - * @param {!Array.HandPositionRecord} handPositions the positions of the hands - * juggling the pattern containing the paths. - * @return {!Array.CurveSet} the CurveSets. - */ -CurveSet.getCurveSetsFromPaths = function(paths, handPositions) { - var curveSets = []; - for (var index in paths) { - var path = paths[index]; - curveSets[index] = CurveSet.getCurveSetFromPath(path, handPositions); - } - return curveSets; -} - -/** - * This is a temporary top-level calculation function that converts a standard - * asynchronous siteswap, expressed as a string of digits, into a full - * ready-to-animate set of CurveSets. Later on we'll be using an interface that - * can create a richer set of patterns than those expressable in the traditional - * string-of-ints format. - * @param {!String} patternString the siteswap. - * @param {!number} numHands the number of hands to use for the pattern. - * @param {!String} style how to space the hands ["pairs" or "even"]. - * @return {!Object} a fully-analyzed pattern as CurveSets and associated data. - */ -function computeFullPatternFromString(patternString, numHands, style) { - var patternAsStrings = patternString.split(/[ ,]+ */); - var patternSegment = []; - for (var index in patternAsStrings) { - if (patternAsStrings[index]) { // Beware extra whitespace at the ends. - patternSegment.push(parseInt(patternAsStrings[index])); - } - } - var pattern = []; - // Now expand the pattern out to the length of the LCM of pattern length and - // number of hands, so that each throw gets done in each of its incarnations. - var multiple = JugglingMath.computeLCM(patternSegment.length, numHands) / - patternSegment.length; - for (var i = 0; i < multiple; ++i) { - pattern = pattern.concat(patternSegment); - } - - var fullPattern = expandPattern(pattern, numHands); - var patternLength = fullPattern[0].length; - - var ballPaths = generateBallPaths(fullPattern); - var handPaths = generateHandPaths(ballPaths, numHands, patternLength); - - var handPositions = computeHandPositions(numHands, style); - var ballCurveSets = CurveSet.getCurveSetsFromPaths(ballPaths, handPositions); - var handCurveSets = CurveSet.getCurveSetsFromPaths(handPaths, handPositions); - - // Find the LCM of all the curveSet durations. This will be the length of the - // fully-expanded queue. We could expand to this before computing the - // CurveSets, but this way's probably just a little cheaper. - var lcmDuration = 1; - for (var i in ballCurveSets) { - var duration = ballCurveSets[i].duration; - if (duration > lcmDuration || lcmDuration % duration) { - lcmDuration = JugglingMath.computeLCM(lcmDuration, duration); - } - } - for (var i in handCurveSets) { - var duration = handCurveSets[i].duration; - if (duration > lcmDuration || lcmDuration % duration) { - lcmDuration = JugglingMath.computeLCM(lcmDuration, duration); - } - } - return { - numBalls: ballPaths.length, - numHands: handPaths.length, - duration: lcmDuration, - handCurveSets: handCurveSets, - ballCurveSets: ballCurveSets - } -} +// @@REWRITE(insert js-copyright) +// @@REWRITE(delete-start) +// Copyright 2009 Google Inc. All Rights Reserved +// @@REWRITE(delete-end) + +/** + * @fileoverview This file contains all the math for the siteswap animator. It + * handles all of the site-swap-related stuff [converting a sequence of integers + * into a more-useful representation of a pattern, pattern validation, etc.] as + * well as all the physics used for the simulation. + */ + +/** + * This is a container class that holds the coefficients of an equation + * describing the motion of an object. + * The basic equation is: + * f(x) := a t^2 + b t + c + d sin (f t) + e cos (f t). + * However, sometimes we LERP between that function and this one: + * g(x) := lA t^2 + lB t + lC + * lerpRate [so far] is always either 1 [LERP from f to g over 1 beat] or -1, + * [LERP from g to f over one beat]. + * + * Just plug in t to evaluate the equation. There's no JavaScript function to + * do this because it's always done on the GPU. + * + * @constructor + */ +EquationCoefficients = function(a, b, c, d, e, f, lA, lB, lC, lerpRate) { + assert(!isNaN(a) && !isNaN(b) && !isNaN(c)); + d = d || 0; + e = e || 0; + f = f || 0; + assert(!isNaN(d) && !isNaN(e) && !isNaN(f)); + lA = lA || 0; + lB = lB || 0; + lC = lC || 0; + assert(!isNaN(lA) && !isNaN(lB) && !isNaN(lC)); + lerpRate = lerpRate || 0; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + this.lA = lA; + this.lB = lB; + this.lC = lC; + this.lerpRate = lerpRate; +} + +/** + * Create a new equation that's equivalent to this equation's coefficients a-f + * with a LERP to the polynomial portion of the supplied equation. + * @param {!EquationCoefficients} eqn the source of coefficients. + * @param {!number} lerpRate the rate and direction of the LERP; positive for + * "from this equation to the new one" and vice-versa. + * @return {!EquationCoefficients} a new set of coefficients. + */ +EquationCoefficients.prototype.lerpIn = function(eqn, lerpRate) { + assert(!this.lerpRate); + return new EquationCoefficients(this.a, this.b, this.c, this.d, this.e, + this.f, eqn.a, eqn.b, eqn.c, lerpRate); +}; + +/** + * Convert the EquationCoefficients to a string for debugging. + * @return {String} debugging output. + */ +EquationCoefficients.prototype.toString = function() { + return 'F(t) := ' + this.a.toFixed(2) + ' t^2 + ' + this.b.toFixed(2) + + ' t + ' + this.c.toFixed(2) + ' + ' + + this.d.toFixed(2) + ' sin(' + this.f.toFixed(2) + ' t) + ' + + this.e.toFixed(2) + ' cos(' + this.f.toFixed(2) + ' t) + LERP(' + + this.lerpRate.toFixed(2) + ') of ' + + this.lA.toFixed(2) + ' t^2 + ' + this.lB.toFixed(2) + + ' t + ' + this.lC.toFixed(2); +}; + +/** + * A set of equations which describe the motion of an object over time. + * The three equations each supply one dimension of the motion, and the curve is + * valid from startTime to startTime + duration. + * @param {!number} startTime the initial time at which the curve is valid. + * @param {!number} duration how long [in beats] the curve is valid. + * @param {!EquationCoefficients} xEqn the equation for motion in x. + * @param {!EquationCoefficients} yEqn the equation for motion in y. + * @param {!EquationCoefficients} zEqn the equation for motion in z. + * @constructor + */ +Curve = function(startTime, duration, xEqn, yEqn, zEqn) { + this.startTime = startTime; + this.duration = duration; + this.xEqn = xEqn; + this.yEqn = yEqn; + this.zEqn = zEqn; +} + +/** + * Convert the Curve to a string for debugging. + * @return {String} debugging output. + */ +Curve.prototype.toString = function() { + var s = 'startTime: ' + this.startTime + '\n'; + s += 'duration: ' + this.duration + '\n'; + s += this.xEqn + '\n'; + s += this.yEqn + '\n'; + s += this.zEqn + '\n'; + return s; +}; + +/** + * Modify this curve's coefficients to include a LERP to the polynomial + * portion of the supplied curve. + * @param {!Curve} curve the source of coefficients. + * @param {!number} lerpRate the rate and direction of the LERP; positive for + * "from this equation to the new one" and vice-versa. + * @return {!Curve} a new curve. + */ +Curve.prototype.lerpIn = function(curve, lerpRate) { + assert(this.startTime == curve.startTime); + assert(this.duration == curve.duration); + var xEqn = this.xEqn.lerpIn(curve.xEqn, lerpRate); + var yEqn = this.yEqn.lerpIn(curve.yEqn, lerpRate); + var zEqn = this.zEqn.lerpIn(curve.zEqn, lerpRate); + return new Curve(this.startTime, this.duration, xEqn, yEqn, zEqn); +}; + +/** + * Produce a set of polynomial coefficients that describe linear motion between + * two points in 1 dimension. + * @param {!number} startPos the starting position. + * @param {!number} endPos the ending position. + * @param {!number} duration how long the motion takes. + * @return {!EquationCoefficients} the equation for the motion. + */ +Curve.computeLinearCoefficients = function(startPos, endPos, duration) { + return new EquationCoefficients( + 0, (endPos - startPos) / duration, startPos); +} + +var GRAVITY = 1; // Higher means higher throws for the same duration. +/** + * Produce a set of polynomial coefficients that describe parabolic motion + * between two points in 1 dimension. + * @param {!number} startPos the starting position. + * @param {!number} endPos the ending position. + * @param {!number} duration how long the motion takes. + * @return {!EquationCoefficients} the equation for the motion. + */ +Curve.computeParabolicCoefficients = function(startPos, endPos, duration) { + var dY = endPos - startPos; + return new EquationCoefficients(-GRAVITY / 2, + dY / duration + GRAVITY * duration / 2, + startPos); +} + +/** + * Compute the curve taken by a ball given its throw and catch positions, the + * time it was thrown, and how long it stayed in the air. + * + * We use duration rather than throwTime and catchTime because, what + * with the modular arithmetic used in our records, catchTime might be before + * throwTime, and in some representations the pattern could wrap around a few + * times while the ball's in the air. When the parabola computed here is used, + * time must be supplied as an offset from the time of the throw, and must of + * course not wrap at all. That is, these coefficients work for f(0) == + * throwPos, f(duration) == catchPos. + * + * We treat the y axis as vertical and thus affected by gravity. + * + * @param {!EquationCoefficients} throwPos + * @param {!EquationCoefficients} catchPos + * @param {!number} startTime + * @param {!number} duration + * @return {!Curve} + */ +Curve.computeThrowCurve = function(throwPos, catchPos, startTime, duration) { + var xEqn = Curve.computeLinearCoefficients(throwPos.x, catchPos.x, duration); + var yEqn = Curve.computeParabolicCoefficients(throwPos.y, catchPos.y, + duration); + var zEqn = Curve.computeLinearCoefficients(throwPos.z, catchPos.z, duration); + return new Curve(startTime, duration, xEqn, yEqn, zEqn); +} + +/** + * Compute a straight line Curve given start and end positions, the start time, + * and the duration of the motion. + * + * @param {!EquationCoefficients} startPos + * @param {!EquationCoefficients} endPos + * @param {!number} startTime + * @param {!number} duration + * @return {!Curve} + */ +Curve.computeStraightLineCurve = + function(startPos, endPos, startTime, duration) { + var xEqn = Curve.computeLinearCoefficients(startPos.x, endPos.x, duration); + var yEqn = Curve.computeLinearCoefficients(startPos.y, endPos.y, duration); + var zEqn = Curve.computeLinearCoefficients(startPos.z, endPos.z, duration); + return new Curve(startTime, duration, xEqn, yEqn, zEqn); +} + +/** + * Threshold horizontal distance below which computeCircularCurve won't bother + * trying to approximate a circular curve. See the comment above + * computeCircularCurve for more info. + * @type {number} + */ +Curve.EPSILON = .0001; + +/** + * Compute a circular curve, used as an approximation for the motion of a hand + * between a catch and its following throw. + * + * Assumes a lot of stuff about this looking like a "normal" throw: the catch is + * moving roughly the opposite direction as the throw, the throw and catch + * aren't at the same place, and such. Otherwise this looks very odd at best. + * This is used for the height of the curve. + * This produces coefficients for d sin(f t) + e cos(f t) for each of x, y, z. + * It produces a vertical-ish circular curve from the start to the end, going + * down, then up. So if dV [the distance from the start to finish in the x-z + * plane, ignoring y] is less than Curve.EPSILON, it doesn't know which way down + * is, and it bails by returning a straight line instead. + */ +Curve.computeCircularCurve = function(startPos, endPos, startTime, duration) { + var dX = endPos.x - startPos.x; + var dY = endPos.y - startPos.y; + var dZ = endPos.z - startPos.z; + var dV = Math.sqrt(dX * dX + dZ * dZ); + if (dV < Curve.EPSILON) { + return Curve.computeStraightLineCurve(startPos, endPos, startTime, + duration); + } + var negHalfdV = -0.5 * dV; + var negHalfdY = -0.5 * dY; + var f = Math.PI / duration; + var yEqn = new EquationCoefficients( + 0, 0, startPos.y + dY / 2, + negHalfdV, negHalfdY, f); + var ratio = dX / dV; + var xEqn = new EquationCoefficients( + 0, 0, startPos.x + dX / 2, + negHalfdY * ratio, negHalfdV * ratio, f); + ratio = dZ / dV; + var zEqn = new EquationCoefficients( + 0, 0, startPos.z + dZ / 2, + negHalfdY * ratio, negHalfdV * ratio, f); + return new Curve(startTime, duration, xEqn, yEqn, zEqn); +} + +/** + * This is the abstract base class for an object that describes a throw, catch, + * or empty hand [placeholder] in a site-swap pattern. + * @constructor + */ +Descriptor = function() { +} + +/** + * Create an otherwise-identical copy of this descriptor at a given time offset. + * Note that offset may put time past patternLength; the caller will have to fix + * this up manually. + * @param {number} offset how many beats to offset the new descriptor. + * Derived classes must override this function. + */ +Descriptor.prototype.clone = function(offset) { + throw new Error('Unimplemented.'); +}; + +/** + * Generate the Curve implied by this descriptor and the supplied hand + * positions. + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * Derived classes must override this function. + */ +Descriptor.prototype.generateCurve = function(handPositions) { + throw new Error('Unimplemented.'); +}; + +/** + * Adjust the start time of this Descriptor to be in [0, pathLength). + * @param {!number} pathLength the duration of a path, in beats. + * @return {!Descriptor} this. + */ +Descriptor.prototype.fixUpModPathLength = function(pathLength) { + this.time = this.time % pathLength; + return this; +}; + +/** + * This describes a throw in a site-swap pattern. + * @param {!number} throwNum the site-swap number of the throw. + * @param {!number} throwTime the time this throw occurs. + * @param {!number} sourceHand the index of the throwing hand. + * @param {!number} destHand the index of the catching hand. + * @constructor + */ +ThrowDescriptor = function(throwNum, throwTime, sourceHand, destHand) { + this.throwNum = throwNum; + this.sourceHand = sourceHand; + this.destHand = destHand; + this.time = throwTime; +} + +/** + * This is a subclass of Descriptor. + */ +ThrowDescriptor.prototype = new Descriptor(); + +/** + * Set up the constructor, just to be neat. + */ +ThrowDescriptor.prototype.constructor = ThrowDescriptor; + +/** + * We label each Descriptor subclass with a type for debugging. + */ +ThrowDescriptor.prototype.type = 'THROW'; + +/** + * Create an otherwise-identical copy of this descriptor at a given time offset. + * Note that offset may put time past patternLength; the caller will have to fix + * this up manually. + * @param {number} offset how many beats to offset the new descriptor. + * @return {!Descriptor} the new copy. + */ +ThrowDescriptor.prototype.clone = function(offset) { + offset = offset || 0; // Turn null into 0. + return new ThrowDescriptor(this.throwNum, this.time + offset, + this.sourceHand, this.destHand); +}; + +/** + * Convert the ThrowDescriptor to a string for debugging. + * @return {String} debugging output. + */ +ThrowDescriptor.prototype.toString = function() { + return '(' + this.throwNum + ' from hand ' + this.sourceHand + ' to hand ' + + this.destHand + ')'; +}; + +/** + * Generate the Curve implied by this descriptor and the supplied hand + * positions. + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * @return {!Curve} the curve. + */ +ThrowDescriptor.prototype.generateCurve = function(handPositions) { + var startPos = handPositions[this.sourceHand].throwPositions[this.destHand]; + var endPos = handPositions[this.destHand].catchPosition; + return Curve.computeThrowCurve(startPos, endPos, this.time, + this.throwNum - 1); }; + +/** + * This describes a catch in a site-swap pattern. + * @param {!number} hand the index of the catching hand. + * @param {!number} sourceThrowNum the site-swap number of the preceeding throw. + * @param {!number} destThrowNum the site-swap number of the following throw. + * @param {!number} sourceHand the index of the hand throwing the source throw. + * @param {!number} destHand the index of the hand catching the following throw. + * @param {!number} catchTime the time at which the catch occurs. + * @constructor + */ +CarryDescriptor = function(hand, sourceThrowNum, destThrowNum, sourceHand, + destHand, catchTime) { + this.hand = hand; + this.sourceThrowNum = sourceThrowNum; + this.destThrowNum = destThrowNum; + this.sourceHand = sourceHand; + this.destHand = destHand; + this.time = catchTime; +} + +/** + * This is a subclass of Descriptor. + */ +CarryDescriptor.prototype = new Descriptor(); + +/** + * Set up the constructor, just to be neat. + */ +CarryDescriptor.prototype.constructor = CarryDescriptor; + +/** + * We label each Descriptor subclass with a type for debugging. + */ +CarryDescriptor.prototype.type = 'CARRY'; + +/** + * Since this gets pathLength, not patternLength, we'll have to collapse sets + * of CarryDescriptors later, as they may be spread sparsely through the full + * animation and we'll only want them to be distributed over the full pattern + * length. We may have dupes to throw away as well. + * @param {!ThrowDescriptor} inThrowDescriptor + * @param {!ThrowDescriptor} outThrowDescriptor + * @param {!number} pathLength + * @return {!CarryDescriptor} + */ +CarryDescriptor.fromThrowDescriptors = function(inThrowDescriptor, + outThrowDescriptor, pathLength) { + assert(inThrowDescriptor.destHand == outThrowDescriptor.sourceHand); + assert((inThrowDescriptor.time + inThrowDescriptor.throwNum) % + pathLength == outThrowDescriptor.time); + return new CarryDescriptor(inThrowDescriptor.destHand, + inThrowDescriptor.throwNum, outThrowDescriptor.throwNum, + inThrowDescriptor.sourceHand, outThrowDescriptor.destHand, + (outThrowDescriptor.time + pathLength - 1) % pathLength); +}; + +/** + * Create an otherwise-identical copy of this descriptor at a given time offset. + * Note that offset may put time past patternLength; the caller will have to fix + * this up manually. + * @param {number} offset how many beats to offset the new descriptor. + * @return {!Descriptor} the new copy. + */ +CarryDescriptor.prototype.clone = function(offset) { + offset = offset || 0; // Turn null into 0. + return new CarryDescriptor(this.hand, this.sourceThrowNum, + this.destThrowNum, this.sourceHand, this.destHand, this.time + offset); +}; + +/** + * Convert the CarryDescriptor to a string for debugging. + * @return {String} debugging output. + */ +CarryDescriptor.prototype.toString = function() { + return 'time: ' + this.time + ' (hand ' + this.hand + ' catches ' + + this.sourceThrowNum + ' from hand ' + this.sourceHand + ' then throws ' + + this.destThrowNum + ' to hand ' + this.destHand + ')'; +}; + +/** + * Test if this CarryDescriptor is equivalent to another, mod patternLength. + * @param {!CarryDescriptor} cd the other CarryDescriptor. + * @param {!number} patternLength the length of the pattern. + * @return {!bool} + */ +CarryDescriptor.prototype.equalsWithMod = function(cd, patternLength) { + if (!(cd instanceof CarryDescriptor)) { + return false; + } + if (this.hand != cd.hand) { + return false; + } + if (this.sourceThrowNum != cd.sourceThrowNum) { + return false; + } + if (this.destThrowNum != cd.destThrowNum) { + return false; + } + if (this.sourceHand != cd.sourceHand) { + return false; + } + if (this.destHand != cd.destHand) { + return false; + } + if (this.time % patternLength != cd.time % patternLength) { + return false; + } + return true; +}; + +/** + * Generate the Curve implied by this descriptor and the supplied hand + * positions. + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * @return {!Curve} the curve. + */ +CarryDescriptor.prototype.generateCurve = function(handPositions) { + var startPos = handPositions[this.hand].catchPosition; + var endPos = handPositions[this.hand].throwPositions[this.destHand]; + return Curve.computeCircularCurve(startPos, endPos, this.time, 1); +}; + +/** + * This describes a carry of a "1" in a site-swap pattern. + * The flags isThrow and isCatch tell whether this is the actual 1 [isThrow] or + * the carry that receives the handoff [isCatch]. It's legal for both to be + * true, which happens when there are two 1s in a row. + * @param {!number} sourceThrowNum the site-swap number of the prev throw + * [including this one if isCatch]. + * @param {!number} sourceHand the index of the hand throwing sourceThrowNum. + * @param {!number} destThrowNum the site-swap number of the next throw + * [including this one if isThrow]. + * @param {!number} destHand the index of the hand catching destThrowNum. + * @param {!number} hand the index of the hand doing this carry. + * @param {!number} time the time at which the carry starts. + * @param {!bool} isThrow whether this is a 1. + * @param {!bool} isCatch whether this is the carry after a 1. + * @constructor + */ +CarryOneDescriptor = function(sourceThrowNum, sourceHand, destThrowNum, + destHand, hand, time, isThrow, isCatch) { + // It's possible to have !isCatch with sourceThrowNum == 1 temporarily, if we + // just haven't handled that 1 yet [we're doing the throw of this one, and + // will later get to the previous one, due to wraparound], and vice-versa. + assert(isThrow || (sourceThrowNum == 1)); + assert(isCatch || (destThrowNum == 1)); + this.sourceThrowNum = sourceThrowNum; + this.sourceHand = sourceHand; + this.destHand = destHand; + this.destThrowNum = destThrowNum; + this.hand = hand; + this.time = time; + this.isThrow = isThrow; + this.isCatch = isCatch; + return this; +} + +/** + * This is a subclass of Descriptor. + */ +CarryOneDescriptor.prototype = new Descriptor(); + +/** + * Set up the constructor, just to be neat. + */ +CarryOneDescriptor.prototype.constructor = CarryOneDescriptor; + +/** + * We label each Descriptor subclass with a type for debugging. + */ +CarryOneDescriptor.prototype.type = 'CARRY_ONE'; + +/** + * Create a pair of CarryOneDescriptors to describe the carry that is a throw of + * 1. A 1 spends all its time being carried, so these two carries surrounding + * it represent [and therefore don't have] a throw between them. + * Prev and post are generally the ordinary CarryDescriptors surrounding the + * throw of 1 that we're trying to implement. However, they could each [or + * both] independently be CarryOneDescriptors implementing other 1 throws. + * @param {!Descriptor} prev the carry descriptor previous to the 1. + * @param {!Descriptor} post the carry descriptor subsequent to the 1. + * @return {!Array.CarryOneDescriptor} a pair of CarryOneDescriptors. + */ +CarryOneDescriptor.getDescriptorPair = function(prev, post) { + assert(prev instanceof CarryDescriptor || prev instanceof CarryOneDescriptor); + assert(post instanceof CarryDescriptor || post instanceof CarryOneDescriptor); + assert(prev.destHand == post.hand); + assert(prev.hand == post.sourceHand); + var newPrev; + var newPost; + if (prev instanceof CarryOneDescriptor) { + assert(prev.isCatch && !prev.isThrow); + newPrev = prev; + newPrev.isThrow = true; + assert(newPrev.destHand == post.hand); + } else { + newPrev = new CarryOneDescriptor(prev.sourceThrowNum, prev.sourceHand, 1, + post.hand, prev.hand, prev.time, true, false); + } + if (post instanceof CarryOneDescriptor) { + assert(post.isThrow && !post.isCatch); + newPost = post; + newPost.isCatch = true; + assert(newPost.sourceHand == prev.hand); + assert(newPost.sourceThrowNum == 1); + } else { + newPost = new CarryOneDescriptor(1, prev.hand, post.destThrowNum, + post.destHand, post.hand, post.time, false, true); + } + return [newPrev, newPost]; +}; + +/** + * Convert the CarryOneDescriptor to a string for debugging. + * @return {String} debugging output. + */ +CarryOneDescriptor.prototype.toString = function() { + var s; + if (this.isThrow) { + s = 'Hand ' + this.hand + ' catches a ' + this.sourceThrowNum + ' from ' + + this.sourceHand + ' at time ' + this.time + ' and then passes a 1 to ' + + this.destHand + '.'; + } else { + assert(this.isCatch && this.sourceThrowNum == 1); + s = 'Hand ' + this.hand + ' catches a 1 from ' + this.sourceHand + + ' at time ' + this.time + ' and then passes a ' + this.destThrowNum + + ' to ' + this.destHand + '.'; + } + return s; +}; + +/** + * Compute the curve taken by a ball during the carry representing a 1, as long + * as it's not both a catch and a throw of a 1, which is handled elsewhere. + * It's either a LERP from a circular curve [a catch of a throw > 1] to a + * straight line to the handoff point [for isThrow] or a LERP from a straight + * line from the handoff to a circular curve for the next throw > 1 [for + * isCatch]. + * + * @param {!EquationCoefficients} catchPos + * @param {!EquationCoefficients} throwPos + * @param {!EquationCoefficients} handoffPos + * @param {!number} startTime + * @param {!bool} isCatch whether this is the carry after a 1. + * @param {!bool} isThrow whether this is a 1. + * @return {!Curve} + */ +Curve.computeCarryOneCurve = function(catchPos, throwPos, handoffPos, startTime, + isCatch, isThrow) { + assert(!isCatch != !isThrow); + var curve = Curve.computeCircularCurve(catchPos, throwPos, startTime, 1); + var curve2 = Curve.computeStraightLineCurve(handoffPos, handoffPos, + startTime, 1); + return curve.lerpIn(curve2, isThrow ? 1 : -1); +} + +/** + * Compute the curve taken by a ball during the carry representing a 1 that is + * both the catch of one 1 and the immediately-following throw of another 1. + * + * @param {!EquationCoefficients} leadingHandoffPos + * @param {!EquationCoefficients} trailingHandoffPos + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * @param {!number} hand + * @param {!number} time the time at which the first 1's catch takes place. + * @return {!Curve} + */ +Curve.computeConsecutiveCarryOneCurve = function(leadingHandoffPos, + trailingHandoffPos, handPositions, hand, time) { + var curve = Curve.computeStraightLineCurve(leadingHandoffPos, + handPositions[hand].basePosition, time, 1); + var curve2 = + Curve.computeStraightLineCurve(handPositions[hand].basePosition, + trailingHandoffPos, time, 1); + return curve.lerpIn(curve2, 1); +} + +/** + * Generate the Curve implied by this descriptor and the supplied hand + * positions. + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * @return {!Curve} the curve. + */ +CarryOneDescriptor.prototype.generateCurve = function(handPositions) { + var leadingHandoffPos, trailingHandoffPos; + if (this.isCatch) { + var p0 = handPositions[this.hand].basePosition; + var p1 = handPositions[this.sourceHand].basePosition; + handoffPos = leadingHandoffPos = p0.add(p1).scale(0.5); + } + if (this.isThrow) { + var p0 = handPositions[this.hand].basePosition; + var p1 = handPositions[this.destHand].basePosition; + handoffPos = trailingHandoffPos = p0.add(p1).scale(0.5); + } + if (!this.isCatch || !this.isThrow) { + return Curve.computeCarryOneCurve(handPositions[this.hand].catchPosition, + handPositions[this.hand].throwPositions[this.destHand], handoffPos, + this.time, this.isCatch, this.isThrow); + } else { + return Curve.computeConsecutiveCarryOneCurve(leadingHandoffPos, + trailingHandoffPos, handPositions, this.hand, this.time); + } +}; + +/** + * Create an otherwise-identical copy of this descriptor at a given time offset. + * Note that offset may put time past patternLength; the caller will have to fix + * this up manually. + * @param {number} offset how many beats to offset the new descriptor. + * @return {!Descriptor} the new copy. + */ +CarryOneDescriptor.prototype.clone = function(offset) { + offset = offset || 0; // Turn null into 0. + return new CarryOneDescriptor(this.sourceThrowNum, this.sourceHand, + this.destThrowNum, this.destHand, this.hand, this.time + offset, + this.isThrow, this.isCatch); +}; + +/** + * Test if this CarryOneDescriptor is equivalent to another, mod patternLength. + * @param {!CarryOneDescriptor} cd the other CarryOneDescriptor. + * @param {!number} patternLength the length of the pattern. + * @return {!bool} + */ +CarryOneDescriptor.prototype.equalsWithMod = function(cd, patternLength) { + if (!(cd instanceof CarryOneDescriptor)) { + return false; + } + if (this.hand != cd.hand) { + return false; + } + if (this.sourceThrowNum != cd.sourceThrowNum) { + return false; + } + if (this.destThrowNum != cd.destThrowNum) { + return false; + } + if (this.sourceHand != cd.sourceHand) { + return false; + } + if (this.destHand != cd.destHand) { + return false; + } + if (this.isCatch != cd.isCatch) { + return false; + } + if (this.isThrow != cd.isThrow) { + return false; + } + if (this.time % patternLength != cd.time % patternLength) { + return false; + } + return true; +}; + +/** + * This describes an empty hand in a site-swap pattern. + * @param {!Descriptor} cd0 the CarryDescriptor or CarryOneDescriptor describing + * this hand immediately before it was emptied. + * @param {!Descriptor} cd1 the CarryDescriptor or CarryOneDescriptor describing + * this hand immediately after it's done being empty. + * @param {!number} patternLength the length of the pattern. + * @constructor + */ +EmptyHandDescriptor = function(cd0, cd1, patternLength) { + assert(cd0.hand == cd1.hand); + this.hand = cd0.hand; + this.prevThrowDest = cd0.destHand; + this.sourceThrowNum = cd0.destThrowNum; + this.nextCatchSource = cd1.sourceHand; + this.destThrowNum = cd1.sourceThrowNum; + // This code assumes that each CarryDescriptor and CarryOneDescriptor always + // has a duration of 1 beat. If we want to be able to allow long-held balls + // [instead of thrown twos, for example], we'll have to fix that here and a + // number of other places. + this.time = (cd0.time + 1) % patternLength; + this.duration = cd1.time - this.time; + if (this.duration < 0) { + this.duration += patternLength; + assert(this.duration > 0); + } +} + +/** + * This is a subclass of Descriptor. + */ +EmptyHandDescriptor.prototype = new Descriptor(); + +/** + * Set up the constructor, just to be neat. + */ +EmptyHandDescriptor.prototype.constructor = EmptyHandDescriptor; + +/** + * We label each Descriptor subclass with a type for debugging. + */ +EmptyHandDescriptor.prototype.type = 'EMPTY'; + +/** + * Convert the EmptyHandDescriptor to a string for debugging. + * @return {String} debugging output. + */ +EmptyHandDescriptor.prototype.toString = function() { + return 'time: ' + this.time + ' for ' + this.duration + ' (hand ' + + this.hand + ', after throwing a ' + this.sourceThrowNum + ' to hand ' + + this.prevThrowDest + ' then catches a ' + this.destThrowNum + + ' from hand ' + this.nextCatchSource + ')'; +}; + +/** + * Generate the Curve implied by this descriptor and the supplied hand + * positions. + * @param {!Array.HandPositionRecord} handPositions where the hands will be. + * @return {!Curve} the curve. + */ +EmptyHandDescriptor.prototype.generateCurve = function(handPositions) { + var startPos, endPos; + if (this.sourceThrowNum == 1) { + var p0 = handPositions[this.hand].basePosition; + var p1 = handPositions[this.prevThrowDest].basePosition; + startPos = p0.add(p1).scale(0.5); + } else { + startPos = handPositions[this.hand].throwPositions[this.prevThrowDest]; + } + if (this.destThrowNum == 1) { + var p0 = handPositions[this.hand].basePosition; + var p1 = handPositions[this.nextCatchSource].basePosition; + endPos = p0.add(p1).scale(0.5); + } else { + endPos = handPositions[this.hand].catchPosition; + } + // TODO: Replace with a good empty-hand curve. + return Curve.computeStraightLineCurve(startPos, endPos, this.time, + this.duration); +}; + +/** + * A series of descriptors that describes the full path of an object during a + * pattern. + * @param {!Array.Descriptor} descriptors all descriptors for the object. + * @param {!number} pathLength the length of the path in beats. + * @constructor + */ +Path = function(descriptors, pathLength) { + this.descriptors = descriptors; + this.pathLength = pathLength; +} + +/** + * Create a Path representing a ball, filling in the gaps between the throws + * with carry descriptors. Since it's a ball's path, there are no + * EmptyHandDescriptors in the output. + * @param {!Array.ThrowDescriptor} throwDescriptors the ball's part of the + * pattern. + * @param {!number} pathLength the length of the pattern in beats. + * @return {!Path} the ball's full path. + */ +Path.ballPathFromThrowDescriptors = function(throwDescriptors, pathLength) { + return new Path( + Path.createDescriptorList(throwDescriptors, pathLength), pathLength); +}; + +/** + * Create the sequence of ThrowDescriptors, CarryDescriptors, and + * CarryOneDescriptor describing the path of a ball through a pattern. + * A sequence such as (h j k) generally maps to an alternating series of throw + * and carry descriptors [Th Chj Tj Cjk Tk Ck? ...]. However, when j is a 1, + * you remove the throw descriptor and modify the previous and subsequent carry + * descriptors, since the throw descriptor has zero duration and the carry + * descriptors need to take into account the handoff. + * @param {!Array.ThrowDescriptor} throwDescriptors the ball's part of the + * pattern. + * @param {!number} pathLength the length of the pattern in beats. + * @return {!Array.Descriptor} the full set of descriptors for the ball. + */ +Path.createDescriptorList = function(throwDescriptors, pathLength) { + var descriptors = []; + var prevThrow; + for (var index in throwDescriptors) { + var td = throwDescriptors[index]; + if (prevThrow) { + descriptors.push( + CarryDescriptor.fromThrowDescriptors(prevThrow, td, pathLength)); + } // Else it's handled after the loop. + descriptors.push(td); + prevThrow = td; + } + descriptors.push( + CarryDescriptor.fromThrowDescriptors(prevThrow, throwDescriptors[0], + pathLength)); + // Now post-process to take care of throws of 1. It's easier to do it here + // than during construction since we can now assume that the previous and + // subsequent carry descriptors are already in place [modulo pathLength]. + for (var i = 0; i < descriptors.length; ++i) { + var descriptor = descriptors[i]; + if (descriptor instanceof ThrowDescriptor) { + if (descriptor.throwNum == 1) { + var prevIndex = (i + descriptors.length - 1) % descriptors.length; + var postIndex = (i + 1) % descriptors.length; + var replacements = CarryOneDescriptor.getDescriptorPair( + descriptors[prevIndex], descriptors[postIndex]); + descriptors[prevIndex] = replacements[0]; + descriptors[postIndex] = replacements[1]; + descriptors.splice(i, 1); + // We've removed a descriptor from the array, but since we can never + // have 2 ThrowDescriptors in a row, we don't need to decrement i. + } + } + } + return descriptors; +}; + +/** + * Convert the Path to a string for debugging. + * @return {String} debugging output. + */ +Path.prototype.toString = function() { + var ret = 'pathLength is ' + this.pathLength + '; ['; + for (var index in this.descriptors) { + ret += this.descriptors[index].toString(); + } + ret += ']'; + return ret; +}; + +/** + * Create an otherwise-identical copy of this path at a given time offset. + * Note that offset may put time references in the Path past the length of the + * pattern. The caller must fix this up manually. + * @param {number} offset how many beats to offset the new Path. + * @return {!Path} the new copy. + */ +Path.prototype.clone = function(offset) { + offset = offset || 0; // Turn null into 0. + var descriptors = []; + for (var index in this.descriptors) { + descriptors.push(this.descriptors[index].clone(offset)); + } + return new Path(descriptors, this.pathLength); +}; + +/** + * Adjust the start time of all descriptors to be in [0, pathLength) via modular + * arithmetic. Reorder the array such that they're sorted in increasing order + * of time. + * @return {!Path} this. + */ +Path.prototype.fixUpModPathLength = function() { + var splitIndex; + var prevTime = 0; + for (var index in this.descriptors) { + var d = this.descriptors[index]; + d.fixUpModPathLength(this.pathLength); + if (d.time < prevTime) { + assert(null == splitIndex); + splitIndex = index; // From here to the end should move to the start. + } + prevTime = d.time; + } + if (null != splitIndex) { + var temp = this.descriptors.slice(splitIndex); + this.descriptors.length = splitIndex; + this.descriptors = temp.concat(this.descriptors); + } + return this; +}; + +/** + * Take a standard asynch siteswap pattern [expressed as an array of ints] and + * a number of hands, and expand it into a 2D grid of ThrowDescriptors with one + * row per hand. + * Non-asynch patterns are more complicated, since their linear forms aren't + * fully-specified, so we don't handle them here. + * You'll want to expand your pattern to the LCM of numHands and minimal pattern + * length before calling this. + * The basic approach doesn't really work for one-handed patterns. It ends up + * with catches and throws happening at the same time [having removed all + * empty-hand time in between them]. To fix this, we double all throw heights + * and space them out, as if doing a two-handed pattern with all zeroes from the + * other hand. Yes, this points out that the overall approach we're taking is a + * bit odd [since you end up with hands empty for time proportional to the + * number of hands], but you have to make some sort of assumptions to generalize + * siteswaps to N hands, and that's what I chose. + * @param {!Array.number} pattern an asynch siteswap pattern. + * @param {!number} numHands the number of hands. + * @return {!Array.Array.ThrowDescriptor} the expanded pattern. + */ +function expandPattern(pattern, numHands) { + var fullPattern = []; + assert(numHands > 0); + if (numHands == 1) { + numHands = 2; + var temp = []; + for (var i = 0; i < pattern.length; ++i) { + temp[2 * i] = 2 * pattern[i]; + temp[2 * i + 1] = 0; + } + pattern = temp; + } + for (var hand = 0; hand < numHands; ++hand) { + fullPattern[hand] = []; + } + for (var time = 0; time < pattern.length; ++time) { + for (var hand = 0; hand < numHands; ++hand) { + var t; + if (hand == time % numHands) { + t = new ThrowDescriptor(pattern[time], time, hand, + (hand + pattern[time]) % numHands); + } else { + // These are ignored during analysis, so they don't appear in BallPaths. + t = new ThrowDescriptor(0, time, hand, hand); + } + fullPattern[hand].push(t); + } + } + return fullPattern; +} + +// TODO: Wrap the final pattern in a class, then make the remaining few global +// functions be members of that class to clean up the global namespace. + +/** + * Given a valid site-swap for a nonzero number of balls, stored as an expanded + * pattern array-of-arrays, with pattern length the LCM of hands and minimal + * pattern length, produce Paths for all the balls. + * @param {!Array.Array.ThrowDescriptor} pattern a valid pattern. + * @return {!Array.Path} the paths of all the balls. + */ +function generateBallPaths(pattern) { + var numHands = pattern.length; + assert(numHands > 0); + var patternLength = pattern[0].length; + assert(patternLength > 0); + var sum = 0; + for (var hand in pattern) { + for (var time in pattern[hand]) { + sum += pattern[hand][time].throwNum; + } + } + var numBalls = sum / patternLength; + assert(numBalls == Math.round(numBalls)); + assert(numBalls > 0); + + var ballsToAllocate = numBalls; + var ballPaths = []; + // NOTE: The indices of locationsChecked are reversed from those of pattern + // for simplicity of allocation. This might be worth flipping to match. + var locationsChecked = []; + for (var time = 0; time < patternLength && ballsToAllocate; ++time) { + locationsChecked[time] = locationsChecked[time] || []; + for (var hand = 0; hand < numHands && ballsToAllocate; ++hand) { + if (locationsChecked[time][hand]) { + continue; + } + var curThrowDesc = pattern[hand][time]; + var curThrow = curThrowDesc.throwNum; + if (!curThrow) { + assert(curThrow === 0); + continue; + } + var throwDescriptors = []; + var curTime = time; + var curHand = hand; + var wraps = 0; + do { + if (!locationsChecked[curTime]) { + locationsChecked[curTime] = []; + } + assert(!locationsChecked[curTime][curHand]); + locationsChecked[curTime][curHand] = true; + // We copy curThrowDesc here, adding wraps * patternLength, to get + // the true throw time relative to offset. Later we'll add in offset + // when we clone again, then mod by pathLength. + throwDescriptors.push(curThrowDesc.clone(wraps * patternLength)); + var nextThrowTime = curThrow + curTime; + wraps += Math.floor(nextThrowTime / patternLength); + curTime = nextThrowTime % patternLength; + assert(curTime >= time); // Else we'd have covered it earlier. + curHand = curThrowDesc.destHand; + var tempThrowDesc = curThrowDesc; + curThrowDesc = pattern[curHand][curTime]; + curThrow = curThrowDesc.throwNum; + assert(tempThrowDesc.destHand == curThrowDesc.sourceHand); + assert(curThrowDesc.time == + (tempThrowDesc.throwNum + tempThrowDesc.time) % patternLength); + } while (curTime != time || curHand != hand); + var pathLength = wraps * patternLength; + var ballPath = + Path.ballPathFromThrowDescriptors(throwDescriptors, pathLength); + for (var i = 0; i < wraps; ++i) { + var offset = i * patternLength % pathLength; + ballPaths.push(ballPath.clone(offset, pathLength).fixUpModPathLength()); + } + ballsToAllocate -= wraps; + assert(ballsToAllocate >= 0); + } + } + return ballPaths; +} + +/** + * Given an array of ball paths, produce the corresponding set of hand paths. + * @param {!Array.Path} ballPaths the Paths of all the balls in the pattern. + * @param {!number} numHands how many hands to use in the pattern. + * @param {!number} patternLength the length, in beats, of the pattern. + * @return {!Array.Path} the paths of all the hands. + */ +function generateHandPaths(ballPaths, numHands, patternLength) { + assert(numHands > 0); + assert(patternLength > 0); + var handRecords = []; // One record per hand. + for (var idxBR in ballPaths) { + var descriptors = ballPaths[idxBR].descriptors; + for (var idxD in descriptors) { + var descriptor = descriptors[idxD]; + // TODO: Fix likely needed for throws of 1. + if (!(descriptor instanceof ThrowDescriptor)) { + // It's a CarryDescriptor or a CarryOneDescriptor. + var hand = descriptor.hand; + if (!handRecords[hand]) { + handRecords[hand] = []; + } + // TODO: Should we not shorten stuff here if we're going to lengthen + // everything later anyway? Is there a risk of inconsistency due to + // ball paths of different lengths? + var catchTime = descriptor.time % patternLength; + if (!handRecords[hand][catchTime]) { + // We pass in this offset to set the new descriptor's time to + // catchTime, so as to keep it within [0, patternLength). + handRecords[hand][catchTime] = + descriptor.clone(catchTime - descriptor.time); + } else { + assert( + handRecords[hand][catchTime].equalsWithMod( + descriptor, patternLength)); + } + } + } + } + var handPaths = []; + for (var hand in handRecords) { + var outDescriptors = []; + var inDescriptors = handRecords[hand]; + var prevDescriptor = null; + var descriptor; + for (var idxD in inDescriptors) { + descriptor = inDescriptors[idxD]; + assert(descriptor); // Enumeration should skip array holes. + assert(descriptor.hand == hand); + if (prevDescriptor) { + outDescriptors.push(new EmptyHandDescriptor(prevDescriptor, descriptor, + patternLength)); + } + outDescriptors.push(descriptor.clone()); + prevDescriptor = descriptor; + } + // Note that this EmptyHandDescriptor that wraps around the end lives at the + // end of the array, not the beginning, despite the fact that it may be the + // active one at time zero. This is the same behavior as with Paths for + // balls. + descriptor = new EmptyHandDescriptor(prevDescriptor, outDescriptors[0], + patternLength); + if (descriptor.time < outDescriptors[0].time) { + assert(descriptor.time + descriptor.duration == outDescriptors[0].time); + outDescriptors.unshift(descriptor); + } else { + assert(descriptor.time == + outDescriptors[outDescriptors.length - 1].time + 1); + outDescriptors.push(descriptor); + } + handPaths[hand] = + new Path(outDescriptors, patternLength).fixUpModPathLength(); + } + return handPaths; +} + +// NOTE: All this Vector stuff does lots of object allocations. If that's a +// problem for your browser [e.g. IE6], you'd better stick with the embedded V8. +// This code predates the creation of o3djs/math.js; I should probably switch it +// over at some point, but for now it's not worth the trouble. + +/** + * A simple 3-dimensional vector. + * @constructor + */ +Vector = function(x, y, z) { + this.x = x; + this.y = y; + this.z = z; +} + +Vector.prototype.sub = function(v) { + return new Vector(this.x - v.x, this.y - v.y, this.z - v.z); +}; + +Vector.prototype.add = function(v) { + return new Vector(this.x + v.x, this.y + v.y, this.z + v.z); +}; + +Vector.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +}; + +Vector.prototype.length = function() { + return Math.sqrt(this.dot(this)); +}; + +Vector.prototype.scale = function(s) { + return new Vector(this.x * s, this.y * s, this.z * s); +}; + +Vector.prototype.set = function(v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; +}; + +Vector.prototype.normalize = function() { + var length = this.length(); + assert(length); + this.set(this.scale(1 / length)); + return this; +}; + +/** + * Convert the Vector to a string for debugging. + * @return {String} debugging output. + */ +Vector.prototype.toString = function() { + return '{' + this.x.toFixed(3) + ', ' + this.y.toFixed(3) + ', ' + + this.z.toFixed(3) + '}'; +}; + +/** + * A container class that holds the positions relevant to a hand: where it is + * when it's not doing anything, where it likes to catch balls, and where it + * likes to throw balls to each of the other hands. + * @param {!Vector} basePosition the centroid of throw and catch positions when + * the hand throws to itself. + * @param {!Vector} catchPosition where the hand likes to catch balls. + * @constructor + */ +HandPositionRecord = function(basePosition, catchPosition) { + this.basePosition = basePosition; + this.catchPosition = catchPosition; + this.throwPositions = []; +} + +/** + * Convert the HandPositionRecord to a string for debugging. + * @return {String} debugging output. + */ +HandPositionRecord.prototype.toString = function() { + var s = 'base: ' + this.basePosition.toString() + ';\n'; + s += 'catch: ' + this.catchPosition.toString() + ';\n'; + s += 'throws:\n'; + for (var i = 0; i < this.throwPositions.length; ++i) { + s += '[' + i + '] ' + this.throwPositions[i].toString() + '\n'; + } + return s; +}; + +/** + * Compute all the hand positions used in a pattern given a number of hands and + * a grouping style ["even" for evenly-spaced hands, "pairs" to group them in + * pairs, as with 2-handed jugglers]. + * @param {!number} numHands the number of hands to use. + * @param {!String} style the grouping style. + * @return {!Array.HandPositionRecord} a full set of hand positions. + */ +function computeHandPositions(numHands, style) { + assert(numHands > 0); + var majorRadiusScale = 0.75; + var majorRadius = majorRadiusScale * (numHands - 1); + var throwCatchOffset = 0.45; + var catchRadius = majorRadius + throwCatchOffset; + var handPositionRecords = []; + for (var hand = 0; hand < numHands; ++hand) { + var circleFraction; + if (style == 'even') { + circleFraction = hand / numHands; + } else { + assert(style == 'pairs'); + circleFraction = (hand + Math.floor(hand / 2)) / (1.5 * numHands); + } + var cos = Math.cos(Math.PI * 2 * circleFraction); + var sin = Math.sin(Math.PI * 2 * circleFraction); + var cX = catchRadius * cos; + var cY = 0; + var cZ = catchRadius * sin; + var bX = majorRadius * cos; + var bY = 0; + var bZ = majorRadius * sin; + handPositionRecords[hand] = new HandPositionRecord( + new Vector(bX, bY, bZ), new Vector(cX, cY, cZ)); + } + // Now that we've got all the hands' base and catch positions, we need to + // compute the appropriate throw positions for each hand pair. + for (var source = 0; source < numHands; ++source) { + var throwHand = handPositionRecords[source]; + for (var target = 0; target < numHands; ++target) { + var catchHand = handPositionRecords[target]; + if (throwHand == catchHand) { + var baseV = throwHand.basePosition; + throwHand.throwPositions[target] = + baseV.add(baseV.sub(throwHand.catchPosition)); + } else { + var directionV = + catchHand.catchPosition.sub(throwHand.basePosition).normalize(); + var offsetV = directionV.scale(throwCatchOffset); + throwHand.throwPositions[target] = + throwHand.basePosition.add(offsetV); + } + } + } + return handPositionRecords; +} + +/** + * Convert an array of HandPositionRecord to a string for debugging. + * @param {!Array.HandPositionRecord} positions the positions to display. + * @return {String} debugging output. + */ +function getStringFromHandPositions(positions) { + var s = ''; + for (index in positions) { + s += positions[index].toString(); + } + return s; +} + +/** + * The set of curves an object passes through throughout a full animation cycle. + * @param {!number} duration the length of the animation in beats. + * @param {!Array.Curve} curves the full set of Curves. + * @constructor + */ +CurveSet = function(duration, curves) { + this.duration = duration; + this.curves = curves; +} + +/** + * Looks up what curve is active at a particular time. This is slower than + * getCurveForTime, but can be used even if no Curve starts precisely at + * unsafeTime % this.duration. + * @param {!number} unsafeTime the time at which to check. + * @return {!Curve} the curve active at unsafeTime. + */ +CurveSet.prototype.getCurveForUnsafeTime = function(unsafeTime) { + unsafeTime %= this.duration; + time = Math.floor(unsafeTime); + if (this.curves[time]) { + return this.curves[time]; + } + var curve; + for (var i = time; i >= 0; --i) { + curve = this.curves[i]; + if (curve) { + assert(i + curve.duration >= unsafeTime); + return curve; + } + } + // We must want the last one. There's always a last one, given how we + // construct the CurveSets; they're sparse, but the length gets set by adding + // elements at the end. + curve = this.curves[this.curves.length - 1]; + unsafeTime += this.duration; + assert(curve.startTime <= unsafeTime); + assert(curve.startTime + curve.duration > unsafeTime); + return curve; +}; + +/** + * Looks up what curve is active at a particular time. This is faster than + * getCurveForUnsafeTime, but can only be used if if a Curve starts precisely at + * unsafeTime % this.duration. + * @param {!number} time the time at which to check. + * @return {!Curve} the curve starting at time. + */ +CurveSet.prototype.getCurveForTime = function(time) { + return this.curves[time % this.duration]; +}; + +/** + * Convert the CurveSet to a string for debugging. + * @return {String} debugging output. + */ +CurveSet.prototype.toString = function() { + var s = 'Duration: ' + this.duration + '\n'; + for (var c in this.curves) { + s += this.curves[c].toString(); + } + return s; +}; + +/** + * Namespace object to hold the pure math functions. + * TODO: Consider just rolling these into the Pattern object, when it gets + * created. + */ +var JugglingMath = {}; + +/** + * Computes the greatest common devisor of integers a and b. + * @param {!number} a an integer. + * @param {!number} b an integer. + * @return {!number} the GCD of a and b. + */ +JugglingMath.computeGCD = function(a, b) { + assert(Math.round(a) == a); + assert(Math.round(b) == b); + assert(a >= 0); + assert(b >= 0); + if (!b) { + return a; + } else { + return JugglingMath.computeGCD(b, a % b); + } +} + +/** + * Computes the least common multiple of integers a and b, by making use of the + * fact that LCM(a, b) * GCD(a, b) == a * b. + * @param {!number} a an integer. + * @param {!number} b an integer. + * @return {!number} the LCM of a and b. + */ +JugglingMath.computeLCM = function(a, b) { + assert(Math.round(a) == a); + assert(Math.round(b) == b); + assert(a >= 0); + assert(b >= 0); + var ret = a * b / JugglingMath.computeGCD(a, b); + assert(Math.round(ret) == ret); + return ret; +} + +/** + * Given a Path and a set of hand positions, compute the corresponding set of + * Curves. + * @param {!Path} path the path of an object. + * @param {!Array.HandPositionRecord} handPositions the positions of the hands + * juggling the pattern containing the path. + * @return {!CurveSet} the full set of curves. + */ +CurveSet.getCurveSetFromPath = function(path, handPositions) { + var curves = []; + var pathLength = path.pathLength; + for (var index in path.descriptors) { + var descriptor = path.descriptors[index]; + var curve = descriptor.generateCurve(handPositions); + assert(!curves[curve.startTime]); + assert(curve.startTime < pathLength); + curves[curve.startTime] = curve; + } + return new CurveSet(pathLength, curves); +} + +/** + * Given a set of Paths and a set of hand positions, compute the corresponding + * CurveSets. + * @param {!Array.Path} paths the paths of a number of objects. + * @param {!Array.HandPositionRecord} handPositions the positions of the hands + * juggling the pattern containing the paths. + * @return {!Array.CurveSet} the CurveSets. + */ +CurveSet.getCurveSetsFromPaths = function(paths, handPositions) { + var curveSets = []; + for (var index in paths) { + var path = paths[index]; + curveSets[index] = CurveSet.getCurveSetFromPath(path, handPositions); + } + return curveSets; +} + +/** + * This is a temporary top-level calculation function that converts a standard + * asynchronous siteswap, expressed as a string of digits, into a full + * ready-to-animate set of CurveSets. Later on we'll be using an interface that + * can create a richer set of patterns than those expressable in the traditional + * string-of-ints format. + * @param {!String} patternString the siteswap. + * @param {!number} numHands the number of hands to use for the pattern. + * @param {!String} style how to space the hands ["pairs" or "even"]. + * @return {!Object} a fully-analyzed pattern as CurveSets and associated data. + */ +function computeFullPatternFromString(patternString, numHands, style) { + var patternAsStrings = patternString.split(/[ ,]+ */); + var patternSegment = []; + for (var index in patternAsStrings) { + if (patternAsStrings[index]) { // Beware extra whitespace at the ends. + patternSegment.push(parseInt(patternAsStrings[index])); + } + } + var pattern = []; + // Now expand the pattern out to the length of the LCM of pattern length and + // number of hands, so that each throw gets done in each of its incarnations. + var multiple = JugglingMath.computeLCM(patternSegment.length, numHands) / + patternSegment.length; + for (var i = 0; i < multiple; ++i) { + pattern = pattern.concat(patternSegment); + } + + var fullPattern = expandPattern(pattern, numHands); + var patternLength = fullPattern[0].length; + + var ballPaths = generateBallPaths(fullPattern); + var handPaths = generateHandPaths(ballPaths, numHands, patternLength); + + var handPositions = computeHandPositions(numHands, style); + var ballCurveSets = CurveSet.getCurveSetsFromPaths(ballPaths, handPositions); + var handCurveSets = CurveSet.getCurveSetsFromPaths(handPaths, handPositions); + + // Find the LCM of all the curveSet durations. This will be the length of the + // fully-expanded queue. We could expand to this before computing the + // CurveSets, but this way's probably just a little cheaper. + var lcmDuration = 1; + for (var i in ballCurveSets) { + var duration = ballCurveSets[i].duration; + if (duration > lcmDuration || lcmDuration % duration) { + lcmDuration = JugglingMath.computeLCM(lcmDuration, duration); + } + } + for (var i in handCurveSets) { + var duration = handCurveSets[i].duration; + if (duration > lcmDuration || lcmDuration % duration) { + lcmDuration = JugglingMath.computeLCM(lcmDuration, duration); + } + } + return { + numBalls: ballPaths.length, + numHands: handPaths.length, + duration: lcmDuration, + handCurveSets: handCurveSets, + ballCurveSets: ballCurveSets + } +} diff --git a/o3d/samples/siteswap/siteswap.html b/o3d/samples/siteswap/siteswap.html index 15d7497..06656eb 100644 --- a/o3d/samples/siteswap/siteswap.html +++ b/o3d/samples/siteswap/siteswap.html @@ -1,323 +1,323 @@ - - - - - - - - Site Swap Simulator - - - - - - - - - - - - - - -
-

Juggler

- This sample displays a site-swap juggling pattern with animation done by - a vertex shader. -
- - - - - -
- - - - - - - -
- - 1 Hand
- - 2 Hands
- - 3 Hands
- - 4 Hands
-
- - - Animate - Pair Hands - - -
-
-
-
- - - - -
- -
- -
- - -
- - + + + + + + + + Site Swap Simulator + + + + + + + + + + + + + + +
+

Juggler

+ This sample displays a site-swap juggling pattern with animation done by + a vertex shader. +
+ + + + + +
+ + + + + + + +
+ + 1 Hand
+ + 2 Hands
+ + 3 Hands
+ + 4 Hands
+
+ + + Animate + Pair Hands + + +
+
+
+
+ + + + +
+ +
+ +
+ + +
+ + diff --git a/o3d/samples/siteswap/siteswap.js b/o3d/samples/siteswap/siteswap.js index 24a59a7cd..6b77aff 100644 --- a/o3d/samples/siteswap/siteswap.js +++ b/o3d/samples/siteswap/siteswap.js @@ -1,415 +1,415 @@ -// @@REWRITE(insert js-copyright) -// @@REWRITE(delete-start) -// Copyright 2009 Google Inc. All Rights Reserved -// @@REWRITE(delete-end) - -/** - * This file contains the top-level logic and o3d-related code for the siteswap - * animator. - */ - -o3djs.require('o3djs.rendergraph'); -o3djs.require('o3djs.math'); -o3djs.require('o3djs.primitives'); -o3djs.require('o3djs.dump'); - -// Global variables are all referenced via g, so that either interpreter can -// find them easily. -var g = {}; - -/** - * Creates a color based on an input index as a seed. - * @param {!number} index the seed value to select the color. - * @return {!Array.number} an [r g b a] color. - */ -function createColor(index) { - var N = 12; // Number of distinct colors. - var root3 = Math.sqrt(3); - var theta = 2 * Math.PI * index / N; - var sin = Math.sin(theta); - var cos = Math.cos(theta); - return [(1 / 3 + 2 / 3 * cos) + (1 / 3 - cos / 3 - sin / root3), - (1 / 3 - cos / 3 + sin / root3) + (1 / 3 + 2 / 3 * cos), - (1 / 3 - cos / 3 - sin / root3) + (1 / 3 - cos / 3 + sin / root3), - 1]; -} - -/** - * Creates a material, given the index as a seed to make it distinguishable. - * @param {number} index an integer used to create a distinctive color. - * @return {!o3d.Material} the material. - */ -function createMaterial(index) { - var material = g.pack.createObject('Material'); - - // Apply our effect to this material. The effect tells the 3D hardware - // which shader to use. - material.effect = g.effect; - - // Set the material's drawList - material.drawList = g.viewInfo.performanceDrawList; - - // This will create our quadColor parameter on the material. - g.effect.createUniformParameters(material); - - // Set up the individual parameters in our effect file. - - // Light position - var light_pos_param = material.getParam('light_pos'); - light_pos_param.value = [10, 10, 20]; - - // Phong components of the light source - var light_ambient_param = material.getParam('light_ambient'); - var light_diffuse_param = material.getParam('light_diffuse'); - var light_specular_param = material.getParam('light_specular'); - - // White ambient light - light_ambient_param.value = [0.04, 0.04, 0.04, 1]; - - light_diffuse_param.value = createColor(index); - // White specular light - light_specular_param.value = [0.5, 0.5, 0.5, 1]; - - // Shininess of the material (for specular lighting) - var shininess_param = material.getParam('shininess'); - shininess_param.value = 30.0; - - // Bind the counter's count to the input of the FunctionEval. - var paramTime = material.getParam('time'); - paramTime.bind(g.counter.getParam('count')); - - material.getParam('camera_pos').value = g.eye; - - return material; -} - -/** - * Gets a material from our cache, creating it if it's not yet been made. - * Uses index as a seed to make the material distinguishable. - * @param {number} index an integer used to create/fetch a distinctive color. - * @return {!o3d.Material} the material. - */ -function getMaterial(index) { - g.materials = g.materials || []; // See initStep2 for a comment. - if (!g.materials[index]) { - g.materials[index] = createMaterial(index); - } - return g.materials[index]; -} - -/** - * Initializes g.o3d. - * @param {Array} clientElements Array of o3d object elements. - */ -function initStep2(clientElements) { - // Initializes global variables and libraries. - window.g = g; - - // Used to tell whether we need to recompute our view on resize. - g.o3dWidth = -1; - g.o3dHeight = -1; - - // We create a different material for each color of object. - //g.materials = []; // TODO(ericu): If this is executed, we fail. Why? - - // We hold on to all the shapes here so that we can clean them up when we want - // to change patterns. - g.ballShapes = []; - g.handShapes = []; - - g.o3dElement = clientElements[0]; - g.o3d = g.o3dElement.o3d; - g.math = o3djs.math; - g.client = g.o3dElement.client; - - // Initialize client sample libraries. - o3djs.base.init(g.o3dElement); - - // Create a g.pack to manage our resources/assets - g.pack = g.client.createPack(); - - // Create the render graph for a view. - g.viewInfo = o3djs.rendergraph.createBasicView( - g.pack, - g.client.root, - g.client.renderGraphRoot); - - // Get the default context to hold view/projection matrices. - g.context = g.viewInfo.drawContext; - - // Load a simple effect from a textarea. - g.effect = g.pack.createObject('Effect'); - g.effect.loadFromFXString(document.getElementById('shader').value); - - // Eye-position: this is where our camera is located. - // Global because each material we create must also know where it is, so that - // the shader works properly. - g.eye = [1, 6, 10]; - - // Target, this is the point at which our camera is pointed. - var target = [0, 2, 0]; - - // Up-vector, this tells the camera which direction is 'up'. - // We define the positive y-direction to be up in this example. - var up = [0, 1, 0]; - - g.context.view = g.math.matrix4.lookAt(g.eye, target, up); - - // Make a SecondCounter to provide the time for our animation. - g.counter = g.pack.createObject('SecondCounter'); - g.counter.multiplier = 3; // Speed up time; this is in throws per second. - - // Generate the projection and viewProjection matrices based - // on the g.o3d plugin size by calling onResize(). - onResize(); - - // If we don't check the size of the client area every frame we don't get a - // chance to adjust the perspective matrix fast enough to keep up with the - // browser resizing us. - // TODO(ericu): Switch to using the resize event once it's checked in. - g.client.setRenderCallback(onResize); -} - -/** - * Stops or starts the animation based on the state of an html checkbox. - */ -function updateAnimating() { - var box = document.the_form.check_box; - g.counter.running = box.checked; -} - -/** - * Generates the projection matrix based on the size of the g.o3d plugin - * and calculates the view-projection matrix. - */ -function onResize() { - var newWidth = g.client.width; - var newHeight = g.client.height; - - if (newWidth != g.o3dWidth || newHeight != g.o3dHeight) { - debug('resizing'); - g.o3dWidth = newWidth; - g.o3dHeight = newHeight; - - // Create our projection matrix, with a vertical field of view of 45 degrees - // a near clipping plane of 0.1 and far clipping plane of 100. - g.context.projection = g.math.matrix4.perspective( - 45 * Math.PI / 180, - g.o3dWidth / g.o3dHeight, - 0.1, - 100); - } -} - -/** - * Computes and prepares animation of the pattern input via the html form. If - * the box is checked, this will immediately begin animation as well. - */ -function onComputePattern() { - try { - g.counter.removeAllCallbacks(); - var group = document.the_form.radio_group_hands; - var numHands = -1; - for (var i = 0; i < group.length; ++i) { - if (group[i].checked) { - numHands = parseInt(group[i].value); - } - } - var style = 'even'; - if (document.the_form.pair_hands.checked) { - style = 'pairs'; - } - var patternString = document.getElementById('input_pattern').value; - var patternData = - computeFullPatternFromString(patternString, numHands, style); - startAnimation( - patternData.numBalls, - patternData.numHands, - patternData.duration, - patternData.ballCurveSets, - patternData.handCurveSets); - } catch (ex) { - popup(stringifyObj(ex)); - throw ex; - } - setUpSelection(); -} - -/** - * Removes any callbacks so they don't get called after the page has unloaded. - */ -function cleanup() { - g.client.cleanup(); -} - - -/** - * Dump out a newline-terminated string to the debug console, if available. - * @param {!string} s the string to output. - */ -function debug(s) { - o3djs.dump.dump(s + '\n'); -} - -/** - * Dump out a newline-terminated string to the debug console, if available, - * then display it via an alert. - * @param {!string} s the string to output. - */ -function popup(s) { - debug(s); - window.alert(s); -} - -/** - * If t, throw an exception. - * @param {!bool} t the value to test. - */ -function assert(t) { - if (!t) { - throw new Error('Assertion failed!'); - } -} - -/** - * Convert an object to a string containing a full one-level-deep property - * listing, with values. - * @param {!Object} o the object to convert. - * @return {!string} the converted object. - */ -function stringifyObj(o) { - var s = ''; - for (var i in o) { - s += i + ':' + o[i] + '\n'; - } - return s; -} - -/** - * Add the information in a curve to the params on a shape, such that the vertex - * shader will move the shape along the curve at times after timeBase. - * @param {!Curve} curve the curve the shape should follow. - * @param {!o3d.Shape} shape the shape being moved. - * @param {!number} timeBase the base to subtract from the current time when - * giving the curve calculation its time input. - */ -function setParamCurveInfo(curve, shape, timeBase) { - assert(curve); - assert(shape); - try { - shape.elements[0].getParam('time_base').value = timeBase; - shape.elements[0].getParam('coeff_a').value = - [curve.xEqn.a, curve.yEqn.a, curve.zEqn.a]; - shape.elements[0].getParam('coeff_b').value = - [curve.xEqn.b, curve.yEqn.b, curve.zEqn.b]; - shape.elements[0].getParam('coeff_c').value = - [curve.xEqn.c, curve.yEqn.c, curve.zEqn.c]; - shape.elements[0].getParam('coeff_d').value = - [curve.xEqn.d, curve.yEqn.d, curve.zEqn.d]; - shape.elements[0].getParam('coeff_e').value = - [curve.xEqn.e, curve.yEqn.e, curve.zEqn.e]; - shape.elements[0].getParam('coeff_f').value = - [curve.xEqn.f, curve.yEqn.f, curve.zEqn.f]; - - assert(curve.xEqn.lerpRate == curve.yEqn.lerpRate); - assert(curve.xEqn.lerpRate == curve.zEqn.lerpRate); - shape.elements[0].getParam('coeff_lerp').value = curve.xEqn.lerpRate; - if (curve.xEqn.lerpRate) { - shape.elements[0].getParam('coeff_l_a').value = - [curve.xEqn.lA, curve.yEqn.lA, curve.zEqn.lA]; - shape.elements[0].getParam('coeff_l_b').value = - [curve.xEqn.lB, curve.yEqn.lB, curve.zEqn.lB]; - shape.elements[0].getParam('coeff_l_c').value = - [curve.xEqn.lC, curve.yEqn.lC, curve.zEqn.lC]; - } - } catch (ex) { - debug(ex); - throw ex; - } -} - -/** - * Create the params that the shader expects on the supplied shape's first - * element. - * @param {!o3d.Shape} shape the shape on whose first element to create params. - */ -function createParams(shape) { - shape.elements[0].createParam('coeff_a', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_b', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_c', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_d', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_e', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_f', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_l_a', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_l_b', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_l_c', 'ParamFloat3').value = [0, 0, 0]; - shape.elements[0].createParam('coeff_lerp', 'ParamFloat').value = 0; - shape.elements[0].createParam('time_base', 'ParamFloat').value = 0; -} - -/** - * Adjust the number of ball shapes in g.pack. - * @param {!number} numBalls the number of balls desired. - */ -function setNumBalls(numBalls) { - for (var i = 0; i < g.ballShapes.length; ++i) { - g.pack.removeObject(g.ballShapes[i]); - g.client.root.removeShape(g.ballShapes[i]); - } - g.ballShapes = []; - - for (var i = 0; i < numBalls; ++i) { - var shape = o3djs.primitives.createSphere(g.pack, - getMaterial(5 * i), - 0.10, - 70, - 70); - shape.name = 'Ball ' + i; - - // generate the draw elements. - shape.createDrawElements(g.pack, null); - - // Now attach the sphere to the root of the scene graph. - g.client.root.addShape(shape); - - // Create the material params for the shader. - createParams(shape); - - g.ballShapes[i] = shape; - } -} - -/** - * Adjust the number of hand shapes in g.pack. - * @param {!number} numHands the number of hands desired. - */ -function setNumHands(numHands) { - g.counter.removeAllCallbacks(); - - for (var i = 0; i < g.handShapes.length; ++i) { - g.pack.removeObject(g.handShapes[i]); - g.client.root.removeShape(g.handShapes[i]); - } - g.handShapes = []; - - for (var i = 0; i < numHands; ++i) { - var shape = o3djs.primitives.createBox(g.pack, - getMaterial(3 * (i + 1)), - 0.25, - 0.05, - 0.25); - shape.name = 'Hand ' + i; - - // generate the draw elements. - shape.createDrawElements(g.pack, null); - - // Now attach the sphere to the root of the scene graph. - g.client.root.addShape(shape); - - // Create the material params for the shader. - createParams(shape); - - g.handShapes[i] = shape; - } -} - +// @@REWRITE(insert js-copyright) +// @@REWRITE(delete-start) +// Copyright 2009 Google Inc. All Rights Reserved +// @@REWRITE(delete-end) + +/** + * This file contains the top-level logic and o3d-related code for the siteswap + * animator. + */ + +o3djs.require('o3djs.rendergraph'); +o3djs.require('o3djs.math'); +o3djs.require('o3djs.primitives'); +o3djs.require('o3djs.dump'); + +// Global variables are all referenced via g, so that either interpreter can +// find them easily. +var g = {}; + +/** + * Creates a color based on an input index as a seed. + * @param {!number} index the seed value to select the color. + * @return {!Array.number} an [r g b a] color. + */ +function createColor(index) { + var N = 12; // Number of distinct colors. + var root3 = Math.sqrt(3); + var theta = 2 * Math.PI * index / N; + var sin = Math.sin(theta); + var cos = Math.cos(theta); + return [(1 / 3 + 2 / 3 * cos) + (1 / 3 - cos / 3 - sin / root3), + (1 / 3 - cos / 3 + sin / root3) + (1 / 3 + 2 / 3 * cos), + (1 / 3 - cos / 3 - sin / root3) + (1 / 3 - cos / 3 + sin / root3), + 1]; +} + +/** + * Creates a material, given the index as a seed to make it distinguishable. + * @param {number} index an integer used to create a distinctive color. + * @return {!o3d.Material} the material. + */ +function createMaterial(index) { + var material = g.pack.createObject('Material'); + + // Apply our effect to this material. The effect tells the 3D hardware + // which shader to use. + material.effect = g.effect; + + // Set the material's drawList + material.drawList = g.viewInfo.performanceDrawList; + + // This will create our quadColor parameter on the material. + g.effect.createUniformParameters(material); + + // Set up the individual parameters in our effect file. + + // Light position + var light_pos_param = material.getParam('light_pos'); + light_pos_param.value = [10, 10, 20]; + + // Phong components of the light source + var light_ambient_param = material.getParam('light_ambient'); + var light_diffuse_param = material.getParam('light_diffuse'); + var light_specular_param = material.getParam('light_specular'); + + // White ambient light + light_ambient_param.value = [0.04, 0.04, 0.04, 1]; + + light_diffuse_param.value = createColor(index); + // White specular light + light_specular_param.value = [0.5, 0.5, 0.5, 1]; + + // Shininess of the material (for specular lighting) + var shininess_param = material.getParam('shininess'); + shininess_param.value = 30.0; + + // Bind the counter's count to the input of the FunctionEval. + var paramTime = material.getParam('time'); + paramTime.bind(g.counter.getParam('count')); + + material.getParam('camera_pos').value = g.eye; + + return material; +} + +/** + * Gets a material from our cache, creating it if it's not yet been made. + * Uses index as a seed to make the material distinguishable. + * @param {number} index an integer used to create/fetch a distinctive color. + * @return {!o3d.Material} the material. + */ +function getMaterial(index) { + g.materials = g.materials || []; // See initStep2 for a comment. + if (!g.materials[index]) { + g.materials[index] = createMaterial(index); + } + return g.materials[index]; +} + +/** + * Initializes g.o3d. + * @param {Array} clientElements Array of o3d object elements. + */ +function initStep2(clientElements) { + // Initializes global variables and libraries. + window.g = g; + + // Used to tell whether we need to recompute our view on resize. + g.o3dWidth = -1; + g.o3dHeight = -1; + + // We create a different material for each color of object. + //g.materials = []; // TODO(ericu): If this is executed, we fail. Why? + + // We hold on to all the shapes here so that we can clean them up when we want + // to change patterns. + g.ballShapes = []; + g.handShapes = []; + + g.o3dElement = clientElements[0]; + g.o3d = g.o3dElement.o3d; + g.math = o3djs.math; + g.client = g.o3dElement.client; + + // Initialize client sample libraries. + o3djs.base.init(g.o3dElement); + + // Create a g.pack to manage our resources/assets + g.pack = g.client.createPack(); + + // Create the render graph for a view. + g.viewInfo = o3djs.rendergraph.createBasicView( + g.pack, + g.client.root, + g.client.renderGraphRoot); + + // Get the default context to hold view/projection matrices. + g.context = g.viewInfo.drawContext; + + // Load a simple effect from a textarea. + g.effect = g.pack.createObject('Effect'); + g.effect.loadFromFXString(document.getElementById('shader').value); + + // Eye-position: this is where our camera is located. + // Global because each material we create must also know where it is, so that + // the shader works properly. + g.eye = [1, 6, 10]; + + // Target, this is the point at which our camera is pointed. + var target = [0, 2, 0]; + + // Up-vector, this tells the camera which direction is 'up'. + // We define the positive y-direction to be up in this example. + var up = [0, 1, 0]; + + g.context.view = g.math.matrix4.lookAt(g.eye, target, up); + + // Make a SecondCounter to provide the time for our animation. + g.counter = g.pack.createObject('SecondCounter'); + g.counter.multiplier = 3; // Speed up time; this is in throws per second. + + // Generate the projection and viewProjection matrices based + // on the g.o3d plugin size by calling onResize(). + onResize(); + + // If we don't check the size of the client area every frame we don't get a + // chance to adjust the perspective matrix fast enough to keep up with the + // browser resizing us. + // TODO(ericu): Switch to using the resize event once it's checked in. + g.client.setRenderCallback(onResize); +} + +/** + * Stops or starts the animation based on the state of an html checkbox. + */ +function updateAnimating() { + var box = document.the_form.check_box; + g.counter.running = box.checked; +} + +/** + * Generates the projection matrix based on the size of the g.o3d plugin + * and calculates the view-projection matrix. + */ +function onResize() { + var newWidth = g.client.width; + var newHeight = g.client.height; + + if (newWidth != g.o3dWidth || newHeight != g.o3dHeight) { + debug('resizing'); + g.o3dWidth = newWidth; + g.o3dHeight = newHeight; + + // Create our projection matrix, with a vertical field of view of 45 degrees + // a near clipping plane of 0.1 and far clipping plane of 100. + g.context.projection = g.math.matrix4.perspective( + 45 * Math.PI / 180, + g.o3dWidth / g.o3dHeight, + 0.1, + 100); + } +} + +/** + * Computes and prepares animation of the pattern input via the html form. If + * the box is checked, this will immediately begin animation as well. + */ +function onComputePattern() { + try { + g.counter.removeAllCallbacks(); + var group = document.the_form.radio_group_hands; + var numHands = -1; + for (var i = 0; i < group.length; ++i) { + if (group[i].checked) { + numHands = parseInt(group[i].value); + } + } + var style = 'even'; + if (document.the_form.pair_hands.checked) { + style = 'pairs'; + } + var patternString = document.getElementById('input_pattern').value; + var patternData = + computeFullPatternFromString(patternString, numHands, style); + startAnimation( + patternData.numBalls, + patternData.numHands, + patternData.duration, + patternData.ballCurveSets, + patternData.handCurveSets); + } catch (ex) { + popup(stringifyObj(ex)); + throw ex; + } + setUpSelection(); +} + +/** + * Removes any callbacks so they don't get called after the page has unloaded. + */ +function cleanup() { + g.client.cleanup(); +} + + +/** + * Dump out a newline-terminated string to the debug console, if available. + * @param {!string} s the string to output. + */ +function debug(s) { + o3djs.dump.dump(s + '\n'); +} + +/** + * Dump out a newline-terminated string to the debug console, if available, + * then display it via an alert. + * @param {!string} s the string to output. + */ +function popup(s) { + debug(s); + window.alert(s); +} + +/** + * If t, throw an exception. + * @param {!bool} t the value to test. + */ +function assert(t) { + if (!t) { + throw new Error('Assertion failed!'); + } +} + +/** + * Convert an object to a string containing a full one-level-deep property + * listing, with values. + * @param {!Object} o the object to convert. + * @return {!string} the converted object. + */ +function stringifyObj(o) { + var s = ''; + for (var i in o) { + s += i + ':' + o[i] + '\n'; + } + return s; +} + +/** + * Add the information in a curve to the params on a shape, such that the vertex + * shader will move the shape along the curve at times after timeBase. + * @param {!Curve} curve the curve the shape should follow. + * @param {!o3d.Shape} shape the shape being moved. + * @param {!number} timeBase the base to subtract from the current time when + * giving the curve calculation its time input. + */ +function setParamCurveInfo(curve, shape, timeBase) { + assert(curve); + assert(shape); + try { + shape.elements[0].getParam('time_base').value = timeBase; + shape.elements[0].getParam('coeff_a').value = + [curve.xEqn.a, curve.yEqn.a, curve.zEqn.a]; + shape.elements[0].getParam('coeff_b').value = + [curve.xEqn.b, curve.yEqn.b, curve.zEqn.b]; + shape.elements[0].getParam('coeff_c').value = + [curve.xEqn.c, curve.yEqn.c, curve.zEqn.c]; + shape.elements[0].getParam('coeff_d').value = + [curve.xEqn.d, curve.yEqn.d, curve.zEqn.d]; + shape.elements[0].getParam('coeff_e').value = + [curve.xEqn.e, curve.yEqn.e, curve.zEqn.e]; + shape.elements[0].getParam('coeff_f').value = + [curve.xEqn.f, curve.yEqn.f, curve.zEqn.f]; + + assert(curve.xEqn.lerpRate == curve.yEqn.lerpRate); + assert(curve.xEqn.lerpRate == curve.zEqn.lerpRate); + shape.elements[0].getParam('coeff_lerp').value = curve.xEqn.lerpRate; + if (curve.xEqn.lerpRate) { + shape.elements[0].getParam('coeff_l_a').value = + [curve.xEqn.lA, curve.yEqn.lA, curve.zEqn.lA]; + shape.elements[0].getParam('coeff_l_b').value = + [curve.xEqn.lB, curve.yEqn.lB, curve.zEqn.lB]; + shape.elements[0].getParam('coeff_l_c').value = + [curve.xEqn.lC, curve.yEqn.lC, curve.zEqn.lC]; + } + } catch (ex) { + debug(ex); + throw ex; + } +} + +/** + * Create the params that the shader expects on the supplied shape's first + * element. + * @param {!o3d.Shape} shape the shape on whose first element to create params. + */ +function createParams(shape) { + shape.elements[0].createParam('coeff_a', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_b', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_c', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_d', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_e', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_f', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_l_a', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_l_b', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_l_c', 'ParamFloat3').value = [0, 0, 0]; + shape.elements[0].createParam('coeff_lerp', 'ParamFloat').value = 0; + shape.elements[0].createParam('time_base', 'ParamFloat').value = 0; +} + +/** + * Adjust the number of ball shapes in g.pack. + * @param {!number} numBalls the number of balls desired. + */ +function setNumBalls(numBalls) { + for (var i = 0; i < g.ballShapes.length; ++i) { + g.pack.removeObject(g.ballShapes[i]); + g.client.root.removeShape(g.ballShapes[i]); + } + g.ballShapes = []; + + for (var i = 0; i < numBalls; ++i) { + var shape = o3djs.primitives.createSphere(g.pack, + getMaterial(5 * i), + 0.10, + 70, + 70); + shape.name = 'Ball ' + i; + + // generate the draw elements. + shape.createDrawElements(g.pack, null); + + // Now attach the sphere to the root of the scene graph. + g.client.root.addShape(shape); + + // Create the material params for the shader. + createParams(shape); + + g.ballShapes[i] = shape; + } +} + +/** + * Adjust the number of hand shapes in g.pack. + * @param {!number} numHands the number of hands desired. + */ +function setNumHands(numHands) { + g.counter.removeAllCallbacks(); + + for (var i = 0; i < g.handShapes.length; ++i) { + g.pack.removeObject(g.handShapes[i]); + g.client.root.removeShape(g.handShapes[i]); + } + g.handShapes = []; + + for (var i = 0; i < numHands; ++i) { + var shape = o3djs.primitives.createBox(g.pack, + getMaterial(3 * (i + 1)), + 0.25, + 0.05, + 0.25); + shape.name = 'Hand ' + i; + + // generate the draw elements. + shape.createDrawElements(g.pack, null); + + // Now attach the sphere to the root of the scene graph. + g.client.root.addShape(shape); + + // Create the material params for the shader. + createParams(shape); + + g.handShapes[i] = shape; + } +} + -- cgit v1.1