AS3 Flash 3D Engine SWFZ Source Code Goes Open Source

Jono is giving SWFZ to science and the open source devices.

The SWFZ engine is one Flash 3D engine that took a different approach. It is a bit early in its technique used but the author at custom:media Jono has decided to float the source code out there in ghost mode (no active development but not dead). It is just ready to branch and others to run with it. He is floating the source but I think in 1-2 years this will be the preferred method if processors and multicore parallel usage is optimized. We shall see.

The implementation method and difference with SWFZ engine in Flash for 3d is that is is a pixel based renderer or scanline. It is based on a really fun game engine called Irrlicht which has been pretty active for the last few years but is a C++ DirectX and OpenGL engine. Since SWFZ has to run in Flash and it is a pixel renderer/scanline it has some limitations currently in Flash. Games and renders have to be fast to pull this off and Flash is limited by the software renderer but as computers get multiple processors and flash player gets better at this then this will be a viable option (it is the same thing that limits Canvas based renderers right now). One main problem with this is you can’t go too full screen the biggest sizes that perform well are smaller windows 320×240 etc. But if the processors can handle it it is actually more efficient when it removes overlap, extra triangle drawing and painters algorithm like problems dont’ pop up (triangle overlap when on same plane). This method draws pixel by pixel but fast enough flash engines like Papervision, Sandy3D and Away3D draw overlaps due to the drawing technique, back to front.

But SWFZ still manages to pull off some amazing feats such as these demos

Quake Demo

Terrain Demo

Yoshis Hip Hop Couzin

Jono has put some great classes into SWFZ engine such as bsp parsers, quake md2 parsers, animated mesh, and lots of great examples in porting C++ Irrlicht to AS3. This was a very early example of how AS3 was fun for programmers to port stuff from C or C++ into Flash. AS3 is just fun. Also be sure to check the site for more samples like an FPS game, some basic ai etc.

Jono has been working on 3d in Flash for a while and actually this message is what shows the difference betweeen this approach and other flash engines the way Papervision, Sandy and Away3D (pv3d derivative) make 3d in flash fast enough (Painter’s Algorithm and drawing skewed movieclips and textures.

Demos

More about the Engine Some Notes

The SWFZ engine.

Overview:

SWFZ engine is the result of four years of me messing with 3D in Flash.

I was a complete newbie to 3D, so a lot of learning has happened to get to here.

If you’re interested in 3D engines check out the resource links at the bottom of the page:

The Demo:

Model

  • .md2 format from ID’s Quake2.
  • Uses frame based animation
  • Textured with jpeg
  • No lighting, No Gouraud Shading, just plain texture

Skybox

  • Textures are just jpegs.

Boxes

  • Rendering – Textured Gouraud , Textured Gouraud with Alpha, Textured Gouraud with Quick Alpha, Gouraud Shaded, and the large box is just Textured.
  • Star Texture – Targa (.tga) file format.

AS3 classes

  • 171 classes and interfaces

Scene

  • No lighting
  • No collision detection

SWFZ engine technology:

The demo only shows a small part of the capabilities of the engine. In the coming weeks I will get www.custommedia.co.nz up and running and start to post more info then.

Currently implement stuff:

New file formats supported

  • .tga – Targa Image
  • .bmp – Bitmap Image
  • .3ds – 3D Studio Max
  • .bsp – Quake3 levels
  • .md2 – Quake2 models
  • .obj – Wavefront 3d object (static)
  • .zip – Read from a zip archive (all in Flash, no server side scripts)

3D Rendering

  • mipmaps
  • perspective correct texturing + affine texturing
  • Flat shading
  • Gouraud
  • Textured Gouraud
  • Textured Flat
  • Textured Two Layers
  • Gouraud Alpha
  • Textured Flat Alpha
  • Textured Gouraud Alpha

3D Scene

  • Billboards
  • Parent, Child scene nodes
  • OctTree
  • Skybox
  • Static Meshes
  • Animated Meshes
  • Basic collision detection
  • Scene node animators

If any code is useful to you maybe drop him a donation or what would be nice if this was all setup at google code and used to be integrated into other engines. Irrlicht ports are fun and there is a future in this method when processors catch up I think.

Advertisements

AS3 Extruding Pixels to 3D with Papervision 3D

Den Ivanov scored another “why didn’t I think of that” flash demos with extruding pixels into 3d with papervision.  He has some great samples and demos on his site about it.  Den Ivanov is a long time flasher and recent scores with Brahma bus (one of the coolest first papervision commercial projects) and the terrain generator.

This uses ExtrudeImage which does exactly what the class says.

AS3 Real-Time Raytracing

Forrest Briggs throwing down with a real-time raytracer in AS3. Also a C++ OpenGL version sample on the page.

Real-time pixel manipulation in flash is getting faster, but is still probably going to have to be faked in AS3, maybe AS4 will provide us per pixel speeds that Andre Michelle has been harping on since flash 8.5. Native operations can be much faster in that area. AIF might look to change some of that but that is Flash 10.

Here is the code for the as3 raytracer. Read more at laserpirate.

package
{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.utils.getTimer;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFormat;

public class RayTracer extends Sprite
{
private var t:Number;
private var dt:Number = .01;
private var frameTimeTxt:TextField;

public static const BUFFER_WIDTH:int = 160;
public static const BUFFER_HEIGHT:int = 120;
public static const BUFFER_SCALEDDOWN:int = 320 / BUFFER_WIDTH;

public static const HALF_BUFFER_WIDTH:int = BUFFER_WIDTH / 2;
public static const HALF_BUFFER_HEIGHT:int = BUFFER_HEIGHT / 2;

private var outputBitmapData:BitmapData;
private var outputBitmap:Bitmap;

public var FOV:Number = 20;

public var sphereCenterX:Array = [0, 0, 0, 0];
public var sphereCenterY:Array = [0, -.2, .4, 100.5];
public var sphereCenterZ:Array = [4, 4, 4, 10];
public var sphereRadius:Array = [.35, .35, .25, 100];
public var sphereR:Array = [255, 0, 0, 20];
public var sphereG:Array = [0, 150, 0, 20];
public var sphereB:Array = [0, 0, 255, 20];
public var sphereReflects:Array = [false, false, false, true];
public var sphereReflectiveness:Array = [0,0,0,.3];
public var sphere2dX:Array = new Array(sphereCenterX.length);
public var sphere2dY:Array = new Array(sphereCenterX.length);
public var sphere2dR:Array = new Array(sphereCenterX.length);

public var numSpheres = sphereCenterX.length;

var skyR:int = 20;
var skyG:int = 20;
var skyB:int = 20;
var skyColor:int = (skyR< <16) + (skyG<<8) + skyB; var ambientIllumination:Number = .1; var canvas:BlankClip; var theta:Number = 0; var mouseIsDown:Boolean = false; var mouseDownTheta:Number = 0; var mouseDownX:Number = 0; public function RayTracer() { outputBitmapData = new BitmapData(BUFFER_WIDTH, BUFFER_HEIGHT, false); outputBitmap = new Bitmap(outputBitmapData); addChild(outputBitmap); //outputBitmap.smoothing = true; outputBitmap.width= 320; outputBitmap.height = 240; canvas = new BlankClip; addChild(canvas); canvas.buttonMode = true; canvas.useHandCursor = true; frameTimeTxt = new TextField(); frameTimeTxt.defaultTextFormat = new TextFormat("Arial"); frameTimeTxt.x = 8; frameTimeTxt.y = 8; frameTimeTxt.width = 640; frameTimeTxt.textColor = 0xFFFFFF; frameTimeTxt.selectable = false; addChild(frameTimeTxt); t = 0; addEventListener(Event.ENTER_FRAME, update, false, 0, true); canvas.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); canvas.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); } public function mouseDownHandler(e:*):void { mouseIsDown = true; mouseDownX = stage.mouseX; mouseDownTheta = theta; } public function mouseUpHandler(e:*):void { mouseIsDown = false; } public function update(e:*) { // start frame timer and update global time var timer:Number = getTimer(); t += dt; // handle mouse rotation if( mouseIsDown ) theta = mouseDownTheta - .0015 * (stage.mouseX - mouseDownX); theta += dt; // do some funky animation sphereCenterX[0] = .5*Math.sin(theta*5); sphereCenterZ[0] =1 + .5*Math.cos(theta*5); sphereCenterX[1] = .5*Math.sin(theta*5 + 2 * Math.PI / 3); sphereCenterZ[1] = 1 + .5*Math.cos(theta*5 + 2 * Math.PI / 3); sphereCenterX[2] = .5*Math.sin(theta*5 + 4 * Math.PI / 3); sphereCenterZ[2] = 1 + .5*Math.cos(theta*5 + 4 * Math.PI / 3); // reused variables var x:int; var y:int; var i:int; var j:int; var r:int; var g:int; var b:int; var dx:Number; var dy:Number; var rayDirX:Number; var rayDirY:Number; var rayDirZ:Number; var rayDirMag:Number; var reflectRayDirX:Number; var reflectRayDirY:Number; var reflectRayDirZ:Number; var intersectionX:Number; var intersectionY:Number; var intersectionZ:Number; var reflectIntersectionX:Number; var reflectIntersectionY:Number; var reflectIntersectionZ:Number; var rayToSphereCenterX:Number; var rayToSphereCenterY:Number; var rayToSphereCenterZ:Number; var lengthRTSC2:Number; var closestApproach:Number; var halfCord2:Number; var dist:Number; var normalX:Number; var normalY:Number; var normalZ:Number; var normalMag:Number; var illumination:Number; var reflectIllumination:Number; var reflectR:Number; var reflectG:Number; var reflectB:Number; // setup light dir var lightDirX:Number = .3; var lightDirY:Number = -1; var lightDirZ:Number = -.5; var lightDirMag:Number = 1/Math.sqrt(lightDirX*lightDirX +lightDirY*lightDirY +lightDirZ*lightDirZ); lightDirX *= lightDirMag; lightDirY *= lightDirMag; lightDirZ *= lightDirMag; // vars used to in intersection tests var closestIntersectionDist:Number; var closestSphereIndex:int; var reflectClosestSphereIndex:int; // compute screen space bounding circles //canvas.graphics.clear(); //canvas.graphics.lineStyle(1, 0xFF0000, .25); for(i = 0; i < numSpheres; ++i) { sphere2dX[i] = (BUFFER_WIDTH / 2 + FOV * sphereCenterX[i] / sphereCenterZ[i]); sphere2dY[i] = (BUFFER_HEIGHT /2 + FOV * sphereCenterY[i] / sphereCenterZ[i]); sphere2dR[i] = (3 * FOV * sphereRadius[i] / sphereCenterZ[i]); //canvas.graphics.drawCircle(sphere2dX[i]*BUFFER_SCALEDDOWN, sphere2dY[i]*BUFFER_SCALEDDOWN, sphere2dR[i]*BUFFER_SCALEDDOWN); sphere2dR[i] *= sphere2dR[i]; // store the squared value } // write to each pixel outputBitmapData.lock(); for(y = 0; y < BUFFER_HEIGHT; ++y) { for(x = 0; x < BUFFER_WIDTH; ++x) { // compute ray direction rayDirX = x - HALF_BUFFER_WIDTH; rayDirY = y - HALF_BUFFER_HEIGHT; rayDirZ = FOV; rayDirMag = 1/Math.sqrt(rayDirX * rayDirX + rayDirY * rayDirY +rayDirZ * rayDirZ); rayDirX *= rayDirMag; rayDirY *= rayDirMag; rayDirZ *= rayDirMag; /// trace the primary ray /// closestIntersectionDist = Number.POSITIVE_INFINITY; closestSphereIndex = -1 for(i = 0; i < numSpheres; ++i) { // check against screen space bounding circle dx = x - sphere2dX[i]; dy = y - sphere2dY[i]; if( dx * dx + dy * dy > sphere2dR[i] ) continue;

// begin actual ray tracing if its inside the bounding circle

lengthRTSC2 = sphereCenterX[i] * sphereCenterX[i] +
sphereCenterY[i] * sphereCenterY[i] +
sphereCenterZ[i] * sphereCenterZ[i];

closestApproach = sphereCenterX[i] * rayDirX +
sphereCenterY[i] * rayDirY +
sphereCenterZ[i] * rayDirZ;

if( closestApproach < 0 ) // intersection behind the origin continue; halfCord2 = sphereRadius[i] * sphereRadius[i] - lengthRTSC2 + (closestApproach * closestApproach); if( halfCord2 < 0 ) // ray misses the sphere continue; // ray hits the sphere dist = closestApproach - Math.sqrt(halfCord2); if( dist < closestIntersectionDist ) { closestIntersectionDist = dist; closestSphereIndex=i; } } /// end of trace primary ray /// // primary ray doesn't hit anything if( closestSphereIndex == - 1) { outputBitmapData.setPixel(x, y, skyColor); } else // primary ray hits a sphere.. calculate shading, shadow and reflection { // location of ray-sphere intersection intersectionX = rayDirX * closestIntersectionDist; intersectionY = rayDirY * closestIntersectionDist; intersectionZ = rayDirZ * closestIntersectionDist; // sphere normal at intersection point normalX = intersectionX - sphereCenterX[closestSphereIndex]; normalY = intersectionY - sphereCenterY[closestSphereIndex]; normalZ = intersectionZ - sphereCenterZ[closestSphereIndex]; normalX /= sphereRadius[closestSphereIndex]; // could be multiply by precacluated 1/rad normalY /= sphereRadius[closestSphereIndex]; normalZ /= sphereRadius[closestSphereIndex]; // diffuse illumination coef illumination = normalX * lightDirX + normalY * lightDirY + normalZ * lightDirZ; if( illumination < ambientIllumination ) illumination = ambientIllumination; /// trace a shadow ray /// var isInShadow:Boolean = false; for(j = 0; j < numSpheres; ++j) { if( j == closestSphereIndex ) continue; rayToSphereCenterX = sphereCenterX[j] - intersectionX; rayToSphereCenterY = sphereCenterY[j] - intersectionY; rayToSphereCenterZ = sphereCenterZ[j] - intersectionZ; lengthRTSC2 = rayToSphereCenterX * rayToSphereCenterX + rayToSphereCenterY * rayToSphereCenterY + rayToSphereCenterZ * rayToSphereCenterZ; closestApproach = rayToSphereCenterX * lightDirX + rayToSphereCenterY * lightDirY + rayToSphereCenterZ * lightDirZ; if( closestApproach < 0 ) // intersection behind the origin continue; halfCord2 = sphereRadius[j] * sphereRadius[j] - lengthRTSC2 + (closestApproach * closestApproach); if( halfCord2 < 0 ) // ray misses the sphere continue; isInShadow = true; break; } /// end of shadow ray /// if( isInShadow ) illumination *= .5; /// trace reflected ray /// if( sphereReflects[closestSphereIndex] ) { // calculate reflected ray direction var reflectCoef:Number = 2 * (rayDirX * normalX + rayDirY * normalY + rayDirZ * normalZ); reflectRayDirX = rayDirX - normalX * reflectCoef; reflectRayDirY = rayDirY - normalY * reflectCoef; reflectRayDirZ = rayDirZ - normalZ * reflectCoef; closestIntersectionDist = Number.POSITIVE_INFINITY; reflectClosestSphereIndex = -1 for(j = 0; j < numSpheres; ++j) { if( j == closestSphereIndex ) continue; rayToSphereCenterX = sphereCenterX[j] - intersectionX; rayToSphereCenterY = sphereCenterY[j] - intersectionY; rayToSphereCenterZ = sphereCenterZ[j] - intersectionZ; lengthRTSC2 = rayToSphereCenterX * rayToSphereCenterX + rayToSphereCenterY * rayToSphereCenterY + rayToSphereCenterZ * rayToSphereCenterZ; closestApproach = rayToSphereCenterX * reflectRayDirX + rayToSphereCenterY * reflectRayDirY + rayToSphereCenterZ * reflectRayDirZ; if( closestApproach < 0 ) // intersection behind the origin continue; halfCord2 = sphereRadius[j] * sphereRadius[j] - lengthRTSC2 + (closestApproach * closestApproach); if( halfCord2 < 0 ) // ray misses the sphere continue; // ray hits the sphere dist = closestApproach - Math.sqrt(halfCord2); if( dist < closestIntersectionDist ) { closestIntersectionDist = dist; reflectClosestSphereIndex=j; } } // end loop through spheres for reflect ray if( reflectClosestSphereIndex == - 1) // reflected ray misses { r = sphereR[closestSphereIndex] * illumination; g = sphereG[closestSphereIndex] * illumination; b = sphereB[closestSphereIndex] * illumination; } else { //trace("ref hit"); // location of ray-sphere intersection reflectIntersectionX = reflectRayDirX * closestIntersectionDist + intersectionX; reflectIntersectionY = reflectRayDirY * closestIntersectionDist + intersectionY; reflectIntersectionZ = reflectRayDirZ * closestIntersectionDist + intersectionZ; // sphere normal at intersection point normalX = reflectIntersectionX - sphereCenterX[reflectClosestSphereIndex]; normalY = reflectIntersectionY - sphereCenterY[reflectClosestSphereIndex]; normalZ = reflectIntersectionZ - sphereCenterZ[reflectClosestSphereIndex]; normalX /= sphereRadius[reflectClosestSphereIndex]; // could be multiply by precacluated 1/rad normalY /= sphereRadius[reflectClosestSphereIndex]; normalZ /= sphereRadius[reflectClosestSphereIndex]; // diffuse illumination coef reflectIllumination = normalX * lightDirX + normalY * lightDirY + normalZ * lightDirZ; if( reflectIllumination < ambientIllumination ) reflectIllumination = ambientIllumination; r = sphereR[closestSphereIndex] * illumination + .5 * sphereR[reflectClosestSphereIndex] * reflectIllumination; g = sphereG[closestSphereIndex] * illumination + .5 * sphereG[reflectClosestSphereIndex] * reflectIllumination; b = sphereB[closestSphereIndex] * illumination + .5 * sphereB[reflectClosestSphereIndex] * reflectIllumination; if( r > 255 ) r = 255;
if( g > 255 ) g = 255;
if( b > 255 ) b = 255;

} // end if reflected ray hits

} /// end if reflects
else // primary ray doesn’t reflect
{
r = sphereR[closestSphereIndex] * illumination;
g = sphereG[closestSphereIndex] * illumination;
b = sphereB[closestSphereIndex] * illumination;
}

outputBitmapData.setPixel(x, y, (r<<16) + (g<<8) + b); } // end if primary ray hit } // end x loop } // end y loop outputBitmapData.unlock(); // compute FPS var fps:Number = 1.0/((getTimer() - timer) / 1000.0); frameTimeTxt.text = "Drag to rotate. FPS: " + int(fps); } } }[/sourcecode]