AS3 Procedural Tree with Papervision 3D

Posted: February 6th, 2010 | Author: Zack | Filed under: ActionScript 3, Experimental, Papervision 3D | Tags: , , , , | No Comments »

Procedural Tree
One of the true powers of PaperVision 3D is getting down and dirty at the geometry level and creating custom meshes. Doing this requires knowledge of what a mesh consists of and how to construct a TriangularMesh.

Basically a mesh consists of two things: Vertices and Faces. Vertices are basically the corner points of a Triangle. A triangle consists of three corner points. Faces are created from these Triangles. For instance, a Plane primitive is created from two Triangles and four Vertices see this diagram:

Anatomy of a Plane

3D modelers create tons of Vertices and Triangles to form models. We can do the same with Papervision 3D via code. The biggest issue facing developers wanting to create models in Papervision 3D is that we are restricted to the amount of geometry we can create due to performance issue. In short, lots of triangles = little performance.

This is why I have created this example, to show how you can create custom meshes and reuse geometry to optimize a model. I could have used a bunch of boxes or cylinders to create the branches, but this would have left a lot of extra Vertices that were not connected and there would have been duplicates, which is not optimized at all.

The example consists of two classes, ProceduralTree.as and Tree.as. I won’t go into the ProceduralTree.as class much because it is pretty self explanatory. It sets up a BasicView and environment as well as some of the functionality, such as “click to create” and “rotate to mouse”.

The Tree.as class is where the tree is created with two primary methods. The generateTree method is where the procedure starts. We start with a “trunk” branch and then continue looping until we’ve reached the specified depth or width is less than zero. Each loop we look in the _branches array for branched to either split or continue straight from. That’s the entire theory behind branching. Look at the last branch end and continue from there. The more branhces that split off, the more branches will be created at the next depth. Let’s take a look at this method:

private function generateTree(depth:Number = 5, startWidth:Number = 100):void
{
    _branches = [];
    _width = startWidth;
   
    var totalBranches:Number;
   
    // step through each level of the branch
    for(var i:Number = 0; i < depth; i++)
    {
        // in the "createBranch" method branches are stored.
        // these are all the end branches, so loop through and create new branches from them
        totalBranches = _branches.length;
           
        if(totalBranches > 0)
        {
            for(var ii:Number = 0; ii < totalBranches; ii++)
            {
                if(Math.random() > .5)
                {
                    // split the banch, one left and one right
                    createBranch(_branches[0], depth-i, "left");
                    createBranch(_branches[0], depth-i, "right");
                }else{
                    // continue with a straight branch
                    createBranch(_branches[0], depth-i, "straight");
                }
                // we've created new branches from the last branch, so remove it from the array
                _branches.shift();
            }
        }
        else
        {
            // no branches exist yet, so create trunk
            createBranch([], i);
        }
        // reduce width of next branches
        _width = _width - (_width / (depth - i));
    }
   
    // finalize geometry of tree
    finalizeTree();
}

You’ll see above that a branch is created through the createBranch method. Within this method we create all the geometry and calculate how to move the top points based on the direction passed in. This diagram shows the basic anatomy of each branch:

Anatomy of a Branch

Here’s the createBranch method:

