forked from: IK実験
IK実験・アルゴリズム修正版
クリックで追加。
カーソルキー左右で回転レート調整、上下で繰り返し回数変更
前回のグラディウスの触手風のアルゴリズムから、もうちょっと
まともなIKっぽいやり方に変えてみた。
毎回初期状態にリセットしてから、根元から目標に向かって
最大レートまで回転させるのを繰り返す。繰り返し回数を増やすと
精度が上がる。
動きの面白さは減ったけど、このアルゴリズムならクォータニオンを
使えば3次元でも関節計算用のIKとしてそのまま使えそう。
// forked from Nao_u's IK実験
//
// IK実験・アルゴリズム修正版
//
// クリックで追加。
// カーソルキー左右で回転レート調整、上下で繰り返し回数変更
//
//
// 前回のグラディウスの触手風のアルゴリズムから、もうちょっと
// まともなIKっぽいやり方に変えてみた。
//
// 毎回初期状態にリセットしてから、根元から目標に向かって
// 最大レートまで回転させるのを繰り返す。繰り返し回数を増やすと
// 精度が上がる。
//
// 動きの面白さは減ったけど、このアルゴリズムならクォータニオンを
// 使えば3次元でも関節計算用のIKとしてそのまま使えそう。
//
package {
import flash.display.Sprite;
import flash.events.*;
[SWF(width="465", height="465", backgroundColor="0xFFFFFF", frameRate="60")]
public class FlashTest extends Sprite {
public function FlashTest() {
Main = this;
initialize();
stage.addEventListener(Event.ENTER_FRAME,update);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyCheckDown);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onClick);
}
}
}
import flash.display.Sprite;
import flash.text.*;
import flash.events.*;
import flash.geom.*;
import flash.ui.Keyboard;
var SCREEN_W:Number = 465, SCREEN_H:Number = 465;
var Main:Sprite;
var Text:TextField
var RepNum:int = 10;
var RotRate:Number = 0.1;
var IkAry:Array = new Array;
function initialize():void{
Text = new TextField();
Text.text = "";
Text.autoSize = "left";
Main.addChild(Text);
IkAry.push( new IkBase(100, 150, 14 ) );
}
function onClick(event:MouseEvent):void{
IkAry.push( new IkBase(Main.stage.mouseX, Main.stage.mouseY, 8+Math.random()*5 ) );
if( IkAry.length > 6 ){
IkAry.splice( 0, 1 );
}
}
function keyCheckDown(event:KeyboardEvent):void {
switch (event.keyCode){
case Keyboard.UP: RepNum++; break;
case Keyboard.DOWN: RepNum--; break;
case Keyboard.LEFT: RotRate-=0.005; break;
case Keyboard.RIGHT: RotRate+=0.005; break;
}
if( RepNum < 1 ) RepNum = 1;
}
function update(e :Event):void{
graphicClear();
Text.text = "回転レート:"+(RotRate).toFixed(3)+" 繰り返し回数:"+RepNum;
var ik:IkBase;
for each( ik in IkAry ) ik.resetNodeRot();
for( var i:int=0; i<RepNum; i++ ) {
for each( ik in IkAry ) ik.update();
}
for each( ik in IkAry ) ik.draw();
}
class Node{
public var x:Number;
public var y:Number;
public var Rot:Number = 0;
public var WorldRot:Number = 0;
public function Node( ){
}
}
class IkBase{
public var Length:Number = 25.0;
public var Base:Point = new Point(0,0);
public var Target:Point = new Point(0,0);
public var Nodes:Vector.<Node> = new Vector.<Node>;
public function IkBase( x:Number, y:Number, num:int ){
Base.x = x;
Base.y = y;
for( var i:int=0; i<num; i++ ) Nodes[i] = new Node();
calcNodePos();
}
// 内積
public function dot( p0:Point, p1:Point ):Number{
return p0.x*p1.x + p0.y*p1.y
}
// 値のクランプ
public function clamp( x:Number, min:Number, max:Number ):Number{
if( x < min ) x = min;
if( x > max ) x = max;
return x;
}
// 角度を-PIからPIに収める
public function radAdjust( rad:Number ):Number{
if( rad < -Math.PI ){
do{ rad += Math.PI*2; }while( rad < -Math.PI );
}else if( rad > Math.PI ){
do{ rad -= Math.PI*2; }while( rad > Math.PI );
}
return rad;
}
// 角度を加算して-πからπに調整
public function radAdd( lhs:Number, rhs:Number ):Number{
return radAdjust( lhs + rhs );
}
// 角度の差分を取って-πからπに調整
public function radSub( lhs:Number, rhs:Number ):Number{
return radAdjust( lhs - rhs );
}
// 角度を制限つきで線形補間
public function radLerpRange( src0:Number, src1:Number, rate:Number, rotSpd:Number ):Number{
var add:Number = radSub( src1, src0 ) * rate;
add = clamp( add, -rotSpd, rotSpd );
return radAdd( src0, add );
}
public function update():void{
Target.x = Main.stage.mouseX;
Target.y = Main.stage.mouseY;
var rotMax:Number = 0.001;
// 先端ノード
var sn:Node = Nodes[Nodes.length-1]
for( var i:int=1; i<=Nodes.length-2; i++ ){
// for( var i:int=Nodes.length-2; i>=1; i-- ){ // 逆順にすると計算が不安定になりやすい?
var n:Node = Nodes[i];
var tv:Point = new Point();
var sv:Point = new Point();
var rv:Point = new Point();
var lv:Point = new Point();
// 間接から目標に向いたベクトル
tv.x = Target.x - n.x;
tv.y = Target.y - n.y;
// 間接から先端に向いたベクトル
sv.x = sn.x - n.x;
sv.y = sn.y - n.y;
if( tv.x == 0 && tv.y == 0 ) tv.y = 1;
if( sv.x == 0 && sv.y == 0 ) sv.y = 1;
var angT:Number = Math.atan2( tv.x, tv.y );
var angS:Number = Math.atan2( sv.x, sv.y );
var ang:Number = radLerpRange( angS, angT, RotRate, Math.PI );
n.Rot += ang - angS;
}
calcNodePos();
}
public function calcNodePos():void{
Nodes[0].x = Base.x;
Nodes[0].y = Base.y;
var pn:Node = Nodes[0];
for( var i:int=1; i<Nodes.length; i++ ){
var n:Node = Nodes[i];
n.WorldRot = pn.WorldRot + n.Rot;
n.x = Length * Math.sin( pn.WorldRot );
n.y = Length * Math.cos( pn.WorldRot );
n.x += pn.x;
n.y += pn.y;
pn = n;
}
}
public function resetNodeRot():void{
for( var i:int=0; i<Nodes.length; i++ ){
var n:Node = Nodes[i];
n.Rot = 0;
n.WorldRot = 0;//pn.WorldRot + n.Rot;
}
}
public function draw():void{
var pn:Node = Nodes[0];
for( var i:int=1; i<Nodes.length; i++ ){
var n:Node = Nodes[i];
drawLine( pn.x, pn.y, n.x, n.y, 7.5, 0x808020 );
pn = n;
}
drawCircle( Target.x, Target.y, 12, 0xe04000 );
for each( n in Nodes ) drawCircle( n.x, n.y, 8, 0xe0d000 );
drawCircle( Base.x, Base.y, 12, 0xb0a000 );
}
}
function graphicClear():void{
Main.graphics.clear();
Main.graphics.lineStyle(1.2,0xb0b040);
Main.graphics.moveTo( SCREEN_W/2, 0 );
Main.graphics.lineTo( SCREEN_W/2, SCREEN_H );
Main.graphics.moveTo( 0, SCREEN_H/2 );
Main.graphics.lineTo( SCREEN_W, SCREEN_H/2 );
}
function drawLine( sx:Number, sy:Number, ex:Number, ey:Number, size:Number, col:int ):void{
Main.graphics.lineStyle(size,col);
Main.graphics.moveTo( sx, sy );
Main.graphics.lineTo( ex, ey );
}
function drawCircle( x:Number, y:Number, size:Number, col:int ):void{
Main.graphics.lineStyle(1.4,0x000000);
Main.graphics.beginFill(col,1);
Main.graphics.drawCircle(x,y,size);
Main.graphics.endFill();
}