3D Lines in CS4 using drawPath

Intro

In the previous post on Pendulums, Papervision’s line3D class was used to plot the orbit of a Foucault Pendulum in a rotated plane. But there was a problem – adding more line segments slowed the processor and as result line segments had to be removed. Aha! You just thought I had created some really cool effect by erasing the end of my line while making the front. No, without erasing, everything would eventually slow down to a stand still.

Let’s speed things up a little by using CS4’s drawPath method. Click on the image below to see the demo.

3D Lines in CS4

3D Lines in CS4

Source

Demo

YouTube


Discussion: Drawing Points and Lines

Keith Peters in his book does a similar example using CS3 line functions, but he doesn’t sort his filled circles. His circles are black, and when you run his demo it appears as if the application is z-sorting-but it isn’t. If you try to applying his zSort algorithm the connecting lines go hay-wire. They don’t know where to go and try to follow his black circles as they rapidly sort.

Here’s the Trick

Commonly when we run into such issues in Papervision3D we create dummy graphics for our wayward objects to follow. So in this case you create dummy graphics under your circles for your lines to follow. Thus, separating the zSortng of the circles from the positions of your lines.

There ‘s a little bit of double processing here, but it’s worth it. And it’s very typical of solving problems of this type. In the code below, you see the parallel creation of the circle (or ball) and the dummy marker (or mark).

var ball:BallCS4 = new BallCS4(10, 0);
var mark:MarkCS4 = new MarkCS4(0, 0);
marks.push(ball);
balls.push(ball);
mark.xpos = ball.xpos = Math.random() * 200 – 100;
mark.ypos = ball.ypos = Math.random() * 200 – 100;
mark.zpos = ball.zpos = Math.random() * 200 – 100;
myHolder.addChild(ball);
myHolder.addChild(mark);

The MarkCS4 class is your dummy marker and is an object of zero pixel size – how do like that one! The BallCS4 class is similar to the MarkCS4 class. But draws a circle instead of a zero pixel line.

BallCS4 uses the drawPath method to create its circle in CS4. Since there are no drawCircles methods in CS4 you have to create one. I used four radial curves (created by the CURVE_TO method), which looks fine for small circles, but you’ll need to add eight for larger circles. The radius parameter (below) determines the size of your filled circle.

commands.push(GraphicsPathCommand.MOVE_TO);
data.push(-radius/1.9, radius/1.9);

data.push(0, radius);
data.push(radius/1.9, radius/1.9);

data.push(radius, 0);
data.push(radius/1.9, -radius/1.9);

data.push(0, -radius);
data.push(-radius/1.9, -radius/1.9);

data.push(-radius, 0);
data.push(-radius/1.9, radius/1.9);

commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);

graphics.lineStyle(0);
graphics.beginFill(0x800000);
graphics.drawPath(commands, data);

Extra Reading

A great article which explains this in more detail can be found on Senocular’s blog. Once you’ve got your “commands” and “data” Vectors loaded, you throw them into the drawPath function…and TaDah – sphere (or in this case filled circle)! Instantiate it as many times as you want.

zSort

Now that you’ve decoupled the spheres from the lines using the marker graphics you can now use Keith’s zSort method.

private function sortZ():void
{
balls.sortOn(“zpos”, Array.NUMERIC | Array.DESCENDING );
for(var i:uint = 0; i < numBalls; i++)
{
var ball:BallCS4 = balls[i];
myHolder.addChild(ball as Sprite);
}
}

The Big Limitation

The big limitation is that drawPath only draws in x and y – hey where’s the z Adobe? So to get around that limitation you have to use perspective scaling given in the code below:

var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = ball.xpos*scale;
ball.y = ball.ypos*scale;

That’s it, you’re ready to start using 3D lines in CS4. In an upcoming post, we will treat Creating 3D Springs and Making a Node Garden. And my favorite, 3D spring data visualization which I can’t wait to release. I’ve been working on this one for a while.

To see the entire code download the source above or click on the more button below.

Main Application

