APE: Creating an Elliptical Scaffold
I’ve been interested in creating a rolling shape type of sprite with ActionScript Physics Engine for use in a game. —A shape that could be moved around a landscape by rolling a weighted wheel particle around inside it. While building this, I discovered that the spring constraints in APE are really quite soft; requiring the diagonal constraints that support the structure to be doubled up. The result is a fairly stable structure that has quite a few more points than I’d like it to have.
As with most APE models, the less stable the model is, the more fun you can have with it. With this, it’s possible to run the elliptical scaffold over the obstacle, and get it airborne. As soon as the wheel particle looses contact with the scaffold, the model begins to oscillate.
To use: Click on the swf, and use the left/right arrows on your keyboard to control the wheel particle inside the scaffold. Source below or download source files.
main.as:
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.ui.Keyboard;
import flash.events.KeyboardEvent;
import org.cove.ape.*;
public class Main extends Sprite
{
private var defaultGroup:Group;
private var eScaffold:EllipticalScaffold;
private var outerPoints:Array; // points that define the outer ellipse
private var innerPoints:Array; // points that define the inner ellipse
private var wheel1:WheelParticle;
public function Main()
{
stage.frameRate = 30;
APEngine.init(1/4);
// set the default diplay container
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,1));
defaultGroup = new Group();
defaultGroup.collideInternal = true;
APEngine.addGroup(defaultGroup);
init();
}
public function init():void
{
drawFloor();
drawWheel();
drawObstacles();
eScaffold = new EllipticalScaffold(defaultGroup);
eScaffold.drawOuterEllipse(200, 50, 50, 40, -20, 20);
eScaffold.drawInnerEllipse(195, 80, 40, 30, -20, 20);
eScaffold.createScaffold();
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_pressed);
stage.addEventListener(KeyboardEvent.KEY_UP, key_released);
stage.addEventListener(Event.ENTER_FRAME, run);
}
private function drawObstacles():void
{
var bump4:CircleParticle = new CircleParticle(480, 420, 100, true, 1, 0, 1);
defaultGroup.addParticle(bump4);
}
private function drawFloor():void
{
var floor:RectangleParticle = new RectangleParticle(260, 370, 600, 50, 0.0, true, 10, 0.4, 0.55);
floor.setStyle(0, 0, 0, Math.random() * 0x111111);
defaultGroup.addParticle(floor);
}
public function drawWheel():void
{
// WheelParticle(x, y, radius, fixed, mass, elasticity, friction, traction)
wheel1 = new WheelParticle(160, 210, 70, false, 10, 0.1, 1, 1);
defaultGroup.addParticle(wheel1);
}
// Events //////////////////////////////////
function key_pressed(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.RIGHT)
{
wheel1.angularVelocity = 1.2;
}
if (event.keyCode == Keyboard.LEFT)
{
wheel1.angularVelocity = -1.2;
}
}
private function key_released(event:KeyboardEvent):void
{
wheel1.angularVelocity = 0;
}
private function run(e:Event):void
{
APEngine.step();
APEngine.paint();
}
}
}
EllipticalScaffold.as:
package
{
import org.cove.ape.*;
/*
* EllipticalScaffold
*
* To create an EllipticalScaffold in ActionScript Physics Engine:
* 1. Draw the outer an inner ellipses by calling drawOuterEllipse() & drawInnerEllipse()
* 2. Join the 2 ellipses by calling createScaffold()
*/
public class EllipticalScaffold
{
private var outerPoints:Array; // point coordinates that define the outer ellipse
private var innerPoints:Array; // point coordinates that define the inner ellipse
private var outerCircles:Array; // Array of circle particles drawn on the stage that define the outer ellipse
private var innerCircles:Array; // Array of circle particles drawn on the stage that define the inner ellipse
private var defaultGroup:Group;
public function EllipticalScaffold(defaultGrp:Group)
{
defaultGroup = defaultGrp;
}
private function init():void
{
outerPoints = new Array();
innerPoints = new Array();
outerCircles = new Array();
innerCircles = new Array();
}
/*
* drawOuterEllipse.
*
* @param x {number} X coordinate
* @param y {number} Y coordinate
* @param a {number} Semimajor axis
* @param b {number} Semiminor axis
* @param steps {number} Number of points in the ellipse
*/
public function drawOuterEllipse(x:Number, y:Number, a:Number, b:Number, angle:Number, steps:Number):void
{
outerPoints = calculateEllipse(x, y, a, b, angle, steps);
outerCircles = drawEllipse(outerPoints);
}
public function drawInnerEllipse(x:Number, y:Number, a:Number, b:Number, angle:Number, steps:Number):void
{
innerPoints = calculateEllipse(x, y, a, b, angle, steps);
innerCircles = drawEllipse(innerPoints);
}
public function createScaffold():void
{
joinEllipses(outerCircles, innerCircles);
}
/*
* This functions returns an array containing the points to draw an
* ellipse.
*
* @param x {number} X coordinate
* @param y {number} Y coordinate
* @param a {number} Semimajor axis
* @param b {number} Semiminor axis
* @param angle {number} Angle of the ellipse
*/
private function calculateEllipse(x:Number, y:Number, a:Number, b:Number, angle:Number, steps:Number)
{
var points:Array = new Array();
// Angle is given by Degree Value
var beta = -angle * (Math.PI / 180); //(Math.PI/180) converts Degree Value into Radians
var sinbeta = Math.sin(beta);
var cosbeta = Math.cos(beta);
for (var i = 0; i < 360; i += 360 / steps)
{
var alpha = i * (Math.PI / 180) ;
var sinalpha = Math.sin(alpha);
var cosalpha = Math.cos(alpha);
var x = x + (a * cosalpha * cosbeta - b * sinalpha * sinbeta);
var y = y + (a * cosalpha * sinbeta + b * sinalpha * cosbeta);
var o:Object = new Object();
o.x = x;
o.y = y;
points.push(o);
}
return points;
}
/*
* drawEllipse
* Draw the points of the ellipse as circle particles.
* Return an array of those circle particles.
*/
private function drawEllipse(points:Array):Array
{
var circles:Array = new Array();
var i:Number;
for( i= 0; i < points.length; i++)
{
// CircleParticle(x, y, radius, fixed, mass, elasticity, friction)
var c:CircleParticle = new CircleParticle(points[i].x, points[i].y, 0, false, 0.4, 0, 1);
defaultGroup.addParticle(c);
circles.push(c);
if(i > 0)
{
var s:SpringConstraint = new SpringConstraint(circles[i-1], circles[i], 1, true, 1, 1, true);
defaultGroup.addConstraint(s);
}
}
// create the final constraint to close the ellipse:
var t:SpringConstraint = new SpringConstraint(circles[i-1], circles[0], 1, true, 1, 1, true);
defaultGroup.addConstraint(t);
return circles;
}
// join the ellipses with spring constraints:
private function joinEllipses(outerPoints:Array, innerPoints:Array):void
{
var i:Number;
// join each inner and outer ellipse point:
for(i = 0; i < outerPoints.length; i++)
{
var j:SpringConstraint = new SpringConstraint(outerPoints[i], innerPoints[i], 1, true, 3);
defaultGroup.addConstraint(j); //
}
// create diagonals to the right:
for(i = 0; i < outerPoints.length -1; i++)
{
// join each inner point to the next outer point:
var d1:SpringConstraint = new SpringConstraint(innerPoints[i], outerPoints[i +1], 1, true, 3);
defaultGroup.addConstraint(d1); //
}
var d1close:SpringConstraint = new SpringConstraint(innerPoints[innerPoints.length-1], outerPoints[0], 1, true, 3);
defaultGroup.addConstraint(d1close);
// create diagonals to the left:
for(i = outerPoints.length -1; i > 0; i--)
{
// join each innner point to the next outer point:
var d2:SpringConstraint = new SpringConstraint(innerPoints[i], outerPoints[i -1], 1, true, 3);
defaultGroup.addConstraint(d2);
}
var d2close:SpringConstraint = new SpringConstraint(innerPoints[0], outerPoints[outerPoints.length -1], 1, true, 3);
defaultGroup.addConstraint(d2close);
}
}
}
APE: Collision Within an Elliptical Spring Constraint
Recently I was using ActionScript Physics Engine to create a simple 2-D arcade-style game. I looked into building an elliptical spring constraint in APE to serve as the boundaries of the game. In the end I decided not to use the elliptical boundaries, but they are easy enough to code up. Here’s a quick demo, and the ActionScript source code:
package
{
import flash.display.Sprite;
import flash.events.Event;
import org.cove.ape.*;
public class Main extends Sprite
{
public var defaultGroup:Group;
var car:SpringConstraint;
var ellipsePoints:Array; // points that define the outer ellipse
var wheel1:WheelParticle;
public function Main()
{
stage.frameRate = 30;
APEngine.init(1/4);
// set the default diplay container
APEngine.container = this;
defaultGroup = new Group();
defaultGroup.collideInternal = true;
init();
APEngine.addGroup(defaultGroup);
stage.addEventListener(Event.ENTER_FRAME, run);
}
public function init():void
{
ellipsePoints = new Array();
ellipsePoints = drawEllipse(calculateEllipse(380, 80, 60, 36, -24, 32));
addBall();
}
public function addBall():void
{
var circle = new CircleParticle(
160, // x
210, // y
40, // radius
false, // fixed
10, // mass
1.0, // elasticty
0 // friction
);
circle.setStyle(0, 0, 0, Math.random() * 0xffffff);
defaultGroup.addParticle(circle);
circle.velocity = (new Vector(20, 1));
}
/*
* This functions returns an array containing the points to draw an
* ellipse.
*
* @param x {double} X coordinate
* @param y {double} Y coordinate
* @param a {double} Semimajor axis
* @param b {double} Semiminor axis
* @param angle {double} Angle of the ellipse
*/
private function calculateEllipse(x, y, a, b, angle, steps)
{
if (steps == null)
steps = 36;
var points:Array = new Array();
// Angle is given by Degree Value
var beta = -angle * (Math.PI / 180); //(Math.PI/180) converts Degree Value into Radians
var sinbeta = Math.sin(beta);
var cosbeta = Math.cos(beta);
for (var i = 0; i < 360; i += 360 / steps)
{
var alpha = i * (Math.PI / 180) ;
var sinalpha = Math.sin(alpha);
var cosalpha = Math.cos(alpha);
var x = x + (a * cosalpha * cosbeta - b * sinalpha * sinbeta);
var y = y + (a * cosalpha * sinbeta + b * sinalpha * cosbeta);
var o:Object = new Object();
o.x = x;
o.y = y;
points.push(o);
}
return points;
}
private function drawEllipse(points:Array):Array
{
var circles:Array = new Array();
var i:Number;
for( i= 0; i < points.length; i++)
{
// CircleParticle(x, y, radius, fixed, mass, elasticity, friction)
var c:CircleParticle = new CircleParticle(points[i].x, points[i].y, 0, true, 0.4, 0, 0);
defaultGroup.addParticle(c);
circles.push(c);
if(i > 0)
{
var s:SpringConstraint = new SpringConstraint(circles[i-1], circles[i], 1, true, 1, 1, true);
defaultGroup.addConstraint(s);
}
}
// create the final constraint to close the ellipse:
var t:SpringConstraint = new SpringConstraint(circles[i-1], circles[0], 1, true, 1, 1, true);
defaultGroup.addConstraint(t);
return circles;
}
private function run(e:Event):void
{
APEngine.step();
APEngine.paint();
}
}
}
Anatomy of an Ellipse:

