Skip to content

Instantly share code, notes, and snippets.

@roipeker
Created April 26, 2018 20:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roipeker/ca1b11a7af680247e1945b2860f83304 to your computer and use it in GitHub Desktop.
Save roipeker/ca1b11a7af680247e1945b2860f83304 to your computer and use it in GitHub Desktop.
Cubic BezierPath for Starling compatible with Greensock and Starling's Tween engines.
// =================================================================================================
//
// Created by Rodrigo Lopez [roipeker™] on 26/04/2018.
//
// =================================================================================================
/**
*
* Cubic bezier path compatible with Tween engines (x/y/rotation), specially designed to use in Starling Tween
* with display Objects.
*
* Has an easy usage, supports pooling, auto rotation and the curve supports live modification of all points
* based on a percentage.
*
*
* Sample code:
// use a path directly.
Starling.juggler.tween(
BezierPath.get( obj, 400, 400, 200, 200, true ),
2, { percent: 1 });
// put back in pool....
var path:BezierPath = BezierPath.get( obj, 400, 400, 200, 200 ) ;
Starling.juggler.tween( path, 2, { percent: 1, onComplete: path.backToPool });
// *** Complex demo:
var quad:Quad = new Quad(32,32, 0x333333);
addChild(quad);
quad.alignPivot();
// create 3 control points
var p1:Canvas = createDot(0x0); // start position dot
var p2:Canvas = createDot(0xff0000);// control point dot
var p3:Canvas = createDot(0x0); // end position dot
function createDot(color:uint):Canvas {
var c:Canvas = new Canvas();
c.beginFill(color);
c.drawCircle(0,0,5);
c.endFill();
addChild(c);
return c ;
}
// starting curve position taken from the displayObject.
quad.x = 100 ;
quad.y = 100 ;
p1.x = quad.x;
p1.y = quad.y;
// create the Bezier path...
var path:BezierPath = BezierPath.get(quad, 300,300,0,0);
path.autoRotate = true ;
// when finished... always put back in pool, or change targets.
// , onComplete: path.backPool }) ; .... or BezierPath.put(path)
Starling.juggler.tween( path, 1, { percent: 1, repeatCount:0, reverse:true, repeatDelay: .1, transitionFunc: MaterialEase.easeInOut });
// move the start point if you feel adventurous.
Starling.juggler.tween( path, 20, { sx: 400, repeatCount:0, reverse:true, repeatDelay: 1.5, onUpdate:function(){
p1.x = path.sx;
}});
randomize();
// randomize target position and control point.
function randomize(){
p3.x = path.tx = Math.random() * stage.stageWidth;
p3.y = path.ty = Math.random() * stage.stageHeight;
// Simpe utility to autocalculate control points to form an arc ( 0 = auto calculate direction)
path.arc( 0 );
p2.x = path.cx;
p2.y = path.cy;
Starling.juggler.delayCall( randomize, 4 );
}
*/
package roipeker.easing {
import flash.geom.Point;
import starling.display.DisplayObject;
public class BezierPath {
private static const point:Point = new Point();
private var _target:DisplayObject;
public var sx:Number;
public var sy:Number;
public var tx:Number;
public var ty:Number;
public var cx:Number;
public var cy:Number;
private var _percent:Number = 0;
public var autoRotate:Boolean = false;
private static var _pool:Vector.<BezierPath> = new <BezierPath>[];
private var _inPool:Boolean = false;
// when percent=1, the instance goes to the pool automatically.
// WATCH OUT to not use repeat/yoyo, etc.
private var autoPool:Boolean = false;
/**
* constructor (not required)
*/
public function BezierPath() {
}
/**
* Factory method to return a instance from Pool (or new one).
*
* @param target
* @param targetX
* @param targetY
* @param controlX
* @param controlY
* @param autoRelease Warning, NOT KEEEP ANY REFERENCE, or yoyo (repeatCount=0) in tween.... use once and goes back to pool.
*
* @return
*/
public static function get(target:DisplayObject, targetX:Number, targetY:Number, controlX:Number = Number.NaN, controlY:Number = Number.NaN, autoRelease:Boolean = false):BezierPath {
var bezierPath:BezierPath;
if (_pool.length == 0) {
bezierPath = new BezierPath();
} else {
bezierPath = _pool.pop();
}
bezierPath._inPool = false;
bezierPath.autoPool = autoRelease;
bezierPath.setup(target, targetX, targetY, controlX, controlY);
if (isNaN(controlX) && isNaN(controlY)) {
bezierPath.arc(0);
}
return bezierPath;
}
/**
* Factory method to put back a BezierPath instance back in the pool
* @param bezierPath
*/
public static function put(bezierPath:BezierPath):void {
if (bezierPath) {
bezierPath.reset();
if (bezierPath._inPool) {
trace("BezierPath already in pool.");
return;
}
bezierPath._inPool = true;
_pool[_pool.length] = bezierPath;
}
}
/**
* Helper function to calculate a cubic bezier curve based on percent (a)
* @param a percent
* @param x1 start x
* @param y1 start y
* @param x2 control point x
* @param y2 control point y
* @param x3 end x
* @param y3 end y
* @param out (optional) reuse Point
* @return Point coordinate with current position in curve.
*/
public static function cubicBezier(a:Number, x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number, out:Point = null):Point {
if (!out) out = new Point();
var b:Number = 1 - a;
var pre1:Number = a * a;
var pre2:Number = 2 * a * b;
var pre3:Number = b * b;
out.x = pre1 * x1 + pre2 * x2 + pre3 * x3;
out.y = pre1 * y1 + pre2 * y2 + pre3 * y3;
return out;
}
/**
* Shortcut to call from Tween::onComplete: callback
*/
public function backToPool():void {
BezierPath.put(this);
}
/**
* Reset the bezier path instance (usually called from the Factory method)
*/
public function reset():void {
autoRotate = false;
autoPool = false;
_target = null;
sx = sy = tx = ty = cx = cy = 0;
}
/**
* Setup a new bezier path to follow.
* @param target DisplayObject that will be updated.
* @param targetX target position x.
* @param targetY target position y.
* @param controlX control point x (cubic bezier)
* @param controlY control point y (cubic bezier)
* @return
*/
public function setup(target:DisplayObject, targetX:Number, targetY:Number, controlX:Number, controlY:Number):BezierPath {
this.target = target;
_percent = 0;
// initialize position.
this.tx = targetX;
this.ty = targetY;
this.cx = controlX;
this.cy = controlY;
return this;
}
/**
* Simple method to adjust the control point to emulate an arc (1/4 circle).
* (doesnt use kappa approach), but works for Material Motion.
* @param dir 0 chooses the "natural" arc.
* -1 ( < 0 ) bottom arc (down)
* +1 ( > 0 ) top arc (up)
*/
public function arc(dir:int = 0):void {
if (dir == 0) {
dir = ty > sy ? -1 : 1;
}
if (dir < 0) {
cx = sx;
cy = ty;
} else {
cx = tx;
cy = sy;
}
}
//============================
// ACCESSORS --
//============================
public function get percent():Number {
return _percent;
}
public function set percent(value:Number):void {
// no need to calculate percent if its not appliable.
if (_inPool) {
trace("BezierPath instance in the pool. Can't be used.");
return;
}
if (!_target) {
trace("BezierPath requires a target DisplayObject.");
return;
}
if (_percent == value) return;
_percent = value;
cubicBezier(_percent, sx, sy, cx, cy, tx, ty, point);
if (autoRotate) {
_target.rotation = Math.atan2(point.y - _target.y, point.x - _target.x);
}
_target.x = point.x;
_target.y = point.y;
if (_percent == 1) {
if (autoPool) {
put(this);
}
}
}
public function get target():DisplayObject {
return _target;
}
public function set target(value:DisplayObject):void {
if (_inPool) {
// throw error.
trace("BezierPath instance in the pool. Can't be used.");
return;
}
if (_target == value) return;
_target = value;
if (_target) {
sx = _target.x;
sy = _target.y;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment