CS4 Animated Shuttle – drawTriangles Blender Parser

Intro

There’s been a number of posts speculating whether 3D models could be brought into CS4. The problem has do with properly mapping uv coordinates to the appropriate vertex values. In this post, I’ll demonstrate the development of a very simple parser which allows you to bring Blender models into CS4. It uses the drawTriangles method, the Blender XML Exporter created in a previous post, and a one-to-one mapping scheme of vertex and uv data points.

I’m sure that as time progresses more advanced parsers will be developed, but for our present projects, this one works really well.

Animated Shuttle

Animated Shuttle

Demo

Source

YouTube

Discussion

To create a Blender parser using the drawTriangles method, you’ve got to get your mapping right. It must be one-to-one: each vertex must have one unique uv point. But in modeling software, such as Blender, that’s not true. You can have multiple uv points for one vertex point as shown in the image below

One 3D Point Becomes four in 2D

One 3D Point Becomes four in 2D

So, you can see the problem from the figure above. When you unfold a pyramid, uv mapping of 0ne 3D vertex point becomes four 2D points. In this case, the Blender data will give four uv data points for the apex of your pyramid above (point 4: 0,0,0 -> uv:points: 00, 01, 10, 11). But drawTriangles does not know what to do with this. It wants one uv point for each vertex. The way around this problem is to collect all similar uv data points per vertex and assign a new vertex number for non -unique points. So for example,

(point 4: 0,0,0 -> uv:points: 00, 01, 10, 11

becomes,

point 4: 0,0,0 -> 00
point 5: 0,0,0 -> 01
point 6: 0,0,0 -> 10
point 7: 0,0,0 -> 11

Importantly, points 0-3 are the four base pyramid points which are not unwrapped and as a result don’t need to be assigned new vertex numbers. The new vertex numbers have the same vertex coordinates as shown above – only the point assignment for the triangular fan is changed.

Note: The number of extra points you need to add is entirely dependent on how you unfold your object. If you use the hole punch method, you only need four extra vertices for an entire figure. As in anything, there’s no free lunch. You always give up something to gain something.

Code

The code sorts points into unique vertices and uv numbers (creating a 0ne-to-one mapping). If unique data can not be found new vertices are assigned creating a 0ne-to-one mapping as described in the post on the polar cube.

Blender XML Exporter

The whole process starts by creating a Blender XML file and photoshop image. This is done by using the Blender XML Exporter discussed in an earlier post.

The Blender XML file is imported into the program, parsed, and sorted. Then the sorter code below is used to create the one-to-one mapping scheme discussed above. Here are the steps:

1. The code starts by grabbing the index and vertex data created from the Blender XML export.

for (var i:int = 0; i< myFaceNum; i++) {

//Grab Indices from Blender XML
indices.push(int(mySplitFace[12*i]));
indices.push(int(mySplitFace[12*i+1]));
indices.push(int(mySplitFace[12*i+2]));

//Grab UV Data from Blender XML
myUVDatArray.push(Number(mySplitFace[12*i+3]));
myUVDatArray.push(Number(1-mySplitFace[12*i+4]));

myUVDatArray.push(Number(mySplitFace[12*i+5]));
myUVDatArray.push(Number(1-mySplitFace[12*i+6]));

myUVDatArray.push(Number(mySplitFace[12*i+7]));
myUVDatArray.push(Number(1-mySplitFace[12*i+8]));
}

2. The raw data above is sorted and a one-to-one mapping scheme created.

//Ferts Sorting Program
myFertsNum=indices.length;
myVertsNum=verts.length/3-1;//iteration number

for (var j:int = 0; j< myFertsNum; j++) {
for (var k:int = j+1; k<myFertsNum; k++) {
if (indices[j]==indices[k]) {
if ( (myUVDatArray[2*j]==myUVDatArray[2*k]) && (myUVDatArray[2*j+1]==myUVDatArray[2*k+1]) ) {
} else { verts.push(verts[3*indices[k]],verts[3*indices[k]+1],verts[3*indices[k]+2])
myVertsNum++;
indices[k]=myVertsNum; }}}}

3. And the uvtData is extracted.

//Sort uvtData (verts match uvtData-the golden rule for drawTriangles)
for (var m:int = 0; m<verts.length/3; m++) {
for (var n:int = 0; n<indices.length; n++) {
if (indices[n]==m) {
uvtData.push(myUVDatArray[2*n], myUVDatArray[2*n+1], 0);
break; }}}

Important Note!!!

The resulting parser works really well for convex structures, but has difficulty with concave objects. Due to sorting issues.

Both Flash & Math and Dorking Around 3D in Flash (and in one of my previous post) have suggested individual triangle sorting routines, and Dorking Around 3D in Flash has a working textured convex example using triangle sorting. Further testing and development needs to be done in this area: Individual triangle sorting may handle concave figures without a problem, but at present they do not use the drawTriangles method or an XML importer from Blender, as presented in this post.

Encapsulation

The code is fully encapsulated and closely resembles Papervision3D. A primitive is declared and an animation loop created inside a wrapper file (just as in Papervision3D).

blenderHouse = new BlenderPrim(“assets/data/shuttle.xml”, bmp, 300.0, 300.0, 10);

The parameters of the constructor function above are xml, bitmap, width and height. And the Blender Primitive lives in tbe org/lively3d/objects/primitives folder. This is the same as Papervision3D’s primitive path except for the lively3d part which is used to distinguish the different approaches.

Click the more button below to view the wrapper and Blender Primitive classes.

Wrapper Class

package {
import flash.events.Event;
import flash.display.*;
import flash.geom.*;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.filters.*;

import org.lively3d.objects.primitives.*;

public class BlenderMain extends Sprite {

//Image

[Embed(source='assets/images/shuttle.jpg')]
public var MyPhoto:Class;

private var blenderHouse:BlenderPrim;
private var myAngle:Number=0;
private var mySound:URLRequest=new URLRequest(“assets/sounds/sound1.mp3″);
private var sound:Sound = new Sound();

public function BlenderMain():void {

sound.load(mySound);
sound.addEventListener(Event.COMPLETE, onComplete);

var bmp:BitmapData = (new MyPhoto()).bitmapData;

blenderHouse = new BlenderPrim(“assets/data/shuttle.xml”, bmp, 300.0, 300.0, 10);

blenderHouse.x = stage.stageWidth/2;
blenderHouse.y = stage.stageHeight/2;
blenderHouse.z=-200;

blenderHouse.filters =[ new GlowFilter(0xFEC167,0.7,50.0,50.0,1) ];

addChild(blenderHouse);

addEventListener(Event.ENTER_FRAME, update);

}

private function onComplete(event:Event):void{

sound.play();

}

private function update(e:Event):void {

myAngle+=1;
blenderHouse.animLoop();
blenderHouse.rotationZ=myAngle;

}
}
}

Blender Primitive

package org.lively3d.objects.primitives{

import flash.net.*;

import flash.display.*;
import flash.geom.*;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;

//By Michael Leroy Lively. No Restrictions! I’m an open source fanatic – please use my code however you want, just don’t hold me responsible if it breaks!!! My email is livelyjesus@gmail.com

public class BlenderPrim extends Sprite {

public var w:Number=800;
public var h:Number=600;

public var bitmapData:BitmapData=new BitmapData(1,1,true,0xFFFFFFFF);

//Primitive Declarations
private var primitive:Sprite;
private var paraVec:Vector3D;
private var bmp:BitmapData;
private var myXML:String;
public var myAngle:Number=0;
public var xmlLoaded:Boolean=false;
public var focalLength:Number=250;

//Vertice Data
private var verts:Vector.<Number>=new Vector.<Number> ;
private var indices:Vector.<int>=new Vector.<int> ;
private var uvtData:Vector.<Number>=new Vector.<Number> ;
private var projectedVerts:Vector.<Number>;

private var perspective:PerspectiveProjection;
private var projectionMatrix:Matrix3D = new Matrix3D();

//XML Code
private var xmlReq:URLRequest;
private var xmlLoader:URLLoader;
private var myData_xml:XML;
private var iFacNum:Number;

private var myFertsNum:int;
private var myVertsNum:int;
private var stayAWhile:Number;

//Create Analysis Arrays
private var myIndexArray:Vector.<int>=new Vector.<int> ;
private var myUVDatArray:Vector.<Number>=new Vector.<Number> ;

public function BlenderPrim(myXMl:String, bmp:BitmapData, w:Number, h:Number, myAngle:Number) {

myAngle=myAngle;
myXML=myXMl;
bitmapData=bmp;
this.w=w;
this.h=h;

var externalXML:XML;
var loader:URLLoader = new URLLoader();
var request:URLRequest=new URLRequest(myXMl);

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);

buildBlender(externalXML);

xmlLoaded=true;

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

}

private function buildBlender(myData_xml:XML):void {

var myVerts:Array=myData_xml.myPrimSet[1].myVertices.split(“,”);
var mySplitFace:Array=myData_xml.myPrimSet[1].myFaces.split(“,”);

//Arrays created to make the anaylysis easier and the program portable

for (var iverts:int = 0; iverts< myVerts.length-1; iverts++) {

verts.push( myVerts[iverts]);

}

var myFaceNum:int = (mySplitFace.length-1)/12;

for (var i:int = 0; i< myFaceNum; i++) {

//Indices
indices.push(int(mySplitFace[12*i]));
indices.push(int(mySplitFace[12*i+1]));
indices.push(int(mySplitFace[12*i+2]));

trace(int(mySplitFace[12*i])+” “+int(mySplitFace[12*i+1])+” “+int(mySplitFace[12*i+2]));

//UV Data

myUVDatArray.push(Number(mySplitFace[12*i+3]));
myUVDatArray.push(Number(1-mySplitFace[12*i+4]));
//trace(Number(mySplitFace[12*i+3])+” “+Number(mySplitFace[12*i+4]));

myUVDatArray.push(Number(mySplitFace[12*i+5]));
myUVDatArray.push(Number(1-mySplitFace[12*i+6]));
//trace(Number(mySplitFace[12*i+5])+” “+Number(mySplitFace[12*i+6]));

myUVDatArray.push(Number(mySplitFace[12*i+7]));
myUVDatArray.push(Number(1-mySplitFace[12*i+8]));
//trace(Number(mySplitFace[12*i+7])+” “+Number(mySplitFace[12*i+8]));
}

//Ferts Sorting Program
myFertsNum=indices.length;
myVertsNum=verts.length/3-1;//iteration number

trace(myVertsNum+ “my vet number minus one”);

for (var j:int = 0; j< myFertsNum; j++) {

for (var k:int = j+1; k<myFertsNum; k++) {

if (indices[j]==indices[k]) {

if ( (myUVDatArray[2*j]==myUVDatArray[2*k]) && (myUVDatArray[2*j+1]==myUVDatArray[2*k+1]) ) {

} else {

verts.push(verts[3*indices[k]],verts[3*indices[k]+1],verts[3*indices[k]+2]);

myVertsNum++;

indices[k]=myVertsNum;

}
}

}
}

//Sort uvtData (verts match uvtData-the golden rule for drawTriangles)
for (var m:int = 0; m<verts.length/3; m++) {

for (var n:int = 0; n<indices.length; n++) {

if (indices[n]==m) {

uvtData.push(myUVDatArray[2*n], myUVDatArray[2*n+1], 0);

break;

}
}
}

//Instantiate parameters
projectedVerts = new Vector.<Number>();

//remove primitive from stage before adding the next one

addChild(primitive= new Sprite());

//Set perspective
perspective= new PerspectiveProjection();
perspective.fieldOfView=35.0;// camera angle, in degrees

//Initialize projected vertices
for (var p:int = 0; p<verts.length/3; p++) {
projectedVerts.push(0.0,0.0);
}

}

public function animLoop():void {
// Set up a viewpoint:

if(xmlLoaded==true){
projectionMatrix=perspective.toMatrix3D();

//projectionMatrix.prependTranslation(.1,.1,0.4);
myAngle+=.1;
//Rotate Primtive
projectionMatrix.prependRotation( 90 ,new Vector3D(0,0,1));
projectionMatrix.prependRotation( 90 ,new Vector3D(0,1,0));
projectionMatrix.prependRotation( -180 ,new Vector3D(1,0,0));
//projectionMatrix.prependRotation( myAngle ,new Vector3D(1,0,0));
//projectionMatrix.prependRotation( myAngle ,new Vector3D(0,0,1));

//blenderHouse.z=500*Math.sin(myAngle)+1000;
//blenderHouse.x=500*Math.cos(myAngle)+200;

projectionMatrix.prependTranslation((2+Math.sin(myAngle)),4*(2+Math.cos(myAngle))-8,0);

//var myYRot:Number=Math.atan2(Math.sin(myAngle), -Math.cos(myAngle)*180/Math.PI );

projectionMatrix.prependRotation(myAngle*180/Math.PI+90 ,new Vector3D(0,0,1));

//Update projection vectors
Utils3D.projectVectors(projectionMatrix, verts, projectedVerts, uvtData);
//Draw primtive
with (primitive.graphics) {
clear();
beginBitmapFill(bitmapData,null, false,false);
drawTriangles(projectedVerts, indices, uvtData,TriangleCulling.NEGATIVE);
endFill();

}

}

}

}
}

About these ads

8 Responses to CS4 Animated Shuttle – drawTriangles Blender Parser

  1. makc3d says:

    about multiple UVs per vertex thing, you can duplicate vertices, but this takes extra CPU when you transform vertices vector. this is negligible for small shuttles, right, but when it comes to something big, you will have to track duplicated vertices, and in order to do so you are going to need same sort of abstraction that papervision provides :( so, we can wait for pv/away/etc team to finish their CS4 branch, or we can hope for from-the-scratch CS4 engine (such as agile3d), but one thing for sure – I aint gonna write mine own.

  2. Mike Lively says:

    Actually, if you use the hole punch method, its only four extra vertices for an entire object.

    And if you use the polar equations developed earlier (polar cube), no extra vertices are needed at all…

    Also, I’ve been trained by Ralph Hauwert the primary author of Great White (Papervision3D 2.0), and by John Grden (another core team member) in Papervision3D optimization. The techniques of encapsulation that I am using are the ones I learned from Ralph. And my approach to optimization is from John.

    The Papervision3D team only got 50% of what it wanted in the Flash 10 engine…thus the approach. However, I’m tickled pink to get my models flying around in CS4…and as you will see in future post…it’s a little more robust than one might think.

    Next week I’ll be releasing the polar sculpty prim which will work with any program…Best Regards…thanks for your post.

  3. makc3d says:

    I wonder how well that hole punch would work for torus, for example? Also, most of artists just unwrap the texture into hundreds of fragments, e.g. here http://img.photobucket.com/albums/v282/serverandenforcer/UVWunwrap.jpg

  4. Mike Lively says:

    Right…! Thanks for the input.

  5. [...] points per vertex (or triangle fan), will not work with the drawTriangles method of CS4. There are work-a-rounds, but eventually it all boils down to creating some type of one-to-one mapping [...]

  6. Pradeek says:

    This is cool but how many faces can Flash Player 10 handle without crashing ? Have you tested this yet ?

    • Mike Lively says:

      I just finished a Google Maps project where I had numerous 3D models running simultaneously with no glitches or slow downs. I haven’t had anything crash yet. But we build low polygon and count our vertices.

      I think the answer is that it performs as good as the Flash 10 engine can perform: since by using drawTriangles the majority of the work is being done by the Flash 10 engine. So it’s as good as it gets for a scripted language.

      My real concern has been the convex/concave issue which at this point I can’t seem to get around…I appreciate the comment. Thanks

  7. anton says:

    Yep, this “one UV per vertex” is ludicrous and seems to be the bottleneck for a flash 10 – based 3d engine. I’ve managed to bring in and transform a model with around 25,000 polys (triangles) at 35 frames per second, but rendering was done in a single pass and obviously all textures were messed up. I’m still trying to find a solution to map texcoords from collada to single vertex.

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: