Click here to Skip to main content
15,896,912 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.5K   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>

</head>


<body onload="drawPolygon();">
<script>

	/********************************************/
	/*********************************************
		N-GON
		BY JOEY FORTUNA
		http://neoncelery.com
		COPYRIGHT (C) 2004, Joey Fortuna
		PERMISSION TO USE GRANTED PROVIDING THE
		ADOPTER INCLUDES THIS NOTICE
	*********************************************/
	/********************************************/
var nSides=6;
var iRadius=150;
var iStartX=300;
var iStartY=400;
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=250;

function drawPolygon() {
		nGon=new nGonObj(nSides,iStartX,iStartY,iRadius);
		getWinSize();
}
function getWinSize()
	{

			winX = document.body.clientWidth - 10;
			winY = document.body.clientHeight - 10;
			nGon.centerX=winX/2;
			nGon.centerY=winY/2;
			if (nGon.is2D) nGon.draw2D();
			else if (nGon.is3D) nGon.draw3D();
	}

function changeSides(dir) {

	var newSides=document.ngonform.NumSides.value;
	newSides=(dir==1)?parseInt(newSides)+1:parseInt(newSides)-1;
	if (newSides<3) newSides=3;
	document.ngonform.NumSides.value=newSides;
	nGon.changeSides(newSides);
	return true;
}

function nGonPointObj(tid) {

	this.id=tid;
	this.redraw=NGON_POINT_redraw;

	var newObj;
	newObj=document.createElement("div");
	newObj.id="nGonPoint"+tid;
	newObj.innerHTML=tid;

	newObj.style.position="absolute";
	newObj.style.textAlign="center";
	newObj.style.fontWeight="bold";
	newObj.style.fontFamily="arial";
	newObj.style.display="none";
	newObj.style.width="20px";
	this.divObj=document.body.appendChild(newObj);
	this.divObj.style.cursor="hand";
	this.x=0;
	this.y=0;
	this.z=0;
	this.scrX=0;
	this.scrY=0;
	this.divObj.style.top=this.y+"px";
	this.divObj.style.left=this.x+"px";
	this.parallel=false;

	return this;
}
function NGON_POINT_redraw() {
	this.divObj.style.left=this.scrX+"px";
	this.divObj.style.top=this.scrY+"px";
}

function nGonObj(nSides,iStartX,iStartY,iRadius) {
	this.rotate2DAngle=0;
	this.radiusLength=iRadius;
	this.centerX=iStartX;
	this.centerY=iStartY;
	this.centerZ=0;
	this.name="NGonObject";
	this.is2D=true;
	this.is3D=false;
	this.nPoints=new Array();
	this.nLines=new Array();
	this.angleDivisor=0;
	this.angle=0;
	this.nSides=nSides;
	this.draw2D=NGON_draw2D;
	this.draw3D=NGON_draw3D;
	this.changeSides=NGON_changeSides;
	this.rotate2D=NGON_rotate2D;
	this.rotate3D=NGON_rotate3D;
	this.clear=NGON_clear;
	this.add=NGON_add;
	this.setup=NGON_setup;
	this.drawLines2D=NGON_drawLines2D;
	this.drawLines3D=NGON_drawLines3D;
	this.xRadius=iRadius;
	this.yRadius=iRadius;

	this.setup();

}

function NGON_changeSides(nNewSides) {
	this.clear();
	this.nSides=nNewSides;
	this.setup();
	if (nGon.is2D) nGon.draw2D();
	else if (nGon.is3D) nGon.draw3D();
}

function NGON_setup() {

	this.angleDivisor=this.nSides-2;
	this.angle=(this.angleDivisor*Math.PI)/this.nSides;
	for (i=0;i<(2*this.nSides)+1;i++) {
		var obj,objLine;
		obj=new nGonPointObj(i);
		objLine = document.body.appendChild(document.createElement("v:Line"));
		objLine.style.position="absolute";
		objLine.strokecolor = "black";
		this.nLines[this.nLines.length]=objLine;
		this.add(obj);
	}
	for (i=0;i<this.nSides;i++) {
		objLine = document.body.appendChild(document.createElement("v:Line"));
		objLine.style.position="absolute";
		objLine.strokecolor = "black";
		this.nLines[this.nLines.length]=objLine;
	}
	return this;

}
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.nLines.length-1;i>=0;i--) {
		this.nLines[i].style.display="none";
		this.nLines[i]=null;
		this.nLines.pop(i)
	}
	while (this.nLines.length>0) this.nLines.pop(0);
}



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

