ベジェを等分してアニメーション
バウンディングボックスの取得とMatrixの適用とパスの分割に対応させたので、再テストです。
1.ベジェパスを作成
2.境界を調べてMatrixを適用してセンターへ
3.パスを等間隔で分割
4.分割されたパスの境界を調べる
5.パスは(0,0)で始まるようにMatrixを適用
6.シェイプの(0,0)に描画してシェイプを元のパスの場所に移動
7.アニメーション
って事をやってます。
package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.BlurFilter;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
[SWF(backgroundColor=0x000000, width=465, height=465, frameRate=60)]
public class path_test2 extends Sprite
{
public function path_test2()
{
var path:Path = new Path();
//イラレで作ったSVGから抜粋
var data:XML = <data>
<path d="M16.781,41.216L4.71,55.643L0,50.932l7.948-8.831c3.925-4.515,6.868-8.538,8.833-12.07v-7.36L2.943,26.202l-1.765-6.184l15.604-2.942V0h5.298c2.158,0.395,2.355,1.181,0.589,2.355v12.071l4.416,2.649c1.96,0.982,1.667,2.063-0.883,3.238l-3.533,5.005v3.533c8.636-6.869,16.879-10.304,24.729-10.304c5.886,0,10.401,1.57,13.542,4.71c3.728,3.73,5.593,8.539,5.593,14.425c0,6.085-1.668,10.795-5.003,14.131c-4.32,4.32-10.699,8.049-19.137,11.188l-2.945-5.888c7.458-2.748,12.856-5.791,16.193-9.127c2.746-2.748,4.121-6.183,4.121-10.305c0-4.121-1.375-7.554-4.121-10.305c-2.16-2.157-5.105-3.237-8.833-3.237c-7.065,0-15.114,4.123-24.14,12.363v30.913h-5.888V41.216z"/>
</data>;
var x:Number = 0, y:Number = 0;
for each (var p:XML in data.path)
{
var tokens:String = p.@d;
for each (var token:String in tokens.match(/([cChHlLMVv][^a-z]+)|z/ig))
{
var command:String = token.charAt(0);
var args:Array = token.substring(1).match(/[-0-9.][0-9.]*/g);
args.every(function():Boolean
{
args[arguments[1]] = Number(arguments[0]);
return true;
});
switch (command)
{
case "c":
path.curveTo(x + args[0], y + args[1], x + args[2], y + args[3], x + args[4], y + args[5]);
x += args[4];
y += args[5];
break;
case "C":
path.curveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
x = args[4];
y = args[5];
break;
case "l":
path.lineTo(x + args[0], y + args[1]);
x += args[0];
y += args[1];
break;
case "L":
path.lineTo(args[0], args[1]);
x = args[0];
y = args[1];
break;
case "v":
path.lineTo(x, y + args[0]);
y += args[0];
break;
case "V":
path.lineTo(x, args[0]);
y = args[0];
break;
case "h":
path.lineTo(x + args[0], y);
x += args[0];
break;
case "H":
path.lineTo(args[0], y);
x = args[0];
break;
case "M":
path.moveTo(args[0], args[1]);
x = args[0];
y = args[1];
break;
case "z":
path.close();
break;
}
}
}
//境界を取得してセンターへ移動
var rect:Rectangle = new Rectangle();
var matrix:Matrix = new Matrix();
path.getBounds(rect);
matrix.translate(rect.width / -2, rect.height / -2);
matrix.scale(3, 3);
matrix.translate(rect.width / 2, rect.height / 2);
matrix.translate((465 - rect.width) / 2 - rect.x, (465 - rect.height) / 2 - rect.y);
path.applyMatrix(matrix);
//パスを分割してシェイプへ描画
var paths:Array = [];
var shapes:Array = [];
for (var i:int = 1; i < 40; i++)
{
path.split(1 / (40 - (i - 1)), paths);
draw(shapes, paths[0], rect, matrix);
path = paths[1];
}
draw(shapes, path, rect, matrix);
var r:int = 0;
addEventListener(Event.ENTER_FRAME, function():void
{
r += 3;
if (r === 360)
{
r = 0;
}
for (var i:int = 0, len:uint = shapes.length; i < len; i++)
{
var shape:Shape = shapes[i];
shape.rotation = (r / 360) * (r / 360) * 360;
}
});
}
private function draw(shapes:Array, path:Path, rect:Rectangle, matrix:Matrix):void
{
var shape:Shape = new Shape();
shape.filters = [new GlowFilter(0xffffff, 0.5, 16, 16, 1), new BlurFilter()];
addChild(shape);
path.getBounds(rect);
matrix.identity();
matrix.translate(-rect.x, -rect.y);
path.applyMatrix(matrix);
shape.x = rect.x;
shape.y = rect.y;
shape.graphics.lineStyle(1.5, Math.random() * 0x1000000);
path.debug(shape.graphics);
shapes.push(shape);
}
}
}
//以下自作ライブラリから抜粋
//http://sweezy.googlecode.com/svn/trunk/sweezy-anim/src/sweezy/anim/util/Path.as
import flash.display.Graphics;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
class Path
{
private static const QUAD_TO_CUBIC_RATIO:Number = 2 / 3;
private var _interpolate:uint;
private var _lastX:Number;
private var _lastY:Number;
private var _open:Boolean;
private var _pathInfos:Array;
private var _totalLength:Number;
private var _x:Number;
private var _y:Number;
public function Path(interpolate:uint = 0)
{
_x = 0;
_y = 0;
_lastX = 0;
_lastY = 0;
_pathInfos = [];
_open = false;
_interpolate = interpolate;
}
public function add(path:Path):void
{
if (path === null)
{
return;
}
add0(path, _pathInfos.concat(path._pathInfos));
}
public function applyMatrix(matrix:Matrix):void
{
if (matrix === null)
{
return;
}
var a:Number = matrix.a;
var b:Number = matrix.b;
var c:Number = matrix.c;
var d:Number = matrix.d;
var tx:Number = matrix.tx;
var ty:Number = matrix.ty;
var x:Number, y:Number, info:PathInfo;
x = _x;
y = _y;
_x = x * a + y * c + tx;
_y = x * b + y * d + ty;
x = _lastX;
y = _lastY;
_lastX = x * a + y * c + tx;
_lastY = x * b + y * d + ty;
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
info = _pathInfos[i] as PathInfo;
x = info.sx;
y = info.sy;
info.sx = x * a + y * c + tx;
info.sy = x * b + y * d + ty;
x = info.ex;
y = info.ey;
info.ex = x * a + y * c + tx;
info.ey = x * b + y * d + ty;
if (info.type !== 0)
{
x = info.cx1;
y = info.cy1;
info.cx1 = x * a + y * c + tx;
info.cy1 = x * b + y * d + ty;
x = info.cx2;
y = info.cy2;
info.cx2 = x * a + y * c + tx;
info.cy2 = x * b + y * d + ty;
}
info.computeLength();
}
}
public function close():void
{
if (!_open)
{
return;
}
if (_x === _lastX && _y === _lastY)
{
return;
}
lineTo(_lastX, _lastY);
}
public function curveTo(controlX1:Number, controlY1:Number, controlX2:Number, controlY2:Number, anchorX:Number, anchorY:Number):void
{
addPath(2, anchorX, anchorY, controlX1, controlY1, controlX2, controlY2);
}
public function debug(g:Graphics):void
{
var info:PathInfo;
var point:Point = new Point();
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
info = _pathInfos[i] as PathInfo;
g.moveTo(info.sx, info.sy);
if (info.type === 0)
{
g.lineTo(info.ex, info.ey);
}
else
{
for (var j:int = 0; j <= 20; j++)
{
info.pointForT(j / 20, point);
g.lineTo(point.x, point.y);
}
}
}
}
public function getBounds(rect:Rectangle):void
{
var info:PathInfo;
var xBounds:Array = [];
var yBounds:Array = [];
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
info = _pathInfos[i] as PathInfo;
info.getBounds(xBounds, yBounds);
}
trace(xBounds);
trace(yBounds);
rect.left = Math.min.apply(null, xBounds);
rect.right = Math.max.apply(null, xBounds);
rect.top = Math.min.apply(null, yBounds);
rect.bottom = Math.max.apply(null, yBounds);
}
public function getLength():Number
{
if (isNaN(_totalLength))
{
_totalLength = 0;
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
_totalLength += (_pathInfos[i] as PathInfo).len;
}
}
return _totalLength;
}
public function lineTo(x:Number, y:Number):void
{
if (_x === x && _y === y)
{
return;
}
addPath(0, x, y, NaN, NaN, NaN, NaN);
}
public function moveTo(x:Number, y:Number):void
{
if (_x === x && _y === y)
{
return;
}
_x = x;
_y = y;
_lastX = x;
_lastY = y;
}
public function pointForT(t:Number, point:Point):void
{
if (isNaN(t) || t < 0 || t > 1 || point === null)
{
return;
}
var target:Number = getLength() * t;
var start:Number = 0;
var end:Number = 0;
var info:PathInfo;
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
info = _pathInfos[i] as PathInfo;
start = end;
end += info.len;
if (start <= target && target <= end)
{
break;
}
}
info.pointForT(1 - (end - target) / info.len, point);
}
public function quadTo(controlX:Number, controlY:Number, anchorX:Number, anchorY:Number):void
{
addPath(1, anchorX, anchorY, controlX, controlY, NaN, NaN);
}
public function split(t:Number, array:Array):int
{
if (isNaN(t) || t < 0 || t > 1 || array === null)
{
return 0;
}
var target:Number = getLength() * t;
var start:Number = 0;
var end:Number = 0;
var info:PathInfo;
var index:int = 0;
for (var i:int = 0, len:uint = _pathInfos.length; i < len; i++)
{
index = i;
info = _pathInfos[i] as PathInfo;
start = end;
end += info.len;
if (start <= target && target <= end)
{
break;
}
}
t = 1 - (end - target) / info.len;
var path0:Path, path1:Path;
if (t === 0)
{
if (index === 0)
{
path0 = new Path();
path0.add(this);
array[0] = path0;
return 1;
}
else
{
path0 = new Path();
path0.add0(this, _pathInfos.slice(0, index));
array[0] = path0;
path1 = new Path();
path1.add0(this, _pathInfos.slice(index));
array[1] = path1;
return 2;
}
}
else if (t === 1)
{
if (index === _pathInfos.length - 1)
{
path0 = new Path();
path0.add(this);
array[0] = path0;
return 1;
}
else
{
path0 = new Path();
path0.add0(this, _pathInfos.slice(0, index));
array[0] = path0;
path1 = new Path();
path1.add0(this, _pathInfos.slice(index));
array[1] = path1;
return 2;
}
}
info.split(t, array);
var a:Array = _pathInfos.slice(0, index);
a.push(array[0]);
path0 = new Path();
path0.add0(this, a);
a = _pathInfos.slice(index + 1);
a.unshift(array[1]);
path1 = new Path();
path1.add0(this, a);
array[0] = path0;
array[1] = path1;
return 2;
}
private function add0(path:Path, infos:Array):void
{
_pathInfos = infos;
_totalLength = NaN;
_x = path._x;
_y = path._y;
_lastX = path._lastX;
_lastY = path._lastY;
_open = path._open;
}
private function addPath(type:uint, endX:Number, endY:Number, cx1:Number, cy1:Number, cx2:Number, cy2:Number):void
{
var info:PathInfo = new PathInfo();
info.type = type;
info.sx = _x;
info.sy = _y;
info.ex = endX;
info.ey = endY;
info.interpolate = _interpolate === 0 ? 50 : _interpolate;
if (type === 1)
{
info.cx1 = _x + (cx1 - _x) * QUAD_TO_CUBIC_RATIO;
info.cy1 = _y + (cy1 - _y) * QUAD_TO_CUBIC_RATIO;
info.cx2 = endX + (cx1 - endX) * QUAD_TO_CUBIC_RATIO;
info.cy2 = endY + (cy1 - endY) * QUAD_TO_CUBIC_RATIO;
}
else
{
info.cx1 = cx1;
info.cy1 = cy1;
info.cx2 = cx2;
info.cy2 = cy2;
}
info.computeLength();
if (isNaN(info.len))
{
return;
}
_pathInfos[_pathInfos.length] = info;
_x = endX;
_y = endY;
_open = !(_lastX === endX && _lastY === endY);
_totalLength = NaN;
}
}
class PathInfo
{
public var cx1:Number;
public var cx2:Number;
public var cy1:Number;
public var cy2:Number;
public var ex:Number;
public var ey:Number;
public var interpolate:uint;
public var len:Number;
public var px:Number;
public var py:Number;
public var sx:Number;
public var sy:Number;
public var type:uint;
public function computeLength():void
{
if (type === 0)
{
len = Math.sqrt((sx - ex) * (sx - ex) + (sy - ey) * (sy - ey));
}
else
{
len = getBezierLength(0, 1);
}
}
public function getBounds(xBounds:Array, yBounds:Array):void
{
if (type === 0)
{
xBounds[xBounds.length] = sx;
yBounds[yBounds.length] = sy;
xBounds[xBounds.length] = ex;
yBounds[yBounds.length] = ey;
}
else
{
xBounds[xBounds.length] = sx;
yBounds[yBounds.length] = sy;
xBounds[xBounds.length] = ex;
yBounds[yBounds.length] = ey;
var bx:Number = 6 * sx - 12 * cx1 + 6 * cx2;
var by:Number = 6 * sy - 12 * cy1 + 6 * cy2;
var ax:Number = -3 * sx + 9 * cx1 - 9 * cx2 + 3 * ex;
var ay:Number = -3 * sy + 9 * cy1 - 9 * cx2 + 3 * ey;
var cx:Number = 3 * cx1 - 3 * sx;
var cy:Number = 3 * cy1 - 3 * sy;
var t:Number;
if (ax === 0)
{
if (bx !== 0)
{
t = -cx / bx;
if (0 < t && t < 1)
{
bezierPointForT(t);
xBounds[xBounds.length] = px;
}
}
}
else
{
var b2acx:Number = bx * bx - 4 * cx * ax;
if (b2acx >= 0)
{
t = (-bx + Math.sqrt(b2acx)) / (ax * 2);
if (0 < t && t < 1)
{
bezierPointForT(t);
xBounds[xBounds.length] = px;
}
t = (-bx - Math.sqrt(b2acx)) / (ax * 2);
if (0 < t && t < 1)
{
bezierPointForT(t);
xBounds[xBounds.length] = px;
}
}
}
if (ay === 0)
{
if (by !== 0)
{
t = -cy / by;
if (0 < t && t < 1)
{
bezierPointForT(t);
yBounds[yBounds.length] = py;
}
}
}
else
{
var b2acy:Number = by * by - 4 * cy * ay;
if (b2acy >= 0)
{
t = (-by + Math.sqrt(b2acy)) / (ay * 2);
if (0 < t && t < 1)
{
bezierPointForT(t);
yBounds[yBounds.length] = py;
}
t = (-by - Math.sqrt(b2acy)) / (ay * 2);
if (0 < t && t < 1)
{
bezierPointForT(t);
yBounds[yBounds.length] = py;
}
}
}
}
}
public function pointForT(t:Number, point:Point):void
{
if (type === 0)
{
point.x = sx + (ex - sx) * t;
point.y = sy + (ey - sy) * t;
}
else
{
t = getCoordFromLength(len * t);
bezierPointForT(t);
point.x = px;
point.y = py;
}
}
public function split(t:Number, array:Array):void
{
var info0:PathInfo = new PathInfo();
var info1:PathInfo = new PathInfo();
info0.type = type;
info1.type = type;
info0.interpolate = interpolate;
info1.interpolate = interpolate;
info0.sx = sx;
info1.ex = ex;
info0.sy = sy;
info1.ey = ey;
if (type === 0)
{
info0.ex = info1.sx = sx + (ex - sx) * t;
info0.ey = info1.sy = sy + (ey - sy) * t;
}
else
{
t = 1 - getCoordFromLength(len * t);
var x:Number = cx2 + (cx1 - cx2) * t;
var y:Number = cy2 + (cy1 - cy2) * t;
info0.cx1 = cx1 + (sx - cx1) * t;
info0.cy1 = cy1 + (sy - cy1) * t;
info1.cx2 = ex + (cx2 - ex) * t;
info1.cy2 = ey + (cy2 - ey) * t;
info0.cx2 = x + (info0.cx1 - x) * t;
info0.cy2 = y + (info0.cy1 - y) * t;
info1.cx1 = info1.cx2 + (x - info1.cx2) * t;
info1.cy1 = info1.cy2 + (y - info1.cy2) * t;
info0.ex = info1.sx = info1.cx1 + (info0.cx2 - info1.cx1) * t;
info0.ey = info1.sy = info1.cy1 + (info0.cy2 - info1.cy1) * t;
}
info0.computeLength();
info1.computeLength();
array[0] = info0;
array[1] = info1;
}
private function bezierPointForT(t:Number):void
{
var x0:Number = cx1 - sx;
var y0:Number = cy1 - sy;
var x1:Number = cx2 - cx1 - x0;
var y1:Number = cy2 - cy1 - y0;
var x2:Number = ex - cx2 - x1 - cx2 + cx1;
var y2:Number = ey - cy2 - y1 - cy2 + cy1;
px = x2 * t * t * t + 3 * x1 * t * t + 3 * x0 * t + sx;
py = y2 * t * t * t + 3 * y1 * t * t + 3 * y0 * t + sy;
}
private function getBezierLength(ta:Number, tb:Number):Number
{
var t:Number, x:Number, y:Number, ft:Number;
var t2:Number = (tb - ta) / (2 * interpolate);
var x0:Number = cx1 - sx;
var y0:Number = cy1 - sy;
var x1:Number = cx2 - cx1 - x0;
var y1:Number = cy2 - cy1 - y0;
var x2:Number = ex - cx2 - x1 - cx2 + cx1;
var y2:Number = ey - cy2 - y1 - cy2 + cy1;
x = 3 * (x2 * ta * ta + 2 * x1 * ta + x0);
y = 3 * (y2 * ta * ta + 2 * y1 * ta + y0);
ft = Math.sqrt(x * x + y * y);
x = 3 * (x2 * tb * tb + 2 * x1 * tb + x0);
y = 3 * (y2 * tb * tb + 2 * y1 * tb + y0);
ft += Math.sqrt(x * x + y * y);
for (var i:int = 1; i <= interpolate; i++)
{
t = ta + (2 * i - 1) * t2;
x = 3 * (x2 * t * t + 2 * x1 * t + x0);
y = 3 * (y2 * t * t + 2 * y1 * t + y0);
ft += Math.sqrt(x * x + y * y) * 4;
t = ta + 2 * i * t2;
x = 3 * (x2 * t * t + 2 * x1 * t + x0);
y = 3 * (y2 * t * t + 2 * y1 * t + y0);
ft += i < interpolate ? Math.sqrt(x * x + y * y) * 2 : 0;
}
return t2 / 3 * ft;
}
private function getCoordFromLength(len:Number):Number
{
var x:Number, y:Number, v:Number, v2:Number;
var t:Number = 1, d:Number = 0;
var x0:Number = cx1 - sx;
var y0:Number = cy1 - sy;
var x1:Number = cx2 - cx1 - x0;
var y1:Number = cy2 - cy1 - y0;
var x2:Number = ex - cx2 - x1 - cx2 + cx1;
var y2:Number = ey - cy2 - y1 - cy2 + cy1;
if (len === 0)
{
return 0;
}
else if (len >= this.len)
{
return 1;
}
t = len / this.len;
while (true)
{
v2 = getBezierLength(0, t);
v = ((v = v2 - len) < 0 ? -v : v) / len;
if (v <= 0.000001)
{
break;
}
x = 3 * (x2 * t * t + 2 * x1 * t + x0);
y = 3 * (y2 * t * t + 2 * y1 * t + y0);
d += x * x + y * y;
t += (len - v2) / Math.sqrt(d);
}
return t;
}
}