Created
April 26, 2018 20:57
-
-
Save roipeker/ca1b11a7af680247e1945b2860f83304 to your computer and use it in GitHub Desktop.
Cubic BezierPath for Starling compatible with Greensock and Starling's Tween engines.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ================================================================================================= | |
// | |
// 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