ベジェと直線のパス上を等速でアニメーション
作ったライブラリのテストついでに。
SVGで作ったパスの上を等速で移動します。
座標を距離から求めてますので等速での移動が出来るようになってます。
数学苦手なんで、各種サイトを参考にしてチューニングしてます。
package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
[SWF(backgroundColor=0xffffff, width=465, height=465, frameRate=60)]
public class path_test extends Sprite
{
public function path_test()
{
var path:Path = new Path();
var s:Shape = new Shape();
addChild(s);
//イラレで作った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"/>
<path d="M102.435,37.209c-4.908,0-9.128,2.945-12.659,8.831c-2.16,3.534-4.32,8.048-6.478,13.543l-6.771-3.532c8.046-12.562,17.664-30.421,28.85-53.582l6.478,3.532c1.373,1.375,1.078,2.16-0.883,2.355c-4.123,6.086-9.03,14.429-14.721,25.024c3.335-1.373,6.28-2.06,8.833-2.06c2.355,0,4.218,0.687,5.593,2.06c1.373,1.375,2.255,4.123,2.65,8.243l1.766,11.188c0.393,2.55,1.962,3.826,4.71,3.826c2.551,0,4.711-0.883,6.478-2.649c3.729-3.729,6.671-11.089,8.831-22.081l6.478,3.241c-2.552,11.186-5.595,18.546-9.127,22.078c-3.926,3.926-8.341,5.889-13.247,5.889c-6.085,0-9.618-3.237-10.598-9.716l-2.063-12.659C106.163,38.386,104.791,37.209,102.435,37.209z"/>
<path d="M188.227,20.019c-5.103,0.982-9.911,1.769-14.426,2.355c-2.945,17.469-6.575,31.306-10.891,41.512l-6.478-2.062c5.298-12.363,9.028-25.316,11.188-38.86c-4.908,0.393-9.128,0.589-12.661,0.589l-1.178-5.89c5.496,0,10.401-0.194,14.721-0.587c0.785-6.083,1.471-11.383,2.06-15.899l5.888,1.179c2.158,0.59,2.355,1.375,0.59,2.355c-0.59,2.355-1.375,6.28-2.355,11.775c4.513-0.588,8.634-1.373,12.364-2.355L188.227,20.019z M214.429,56.819v6.479h-12.953c-7.46,0-14.526-1.768-21.197-5.3l2.65-5.299c5.101,2.747,11.284,4.12,18.547,4.12H214.429z M212.957,32.385c-11.383,0.394-20.314,1.963-26.79,4.711l-1.768-5.889c8.44-3.335,17.566-5.005,27.379-5.005L212.957,32.385z M200.003,5.888c3.532,2.161,7.36,4.713,11.481,7.656l-2.648,4.119c-4.32-2.942-8.148-5.492-11.483-7.652L200.003,5.888z M205.891,0c3.14,1.768,6.965,4.32,11.481,7.655l-2.648,4.12c-4.32-3.14-8.147-5.689-11.482-7.652L205.891,0z"/>
<path d="M248.763,44.258c-7.459,6.086-13.25,11.287-17.371,15.605l-5.002-5.007c5.297-5.102,10.891-10.108,16.777-15.014L248.763,44.258z M258.478,58.39c2.354,0,4.021-0.49,5.004-1.473c1.178-1.175,1.768-3.237,1.768-6.183c0-3.141-0.885-5.593-2.649-7.359c-2.355-2.354-5.005-4.219-7.948-5.594c-4.711-2.159-7.559-3.729-8.538-4.711c-0.982-0.98-1.473-2.354-1.473-4.12c0-1.766,0.882-4.318,2.65-7.655l7.36,2.944c-1.57,2.161-2.355,3.63-2.355,4.416c0,0.589,1.078,1.375,3.238,2.354c6.083,2.751,10.203,5.203,12.363,7.36c3.141,3.141,4.711,7.264,4.711,12.364c0,5.104-1.275,8.933-3.826,11.482c-2.355,2.355-5.791,3.534-10.305,3.534c-3.926,0-8.441-1.278-13.542-3.828l2.06-7.066C251.901,57.212,255.729,58.39,258.478,58.39z M246.408,9.519l3.532-5.889c6.281,3.338,12.854,5.988,19.725,7.951l-3.238,6.476C257.007,14.721,250.333,11.873,246.408,9.519z M289.685,55.444c-5.496-8.438-10.404-14.719-14.721-18.842l5.3-3.824c6.28,5.888,11.283,12.07,15.013,18.546L289.685,55.444z"/>
<path d="M353.046,55.331c2.354-2.355,3.531-5.495,3.531-9.42c0-4.316-1.276-7.751-3.826-10.305c-2.551-2.55-6.574-3.827-12.069-3.827c-6.478,0-11.483,1.277-15.017,3.827c-3.335,2.355-7.652,6.576-12.953,12.659l-5.003-4.416c14.131-14.131,24.827-25.906,32.087-35.328c-6.475,0.786-12.953,1.474-19.429,2.062l-2.06-6.771c9.615,0,18.151-0.49,25.611-1.473l2.65-1.179l3.238,3.24c2.157,1.963,1.668,3.238-1.473,3.826c-5.889,6.676-11.775,13.25-17.664,19.727c4.12-0.98,7.556-1.473,10.303-1.473c7.459,0,12.954,1.767,16.486,5.298c3.533,3.535,5.301,8.048,5.301,13.543c0,5.693-1.57,10.108-4.71,13.249c-4.711,4.71-10.698,7.065-17.96,7.065c-6.869,0-11.873-1.57-15.013-4.71c-2.553-2.551-3.828-5.594-3.828-9.128c0-2.355,0.882-4.415,2.65-6.182c1.96-1.962,4.218-2.944,6.771-2.944c3.533,0,6.479,1.177,8.833,3.532c2.745,2.75,4.61,7.068,5.593,12.954C348.237,58.769,350.886,57.492,353.046,55.331z M335.677,49.737c-1.375-1.372-2.748-2.06-4.123-2.06c-1.57,0-2.748,0.393-3.533,1.178c-0.98,0.982-1.471,2.063-1.471,3.237c0,1.769,0.785,3.436,2.355,5.006c1.961,1.963,4.514,2.943,7.654,2.943c0.784,0,1.667,0,2.649,0C338.619,54.94,337.442,51.506,335.677,49.737z"/>
</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;
}
}
}
path.debug(s.graphics, 150);
var s2:Shape = new Shape();
s2.graphics.beginFill(0x0000ff);
s2.graphics.drawCircle(0, 0, 3);
addChild(s2);
var t:int = 0;
var point:Point = new Point();
addEventListener(Event.ENTER_FRAME, function():void
{
path.pointForT(t / 500, point);
s2.x = point.x;
s2.y = point.y;
if (++t > 500)
{
t = 0;
}
});
}
}
}
//以下自作ライブラリから抜粋
//http://sweezy.googlecode.com/svn/trunk/sweezy-anim/src/sweezy/anim/util/Path.as
import flash.display.Graphics;
import flash.geom.Point;
class Path
{
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()
{
_x = 0;
_y = 0;
_lastX = 0;
_lastY = 0;
_pathInfos = [];
_open = false;
}
public function add(path:Path):void
{
if (path === null)
{
return;
}
_pathInfos = _pathInfos.concat(path._pathInfos);
_totalLength = NaN;
_x = path._x;
_y = path._y;
_lastX = path._lastX;
_lastY = path._lastY;
_open = path._open;
}
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, div:int):void
{
g.clear();
g.lineStyle(1);
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);
}
}
}
g.lineStyle();
for (var k:int = 0; k <= div; k++)
{
pointForT(k / div, point);
g.beginFill(0xff0000, 0.5);
g.drawCircle(point.x, point.y, 2);
g.endFill();
}
}
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);
}
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;
if (type === 1)
{
var d:Number = 2 / 3;
info.cx1 = _x + (cx1 - _x) * d;
info.cy1 = _y + (cy1 - _y) * d;
info.cx2 = endX + (cx1 - endX) * d;
info.cy2 = endY + (cy1 - endY) * d;
}
else
{
info.cx1 = cx1;
info.cy1 = cy1;
info.cx2 = cx2;
info.cy2 = cy2;
}
info.computeLength();
_pathInfos[_pathInfos.length] = info;
_x = endX;
_y = endY;
_open = !(_lastX === endX && _lastY === endY);
_totalLength = NaN;
}
}
class PathInfo
{
private static const N:int = 50;
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 len: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 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);
}
}
private function bezierPointForT(t:Number, point:Point):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;
point.x = x2 * t * t * t + 3 * x1 * t * t + 3 * x0 * t + sx;
point.y = 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 * N);
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 <= N; 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 < N ? 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;
t = len / getBezierLength(0, t);
if (t === 0 || t === 1)
{
return t;
}
while (true)
{
v2 = getBezierLength(0, t);
v = ((v = v2 - len) < 0 ? -v : v) / len;
if (v <= 0.0001)
{
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;
}
}