CS4 Flat Shaded Tie Fighter with drawTriangles

Intro

Here’s a CS4 Flat Shaded Tie Fighter. I built the tie fighter in Blender, and used drawTriangles to render the model in CS4. The shaders come directly form Ralph Hauwert’s fast light maps … thanks Ralph. Here I only treat flat shading, but in the book (and its website). I treat the rest of them: cell, gouraud, phong, and envmap.

Since I’ve treated bringing Blender and 3DSMax models into CS4 ad nauseum, I won’t belabor the point. This program culls, has its own light class with brightness (here it alternates in brightness), depth sorts, and displays concavity with no problems.

The perlin noise clouds came from flepstudio … thanks flepstudio!

CS4 Flat Shaded Teifighter drawTriangles

CS4 Flat Shaded Tiefighter using drawTriangles

Source

Demo

YouTube

Discussion

Here’s a brief explanation of flat shading in CS4.

The light class is a point light with brightness (contained in the org/lively3d/lights/ folder of your download). It gives you the position of your light source, brightness, light color, ambient color, and specular level.

Here’s the Hack

The lightColor variable is the color of your light source. The ambientColor variable is not true ambient light. It’s the first and second parameter in a gradient fill (the third one is your light color), and it governs the color that your object will take on. The specularLevel variable adjusts the number of shades between your middle ambient color and light color. And gives the appearance of making your surface shiny at low numbers-hey, it’s a great hack!

Here’s how the gradient fill works:

The beginGradientFill method has several parameters but only the ones used here are listed below: type, color, alphas, ratios, and matrix.
beginGradientFill(type, colors, alphas, ratios, matrix)

They do the following:

  • type-specifies which gradient type to use: linear or radial
  • colors-an array of color values used in the gradient
  • alphas-an array of alpha values corresponding to the colors
  • ratios-defines the percentage of the width where the color is sampled at 100%.
  • matrix- a transformation matrix which controls scale, skewing, and location of your gradient appearance.

Here’s how the light map is implemented:

A bitmapdata object is created named tempmap which is just a simple strip 255 pixels long and 1 pixel wide, the gradient is calculated using the ambient and light colors, and the gradient is drawn into the tempmap and returned.

The process described above is demonstrated in the code below, which comes from Ralph Hauwert’s LightMaps class and is used to create the light maps for flat, cell, phong, and gouraud.

var tempmap:BitmapData = new BitmapData(255,1,false,0);
var s:Sprite = new Sprite();
var m:Matrix = new Matrix();
m.createGradientBox(255,1,0,0,0);
s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,ambientColor,lightColor],[1,1,1],[0,255-specularLevel,255],m);
s.graphics.drawRect(0,0,255,1);
s.graphics.endFill();
tempmap.draw(s);
return tempmap;
Now that you understand how a simple light map is created the steps to creating a flat shade are easy:

Step 1: Create your light map

_colors = LightMaps.getFlatMapArray(lightColor, ambientColor, specularLevel );

Step 2: Return a cosine (zd) from the dot product of your light vector with your triangle normal vector. This gives the cosine of the angle of your triangle face normal to your light source. If your light source is at 90 degrees to your object triangle (no light hitting it) then the cosine value (zd) is zero. If your triangle is directly facing the light source then your cosine is 1 (completely lit).

AVect[0]=new Vector3D(dispVec[facesVec[curFace][0]].x,dispVec[facesVec[curFace][0]].y,dispVec[facesVec[curFace][0]].z);
AVect[1]=new Vector3D(dispVec[facesVec[curFace][1]].x,dispVec[facesVec[curFace][1]].y,dispVec[facesVec[curFace][1]].z);
AVect[2]=new Vector3D(dispVec[facesVec[curFace][2]].x,dispVec[facesVec[curFace][2]].y,dispVec[facesVec[curFace][2]].z);

AVect[3]= AVect[0].subtract(AVect[1]);
AVect[4]= AVect[1].subtract(AVect[2]);

AVect[5]=AVect[4].crossProduct(AVect[3]);
AVect[6]=new Vector3D(light.x, light.y, light.z);

var mag1:Number=AVect[5].length;
var mag2:Number=AVect[6].length;

var zd:Number = AVect[6].dotProduct(AVect[5])/(mag1*mag2);

In CS4, you totally eliminate Papervision’s Numbers class since all the calculations provided by that class are now internal to CS4.

So for example a dot product of two matrices in CS4 is given by

var zd:Number = AVect[6].dotProduct(AVect[5])/(mag1*mag2);

Step 3: Calculate the index value from your zd by multiplying it by 255 (hex 0xff) and grab the respective color from your “_colors” array. Essentially as your triangle turns away from your light source the hex value is reduced: effectively darkening your pixels.

zAngle = zd*0xff; //0xff if hex for 255
currentColor = _colors[zAngle];

Step 4: Render your triangle and fill it with your calculated light map color.

spObjImage.graphics.beginFill(currentColor);
spObjImage.graphics.drawTriangles(vertices);

The code above uses drawTriangles exclusively, where PV3D draws individual lines. The big advantage here is that you can make the triangles blow up – can’t wait to show you that!!! And theoretically, it should be faster than PV3D.

Figure 4-4 The Four Steps to Flat Shading

Figure 4-4 The Four Steps to Flat Shading

This probably seems like a lot to absorb, but the good news is that the rest of the shaders follow pretty much of the same process (with the addition of a few matrix methods).

To see the complete code download the source above or click on the more button below:

Main Code

