id:flashrodさんを参考に物理シミュレーション

(参考:flashrod

Box2Dってバネ制約ないんすね。びっくりした。


http://blog.technohippy.net/Breast.swf
揺れます(不自然・・・)。

ソースは以下。
ところどころ自分でも何やってるかよく分かってません。

Spring.as

package {
  import Box2D.Collision.*;
  import Box2D.Collision.Shapes.*;
  import Box2D.Common.Math.*;
  import Box2D.Dynamics.*;
  import Box2D.Dynamics.Joints.*;
  import flash.display.Sprite;
  import flash.events.TimerEvent;
  import flash.geom.Point;
  import flash.utils.Timer;
  import Spring;

  public class Breast extends Sprite {
    private static var SWELLING_RATE:Number = 3;
    public var world:b2World;
    private var m_physScale:Number = 1.0;
    private var bodies:Array = new Array();
    private var springs:Array = new Array();

    public function Breast() {
      world = createWorld();
      var points:Array = new Array();
      var center:Point = new Point(100, 100);
      var radius:int = 80;

      // breast
      for (var i:int = 0; i < 7; i++) {
        var theta:Number = (i / 6) * Math.PI
        var point:Point = new Point(radius * Math.sin(theta), 200 - radius * Math.cos(theta));
        point.y += point.x / SWELLING_RATE;
        var bodyDef:b2BodyDef = createBodyDef(point, [0, 6].indexOf(i) != -1);
        var body:b2Body = world.CreateBody(bodyDef);
        bodies.push(body);
        points.push(point);
      }

      // middle anchor
      bodyDef = createBodyDef(new Point(
        bodies[0].GetCenterPosition().x, 
        (bodies[0].GetCenterPosition().y + bodies[bodies.length-1].GetCenterPosition().y) / 2
      ), true);
      bodies.push(world.CreateBody(bodyDef));
      points.push(point);

      // wall
      var wallBox:b2BoxDef = new b2BoxDef();
      wallBox.extents.Set(10/m_physScale, 680/m_physScale);
      var wallBody:b2BodyDef = new b2BodyDef();
      wallBody.position.Set(-10/m_physScale, (360+95)/m_physScale);
      wallBody.AddShape(wallBox);
      world.CreateBody(wallBody);

      for (i = 0; i < bodies.length; i++) {
        for (var j:int = i; j < bodies.length; j++) {
          if (i == j) continue;
          var spring:Spring = new Spring(bodies[i], bodies[j], 10, 0);
          springs.push(spring);
        }
      }

      startSimulation();
    }

    private function createWorld():b2World {
      var aabb:b2AABB = new b2AABB();
      aabb.minVertex.Set(-1000.0, -1000.0);
      aabb.maxVertex.Set(1000.0, 1000.0);
      var gravity:b2Vec2 = new b2Vec2(0.0, 300.0);
      var doSleep:Boolean = true;
      return new b2World(aabb, gravity, doSleep);
    }

    private function createBodyDef(point:Point, fixed:Boolean):b2BodyDef {
      var boxDef:b2BoxDef = new b2BoxDef();
      boxDef.extents.Set(1, 1);
      if (!fixed) boxDef.density = 0.1;

      var bodyDef:b2BodyDef = new b2BodyDef();
      bodyDef.position.Set(point.x, point.y);
      bodyDef.AddShape(boxDef);
      return bodyDef;
    }

    private function startSimulation():void {
      var step:Number = 1.0 / 60;
      var iterations:int = 100;
      var timer:Timer = new Timer(50);
      timer.addEventListener(TimerEvent.TIMER, function(event:TimerEvent):void {
        graphics.clear();
        for (var i:int = 0; i < springs.length; i++) {
          springs[i].addSpringForce();
        }
        world.Step(step, iterations);
        graphics.lineStyle(1,0xffffff,1);
        var firstPoint:b2Vec2 = bodies[0].GetCenterPosition();
        graphics.moveTo(firstPoint.x, 0);
        graphics.lineTo(firstPoint.x, firstPoint.y);
        for (var k:int = 1; k < bodies.length - 2; k++) {
          var controlPoint:b2Vec2 = bodies[k].GetCenterPosition();
          var anchorPoint:b2Vec2 = controlPoint.Copy();
          anchorPoint.Add(bodies[k+1].GetCenterPosition());
          anchorPoint.Multiply(0.5);
          graphics.curveTo(controlPoint.x, controlPoint.y, anchorPoint.x, anchorPoint.y);
        }
        var lastPoint:b2Vec2 = bodies[bodies.length-2].GetCenterPosition();
        graphics.lineTo(lastPoint.x, lastPoint.y);
        graphics.lineTo(lastPoint.x, 1000);

      });
      timer.start();
    }
  }
}

Spring.as

package {
  import Box2D.Common.Math.b2Vec2;
  import Box2D.Dynamics.b2Body;

  public class Spring {
    private var bA:b2Body;
    private var bB:b2Body;
    private var k:Number;
    private var friction:Number;
    private var desiredDistance:Number;

    public function Spring(bA:b2Body, bB:b2Body, k:Number, friction:Number) {
      var v:b2Vec2 = bA.m_position.Copy();
      v.Subtract(bB.m_position);
      this.desiredDistance = v.Length();
      this.bA = bA;
      this.bB = bB;
      this.k = k;
      this.friction = friction;
    }

    public function addSpringForce():void {
      var vecAtoB:b2Vec2 = bB.m_position.Copy();
      vecAtoB.Subtract(bA.m_position);
      var diff:Number = vecAtoB.Length() - desiredDistance;
      var normAtoB:b2Vec2 = vecAtoB.Copy();
      normAtoB.Multiply(1.0 / vecAtoB.Length())
      var normBtoA:b2Vec2 = vecAtoB.Copy();
      normAtoB.Multiply(k * diff);
      normBtoA.Multiply(-1 * k * diff);
      bB.ApplyForce(normBtoA, bB.m_position);
      bA.ApplyForce(normAtoB, bA.m_position);
      bA.WakeUp();
      bB.WakeUp();
    }
  }
}