線描速度の推定
/**
* Copyright knd ( http://wonderfl.net/user/knd )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/8vrs
*/
// forked from knd's 曲線による補間
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.getTimer;
/**
* 補間して曲線を引きます
* 任意の時点の速度が推定できるので、筆圧のような効果を出してます。
* @author @kndys
*/
[SWF(frameRate="12")]
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
var curve:Curve = new Curve();
var lastX:Number = mouseX, lastY:Number = mouseY;
var lastT:int;
var currX:Number = lastX, currY:Number = lastY;
var currT:int;
var press:Boolean = false;
var lineNum:uint = 10;
var position:Point = new Point();
var velocity:Point = new Point();
var draw:Function = function():void
{
graphics.lineStyle(0, 0x110022);
if (curve.getPoint(position, 0.0))
{
graphics.moveTo(position.x, position.y);
for (var u:uint = 1; u <= lineNum; u++)
{
var ratio:Number = Number(u) / lineNum;
curve.getPoint(position, ratio);
curve.getVelocity(velocity, ratio);
graphics.lineStyle(velocity.length * 5.0);
graphics.lineTo(position.x, position.y);
}
}
};
// MOUSE_MOVE ではなく ENTER_FRAME を使う。
stage.addEventListener(Event.ENTER_FRAME,
function(e:Event):void
{
currX = mouseX, currY = mouseY, currT = getTimer();;
if (press)
{
graphics.lineStyle(0, 0x4488ff, 0.5);
graphics.moveTo(lastX, lastY);
graphics.lineTo(currX, currY);
graphics.drawCircle(currX, currY, 5);
curve.push(currX, currY, currT); // 点(x,y,t)を追加
draw();
}
lastX = currX, lastY = currY, lastT = currT;
} );
stage.addEventListener(MouseEvent.MOUSE_DOWN,
function (e:Event):void
{
if (!press)
{
currX = mouseX, currY = mouseY, currT = getTimer();;
graphics.lineStyle(0, 0x4488ff, 0.5);
graphics.drawCircle(currX, currY, 5);
curve.push(lastX, lastY, lastT); // 開始点の速度を推測するためのダミー点
curve.push(currX, currY, currT); // 開始点
lastX = currX, lastY = currY, lastT = currT;
press = true;
}
} );
stage.addEventListener(MouseEvent.MOUSE_UP,
function (e:Event):void
{
if (press)
{
currX = mouseX, currY = mouseY, currT = getTimer();;
curve.push(currX, currY, currT); //
draw();
curve.reset();
lastX = currX, lastY = currY, lastT = currT;
press = false;
}
} );
}
}
}
import flash.geom.Point;
/**
* 動点にはたらく加速度が時間tの1次関数になっているという前提のもと
* 離散点(x,y,t)間を曲線で補間する方法を思いつきました
* どうせ車輪の再発明でしょうけど
* @author @kndys
*/
internal class Curve
{
// (x,y,t) 3点と 初速(u0,v0) から曲線の式を求める
private var _x0:Number, _y0:Number,
_x1:Number, _y1:Number,
_x2:Number, _y2:Number;
private var _t0:Number, _t1:Number, _t2:Number;
private var _u0:Number, _v0:Number;
// dt = t - t0 として
// 位置を x(dt) = ax*dt^3 + bx*dt^2 + u0*dt + x0
// 速度を u(dt) = 3*ax*dt^2 + 2*bx*dt + u0;
// という関数でフィッティングする
private var _ax:Number, _bx:Number,
_ay:Number, _by:Number;
// 上記係数が求まると次回計算の初速も求まる
private var _u1:Number, _v1:Number;
// pushされた点の数
private var _sampleNum:uint;
public function Curve()
{
reset();
}
public function push(x:Number, y:Number, t:int):void
{
_x0 = _x1;
_x1 = _x2;
_x2 = x;
_y0 = _y1;
_y1 = _y2;
_y2 = y;
_t0 = _t1;
_t1 = _t2;
_t2 = t;
_sampleNum++;
if (_sampleNum < 3) return; // 3点目までは計算しない
_u0 = _u1;
_v0 = _v1;
var dx1:Number = _x1 - _x0;
var dx2:Number = _x2 - _x0;
var dy1:Number = _y1 - _y0;
var dy2:Number = _y2 - _y0;
var dt1:Number = _t1 - _t0;
var dt2:Number = _t2 - _t0;
if (dt2 == 0) dt2 = 1.0;
if (dt1 == 0) dt1 = 0.5 * dt2;
var k1:Number = 1.0 / (dt1 * dt1 * (dt1 - dt2));
var k2:Number = 1.0 / (dt2 * dt2 * (dt2 - dt1));
if (_sampleNum == 3)
{
_u0 = 0.0;
_v0 = 0.0;
}
var p1:Number = dx1 - _u0 * dt1;
var p2:Number = dx2 - _u0 * dt2;
var q1:Number = dy1 - _v0 * dt1;
var q2:Number = dy2 - _v0 * dt2;
_ax = p1 * k1 + p2 * k2;
_ay = q1 * k1 + q2 * k2;
_bx = - p1 * k1 * dt2 - p2 * k2 * dt1;
_by = - q1 * k1 * dt2 - q2 * k2 * dt1;
// 次回の初速を求めておく
_u1 = 3 * _ax * dt1 * dt1 + 2 * _bx * dt1 + _u0;
_v1 = 3 * _ay * dt1 * dt1 + 2 * _by * dt1 + _v0;
}
public function getPoint(dst:Point, ratio:Number):Boolean
{
if (_sampleNum < 3) return false;
_calcPoint(dst, _calcTime(ratio));
return true;
}
public function getVelocity(dst:Point, ratio:Number):Boolean
{
if (_sampleNum < 3) return false;
_calcVelocity(dst, _calcTime(ratio));
return true;
}
private function _calcTime(ratio:Number):Number
{
var dt:Number;
if (ratio <= 1.0)
{
dt = (_t1 - _t0) * ratio;
}
else
{
dt = _t1 + (_t2 - _t1) * (ratio - 1.0);
}
return dt;
}
private function _calcPoint(dst:Point, dt:Number):void
{
var dt2:Number = dt * dt;
var dt3:Number = dt2 * dt;
dst.x = _ax * dt3 + _bx * dt2 + _u0 * dt + _x0;
dst.y = _ay * dt3 + _by * dt2 + _v0 * dt + _y0;
}
private function _calcVelocity(dst:Point, dt:Number):void
{
var dt2:Number = dt * dt;
dst.x = 3 * _ax * dt2 + 2 * _bx * dt + _u0;
dst.y = 3 * _ay * dt2 + 2 * _by * dt + _v0;
}
//public function draw(g:Graphics):void
//{
//if (_sampleNum < 3) return; // 線が引けない
//
//var dx1:Number = _x1 - _x0;
//var dy1:Number = _y1 - _y0;
//var dt1:Number = _t1 - _t0;
//
// 曲線の分割数 直線距離ピクセル数の半分とした
//var n:int = (0.5 * Math.sqrt(dx1 * dx1 + dy1 * dy1) | 0) + 1;
//
//g.moveTo(_x0, _y0);
//for (var i:int = 1; i <= n; i++)
//{
//var dti:Number = dt1 * i / n;
//var dti2:Number = dti * dti;
//var dti3:Number = dti2 * dti;
//var xt:Number = _ax * dti3 + _bx * dti2 + _u0 * dti + _x0;
//var yt:Number = _ay * dti3 + _by * dti2 + _v0 * dti + _y0;
//g.lineTo(xt, yt);
//}
//
//}
//
//public function drawEnd(g:Graphics):void
//{
//if (_sampleNum < 2) return;
//else if (_sampleNum == 2)
//{
//g.moveTo(_x0, _y0);
//g.lineTo(_x1, _y1);
//return;
//}
//
// 最後の1点まで線を引くために、前回計算した係数の値をそのまま利用する。
//var dx:Number = _x2 - _x1;
//var dy:Number = _y2 - _y1;
//var dt10:Number = _t1 - _t0;
//var dt21:Number = _t2 - _t1;
//var n:int = (0.5 * Math.sqrt(dx * dx + dy * dy) | 0) + 1;
//g.moveTo(_x1, _y1);
//for (var i:int = 1; i <= n; i++)
//{
//var dti:Number = dt10 + (dt21 * i / n);
//var dti2:Number = dti * dti;
//var dti3:Number = dti2 * dti;
//var xt:Number = _ax * dti3 + _bx * dti2 + _u0 * dti + _x0;
//var yt:Number = _ay * dti3 + _by * dti2 + _v0 * dti + _y0;
//g.lineTo(xt, yt);
//}
//}
public function reset():void
{
_sampleNum = 0;
}
}