Click here to Skip to main content
15,896,398 members
Articles / Web Development / HTML

Physical Phenomena

Rate me:
Please Sign up or sign in to vote.
3.55/5 (12 votes)
22 Feb 20054 min read 53.4K   528   16  
JavaScript simulation of common physical phenomena -- Newtonian basics and 3D Engine. Favourite of Kevin Fortuna.
<HTML xmlns:v="urn:schemas-microsoft-com:vml">
<STYLE>
v\:* { behavior: url(#default#vml); }
</STYLE>

<head>

<style>
	button { font-face:arial;font-size:10px;border:1px solid black;}
	input { font-face:arial;font-size:10px;border:1px solid black;}
</style>

<script>
	/********************************************/
	/*********************************************
		3D Engine - Faces
		BY JOEY FORTUNA
		http://neoncelery.com
		COPYRIGHT (C) 2004, Joey Fortuna
		PERMISSION TO USE GRANTED PROVIDING THE
		ADOPTER INCLUDES THIS NOTICE
	*********************************************/
	/********************************************/
var nGon;
var moveX=0;
var oldMoveX=0;
var moveY=0;
var oldMoveY=0;
var xRotate=0;
var yRotate=0;
var bRightMouseDown=false;
var bLeftMouseDown=false;
var iDistance=525;
var iFov=590;
var iLineYDiff=60;
var iTimeoutId=0;
var iRotationAngle=5;
var iZoom=0;

function drawPolygon() {
		nGon=new nGonObj();
		getWinSize();
	/*******************
			X-WING FIGHTER
			point coords
			..probably better to define faces in triplets
			NOTE: the order of points matters somehow for face normals.
					example:
						 -25,-75,25 -50,-50,-50 -50,-50,50 works properly, but:
						  -25,-75,25 -50,-50,50 -50,-50,-50 does not
		********************/
		var strPoints="";
		strPoints+="-25,-75,25 25,-75,25 25,-75,-25";  // top
		strPoints+=" 25,-75,-25 -25,-75,-25 -25,-75,25"; //top

		strPoints+=" 25,-75,25 50,-50,50 50,-50,-50"; // right side
		strPoints+=" 50,-50,-50 25,-75,-25 25,-75,25"; // right side

		strPoints+=" -50,-50,50 50,-50,50 -25,-75,25"; //front
		strPoints+=" 50,-50,50 25,-75,25 -25,-75,25"; //front


		strPoints+=" -50,-50,-50 -25,-75,-25 50,-50,-50"; //back
		strPoints+=" 50,-50,-50 -25,-75,-25 25,-75,-25"; //back

		strPoints+=" -25,-75,25 -50,-50,-50 -50,-50,50"; // left side
		strPoints+=" -50,-50,-50 -25,-75,25 -25,-75,-25"; //left side

		strPoints+=" 50,-50,-50 65,-25,50 65,-25,-50"; // long right side
		strPoints+=" 50,-50,-50 50,-50,50 65,-25,50"; // long right side

		strPoints+=" -50,-50,-50 -65,-25,-50 -65,-25,50"; // long left side
		strPoints+=" -50,-50,-50 -65,-25,50 -50,-50,50"; // long left side


		strPoints+=" -50,-50,50 -50,-25,65 50,-25,65"; // long front
		strPoints+=" -50,-50,50 50,-25,65 50,-50,50"; // long front


		strPoints+=" -50,-50,-50 50,-25,-65 -50,-25,-65"; // long back
		strPoints+=" -50,-50,-50 50,-50,-50 50,-25,-65"; // long back

		//*** CORNERS ***/
		strPoints+=" -50,-50,50 -65,-25,50 -50,-25,65";
		strPoints+=" 50,-50,50 50,-25,65 65,-25,50";
		strPoints+=" -50,-50,-50 -50,-25,-65 -65,-25,-50";
		strPoints+=" 50,-50,-50 65,-25,-50 50,-25,-65";


	//* 	BOTTOM 	*/



		strPoints+=" -25,75,25 25,75,-25 25,75,25";  // top
		strPoints+=" 25,75,-25 -25,75,25 -25,75,-25"; //top

		strPoints+=" 25,75,25 50,50,-50 50,50,50"; // right side
		strPoints+=" 50,50,-50 25,75,25 25,75,-25"; // right side
		strPoints+=" 50,50,-50 65,25,-50 65,25,50"; // long right side
		strPoints+=" 50,50,-50 65,25,50 50,50,50"; // long right side
		strPoints+=" 65,-25,50 65,25,50 65,25,-50"; // right main
		strPoints+=" 65,-25,50 65,25,-50 65,-25,-50"; // right main

		strPoints+=" -50,50,50 -25,75,25 50,50,50"; //front
		strPoints+=" 50,50,50 -25,75,25 25,75,25"; //front
		strPoints+=" -50,25,65 50,25,65 -50,-25,65"; //front main
		strPoints+=" -50,-25,65 50,25,65 50,-25,65"; //front main


		strPoints+=" -50,50,-50 50,50,-50 -25,75,-25"; //back
		strPoints+=" 50,50,-50 25,75,-25 -25,75,-25"; //back
		strPoints+=" -50,25,-65 -50,-25,-65 50,25,-65"; //back main
		strPoints+=" -50,-25,-65 50,-25,-65 50,25,-65"; //back main

		strPoints+=" -25,75,25 -50,50,50 -50,50,-50"; // left side
		strPoints+=" -50,50,-50 -25,75,-25 -25,75,25"; //left side
		strPoints+=" -50,50,-50 -65,25,50 -65,25,-50"; // long left side
		strPoints+=" -50,50,-50 -50,50,50 -65,25,50"; // long left side
		strPoints+=" -65,-25,50 -65,25,-50 -65,25,50"; // left main
		strPoints+=" -65,-25,50 -65,-25,-50 -65,25,-50"; // left main


		strPoints+=" -50,50,50 50,25,65 -50,25,65"; // long front
		strPoints+=" -50,50,50 50,50,50 50,25,65"; // long front


		strPoints+=" -50,50,-50 -50,25,-65 50,25,-65"; // long back
		strPoints+=" -50,50,-50 50,25,-65 50,50,-50"; // long back

		//*** CORNERS ***/
		//* short */
		strPoints+=" -50,50,50 -50,25,65 -65,25,50";
		strPoints+=" 50,50,50 65,25,50 50,25,65";
		strPoints+=" -50,50,-50 -65,25,-50 -50,25,-65";
		strPoints+=" 50,50,-50 50,25,-65 65,25,-50";

		//* top */
		strPoints+=" -50,-25,65 -65,25,50 -50,25,65";
		strPoints+=" -50,-25,65 -65,-25,50 -65,25,50";
		strPoints+=" 50,-25,65 50,25,65 65,25,50";
		strPoints+=" 50,-25,65 65,25,50 65,-25,50";
		strPoints+=" -50,-25,-65 -50,25,-65 -65,25,-50";
		strPoints+=" -50,-25,-65 -65,25,-50 -65,-25,-50";
		strPoints+=" 50,-25,-65 65,25,-50 50,25,-65";
		strPoints+=" 50,-25,-65 65,-25,-50 65,25,-50";

		//**** WING STRUTS *****/
		// LEFT
		strPoints+=" -65,10,10 -150,10,10 -150,10,-10";
		strPoints+=" -65,10,10 -150,10,-10 -65,10,-10";
		strPoints+=" -65,-10,10 -150,-10,-10 -150,-10,10";
		strPoints+=" -65,-10,10 -65,-10,-10 -150,-10,-10";
		strPoints+=" -65,10,10 -65,-10,10 -150,10,10";
		strPoints+=" -150,-10,10 -150,10,10 -65,-10,10";
		strPoints+=" -65,10,-10 -150,10,-10 -65,-10,-10";
		strPoints+=" -150,-10,-10 -65,-10,-10 -150,10,-10";

		// RIGHT
		strPoints+=" 65,10,10 150,10,-10 150,10,10";
		strPoints+=" 65,10,10 65,10,-10 150,10,-10";
		strPoints+=" 65,-10,10 150,-10,10 150,-10,-10";
		strPoints+=" 65,-10,10 150,-10,-10 65,-10,-10";
		strPoints+=" 65,10,10 150,10,10 65,-10,10";
		strPoints+=" 150,-10,10 65,-10,10 150,10,10";
		strPoints+=" 65,10,-10 65,-10,-10 150,10,-10";
		strPoints+=" 150,-10,-10 150,10,-10 65,-10,-10";

		//******WINGS*******/
		strPoints+=" 150,-50,-65 150,50,65 150,-50,65"; //right
		strPoints+=" 150,50,-65 150,50,65 150,-50,-65"; //right
		strPoints+=" 150,50,-65 150,50,65 125,65,-65"; //right
		strPoints+=" 150,50,65 125,65,65 125,65,-65"; //right
		strPoints+=" 150,-50,-65 125,-65,-65 150,-50,65"; //right
		strPoints+=" 150,-50,65 125,-65,-65 125,-65,65"; //right

		strPoints+=" 152,-50,-65 152,-50,65 152,50,65"; //right
		strPoints+=" 152,50,-65 152,-50,-65 152,50,65"; //right
		strPoints+=" 152,50,-65 127,65,-65 152,50,65"; //right
		strPoints+=" 152,50,65 127,65,-65 127,65,65"; //right
		strPoints+=" 152,-50,-65 152,-50,65 127,-65,-65"; //right
		strPoints+=" 152,-50,65 127,-65,65 127,-65,-65"; //right

		strPoints+=" -150,-50,-65 -150,-50,65 -150,50,65"; //left
		strPoints+=" -150,50,-65 -150,-50,-65 -150,50,65"; //left
		strPoints+=" -150,50,-65 -125,65,-65 -150,50,65"; //left
		strPoints+=" -150,50,65 -125,65,-65 -125,65,65"; //left
		strPoints+=" -150,-50,-65 -150,-50,65 -125,-65,-65"; //left
		strPoints+=" -150,-50,65 -125,-65,65 -125,-65,-65"; //left

		strPoints+=" -152,-50,-65 -152,50,65 -152,-50,65"; //left
		strPoints+=" -152,50,-65 -152,50,65 -152,-50,-65"; //left
		strPoints+=" -152,50,-65 -152,50,65 -127,65,-65"; //left
		strPoints+=" -152,50,65 -127,65,65 -127,65,-65"; //left
		strPoints+=" -152,-50,-65 -127,-65,-65 -152,-50,65"; //left
		strPoints+=" -152,-50,65 -127,-65,-65 -127,-65,65"; //left

		nGon.init(strPoints);
		nGon.rotate(0,0,0);
		randomRotate();
}

function randomRotate() {
	var tDate=new Date();
	var ms=Math.random()*100;
	var xrot=0;
	var yrot=0;
	nGon.rotate(1,1,1);
	iTimeoutId=setTimeout("randomRotate()",2);
}
function getWinSize()
	{

			winX = document.body.clientWidth - 10;
			winY = document.body.clientHeight - 10;
			nGon.centerX=winX/2;
			nGon.centerY=winY/2-100;
	}



function nGonPointObj(tid,x,y,z) {
	this.id=tid;
	this.x=x;
	this.y=y;
	this.z=z;
	this.cartX;
	this.cartY;
	this.scrX=x;
	this.scrY=y;
/*
	var newObj=newObj=document.createElement("div");
	newObj.id="nGonPoint"+tid;
	newObj.innerHTML="0";
	newObj.style.position="absolute";
	newObj.style.display="none";
	newObj.style.textAlign="center";
	newObj.style.fontWeight="bold";
	newObj.style.fontSize="16px";
	newObj.style.fontFamily="arial";
	newObj.style.width="20px";
	newObj.style.cursor="hand";
	newObj.style.top=this.y+"px";
	newObj.style.left=this.x+"px";
	var divObj=document.body.appendChild(newObj);
	this.divObj=divObj;
	this.redraw=NGON_POINT_redraw;
*/
	return this;
}

function nGonFaceObj(p1,p2,p3) {
	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;
	this.myname="shape";
	this.p1=p1;
	this.p2=p2;
	this.p3=p3;
	return this;
}
function nGonObj() {
	this.centerX=0;
	this.centerY=0;
	this.centerZ=0;
	this.parallel=false;
	this.name="NGonObject";
	this.nPoints=new Array();
	this.nFaces=new Array();
	this.angleDivisor=0;
	this.angle=0;
	this.rotate=NGON_rotate;
	this.clear=NGON_clear;
	this.addPoint=NGON_addPoint;
	this.addFace=NGON_addFace;
	this.init=NGON_init;
	this.drawFaces=NGON_drawFaces;
	this.hasPoint=NGON_hasPoint;
	return this;
}

function NGON_POINT_redraw() {

	this.divObj.style.left=this.scrX+"px";
	this.divObj.style.top=this.scrY+"px";
}
function NGON_hasPoint(strPointId) {
	var k=0;
	for (k=0;i<this.nPoints.length;k++) {
		if (this.nPoints[k].id==strPointId) return true;
	}
	return false;
}


function NGON_init(strPoints) {
	// passed an array of points: "200,200,0 300,200,0 250,200,0"
	// create n pointObjs
	// create n faceObjs
	var pointArray=strPoints.split(" ");
	for (i=0;i<pointArray.length;i++) {
		var coorArray=pointArray[i].split(",");
		var objPointId=coorArray[0]+"_"+coorArray[1]+"_"+coorArray[2];
		if (!this.hasPoint(objPointId)) {
			var objPoint= new nGonPointObj(objPointId,this.centerX+parseInt(coorArray[0]),this.centerY+parseInt(coorArray[1]),parseInt(coorArray[2]));
			objPoint.cartX=parseInt(coorArray[0]);
			objPoint.cartY=parseInt(coorArray[1]);
			this.addPoint(objPoint);
		}
		if ((i+1)%3==0) {
			var objFace = new nGonFaceObj(this.nPoints[i],this.nPoints[i-1],this.nPoints[i-2]);
			this.addFace(objFace);
		}

	}
}
function NGON_clear() {
	for (i=this.nPoints.length-1;i>=0;i--) {
		//this.nPoints[i].divObj.style.display="none";
		//this.nPoints[i].divObj=null;
		this.nPoints.pop(i);
	}
	while (this.nPoints.length>0) this.nPoints.pop(0);
	for (i=this.nFaces.length-1;i>=0;i--) {
		this.nFaces[i].shape.style.display="none";
		this.nFaces[i].shape=null;
		this.nFaces[i]=null;
		this.nFaces.pop(i);
	}
	while (this.nFaces.length>0) this.nFaces.pop(0);
}



function NGON_addFace(obj) {
	this.nFaces[this.nFaces.length]=obj;
}

function NGON_addPoint(obj) {
	this.nPoints[this.nPoints.length]=obj;
}



function showFace(objFace,strPath) {
	objFace.shape.style.display="inline";
	objFace.shape.style.width=winX;
	objFace.shape.style.height=winY;
	objFace.shape.coordsize=winX+","+winY;
	objFace.shape.path.v=strPath;
}

function NGON_drawFaces () {
	var strPath="";
	var dprod=0;
	for (i=0;i<this.nFaces.length;i++) {
		var faceObj=this.nFaces[i];
		strPath="m";
		strPath+=parseInt(faceObj.p1.scrX)+","+parseInt(faceObj.p1.scrY-iLineYDiff);
		strPath+=" l ";
		strPath+=parseInt(faceObj.p2.scrX)+","+parseInt(faceObj.p2.scrY-iLineYDiff)+" ";
		strPath+=parseInt(faceObj.p3.scrX)+","+parseInt(faceObj.p3.scrY-iLineYDiff);
		strPath+="xe";

		dprod=faceNormal(faceObj);

		if (dprod>0) {
			showFace(faceObj,strPath);
			faceObj.shape.style.zIndex=faceObj.p1.z+faceObj.p2.z+faceObj.p3.z;
		}
		else faceObj.shape.style.display="none";



	}
}

function faceNormal(faceObj) {
		var point1,point2,point3,point4;

		point1=faceObj.p1;
		point2=faceObj.p2;
		point3=faceObj.p2;
		point4=faceObj.p3;

		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);


	    return dprod;
}

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;


	for (i=0;i<this.nPoints.length;i++) {
		var pointObj=this.nPoints[i];
		var x=pointObj.x-this.centerX;
		var y=this.centerY-pointObj.y;

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

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

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

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



		// PARALLEL PROJECTION //
		pointObj.x=newx+this.centerX;
		pointObj.y=this.centerY-newy;

		// PERSPECTIVE PROJECTION //
		iDistance+=iZoom;
		if (!this.parallel) {
			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;
		}
		else {
			pointObj.scrX=pointObj.x;
			pointObj.scrY=pointObj.y;
			pointObj.cartX=newx;
			pointObj.cartY=-newy;
		}

		pointObj.z=newz;
	//	pointObj.redraw();
	}
		this.drawFaces();

}



	function MoveHandler(e) {

		moveX = window.event.x + document.body.scrollLeft;
		moveY = window.event.y;
		if (moveX<oldMoveX) xRotate=1;
		else if(moveX>oldMoveX) xRotate=-1;
		else xRotate=0;

		if (moveY<oldMoveY) yRotate=1;
		else if(moveY>oldMoveY) yRotate=-1;
		else yRotate=0;

		oldMoveX=moveX;
		oldMoveY=moveY;

		if (bRightMouseDown && (xRotate!=0 || yRotate!=0)) {
			nGon.rotate(xRotate,yRotate,0);
		}
		else if (nGon) {
			for (i=1;i<nGon.nPoints.length;i++) {
				var objPoint=nGon.nPoints[i];
				if (moveX>objPoint.scrX-10 &&
					moveX<objPoint.scrX+10 &&
					moveY>objPoint.scrY-10 &&
					moveY<objPoint.scrY+10 &&
					bLeftMouseDown) {
						objPoint.x=moveX;
						objPoint.y=moveY;
						objPoint.scrX=moveX;
						objPoint.scrY=moveY;
						objPoint.redraw();
					}

			}

		}
	}



	function KeyHandler(e) {

		switch(window.event.keyCode) {
		case 51:
			nGon.clear();
			nGon.init();
		break;
		case 16: // strafe left;
			nGon.centerX-=10;
			nGon.rotate(0,0,0);
		break;
		case 67: // strafe right;
			nGon.centerX+=10;
			nGon.rotate(0,0,0);
		break;
		case 80: // toggle parallel
			nGon.parallel=!nGon.parallel;
			projection.innerHTML=(nGon.parallel)?"parallel projection":"perspective projection";
			nGon.rotate(0,0,0);
		break;
		case 37:
			nGon.rotate(1,0,0);
		break;


		case 38:
			nGon.rotate(0,1,0);
		break;

		case 39:
			nGon.rotate(-1,0,0);
		break;

		case 40:
			nGon.rotate(0,-1,0);
		break;

		case 83:
			iZoom=-1;
			nGon.rotate(0,0,0);
			iZoom=0;
		break;

		case 90:
			iZoom=1;
			nGon.rotate(0,0,0);
			iZoom=0;
		break;
		default:
		break;
		}
	}
	function PressHandler(e) {
		mousegrabber.setCapture(true);
		if (window.event.button==1) {
			bLeftMouseDown=true;
			clearTimeout(iTimeoutId);
		}
		else if (window.event.button==2) {bRightMouseDown=true;}
		return false;
	}
	function UpHandler(e) {
		mousegrabber.releaseCapture();
		bLeftMouseDown=false;
		bRightMouseDown=false;
		return false;
	}
	window.onresize = getWinSize;
	document.onmouseup=UpHandler;
	document.onkeydown = KeyHandler;
	document.onmousemove = MoveHandler;
	document.onmousedown = PressHandler;
	document.oncontextmenu=new Function("return false;");
</script>


<LINK rel="stylesheet" type="text/css" href="style.css">



</head>


<body onload="drawPolygon();">
<div onmouseup="this.releaseCapture();" id="mousegrabber" style="border:none;font-family:arial;">
<table width=100% cellpadding=0 cellspacing=0>
<tr>
<td>3D Engine - Faces</td>
<td align=right><font style="font-size:12px;">
<script src="nav.js"></script>

</td>
</tr>
</table>
<table><tr>

<td><font style="font-size:10px;"><b>left-click</b> </td><td><font style="font-size:10px;">- stop rotation </td></tr><tr>
<td><font style="font-size:10px;"><b>right-click & drag</b> </td><td><font style="font-size:10px;">- manual rotate</td></tr>
<td><font style="font-size:10px;"><b>s</b> </td><td><font style="font-size:10px;">- zoom in</td></tr>
<td><font style="font-size:10px;"><b>z</b> </td><td><font style="font-size:10px;">- zoom out</td></tr>
<td><font style="font-size:10px;"><b>p</b> </td><td><font style="font-size:10px;">- toggle parallel / perspective projection</td></tr>
<td colspan=2><font style="font-size:10px;">*note: window resize shifts center and may cause funky orbit</td></tr>
</tr>
</td>
</table>
<br>
<div id="projection" style="font-size:10px;font-weight:bold;color:#990000;">perspective projection</div>
<font style="font-size:10px;">

</font>


</div>



</body>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
United States United States

Comments and Discussions