package {
import __AS3__.vec.Vector;

import flash.display.GraphicsPathCommand;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import objects.*;

public class Lines3DCS4 extends Sprite
{
private var balls:Array = new Array();
private var marks:Array = new Array();

private var numBalls:uint = 40;
private var fl:Number=250;
private var vpX:Number = stage.stageWidth/2;
private var vpY:Number = stage.stageHeight/2;
private var commands:Vector.<int> = new Vector.<int>(50,true);
private var data:Vector.<Number> = new Vector.<Number>(100,true);

private var myHolder:Sprite = new Sprite();
//private var myHolder:Sprite = new Sprite();

public function Lines3DCS4()
{
init();
}

private function init():void
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;

for(var i:uint = 0; i < numBalls; i++)
{
var ball:BallCS4 = new BallCS4(10, 0);
var mark:MarkCS4 = new MarkCS4(0, 0);
marks.push(ball);
balls.push(ball);
mark.xpos = ball.xpos = Math.random() * 200 – 100;
mark.ypos = ball.ypos = Math.random() * 200 – 100;
mark.zpos = ball.zpos = Math.random() * 200 – 100;
myHolder.addChild(ball);
myHolder.addChild(mark);

}

addChild(myHolder);

myHolder.x=vpX+100;
myHolder.y=vpY;

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{

var angleX:Number = (mouseY – vpY) * .0005;
var angleY:Number = (mouseX – vpX) * .0005;
for(var i:uint = 0; i < numBalls; i++)
{
var ball:BallCS4 = balls[i];
rotateX(ball, angleX);
rotateY(ball, angleY);

var mark:BallCS4 = marks[i];

rotateX(mark, angleX);
rotateY(mark, angleY);

doPerspective(ball);
doPerspective(mark);

}

sortZ();

commands[0] = GraphicsPathCommand.MOVE_TO;
data[0] = marks[0].x; // x
data[1] = marks[0].y;

for(i = 1; i < numBalls; i++)
{
data[2*i]=marks[i].x;
data[2*i+1]=marks[i].y;

commands[i] = GraphicsPathCommand.LINE_TO;

}

myHolder.graphics.clear();
myHolder.graphics.lineStyle(0);
myHolder.graphics.drawPath(commands, data);

}

private function rotateX(ball:BallCS4, angleX:Number):void
{
var cosX:Number = Math.cos(angleX);
var sinX:Number = Math.sin(angleX);

var y1:Number = ball.ypos * cosX – ball.zpos * sinX;
var z1:Number = ball.zpos * cosX + ball.ypos * sinX;

ball.ypos = y1;
ball.zpos = z1;
}
private function rotateY(ball:BallCS4, angleY:Number):void
{
var cosY:Number = Math.cos(angleY);
var sinY:Number = Math.sin(angleY);

var x1:Number = ball.xpos * cosY – ball.zpos * sinY;
var z1:Number = ball.zpos * cosY + ball.xpos * sinY;

ball.xpos = x1;
ball.zpos = z1;
}

private function doPerspective(ball:BallCS4):void
{

if(ball.zpos > -fl)
{

var scale:Number = fl / (fl + ball.zpos);

ball.scaleX = ball.scaleY = scale;
ball.x = ball.xpos*scale;
ball.y = ball.ypos*scale;

}
else
{
ball.visible = false;
}

}
private function sortZ():void
{
balls.sortOn(“zpos”, Array.NUMERIC | Array.DESCENDING );
for(var i:uint = 0; i < numBalls; i++)
{
var ball:BallCS4 = balls[i];
myHolder.addChild(ball as Sprite);
}

}

}
}
BallCS4 (draws your filled circle)

package objects{
import __AS3__.vec.Vector;
import flash.display.GraphicsPathCommand;
import flash.display.Sprite;

public class BallCS4 extends Sprite {

private var commands:Vector.<int> = new Vector.<int>();
private var data:Vector.<Number> = new Vector.<Number>();

public var radius:Number;
private var color:uint;
public var xpos:Number = 0;
public var ypos:Number = 0;
public var zpos:Number = 0;
public var vx:Number = 0;
public var vy:Number = 0;
public var vz:Number = 0;
public var mass:Number = 1;

public function BallCS4(radius:Number=40, color:uint=0xff0000) {
this.radius = radius;
this.color = color;
init();
}
public function init():void {

commands.push(GraphicsPathCommand.MOVE_TO);
data.push(-radius/1.9, radius/1.9);

data.push(0, radius);
data.push(radius/1.9, radius/1.9);

data.push(radius, 0);
data.push(radius/1.9, -radius/1.9);

data.push(0, -radius);
data.push(-radius/1.9, -radius/1.9);

data.push(-radius, 0);
data.push(-radius/1.9, radius/1.9);

commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);
commands.push(GraphicsPathCommand.CURVE_TO);

graphics.lineStyle(0);
graphics.beginFill(0x800000);
graphics.drawPath(commands, data);
}
}
}

MarkCS4 (draws your zero pixel marker)

package objects{
import __AS3__.vec.Vector;
import flash.display.GraphicsPathCommand;
import flash.display.Sprite;

public class MarkCS4 extends Sprite {

private var commands:Vector.<int> = new Vector.<int>();
private var data:Vector.<Number> = new Vector.<Number>();

public var radius:Number;
private var color:uint;
public var xpos:Number = 0;
public var ypos:Number = 0;
public var zpos:Number = 0;
public var vx:Number = 0;
public var vy:Number = 0;
public var vz:Number = 0;
public var mass:Number = 1;

public function MarkCS4(radius:Number=40, color:uint=0xff0000) {
this.radius = radius;
this.color = color;
init();
}
public function init():void {

commands.push(GraphicsPathCommand.MOVE_TO);
data.push(-radius/2, 0);
commands.push(GraphicsPathCommand.LINE_TO);
data.push(radius/2, 0);
graphics.lineStyle(0);
graphics.drawPath(commands, data);
}
}
}


Extra Reading

Excerpt from Senocular’s Blog

Drawing Paths Through Vectors