package {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
import flash.net.*;

import org.lively3d.lights.SimpleLight;
import org.lively3d.materials.utils.LightMaps;

//By Michael Lively … an open source fanatic. Please use without restriction, as Jesus said, freely give and freely receive.

public class CS4TIEFlatShadePerlin extends Sprite
{
private var numVertices:int;
private var numFaces:int;

private var vertsVec:Vector.<Vector3D>=new Vector.<Vector3D>();
private var facesVec:Vector.<Array>=new Vector.<Array>();
private var facesColors:Vector.<Number>= new Vector.<Number>();

private var fLen:Number=500;

private var spBoard:Sprite=new Sprite();
private var shBack:Shape=new Shape();
private var spObject:Sprite=new Sprite();
private var spObjImage:Sprite=new Sprite();
private var doRotate:Boolean=false;
private var prevX:Number;
private var prevY:Number;

private var myTick:int=0;

private var AVect:Vector.<Vector3D>=new Vector.<Vector3D>();

//set a light in the scene
private var light:SimpleLight=new SimpleLight(100, 100, -1000, 1,0xffffff,0x887700,5);

//The ‘velocity’ variables are related to auto rotation.

private var velX:Number=1;
private var velY:Number=2;
private var velMag:Number=5;
//for shading
private var _colors:Array;

public var myFileGet:String;
public var xmlReq:URLRequest;
public var xmlLoader:URLLoader;
public var myData_xml:XML;
public var iFacNum:Number;

private var i:int;
private var j:int;
private var distArray:Array= new Array();
private var dispVec:Vector.<Vector3D>=new Vector.<Vector3D>();
private var newVertsVec:Vector.<Vector3D>=new Vector.<Vector3D>();
private var zAverage:Number;
private var dist:Number;
private var curFace:int;
private var curFaceLen:int;
private var curObjMat:Matrix3D;

private var bitmap:BitmapData;
private var angle:Number=0;
private var _offset:Number=0;

public function CS4TIEFlatShadePerlin()
{

this.addChild(spBoard);
spBoard.x=180;
spBoard.y=205;
spBoard.filters=[ new DropShadowFilter() ];
spBoard.addChild(shBack);
spBoard.addChild(spObjImage);
spObject.rotationX=0;
spObject.rotationY=0;
spObject.rotationZ=0;

var externalXML:XML;
var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest(“tiefighterlow.xml”);

loader.load(request);
loader.addEventListener(Event.COMPLETE, onComplete);

function onComplete(event:Event):void
{
var loader:URLLoader = event.target as URLLoader;
if (loader != null)
{
externalXML = new XML(loader.data);
setVertices(externalXML);

init2();
}
else
{
trace(“loader is not a URLLoader!”);
}

}
rotateObj(0,0,0);

addList();
}

private function setVertices(myData_xml:XML):void {

var mySlitVert:Array=myData_xml.myPrimSet[0].myVertices.split(“,”);
var mySlitFace:Array=myData_xml.myPrimSet[0].myFaces.split(“,”);
numVertices=(mySlitVert.length-1)/3;
var myScale:Number=20;

for(i=0;i<=numVertices;i++){

vertsVec[i]= new Vector3D(mySlitVert[i*3]*myScale,mySlitVert[i*3+1]*myScale,mySlitVert[i*3+2]*myScale);
}

numFaces = (mySlitFace.length-1)/3;

for(j=0;j<=numFaces;j++){

facesVec[j]=[mySlitFace[j*3],mySlitFace[j*3+1],mySlitFace[j*3+2]];
facesColors[j]=Math.random()*0xffffff;
}}

//There are no changes in rotateObj function.

private function rotateObj(rotx:Number,roty:Number,rotz:Number):void {

spObject.transform.matrix3D.appendRotation(rotx,Vector3D.X_AXIS);
spObject.transform.matrix3D.appendRotation(roty,Vector3D.Y_AXIS);
spObject.transform.matrix3D.appendRotation(rotz,Vector3D.Z_AXIS);
curObjMat=spObject.transform.matrix3D.clone();
spObjImage.graphics.clear();

for(i=0;i<numVertices;i++){

newVertsVec[i]=curObjMat.deltaTransformVector(vertsVec[i]);

}

for(i=0;i<numVertices;i++){

newVertsVec[i].w=(fLen+newVertsVec[i].z)/fLen;
newVertsVec[i].project();
}

//Depth Sorting

for(i=0;i<numFaces;i++){

zAverage=0;
for(j=0;j<3;j++){
zAverage+=newVertsVec[facesVec[i][j]].z;
}

zAverage/=3;
dist=zAverage;
distArray[i]=[dist,i];
}

//distArray.sortOn(“byDist”, Array.DESCENDING | Array.NUMERIC);

distArray.sort(byDist);

for(i=0;i<numVertices;i++){

dispVec[i]=new Vector3D();
dispVec[i].x=newVertsVec[i].x;
dispVec[i].y=newVertsVec[i].y;
dispVec[i].z=newVertsVec[i].z;
}

for(i=0;i<numFaces;i++){

curFace=distArray[i][1];
var Shader:String=”flat”;

switch(Shader){

case “flat”:

AVect[0]=new Vector3D(dispVec[facesVec[curFace][0]].x,dispVec[facesVec[curFace][0]].y,dispVec[facesVec[curFace][0]].z);
AVect[1]=new Vector3D(dispVec[facesVec[curFace][1]].x,dispVec[facesVec[curFace][1]].y,dispVec[facesVec[curFace][1]].z);
AVect[2]=new Vector3D(dispVec[facesVec[curFace][2]].x,dispVec[facesVec[curFace][2]].y,dispVec[facesVec[curFace][2]].z);

AVect[3]= AVect[0].subtract(AVect[1]);
AVect[4]= AVect[1].subtract(AVect[2]);

AVect[5]=AVect[4].crossProduct(AVect[3]);
AVect[6]=new Vector3D(light.x, light.y, light.z);

var mag1:Number=AVect[5].length;
var mag2:Number=AVect[6].length;

var zd:Number = AVect[6].dotProduct(AVect[5])/(mag1*mag2);

if(zd < 0){
zd = 0;
}else{
zd = Math.abs(zd);
};

_colors = LightMaps.getFlatMapArray(light.lightColor, light.ambientColor, light.specular);
var zAngle:int = zd*0xff*light.brightness;
var currentColor:Number = _colors[zAngle];

break;
case “cell”:
//See the book website
break;
case “gouraud”:
//See the book website
break;
case “phong”:
//See the book website
break;
case “envmap”:
//See the book website
break;

default:
//Not in my arsenal
break;}

if(zd>0){
var vertices:Vector.<Number>=new Vector.<Number>();
vertices.push(dispVec[facesVec[curFace][0]].x, dispVec[facesVec[curFace][0]].y);
vertices.push(dispVec[facesVec[curFace][1]].x, dispVec[facesVec[curFace][1]].y);
vertices.push(dispVec[facesVec[curFace][2]].x, dispVec[facesVec[curFace][2]].y);
spObjImage.graphics.lineStyle(0);
spObjImage.graphics.beginFill(currentColor);
spObjImage.graphics.drawTriangles(vertices);
}}
}

private function byDist(v:Array,w:Array):Number {

if (v[0]>w[0]){

return -1;

} else if (v[0]<w[0]){

return 1;

} else {

return 0;
}

}

private function init2():void
{
this.bitmap=new BitmapData
(stage.stageWidth,stage.stageHeight,
true,0xFFFFFFFF);
var image:Bitmap=new Bitmap(this.bitmap);
this.addChild(image);
this.addEventListener
(Event.ENTER_FRAME,onEnterFrame2);
}

private function onEnterFrame2(event:Event):void
{
var point:Point=new Point(this._offset,0);
this.bitmap.perlinNoise(100,100,2,100,false,
true,BitmapDataChannel.ALPHA,false,
[point,point]);
this._offset+=2;

light.brightness=Math.abs(Math.sin(myTick++/4));
}

private function addList():void{

spBoard.addEventListener(MouseEvent.ROLL_OUT,boardOut);
spBoard.addEventListener(MouseEvent.MOUSE_MOVE,boardMove);
spBoard.addEventListener(MouseEvent.MOUSE_DOWN,boardDown);
spBoard.addEventListener(MouseEvent.MOUSE_UP,boardUp);
this.addEventListener(Event.ENTER_FRAME,whenEnterFrame);

}

private function boardOut(e:MouseEvent):void {
doRotate=false;
}

private function boardDown(e:MouseEvent):void {

prevX=spBoard.mouseX;
prevY=spBoard.mouseY;
velX=0;
velY=0;
velMag=0;
doRotate=true;
}

private function boardUp(e:MouseEvent):void {

doRotate=false;

}

private function boardMove(e:MouseEvent):void {

var locX:Number=prevX;
var locY:Number=prevY;
if(doRotate){
prevX=spBoard.mouseX;
prevY=spBoard.mouseY;
velX=-2*(prevX-locX);
velY=2*(prevY-locY);
velMag=Math.abs(velX)+Math.abs(velY);
rotateObj(prevY-locY,-(prevX-locX),0);
e.updateAfterEvent();

}
}

private function whenEnterFrame(e:Event):void {

if(!doRotate && velMag>0){

rotateObj(velY,velX,0);

}}
}}

Light Class

package org.lively3d.lights
{

public class SimpleLight
{

public var x:Number;
public var y:Number;
public var z:Number;
public var lightColor:uint;
public var ambientColor:uint;
public var specular:int;

public var brightness:Number;

//By Michael Lively … an open source fanatic. Please use without restriction, as Jesus said, freely give and freely receive.

public function SimpleLight(x:Number=100,y:Number=100,z:Number=-100, brightness:Number=1,lightColor:uint=0xffffff,ambientColor:uint=0x000099,specular:int=20 )
{
this.x=x;
this.y=y;
this.z=z;

this.brightness=brightness;

this.lightColor=lightColor;
this.ambientColor=ambientColor;
this.specular=specular;

}

}
}

Ralph’s fast Light Map

package org.lively3d.materials.utils
{
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Sprite;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Point;

/**
* @Author Ralph Hauwert mod by Lively
*/
public class LightMaps
{
private static var origin:Point = new Point();

public static function getFlatMapArray(lightColor:uint, ambientColor:uint, specularLevel:uint):Array
{
var array:Array = new Array();
var tempmap:BitmapData = new BitmapData(256,1,false,0);
var s:Sprite = new Sprite();
var m:Matrix = new Matrix();
m.createGradientBox(256,1,0,0,0);
s.graphics.beginGradientFill(GradientType.LINEAR, [lightColor,ambientColor,ambientColor],[1,1,1],[0,255-specularLevel,255],m);
s.graphics.drawRect(0,0,256,1);
s.graphics.endFill();
tempmap.draw(s);

var i:int = 256;
while(i–){
array.push(tempmap.getPixel(i,0));
}

tempmap.dispose();

return array;
}

public static function getFlatMap(lightColor:uint, ambientColor:uint, specularLevel:uint):BitmapData
{
var tempmap:BitmapData = new BitmapData(255,1,false,0);
var s:Sprite = new Sprite();
var m:Matrix = new Matrix();
m.createGradientBox(255,1,0,0,0);
s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,ambientColor,lightColor],[1,1,1],[0,255-specularLevel,255],m);
s.graphics.drawRect(0,0,255,1);
s.graphics.endFill();
tempmap.draw(s);
return tempmap;
}

