Click here to Skip to main content
Click here to Skip to main content

Physical Phenomena

, 22 Feb 2005
Rate this:
Please Sign up or sign in to vote.
JavaScript simulation of common physical phenomena -- Newtonian basics and 3D Engine. Favourite of Kevin Fortuna.

Sample screenshot

Introduction

This collection of JavaScripts represents my attempt at modeling basic physical phenomena (rotation of rigid bodies, 3D rotation, generation of n-sided polygons, collision, pendula, and spring action) using only JavaScript and HTML.

All are viewable at joey fortuna's site.

JavaScript served as a good modeling language for the algorithms, since IE handles all of the animation and rendering and I could concentrate on the math alone. I should note, for the record, that these scripts are not cross-browser -- mostly because they weren't really intended to be web presentations, just exercises.

Most of the source is self-explanatory and pretty heavily-commented by me for me as I was plodding through this.

About the 3D "Engine"

The 3D code is probably the most basic way of generating a 3D image. A routine NGON_init is fed a series of three values, which represent x, y and z coordinates. Each triplet is made into a point object (if that point doesn't already exist). Since the monitor space is set up as the fourth quadrant of a Cartesian axis, the Cartesian values are also stored in the point object for reference later.

At every third point, a face object is created. I'm using VML to draw the faces. One pitfall with VML is forgetting this patch of code at the top of the page:

<HTML xmlns:v="urn:schemas-microsoft-com:vml">
<STYLE>
v\:* { behavior: url(#default#vml); }
</STYLE>

The actual triangle for each face is created when the face object is created (function nGonFaceObj) like this:

var objShape=document.body.appendChild(document.createElement("v:shape"));
objShape.style.position="absolute";
objShape.style.display="inline";
objShape.style.zindex="-1";
objShape.fillcolor="silver";
objShape.coordorigin="0,0";
this.shape=objShape;

I believe the graphics for this system could be easily transposed to DirectX or OpenGL.

The essential routines for 3D are NGON_rotate() and faceNormal().

In NGON_rotate, first check to see along which axis the user is rotating the figure. This is passed as a 1 or 0 to the function itself:

function NGON_rotate(xRotate,yRotate,zRotate) {

var xRotateAngle=(-yRotate)*(iRotationAngle*Math.PI)/180;
var yRotateAngle=(-xRotate)*(iRotationAngle*Math.PI)/180;
var zRotateAngle=(-zRotate)*(iRotationAngle*Math.PI)/180;

In the above block, iRotationAngle is a global constant that I've set to 10. By multiplying it by PI and dividing by 180, you turn the angle of 10 degrees into radians, which the trig functions of the JavaScript Math class like to work with.

The most important step in rotating in 3D is the following:

var z=pointObj.z;
var newy=y;
var newx=x;
var newz=z;

newy=(y*Math.cos(xRotateAngle))-(z*Math.sin(xRotateAngle));
newz=(z*Math.cos(xRotateAngle))+(y*Math.sin(xRotateAngle));
y=newy;
z=newz;

newz=(z*Math.cos(yRotateAngle))-(x*Math.sin(yRotateAngle));
newx=(x*Math.cos(yRotateAngle))+(z*Math.sin(yRotateAngle));
z=newz;
x=newx;

newx=(x*Math.cos(zRotateAngle))-(y*Math.sin(zRotateAngle));
newy=(y*Math.cos(zRotateAngle))+(x*Math.sin(zRotateAngle));
x=newx;
y=newy;

This step performs vector math on the three axes of each point in the object. The implications of this are most visible in the 3D Engine - Points script which only displays the points.

The next step in rotating and displaying the 3D object is projecting it from a parallel projection (press "p" when viewing the objects to see the difference) with no perspective distortion to a perspective projection.

pointObj.scrX = parseInt(((iFov*newx) / (iDistance-(newz+this.centerZ))) + 
                                         (this.centerX));
pointObj.scrY = parseInt((this.centerY) - ((iFov*newy) / 
                          (iDistance-(newz+this.centerZ)))); 
pointObj.cartX=pointObj.scrX-this.centerX;
pointObj.cartY=pointObj.scrY-this.centerX;
pointObj.cartY=-newy;

There's a little elfin magic in this as the iFov (Field-of-view) values and iDistance values may need to be tweaked to suit your purposes. You can achieve any effect from fish-eye lens all the way down to straight parallel projection by playing with these values.

The last step is backface culling which is done by calculating the face normals and determining whether or not they're aimed into the receding distance. This is where the Cartesian values for each point really come in handy, since performing the fourth quadrant translation can get kind of tedious here.

var sx1=point1.cartX-point2.cartX;
var sy1=point1.cartY-point2.cartY;
var sz1=point1.z-point2.z; 
var sx2=point3.cartX-point4.cartX;
var sy2=point3.cartY-point4.cartY;
var sz2=point3.z-point4.z; 
var dpx = sy1 * sz2 - sy2 * sz1;
var dpy = sx1 * sz2 - sx2 * sz1;
var dpz = sx1 * sy2 - sx2 * sy1; 
var dprod = 0 * dpx + 0 * dpy + iFov*(dpz/iDistance);

This, again, is a vector operation -- specifically, the cross product of any two of the face vectors (sides, here). You must calculate the edge vectors originating from the same point, otherwise your normal will be inverted and your backface will show up. This had me flummoxed for a day.

Collectively, these scripts are a favorite of Kevin Fortuna.

The other scripts

Each page stands on its own with the exception of the "Newtonia" section, which is an object-oriented worldspace that allows for on-the-fly creation of pendula, springs, or static objects to be dropped into the same 2D world with each other.

Newtonia's collision detection relies on the idea of bounding boxes, and that the center of colliding objects intersect at a line that is of a certain angle from the horizon. This uses polar coordinates to determine where, along the edge of each object, the point of collision lies. Since these are bounding boxes, I have to kind of fudge it by checking where the colliding object is with respect to the...uh...other colliding object.

Here's the essential code:

function checkCollide(obj1,obj2) {
    var obj1CX=obj1.x+(obj1.width/2);
    var obj1CY=obj1.y+(obj1.height/2);
    var obj2CX=obj2.x+(obj2.width/2);
    var obj2CY=obj2.y+(obj2.height/2); 
    var dx=obj2CX-obj1CX;
    var dy=obj1CY-obj2CY;
    var theta=obj1.angleToCorner;
    var alpha=Math.atan(dy/dx);
    var r=Math.sqrt(dx*dx+dy*dy);
    var a,b,e,f,g,h;
    var c=0;
    var d=0; 
    a=0;
    b=0;
    g=dx;
    h=dy; 
    if ((alpha>theta || alpha<-theta) && dy>=0) d=obj1.height/2;
    else if ((alpha>theta ||alpha<-theta) && dy<0) d=-(obj1.height/2);
    else if ((alpha<theta && alpha>-theta) && dx>=0) c=(obj1.width/2);
    else if ((alpha<theta && alpha>-theta) && dx<0) c=-(obj1.width/2);
    if (d==0 && c!=0) d=b+((c-a)*((h-b)/(g-a)));
    else if (c==0 && d!=0) c=a+((d-b)*((g-a)/(h-b)));
    var dobj1x=c-a;
    var dobj1y=d-b; 
    obj1.r=Math.sqrt((dobj1x*dobj1x)+(dobj1y*dobj1y));
}
function collide(obj1,obj2) {
    var vec=new Vector(0,0);
    var obj1CX=obj1.x+(obj1.width/2);
    var obj1CY=obj1.y+(obj1.height/2);
    var obj2CX=obj2.x+(obj2.width/2);
    var obj2CY=obj2.y+(obj2.height/2); 
    var dx=obj2CX-obj1CX;
    var dy=obj1CY-obj2CY; 
    var r=Math.sqrt(dx*dx+dy*dy);
    checkCollide(obj1,obj2);
    checkCollide(obj2,obj1); 
    if ((obj1.r+obj2.r)>=r) {
    if (dx<0) vec.x=1;
    else if (dx>0) vec.x=-1;
    if (dy<0) vec.y=-1;
    else if (dy>0) vec.y=1;
    return vec;
}
    else return vec;
}

It's easier with two bounding circles. For example, here's a script that will follow your mouse around the circumference of a circle. Think of the coordinates of the mouse cursor as the center of another object, and you'll get the sense of how this could be used for collisions.

<script>

var r=150;
var cx=400;
var cy=400;

function MoveHandler(e) {
    var mouseX=window.event.x;
    var mouseY=window.event.y;
    setPoints(mouseX,mouseY);
}

function signOf(val) {
    return Math.abs(val)/val;
}
function setPoints(mx,my) {
    var dx=mx-cx;
    var dy=my-cy;
    if (dx==0) dx=1;
    var theta=Math.atan(dy/dx);
    var newX=signOf(dx)*r*Math.cos(theta)+cx;
    var newY=signOf(dx)*r*Math.sin(theta)+cy;


    eye.style.left=newX;
    eye.style.top=newY;

}

document.onmousemove = MoveHandler;

</script>

The other scripts in this collection are borrowed from the math in Newtonia or the 3D engine. Kevin Fortuna particularly liked the Spinning Menu. Here's a quick description of the other scripts:

  • Spinning Menu - this displays a "Lazy-Susan" style menu which spins from the background to the foreground, fading as it recedes.
  • N-Gon - this allows the user to create an n-sided polygon in two or three dimensions. Rotation and zooming is also enabled. The objects are drawn in wire-frame. The code uses DHTML and VML.
  • N-Gon Filled - this script has the same feature set as N-Gon, with the difference that the polygons are filled. Also uses DHTML and VML.
  • Ole' Footeye - this demonstrates spring properties in the figure of a disembodied foot sporting a single bouncing eye.
  • Nice Shootin' - this script demonstrates principles of gravity and trajectory using DHTML.

Sample screenshot

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author


Comments and Discussions

 
GeneralSource files not found Pinmemberdnunes5-Mar-05 5:03 
GeneralRe: Source files not found PinmemberJoey Fortuna5-Mar-05 5:18 
Sorry about that. Not sure why you saw that error. The source came through okay for me.
 
CodeProject Editors? Any Idea?
 
Meanwhile, you can always grab the source from my site:
 
http://neoncelery.com/joey
 
Thanks for checking out the code! I hope it's useful to you.
 
Joey Fortuna
GeneralRe: Source files not found Pinmemberdnunes5-Mar-05 6:53 
GeneralRe: Source files not found PinmemberJoey Fortuna5-Mar-05 8:10 
GeneralError : Newtonia :Invalid character then object expected Pinmembermagister23-Feb-05 23:47 
Questionimage? PinmemberDanielHac10-Feb-05 14:17 
GeneralGreat work! - Not cross browser. PinmemberSteven Bristol9-Feb-05 2:16 
GeneralA bit short PinmemberWillemM8-Feb-05 21:27 
GeneralRe: A bit short PinmemberWillemM18-Feb-05 20:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 22 Feb 2005
Article Copyright 2005 by Joey Fortuna
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid