- Simple rendering
- Simple physics
- Collision detection and handling
- Per-object collision detection
- 200 objects bouncing off each other
- Tests: collision-detect.js • collision-response.js • collision-world.js • cubic.js • quadratic.js • quartic.js

Every object starts off as a shape (`nmlorg.shape.Shape`

). A shape contains geometry,
color information, etc. Each shape exists on its own, independent of a canvas, world, etc., and
does not have a position or physics model.

var shape = new nmlorg.shape.Shape( ['position', 'color'], 'triangles', [// [position ] [color ] x1, y1, z1, r1, g1, b1, 1, x2, y2, z2, r2, g2, b2, 1, x3, y3, z3, r3, g3, b3, 1, ]);

Shapes are coupled with a 3D position inside a world to form a position object (`nmlorg.world.PositionObject`

). A single
shape can be added to a world multiple times, creating a new position object each time. When a world is
rendered, every shape is sent to the graphics card once, then it is repeatedly painted for every
position object.

var positionObject1 = world.addObject(shape); var positionObject2 = world.addObject(shape); positionObject1.translate(x1, y1, z1); positionObject2.translate(x2, y2, z2);

Each world is a collection of objects and canvases (`nmlorg.gl.Canvas`

). When a shape is added to a world,
its geometry, color, etc. information (as vertex buffers) is added to each canvas. When the world is
rendered, each object is rendered to each canvas.

var canvas1 = new nmlorg.gl.Canvas(width1, height1); var canvas2 = new nmlorg.gl.Canvas(width2, height2); var world = new nmlorg.world.World([canvas1, canvas2]);

The world can be rendered once by calling `world.draw()`

. The world can be set to render
itself repeatedly (using the browser's `requestAnimationFrame`

) using `world.start()`

. Custom code can be run once
for each animation frame by setting the world's `eachFrame`

.

world.eachFrame = function(timeStep) { positionObject1.translate(0, 0, -1); positionObject2.translate(2, 0, 0); };

A custom animation function can be called for each position object by passing it to the constructor
(directly or as the optional second argument to `world.addObject`

). The position object
will be available as `this`

and any arguments passed after the animation function will be
passed back into the function, followed by a value containing the amount of time (in seconds) since the
previous frame began rendering.

function animateObject(x, timeStep) { this.translate(x[0], x[1], x[2]); } var positionObject1 = world.addObject(shape, animateObject, [0, 0, -1]); var positionObject2 = world.addObject(shape, animateObject, [2, 0, 0]);

physics.js provides an object-management system
completely independent from the renderer. Typically, a physical object (`nmlorg.physics.Object`

) will be created
at the same time as a naive position object (`nmlorg.world.PositionObject`

, via
`world.addObject`

), then the
position object's `animate`

callback will tell the physical object to calculate its motion
and then copy the physical object's current position to the position object.

function animateObject(obj, timeStep) { obj.compound(timeStep); this.setPosition(obj.position[0], obj.position[1], obj.position[2]); } for (var i = 0; i < 2; i++) { var physicalObject = new nmlorg.physics.Object(x, y, z); var positionObject = world.addObject(shape, animateObject, physicalObject); }

Each physical object maintains its current position in 3D space along with its current velocity
(measured in pixels per second) and a list of zero or more forces that apply to the object. The `compound`

call is responsible for first
adjusting the object's velocity based on the active forces, and then updating the object's position
based on its velocity. Forces may either be indefinite (such as the simplified force of gravity) or
pre-set for a fixed duration (such as the force from a rocket thruster with a fixed amount of fuel).
[[At present, there is no way to add a force and have it disabled programmatically. To effect that you
would need to re-schedule the force before every call to `compound`

with a fixed duration set to
the timeStep.]] If a fixed-duration force expires during the current frame (if its remaining duration is
less than timeStep), the frame is divided into a subframe lasting until the force expires, then the
remaining time left in the frame, with the correct average velocity applied to the position for each
subframe. If two forces expire during the current frame (at different times), the frame is divided into
`[(0, firstExpiration), (firstExpiration, secondExpiration), (secondExpiration, timeStep)]`

;
and so on.

Forces are registered using ```
addForce(xAccel, yAccel, zAccel, optional
duration)
```

. To roughly simulate Earth gravity (at the scale of 1 meter = 10 pixels), you can
call `addEarthGravity`

.

function animateObject(obj, timeStep) { obj.compound(timeStep); this.setPosition(obj.position[0], obj.position[1], obj.position[2]); } for (var i = 0; i < 2; i++) { var physicalObject = new nmlorg.physics.Object(x, y, z); var positionObject = world.addObject(shape, animateObject, physicalObject);physicalObject.addEarthGravity();}

The physics object-management system can be thought of as keeping track of the positions and
velocities of the **centers** of objects in the physical model. An independent collision
object-management system (provided by collision-detect.js, collision-response.js, and collision-world.js) extends this model to include a
simplified shape and material behavior for each object. (The collision shape is intentionally kept
separate from the geometry defined by the world renderer's own object manager to simplify the math. In
many cases, individual objects will be modeled as spheres to calculate collisions, even if they are
cuboid or some other complex shape when drawn to the screen.)

The two functions of the collision system are 1. to detect (predict) when two objects' surfaces are touching such that, without adjusting their velocities, they will pass through each other, and 2. to adjust the velocities of one or both objects at the time of collision to prevent them from passing through each other.

If the world contains two objects, they will either collide in the current frame or they won't. If
they do, the frame needs to be subdivided into ```
[(0, timeToCollision), (timeToCollision,
timeStep)]
```

with an adjustment to one or both object's velocities at the collision point.

If the world contains three objects, there can be 1. no collisions, 2. a single collision between
obj1 and obj2, 3. a single collision between obj1 and obj3, 4. a single collision between obj2 and obj3,
5. a single collision between obj1, obj2, and obj3, 6. a single collision between obj1 and obj2 followed
by a single collision between obj1 and obj3, 7. a single collision between obj1 and obj3 followed by a
single collision between obj1 and obj2, 8. a single collision obetween obj1 and obj2 followed by a
single collision between obj1 and obj3 followed by a single collision between obj1 and obj2, and so on.
In the case of three colinear objects, with obj1 moving toward both obj2 and obj3 but elastically
striking obj2 first, at the start of the frame a collision between obj1 and obj3 will be predicted that
never occurs. In the case of three colinear objects, with obj1 moving toward obj2 and away from obj3,
after an elastic collision with the immovable obj2, obj1 will begin moving toward and eventually collide
with obj3, which could not have been predicted based on the objects' velocities at the start of the
frame. Therefore, the world needs to predict the earliest collision first, adjust all colliding objects'
velocities, and then completely restart the collision detection process, until there are no more
collisions before the time remaining in the current frame. This is similar to the physical object's `compound`

, which subdivides the current
frame to calculate the object's changes to velocity and position with variable acceleration; however, to
perform accurate collision detection, all objects must be checked at once.

function animateObject(obj, timeStep) {~~obj.compound(timeStep);~~this.setPosition(obj.position[0], obj.position[1], obj.position[2]); }var collisionWorld = new nmlorg.collision.World();for (var i = 0; i < 2; i++) { var physicalObject = new nmlorg.physics.Object(x, y, z); var positionObject = world.addObject(shape, animateObject, physicalObject); physicalObject.addEarthGravity();collisionWorld.addObject(physicalObject, ...);}world.eachFrame = function(timeStep) { collisionWorld.compound(timeStep); };

To predict when two objects are colliding, the collision system needs to know both the positions of
both objects' centers (`nmlorg.physics.Object`

) and their shapes. To
simplify the math of collision detection, all objects are modeled as either spheres ~~or 2D
surfaces~~ via `nmlorg.collision.Shape`

subclasses (`nmlorg.collision.Sphere`

).

var collisionWorld = new nmlorg.collision.World();var collisionShape = new nmlorg.collision.Sphere(radius);for (var i = 0; i < 2; i++) { var physicalObject = new nmlorg.physics.Object(x, y, z); var positionObject = world.addObject(shape, animateObject, physicalObject); physicalObject.addEarthGravity(); collisionWorld.addObject(physicalObject,collisionShape,...); }

Two objects are "colliding" when their surfaces are touching, and two spherical objects are colliding when the distance between their centers is equal to the sum of their radii:

sqrt((obj1.pos[t].x - obj2.pos[t].x)^{2}+ (obj1.pos[t].y - obj2.pos[t].y)^{2}+ (obj1.pos[t].z - obj2.pos[t].z)^{2}) = obj1.radius + obj2.radius

Assuming constant velocity, the equation for the position of an object at a given time is:

obj.pos[t] = obj.pos[0] + obj.vel * t

To simplify the math, assume our frame of reference is the second object. (That is, when the camera
is fixed over the origin, if object 1 is moving to the right and object 2 is moving down, we can
calculate the same motion if the camera is fixed over the second object, with object 1 moving to the
right and up.) The second object will have a relative position and velocity of 0, and the first object
will have a relative position of `obj1.pos - obj2.pos`

and velocity of ```
obj1.vel -
obj2.vel
```

.

pos = obj1.pos - obj2.pos vel = obj1.vel - obj2.vel

Performing the substitutions and solving for t:

sqrt((pos.x + vel.x * t)

^{2}+ (pos.y + vel.y * t)^{2}+ (pos.z + vel.z * t)^{2}) = obj1.radius + obj2.radius(pos.x + vel.x * t)

^{2}+ (pos.y + vel.y * t)^{2}+ (pos.z + vel.z * t)^{2}= (obj1.radius + obj2.radius)^{2}pos.x

^{2}+ 2 * pos.x * vel.x * t + (vel.x * t)^{2}+ pos.y^{2}+ 2 * pos.y * vel.y * t + (vel.y * t)^{2}+ pos.z^{2}+ 2 * pos.z * vel.z * t + (vel.z * t)^{2}= (obj1.radius + obj2.radius)^{2}pos.x

^{2}+ 2 * pos.x * vel.x * t + vel.x^{2}* t^{2}+ pos.y^{2}+ 2 * pos.y * vel.y * t + vel.y^{2}* t^{2}+ pos.z^{2}+ 2 * pos.z * vel.z * t + vel.z^{2}* t^{2}= (obj1.radius + obj2.radius)^{2}- Factor out t
^{2}:t

^{2}* (vel.x^{2}+ vel.y^{2}+ vel.y^{2}) + pos.x^{2}+ 2 * pos.x * vel.x * t + pos.y^{2}+ 2 * pos.y * vel.y * t + pos.z^{2}+ 2 * pos.z * vel.z * t = (obj1.radius + obj2.radius)^{2} - Factor out t:
t

^{2}* (vel.x^{2}+ vel.y^{2}+ vel.y^{2}) + t * (2 * pos.x * vel.x + 2 * pos.y * vel.y + 2 * pos.z * vel.z) + pos.x^{2}+ pos.y^{2}+ pos.z^{2}= (obj1.radius + obj2.radius)^{2} t

^{2}* (vel.x^{2}+ vel.y^{2}+ vel.y^{2}) + t * (2 * pos.x * vel.x + 2 * pos.y * vel.y + 2 * pos.z * vel.z) + pos.x^{2}+ pos.y^{2}+ pos.z^{2}- (obj1.radius + obj2.radius)^{2}= 0

This is now a quadratic equation with:

a = vel.x^{2}+ vel.y^{2}+ vel.y^{2}b = 2 * pos.x * vel.x + 2 * pos.y * vel.y + 2 * pos.z * vel.z c = pos.x^{2}+ pos.y^{2}+ pos.z^{2}- (obj1.radius + obj2.radius)^{2}

A quadratic equation solver is available as `nmlorg.quadratic.solve`

.

obj.pos[t] = obj.pos[0] + obj.vel[0] * t + (1/2) * obj.accel * t^{2}

Similar to the constant velocity case:

pos = obj1.pos - obj2.pos vel = obj1.vel - obj2.vel accel = obj1.accel - obj2.accel

Performing the substitutions and solving for t:

sqrt((pos.x + vel.x * t + accel.x * t

^{2}/ 2)^{2}+ (pos.y + vel.y * t + accel.y * t^{2}/ 2)^{2}+ (pos.z + vel.z * t + accel.z * t^{2}/ 2)^{2}) = obj1.radius + obj2.radius(pos.x + vel.x * t + accel.x * t

^{2}/ 2)^{2}+ (pos.y + vel.y * t + accel.y * t^{2}/ 2)^{2}+ (pos.z + vel.z * t + accel.z * t^{2}/ 2)^{2}= (obj1.radius + obj2.radius)^{2}pos.x

^{2}+ 2 * pos.x * vel.x * t + pos.x * accel.x * t^{2}+ (vel.x * t)^{2}+ vel.x * accel.x * t^{3}+ (accel.x * t^{2})^{2}+ pos.y^{2}+ 2 * pos.y * vel.y * t + pos.y * accel.y * t^{2}+ (vel.y * t)^{2}+ vel.y * accel.y * t^{3}+ (accel.y * t^{2})^{2}+ pos.z^{2}+ 2 * pos.z * vel.z * t + pos.z * accel.z * t^{2}+ (vel.z * t)^{2}+ vel.z * accel.z * t^{3}+ (accel.z * t^{2})^{2}= (obj1.radius + obj2.radius)^{2}pos.x

^{2}+ 2 * pos.x * vel.x * t + pos.x * accel.x * t^{2}+ vel.x^{2}* t^{2}+ vel.x * accel.x * t^{3}+ accel.x^{2}* t^{4}+ pos.y^{2}+ 2 * pos.y * vel.y * t + pos.y * accel.y * t^{2}+ vel.y^{2}* t^{2}+ vel.y * accel.y * t^{3}+ accel.y^{2}* t^{4}+ pos.z^{2}+ 2 * pos.z * vel.z * t + pos.z * accel.z * t^{2}+ vel.z^{2}* t^{2}+ vel.z * accel.z * t^{3}+ accel.z^{2}* t^{4}= (obj1.radius + obj2.radius)^{2}- Factor out t
^{4}:t

^{4}* (accel.x^{2}+ accel.y^{2}+ accel.z^{2}) + pos.x^{2}+ 2 * pos.x * vel.x * t + pos.x * accel.x * t^{2}+ vel.x^{2}* t^{2}+ vel.x * accel.x * t^{3}+ pos.y^{2}+ 2 * pos.y * vel.y * t + pos.y * accel.y * t^{2}+ vel.y^{2}* t^{2}+ vel.y * accel.y * t^{3}+ pos.z^{2}+ 2 * pos.z * vel.z * t + pos.z * accel.z * t^{2}+ vel.z^{2}* t^{2}+ vel.z * accel.z * t^{3}= (obj1.radius + obj2.radius)^{2} - Factor out t
^{3}:t

^{4}* (accel.x^{2}+ accel.y^{2}+ accel.z^{2}) + t^{3}* (accel.x * vel.x + accel.y * vel.y + accel.z * vel.z) + pos.x^{2}+ 2 * pos.x * vel.x * t + pos.x * accel.x * t^{2}+ vel.x^{2}* t^{2}+ pos.y^{2}+ 2 * pos.y * vel.y * t + pos.y * accel.y * t^{2}+ vel.y^{2}* t^{2}+ pos.z^{2}+ 2 * pos.z * vel.z * t + pos.z * accel.y * t^{2}+ vel.z^{2}* t^{2}= (obj1.radius + obj2.radius)^{2} - Factor out t
^{2}:t

^{4}* (accel.x^{2}+ accel.y^{2}+ accel.z^{2}) + t^{3}* (accel.x * vel.x + accel.y * vel.y + accel.z * vel.z) + t^{2}* (pos.x * accel.x + vel.x^{2}+ pos.y * accel.y + vel.y^{2}+ pos.z * accel.z + vel.z^{2}) + pos.x^{2}+ 2 * pos.x * vel.x * t + pos.y^{2}+ 2 * pos.y * vel.y * t + pos.z^{2}+ 2 * pos.z * vel.z * t = (obj1.radius + obj2.radius)^{2} - Factor out t:
t

^{4}* (accel.x^{2}+ accel.y^{2}+ accel.z^{2}) + t^{3}* (accel.x * vel.x + accel.y * vel.y + accel.z * vel.z) + t^{2}* (pos.x * accel.x + vel.x^{2}+ pos.y * accel.y + vel.y^{2}+ pos.z * accel.z + vel.z^{2}) + t * 2 * (pos.x * vel.x + pos.y * vel.y + pos.z * vel.z) + pos.x^{2}+ pos.y^{2}+ pos.z^{2}= (obj1.radius + obj2.radius)^{2}

This is now a quartic equation with:

a = accel.x^{2}+ accel.y^{2}+ accel.z^{2}b = accel.x * vel.x + accel.y * vel.y + accel.z * vel.z c = pos.x * accel.x + vel.x^{2}+ pos.y * accel.y + vel.y^{2}+ pos.z * accel.z + vel.z^{2}d = 2 * (pos.x * vel.x + pos.y * vel.y + pos.z * vel.z) e = pos.x^{2}+ pos.y^{2}+ pos.z^{2}- (obj1.radius + obj2.radius)^{2}

A quartic equation solver is available as `nmlorg.quartic.solve`

. The full
detection algorithm is implemented as `nmlorg.collision.timeToOrigin`

.

Strictly speaking, two objects are in collision not just when they are touching, but when they are touching and their velocities are such that they will pass through each other after the point of collision. To prevent objects from passing through each other, their velocities must be adjusted at the point of collision in some way. The simplest thing to visualize is letting a rubber ball roll out of your hand: After it is initially released, it will move both toward the floor and away from you. Eventually it will come in contact with the floor, at which point its velocity changes so it is moving back up at some fraction of the speed it was moving down just prior to the collision, and still away from you at roughly the same speed. Eventually, after bouncing against the floor several times, it will stop moving up at all but continue moving away from you, simply rolling along the floor.

Now visualize dropping an object the same size, shape, and mass of the rubber ball, but made out of
concrete. After the first collision with the floor, it will simply start rolling, rather than bouncing.
The difference between the two collisions can be modeled as a difference in the objects' material, via
`nmlorg.collision.Material`

.

var collisionWorld = new nmlorg.collision.World(); var collisionShape = new nmlorg.collision.Sphere(radius);var bouncyBall = new nmlorg.collision.Material(elastic, inelastic, heat);for (var i = 0; i < 2; i++) { var physicalObject = new nmlorg.physics.Object(x, y, z); var positionObject = world.addObject(shape, animateObject, physicalObject); physicalObject.addEarthGravity(); collisionWorld.addObject(physicalObject, collisionShape,bouncyBall); }

The three material characteristics `elastic`

, `inelastic`

, and
`heat`

are used to calculate how the object's momentum is affected by a collision. A high
value for `elastic`

means the object will transfer more of its energy to the other object.
(Visualize `(elastic=1, inelastic=0, heat=0)`

as a Newton's cradle that never stops.) A high value
for `inelastic`

means the object will attempt to equalize more of its energy with the other
object. (Visualize `(elastic=0, inelastic=1, heat=0)`

as a piece of wet
clay hitting a skateboard at an angle.) In both elastic and inelastic ideal collisions, the total
momentum of the two objects as a system is preserved; a high value for `heat`

means the
object will dissipate more of its energy into the atmosphere. (Visualize ```
(elastic=0, inelastic=0,
heat=1)
```

as a glass figurine shattering against the floor.) The values given are normalized with
each other, so `(1, 2, 3)`

== `(.01, .02, .03)`

== ```
(.17, .33,
.5)
```

.

With spherical objects, the point of collision is always on the same line as the centers of both objects at the time of impact. This line is the "line of collision".

When two objects collide "head on", where the centers of both objects are moving directly toward each other (i.e. the relative velocity of the colliding objects is parallel to the line of collision), the collision can be resolved as a one-dimensional collision along the line of collision (only the magnitudes of the final velocities need to be calculated, the directionality will be preserved by dividing the original vectors by their magnitudes and then multiplying the final magnitude).

For example, if object 1 at [-100, 0] of radius 30 is moving at [20, 0] and object 2 at [0, 0] of radius 30 is at rest, the line of collision follows the x axis (running through [-60, 0] and [0, 0]) as does the relative velocity ([20, 0]). After an inelastic (clay-on-skateboard) collision, both objects will continue moving along the x axis at velocity [10, 0]:

After an elastic (Newton's cradle) collision, object 1 will be at rest and object 2 will be moving along the x axis at [20, 0]:

However, when two objects collide obliquely, where the relative velocity of the colliding objects is
not parallel to the line of collision, their relative motion must be broken up into colliding and
non-colliding components. The component vector along the line of collision is the relative velocity fed
into the collision response formula (such as `m`

_{1}v_{1} +
m_{2}v_{2} = (m_{1} + m_{2})v

If object 1 at [-100, 0] of radius 30 is moving at [20, 0] and object 2 at [0, 20] of radius 30 is moving at [0, -10], they will collide when object 1 is at [-60, 0] and object 2 is at [0, 0]. After either type of collision, object 1's y-direction velocity will remain 0, and object 2's y-direction velocity will remain -10; this component was tangent to the line of collision (which was along the x axis) and so no energy transfer takes place.

Inelastic:

Elastic:

If object 1 at [-96, 18] of radius 30 is moving at [20, 0] and object 2 at [0, 20] of radius 30 is moving at [0, -10], they will collide when object 1 is at [-56, 18] and object 2 is at [0, 0]:

This time, the line of collision does not follow the same axes as the component vectors, so it is simplest to combine them into a single relative velocity (for example, object 1 moving at [20, 10] and object 2 at rest):

Then break the relative velocity into components in a new two-dimensional coordinate system with the line of collision as the x axis. First calculate the angle of the line of collision from the original x axis: Form a right triangle with a hypotenuse running between the centers of both objects and a point at the y position of the center of object 1 and the x position of the center of object 2. Calculate the angle using SOHCAHTOA:

tan(angle_{LOC}) = (obj2.pos.y - obj1.pos.y) / (obj2.pos.x - obj1.pos.x) angle_{LOC}= atan2(obj2.pos.y - obj1.pos.y, obj2.pos.x - obj1.pos.x) angle_{LOC}= atan2(0 - 18, 0 - -56) angle_{LOC}= atan2(-18, 56) angle_{LOC}= -17.8189°

Next calculate the angle of the relative velocity vector from the original x axis: Form a right triangle with the origin as one point, the relative velocity vector as a second, and a point at the velocity vector's x component along the x axis for the third. Calculate the angle using SOHCAHTOA:

tan(angle_{vel}) = vel.y / vel.x angle_{vel}= atan2(vel.y, vel.x) [atan(vel.y / vel.x)] angle_{vel}= atan2(10, 20) angle_{vel}= 26.5651°

To move into the alternate coordinate system, subtract angle_{LOC} from angle_{vel}.
Use this new angle to form a right triangle with a hypotenuse equal in length to the magnitude of the
relative velocity vector (`sqrt(vel.x`

), and solve for the
other sides; these are your x (colliding) and y (non-colliding) components.^{2} + vel.y^{2})

mag = sqrt(vel.x^{2}+ vel.y^{2}) mag = sqrt(20^{2}+ 10^{2}) mag = sqrt(400 + 100) mag = 22.3607 sin(angle_{vel}- angle_{LOC}) = non-colliding / mag sin(26.5651 - -17.8189) = non-colliding / 22.3607 22.3607 * sin(44.3840) = non-colliding non-colliding = 15.6405 cos(angle_{vel}- angle_{LOC}) = colliding / mag cos(26.5651 - -17.8189) = colliding / 22.36 22.3607 * cos(44.3840) = colliding colliding = 15.9805

Apply the objects' materials' collision response formula to the x (colliding) component, then add the original y (non-colliding) component back into the result, reverse the coordinate system shift, and re-separate the final relative velocity into object 1's and object 2's.

When obj1.pos.z != obj2.pos.z, the simple angle subtraction separation won't quite cut it. To fully
support arbitrary 3D position and velocity differences, collision-response.js separates out the
colliding component using a rotation matrix (shifting [dx, dy, dz] first to the plane defined by the x
and z axes, then to the x axis), available as `nmlorg.collision.transformVelocity`

and `nmlorg.collision.restoreVelocity`

.
The full response algorithm is implemented as `material.handleCollision`

.