ラグドール物理

「詳解 ActionScript 3.0アニメーション」の「6.2ベレ法」では「ラグドール物理」という用語が出てきます。
http://en.wikipedia.org/wiki/Ragdoll_physics
にあるとおり、ボロ布でできた縫いぐるみのように、力の入っていないグダグダのキャラクタの運動をシミュレーションする方法です。どんな時に使うかというと、キャラクタが気絶したり死んだときが代表的です。

6章のVerletPoint.asとVerletStick.asを利用して、ラグドール物理の例を作ってみました。
メインドキュメントは RagDoll.as です。また、点と点をバネでつなぐ VerletSpring も定義します。図で灰色で描画されているのがバネです。
RagDoll.swf 直
(↑クリックすると再生)

マウスをクリックすると、キャラクタが跳ね上がります。上下の矢印キーで、バネが緊くなったり、緩んだりします。
最初、バネは最強になっていてVerletStickと変わりません。[↓]キーを押すと、どんどんグニャグニャになって潰れてしまいます。
一度グニャグニャになったキャラクタを、マウスとキーの操作で再び立たせることができたら、エライです。

(ソースは続きに掲載。)
RagDoll.as 直

package {
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;
    import flash.geom.Rectangle;

    public class RagDoll extends Sprite {
        private var _points:Array;
        private var _sticks:Array;
        private var _springs:Array;
        private var _stageRect:Rectangle;
        private var _curK:Number;

        public function RagDoll() {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            _stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

            _points = new Array();
            _sticks = new Array();
            _springs = new Array();
            _curK = 1;

            var neck:VerletPoint = makePoint(125, 150);
            var lhead:VerletPoint = makePoint(100, 100);
            var rhead:VerletPoint = makePoint(150, 100);
            var lhand:VerletPoint = makePoint(20, 150);
            var rhand:VerletPoint = makePoint(230, 150);
            var lhip:VerletPoint = makePoint(80, 250);
            var rhip:VerletPoint = makePoint(170, 250);
            var lfoot:VerletPoint = makePoint(70, 350);
            var rfoot:VerletPoint = makePoint(180, 350);

            makeStick(lhead, rhead);
            makeStick(lhead, neck);
            makeStick(rhead, neck);
            makeStick(lhand, neck);
            makeStick(rhand, neck);
            makeStick(neck, lhip);
            makeStick(neck, rhip);
            makeStick(lhip, rhip);
            makeStick(lhip, lfoot);
            makeStick(rhip, rfoot);
            makeSpring(lfoot, rhip);
            makeSpring(rfoot, lhip);
            makeSpring(lhead, lhip);
            makeSpring(rhead, rhip);
            makeSpring(lhead, lhand);
            makeSpring(rhead, rhand);
            makeSpring(lhand, rhand);
            makeSpring(lfoot, rfoot);
            makeSpring(lhand, lhip);
            makeSpring(rhand, rhip);

            addEventListener(Event.ENTER_FRAME, onEnterFrame);
            stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
            stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        }

        private function onEnterFrame(event:Event):void {
            updatePoints();

            for (var i:int = 0; i < 1; i++) {
                constrainPoints();
                updateSticks();
                updateSprings();
            }

            graphics.clear();
            renderSprings();
            renderPoints();
            renderSticks();
        }

        private function makePoint(xpos:Number, ypos:Number):VerletPoint {
            var point:VerletPoint = new VerletPoint(xpos, ypos);
            _points.push(point);
            return point;
        }

        private function makeStick(pointA:VerletPoint, pointB:VerletPoint,
                                   length:Number = -1):VerletStick {
            var stick:VerletStick = new VerletStick(pointA, pointB, length);
            _sticks.push(stick);
            return stick;
        }

        private function makeSpring(pointA:VerletPoint, pointB:VerletPoint,
                                    length:Number = -1):VerletSpring {
            var spring:VerletSpring = new VerletSpring(pointA, pointB,
                                                       _curK, length);
            _springs.push(spring);
            return spring;
        }

        private function updatePoints():void {
            for (var i:int = 0; i < _points.length; i++) {
                var point:VerletPoint = _points[i] as VerletPoint;
                point.y += .5;
                point.update();
            }
        }

        private function constrainPoints():void {
            for (var i:int = 0; i < _points.length; i++) {
                var point:VerletPoint = _points[i] as VerletPoint;
                point.constrain(_stageRect);
            }
        }

        private function renderPoints():void {
            for (var i:int = 0; i < _points.length; i++) {
                var point:VerletPoint = _points[i] as VerletPoint;
                point.render(graphics);
            }
        }

        private function updateSticks():void {
            for (var i:int = 0; i < _sticks.length; i++) {
                var stick:VerletStick = _sticks[i] as VerletStick;
                stick.update();
            }
        }

        private function renderSticks():void {
            for (var i:int = 0; i < _sticks.length; i++) {
                var stick:VerletStick = _sticks[i] as VerletStick;
                stick.render(graphics);
            }
        }

        private function updateSprings():void {
            for (var i:int = 0; i < _springs.length; i++) {
                var spring:VerletSpring = _springs[i] as VerletSpring;
                spring.update();
            }
        }

        private function renderSprings():void {
            for (var i:int = 0; i < _springs.length; i++) {
                var spring:VerletSpring = _springs[i] as VerletSpring;
                spring.render(graphics);
            }
        }

        public function onMouseDown(event:MouseEvent):void {
            _points[0].y -= 100;
        }

        private function onKeyUp(event:KeyboardEvent):void {
            if (event.keyCode == Keyboard.UP && _curK <= 0.5) {
                _curK *= 2;
            } else if (event.keyCode == Keyboard.DOWN && _curK > 1.0 / 256) {
                _curK /= 2;
            }
            for (var i:int = 0; i < _springs.length; i++) {
                var spring:VerletSpring = _springs[i] as VerletSpring;
                spring.k = _curK;
            }
        }
    }
}

VerletSpring.as 直

package {
    import flash.display.Graphics;

    public class VerletSpring {
        private var _pointA:VerletPoint;
        private var _pointB:VerletPoint;
        private var _k:Number;
        private var _length:Number;

        public function VerletSpring(pointA:VerletPoint, pointB:VerletPoint,
                                     k:Number, length:Number = -1) {
            _pointA = pointA;
            _pointB = pointB;
            _k = k;
            if (length == -1) {
                var dx:Number = _pointA.x - _pointB.x;
                var dy:Number = _pointA.y - _pointB.y;
                _length = Math.sqrt(dx * dx + dy * dy);
            } else {
                _length = length;
            }
        }

        public function update():void {
            var dx:Number = _pointB.x - _pointA.x;
            var dy:Number = _pointB.y - _pointA.y;
            var dist:Number = Math.sqrt(dx * dx + dy * dy);
            var diff:Number = _length - dist;
            var offsetX:Number = (diff * dx / dist) / 2;
            var offsetY:Number = (diff * dy / dist) / 2;
            var k:Number = 20;
            _pointA.x -= offsetX * _k;
            _pointA.y -= offsetY * _k;
            _pointB.x += offsetX * _k;
            _pointB.y += offsetY * _k;
        }

        public function render(g:Graphics):void {
            g.lineStyle(0, 0xcccccc);
            g.moveTo(_pointA.x, _pointA.y);
            g.lineTo(_pointB.x, _pointB.y);
        }

        public function set k(v:Number):void {
            _k = v;
        }
    }
}