|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis 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 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 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 In 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, 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 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 scriptsEach 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:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||