Another new feature to ActionScript is typed arrays, or vectors. These are not to be confused with the vectors used to describe vector-based (non-bitmap) drawings. Here, vector refers to a kind of an array that can only store one type of value. Those familiar with C++ or Java may recognize the name. To others, it may just sound out of place. Rest assured, it’s an established term.

Vectors are almost exactly like arrays at their core, having pretty much the same API. There are only a few real differences. These include:

* Elements within a vector all share the same type (vectors instances define those types using the syntax Vector.<Type>)
* Vectors have an additional property, fixed, which determines whether or not the length of the vector can change dynamically
* The vector constructor allows for two optional arguments, length and fixed; vector values cannot be defined in the constructor
* There is no equivalent to the bracket ([]) array constructor shorthand for vectors

Additionally, there may be one or two array-specific APIs in ActionScript that do not carry over into vector instances. I think currently, that is limited to sortOn. For all other events and purposes, vectors are just like arrays.

// creating a vector that contains int elements
var primeNumbers:Vector.<int> = new Vector.<int>(4, true);
primeNumbers[0] = 2;
primeNumbers[1] = 3;
primeNumbers[2] = 5;
primeNumbers[3] = 7;

So why use vectors? Given that vectors are dense, typed arrays, they offer a performance advantage over arrays – this especially for primitive values such as Numbers and ints. A few methods of the new drawing API such as drawPath and drawTriangles use vectors for this very reason.

drawPath

The drawPath method is the new drawing API workhorse for defining shapes and other path.

public function drawPath(commands:Vector.<int>, data:Vector.<Number>, winding:String=”evenOdd”);

It consolidates moveTo, lineTo, and curveTo into a single method that can handle each of those operations with a single call. Instead of each of those being called separately, they are abstracted into numeric identifiers. For example, a moveTo operation is signified by a 1, while a lineTo operation is a 2, etc. Those values are then stored in a commands Vector.<int>. Each command corresponds to position values stored into a data Vector.<Number> where two consecutive numbers define a point in the target coordinate space. Note that these are not Point objects; the vector is simply a series of numbers where each group of two numbers represents a point value. When both the command and data vectors are supplied to drawPath, each command is matched up with their respective point values (a collection of 2 or 4 numbers) to generate a path in the Graphics object from which drawPath was called.

The command values used by drawPath are stored in an enumeration class called GraphicsPathCommand. Its values are defined as:

public static const NO_OP:int = 0;
public static const MOVE_TO:int = 1;
public static const LINE_TO:int = 2;
public static const CURVE_TO:int = 3;
public static const WIDE_MOVE_TO = 4;
public static const WIDE_LINE_TO:int = 5;

The WIDE variants of moveTo and lineTo use an extra set of coordinates, relying on 4 number values instead of the standard 2. Because of this, it can be easy to quickly change a moveTo or lineTo command into a curveTo without having to add additional values to the data vector. The extra coordinate used by curveTo would already be there, just ignored by the wide moveTo and wide lineTo operations.

Example:

var commands:Vector.<int> = new Vector.<int>(5, true);
commands[0] = GraphicsPathCommand.MOVE_TO;
commands[1] = GraphicsPathCommand.LINE_TO;
commands[2] = GraphicsPathCommand.LINE_TO;
commands[3] = GraphicsPathCommand.LINE_TO;
commands[4] = GraphicsPathCommand.LINE_TO;

var data:Vector.<Number> = new Vector.<Number>(10, true);
data[0] = 10; // x
data[1] = 10; // y
data[2] = 100;
data[3] = 10;
data[4] = 100;
data[5] = 100;
data[6] = 10;
data[7] = 100;
data[8] = 10;
data[9] = 10;

graphics.beginFill(0x800000);
graphics.drawPath(commands, data);

Notice that there are twice as many data values as there are commands. For non-curveTo and non-wide commands, commands.length = data.length/2. When using curveTo and wide commands, commands.length = data.length/4. Combinations of each could result in different ratios, but commands.length should always be an even value.

I know what you’re thinking, that’s a lot of code just for a square. Keep in mind that once you have those commands and data vectors, they can easily be reused again. You’ll see this even more with the IGraphicsData objects.

That’s not to say that this code cannot be reduced; it can. You can use a couple of shortcuts such as starting with vectors without a fixed length and adding all values with a single push call, or using the a vector conversion function to convert an array to a vector directly within the drawPath call. Here is the same code condensed:

graphics.beginFill(0x800000);
graphics.drawPath(
Vector.<int>([1,2,2,2,2]),
Vector.<Number>([10,10, 100,10, 100,100, 10,100, 10,10]));


About these ads

3 Responses to 3D Lines in CS4 using drawPath

  1. Indyaner says:

    Amazing. Simply Amazing. I’m sitting here stunned and cant wait to see the “sorting”-part you was talking about.

    I like the way you let someone like me look inside your developing and give me a chance to learn from it. I doint need people to code for me, but I need examples to study them and get up with own Ideas based on the technics I’ve read about. Thanks for sharing this.

    Read you at Nabble.

  2. […] And the atom shape and fill in a previous post in this blog on 3D Lines in CS4 using drawPath. […]

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: