3D Rendering Engine
Arrow keys to rotate the cube. Can add also render a uv map, but took out some of the code.
/**
* Copyright bennett.yeates ( http://wonderfl.net/user/bennett.yeates )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/oNMK
*/
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
/**
* ...
* @author Bennett Yeates
*
* */
[SWF(frameRate=30, backgroundColor=0x0)]
public class Main extends Sprite
{
/*=========================================================================================
VARS
=========================================================================================*/
// =============================
// PUBLIC
// =============================
public var square:Object3D;
// =============================
// PROTECTED
// =============================
protected var _renderer:Renderer;
protected var _canvas:BitmapData;
// =============================
// PRIVATE
// =============================
// list of key presses
private static var _keys:Object;
// =============================
// CONST
// =============================
private static const GRID_LINE_WIDTH:int = 1;
private static const GRID_LINE_COLOR:int = 0xFFFFFF;
private static const GRID_COLUMN_SIZE:int = 25;
private static const GRID_ROW_SIZE:int = 25;
private static const MOVE_PIXELS_BY:int = 10;
/*=========================================================================================
CONSTRUCTOR
=========================================================================================*/
public function Main()
{
_canvas = new BitmapData( stage.stageWidth, stage.stageHeight, false, 0 );
// comment this out if grid lines are not wanted
//createGrid();
_keys = {};
addChild( new Bitmap( _canvas ) );
_renderer = new Renderer( _canvas );
addEventListener( Event.ENTER_FRAME, update );
stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
stage.addEventListener( KeyboardEvent.KEY_UP, onKeyUp );
var mesh:CubeMesh = new CubeMesh();
mesh.setUVData();
square = new Object3D( mesh );
square.scale( 50, 50, 1 );
square.translate( stage.stageWidth/2, stage.stageHeight/2, 0 );
_renderer.renderObject( square );
}
protected function onKeyDown( e:KeyboardEvent ):void
{
_keys[ e.keyCode ] = true;
}
protected function onKeyUp( e:KeyboardEvent ):void
{
_keys[ e.keyCode ] = false;
}
protected function update(event:Event):void
{
var doRedraw:Boolean;
for ( var key:String in _keys )
{
if ( _keys[ key ] === true )
{
switch( int(key) )
{
case Keyboard.DOWN:
square.rotationX += MOVE_PIXELS_BY;
doRedraw = true;
break;
case Keyboard.UP:
square.rotationX -= MOVE_PIXELS_BY;
doRedraw = true;
break;
case Keyboard.LEFT:
square.rotationY -= MOVE_PIXELS_BY;
doRedraw = true;
break;
case Keyboard.RIGHT:
square.rotationY += MOVE_PIXELS_BY;
doRedraw = true;
break;
}
}
}
if ( doRedraw )
{
square.updateTransformVertices();
redraw();
}
}
private function redraw():void
{
_renderer.clear();
_renderer.renderObject( square );
}
private function createGrid():void
{
var grid:Sprite = new Sprite();
grid.graphics.lineStyle(GRID_LINE_WIDTH, GRID_LINE_COLOR);
var columns:int = stage.stageWidth / GRID_COLUMN_SIZE;
var rows:int = stage.stageWidth / GRID_ROW_SIZE;
for ( var i:int = 0; i < columns; ++i )
{
grid.graphics.drawRect( i*GRID_COLUMN_SIZE, 0, GRID_LINE_WIDTH, stage.stageHeight );
}
for ( i = 0; i < rows; ++i )
{
grid.graphics.drawRect( 0, i*GRID_ROW_SIZE, stage.stageWidth, GRID_LINE_WIDTH );
}
_canvas.draw( grid )
}
}
}
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.geom.Matrix3D;
class VertexData
{
public var vector:Vector3D;
// values 0-1 (representing 0-255)
public var red:Number;
public var green:Number;
public var blue:Number;
public var u:Number;
public var v:Number;
public var uvMap:BitmapData;
public var clamp:Boolean;
public function VertexData( x:Number=0, y:Number=0, z:Number=0 )
{
vector = new Vector3D( x, y, z );
}
public function setUV( map:Bitmap, u:Number=0, v:Number=0 ):void
{
this.u = u;
this.v = v;
uvMap = map.bitmapData;
}
public function setColorData( red:Number=1, green:Number=1, blue:Number=1 ):void
{
this.red = red;
this.green = green;
this.blue = blue;
}
/** returns a copy of this vertex instance */
public function clone():VertexData
{
var vertex:VertexData = new VertexData( x, y, z );
vertex.u = this.u;
vertex.v = this.v;
vertex.uvMap = uvMap;
vertex.clamp = this.clamp;
vertex.setColorData( red, green, blue );
return vertex;
}
public function set x( val:Number ):void
{
vector.x = val;
}
public function set y( val:Number ):void
{
vector.y = val;
}
public function set z( val:Number ):void
{
vector.z = val;
}
public function get x():Number
{
return vector.x;
}
public function get y():Number
{
return vector.y;
}
public function get z():Number
{
return vector.z;
}
/** returns pixel data without alpha */
public function getUVPixel( up:Number, vp:Number, pixelType:String="getPixel" ):Number
{
if ( clamp )
{
if ( up > 1 )
{
up = 1;
}
if ( vp > 1 )
{
vp = 1;
}
}
else
{
if ( up > 1 )
{
up %= 1;
}
if ( vp > 1 )
{
vp %= 1;
}
}
return uvMap[ pixelType ]( uvMap.width * up, uvMap.height * vp );
}
}
class CubeMesh
{
public var triangles:Vector.<Vector.<VertexData>>
public function CubeMesh()
{
triangles = new Vector.<Vector.<VertexData>>();
// =============================
// FRONT FACE
// =============================
// top tri
triangles.push(
new <VertexData>
[
new VertexData( -1, -1, 1 ),
new VertexData( 1, -1, 1 ),
new VertexData( -1, 1, 1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( 1, 1, 1 ),
triangles[0][2],
triangles[0][1]
]);
// =============================
// LEFT FACE
// =============================
triangles.push(
new <VertexData>
[
new VertexData( -1, -1, -1 ),
new VertexData( -1, -1, 1 ),
new VertexData( -1, 1, -1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( -1, 1, 1 ),
triangles[2][2],
triangles[2][1]
]);
// =============================
// BACK FACE
// ===================
triangles.push(
new <VertexData>
[
new VertexData( 1, -1, -1 ),
new VertexData( -1, -1, -1 ),
new VertexData( 1, 1, -1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( -1, 1, -1 ),
triangles[4][2],
triangles[4][1]
]);
// =============================
// RIGHT FACE
// =============================
triangles.push(
new <VertexData>
[
new VertexData( 1, -1, 1 ),
new VertexData( 1, -1, -1 ),
new VertexData( 1, 1, 1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( 1, 1, -1 ),
triangles[6][2],
triangles[6][1]
]);
// =============================
// TOP FACE
// =============================
triangles.push(
new <VertexData>
[
new VertexData( -1, -1, -1 ),
new VertexData( 1, -1, -1 ),
new VertexData( -1, -1, 1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( 1, -1, 1 ),
triangles[8][2],
triangles[8][1]
]);
// =============================
// BOTTOM FACE
// =============================
triangles.push(
new <VertexData>
[
new VertexData( -1, 1, 1 ),
new VertexData( 1, 1, 1 ),
new VertexData( -1, 1, -1 )
]);
// bottom tri
triangles.push(
new <VertexData>
[
new VertexData( 1, 1, -1 ),
triangles[10][2],
triangles[10][1]
]);
}
public function setUVData():void
{
for (var i:int = 0; i < triangles.length-1; i += 2)
{
// setting a random color for now
triangles[i][0].setColorData( Math.random(), Math.random(), Math.random() );
triangles[i][1].setColorData( Math.random(), Math.random(), Math.random() );
triangles[i][2].setColorData( Math.random(), Math.random(), Math.random() );
triangles[i+1][0].setColorData( Math.random(), Math.random(), Math.random() );
}
}
}
class Object3D
{
/*=========================================================================================
VAR
=========================================================================================*/
// =============================
// PUBLIC
// =============================
public var mesh:CubeMesh;
// =============================
// PROTECTED
// =============================
protected var _x:Number = 0;
protected var _y:Number = 0;
protected var _z:Number = 0;
protected var _rotationX:Number = 0;
protected var _rotationY:Number = 0;
protected var _rotationZ:Number = 0;
protected var _width:Number = 0;
protected var _height:Number = 0;
protected var _depth:Number = 0;
protected var _scaleX:Number = 0;
protected var _scaleY:Number = 0;
protected var _scaleZ:Number = 0;
protected var _transform:Matrix3D;
protected var _transformVertices:Vector.<Vector.<VertexData>>;
// =============================
// PRIVATE
// =============================
/*=========================================================================================
CONSTRUCTOR
=========================================================================================*/
public function Object3D( mesh:CubeMesh )
{
_transform = new Matrix3D();
_transformVertices = new Vector.<Vector.<VertexData>>;
this.mesh = mesh;
populateTransformVertices();
setWidthAndHeight();
}
private function setWidthAndHeight():void
{
var minX:Number = int.MAX_VALUE;
var maxX:Number = 0;
var minY:Number = int.MAX_VALUE;
var maxY:Number = 0;
for ( var i:int; i < mesh.triangles.length; ++i )
{
for ( var j:int=0; j < mesh.triangles[i].length; ++j )
{
minX = mesh.triangles[i][j].x < minX ? mesh.triangles[i][j].x : minX;
minY = mesh.triangles[i][j].y < minY ? mesh.triangles[i][j].y : minY;
maxX = mesh.triangles[i][j].x > maxX ? mesh.triangles[i][j].x : maxX;
maxY = mesh.triangles[i][j].y > maxY ? mesh.triangles[i][j].y : maxY;
}
}
_width = maxX - minX;
_height = maxY - minY;
}
/** scale - scale the transformation matrix */
public function scale( scaleX:Number, scaleY:Number, scaleZ:Number ):void
{
_scaleX = scaleX;
_scaleY = scaleY;
_scaleZ = scaleZ;
setWidthAndHeight();
updateTransformVertices();
}
/** translate - move the transformation matrix */
public function translate( xval:Number, yval:Number, zval:Number ):void
{
_x = xval;
_y = yval;
_z = zval;
updateTransformVertices();
}
/** populateTransformVertices - creates the list of vertex data based on the mesh */
protected function populateTransformVertices():void
{
_transformVertices.length = 0;
for ( var i:int; i < mesh.triangles.length; ++i )
{
_transformVertices.push( new Vector.<VertexData> );
for ( var j:int = 0; j < mesh.triangles[i].length; ++j )
{
_transformVertices[i].push( mesh.triangles[i][j].clone() );
}
}
}
/** updateTransformVertices - updates the vertices according to the transform */
public function updateTransformVertices():void
{
//var pivot:Vector3D = new Vector3D( _x + _width/2, _y + _height/2, _z + _depth/2 );
populateTransformVertices();
_transform.identity();
_transform.appendScale( _scaleX, _scaleY, _scaleZ );
_transform.appendTranslation( _x, _y, _z );
_transform.prependRotation( _rotationX, Vector3D.X_AXIS );
_transform.prependRotation( _rotationY, Vector3D.Y_AXIS );
_transform.prependRotation( _rotationZ, Vector3D.Z_AXIS );
for ( var i:int; i < _transformVertices.length; ++i )
{
for ( var j:int = 0; j < _transformVertices[i].length; ++j )
{
var vertex:VertexData = _transformVertices[i][j];
vertex.vector = _transform.transformVector( vertex.vector );
vertex.vector.x = Math.round( vertex.vector.x );
vertex.vector.y = Math.round( vertex.vector.y );
vertex.vector.z = Math.round( vertex.vector.z );
}
}
}
/** returns the list of vertex data according to the transforms */
public function get triangles():Vector.<Vector.<VertexData>>
{
return _transformVertices;
}
public function get rotationX():Number
{
return _rotationX;
}
public function set rotationX(value:Number):void
{
_rotationX = value%360;
}
public function get rotationY():Number
{
return _rotationY;
}
public function set rotationY(value:Number):void
{
_rotationY = value%360;
}
public function get rotationZ():Number
{
return _rotationZ;
}
public function set rotationZ(value:Number):void
{
_rotationZ = value%360;
}
public function get x():Number
{
return _x;
}
public function set x(value:Number):void
{
_x = value;
}
public function get y():Number
{
return _y;
}
public function set y(value:Number):void
{
_y = value;
}
public function get z():Number
{
return _z;
}
public function set z(value:Number):void
{
_z = value;
}
public function get width():Number
{
return _width;
}
public function set width(value:Number):void
{
_width = value;
}
public function get height():Number
{
return _height;
}
public function set height(value:Number):void
{
_height = value;
}
public function get depth():Number
{
return _depth;
}
public function set depth(value:Number):void
{
_depth = value;
}
}
class Renderer extends Sprite
{
/*=========================================================================================
VARS
=========================================================================================*/
// =============================
// PUBLIC
// =============================
public var canvas:BitmapData;
public var ambient:Vector3D = new Vector3D( 1, 1, 1 );
/*=========================================================================================
CONSTRUCTOR
=========================================================================================*/
public function Renderer( canvas:BitmapData )
{
this.canvas = canvas;
}
public function cull( object:Object3D ):Vector.<Vector.<VertexData>>
{
var triangles:Vector.<Vector.<VertexData>> = new Vector.<Vector.<VertexData>>;
for ( var i:int; i < object.triangles.length-1; i += 2 )
{
var triangle:Vector.<VertexData> = object.triangles[i];
var v0:Vector3D = triangle[0].clone().vector;
var v1:Vector3D = triangle[1].clone().vector;
var v2:Vector3D = triangle[2].clone().vector;
var n:Vector3D = v1.subtract( v0 ).crossProduct( v2.subtract( v0 ) );
if ( n.z > 0 )
{
triangles.push( triangle );
triangles.push( object.triangles[i+1] );
}
}
return triangles;
}
public function addAmbience( value:Number=0.1 ):void
{
ambient.x += value;
ambient.y += value;
ambient.z += value;
}
public function clear():void
{
canvas.fillRect( canvas.rect, 0 );
}
public function renderObject( object:Object3D ):void
{
var triangles:Vector.<Vector.<VertexData>> = cull( object );
for ( var i:int=0; i < triangles.length; ++i )
{
render( triangles[i] );
}
}
protected function render( triangle:Vector.<VertexData> ):void
{
// not a triangle
if ( triangle.length != 3 )
{
return;
}
triangle.sort( sortByHeight );
var firstPass:Boolean;
var sx:Number = triangle[0].x;
var sy:Number = triangle[0].y;
var endX:Number = sx;
var endY:Number = sy;
var acheight:Number = triangle[2].y - sy;
var abheight:Number = triangle[1].y - sy;
var acstepX:Number = ( triangle[2].x - sx ) / (triangle[2].y - sy != 0 ? triangle[2].y - sy : 1);
var abstepX:Number = (triangle[1].x - sx ) / (triangle[1].y - sy != 0 ? triangle[1].y - sy : 1);
var div_ac:Number = acheight == 0 ? 1 : acheight;
var div_ab:Number = abheight == 0 ? 1 : abheight;
var stepY:int = triangle[2].y < sy ? -1 : 1;
// a to c vertex steps
var acstepRed:Number = (triangle[2].red - triangle[0].red) / div_ac;
var acstepGreen:Number = (triangle[2].green - triangle[0].green) / div_ac;
var acstepBlue:Number = (triangle[2].blue - triangle[0].blue) / div_ac;
var cvd:VertexData = new VertexData();
cvd.red = triangle[0].red;
cvd.green = triangle[0].green;
cvd.blue = triangle[0].blue;
// ac to b vertex steps
var abstepRed:Number = (triangle[1].red - triangle[0].red) / div_ab;
var abstepGreen:Number = (triangle[1].green - triangle[0].green) / div_ab;
var abstepBlue:Number = (triangle[1].blue - triangle[0].blue) / div_ab;
var bvd:VertexData = new VertexData();
bvd.red = triangle[0].red;
bvd.green = triangle[0].green;
bvd.blue = triangle[0].blue;
var uvstepleft:Point = new Point( (triangle[2].u - triangle[0].u) / div_ac, (triangle[2].v - triangle[0].v) / div_ac );
var uvleft:Point = new Point( triangle[0].u, triangle[0].v );
var uvstepright:Point = new Point( (triangle[1].u - triangle[0].u) / div_ab, (triangle[1].v - triangle[0].v) / div_ab );
var uvright:Point = new Point( triangle[0].u, triangle[0].v );
while( sy != triangle[2].y )
{
sx += acstepX;
sy += stepY;
cvd.red += acstepRed;
cvd.green += acstepGreen;
cvd.blue += acstepBlue;
endX += abstepX;
endY += stepY;
bvd.red += abstepRed;
bvd.green += abstepGreen;
bvd.blue += abstepBlue;
// uv steps
uvleft.x += uvstepleft.x;
uvleft.y += uvstepleft.y;
uvright.x += uvstepright.x;
uvright.y += uvstepright.y;
// rounding this for the loop
var dist:int = endX < sx ? Math.floor( endX - sx ) : Math.ceil( endX - sx );
var it:int = dist > 0 ? 1 : -1;
var dir:int = it;
var w:int = dist * dir;
// steps across
var rx:Number = (bvd.red - cvd.red) / w;
var gx:Number = (bvd.green - cvd.green) / w;
var bx:Number = (bvd.blue - cvd.blue) / w;
var xvd:VertexData = new VertexData();
xvd.red = cvd.red;
xvd.green = cvd.green;
xvd.blue = cvd.blue;
// steps across uv
var uvx:Number = (uvright.x - uvleft.x) / w;
var uvy:Number = (uvright.y - uvleft.y) / w;
var uv:Point = new Point( uvleft.x, uvleft.y );
var i:int = 0;
while( i != dist )
{
// for uv map
//var color:Number = triangle[0].getUVPixel( uv.x, uv.y );
//canvas.setPixel( sx + i, sy, applyAmbience( color ));
canvas.setPixel( sx + i, sy, toUint( xvd ));
i += it;
xvd.red += rx;
xvd.green += gx;
xvd.blue += bx;
uv.x += uvx;
uv.y += uvy;
}
if ( !firstPass && ((stepY < 0 && sy <= triangle[1].y) || (stepY > 0 && sy >= triangle[1].y)))
{
var h:Number = triangle[2].y - triangle[1].y != 0 ? triangle[2].y - triangle[1].y : 1;
h *= stepY;
abstepX = ( triangle[2].x - triangle[1].x ) / h;
firstPass = true;
// this is altered, it's really b-c step
abstepRed = ( triangle[2].red - bvd.red ) / h;
abstepGreen = ( triangle[2].green - bvd.green ) / h;
abstepBlue = ( triangle[2].blue - bvd.blue ) / h;
uvstepright = new Point( (triangle[2].u - triangle[1].u ) / h, (triangle[2].v - triangle[1].v) / h );
uvright = new Point( triangle[1].u, triangle[1].v );
}
}
}
/** applyAmbience - returns color multiplied by ambient light */
protected function applyAmbience( color:uint ):uint
{
var rgb:Vector3D = toRGB( color );
// red
rgb.x *= ambient.x;
// green
rgb.y *= ambient.y;
// blue
rgb.z *= ambient.z;
return (int(rgb.x) << 16) + (int(rgb.y) << 8) + int(rgb.z);
}
/** sorting method for returning vector ordered by y position low to high */
protected function sortByHeight( a:VertexData, b:VertexData ):Number
{
return a.y - b.y;
}
/** sorting method for returning vector ordered by x position low to high */
protected function sortByWidth( a:VertexData, b:VertexData ):Number
{
return a.x - b.x;
}
/** sorting method for returning vector ordered by z position low to high */
protected function sortByZ( a:VertexData, b:VertexData ):Number
{
return a.z - b.z;
}
protected function toRGB( color:uint ):Vector3D
{
var r:Number = color >> 16 & 0xFF;
var g:Number = color >> 8 & 0xFF;
var b:Number = color & 0xFF;
return new Vector3D( r, g, b );
}
protected function toUint( vertex:VertexData ):uint
{
var r:Number = ( vertex.red * 255 ) << 16;
var g:Number = ( vertex.green * 255 ) << 8;
var b:Number = vertex.blue * 255;
return r + g + b;
}
protected function getWidth( triangle:Vector.<VertexData> ):Number
{
triangle.sort( sortByWidth );
return triangle[2].x - triangle[0].x;
}
}
class RGB
{
// 0-255 rgb values
public var red:uint;
public var blue:uint;
public var green:uint;
public function RGB( red:uint=0, green:uint=0, blue:uint=0 )
{
this.red = red;
this.blue = blue;
this.green = green;
}
public function toUint():uint
{
return red << 16 | green << 8 | blue;
}
}