function NGON_drawLines2D() {

	for (i=1;i<this.nSides+1;i++) {
			var pointObj=this.nPoints[i];
			this.nLines[i].from=pointObj.scrX+"px,"+(pointObj.scrY-iLineYDiff)+"px";
			if (i<this.nSides+1) this.nLines[i-1].to=pointObj.scrX+","+(pointObj.scrY-iLineYDiff);
		}

		this.nLines[this.nSides].to=this.nLines[1].from;
		this.nLines[0].from=this.nLines[1].from;
}

function NGON_drawLines3D () {

	for (i=1;i<this.nSides;i++) {
		var pointObj=this.nPoints[i];
		this.nLines[i].from=pointObj.scrX+"px,"+(pointObj.scrY-iLineYDiff)+"px";
		if (i<this.nSides) this.nLines[i-1].to=pointObj.scrX+","+(pointObj.scrY-iLineYDiff);
	}
		this.nLines[0].style.display="none";

	for (i=this.nSides;i<(2*this.nSides)+1;i++) {
		var pointObj=this.nPoints[i];
		this.nLines[i].from=pointObj.scrX+"px,"+(pointObj.scrY-iLineYDiff)+"px";
		if (i<(2*this.nSides)+1) this.nLines[i-1].to=pointObj.scrX+","+(pointObj.scrY-iLineYDiff);
	}


	this.nLines[this.nSides].to=this.nLines[1].from;
	this.nLines[(2*this.nSides)].to=this.nLines[this.nSides+1].from;


	for (i=(2*this.nSides+1);i<(3*this.nSides);i++) {
		var pointObj=this.nPoints[i-(2*this.nSides)+1];
		var bpointObj=this.nPoints[i-this.nSides+1];
		this.nLines[i].from=pointObj.scrX+"px,"+(pointObj.scrY-iLineYDiff)+"px";
		this.nLines[i].to=bpointObj.scrX+"px,"+(bpointObj.scrY-iLineYDiff)+"px";
	}
	this.nLines[(3*this.nSides)].from=this.nLines[this.nSides+1].from;
	this.nLines[(3*this.nSides)].to=this.nLines[1].from;
}

function NGON_draw2D() {

	var i=0;
	centerPoint.style.top=this.centerY+"px";
	centerPoint.style.left=this.centerX+"px";
	centerPoint.style.display="inline";

	for (i=1;i<this.nSides+1;i++) {
			this.nPoints[i].divObj.style.display="inline";
			var pointObj=this.nPoints[i];
			pointObj.x=this.centerX+(this.radiusLength*Math.cos((Math.PI-this.angle)*(i-1)));
			pointObj.y=this.centerY+(this.radiusLength*Math.sin((Math.PI-this.angle)*(i-1)));
			pointObj.scrX=pointObj.x;
			pointObj.scrY=pointObj.y;
			this.nPoints[i].redraw();
		}
		if (this.is2D) this.drawLines2D();
		else if (this.is3D) this.drawLines3D();

}

function NGON_draw3D() {

	var i=0;
	centerPoint.style.top=this.centerY+"px";
	centerPoint.style.left=this.centerX+"px";
	centerPoint.style.display="none";


	for (i=1;i<this.nPoints.length;i++) {
		if (i>this.nSides) this.nPoints[i].z=-100;
		else this.nPoints[i].z=100;
		this.nPoints[i].divObj.style.display="inline";
		var pointObj=this.nPoints[i];
		pointObj.x=this.centerX+(this.radiusLength*Math.cos((Math.PI-this.angle)*(i-1)));
		pointObj.y=this.centerY+(this.radiusLength*Math.sin((Math.PI-this.angle)*(i-1)));
		pointObj.scrX=pointObj.x
		pointObj.scrY=pointObj.y;
		this.nPoints[i].redraw();
	}
	this.rotate3D(0,0,0);
}

function stepFade(inc,colr) {
		inc=inc*2;
		if (colr.charAt(0)=='#') colr = colr.substring(1);
		var r = HexToInt(colr.substring(0,2));
		var g = HexToInt(colr.substring(2,4));
		var b = HexToInt(colr.substring(4,6));

		newinc=parseInt((255*inc)<=255?255*inc:255);

		r=100-(100*(newinc/255));
		g=100-(100*(newinc/255));
		b=100-(100*(newinc/255));

		return "#" + IntToHex(r) + IntToHex(g) + IntToHex(b);

}


function IntToHex(n) {
	var result = parseInt(n).toString(16);
	if (result.length==1) result = "0"+result;
	return result;
}

function HexToInt(hex) {
	return parseInt(hex, 16);
}

function NGON_rotate3D(xRotate,yRotate,iZoom) {


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



	for (i=1;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;



/*
		var fSize=10;
		// FONT SIZE ADJUSTMENT //
		if (!this.parallel) {
			fSize=5*(parseInt(1000/(iDistance-newz)));
			pointObj.divObj.style.fontSize=fSize+"px";
		}
		else {
			fSize=parseInt(12*((Math.abs(z)/119)));
			fSize=parseInt(fSize/(6*(Math.abs(z)/z)));
			fSize=(16+(4*fSize));
			if (fSize<=0) fSize=1;
			pointObj.divObj.style.fontSize=fSize+"px";
		}
*/
		// PARALLEL PROJECTION //
		pointObj.x=newx+this.centerX;
		pointObj.y=this.centerY-newy;


		// PERSPECTIVE PROJECTION //

		// ** DO THIS WITH X/Y VALUES
		// ** DERIVED FROM PARALLEL ROTATION
		// ** BUT DON''T UPDATE POINTOBJ.X/Y
		// ** WITH PROJECTED RESULTS!

		iDistance+=iZoom;
		if (!this.parallel) {
			pointObj.scrX = ((iFov*newx) / (iDistance-(newz+this.centerZ))) + (this.centerX);
	  		pointObj.scrY = (this.centerY) - ((iFov*newy) / (iDistance-(newz+this.centerZ)));
		}
		else {
			pointObj.scrX=pointObj.x;
			pointObj.scrY=pointObj.y;
		}

		pointObj.z=newz;

		this.nPoints[i].redraw();
	}
		this.drawLines3D();

}


