Quadratic Bezier Spline Tracer
Click anywhere to spawn a tracker of variable speed that moves along the predetermined path. Inspired by Ironhidegames Kingdom Rush
/**
* Copyright dimumurray ( http://wonderfl.net/user/dimumurray )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/2Z2r
*/
package {
import flash.text.AntiAliasType;
import flash.text.TextFormat;
import flash.text.TextField;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.display.Sprite;
public class PathTest extends Sprite {
private var tracerQueue:Vector.<PathTracer> = new Vector.<PathTracer>();
private var pathSegments:Vector.<QuadBezierSegment> = new Vector.<QuadBezierSegment>();
private var container:Sprite;
private var textField:TextField;
private var path:Vector.<Number> = new <Number>[
176, 112,
80, 96,
96, 192,
112, 288,
240, 240,
368, 192,
432, 256,
496, 320,
448, 368,
400, 416,
320, 384,
240, 352,
192, 368,
144, 384,
160, 448,
176, 512,
304, 496,
432, 480,
496, 496,
560, 512,
544, 592
];
public function PathTest() {
stage.align = StageAlign.TOP_LEFT;
var format:TextFormat = new TextFormat();
format.size = 24;
format.font = "Trebuchet MS";
textField = new TextField();
textField.autoSize = "left";
textField.antiAliasType = AntiAliasType.ADVANCED;
textField.defaultTextFormat = format;
textField.htmlText = "CLICK ANYWHERE";
container = addChild(new Sprite()) as Sprite;
container.scaleY = container.scaleX = .75;
addChild(textField);
init();
}
private function init():void {
buildPathSegments();
drawTrack();
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener( MouseEvent.CLICK, spawnTracker );
}
private function buildPathSegments():void {
var segment:QuadBezierSegment;
for ( var i:int = 0; i < (path.length - 3); i += 4 ) {
segment = new QuadBezierSegment(path[i], path[i+1], path[i+4], path[i+5], path[i+2], path[i+3]);
pathSegments.push(segment);
}
}
private function drawTrack():void {
container.graphics.moveTo(path[0], path[1]);
var commands:Vector.<int> = new <int>[1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3];
container.graphics.lineStyle(4, 0xF88011);
container.graphics.drawPath(commands, path);
}
private function spawnTracker(e:MouseEvent):void {
var tracker:Tracker = new Tracker();
var pathTracer:PathTracer = new PathTracer(tracker, pathSegments, (0.5 + Math.random() * 5));
container.addChild(tracker);
tracerQueue.push(pathTracer);
}
private function update(e:Event):void {
var tracer:PathTracer;
for each (tracer in tracerQueue) {
tracer.interpolate();
}
}
}
}
import flash.display.Shape;
import flash.display.DisplayObject;
import flash.geom.Point;
class QuadBezierSegment {
static private function getArcLength(_ax:Number, _ay:Number, _bx:Number, _by:Number, _cx:Number, _cy:Number):Number {
var ax:Number = _ax - 2*_cx + _bx;
var ay:Number = _ay - 2*_cy + _by;
var bx:Number = 2*(_cx - _ax);
var by:Number = 2*(_cy - _ay);
var a:Number = 4*(ax*ax + ay*ay);
var b:Number = 4*(ax*bx + ay*by);
var c:Number = bx*bx + by*by;
var abc:Number = 2*Math.sqrt(a+b+c);
var a2:Number = Math.sqrt(a);
var a32:Number = 2*a*a2;
var c2:Number = 2*Math.sqrt(c);
var ba:Number = b/a2;
return (a32*abc + a2*b*(abc-c2) + (4*c*a-b*b)*Math.log((2*a2+ba+abc)/(ba+c2)))/(4*a32);
}
private var a:Point; // start anchor point
private var b:Point; // end anchor point
private var c:Point; // control point
private var length:Number; // arc length of the curve
private var ac:Point; // x and y components of the vector AC
private var cb:Point; // x and y components of the vector CB
private var startAngle:Number;
private var angularDisplacement:Number;
/**
* Quadratic Bezier Segment.
*
* @param ax x-component of the start anchor point.
* @param ay y-component of the start anchor point.
* @param bx x-component of the end anchor point.
* @param by y-component of the end anchor point.
* @param cx x-component of the control point.
* @param cy y-component of the control point.
*/
public function QuadBezierSegment(ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number) {
a = new Point(ax, ay);
b = new Point(bx, by);
c = new Point(cx, cy);
ac = c.subtract(a);
cb = b.subtract(c);
startAngle = Math.atan2(ac.y, ac.x);
angularDisplacement = Math.acos( ( ac.x * cb.x + ac.y * cb.y ) / ( ac.length * cb.length ) );
angularDisplacement *= ((ac.x * cb.y - ac.y * cb.x) < 0) ? -1 : 1;
length = getArcLength(a.x, a.y, b.x, b.y, c.x, c.y);
}
/**
* Interpolate using DeCasteljau's Algorithm
*/
public function interpolateObject(target:DisplayObject, t:Number):void {
var d:Point = Point.interpolate(c, a, t);
var e:Point = Point.interpolate(b, c, t);
var f:Point = Point.interpolate(e, d, t);
var g:Point = e.subtract(d);
var angle:Number = Math.atan2(g.y, g.x) * (180/Math.PI);
target.x = f.x;
target.y = f.y;
target.rotation = angle;
// target.rotation = (startAngle + angularDisplacement * t) * (180/Math.PI);
}
public function getLength():Number {
return length;
}
}
class PathTracer {
private var step:Number;
private var accum:Number = 0;
private var segmentIndex:int = 0;
private var numSegments:int;
private var target:DisplayObject;
private var path:Vector.<QuadBezierSegment>;
private var segment:QuadBezierSegment;
private var arcLength:Number;
private var t:Number;
public function PathTracer(target:DisplayObject, path:Vector.<QuadBezierSegment>, speed:Number) {
step = speed || 0;
this.target = target;
this.path = path;
numSegments = path.length;
segment = path[segmentIndex];
segment.interpolateObject(target, 0);
arcLength = segment.getLength();
}
public function interpolate():void {
accum += step;
t = accum / arcLength;
if ( t > 1 ) {
segmentIndex = (segmentIndex + 1) % numSegments;
segment = path[segmentIndex];
accum -= arcLength;
arcLength = segment.getLength();
t = accum / arcLength;
}
segment.interpolateObject(target, t);
}
}
class Tracker extends Shape {
public function Tracker() {
graphics.beginFill(0x0066CC);
graphics.drawRect(-8, -8, 16, 16);
graphics.endFill();
graphics.lineStyle(2, 0xF88011);
graphics.moveTo(0, -32);
graphics.lineTo(0, -16);
graphics.moveTo(0, 16);
graphics.lineTo(0, 32);
}
}