forked from: IK Bone Sample

by h_sakurai forked from IK Bone Sample (diff: 643)
IK の学習用にコメントを付けて見るバージョン
理解をしやすくするために出来るだけソースを短くしてみます。
後で読む。http://gamehell.g.hatena.ne.jp/kenmo/20080123/1201102997
こっそり開発している、3DライブラリにIKを実装しようと思ったのだけど、
いきなり3Dで考えると訳わからなくなってきたので、ちょっと試しに2Dでプロトタイプを作成。

先の事考えて、余分なクラスとか入ってますが気にしないでください。

Joint部分の、抵抗値や、稼動範囲値などは、まだ未実装
次Verでは、自作Meshやシェイプに関連付けられるようにする予定

@author narutohyper

メインのスプライトクラス
♥0 | Line 216 | Modified 2010-08-23 13:01:46 | MIT License
play

ActionScript3 source code

/**
 * Copyright h_sakurai ( http://wonderfl.net/user/h_sakurai )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/uyjh
 */

// forked from narutohyper's IK Bone Sample
package
{
    import flash.display.Sprite;
    
    /**
     * IK の学習用にコメントを付けて見るバージョン
     * 理解をしやすくするために出来るだけソースを短くしてみます。
     * 後で読む。http://gamehell.g.hatena.ne.jp/kenmo/20080123/1201102997
     * こっそり開発している、3DライブラリにIKを実装しようと思ったのだけど、
     * いきなり3Dで考えると訳わからなくなってきたので、ちょっと試しに2Dでプロトタイプを作成。
     *
     * 先の事考えて、余分なクラスとか入ってますが気にしないでください。
     * 
     * Joint部分の、抵抗値や、稼動範囲値などは、まだ未実装
     * 次Verでは、自作Meshやシェイプに関連付けられるようにする予定
     *
     * @author narutohyper
     */
    [SWF(width = 465, height = 465, frameRate = 60)]

    /**
     * メインのスプライトクラス
     */
    public class Main extends Sprite {
        /**
         * IKのアーマチュア略してikaっと
         */
        private var ika:IKArmature2d;

        public function Main() {

            //アーマチュアの作成
            ika = new IKArmature2d();
            //ボーンの作成
            ika.setBone(new IKBone2d('test1', 100, 0));
            ika.setBone(new IKBone2d('test2', 50, 70));
            ika.setBone(new IKBone2d('test3', 150, 170));
            ika.joint('root', 'test1');
            ika.joint('test1', 'test2');            
            ika.joint('test2', 'test3');            
            this.addChild(ika);
            ika.x = 465/2;// 画面の中心に置く
            ika.y = 465/2;

        }

    }
}


import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;

/**
 * IKアーマチュア
 */
class IKArmature2d extends Sprite  {

    /**
     * ルートのジョイント
     */
    private var rootJoint:IKJoint2d = new IKJoint2d()

    /**
     * ボーンの集合
     */
    private var bones:Object = {};

    /**
     * コンストラクタ
     */    
    public function IKArmature2d () {
        // ルートジョイントを追加
        this.addChild(rootJoint)
    }

    /**
     * ボーン設定
     */    
    public function setBone(value:IKBone2d):void {
        // ボーンIDで登録
        bones[value.id]=value;
        this.addChild(value);
    }

    /**
     * ジョイントの設定
     */
    public function joint($boneA:String = null, $boneB:String = null):void {
        if ($boneA) {
            if ($boneA == 'root') {
                if (bones[$boneB]) {
                    bones[$boneB].jointRoot(rootJoint);
                } else {
                    trace('Error:boneが存在しません。', $boneB + '=', bones[$boneB]);
                }
            } else if (bones[$boneB] && bones[$boneA]) {
                bones[$boneB].setParentBone(bones[$boneA]);
                bones[$boneA].setChildBone(bones[$boneB]);
            } else {
                trace('Error:boneが存在しません。', $boneA + '=', bones[$boneA], $boneA + '=', bones[$boneB]);
            }
        }
    }
}

/**
 * IKのボーン
 */
class IKBone2d extends Sprite {
    
    private var joint : IKJoint2d = new IKJoint2d();// ジョイント

    private var _parentBones : Array = [];// 親のボーン配列
    private var _childBones : Array = [];// 子のボーン配列
    
    private var _length : Number;// ボーンの長さ
    private var _bone:Shape= new Shape();// ボーンのシェイプ
    public var id:String;// 名前
    private var _rootFlag:Boolean = false; // ルートかどうかフラグ
    
    private var _clickPoint:Point = new Point();// クリックした位置保存用

    /**
     * コンストラクタ
     */
    public function IKBone2d ($id:String = 'null', $length:Number=100, $rotation:Number=0) {
        id = $id;// 名前保存

        addChild(_bone);// ボーン追加
        addChild(joint);// ジョイント追加
        length = $length;// 長さ設定
        rotationZ = $rotation;// 回転角設定

        // マウスイベント設定
        addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
        addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
        addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);

    }
    
    /**
     * マウスオーバー時処理
     */
    private function onMouseOver(event:MouseEvent):void {
        changeColor(0x3333ff);// 色を変える
    }
    /**
     * マウスアウト時処理
     */
    private function onMouseOut(event:MouseEvent):void {
        changeColor();// 色を戻す。
    }

    /**
     * マウス押下時処理
     */
    private function onMouseDown(event:MouseEvent):void {
        _clickPoint = new Point(mouseX, mouseY);// クリック位置保存

        // マウスイベントを削除
        removeEventListener(MouseEvent.MOUSE_OUT,  onMouseOut);
        removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
        removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);

        // マウスアップと移動イベントを登録
        stage.addEventListener(MouseEvent.MOUSE_UP,   onMouseUp);
        stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
    }

    /**
     * マウスアップ時処理
     */
    private function onMouseUp(event:MouseEvent):void {
        _clickPoint = new Point(0, 0);// クリック位置リセット
        if (event.target != this) {// イベントのターゲットが自分でなければ色を元に戻す。
            changeColor();
        }

        // イベントを登録
        addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
        addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
        addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
        // マウス移動イベントを削除
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
    }

    /**
     * マウス移動時処理
     */
    private function onMouseMove($color:uint = 0x006600):void {
        // 移動処理実行
        move(joint,// IKBone2dの位置
            new Point(parent.mouseX, parent.mouseY),        // IKArmature2dのマウス位置
            new Point(x, y),// IKBone2dの位置
            null,
            null,
            'mouseMove')

        //親と子両方に伝達
        for each (var bone:IKBone2d in _childBones) {
            bone.parentMove(this, new Point(joint.v3d.x + x, joint.v3d.y + y))
        }
        for each (bone in _parentBones) {
            bone.childMove(this, new Point(x, y))
        }
    }

    /**
     * 親から子へ動かす
     */
    public function parentMove($target:IKBone2d,$pt:Point):void {
        move(null, $pt, new Point(joint.v3d.x + x, joint.v3d.y + y),$target,'parent')
        //親から回ってきたら、子に伝達
        for each (var bone:IKBone2d in _childBones) {
            bone.parentMove(this,new Point(joint.v3d.x+x, joint.v3d.y+y));
        }
    }

    /**
     * 子から親を動かす
     */
    public function childMove($target:IKBone2d, $pt:Point):void {
        move(joint, $pt, new Point(x, y), $target);
        //子から回ってきたら、親に伝達
        for each (var bone:IKBone2d in _parentBones) {
            bone.childMove(this,new Point(x, y))
        }
    }
    
    /**
     * 移動処理
     * $j ジョイント
     * $p1 親のアーマチュアの位置
     * $p2 ボーンの位置
     * $mode mouseMove or parent
     */
    private function move($j:IKJoint2d, $p1:Point, $p2:Point, $target:IKBone2d = null , 
        $type:String = null,$mode:String = null):void {
        
        var dx:Number = $p1.x-$p2.x;// ボーン座標とアーマチュアのマウス位置の差分
        var dy:Number = $p1.y-$p2.y;

        // 角度取得
        var angle:Number = Math.atan2( dy, dx );
        if (!$type) {
            rotationZ = angle * 180 / Math.PI + 90;
        } else {
            rotationZ = angle * 180 / Math.PI - 90;
        }

        //もし、jointが固定点(rootJoint)に繋がっていたら、動かさない
        if (!_rootFlag) {
            if ($mode) {// モード指定時の処理(マウスの移動あるいは、親から子を動かす場合)
                // クリックのy座標入りのベクタ
                var v3d:Vector3D = new Vector3D(0 ,_clickPoint.y, 0);
                // マトリックス生成
                var mtx:Matrix3D = new Matrix3D();

                // 回転する
                mtx.prependRotation(rotationZ, Vector3D.Z_AXIS);
                v3d = mtx.transformVector(v3d);
                // クリック位置からxは回転した座標を引いたものにする。
                x = $p1.x - v3d.x
                y = $p1.y - v3d.y;
            } else if ($j) {// ジョイントが指定されている場合
                x = $p1.x - $j.v3d.x;// ジョイント分を引く
                y = $p1.y - $j.v3d.y;
            } else {
                x = $p1.x;// それ以外のときは、位置をそのままに
                y = $p1.y;
            }

        } else if ($target) {// ルートでターゲット指定されている場合
            $target.parentMove(null,new Point(joint.v3d.x+x, joint.v3d.y+y));
        }
    }

    /**
     * 色変更
     */
    private function changeColor($color:uint = 0x006600):void {
        var tc:ColorTransform = new ColorTransform();
        tc.color = $color;
        transform.colorTransform = tc;
    }

    /**
     * ボーンの長さ設定
     */
    public function set length(value:Number):void {
        drawBone(value);
        _length = value;
        joint.y = -value;
        
        joint.v3d = new Vector3D(0, -_length, 0);
        var mtx:Matrix3D = new Matrix3D();
        mtx.prependRotation(rotationZ, Vector3D.Z_AXIS);
        joint.v3d = mtx.transformVector(joint.v3d);
        

    }
    
    public function get length():Number {
        return _length;
    }
    
    //ボーンの回転
    //単にrotationでもいいのだが、3D化に備えあえてrotationZ&Vector3D
    override public function set rotationZ(value:Number):void {
        super.rotationZ = value;
        joint.v3d = new Vector3D(0,-_length, 0);
        var mtx:Matrix3D = new Matrix3D();
        mtx.prependRotation(value, Vector3D.Z_AXIS);
        joint.v3d = mtx.transformVector(joint.v3d);
    }
    
    override public function get rotationZ():Number {
        return super.rotationZ;
    }

    //JointBone(始点Jointを親Boneのjointに結合する)
    public function jointBone(value:IKBone2d):void {
        x = value.joint.v3d.x + value.x;
        y = value.joint.v3d.y + value.y;
    }

    //JointRoot(始点JointをrootJointに結合する)
    public function jointRoot(value:IKJoint2d):void {
        _rootFlag = true;
        x = value.v3d.x;
        y = value.v3d.y;
    }
    
    //親Boneのセット
    public function setParentBone(value:IKBone2d):void {
        _parentBones.push(value);
        jointBone(value);
    }
    

    //子Boneのセット
    public function setChildBone(value:IKBone2d):void {
        _childBones.push(value);
    }
    
    private function drawBone(length:Number=100,$color:uint=0x006600):void {
        //bone画像の生成
        _bone.graphics.clear();
        _bone.graphics.beginFill($color, 1);
        _bone.graphics.moveTo(0,  0);
        _bone.graphics.lineTo(5, -7);
        _bone.graphics.lineTo(length - 5, -2);
        _bone.graphics.lineTo(length,     -2);
        _bone.graphics.lineTo(length - 5,  2);
        _bone.graphics.lineTo(5,  7);
        _bone.graphics.endFill();
        _bone.rotation = -90;
    }
}

/**
 * IKのジョイント
 */
class IKJoint2d extends Sprite {

    public var v3d:Vector3D = new Vector3D(0, 0, 0);
    
    public function IKJoint2d() {
        graphics.lineStyle(3, 0x006600,1);
        graphics.drawCircle(0, 0, 3);
    }
}