Browse the notebook

Category: 3D

Parametric UV Surface

var matrix:Matrix3D = new Matrix3D();
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
var sqrt2:Number = Math.sqrt(2)
var pi:Number = Math.PI;
var pi23:Number= 2 * Math.PI / 3;
var step:Number= pi / 50;
for (var u:Number = -pi; u<pi; u+=step) {
	for (var v:Number = -pi; v<pi; v+=step) {
		// from here http://local.wasp.uwa.edu.au/~pbourke/geometry/hexatorus/
		var px = Math.sin(u) / Math.abs(sqrt2+ Math.cos(v))
        var py = Math.sin(u+pi23) / Math.abs(sqrt2 +Math.cos(v + pi23))
        var pz = Math.cos(u-pi23) / Math.abs(sqrt2 +Math.cos(v - pi23))
		verts.push(px * 50);
		verts.push(py * 50);
		verts.push(pz * 50);
	}
}
var brush:BitmapData=new BitmapData(3,2,true,0x41FFFFFF);
var canvas:BitmapData=new BitmapData(400,400,false,0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
	var p = new Point();
	for (var i:int = 0; i<pVerts.length; i+=2) {
		p.x = pVerts[i];
		p.y = pVerts[i+1];
		canvas.copyPixels(brush, brush.rect, p, null, null, true);
	}
	canvas.unlock();
}

More 3D shapes inspired by (taken from) Paul Bourke’s website. I figured I should probably add a parametric 3D surface to this set of posts – so I chose to plot the Triaxial Hexatorus. It’s important to note that U and V go from -PI to PI.

I googled Triaxial Hexatorus and stumbled upon a great papervision demo that creates meshes using parametric equations – it also morphs between shapes.

I also noticed that I don’t need to populate the uvts or pVerts Vectors with zeros – seems to work fine without doing that.

Read snippet: Parametric UV Surface

Wormhole

var matrix:Matrix3D = new Matrix3D();
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
for (var i:Number = -10; i<10; i+=.04) {
	for (var j:Number = -5; j<8; j+=.04) {
		for (var k:Number = -10; k<10; k+=.04) {
			var s:Number =  i * i * j + j * k * k;
			if (s <10 && s > 9.95) {
				verts.push(i * 20);
				verts.push(j * 20);
				verts.push(k * 20);
				pVerts.push(0),pVerts.push(0);
				uvts.push(0),uvts.push(0),uvts.push(0);
			}
		}
	}
}
var brush:BitmapData=new BitmapData(3,2,true,0x41FFFFFF);
var canvas:BitmapData=new BitmapData(400,400,false,0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
	var p = new Point();
	for (var i:int = 0; i<pVerts.length; i+=2) {
		p.x = pVerts[i];
		p.y = pVerts[i+1];
		canvas.copyPixels(brush, brush.rect, p, null, null, true);
	}
	canvas.unlock();
}

Yet another variation on the post from the last two days. This one plots something resembling a wormhole – I was randomly tweaking the equation for a sphere and stumbled upon this…

Read snippet: Wormhole

Kusner-Schmitt (Tetrahedral Implicit Surface)

var matrix:Matrix3D = new Matrix3D();
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
for (var i:Number = -2; i<2; i+=.02) {
	for (var j:Number = -2; j<2; j+=.02) {
		for (var k:Number = -2; k<2; k+=.02) {
            var s:Number = (i * i + 3) * (j * j + 3) * (k * k + 3) - 32 * (i *j *k + 1);
			if (s<0&&s>-.2) {
				verts.push(i * 100);
				verts.push(j * 100);
				verts.push(k * 100);
				pVerts.push(0),pVerts.push(0);
				uvts.push(0),uvts.push(0),uvts.push(0);
			}
		}
	}
}
var brush:BitmapData=new BitmapData(3,2,true,0x41FFFFFF);
var canvas:BitmapData=new BitmapData(400,400,false,0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
	var p = new Point();
	for (var i:int = 0; i<pVerts.length; i+=2) {
		p.x = pVerts[i];
		p.y = pVerts[i+1];
		canvas.copyPixels(brush, brush.rect, p, null, null, true);
	}
	canvas.unlock();
}

The same as yesterdays with a different plot from Paul Bourke’s website.

Read snippet: Kusner-Schmitt (Tetrahedral Implicit Surface)

Implicit 3D Plot

var matrix:Matrix3D = new Matrix3D();
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
for (var i:Number = -2; i<2; i+=.04) {
	for (var j:Number = -2; j<2; j+=.04) {
		for (var k:Number = -2; k<2; k+=.04) {
			// blobby, from here www.iiit.net/techreports/ImplicitTR.pdf
var s:Number=i*i+j*j+k*k+Math.sin(4*i)-Math.cos(4*j)+Math.sin(4*k)-1;
			if (s<0&&s>-.2) {
				verts.push(i * 60);
				verts.push(j * 60);
				verts.push(k * 60);
				pVerts.push(0),pVerts.push(0);
				uvts.push(0),uvts.push(0),uvts.push(0);
			}
		}
	}
}
var brush:BitmapData=new BitmapData(3,2,true,0x41FFFFFF);
var canvas:BitmapData=new BitmapData(400,400,false,0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
	var p = new Point();
	for (var i:int = 0; i<pVerts.length; i+=2) {
		p.x = pVerts[i];
		p.y = pVerts[i+1];
		canvas.copyPixels(brush, brush.rect, p, null, null, true);
	}
	canvas.unlock();
}

I was looking at some equations for implicit 3D surfaces in this pdf about raytracing… anyway, I realized I could just modify the Utils3D.projectVectors() code (that I wrote a little while ago) to easily render any of the implicit equations mentioned in the pdf. I also did some experimentation with fake lighting and distance rendering which I may post in the future.

(check out the swf on wonderfl.net)

Here are some stills of the above snippet:

Read snippet: Implicit 3D Plot

3D Hypocycloid Shape

var matrix:Matrix3D = new Matrix3D();
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
const TWO_PI:Number=Math.PI * 2;
var step:Number=.05;

var brush:BitmapData = new BitmapData(3, 2, true, 0x41FFFFFF);
var n:Number=8;
var xp:Number=0,yp:Number=0,a:Number=12,t:Number=0;
for (var i:Number = 0; i<TWO_PI; i+=step) {
	for (var j:Number = 0; j<TWO_PI; j+=step) {
        // unoptimized for readability
	var cosi:Number = a/n * ((n - 1) * Math.cos(i) + Math.cos(Math.abs((n - 1) * i)));
	var sini:Number = a/n * ((n - 1) * Math.sin(i) - Math.sin(Math.abs((n - 1) * i)));
	var cosj:Number = a/n * ((n - 1) * Math.cos(j) + Math.cos(Math.abs((n - 1) * j)));
	var sinj:Number = a/n * ((n - 1) * Math.sin(j) - Math.sin(Math.abs((n - 1) * j)));
		verts.push(cosi * cosj);
		verts.push(sini * cosj);
		verts.push(a * sinj);
		pVerts.push(0),pVerts.push(0);
		uvts.push(0),uvts.push(0),uvts.push(0);
	}
}
var canvas:BitmapData=new BitmapData(400,400,false,0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
	var p = new Point();
	for (var i:int = 0; i<pVerts.length; i+=2) {
		p.x = pVerts[i];
		p.y = pVerts[i + 1];
		canvas.copyPixels(brush, brush.rect, p, null, null, true);
	}
	canvas.unlock();
}

Taking the Hypocycloid stuff from yesterday into 3D…

Read snippet: 3D Hypocycloid Shape

Utils3D.projectVectors() & 100,000 pixels

var matrix:Matrix3D = new Matrix3D();

const PARTICLE_NUM:int = 100000;
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();

for (var i:int = 0; i<PARTICLE_NUM; i++){
	verts.push(Math.random()*250 - 125);
	verts.push(Math.random()*250 - 125);
	verts.push(Math.random()*250 - 125);
	
	pVerts.push(0), pVerts.push(0);
	uvts.push(0), uvts.push(0), uvts.push(0);
}

var canvas:BitmapData = new BitmapData(400,400,false, 0x000000);
addChild(new Bitmap(canvas));
var dx:Number=0;
var dy:Number=0;

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {

	dx += (mouseX - dx)/4;
	dy += (mouseY - dy)/4;
	
	matrix.identity();
	matrix.appendRotation(dy,Vector3D.X_AXIS);
	matrix.appendRotation(dx,Vector3D.Y_AXIS);
	matrix.appendTranslation(200, 200, 0);
	
	Utils3D.projectVectors(matrix, verts, pVerts, uvts);
	
	canvas.lock();
	canvas.fillRect(canvas.rect, 0x000000);
        var leng:int = pVerts.length;
	for (var i:int = 0; i<leng; i+=2){
		canvas.setPixel( pVerts[i], pVerts[i + 1], 0xFFFFFF);
	}
	canvas.unlock();
}

The above shows an easy way to use Utils3D.projectVectors() to move some pixels around in 3D. Since the 3D math is done behind the scenes by the flash player it runs quite fast…

Read snippet: Utils3D.projectVectors() & 100,000 pixels

Torus 3D

stage.frameRate = 30;
const TWO_PI:Number = Math.PI * 2;
var centerX:Number = 200;
var centerY:Number = 200;
var p:Array = new Array();
var zpos:Number;
var xpos:Number;
var ypos:Number;
var depth:Number;
var canvas:BitmapData = new BitmapData(400,400,true,0xFF000000);
addChild(new Bitmap(canvas));

var dy:Number = 0
var dx:Number = 0;
 
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.fillRect(canvas.rect, 0xFF000000);
	 
	 dx += (mouseX / 100 - dx)/12;
	 dy += (mouseY / 100 - dy)/12;
	 var xp:Number, yp:Number, zp:Number;
	 
	 canvas.lock();
	 for (var a:Number =0; a < TWO_PI; a+=.08){
		  for (var b:Number =0; b < TWO_PI; b+=.08){
			  xp = (70 + 40 * Math.cos(a)) * Math.cos(b) ;
			  yp = (70 + 40 * Math.cos(a)) * Math.sin(b);
			  zp =  40 * Math.sin(a);
			  calc3D(xp, yp, zp, dx, dy);
			  convert3D();
			  canvas.setPixel(p[0], p[1], 0xFFFFFF);
		  }
	 }
	 canvas.unlock();
}
function calc3D(px:Number, py:Number, pz:Number, rotX:Number=0, rotY:Number=0):void {
	// I first learned this from code by Andries Odendaal - www.wireframe.co.za
	zpos=pz*Math.cos(rotX)-px*Math.sin(rotX) ;
	xpos=pz*Math.sin(rotX)+px*Math.cos(rotX) ;
	ypos=py*Math.cos(rotY)-zpos*Math.sin(rotY) ;
	zpos=py*Math.sin(rotY)+zpos*Math.cos(rotY);
}
function convert3D():void {
	depth = 1/((zpos/200)+1);
	p[0] =  xpos * depth + centerX;
	p[1] =  ypos * depth + centerY;
}

This code draws a rotating 3D torus using setPixel().

Read snippet: Torus 3D

Parallax Displacement Map w/BlendMode

var blends:Array = [BlendMode.ADD, BlendMode.DARKEN, BlendMode.DIFFERENCE, BlendMode.HARDLIGHT, BlendMode.INVERT, BlendMode.LIGHTEN, BlendMode.MULTIPLY, BlendMode.OVERLAY, BlendMode.SCREEN, BlendMode.SUBTRACT];

and a little later…

displace.perlinNoise(150,150, 3, 30, true, false,0,true);
var currentBlend:String = blends[ blendCount % blends.length];
displace.draw(radial, null ,null, currentBlend);
blendCount++;

The above are excepts from a recommendation I made in the comments of yesterdays post…


Try some different blend modes…. take a look at the swf here.

Read snippet: Parallax Displacement Map w/BlendMode

Parallax Displacement Map

[SWF(width=500, height=500, backgroundColor=0xCCCCCC, frameRate=30)]

var canvas:BitmapData = new BitmapData(500, 500, true, 0xFF000000);
addChild(new Bitmap(canvas));

// create a radial gradient
var radial:Shape = new Shape();
var m:Matrix = new Matrix()
m.createGradientBox(500,500,0,0,0);
radial.graphics.beginGradientFill(GradientType.RADIAL, [0xFFFFFF, 0x000000], [1, 1], [0, 200], m, SpreadMethod.PAD);

radial.graphics.drawRect(0,0,500,500);
radial.x = radial.y = 0;

var displace:BitmapData = new BitmapData(500, 500, false,0xFF000000);
displace.perlinNoise(150,150, 3, 30, true, false,0,true);

// try different blendmodes here
displace.draw(radial, null ,null, BlendMode.LIGHTEN);
 
var displacementMap:DisplacementMapFilter = new DisplacementMapFilter(displace, new Point(0,0), 1, 1,  0, 0, DisplacementMapFilterMode.WRAP);

var scale:Number = 50 / stage.stageWidth;

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
 
	canvas.copyPixels(displace, canvas.rect, new Point(0,0));
	 
	displacementMap.scaleX = 25 - mouseX  * scale ;
	displacementMap.scaleY = 25 - mouseY  * scale ;
	
	canvas.applyFilter(canvas, canvas.rect, new Point(0,0), displacementMap);
}

This one is hard to describe, so take a look at the swf first .

I think displacement maps are underused – they’re very powerful. In this snippet I use a DisplacementMapFilter to create a parallax effect on some perlin noise and a radial gradient. The result is an abstract texture that looks 3D.

I got the idea from a demo for the alternative game engine. The first time I saw this demo I had no idea how they were doing it… but after looking at it a few times, I noticed some distortion around the neck area of the model… at that point I recognized it as a displacement map and whipped up a demo (using a drawings as the source image). The alternativa demo also has some color stuff happening to simulate lighting… I’m assuming that’s done with a ColorMatrixFilter or two, but I’m not sure.

As in the alternativa demo, this technique could be used with 2 images… one depth map rendered out from your favorite 3D app and one textured render….

Read snippet: Parallax Displacement Map

Isometric to Cartesian

var centerX:Number=stage.stageWidth/2;
var centerY:Number=stage.stageHeight/2;
// 1 to 2 ratio
// try others 1 / 1.5 etc...
var theta:Number = Math.atan(1 / 2);
 
var cosX:Number=Math.cos(theta);
var sinX:Number=Math.sin(theta);
var pnt:Point = new Point();

function iso3D(x:Number, y:Number, z:Number):Point {
	pnt.x = centerX + (x-z) *  cosX;
	pnt.y = centerY -  (x+z) * sinX - y;
	return pnt;
}

var p:Point = iso3D(0,0,0);

graphics.beginFill(0x000000);
graphics.drawCircle(p.x, p.y, 2);

// x axis positive
trace("x = red");
for (var i:int = 1; i<10; i++){
	graphics.beginFill(0xFF0000);
	p = iso3D(i*10, 0, 0);
	graphics.drawCircle(p.x, p.y, 2);
}

// y axis positive
trace("y = green");
for (i= 1; i<10; i++){
	graphics.beginFill(0x00FF00);
	p = iso3D(0, i * 10, 0);
	graphics.drawCircle(p.x, p.y, 2);
}

// z axis positive
trace("z = blue");
for (i= 1; i<10; i++){
	graphics.beginFill(0x0000FF);
	p = iso3D(0, 0, i * 10);
	graphics.drawCircle(p.x, p.y, 2);
}

The above code demos a conversion from isometric coordinates to cartesian coordinates. It draws out part of the x, y and z axis using the iso3D() function.

I’ve always faked isometric conversion by just tweaking numbers. A few years ago I created some strange isometric engines (here’s another example ). The other day when I wrote the isometric voxels snippet I just created the iso3D() function with a little trial and error. As you’ll see it’s not exactly the same as the one here. This new conversion is the result of some googling. The updated conversion is pretty close to the one that I wrote for the voxel post, but has one less multiplication… and a clearer place to control the scale ratio …

Read snippet: Isometric to Cartesian