Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit 580ad62e330d8672923d0303d9578146f3eed50a @ChristophPacher committed Nov 2, 2010
212 Documentation.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Untitled Document</title>
+<link href="style/style.css" rel="stylesheet" type="text/css" /></head>
+
+<body>
+<h1>Javascript Physics Libary Documentation</h1>
+<h2>Features and Notes:</h2>
+<ul>
+ <li>Axis oriented bounding box collision dection (AABB) and a sweep version of the algorithm for collision dection between fast objects (change this.collAABB(p1, p2) to this.collAABBsweep(p1, p2) in PhysicsWorld.js in line 97 to switch between the two)
+ </li><br />
+ <li>Second order Runge Kutta numerical integration for smaller intergration errors and more stable spring forces
+ </li><br />
+ <li>Boxes can take engery out of a collision if mRestitutionN &gt; 0. If this value is larger than 0.0 boxes will have a hard time to come to rest on top of each other.
+ </li>
+ <br />
+ <li>The Boxes are influence by air drag. The drag coefficient mDragN is computed inscientifically (proportionally to box widht and height) by updateDrag() after every scale update of the box. You can adapt this to your liking. </li><br />
+ <li>The Boxes are influenced by contact friction proportionaly to the collision velocity and mFrictionN
+ </li><br />
+ <li>It is possible to apply an impulse on a particle by using particle.mExternalImpulsesA.push([x,y,z]);
+ </li>
+ <br />
+ <li>To use scriptaculous with the physics engine I needed to adapt the core effects scale and move, so they interact with the engine. I extended them to MoveP and ScaleP, you find them in EffectsParticle.js. ScaleP can scale only around the center of the particle. All combined effects can now easily be adapted to the pysics engine, by using this two extended core effects, see ShakeP for an example.
+ </li><br />
+ <li>MoveP ensures that the particle ends up at the coordinates as defined in the options of the effect. MoveImP applies impuleses. The vector defined by the options divided by the change variable of the move effect, to be exact. So there is an ease in and out in the strenght of the impulese applied over the time of the effect. The particle is still influenced by forces and collisions.
+ </li>
+ <br />
+ <li>Boxes can be dragged and dropped. Mouse screen coordianetes can be transformed back into 3D, so drag and drop works with the 2D and 3D renderer. See the demo implementation and the code snippeds in the getting started tutrorial, for more information. </li>
+ <br />
+ <li>Particles and/ or theircollisions can be killed with world.killParticle(pP) and particle.killCollisions()</li>
+ <br />
+ <li>I optimized the engine by using simple arrays for vectors and and performing vector math directly with the array contents. Every function and class call is costly in javascript, when there are several thousand per second. By this, I could reduce the step time of the simulation of a 6 story pyramid from 90ms to 17ms on my Core2 Duo 2.4 GHz laptop in firefox.
+ </li>
+ <br />
+ <li>The projection and drawing of the boxes is encapsuled in the renderer classes. R2D has no perspective projection and the origin is by default in the lower left corner of the viewport. R3D has perspective projection and the origin is in the middel of the view port. In both renderers the camera can be moved. There is no rotation or rolling implemented. In the demo app the camera can be moved with the keys a,w,s,d und cursor up and down.
+ </li><br />
+ <li>There are several types of additional forces, which can be applied to selected particles. Every forces can be turned on and off. There are general Omi Forces like wind and Omni forces that are mass indipendent like gravity. One instance of these can affect several particles. A particle can always be remove from or added to an Omni force. Moreover there are Attraction/Repell and Spring forces, one instance for every force between two particles. In the demo i only implemented the possibility to add spring/repell forces between particle p20 and all other particles. </li>
+ <br />
+ <li>Boxes fall asleep to save cpu cycles. The sleep algorithm was spifically adapted to work with all types for forces as well as drag and drop. Collisions still need to be calculated. (the box color is changed from red to green in the renderers draw function)
+ </li> <br />
+ <li>Internet Explorer needs a catch block between try /finally blocks. I added one in Prototype's PeriodicalExecuter Class in the onTimerEvent function
+ </li><br />
+ <li>I adapted scriptaculous with 3 lines of code, so that no frames are skipped when the browser lags behind. Frame skips in animations would be seen by the physics engine as a larger impulse, as the engine runs by fixed time steps and does not skip frames, to prevent the simulation from exploding. Large Impulses would result in larger velocities, which would be different from the intended animation. See timePos = this.MyOldPos + 15; in effects.js line 278 for the changes. You need to adapt every new scriptaculous version if you want to upgrade, or you just ignore it if it does not bother you.
+ </li>
+ <br />
+</ul>
+<h2>Getting Started:</h2>
+<p>The follwing Guide describes the Code of the demo app. </p>
+<h3>Includes:</h3>
+<p>You have to put the following includes in the head of your html file. Be sure that the includes point to the position where you placed the folders with the js files. </p>
+<pre>
+&lt;script src=&quot;prototype/prototype.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;scriptaculous/scriptaculous.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;scriptaculous/unittest.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/Vector.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/PhysicsWorld.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/PhysicsForce.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/PhysicsParticle.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/PhysicsCollision.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/EffectsParticle.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;physics/Renderer.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;MyUtilities.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+</pre>
+<h3>Viewport and Renderer:</h3>
+<p>First get the dimensions of the browser window:</p>
+<pre>var windowDim = MyUtilities.getWindowDimensions();
+</pre>
+<p>Create a new Renderer (R3D or R2D), pass the window dimensions and define the viewport inside the window:</p>
+<pre>var myRenderer = new Renderer.R3D(windowDim, [50, 50], [windowDim.width -100 , windowDim.height- 100]);</pre>
+<p>[50, 50] defines the upper left corner of the viewport in browser coordinates (in px, origin is the upper left of the browser window, y+ points down).</p>
+<p>You can set and animate the position of the camera by setting its position vector:</p>
+<pre>myRenderer.mCameraPosV3D = [1,2,3];</pre>
+<h3>World</h3>
+<p>Its time to create your world and pass the renderer you just created:</p>
+<pre>var myWorld = new Physics.World3D(myRenderer);</pre>
+<p>Then define the bounds of the world. Parcticles collide with these. Default is min[0,0,0], max [100,100,100]</p>
+<pre>myWorld.mBoundsMinV3D = [0, 0, 0];
+myWorld.mBoundsMaxV3D = [10, 10, 10];</pre>
+<p>You can start and stop the simulation with:</p>
+<pre>myWorld.start();
+myWorld.stop();
+myWorld.toggleStartStop();</pre>
+<p>But you have not created any particles yet. </p>
+<h3>Particles/boxes</h3>
+<p>A box can be an HTML element like a &lt;div&gt; or &lt;img&gt;. This element can already exist in your HTML document. You then just need the ID of the element </p>
+<pre>var element = document.getElementById("myID");</pre>
+<p>and add some costom style variables that are needed for the scaling of the text. Those should be the the font values of your text when the particle's scale is 1.0: </p>
+<pre>
+element.startFontSize = 16;
+element.fontSizeType = "px";
+</pre>
+<p> If you have done this, you can skip the next steps and continue with the paragraph where you append the element to the body and create the particle.</p>
+<p>Otherwise you can create the element on the fly, like i do it in the demo:</p>
+<pre> var element = document.createElement('div');
+ </pre>
+<p>You can write content insde the element by writing to the innerHtml node: </p>
+<pre>element.innerHTML = ' p' + myWorld.mParticlesA.length + &quot;Lorem ipsum dolor sit amet, consetetur sadipscing elitr&quot;;
+</pre>
+<p>Then set the style of the text, as well as extra information for the modified scale effect ScaleP (dont forget to set these if you created your div with normal HTML)</p>
+<pre>element.style.fontSize = "16px";
+
+// custom style entries needed to correctly scale
+// the content of the div
+element.startFontSize = 16;
+element.fontSizeType = "px";
+
+element.id = 'p' + myWorld.mParticlesA.length;
+element.style.position = 'absolute';
+element.style.width = BoxWidth + 'px';
+element.style.height = BoxWidth + 'px';
+element.style.backgroundColor = '#ddd';
+element.style.MozUserSelect="none";
+</pre>
+<p>Now add the element to the document body and use the id of the element to create a new particle in the engine, that uses the element as its sprite. The Html element, also needs a reference to the particle in the engine:</p>
+<pre>document.body.appendChild(element);
+//position in meters, box extensions from postion in m, mass in kg, start velocity in m/s2, the representing DOM element
+element.particle = myWorld.addParticle([posx, posy, posz], [BoxWidth * myRenderer.px2m /2, BoxWidth * myRenderer.px2m /2, BoxWidth * myRenderer.px2m /2], 1, [0,0,0], element);</pre>
+<p>Set the restitution and friction of the box to your liking: </p>
+<pre>element.particle.mRestitutionN = 0.0;
+element.particle.mFrictionN = 0.5;</pre>
+<p>If you want to set manulally a new scale of the particle do it by setting:</p>
+<pre>element.particle.mNewScaleN = 0.5;
+</pre>
+<p>The particle will then be scaled by the engine and updateDrag() is automatically called. </p>
+<p>You can kill a particle by using:</p>
+<pre>myWorld.killParticle(p);
+</pre>
+<p>All it's refences to collisions and forces will be deleted too. </p>
+<h3>Forces:</h3>
+<p>Lets start with the general forces like wind and gravity, that affect several particles all in the same way. There is only one difference between them, as the acceleration of gravity is independent from the the mass of the particle. These two forces are implemented as Omni and OmniMassInd: </p>
+<pre>
+// parameters: id, world, [affectedParticleN, .....], acceleration vector
+var wind = new Physics.Omni('wind', myWorld, MyUtilities.copyArray(myWorld.mParticlesA), [2, 0, 0]);
+var grav = new Physics.OmniMassInd('grav', myWorld, MyUtilities.copyArray(myWorld.mParticlesA), [0, -9.8, 0]</pre>
+<p>After the creation of an Omni force you can remove or add a particle by using:</p>
+<pre>wind.removeParticle(p);
+wind.addParticle(p);</pre>
+<p>Then there are more specific forces that exist between two particles. </p>
+<p>The first one is Attraction, which can repell too, if its' strength is set negative. You have to make sure that the particles are awake, for the force to work instantly:</p>
+<pre>// parameters:
+// the id string of the force
+// the world
+// the affected particle
+// the source particle
+// is the source attracted to the other particle
+// the strength of the attraction, repells when negative
+// the min
+// and max distance (m) between the particles for the force to work in
+new Physics.Attraction('attrP20', myWorld, p, myWorld.mParticlesA[20], true, -10, 0.5, 3);
+p.setAwake(true);
+myWorld.mParticlesA[20].setAwake(true);</pre>
+<p>Then there are Spring forces, that push and pull the connected particles, until the rest lenght of the spring is reached:</p>
+<pre>// parameters:
+// the id string of the force
+// the world
+// the affected particle
+// the source particle
+// is the source pulled to the other particle by the spring
+// the spring constant, the larger, the stiffer
+// the restlenght of the spring in m
+// the damping, how much kinetic energy is lost because of stretching and compressing of the spring, controls how fast the particles come to rest
+new Physics.Spring('sprP20', myWorld, p, myWorld.mParticlesA[20], true, 7, 2, 0.2);
+p.setAwake(true);
+myWorld.mParticlesA[20].setAwake(true);</pre>
+<p>You can set all forces on and off and kill forces by using e.g.:</p>
+<pre>wind.setOn(false);
+wind.setOn(true);
+myWorld.killForce(wind);</pre>
+<p>You can give every particle an impluse, an instant acceleration, by pushing an acceleration vector on the mExternalImpulsesA array. The vector will be taken from the stack in the next simulation step. You have to make sure, that the particle is awake, otherwise the impulse will not affect the particle:</p>
+<pre>p.setAwake(true);
+p.mExternalImpulsesA.push([0, 0, 0.5]);</pre>
+<h3>Effects:</h3>
+<p>As I already mentioned in the notes, all scriptaculous effects need to be adapted before they can be used with the physics engine. I already did this, for Scale, Move and Shake. All combined effects that use Move and Scale can be easily adapted. I will go through the process on the example of the effect Puff. </p>
+<p>First I copied over the effect declaration of Puff from scriptaculous/effects.js to physics/EffectsParticle.js and renamed the class to Effect.PuffP. The effect creates interanally two effects that run in sync: Effect.Scale and Effect.Opacity. All that has to be done is to rename Effect.Scale to Effect.ScaleP and we are finished, as Effect.Opacity did not need to be adapted to the engine. The result looks like this:</p>
+<pre>Effect.PuffP = function(element) {
+ element = $(element);
+ var oldStyle = {
+ opacity: element.getInlineOpacity(),
+ position: element.getStyle('position'),
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height
+ };
+ return new Effect.Parallel(
+ // changes: replaces Scale by ScaleP
+ [ new Effect.ScaleP(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ Position.absolutize(effect.effects[0].element)
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().setStyle(oldStyle); }
+ }, arguments[1] || { })
+ );
+};</pre>
+<p>You can now for instance use the effect before you kill a particle, like i do it in the demo:</p>
+<pre>new Effect.PuffP(elem, {duration:0.7, fps: 20, afterFinish: function(effect) {
+ effect.effects[0].mParticleP.mWorldW3D.killParticle(effect.effects[0].mParticleP);
+}});</pre>
+<p>Check the demo app for more examples how to use the already adapted effects. You should then have no problems creating your own effects. </p>
+<p>For more information about the handling of effects, like their options or how to queu or sync them, check the scriptaculous help. </p>
+<h3>Drag and Drop:</h3>
+<p>Drag and Drop in combination with normal clicks on particles and a working wakeup and sleeping system, is a little tricky. It's best you take a long look at the implementation of the demo app. I only describe some key points of my implementation, the rest should be obvious from my code.</p>
+<p>First you need two different moveHandler functions that get the mouse coordinates, one for IE and one for all the other browsers.</p>
+<p>Most of the magic happens in the mouseUpdater function, which is called periodically. There I check if the mousebutton has been held down on a particle for longer than 2 engine steps. Only then I set the flag, that the particle is dragged. If it is dragged, I acitvate all flags, so that the engine no more computes forces, collisionsImpulses, does not integrate the velocity and position and that it is controlled externally (by the user). Then there is a special case, if the particle has spring or attraction forces. Then all the connected particles need to be woke up periodically, because the code that would wake them up otherwise, located in the force calculation during integration is not called when the particle is dragged. From then on you just have to position the particle according to the mouse coordinates. The myRenderer.screen2world() function nicely projects the mouse screen coordinates back into the 3D simulation space</p>
+</body>
+</html>
34 MyUtilities.js
@@ -0,0 +1,34 @@
+// MyUtillities.js
+// Copyright (c) Christoph Pacher (http://www.christophpacher.com)
+
+var MyUtilities = {
+ getWindowDimensions: function() {
+ var myWidth = myHeight = 0;
+ if( typeof( window.innerWidth ) == 'number' ) {
+ //Non-IE
+ myWidth = window.innerWidth;
+ myHeight = window.innerHeight;
+ } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
+ //IE 6+ in 'standards compliant mode'
+ myWidth = document.documentElement.clientWidth;
+ myHeight = document.documentElement.clientHeight;
+ } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
+ //IE 4 compatible
+ myWidth = document.body.clientWidth;
+ myHeight = document.body.clientHeight;
+ }
+ return {width: myWidth, height: myHeight};
+ },
+ clamp: function(a, low, high){
+ return Math.max(low, Math.min(a, high));
+ },
+ copyArray: function(a){
+ newA = new Array();
+ var l = a.length;
+ for (var i = 0; i < l; i++)
+ {
+ newA[i] = a[i];
+ }
+ return newA;
+ }
+};
BIN images/128down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/128up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/16down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/16up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/1down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/1up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/256down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/256up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/2down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/2up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/32down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/32up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/4down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/4up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/64down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/64up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/8down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/8up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
540 index.html
@@ -0,0 +1,540 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>JavaScript Physics Libary Test by Christoph Pacher </title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/prototype/1.6.1/prototype.js'></script>
+ <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'></script>
+ <script type='text/javascript'>try{jQuery.noConflict();}catch(e){};</script>
+ <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.3/scriptaculous.js'></script>
+ <script src="jQuery/jquery.batchImageLoad.js" type="text/javascript"></script>
+ <script src="physics/Vector.js" type="text/javascript"></script>
+ <script src="physics/PhysicsWorld.js" type="text/javascript"></script>
+ <script src="physics/PhysicsForce.js" type="text/javascript"></script>
+ <script src="physics/PhysicsParticle.js" type="text/javascript"></script>
+ <script src="physics/PhysicsCollision.js" type="text/javascript"></script>
+ <script src="physics/EffectsParticle.js" type="text/javascript"></script>
+ <script src="physics/Renderer.js" type="text/javascript"></script>
+ <script src="MyUtilities.js" type="text/javascript"></script>
+ <link rel="stylesheet" type="text/css" media="screen" href="style/style.css" />
+
+
+ <script type="text/javascript" charset="utf-8">
+ Position.includeScrollOffsets = true;
+ </script>
+
+<body style="background-color:black;user-select: none; -khtml-user-select: none; -moz-user-select: none;">
+<h1 style="position:absolute;left:0px;top:0px;z-index:2000;color:silver;">javascript physics libary test</h1>
+
+<div id="rahmen" style="position:absolute; top:50px; left:50px; bottom:50px; right:50px;background-color:white;">
+ <div id="start" class="button"> start/stop </div>
+ <div id="wind" class="button"> wind on/off </div>
+ <div id="grav" class="button"> gravity on/off </div>
+ <div id="kill" class="button2"> kill box </div>
+ <div id="scale" class="button2"> scale box </div>
+ <div id="shake" class="button2"> shake box </div>
+ <div id="move" class="button2"> move box </div>
+ <div id="move2" class="button2"> moveImp box </div>
+ <div id="z" class="button2"> zImpulse box </div>
+ <div id="new" class="button2"> new box </div>
+ <div id="remWind" class="button2"> removeWind box </div>
+ <div id="attr" class="button2"> attract to p20 </div>
+ <div id="spring" class="button2"> spring to p20 </div>
+ <div id="killF" class="button2"> kill Forces to p20 </div>
+
+ <div id="debugOut1" class="debugBox">debugout1</div>
+ <div id="debugOut2" class="debugBox">debugout2</div>
+</div>
+
+<script type="text/javascript">
+var matv = V3DP;
+
+var windowDim = MyUtilities.getWindowDimensions();
+
+viewportPadding = [50, 50];
+viewportOrigin = [viewportPadding[0], viewportPadding[1]];
+viewportDim = [windowDim.width - 2*this.viewportPadding[0], windowDim.height - 2*viewportPadding[1]];
+
+
+// tell the renderer the dimensions of the browser window,
+// the viewport origin (upper left corner) and viewport dimensions. all in px
+var myRenderer = new Renderer.R3D(windowDim, viewportOrigin, viewportDim);
+var myWorld = new Physics.World3D(myRenderer);
+
+// the bounds inside the particle world in meters
+this.myWorld.mBoundsMinV3D = [0, 0, 0];
+// let the world end with the sides of the viewport
+// this requires the camera to be positioned like i do it in the next step
+this.myWorld.mBoundsMaxV3D = [this.viewportDim[0] * this.myRenderer.px2m, this.viewportDim[1] * 2 * this.myRenderer.px2m, 10];
+
+
+// if you use the line drawing feature, tell the renderer where the line images are
+Renderer.mLineImgPathS = "images/";
+
+// Position the camera so that the worldspace coords [0,0,0] are in the lower left corner
+// of the viewport.
+this.myRenderer.mCameraPosV3D = [this.viewportDim[0] / 2 * this.myRenderer.px2m, this.viewportDim[1] / 2 * this.myRenderer.px2m, 0];
+
+
+var pyramidLevels = 8;
+var boxwidthPX = 40;
+
+
+buildPyramid( 4, [this.viewportDim[0] / 2 * this.myRenderer.px2m -5, 1, 1], boxwidthPX );
+buildPyramid( 4, [this.viewportDim[0] / 2 * this.myRenderer.px2m -5, 1, 2.2], boxwidthPX );
+buildPyramid( 4, [this.viewportDim[0] / 2 * this.myRenderer.px2m -5, 1, 3.2], boxwidthPX );
+
+
+this.sp = new Physics.Spring('sp1', this.myWorld, this.myWorld.mParticlesA[8], this.myWorld.mParticlesA[9], true, 50, 2, 0.2, true);
+this.myWorld.addParticleLine(this.myWorld.mParticlesA[8], this.myWorld.mParticlesA[9]);
+
+
+
+// you can add froces that act on a set of particles e.g. wind (but there is no shadowing)
+// in meters per second squared
+var wind = new Physics.Omni('wind', myWorld, MyUtilities.copyArray(myWorld.mParticlesA), [2, 0, 0]);
+wind.setOn(false);
+var grav = new Physics.OmniMassInd('grav', myWorld, MyUtilities.copyArray(myWorld.mParticlesA), [0, -9.8, 0]);
+
+var selectedFunction = "scale";
+var oldElem = document.getElementById("scale");
+oldElem.style.backgroundColor = "red";
+document.getElementById("grav").style.backgroundColor = "red";
+document.getElementById("grav").clicked = true;
+
+var mousePos = [0,0];
+var mouseDt = 0.05;
+var mouseIDt = 1.0/mouseDt;
+var mousePeriodical = new PeriodicalExecuter(this.mouseUpdater.bind(this), mouseDt);
+var wakeUpPeriodical = null;
+var toWakeUpPA = [];
+var isThisIE = document.all?true:false;
+var draggedB = false;
+var draggedObj = null;
+var markedObj = null
+var startDownN = 0;
+var downB = false;
+if (isThisIE) {
+ document.observe('mousemove' , moveHandlerIE.bindAsEventListener(this));
+}
+else
+{
+ document.observe('mousemove' , moveHandler.bindAsEventListener(this));
+}
+document.observe('mousedown' , downHandler.bindAsEventListener(this));
+document.observe('mouseup' , upHandler.bindAsEventListener(this));
+document.observe('click' , clickHandler.bindAsEventListener(this));
+document.observe('keydown' , keyDownHandler.bindAsEventListener(this));
+
+
+myWorld.start();
+
+
+function wakeUpHandler(){
+ var l = toWakeUpPA.length;
+ var i = 0;
+ var p = null;
+ for (i = 0; i < l; i++)
+ {
+ p = toWakeUpPA[i];
+ if (p.mSleepsB && p.mNumN != -1) p.setAwake(true);
+ }
+}
+function buildPyramid( pyrHeight, startPos, BoxWidthPx){
+ var BoxWidth = BoxWidthPx * myRenderer.px2m;
+ var separation = BoxWidth/(8.0);
+
+ var pos2 = [0,0,0];
+ var element = null;
+ for (var i = 0; i < pyrHeight; i++)
+ {
+ matv.copyDirect(pos2, startPos);
+ for (var j = i; j < pyrHeight; j++)
+ {
+ var elem = null;
+ elem = newBox('div', ' ', pos2[0], pos2[1], pos2[2], BoxWidthPx);
+ matv.addDirect(pos2, [BoxWidth + separation, 0, 0]);
+ }
+ matv.addDirect(startPos, [(BoxWidth + separation)/2.0, BoxWidth * 2, 0]);
+
+ }
+}
+function newBox(type, content, posx, posy, posz, BoxWidth ){
+
+ element = document.createElement('div');
+ element.innerHTML = ' p' + myWorld.mParticlesA.length;
+ element.innerHTML += content;
+
+ element.id = 'p' + myWorld.mParticlesA.length;
+ jQuery(element).addClass("box");
+ element.style.width = BoxWidth + 'px';
+ element.style.height = BoxWidth + 'px';
+ element.style.MozUserSelect="none";
+ document.body.appendChild(element);
+
+ // The DOM Element we use as our Particle Sprite has css dimensions other than
+ // width and height (eg. fontsize, padding, margin etc) and / or sub elements
+ // (eg. images, divs) that need to be scaled too
+ element.scaleContent = true;
+
+ // we need to save all the css dimensions in their 100% scale state
+ this.myRenderer.saveStartDimensions(element, 0, element);
+
+ //position in meters, box extensions from pos in m, mass in kg, start velocity in m/s2, the representing DOM element
+ element.particle = myWorld.addParticle([posx, posy, posz], [BoxWidth * myRenderer.px2m /2, BoxWidth * myRenderer.px2m /2, BoxWidth * myRenderer.px2m /2], 1, [0,0,0], element);
+ // controls how much of the collision velocitiy is reflected
+ // stacks of blocks will not rest if larger than 0
+ element.particle.mRestitutionN = 0.0;
+ // controls the contact friction
+ element.particle.mFrictionN = 0.5;
+ return element;
+}
+function wakeUpAll(){
+ var l = myWorld.mParticlesA.length;
+ var p1 = null;
+ for (var i = 0; i < l; i++)
+ {
+ p1 = myWorld.mParticlesA[i];
+ if (p1 == null) continue;
+ p1.setAwake(true);
+ }
+}
+function clickHandler(e) {
+ if(draggedB){
+ draggedB = false;
+ return;
+ }
+ var elem = e.element();
+ var id = elem.id;
+ if(elem.id == oldElem.id) return;
+ switch(id)
+ {
+ case "start":
+ myWorld.toggleStartStop();
+ if (elem.clicked)
+ {
+ elem.style.backgroundColor = '#666666';
+ elem.clicked = false;
+ }
+ else
+ {
+ elem.style.backgroundColor = "red";
+ elem.clicked = true;
+ }
+ break;
+ case "wind":
+ if (elem.clicked)
+ {
+ elem.style.backgroundColor = "#666666";
+ wind.setOn(false);
+ }
+ else
+ {
+ elem.style.backgroundColor = "red";
+ wind.setOn(true);
+ }
+ elem.clicked = !elem.clicked;
+ wakeUpAll();
+ break;
+ case "grav":
+ if (elem.clicked)
+ {
+ elem.style.backgroundColor = "#666666";
+ grav.setOn(false);
+ }
+ else
+ {
+ elem.style.backgroundColor = "red";
+ grav.setOn(true);
+ }
+ elem.clicked = !elem.clicked;
+ wakeUpAll();
+ break;
+ case "killF":
+ var l = myWorld.mForcesA.length;
+ var f = null;
+ for (var i = 0; i < l; i++)
+ {
+ f = myWorld.mForcesA[i];
+ if (f)
+ {
+ if (f.mTypeN == 3 || f.mTypeN == 4 )
+ {
+ f.mAffectedP.mCanSleepB = true;
+ f.mSourceP.mCanSleepB = true;
+ myWorld.killForce(f);
+ }
+ }
+ }
+ elem.style.backgroundColor = "red";
+ elem.style.backgroundColor = "#666666";
+ break;
+ case "attr":
+ case "kill":
+ case "new":
+ case "move":
+ case "move2":
+ case "z":
+ case "scale":
+ case "spring":
+ case "shake":
+ case "remWind":
+ oldElem.style.backgroundColor = "#666666";
+ elem.style.backgroundColor = "red";
+ oldElem = elem;
+ selectedFunction = id;
+ break;
+ default:
+ p = elem.particle;
+ // the clicked element has a particle and the simulation is running
+ if (p && p.mWorldW3D.mPeriodical)
+ {
+ switch(selectedFunction)
+ {
+ case "attr":
+ if (p.mNumN != 20)
+ {
+ // last 4 options are: is the source attracted to the other particle
+ // the strength of the attraction
+ // the min and max distance (m) between the particles, for the force to work in
+ new Physics.Attraction('attrP20', myWorld, p, myWorld.mParticlesA[20], true, -10, 0.5, 3);
+ p.setAwake(true);
+ myWorld.mParticlesA[20].setAwake(true);
+ }
+ break;
+ case "spring":
+ if (p.mNumN != 20)
+ {
+ // last 4 options are: is the source pulled to the other particle by the spring
+ // the spring constant
+ // the restlenght of the spring in m
+ // the damping
+ new Physics.Spring('sprP20', myWorld, p, myWorld.mParticlesA[20], true, 7, 2, 0.2);
+ p.setAwake(true);
+ myWorld.mParticlesA[20].setAwake(true);
+ }
+ break;
+ case "kill":
+ new Effect.PuffP(elem, {duration:0.7, fps: 20, afterFinish: function(effect) {
+ effect.effects[0].mParticleP.mWorldW3D.killParticle(effect.effects[0].mParticleP);
+ }});
+
+ break;
+ case "remWind":
+ wind.removeParticle(p);
+ break;
+ case "z":
+ p.setAwake(true);
+ p.mExternalImpulsesA.push([0, 0, 0.5]);
+ break;
+ case "move":
+ p.mNoForcesB = true;
+ p.mNoCollImpB = true;
+ p.mNoIntegrationB = true;
+ p.mExControlledB = true;
+
+ new Effect.MoveP(elem, {x : 40, y : 0, duration:1, fps: 20, afterFinish: function(effect) {
+ effect.mParticleP.mNoForcesB = false;
+ effect.mParticleP.mNoCollImpB = false;
+ effect.mParticleP.mNoIntegrationB = false;
+ effect.mParticleP.mExControlledB = false;
+ }});
+ break;
+ case "scale":
+ if(elem.myCLicked)
+ {
+ new Effect.ScaleP(elem, 25, {duration:0.01});
+ elem.myCLicked = false;
+ }
+ else
+ {
+ new Effect.ScaleP(elem, 400, {duration:0.01});
+ elem.myCLicked = true;
+ }
+ break;
+ case "shake":
+ p.mNoForcesB = true;
+ p.mNoCollImpB = true;
+ p.mNoIntegrationB = true;
+ p.mExControlledB = true;
+ //p.killCollisions();
+ new Effect.ShakeP(elem.id, {distance: 20, duration:1, fps: 20, afterFinish: function(effect) {
+ effect.mParticleP.mNoForcesB = false;
+ effect.mParticleP.mNoCollImpB = false;
+ effect.mParticleP.mNoIntegrationB = false;
+ effect.mParticleP.mExControlledB = false;
+ }});
+ break;
+ case "move2":
+ //p.mNoForcesB = true;
+ //p.mNoCollImpB = true;
+ p.mNoIntegrationB = false;
+ //p.mExControlledB = true;
+ //p.killCollisions();
+ new Effect.MoveImP(elem.id, {x : 40, y : 0, duration:1, fps: 20, afterFinish: function(effect) {
+ var stepTime = effect.mParticleP.mWorldW3D.mDeltaTimeN;
+ var leftImpulses = effect.mParticleP.mExternalImpulsesA.length;
+ // the effect can finish before all impulses from the effect have been applied
+ // to the particle by the engine. so we wait a litte before we
+ // apply forces and collisions again on the particle
+ setTimeout(function (){
+ //effect.mParticleP.mNoForcesB = false;
+ effect.mParticleP.mNoCollImpB = false;
+ //effect.mParticleP.mExControlledB = false;
+ }.bind(this), stepTime * 1000 * ( leftImpulses+ 1) );
+
+ }});
+ break;
+ }
+ }
+ else if(selectedFunction == "new" &&
+ mousePos[0] > myRenderer.mViewPortOriginV2D[0] &&
+ mousePos[1] > myRenderer.mViewPortOriginV2D[1] &&
+ mousePos[0] < myRenderer.mViewPortOriginV2D[0] + myRenderer.mViewPortDimV2D[0] &&
+ mousePos[1] < myRenderer.mViewPortOriginV2D[1] + myRenderer.mViewPortDimV2D[1])
+ {
+ var pos = myRenderer.screen2world([
+ mousePos[0],
+ mousePos[1], 1]);
+
+ var newElem = newBox('div', '',pos[0] ,pos[1],pos[2], boxwidthPX );
+ wind.addParticle(newElem.particle);
+ grav.addParticle(newElem.particle);
+ }
+ break;
+ }
+}
+function moveHandlerIE(e){
+ mousePos[0] = e.clientX;
+ mousePos[1] = e.clientY;
+}
+function moveHandler(e){
+ mousePos[0] = e.pageX;
+ mousePos[1] = e.pageY;
+}
+function mouseUpdater(){
+ if (downB)
+ {
+ if (startDownN == 0)
+ {
+ startDownN = myWorld.mStepN;
+ }
+ else if (myWorld.mStepN - startDownN > 2)
+ {
+ if (draggedObj)
+ {
+ var p = draggedObj.particle;
+ var newPos = myRenderer.screen2world([mousePos[0], mousePos[1], p.mPosV3D[2]]);
+ p.externUpdatePos(newPos, mouseIDt);
+ }
+ else
+ {
+ draggedObj = markedObj;
+ var p = markedObj.particle;
+ p.mNoForcesB = true;
+ p.mNoCollImpB = true;
+ //p.mNoCollB = true;
+ p.mNoIntegrationB = true;
+
+ //p.mCanSleepB = false;
+ p.mExControlledB = true;
+ p.setAwake(true);
+ p.killCollisions();
+
+ var l = p.mAffectedByA.length;
+ var f = null;
+
+ // if the dragged particle is conntected to forces or springs
+ // all the connected particles need to be woke up periodically
+ // because the code that would wake them up otherwise, located
+ // in the force calculation during integration is not called
+ // when the particle is dragged
+ for (var i = 0; i < l; i++)
+ {
+ f = p.mAffectedByA[i];
+ if (f)
+ {
+ if (f.mTypeN == 3 || f.mTypeN == 4 )
+ {
+ // check if there are other forces that keep the particle awake is missing
+ if (f.mAffectedP.mNumN !== p.mNumN)
+ {
+ toWakeUpPA.push(f.mAffectedP);
+ if (f.mAffectedP.mSleepsB && f.mAffectedP.mNumN != -1) f.mAffectedP.setAwake(true);
+ }
+ else
+ {
+ toWakeUpPA.push(f.mSourceP);
+ if (f.mSourceP.mSleepsB && f.mSourceP.mNumN != -1) f.mSourceP.setAwake(true);
+ }
+ }
+ }
+ }
+ wakeUpPeriodical = new PeriodicalExecuter(this.wakeUpHandler.bind(window), 1);
+ }
+ }
+ }
+}
+function downHandler(e){
+ if (e.element().particle)
+ {
+ markedObj = e.element();
+ downB = true;
+ }
+}
+function upHandler(e){
+ if (draggedObj)
+ {
+ draggedB = true;
+ var p = draggedObj.particle;
+ p.mNoForcesB = false;
+ p.mNoCollImpB = false;
+ p.mNoCollB = false;
+ p.mNoIntegrationB = false;
+ //p.mCanSleepB = true;
+ p.mExControlledB = false;
+ wakeUpPeriodical.stop();
+ wakeUpPeriodical = null;
+ toWakeUpPA = [];
+ }
+ draggedObj = null;
+ downB = false;
+ startDownN = 0;
+}
+function keyDownHandler(e){
+ //console.log(e.keyCode);
+ switch (e.keyCode)
+ {
+ // up
+ case 38:
+ myRenderer.mCameraPosV3D[2]+= 0.05;
+ break;
+ //down
+ case 40:
+ myRenderer.mCameraPosV3D[2]-= 0.05;
+ break;
+ //left
+ case 37:
+ myRenderer.mCameraPosV3D[0]-= 0.05;
+ break;
+ //right
+ case 39:
+ myRenderer.mCameraPosV3D[0]+= 0.05;
+ break;
+ // s
+ case 33:
+ myRenderer.mCameraPosV3D[1]-= 0.05;
+ break;
+ // w
+ case 34:
+ myRenderer.mCameraPosV3D[1]+= 0.05;
+ break;
+ default:
+ break;
+ }
+ myWorld.reDrawAll();
+}
+</script>
+</body>
+</html>
56 jQuery/jquery.batchImageLoad.js
@@ -0,0 +1,56 @@
+/**
+ * Plugin which is applied on a list of img objects and calls
+ * the specified callback function, only when all of them are loaded (or errored).
+ * @author: H. Yankov (hristo.yankov at gmail dot com)
+ * @version: 1.0.0 (Feb/22/2010)
+ * http://yankov.us
+ */
+
+(function($) {
+$.fn.batchImageLoad = function(options) {
+ var images = $(this);
+ var originalTotalImagesCount = images.size();
+ var totalImagesCount = originalTotalImagesCount;
+ var elementsLoaded = 0;
+
+ // Init
+ $.fn.batchImageLoad.defaults = {
+ loadingCompleteCallback: null,
+ imageLoadedCallback: null
+ }
+ var opts = $.extend({}, $.fn.batchImageLoad.defaults, options);
+
+ // Start
+ images.each(function() {
+ // The image has already been loaded (cached)
+ if ($(this)[0].complete) {
+ totalImagesCount--;
+ if (opts.imageLoadedCallback) opts.imageLoadedCallback(elementsLoaded, originalTotalImagesCount);
+ // The image is loading, so attach the listener
+ } else {
+ $(this).load(function() {
+ elementsLoaded++;
+
+ if (opts.imageLoadedCallback) opts.imageLoadedCallback(elementsLoaded, originalTotalImagesCount);
+
+ // An image has been loaded
+ if (elementsLoaded >= totalImagesCount)
+ if (opts.loadingCompleteCallback) opts.loadingCompleteCallback();
+ });
+ $(this).error(function() {
+ elementsLoaded++;
+
+ if (opts.imageLoadedCallback) opts.imageLoadedCallback(elementsLoaded, originalTotalImagesCount);
+
+ // The image has errored
+ if (elementsLoaded >= totalImagesCount)
+ if (opts.loadingCompleteCallback) opts.loadingCompleteCallback();
+ });
+ }
+ });
+
+ // There are no unloaded images
+ if (totalImagesCount <= 0)
+ if (opts.loadingCompleteCallback) opts.loadingCompleteCallback();
+};
+})(jQuery);
155 physics/EffectsParticle.js
@@ -0,0 +1,155 @@
+// EffectsParticle.js
+// Copyright (c) Christoph Pacher (http://www.christophpacher.com)
+
+Effect.ScaleP = Class.create(Effect.Scale, {
+ initialize: function(element, percent) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or { } with provided values
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || { });
+ // physics changes start
+ this.mParticleP = this.element.particle;
+ this.mOldScaleN = this.mParticleP.mScaleN;
+ var startScaleN = this.mParticleP.mScaleN * (options.scaleFrom/100.0);
+ this.mParticleP.mNewScaleN = startScaleN;
+ this.mParticleP.setAwake(true);
+ // changes end
+ this.start(options);
+ },
+ update: function(position) {
+ // physics changes start
+ // scale from corner and scale xy independently missing
+ var currentScale = this.mOldScaleN * ((this.options.scaleFrom/100.0) + (this.factor * position));
+ this.mParticleP.mNewScaleN = currentScale;
+ // physics changes end
+ }
+});
+
+
+// if mParticle.mNoForcesB == Particle.mNoCollImpB == mNoIntegrationB == mExControlledB ==true;
+// with this effect it is assured that the particle moves from a to b with no distraction
+// if you just want it to give it a nodge in a direction, push() an impuls vector
+// on the mExternalImpulsesA Array. these impulses get added even during an Effect
+// so dont mix them, if you dont want to.
+Effect.MoveP = Class.create(Effect.Move, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || { });
+ // physics changes start
+ this.mParticleP = this.element.particle;
+ this.mParticleP.setAwake(true);
+ this.mOldPosN = 0;
+ this.px2m = this.mParticleP.mWorldW3D.mRenderer.px2m;
+ // changes end
+ this.start(options);
+ },
+ // physics changes start
+ update: function(position) {
+ var change = (position - this.mOldPosN)* this.px2m;
+ this.mParticleP.externUpdateDisplace([this.options.x * change, this.options.y * change, 0], this.options.fps);
+ this.mOldPosN = position;
+ }
+ // changes end
+});
+
+
+// Same as MoveP but movement is done not via setting position but via setting
+// velocity (Impulses). if you want to give a particle some nodges in a direction, but still
+// want it to react corretly to forces and collisions, this is effect is better
+// than MoveP
+Effect.MoveImP = Class.create(Effect.Move, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || { });
+ // physics changes start
+ this.mParticleP = this.element.particle;
+ this.mParticleP.setAwake(true);
+ this.mOldPosN = 0;
+ this.px2m = this.mParticleP.mWorldW3D.mRenderer.px2m;
+ // changes end
+ this.start(options);
+ },
+ // physics changes start
+ update: function(position) {
+
+ var change = (position - this.mOldPosN) * this.px2m;
+ this.mParticleP.mExternalImpulsesA.push([this.options.x * change, this.options.y * change, 0]);
+ this.mOldPosN = position;
+
+ }
+ // changes end
+});
+
+// Changes Particle: replaced Effect.Move by Effect.MoveP
+Effect.ShakeP = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ distance: 20,
+ duration: 0.5
+ }, arguments[1] || {});
+ var distance = parseFloat(options.distance);
+ var split = parseFloat(options.duration) / 10.0;
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+// changes start
+ return new Effect.MoveP(element,
+ { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ new Effect.MoveP(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.MoveP(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.MoveP(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.MoveP(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+
+ new Effect.MoveP(effect.element,
+ Object.extend({ x: -distance, y: 0, duration: split}, options)
+// changes end
+ ) }}) }}) }}) }}) }});
+};
+
+
+Effect.PuffP = function(element) {
+ element = $(element);
+ var oldStyle = {
+ opacity: element.getInlineOpacity(),
+ position: element.getStyle('position'),
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height
+ };
+ return new Effect.Parallel(
+ // changes: replaces Scale by ScaleP
+ [ new Effect.ScaleP(element, 400,
+ Object.extend({ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true}), arguments[1] || { }),
+
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ Position.absolutize(effect.effects[0].element)
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().setStyle(oldStyle); }
+ }, arguments[1] || { })
+ );
+};
246 physics/PhysicsCollision.js
@@ -0,0 +1,246 @@
+// PhysicsCollisions.js
+// Copyright (c) Christoph Pacher (http://www.christophpacher.com)
+// Collisions impulses base on the theroy of Erin Catto's sequential impulses
+Physics.Collision = Class.create(
+{
+ initialize: function(pA, pB, pHitNormalV3D, pHitAxisN, pHitTimeN, pSeparationN)
+ {
+ this.mA = pA;
+ this.mB = pB;
+ // the Hitnormal Points away from A aka P1, collisions vel is relative to A aka P1
+ this.mHitNormalV3D = pHitNormalV3D;
+ this.mHitTimeN = pHitTimeN;
+ this.mHitAxisN = pHitAxisN;
+ this.mWorldW3D = this.mA.mWorldW3D;
+ this.mHitTangent1V3D = this.mWorldW3D.mHitTangentsA[pHitAxisN][0];
+ this.mHitTangent2V3D = this.mWorldW3D.mHitTangentsA[pHitAxisN][1];
+ this.mPNormalN = 0;
+ this.mPNormalBN = 0;
+ this.mPTangent1N = 0;
+ this.mPTangent2N = 0;
+ this.mMassNormalN = 0;
+ this.mMassTangent1N = 0;
+ this.mMassTangent2N = 0;
+ this.mBiasN = 0;
+ this.mSeparationN = pSeparationN;
+ this.mFrictionN = Math.sqrt(this.mA.mFrictionN * this.mB.mFrictionN);
+ this.mRestitutionN = pA.mRestitutionN > pB.mRestitutionN ? pA.mRestitutionN : pB.mRestitutionN;
+ this.mSleepsB = false;
+ this.mANoImpB = this.mA.mNoCollImpB;
+ this.mBNoImpB = this.mB.mNoCollImpB;
+ },
+ preStep: function(pInvDtN){
+ var matv = V3DP;
+ var mat = Math;
+ var k_allowedPenetration = 0.01;
+ var k_biasFactor = this.mWorldW3D.mSplitImpB ? 0.8 : 0.2;
+ k_biasFactor = this.mWorldW3D.mPosCorrB ? k_biasFactor : 0.0;
+
+ // Precompute normal mass, tangent mass, and bias.
+ var temp = this.mA.mIScaledMassN + this.mB.mIScaledMassN;
+ temp = temp > 0 ? 1/temp : 0;
+ this.mMassNormalN = temp;
+ this.mMassTangent1N = temp;
+ this.mMassTangent2N = temp;
+
+ this.mBiasN = -k_biasFactor * pInvDtN * mat.min(0.0, this.mSeparationN + k_allowedPenetration);
+
+ var vmat = V3DP;
+ var vRelN = vmat.dot(vmat.sub(this.mB.mVelV3D, this.mA.mVelV3D), this.mHitNormalV3D);
+
+
+ if (vRelN < -this.mWorldW3D.mBounceThresholdN)
+ {
+ this.mBiasN += -this.mRestitutionN * vRelN;
+ }
+
+ this.mANoImpB = this.mA.mNoCollImpB;
+ this.mBNoImpB = this.mB.mNoCollImpB;
+
+ if (this.mWorldW3D.mAccumulateImpB)
+ {
+ // Apply normal + friction impulse
+ var PV3D = matv.multNum(this.mHitNormalV3D, this.mPNormalN);
+ matv.addDirect(PV3D, matv.multNum(this.mHitTangent1V3D, this.mPTangent1N));
+ matv.addDirect(PV3D, matv.multNum(this.mHitTangent2V3D, this.mPTangent2N));
+
+ if(!this.mANoImpB)
+ {
+ this.mA.mVelV3D[0] -= PV3D[0] * this.mA.mIScaledMassN;
+ this.mA.mVelV3D[1] -= PV3D[1] * this.mA.mIScaledMassN;
+ this.mA.mVelV3D[2] -= PV3D[2] * this.mA.mIScaledMassN;
+ }
+ if(!this.mBNoImpB)
+ {
+ this.mB.mVelV3D[0] += PV3D[0] * this.mB.mIScaledMassN;
+ this.mB.mVelV3D[1] += PV3D[1] * this.mB.mIScaledMassN;
+ this.mB.mVelV3D[2] += PV3D[2] * this.mB.mIScaledMassN;
+ }
+ }
+ },
+
+ applyImpulse: function(){
+ // this is called 10 times per collision per step so AVOID function calls
+ // and prototype class inits
+ var matv = V3DP;
+ var mat = Math;
+ // Relative velocity at contact
+ var dvV3D = [this.mB.mVelV3D[0] - this.mA.mVelV3D[0],
+ this.mB.mVelV3D[1] - this.mA.mVelV3D[1],
+ this.mB.mVelV3D[2] - this.mA.mVelV3D[2]];
+
+
+ // Compute normal impulse
+ var vnN = dvV3D[0] * this.mHitNormalV3D[0] + dvV3D[1] * this.mHitNormalV3D[1] + dvV3D[2] * this.mHitNormalV3D[2];
+
+ var dPnN = 0;
+ if (this.mWorldW3D.mSplitImpB)
+ dPnN = (-vnN) * this.mMassNormalN;
+ else
+ dPnN = (-vnN + this.mBiasN) * this.mMassNormalN;
+
+ if (this.mWorldW3D.mAccumulateImpB)
+ {
+ // Clamp the accumulated impulse
+ var Pn0N = this.mPNormalN;
+ this.mPNormalN = mat.max(Pn0N + dPnN, 0.0);
+ dPnN = this.mPNormalN - Pn0N;
+ }
+ else
+ dPnN = mat.max(dPnN, 0.0);
+
+ // Apply contact impulse
+
+ if(!this.mANoImpB)
+ {
+ var mult = dPnN * this.mA.mIScaledMassN;
+ this.mA.mVelV3D[0] -= this.mHitNormalV3D[0] * mult;
+ this.mA.mVelV3D[1] -= this.mHitNormalV3D[1] * mult;
+ this.mA.mVelV3D[2] -= this.mHitNormalV3D[2] * mult;
+ }
+ if(!this.mBNoImpB)
+ {
+ var mult = dPnN * this.mB.mIScaledMassN;
+ this.mB.mVelV3D[0] += this.mHitNormalV3D[0] * mult;
+ this.mB.mVelV3D[1] += this.mHitNormalV3D[1] * mult;
+ this.mB.mVelV3D[2] += this.mHitNormalV3D[2] * mult;
+ }
+
+ if (this.mWorldW3D.mSplitImpB)
+ {
+ // Compute bias impulse
+ dvV3D = [this.mB.mBiasedVelV3D[0] - this.mA.mBiasedVelV3D[0],
+ this.mB.mBiasedVelV3D[1] - this.mA.mBiasedVelV3D[1],
+ this.mB.mBiasedVelV3D[2] - this.mA.mBiasedVelV3D[2]];
+
+ var vnbN = dvV3D[0] * this.mHitNormalV3D[0] + dvV3D[1] * this.mHitNormalV3D[1] + dvV3D[2] * this.mHitNormalV3D[2];
+
+ var dPnbN = this.mMassNormalN * (-vnbN + this.mBiasN);
+ var Pnb0N = this.mPNormalBN;
+ // max + -this.mRestitutionN * vRelN
+ this.mPNormalBN = mat.max(Pnb0N + dPnbN, 0.0);
+ dPnbN = this.mPNormalBN - Pnb0N;
+
+ if(!this.mANoImpB)
+ {
+ var mult = dPnbN * this.mA.mIScaledMassN;
+ this.mA.mBiasedVelV3D[0] -= this.mHitNormalV3D[0] * mult;
+ this.mA.mBiasedVelV3D[1] -= this.mHitNormalV3D[1] * mult;
+ this.mA.mBiasedVelV3D[2] -= this.mHitNormalV3D[2] * mult;
+ }
+ if(!this.mBNoImpB)
+ {
+ var mult = dPnbN * this.mB.mIScaledMassN;
+ this.mB.mBiasedVelV3D[0] += this.mHitNormalV3D[0] * mult;
+ this.mB.mBiasedVelV3D[1] += this.mHitNormalV3D[1] * mult;
+ this.mB.mBiasedVelV3D[2] += this.mHitNormalV3D[2] * mult;
+ }
+ }
+ // Relative velocity at contact
+ dvV3D = [this.mB.mVelV3D[0] - this.mA.mVelV3D[0],
+ this.mB.mVelV3D[1] - this.mA.mVelV3D[1],
+ this.mB.mVelV3D[2] - this.mA.mVelV3D[2]];
+
+ var vtN = dvV3D[0] * this.mHitTangent1V3D[0] + dvV3D[1] * this.mHitTangent1V3D[1] + dvV3D[2] * this.mHitTangent1V3D[2];
+ var dPtN = this.mMassTangent1N * (-vtN);
+
+ if (this.mWorldW3D.mAccumulateImpB)
+ {
+ // Compute friction impulse
+ var maxPtN = this.mPNormalN > 0.0 ? this.mFrictionN * this.mPNormalN : 0.01;
+
+ // Clamp friction
+ var oldTangentImpulseN = this.mPTangent1N;
+ //this.mPTangent1N = MyUtilities.clamp(oldTangentImpulseN + dPtN, -maxPtN, maxPtN);
+ this.mPTangent1N = mat.max(-maxPtN, mat.min(oldTangentImpulseN + dPtN, maxPtN));
+ dPtN = this.mPTangent1N - oldTangentImpulseN;
+ }
+ else
+ {
+ var maxPtN = this.mFrictionN * dPnN;
+ dPtN = MyUtilities.clamp(dPtN, -maxPtN, maxPtN);
+
+ }
+ // Apply contact impulse
+
+ if(!this.mANoImpB)
+ {
+ var mult = dPtN * this.mA.mIScaledMassN;
+ this.mA.mVelV3D[0] -= this.mHitTangent1V3D[0] * mult;
+ this.mA.mVelV3D[1] -= this.mHitTangent1V3D[1] * mult;
+ this.mA.mVelV3D[2] -= this.mHitTangent1V3D[2] * mult;
+ }
+ if(!this.mBNoImpB)
+ {
+ var mult = dPtN * this.mB.mIScaledMassN;
+ this.mB.mVelV3D[0] += this.mHitTangent1V3D[0] * mult;
+ this.mB.mVelV3D[1] += this.mHitTangent1V3D[1] * mult;
+ this.mB.mVelV3D[2] += this.mHitTangent1V3D[2] * mult;
+ }
+
+ // Relative velocity at contact
+ dvV3D = [this.mB.mVelV3D[0] - this.mA.mVelV3D[0],
+ this.mB.mVelV3D[1] - this.mA.mVelV3D[1],
+ this.mB.mVelV3D[2] - this.mA.mVelV3D[2]];
+
+
+ var vttN = dvV3D[0] * this.mHitTangent2V3D[0] + dvV3D[1] * this.mHitTangent2V3D[1] + dvV3D[2] * this.mHitTangent2V3D[2];
+ var dPttN = this.mMassTangent2N * (-vttN);
+
+ if (this.mWorldW3D.mAccumulateImpB)
+ {
+ // Compute friction impulse
+ var maxPttN = this.mPNormalN > 0.0 ? this.mFrictionN * this.mPNormalN : 0.01;
+
+ // Clamp friction
+ var oldTangentImpulseN = this.mPTangent2N;
+ //this.mPTangent2N = MyUtilities.clamp(oldTangentImpulseN + dPttN, -maxPttN, maxPttN);
+ this.mPTangent2N = mat.max(-maxPttN, mat.min(oldTangentImpulseN + dPttN, maxPttN));
+ dPttN = this.mPTangent2N - oldTangentImpulseN;
+ }
+ else
+ {
+ var maxPttN = this.mFrictionN * dPnN;
+ dPttN = MyUtilities.clamp(dPttN, -maxPttN, maxPttN);
+
+ }
+ // Apply contact impulse
+
+ if(!this.mANoImpB)
+ {
+ var mult = dPttN * this.mA.mIScaledMassN;
+ this.mA.mVelV3D[0] -= this.mHitTangent2V3D[0] * mult;
+ this.mA.mVelV3D[1] -= this.mHitTangent2V3D[1] * mult;
+ this.mA.mVelV3D[2] -= this.mHitTangent2V3D[2] * mult;
+ }
+ if(!this.mBNoImpB)
+ {
+ var mult = dPttN * this.mB.mIScaledMassN;
+ this.mB.mVelV3D[0] += this.mHitTangent2V3D[0] * mult;
+ this.mB.mVelV3D[1] += this.mHitTangent2V3D[1] * mult;
+ this.mB.mVelV3D[2] += this.mHitTangent2V3D[2] * mult;
+ }
+
+ }
+
+});
330 physics/PhysicsForce.js
@@ -0,0 +1,330 @@
+// PhysicsForce.js
+// Copyright (c) Christoph Pacher (http://www.christophpacher.com)
+
+Physics.Omni = Class.create(
+{
+ initialize: function(pIdS, pWorldW3D, pAffectedPA, pForceV3D)
+ {
+ this.mWorldW3D = pWorldW3D;
+ this.mWorldIndexN = pWorldW3D.mForcesA.length;
+ pWorldW3D.mForcesA[this.mWorldIndexN] = this;
+ this.mTypeN = 1;
+ this.mIdS = pIdS;
+ this.mForceV3D = pForceV3D;
+ // if particles are killed, those become sparse arrays!
+ this.mAffectedPA = pAffectedPA;
+ // the index of the force in the force array of the particle
+ this.mAffectedIndexA = [];
+ this.mOnB = true;
+
+ var l = pAffectedPA.length;
+ var p = null;
+ for (var i = 0; i < l; i++)
+ {
+ p = pAffectedPA[i];
+ if(p) {
+ this.mAffectedIndexA[i] = p.mAffectedByStaticA.length;
+ p.mAffectedByStaticA[this.mAffectedIndexA[i]] = this;
+ p.mInForceStaticIndexA[this.mAffectedIndexA[i]] = i;
+ p.mForceStaticV3D[0] += pForceV3D[0];
+ p.mForceStaticV3D[1] += pForceV3D[1];
+ p.mForceStaticV3D[2] += pForceV3D[2];
+ }
+ }
+ },
+ setOn: function(onB)
+ {
+ var l = this.mAffectedPA.length;
+ var p = null;
+ if (onB)
+ {
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+ p.mForceStaticV3D[0] += this.mForceV3D[0];
+ p.mForceStaticV3D[1] += this.mForceV3D[1];
+ p.mForceStaticV3D[2] += this.mForceV3D[2];
+ }
+ }
+ }
+ else
+ {
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+
+ p.mForceStaticV3D[0] -= this.mForceV3D[0];
+ p.mForceStaticV3D[1] -= this.mForceV3D[1];
+ p.mForceStaticV3D[2] -= this.mForceV3D[2];
+ }
+ }
+ }
+ this.mOnB = onB;
+ },
+ updateForce: function(pNewV3D)
+ {
+ var l = this.mAffectedPA.length;
+ var p = null;
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+ p.mForceStaticV3D[0] -= this.mForceV3D[0] - pNewV3D[0];
+ p.mForceStaticV3D[1] -= this.mForceV3D[1] - pNewV3D[1];
+ p.mForceStaticV3D[2] -= this.mForceV3D[2] - pNewV3D[2];
+ }
+ }
+ this.mForceV3D = pNewV3D;
+ },
+ addParticle: function(p)
+ {
+ var i = this.mAffectedIndexA.length;
+ this.mAffectedPA[i] = p;
+ this.mAffectedIndexA[i] = p.mAffectedByStaticA.length;
+ p.mAffectedByStaticA[this.mAffectedIndexA[i]] = this;
+ p.mInForceStaticIndexA[this.mAffectedIndexA[i]] = i;
+ if (this.mOnB)
+ {
+ p.mForceStaticV3D[0] += this.mForceV3D[0];
+ p.mForceStaticV3D[1] += this.mForceV3D[1];
+ p.mForceStaticV3D[2] += this.mForceV3D[2];
+ }
+ },
+ removeParticle: function(p)
+ {
+ var lf = p.mAffectedByStaticA.length;
+ var f = null;
+ var forceIndex = -1;
+ for (var j = 0; j < lf; j++)
+ {
+ f = p.mAffectedByStaticA[j];
+ if (f && f.mIdS == this.mIdS)
+ {
+ forceIndex = j
+ break;
+ }
+ }
+ if (forceIndex != -1)
+ {
+ p.mAffectedByStaticA[forceIndex] = null;
+ var i = p.mInForceStaticIndexA[forceIndex];
+ p.mInForceStaticIndexA[forceIndex] = null;
+ this.mAffectedPA[i] = null;
+ this.mAffectedIndexA[i] = null;
+ if (this.mOnB)
+ {
+ p.mForceStaticV3D[0] -= this.mForceV3D[0];
+ p.mForceStaticV3D[1] -= this.mForceV3D[1];
+ p.mForceStaticV3D[2] -= this.mForceV3D[2];
+ }
+ }
+ }
+});
+
+Physics.OmniMassInd = Class.create(
+{
+ initialize: function(pIdS, pWorldW3D, pAffectedPA, pForceV3D)
+ {
+ this.mWorldW3D = pWorldW3D;
+ this.mWorldIndexN = pWorldW3D.mForcesA.length;
+ pWorldW3D.mForcesA[this.mWorldIndexN] = this;
+ this.mTypeN = 2;
+ this.mIdS = pIdS;
+ this.mForceV3D = pForceV3D;
+ // if particles are killed, this becomes a sparse array!
+ this.mAffectedPA = pAffectedPA;
+ this.mAffectedIndexA = [];
+ this.mOnB = true;
+
+ var l = pAffectedPA.length;
+ var p = null;
+ for (var i = 0; i < l; i++)
+ {
+ p = pAffectedPA[i];
+ if(p){
+ this.mAffectedIndexA[i] = p.mAffectedByStaticA.length;
+ p.mAffectedByStaticA[this.mAffectedIndexA[i]] = this;
+ p.mInForceStaticIndexA[this.mAffectedIndexA[i]] = i;
+ p.mForceStaticMIndV3D[0] += pForceV3D[0];
+ p.mForceStaticMIndV3D[1] += pForceV3D[1];
+ p.mForceStaticMIndV3D[2] += pForceV3D[2];
+ }
+ }
+
+ },
+ setOn: function(onB)
+ {
+ var l = this.mAffectedPA.length;
+ var p = null;
+ if (onB)
+ {
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+
+ p.mForceStaticMIndV3D[0] += this.mForceV3D[0];
+ p.mForceStaticMIndV3D[1] += this.mForceV3D[1];
+ p.mForceStaticMIndV3D[2] += this.mForceV3D[2];
+ }
+ }
+ }
+ else
+ {
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+
+ p.mForceStaticMIndV3D[0] -= this.mForceV3D[0];
+ p.mForceStaticMIndV3D[1] -= this.mForceV3D[1];
+ p.mForceStaticMIndV3D[2] -= this.mForceV3D[2];
+ }
+ }
+ }
+ this.mOnB = onB;
+ },
+ updateForce: function(pNewV3D)
+ {
+ var l = this.mAffectedPA.length;
+ var p = null;
+ for (var i = 0; i < l; i++)
+ {
+ p = this.mAffectedPA[i];
+ if(p)
+ {
+
+ p.mForceStaticMIndV3D[0] -= this.mForceV3D[0] - pNewV3D[0];
+ p.mForceStaticMIndV3D[1] -= this.mForceV3D[1] - pNewV3D[1];
+ p.mForceStaticMIndV3D[2] -= this.mForceV3D[2] - pNewV3D[2];
+ }
+ }
+ this.mForceV3D = pNewV3D;
+ },
+ addParticle: function(p)
+ {
+ var i = this.mAffectedIndexA.length;
+ this.mAffectedPA[i] = p;
+ this.mAffectedIndexA[i] = p.mAffectedByStaticA.length;
+ p.mAffectedByStaticA[this.mAffectedIndexA[i]] = this;
+ p.mInForceStaticIndexA[this.mAffectedIndexA[i]] = i;
+ if (this.mOnB)
+ {
+ p.mForceStaticMIndV3D[0] += this.mForceV3D[0];
+ p.mForceStaticMIndV3D[1] += this.mForceV3D[1];
+ p.mForceStaticMIndV3D[2] += this.mForceV3D[2];
+ }
+ },
+ removeParticle: function(p)
+ {
+ var lf = p.mAffectedByStaticA.length;
+ var f = null;
+ var forceIndex = -1;
+ for (var j = 0; j < lf; j++)
+ {
+ f = p.mAffectedByStaticA[j];
+ if (f && f.mIdS == this.mIdS)
+ {
+ forceIndex = j
+ break;
+ }
+ }
+ if (forceIndex != -1)
+ {
+ p.mAffectedByStaticA[forceIndex] = null;
+ var i = p.mInForceStaticIndexA[forceIndex];
+ p.mInForceStaticIndexA[forceIndex] = null;
+ this.mAffectedPA[i] = null;
+ this.mAffectedIndexA[i] = null;
+ if (this.mOnB)
+ {
+ p.mForceStaticMIndV3D[0] -= this.mForceV3D[0];
+ p.mForceStaticMIndV3D[1] -= this.mForceV3D[1];
+ p.mForceStaticMIndV3D[2] -= this.mForceV3D[2];
+ }
+ }
+ }
+});
+
+Physics.Attraction = Class.create(
+{
+ initialize: function(pIdS, pWorldW3D, pAffectedP, pSourceP, pAffectSourceB, pStrengthN, pMinDistanceN, pMaxDistanceN)
+ {
+ this.mWorldW3D = pWorldW3D;
+ this.mWorldIndexN = pWorldW3D.mForcesA.length;
+ pWorldW3D.mForcesA[this.mWorldIndexN] = this;
+ this.mTypeN = 3;
+ this.mIdS = pIdS;
+ this.mOnB = true;
+
+
+ // the min and max distance (m) between the particles, for the force to work in
+ this.mMinDistanceN = pMinDistanceN;
+ this.mMaxDistanceN = pMaxDistanceN;
+ //is the source attracted to the other particle
+ this.mAffectSourceB = pAffectSourceB;
+ // make this negativ and you have a repell
+ this.mStrengthN = pStrengthN;
+
+ this.mAffectedP = pAffectedP;
+ this.mInAffectedIndexN = this.mAffectedP.mAffectedByA.length;
+ this.mAffectedP.mAffectedByA[this.mInAffectedIndexN] = this;
+ this.mAffectedP.mInForceIndexA[this.mInAffectedIndexN] = -1;
+
+ this.mSourceP = pSourceP;
+ this.mInSourceIndexN = this.mSourceP.mAffectedByA.length;
+ this.mSourceP.mAffectedByA[this.mInSourceIndexN] = this;
+ this.mSourceP.mInForceIndexA[this.mInSourceIndexN] = -1;
+ },
+ setOn: function(onB)
+ {
+ this.mOnB = onB;
+ }
+});
+
+Physics.Spring = Class.create(
+{
+ initialize: function(pIdS, pWorldW3D, pAffectedP, pSourceP, pAffectSourceB, pSpringKN, pRestLenN, pDampN, pDrawLineB)
+ {
+ this.mWorldW3D = pWorldW3D;
+ this.mWorldIndexN = pWorldW3D.mForcesA.length;
+ pWorldW3D.mForcesA[this.mWorldIndexN] = this;
+ this.mTypeN = 4;
+ this.mIdS = pIdS;
+ this.mOnB = true;
+ this.mDrawLineB = pDrawLineB;
+ this.mPLine;
+
+ // the min and max distance (m) between the particles, for the force to work in
+ this.mSpringKN = pSpringKN;
+ this.mRestLenN = pRestLenN;
+ //is the source attracted to the other particle
+ this.mAffectSourceB = pAffectSourceB;
+ this.mDampN = pDampN;
+
+ this.mAffectedP = pAffectedP;
+ this.mInAffectedIndexN = this.mAffectedP.mAffectedByA.length;
+ this.mAffectedP.mAffectedByA[this.mInAffectedIndexN] = this;
+ this.mAffectedP.mInForceIndexA[this.mInAffectedIndexN] = -1;
+
+ this.mSourceP = pSourceP;
+ this.mInSourceIndexN = this.mSourceP.mAffectedByA.length;
+ this.mSourceP.mAffectedByA[this.mInSourceIndexN] = this;
+ this.mSourceP.mInForceIndexA[this.mInSourceIndexN] = -1;
+
+ if(this.mDrawLineB) {
+ this.mPLine = this.mWorldW3D.addParticleLine(this.mSourceP, this.mAffectedP);
+ }
+ },
+ setOn: function(onB)
+ {
+ this.mOnB = onB;
+ }
+});
515 physics/PhysicsParticle.js
</
@@ -0,0 +1,515 @@
+// PhysicsParticle.js
+// Copyright (c) Christoph Pacher (http://www.christophpacher.com)
+Physics.Particle = Class.create(
+{
+ initialize: function(pWorldW3D, pPosV3D, pExtV3D, pMassN, pVelocityV3D, pSpriteO, pNumN){
+ var matv = V3DP;
+ this.mNumN = pNumN;
+ this.mWorldW3D = pWorldW3D;
+ this.mParticleLinePL = null;
+ this.mCollisionsH = new Hash();
+ // the DOM HTML element
+ this.mSpriteO = pSpriteO;
+ // in meters
+ this.mExtV3D = pExtV3D;
+
+ // in the middle of the particle in meters
+ this.mPosV3D = pPosV3D;
+
+ this.mScaleN = 1.0;
+ // the new scale val from extern app is written here and then applied to
+ // the particle during a step and afterwards set to 0
+ this.mNewScaleN = 0.0;
+ // if a new scale was applied this is set true, and a different collision
+ // normal is used in the collision algorithm
+ this.mOldRenderScaleN = 0.0;
+ this.mScaledB = false;
+ this.mScaledExtV3D = matv.multNum(this.mExtV3D, this.mScaleN);
+
+
+ this.mPosOldV3D = matv.copy(this.mPosV3D);
+ this.mDisplaceV3D = [0,0,0];
+
+ this.mMaxV3D = matv.add(this.mPosV3D, this.mScaledExtV3D);
+ this.mMinV3D = matv.sub(this.mPosV3D, this.mScaledExtV3D);
+ this.mMaxOldV3D = matv.add(this.mPosOldV3D, this.mScaledExtV3D);
+ this.mMinOldV3D = matv.sub(this.mPosOldV3D, this.mScaledExtV3D);
+
+ // in kilogramms
+ this.mMassN = pMassN;
+ this.mIMassN = this.mMassN > 0 ? 1.0/this.mMassN : 0;
+ this.mScaledMassN = this.mMassN * this.mScaleN;
+ this.mIScaledMassN = this.mScaledMassN > 0 ? 1.0/this.mScaledMassN : 0;
+ // in meters per second
+ this.mVelV3D = pVelocityV3D;
+ this.mBiasedVelV3D = [0,0,0];
+
+ this.mExternalImpulsesA = [];
+ // if forces are killed, this becomes a sparse array!
+ this.mAffectedByA = [];
+ this.mAffectedByStaticA = [];
+ // forces that are independent of time and space
+ this.mForceStaticV3D = [0,0,0];
+ // forces that are independent of time space and the particl mass
+ this.mForceStaticMIndV3D = [0,0,0];
+ // the index of the particle in the array of the force object
+ this.mInForceIndexA =[];
+ this.mInForceStaticIndexA =[];
+
+ this.mSleepsB = false;
+ // user controlled particles are not allowed to sleep
+ this.mCanSleepB = true;
+ // spleeping threshold
+ this.mSleepEpsilonN = 0.001;
+ // mVelV3D.dot(mVelV3D) recency weighted averages
+ this.mMotionN = this.mSleepEpsilonN * 2;
+ // controls how much of the current vel is added to the average vel in mMotionN
+ this.mSleepBiasN = Math.pow(0.4, this.mWorldW3D.mDeltaTimeN);
+ this.mStepsToSleepN = 5;
+ this.mStepsSleepCountN = 0;
+
+ this.mDampingN = 0.98;
+
+ // the air or water drag
+ this.mDragN = 0;
+ // drag calculation should be adapted to your needs
+ this.updateDrag();
+
+ // how much energy of the collision speed is transfered to the
+ // separation velocity
+ this.mRestitutionN = 0.0;
+ this.mFrictionN = 0.2;
+ // true, no collision impulses are applied to the body by the collision resolver
+ this.mNoCollImpB = false;
+ // true, this particle generates no new collisions
+ // existing collisions in this.mCollisionsH still will be considered
+ // in collision resolution
+ this.mNoCollB = false;
+ // true, no world forces are applied to the body
+ this.mNoForcesB = false;
+ // true, this particle skips pos and vel integration
+ this.mNoIntegrationB = false;
+ // flag for wakeup fuction
+ this.mExControlledB = false;
+ },
+
+
+ calcAcceleration: function(pVelocityV3D, pPosV3D)
+ {
+ var matv = V3DP;
+ var mat = Math;
+ var forcesSumV3D = [this.mForceStaticV3D[0], this.mForceStaticV3D[1],
+ this.mForceStaticV3D[2]];
+ var i = 0;
+ var forces = this.mAffectedByA;
+ var i = this.mAffectedByA.length;
+ var f = null;
+ while (i--)
+ {
+ if (forces[i])
+ {
+
+ f = forces[i];
+ if (f && f.mOnB)
+ {
+ switch(f.mTypeN)
+ {
+ // attraction
+ case 3:
+ if (f.mSourceP.mNumN == this.mNumN)
+ {
+ if (f.mAffectSourceB)
+ {
+ var distV3D = [f.mAffectedP.mPosV3D[0] - pPosV3D[0],
+ f.mAffectedP.mPosV3D[1] - pPosV3D[1],
+ f.mAffectedP.mPosV3D[2] - pPosV3D[2]];
+ var distN = mat.sqrt(distV3D[0] * distV3D[0] + distV3D[1] * distV3D[1] + distV3D[2] * distV3D[2]);
+
+ if (distN < f.mMaxDistanceN && distN > f.mMinDistanceN)
+ {
+ var mult = f.mStrengthN * f.mAffectedP.mMassN * this.mMassN / (distN * distN *distN);
+ forcesSumV3D[0] += distV3D[0] * mult;
+ forcesSumV3D[1] += distV3D[1] * mult;
+ forcesSumV3D[2] += distV3D[2] * mult;
+ if (this.mMotionN > this.mSleepEpsilonN && f.mAffectedP.mSleepsB && f.mAffectedP.mNumN != -1) f.mAffectedP.setAwake(true);
+ }
+ }
+ }
+ else
+ {
+ var distV3D = [f.mSourceP.mPosV3D[0] - pPosV3D[0],
+ f.mSourceP.mPosV3D[1] - pPosV3D[1],
+ f.mSourceP.mPosV3D[2] - pPosV3D[2]];
+ var distN = mat.sqrt(distV3D[0] * distV3D[0] + distV3D[1] * distV3D[1] + distV3D[2] * distV3D[2]);
+
+ if (distN < f.mMaxDistanceN && distN > f.mMinDistanceN)
+ {
+ // F = G*m1*m2/ d*d
+ // the third distN is for normalization
+ var mult = f.mStrengthN * f.mSourceP.mMassN * this.mMassN / (distN * distN *distN);
+ forcesSumV3D[0] += distV3D[0] * mult;
+ forcesSumV3D[1] += distV3D[1] * mult;
+ forcesSumV3D[2] += distV3D[2] * mult;
+ if (this.mMotionN > this.mSleepEpsilonN && f.mSourceP.mSleepsB && f.mSourceP.mNumN != -1) f.mSourceP.setAwake(true);
+ }
+ }
+ break;
+
+ // spring
+ case 4:
+ if (f.mSourceP.mNumN == this.mNumN)
+ {
+ if (f.mAffectSourceB)
+ {
+ var distV3D = [f.mAffectedP.mPosV3D[0] - pPosV3D[0],
+ f.mAffectedP.mPosV3D[1] - pPosV3D[1],
+ f.mAffectedP.mPosV3D[2] - pPosV3D[2]];
+ var distN = mat.sqrt(distV3D[0] * distV3D[0] + distV3D[1] * distV3D[1] + distV3D[2] * distV3D[2]);
+ var relVelV3D = [f.mAffectedP.mVelV3D[0] - pVelocityV3D[0],
+ f.mAffectedP.mVelV3D[1] - pVelocityV3D[1],
+ f.mAffectedP.mVelV3D[2] - pVelocityV3D[2]];
+ var mult = (distN - f.mRestLenN) * f.mSpringKN / (distN);
+ forcesSumV3D[0] += (distV3D[0] * mult) - (f.mDampN * relVelV3D[0]);
+ forcesSumV3D[1] += (distV3D[1] * mult) - (f.mDampN * relVelV3D[1]);
+ forcesSumV3D[2] += (distV3D[2] * mult) - (f.mDampN * relVelV3D[2]);
+ if (mat.abs(mult) > 0.01 &&
+ this.mMotionN > this.mSleepEpsilonN &&
+ f.mAffectedP.mSleepsB &&
+ f.mAffectedP.mNumN != -1) f.mAffectedP.setAwake(true);
+ }
+ }
+ else
+ {
+ // F = -k(|x|-d)(x/|x|) - bv
+ var distV3D = [f.mSourceP.mPosV3D[0] - pPosV3D[0],
+ f.mSourceP.mPosV3D[1] - pPosV3D[1],
+ f.mSourceP.mPosV3D[2] - pPosV3D[2]];
+ var distN = mat.sqrt(distV3D[0] * distV3D[0] + distV3D[1] * distV3D[1] + distV3D[2] * distV3D[2]);
+ var relVelV3D = [f.mSourceP.mVelV3D[0] - pVelocityV3D[0],
+ f.mSourceP.mVelV3D[1] - pVelocityV3D[1],
+ f.mSourceP.mVelV3D[2] - pVelocityV3D[2]];
+ var mult = (distN - f.mRestLenN) * f.mSpringKN / (distN);
+ forcesSumV3D[0] += (distV3D[0] * mult) - (f.mDampN * relVelV3D[0]);
+ forcesSumV3D[1] += (distV3D[1] * mult) - (f.mDampN * relVelV3D[1]);
+ forcesSumV3D[2] += (distV3D[2] * mult) - (f.mDampN * relVelV3D[2]);
+ if (mat.abs(mult) > 0.01 &&
+ this.mMotionN > this.mSleepEpsilonN &&
+ f.mSourceP.mSleepsB &&
+ f.mSourceP.mNumN != -1) f.mSourceP.setAwake(true);
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (this.mDragN != 0)
+ {
+ forcesSumV3D[0] -= pVelocityV3D[0] *this.mDragN;
+ forcesSumV3D[1] -= pVelocityV3D[1] *this.mDragN;
+ forcesSumV3D[2] -= pVelocityV3D[2] *this.mDragN;
+ }
+
+ forcesSumV3D[0] *= this.mIScaledMassN;
+ forcesSumV3D[1] *= this.mIScaledMassN;
+ forcesSumV3D[2] *= this.mIScaledMassN;
+ forcesSumV3D[0] += this.mForceStaticMIndV3D[0];
+ forcesSumV3D[1] += this.mForceStaticMIndV3D[1];
+ forcesSumV3D[2] += this.mForceStaticMIndV3D[2];
+
+
+ return forcesSumV3D;
+ },
+
+ setAwake: function(awakeB)
+ {
+ if (awakeB)
+ {
+ if (!this.mExControlledB)
+ {
+ this.mNoCollImpB = false;
+ this.mNoIntegrationB = false;
+ }
+
+ //this.mMotionN = this.mSleepEpsilonN *2.0;
+ this.mStepsSleepCountN = 0;
+
+ this.mSleepsB = false;
+ var i, c, aSleeps, bSleeps = 0;
+
+ for( i in this.mCollisionsH._object)
+ {
+ // Bug Fix for "[object Object]" exists in _object after deleting an
+ // Hash entry
+ if (i != "[object Object]")
+ {
+ c = this.mCollisionsH._object[i];
+ aSleeps = c.mA.mSleepsB;
+ bSleeps = c.mB.mSleepsB;
+
+ // if mNumN == -1 it is a bounding object which never wake up
+ if (aSleeps && c.mA.mNumN != -1) c.mA.setAwake(true);
+ else if (bSleeps && c.mB.mNumN != -1) c.mB.setAwake(true);
+ c.mSleepsB = false;
+ }
+ }
+ }
+ else if (this.mCanSleepB)
+ {
+ if (this.mStepsSleepCountN > this.mStepsToSleepN)
+ {
+ this.mSleepsB = true;
+ this.mVelV3D[0] = 0;
+ this.mVelV3D[1] = 0;
+ this.mVelV3D[2] = 0;
+ // set Integration and CollImpulses off so that
+ // invers impulses from mirco collisions between
+ // stacking boxes dont accumulate in mVelV3D of the sleeping box.
+ // this vel is used to compute the constraints.if it accumulates
+ // the upper box thinks, the lower moves downwards, what is wrong
+ this.mNoIntegrationB = true;
+ this.mNoCollImpB = true;
+ }
+ else
+ this.mStepsSleepCountN++;
+ }
+ },
+
+ killCollisions: function()
+ {
+ var collisionsO = this.mCollisionsH._object;
+ var c, aSleeps, bSleeps, key, aNum, bNum = 0;
+ var numParticle = this.mNumN;
+ for( key in collisionsO)
+ {
+ // Bug Fix for "[object Object]" exists in _object after deleting an
+ // Hash entry
+ if (key != "[object Object]")
+ {
+ this.mWorldW3D.mCollisionsH.unset(key);
+ c = collisionsO[key];
+ aSleeps = c.mA.mSleepsB;
+ bSleeps = c.mB.mSleepsB;
+ aNum = c.mA.mNumN;
+ bNum = c.mB.mNumN;
+
+ // if mNumN == -1 it is a bounding object which never wake up
+ if (aNum != numParticle)
+ {
+ if (aNum != -1)
+ {
+ c.mA.mCollisionsH.unset(key);
+ if (aSleeps) c.mA.setAwake(true);
+ }
+ }
+ else
+ {
+ if (bNum != -1)
+ {
+ c.mB.mCollisionsH.unset(key);
+ if (bSleeps) c.mB.setAwake(true);
+ }
+ }
+ }
+ }
+ },
+
+
+ updateDrag: function()
+ {
+ // this is just an unscientific estimation and should be adapted to the
+ // maximum sizes of the particles
+ this.mDragN = (this.mScaledExtV3D[0] * this.mScaledExtV3D[1])/100.0;
+ if (this.mDragN > 1.0) this.mDrag = 1.0;
+ },
+
+ setScale: function(pScaleN)
+ {
+ this.mScaleN = pScaleN;
+ this.mScaledExtV3D = V3DP.multNum(this.mExtV3D, this.mScaleN);
+ this.mScaledMassN = this.mMassN * this.mScaleN;
+ this.mIScaledMassN = 1.0/this.mScaledMassN;
+ },
+
+ stepVel: function(pDeltaTimeN, pIDeltaTimeN)
+ {
+ this.mVelV3D[0] *= this.mDampingN;
+ this.mVelV3D[1] *= this.mDampingN;
+ this.mVelV3D[2] *= this.mDampingN;
+ this.mDisplaceV3D[0] *= this.mDampingN;
+ this.mDisplaceV3D[1] *= this.mDampingN;
+ this.mDisplaceV3D[2] *= this.mDampingN;
+
+
+ if (!this.mNoForcesB)
+ {
+ //Euler Integration
+// var accelV3D = this.calcAcceleration(this.mVelV3D);
+// this.mVelV3D[0] += accelV3D[0] * pDeltaTimeN;
+// this.mVelV3D[1] += accelV3D[1] * pDeltaTimeN;
+// this.mVelV3D[2] += accelV3D[2] * pDeltaTimeN;
+
+ //Runge Kutta Second Order Integration
+ var k1V3D = this.calcAcceleration(this.mVelV3D, this.mPosV3D);
+ var nextVelV3D = [this.mVelV3D[0] + (k1V3D[0] * pDeltaTimeN),
+ this.mVelV3D[1] + (k1V3D[1] * pDeltaTimeN),
+ this.mVelV3D[2] + (k1V3D[2] * pDeltaTimeN)];
+ var nextPosV3D = [this.mPosV3D[0] + (this.mVelV3D[0] * pDeltaTimeN),
+ this.mPosV3D[1] + (this.mVelV3D[1] * pDeltaTimeN),
+ this.mPosV3D[2] + (this.mVelV3D[2] * pDeltaTimeN)];
+ var k2V3D = this.calcAcceleration(nextVelV3D, nextPosV3D);
+ var mult = pDeltaTimeN/2.0;
+ this.mVelV3D[0] += (k1V3D[0] + k2V3D[0]) * mult;
+ this.mVelV3D[1] += (k1V3D[1] + k2V3D[1]) * mult;
+ this.mVelV3D[2] += (k1V3D[2] + k2V3D[2]) * mult;
+ }
+
+ var l = this.mExternalImpulsesA.length;
+ if ( l > 0)
+ {
+ for (var i = 0; i < l; i++)
+ {
+ this.mVelV3D[0] += this.mExternalImpulsesA[i][0] * pIDeltaTimeN * this.mIMassN;
+ this.mVelV3D[1] += this.mExternalImpulsesA[i][1] * pIDeltaTimeN * this.mIMassN;
+ this.mVelV3D[2] += this.mExternalImpulsesA[i][2] * pIDeltaTimeN * this.mIMassN;
+ }
+ this.mExternalImpulsesA = [];
+ }
+ },
+
+ stepPos: function(pDeltaTimeN)
+ {
+ this.mPosOldV3D[0] = this.mPosV3D[0];
+ this.mPosOldV3D[1] = this.mPosV3D[1];
+ this.mPosOldV3D[2] = this.mPosV3D[2];
+
+ this.mMaxOldV3D[0] = this.mMaxV3D[0];
+ this.mMaxOldV3D[1] = this.mMaxV3D[1];
+ this.mMaxOldV3D[2] = this.mMaxV3D[2];
+
+ this.mMinOldV3D[0] = this.mMinV3D[0];
+ this.mMinOldV3D[1] = this.mMinV3D[1];
+ this.mMinOldV3D[2] = this.mMinV3D[2];
+
+
+ // Integration
+ this.mDisplaceV3D[0] = (this.mVelV3D[0] + this.mBiasedVelV3D[0]) * pDeltaTimeN;
+ this.mDisplaceV3D[1] = (this.mVelV3D[1] + this.mBiasedVelV3D[1]) * pDeltaTimeN;
+ this.mDisplaceV3D[2] = (this.mVelV3D[2] + this.mBiasedVelV3D[2]) * pDeltaTimeN;
+
+ this.mPosV3D[0] += this.mDisplaceV3D[0];
+ this.mPosV3D[1] += this.mDisplaceV3D[1];
+ this.mPosV3D[2] += this.mDisplaceV3D[2];
+
+ this.mScaledB = false;
+ if (this.mNewScaleN != 0.0)
+ {
+ this.setScale(this.mNewScaleN);
+ this.updateDrag();
+ this.mNewScaleN = 0.0;
+ this.mScaledB = true;
+
+ }
+ this.mMaxV3D[0] = this.mPosV3D[0] + this.mScaledExtV3D[0];
+ this.mMaxV3D[1] = this.mPosV3D[1] + this.mScaledExtV3D[1];
+ this.mMaxV3D[2] = this.mPosV3D[2] + this.mScaledExtV3D[2];
+
+ this.mMinV3D[0] = this.mPosV3D[0] - this.mScaledExtV3D[0];
+ this.mMinV3D[1] = this.mPosV3D[1] - this.mScaledExtV3D[1];
+ this.mMinV3D[2] = this.mPosV3D[2] - this.mScaledExtV3D[2];
+ },
+
+ externUpdatePos: function(pNewPosV3D, pIDeltaTimeN){
+ var matv = V3DP;
+ this.mPosOldV3D[0] = this.mPosV3D[0];
+ this.mPosOldV3D[1] = this.mPosV3D[1];
+ this.mPosOldV3D[2] = this.mPosV3D[2];
+
+ this.mMaxOldV3D[0] = this.mMaxV3D[0];
+ this.mMaxOldV3D[1] = this.mMaxV3D[1];
+ this.mMaxOldV3D[2] = this.mMaxV3D[2];
+
+ this.mMinOldV3D[0] = this.mMinV3D[0];
+ this.mMinOldV3D[1] = this.mMinV3D[1];
+ this.mMinOldV3D[2] = this.mMinV3D[2];
+
+ this.mPosV3D = pNewPosV3D;
+ this.mDisplaceV3D = matv.sub(pNewPosV3D, this.mPosOldV3D);
+ this.mVelV3D = matv.multNum(this.mDisplaceV3D, pIDeltaTimeN);
+
+ this.mScaledB = false;
+ if (this.mNewScaleN != 0.0)
+ {
+ this.setScale(this.mNewScaleN);
+ this.updateDrag();
+ this.mNewScaleN = 0.0;
+ this.mScaledB = true;
+
+ }
+ this.mMaxV3D[0] = this.mPosV3D[0] + this.mScaledExtV3D[0];
+ this.mMaxV3D[1] = this.mPosV3D[1] + this.mScaledExtV3D[1];
+ this.mMaxV3D[2] = this.mPosV3D[2] + this.mScaledExtV3D[2];
+
+ this.mMinV3D[0] = this.mPosV3D[0] - this.mScaledExtV3D[0];