APE: Creating an Elliptical Scaffold

06.08.2009

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