function NGON_rotate2D(xRotate) {
	var tAngle=xRotate*(5*Math.PI)/180;
	for (i=1;i<this.nSides+1;i++) {
		var pointObj=this.nPoints[i];
		var tx=pointObj.x-this.centerX;
		var ty=this.centerY-pointObj.y;
		var x,y=0;
		x=((tx)*Math.cos(tAngle))-((ty)*Math.sin(tAngle));
		y=((ty)*Math.cos(tAngle))+((tx)*Math.sin(tAngle));
		pointObj.x=this.centerX+x;
		pointObj.y=this.centerY-y;

		pointObj.scrX=pointObj.x;
		pointObj.scrY=pointObj.y;
		/** ROTATING ABOUT THE CENTER ***/
		//pointObj.x=this.centerX+((this.radiusLength*Math.cos(xRotate*this.rotate2DAngle+((Math.PI-this.angle)*(i-1)))));
		//pointObj.y=this.centerY+((this.radiusLength*Math.sin(xRotate*this.rotate2DAngle+((Math.PI-this.angle)*(i-1)))));
		this.nPoints[i].redraw();
	}

		this.drawLines2D();

}

	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 (bLeftMouseDown && xRotate!=0) {
			if (xRotate>0) changeSides(0);
			else changeSides(1);
			if (nGon.is2D) nGon.draw2D();
			else if (nGon.is3D) nGon.draw3D();
		}
		else if (bRightMouseDown && (xRotate!=0 || yRotate!=0)) {
			if (nGon.is2D ) nGon.rotate2D(xRotate);
			else if (nGon.is3D) nGon.rotate3D(xRotate,yRotate,0);
		}
		else if (nGon) {
			for (i=1;i<nGon.nPoints.length;i++) {
				var objPoint=nGon.nPoints[i];
				if (moveX>objPoint.scrX-20 &&
					moveX<objPoint.scrX+20 &&
					moveY>objPoint.scrY-20 &&
					moveY<objPoint.scrY+20)
					objPoint.divObj.style.color="#ff0000";
				else if (objPoint.divObj.style.color=="#ff0000") objPoint.divObj.style.color="#000000";

			}

		}
	}



	function KeyHandler(e) {
		window.status=window.event.keyCode;
		switch(window.event.keyCode) {
		case 50:

			dimension.innerHTML="2D";
			dimension.style.color="#000099";
			nGon.is2D=true;
			nGon.is3D=false;
			nGon.nSides=6;
			document.ngonform.NumSides.value=6;
			nGon.clear();
			nGon.setup();
			nGon.draw2D();
		break;
		case 51:
			dimension.innerHTML="3D";
			dimension.style.color="#990000";
			nGon.is2D=false;
			nGon.is3D=true;
			nGon.nSides=6;
			document.ngonform.NumSides.value=6;
			nGon.clear();
			nGon.setup();
			nGon.draw3D();
		break;
		case 16: // strafe left;
			nGon.centerX-=10;
			if (nGon.is3D) nGon.rotate3D(0,0,0);
		break;
		case 67: // strafe right;
			nGon.centerX+=10;
			if (nGon.is3D) nGon.rotate3D(0,0,0);
		break;
		case 80: // toggle parallel
			if (nGon.is3D) {
				projection.style.color=((nGon.parallel)?"#990000":"#000099");
				projection.innerHTML=((nGon.parallel)?"perspective projection":"parallel projection");
				nGon.parallel=!nGon.parallel;
				nGon.rotate3D(0,0,0);
			}
		break;
		case 37:
			if (nGon.is3D) nGon.rotate3D(1,0,0);
		break;


		case 38:
			if (nGon.is3D) nGon.rotate3D(0,1,0);
		break;

		case 39:
			if (nGon.is3D) nGon.rotate3D(-1,0,0);
		break;

		case 40:
			if (nGon.is3D) nGon.rotate3D(0,-1,0);
		break;

		case 83:
			if (nGon.is3D) nGon.rotate3D(0,0,-1);
		break;

		case 90:
			if (nGon.is3D) nGon.rotate3D(0,0,1);
		break;
		default:
		break;
		}
	}
	function PressHandler(e) {
		mousegrabber.setCapture(true);
		if (window.event.button==1) bLeftMouseDown=true;
		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>


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

</td>
</tr>
</table>
<font style="font-size:10px;">
<table><tr>
<td><font style="font-size:10px;"><b>p</b> </td><td><font style="font-size:10px;">- toggles parallel / perspective viewing</td></tr><tr>
<td><font style="font-size:10px;"><b>s</b> </td><td><font style="font-size:10px;">- zoom in</td></tr><tr>
<td><font style="font-size:10px;"><b>z</b> </td><td><font style="font-size:10px;">- zoom out</td></tr><tr>
<td><font style="font-size:10px;"><b>2</b> </td><td><font style="font-size:10px;">- 2D mode</td></tr><tr>
<td><font style="font-size:10px;"><b>3</b> </td><td><font style="font-size:10px;">- 3D mode</td></tr><tr>
<td><font style="font-size:10px;"><b>left-click & drag</b> </td><td><font style="font-size:10px;">- increase or decrease number of sides</td></tr><tr>
<td><font style="font-size:10px;"><b>right-click & drag</b> </td><td><font style="font-size:10px;">- rotate</td></tr>
</tr>
</td>
</table>
viewing in: <span id="dimension" style="font-size:10px;font-weight:bold;color:#000099;">2D</span>
</font>

<div id="projection" style="font-size:10px;font-weight:bold;color:#990000;">perspective projection</div>

<form name=ngonform style="display:inline;">
<font style="font-size:10px;">number of sides: <input type=text name="NumSides" size=2 onfocus="blur();"> &nbsp;
</form>
</div>

<div id="centerPoint" style="position:absolute;top=100px;left=100px;display:none;">@</div>

<script>
document.ngonform.NumSides.value=nSides;
</script>

</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