private function createBranch(startPoints:Array, depth:Number, direction:String = "straight"):void
{          
    var verts:Array = geometry.vertices;
    var faces:Array = geometry.faces;
   
    var ftl:Vertex3D;
    var ftr:Vertex3D;
    var fbl:Vertex3D;
    var fbr:Vertex3D;
    var btl:Vertex3D;
    var btr:Vertex3D;
    var bbl:Vertex3D;
    var bbr:Vertex3D;
   
    var triVerts1:Array;
    var triVerts2:Array;
    var texMap1:Array;
    var texMap2:Array;
    var triFace1:Triangle3D;
    var triFace2:Triangle3D;
   
    // create arondom height for the branch.
    // max of 250, min of 50
    var height:Number = Math.floor(Math.random() * 200)+50;
    var xMod:Number = 0;
   
    switch(direction)
    {
        case "right":
            // branch is travelling to the right so set the X modifier to the right
            xMod = Math.floor(Math.random() * -200);
            break;
        case "left":
            // branch is travelling to the left so set the X modifier to the left
            xMod = Math.floor(Math.random() * 200);
            break;
    }
   
    // set a random z modifier
    var zMod:Number = Math.floor(Math.random() * 800) - 400;
 
    if(startPoints.length == 4)
    {
        // create bottom front from last branches top points
        fbl = startPoints[0];
        fbr = startPoints[1];
        bbl = startPoints[2];
        bbr = startPoints[3];
                       
        // create bottom vertices
        ftl = new Vertex3D((startPoints[0].x + xMod)+(-_width * 0.5), startPoints[0].y + height, (startPoints[0].z + zMod) + (-_width * 0.5));
        ftr = new Vertex3D((startPoints[0].x + xMod)+(_width * 0.5),  startPoints[0].y + height, (startPoints[0].z + zMod) + (-_width * 0.5));
        btl = new Vertex3D((startPoints[0].x + xMod)+(-_width * 0.5), startPoints[0].y + height, (startPoints[0].z + zMod) + (_width * 0.5));
        btr = new Vertex3D((startPoints[0].x + xMod)+(_width * 0.5),  startPoints[0].y + height, (startPoints[0].z + zMod) + (_width * 0.5));
    }
    else
    {
        // create bottom vertices
        fbl = new Vertex3D(-_width * 0.5, 0, -_width * 0.5); // front bottom left vertex
        fbr = new Vertex3D(_width * 0.5, 0, -_width * 0.5); // front bottom right vertex
        bbl = new Vertex3D(-_width * 0.5, 0, _width * 0.5); // back bottom left vertex
        bbr = new Vertex3D(_width * 0.5, 0, _width * 0.5); // back bottom right vertex
       
        // create top vertices
        ftl = new Vertex3D(-_width * 0.5, height, -_width * 0.5); // front top left vertex
        ftr = new Vertex3D(_width * 0.5, height, -_width * 0.5); // front top right vertex
        btl = new Vertex3D(-_width * 0.5, height, _width * 0.5); // back top left vertex
        btr = new Vertex3D(_width * 0.5, height, _width * 0.5); // back top right vertex
    }
   
    // add vertices to array
    verts.push(ftl, ftr, fbl, fbr, btl, btr, bbl, bbr);
   
    // next, create four side of branch, basically a box with no top or bottom
    // branch segment is created by creating front, back, right and left triangles (
    // two triangles each create a square
    // map the UV's
   
    // create front face
    triVerts1 = [fbl, ftl, ftr];
    triVerts2 = [fbr, fbl, ftr];
    texMap1 = [new NumberUV(0, 0), new NumberUV(0, 1), new NumberUV(1, 1)];
    texMap2 = [new NumberUV(1, 0), new NumberUV(0, 0), new NumberUV(1, 1)];
    triFace1 = new Triangle3D(this, triVerts1, material, texMap1);
    triFace2 = new Triangle3D(this, triVerts2, material, texMap2);
    faces.push(triFace1, triFace2);
   
    // create back face
    triVerts1 = [bbl, btl, btr];
    triVerts2 = [bbr, bbl, btr];
   
    triFace1 = new Triangle3D(this, triVerts1, material, texMap1);
    triFace2 = new Triangle3D(this, triVerts2, material, texMap2);
    faces.push(triFace1, triFace2);
   
    // create right face
    triVerts1 = [fbr, ftr, btr];
    triVerts2 = [bbr, fbr, btr];
    triFace1 = new Triangle3D(this, triVerts1, material, texMap1);
    triFace2 = new Triangle3D(this, triVerts2, material, texMap2);
    faces.push(triFace1, triFace2);
   
    // create left face
    triVerts1 = [fbl, ftl, btl];
    triVerts2 = [bbl, fbl, btl];
    triFace1 = new Triangle3D(this, triVerts1, material, texMap1);
    triFace2 = new Triangle3D(this, triVerts2, material, texMap2);
    faces.push(triFace1, triFace2);
   
    // save last 4 end points to build next branch from
    _branches.push([ftl, ftr, btl, btr]);
}

Finally, in the ProceduralTree.as class, there is the addLeaves method. This method grabs the last fifty vertices from tree object and randomly adds green spheres at them. The reason I kept this method separate from the tree itself is because this way you can add whatever type of leaves you’d like to the ends. Maybe you want no leaves at all. Either way, this approach keeps it flexible.

private function addLeaves():void
{
    _leaves = [];
   
    // create materials for leaves and composite them together
    var flatShader:FlatShadeMaterial = new FlatShadeMaterial(_light, 0x5EDD8F);
    flatShader.doubleSided = true;
   
    var wm:WireframeMaterial = new WireframeMaterial(0x0C5A23);
    wm.doubleSided = true;
   
    var leafComposite:CompositeMaterial = new CompositeMaterial();
    leafComposite.addMaterial(wm);
    leafComposite.addMaterial(flatShader);
    leafComposite.doubleSided = true;
   
    // create leaf object, which is a sphere
    var leaf:Sphere;
    var total:Number = _tree.geometry.vertices.length;
   
    // loop through the last 50 tree points and add the spheres at those positions
    for(var i:Number = total-50; i < total; i++)
    {
        // 10 spheres have been created, stop creating spheres
        if(i < 0 || _leaves.length > 10) return;
       
        // decided whether to place sphere or not
        if(Math.random() > .7)
        {
            // place sphere and position
            leaf = new Sphere(leafComposite, Math.floor(Math.random() * 100) + 20, 6, 6);
            leaf.x = _tree.geometry.vertices[i].x;
            leaf.y = _tree.geometry.vertices[i].y;
            leaf.z = _tree.geometry.vertices[i].z;
            // makes the sphere more oval shaped
            leaf.scaleX = leaf.scaleZ = 2.5 + Math.random();
            _treeHolder.addChild(leaf);
           
            _leaves.push(leaf);
        }
    }
}

I hope this helps reveal some of the possibilities of the custom meshes and the underlying mechanics of how they work. We also got into some procedural/branching theory. As always, enjoy the example and source below.

VIEW EXAMPLE
SOURCE FILES

Read the rest of this post »



Leave a Reply