Drawing with ActionScript Physics Engine
I’ve created a quick demo with APE that allows the user to draw rectangle particles with the mouse. New wheel particles are created every 2 seconds. Particles are removed if they fall outside the stage area so that the demo will not leak memory.
Main.as:
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
import org.cove.ape.*;
// Draw APE Rectangle Particles
// built with Actionscript Physics Engine v0.45
// http://www.cove.org/ape/
public class Main extends Sprite
{
private var myRectangle:RectangleParticle
private var wheel:WheelParticle;
private var defaultGroup:Group;
private var beginX:Number; // mouse drag top left x position
private var beginY:Number; // mouse drag top left y position
private var endX:Number; // mouse drag bottom right x position
private var endY:Number; // mouse drag bottom right y position
private const bound:Number = 600;
public function Main()
{
init();
}
private function init():void
{
stage.frameRate = 65;
// Initialize the engine. The argument here is the time step value.
// Higher values scale the forces in the sim, making it appear to run
// faster or slower. Lower values result in more accurate simulations.
APEngine.init(1/4);
APEngine.damping = 0.98;
// set the default diplay container
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,8));
defaultGroup = new Group();
defaultGroup.collideInternal = true;
//RectangleParticle(x, y, width, height, rotation, fixed, mass, elasticity, friction)
var floor:RectangleParticle = new RectangleParticle(300,490, 600, 20, 0.0, true, 10, 0.4, 0.55);
floor.setStyle(0, 0, 0, Math.random() * 0x111111);
defaultGroup.addParticle(floor);
APEngine.addGroup(defaultGroup);
addEventListener(Event.ENTER_FRAME, run);
stage.addEventListener(MouseEvent.MOUSE_DOWN, beginDrawRect);
stage.addEventListener(MouseEvent.MOUSE_UP, endDrawRect);
setTimer(); // start creating wheel particles
}
private function beginDrawRect(m:MouseEvent):void
{
beginX = mouseX;
beginY = mouseY;
}
private function endDrawRect(m:MouseEvent):void
{
endX = mouseX;
endY = mouseY;
// if we have 2 positive numbers, draw:
if (endX - beginX > 1 && endY - beginY > 1)
{
drawRectangle();
}
}
private function drawRectangle():void
{
//RectangleParticle(x, y, width, height, rotation, fixed, mass, elasticity, friction)
// the more area a rectangle has the more mass we give it:
myRectangle = new RectangleParticle(beginX + ( (endX - beginX)/2 ), beginY + ((endY - beginY)/2), endX - beginX, endY - beginY, 0 , false, (endX - beginX) * (endY - beginY) , 0.4, 0.1);
myRectangle.setStyle(0, 0, 0, Math.random() * 0xffffff);
defaultGroup.addParticle(myRectangle);
}
private function createParticle(e:TimerEvent):void
{
//WheelParticle(x, y, radius, fixed, mass, elasticity, friction, traction)
var rand:Number = Math.random();
// the more area a wheel has the more mass we give it:
wheel = new WheelParticle(rand * stage.stageWidth, -20, rand * 55, false, rand * 55, 0.4, 0.1);
wheel.setStyle(0, 0, 0, Math.round(Math.random() * 0xffffff));
defaultGroup.addParticle(wheel);
checkForBounds();
}
// Check location of particles & remove if out of bounds so that we don't leak memory:
function checkForBounds()
{
/* particles:Array [read-only] The Array of all AbstractParticle
instances added to the AbstractCollection */
for (var i:Number = defaultGroup.particles.length -1; i > 0 ; --i){
/* Check to see if particles is past the 'bound' number, if is then remove it from system
py & px give the X & Y location of the partical, view AbstractParticle in APE api */
if ( defaultGroup.particles[i].py > bound )
{
defaultGroup.removeParticle( defaultGroup.particles[i] ); // Removes an AbstractParticle from the AbstractCollection.
}
}
}
private function setTimer():void
{
var myTimer:Timer= new Timer(2000, 0);
myTimer.addEventListener("timer", createParticle);
myTimer.start();
}
private function run(e:Event):void
{
APEngine.step();
APEngine.paint();
}
}
}
APE: ActionScript Physics Engine
Here’s a game built with the AS3 ActionScript Physics Engine. It’s easy to create a simple a car physics game with APE. Here’s my go at it: Kinetic Car Park. Make the car fly through the air and land in the parking space. Click the game to give it focus, then use the left/right arrow keys on your keyboard to control the game. It’s possible to put the car on every ramp in the game. Not too hard really.
This is a very rough go at a game so no scoring etc. at this point. You can download the source AS3 & Fla files here. You’ll also need the ActionScript Physics Engine. This little demo was created with APE v0.45.
Here’s the document class:
package
{
import org.cove.ape.*;
import flash.display.Sprite;
import flash.events.Event;
import flash.ui.Keyboard;
import flash.events.KeyboardEvent;
public class APEcar extends Sprite
{
private var wheel1:WheelParticle = new WheelParticle(460,20,10,false,100,0.05);
private var wheel2:WheelParticle = new WheelParticle(500,20,10,false,100,0.05);
public function APEcar()
{
addEventListener(Event.ENTER_FRAME, run);
APEngine.init(1/4);
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,2));
var defaultGroup:Group = new Group();
defaultGroup.collideInternal = true;
defaultGroup.addParticle(wheel1);
defaultGroup.addParticle(wheel2);
var leftWall:RectangleParticle = new RectangleParticle(5,0,990,10,4.712389,true);
defaultGroup.addParticle(leftWall);
var rightWall:RectangleParticle = new RectangleParticle(695,0,990,10,4.712389,true);
defaultGroup.addParticle(rightWall);
// Left //
var planeLeft1:RectangleParticle = new RectangleParticle (10, 140, 200, 10, -0.2, true);
defaultGroup.addParticle(planeLeft1);
var planeLeft2:RectangleParticle = new RectangleParticle(10,200,200,10,-0.1,true);
defaultGroup.addParticle(planeLeft2);
var planeLeft3:RectangleParticle = new RectangleParticle(10,300,300,10,0.2,true);
defaultGroup.addParticle(planeLeft3);
// Center //
var planeCenter1:RectangleParticle = new RectangleParticle(420,245,200,10,0.1,true);
defaultGroup.addParticle(planeCenter1);
var planeCenter2:RectangleParticle = new RectangleParticle(503,240,50,10,-0.6,true);
defaultGroup.addParticle(planeCenter2);
var planeCenter4:RectangleParticle = new RectangleParticle(440,350,180,10,0.1,true);
defaultGroup.addParticle(planeCenter4);
var planeCenter5:RectangleParticle = new RectangleParticle(240,490,200,10,-0.4,true);
defaultGroup.addParticle(planeCenter5);
var planeCenter6:RectangleParticle = new RectangleParticle(380,483,80,10,0.6,true);
defaultGroup.addParticle(planeCenter6);
var planeCenter3:RectangleParticle = new RectangleParticle(340,330,50,10,0.7,true);
defaultGroup.addParticle(planeCenter3);
// Right //
var planeRight1:RectangleParticle = new RectangleParticle(670,160,70,10,0,true);
defaultGroup.addParticle(planeRight1);
var planeRight2:RectangleParticle = new RectangleParticle(620,360,70,10, 0.1,true);
defaultGroup.addParticle(planeRight2);
var planeRight4:RectangleParticle = new RectangleParticle(570,380,40,10, -0.8,true);
defaultGroup.addParticle(planeRight4);
var planeRight3:RectangleParticle = new RectangleParticle(560,390,260,10, -0.3,true);
defaultGroup.addParticle(planeRight3);
var speedBump:RectangleParticle = new RectangleParticle(670,156,4,10,0,true);
defaultGroup.addParticle(speedBump);
var floor:RectangleParticle = new RectangleParticle(300,500,1000,10,0,true);
defaultGroup.addParticle(floor);
// put the car together: 2 wheel particles joined by a spring constraint: //
var car:SpringConstraint = new SpringConstraint(wheel1, wheel2, 0.3, true, 3);
defaultGroup.addConstraint(car);
APEngine.addGroup(defaultGroup);
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_pressed);
stage.addEventListener(KeyboardEvent.KEY_UP, key_released);
}
function key_pressed(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.RIGHT)
{
wheel1.angularVelocity = 0.3;
wheel2.angularVelocity = 0.3;
}
if (event.keyCode == Keyboard.LEFT)
{
wheel1.angularVelocity = -0.3;
wheel2.angularVelocity = -0.3;
}
}
function key_released(event:KeyboardEvent):void
{
wheel1.angularVelocity = 0;
wheel2.angularVelocity = 0;
}
private function run(evt:Event):void
{
APEngine.step();
APEngine.paint();
}
}
}