public static function getPhongMap(lightColor:uint, ambientColor:uint, specularLevel:uint, height:int = 255, width:int = 255):BitmapData
{
var lw:Number = height;
var lh:Number = width;
var s:Sprite = new Sprite();
var mat:Matrix = new Matrix();
mat.createGradientBox(lw,lw,0,0,0);
s.graphics.beginGradientFill(GradientType.RADIAL, [lightColor,ambientColor,ambientColor], [1,1,1], [0,255-specularLevel,255], mat);
s.graphics.drawRect(0,0,lw,lw);
s.graphics.endFill();
var bmp:BitmapData = new BitmapData(lw,lw,false,0x0000FF);
bmp.draw(s);
return bmp;
}

public static function getGouraudMap( lightColor:uint, ambientColor:uint, specularLevel:uint ):BitmapData
{
var gouraudMap:BitmapData = new BitmapData(255,3,false,0xFFFFFF);
var s:Sprite = new Sprite();
var m:Matrix = new Matrix();
m.createGradientBox(255,3,0,0,0);
// s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,lightColor],[1,1],[0,255],m);
s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,ambientColor,lightColor],[1,1,1],[0,specularLevel,0xFF],m);
s.graphics.drawRect(0,0,255,3);
s.graphics.endFill();
gouraudMap.draw(s);
return gouraudMap;
}

public static function getGouraudMaterialMap( lightColor:uint, ambientColor:uint, specularLevel:uint ):BitmapData
{
var gouraudMap:BitmapData = new BitmapData(256,3,false,0xFFFFFF);
var s:Sprite = new Sprite();
var m:Matrix = new Matrix();
m.createGradientBox(256,3,0,0,0);
// s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,lightColor],[1,1],[0x77,0xFF],m);
s.graphics.beginGradientFill(GradientType.LINEAR, [ambientColor,ambientColor,lightColor],[1,1,1],[0,specularLevel,0xFF],m);
s.graphics.drawRect(0,0,256,3);
s.graphics.endFill();
gouraudMap.draw(s);
return gouraudMap;
}

public static function getCellMap(color_1:uint, color_2:uint, steps:int):BitmapData
{
/**
* Posterize Code derived from Mario Klingemann.
*/
var bmp:BitmapData = LightMaps.getPhongMap(color_1,color_2,0,255,255);
var n:Number = 0;
var r_1:int = (color_1&0xFF0000)>>16;
var r_2:int = (color_2&0xFF0000)>>16;
var rStep:int = r_2-r_1;
var rlut:Array = new Array();
var glut:Array = new Array();
var blut:Array = new Array();
for (var i:int = 0; i <= 255; i++) {
rlut[i] = (i-(i % Math.round(256/steps))) << 16;
glut[i] = (i-(i % Math.round(256/steps))) << 8;
blut[i] = (i-(i % Math.round(256/steps)));
}
bmp.paletteMap(bmp,bmp.rect,origin, rlut, glut, blut);
bmp.applyFilter(bmp, bmp.rect, origin, new BlurFilter(2,2,2));
return bmp;
}
}
}

Advertisements

3 Responses to CS4 Flat Shaded Tie Fighter with drawTriangles

  1. […] Brightness to PV3D Light Source3D ModelingCreating a Spherical Panorama in PapervisionCS4 Flat Shaded Tie Fighter with drawTrianglesAbout […]

  2. Gil says:

    I’ve been following your blog for some time. However, reading your code on the Blog is really painful! Please use one of the many code formatting scripts available free on the net. Thanks!

    • Mike Lively says:

      Thanks for the advice … this actually brings up an interesting point.

      WordPress.com is free and easy. So we teach it to our academics and use it as well, but it doesn’t allow plugins (thus no code highlighters). I’ll block quote our code from now on – that will help a little.

      I’ll take care of this problem on the book’s website – thanks for the comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: