Thanks

Getting the ropes right actually a lot of tinkering; I had a lot of problems getting the feel I wanted, even keeping the ropes from breaking. I had to experiment a lot with relative weights of the stars compared to the rope segments for example. I am quite happy with the way it turned out, as the rope has a nice elasticity to it but doesn't stretch too much or too easily.
In the end the setup looks like this:
- a static 'fitting' just outside the view above the top;
- a box acting as star,
- a variable number of segments of rope, depending on it's total length. Rope segments are actually small objects connected with distanceJoints. The masses of these objects reduce as they are are located lower in the rope.
- something that (I think) is something of a hack: explicitly setting the distanceJoint's 'm_length' property to the desired (initial) length. Without it, the ropes will stretch.
In the end it was baffling easy to set the whole thing up; yet very fiddly to get a dependable enough simulation with the rope properties I was looking for. I fiddled with any property I could find to get the behavior I liked most (without paying all that much attention to what you might call the simulation's physical correctness).
I read quite a bit about it on this forum and I learned that creating a good rope is not easy. The thing that meant a breakthrough for me was a post by mr. Catto; he suggested making the linking objects in the rope heavier for a dependable simulation. To keep the total weight of the rope as low as possible for a believable behavior, I made the elements lighter as they approach the bottom. The idea is: the lower the link, the less weight it should support.
Also, the number of segments has been reduced a few times. It's a little coarse now, but too many sections also made for ropes that stretched a bit too much.
Here's my source code for one element.
Code:
package nl.moondance.navigation
{
import Box2D.Collision.b2AABB;
import Box2D.Collision.Shapes.b2CircleDef;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Collision.Shapes.b2PolygonShape;
import Box2D.Collision.Shapes.b2Shape;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2World;
import Box2D.Dynamics.Joints.b2DistanceJoint;
import Box2D.Dynamics.Joints.b2DistanceJointDef;
import Box2D.Dynamics.Joints.b2Joint;
import Box2D.Dynamics.Joints.b2MouseJoint;
import Box2D.Dynamics.Joints.b2MouseJointDef;
import flash.display.Sprite;
/**
* Project: Moondance
* Package: nl.moondance.navigation
* Class: MobilePhysics
*
* The mobile for the moondance site. It uses Box2D for flash: http://box2dflash.sourceforge.net/
*
* @author erik@hagreis.com
* @version 0.1
* @since 15-12-2008 17:00
*
* Copyright 2008 Erik Hagreis
*/
public class MobilePhysics
{
// owner
private var owner:Sprite;
// simulation stuff;
private var world:b2World;
private var mouseJoint:b2MouseJoint;
private var iterations:int = 10;
private var timeStep:Number = 1 / 8;
private var mousePVec:b2Vec2 = new b2Vec2();
// used for visual rendering
internal var fitting:b2Body;
internal var star:b2Body;
internal var ropeSegments:Array = new Array();
// ___________________________________________________________________________________________________________________________
// C O N S T R U C T O R
public function MobilePhysics( pOwner:Sprite, pDistanceFromTop:Number = 500 )
{
owner = pOwner;
// World bounding box
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set( -1000.0, -1000.0 );
worldAABB.upperBound.Set( 2000.0, 2000.0 );
// Define the gravity vector
var gravity:b2Vec2 = new b2Vec2( 0.0, 100.0 );
// Initialize world
world = new b2World( worldAABB, gravity, true );
// The fitting
var fittingShapeDef:b2PolygonDef = new b2PolygonDef();
fittingShapeDef.SetAsOrientedBox( 4, 4 );
var fittingBodyDef:b2BodyDef = new b2BodyDef();
fittingBodyDef.position.Set( 0, -100 );
var fittingBody:b2Body = world.CreateBody( fittingBodyDef );
fittingBody.CreateShape( fittingShapeDef );
fitting = fittingBody;
// The star (actually a box)
var starSize:Number = 30;
var starShapeDef:b2PolygonDef = new b2PolygonDef();
starShapeDef.SetAsOrientedBox( 25, 25 );
starShapeDef.density = 1;
starShapeDef.friction = 0.3;
starShapeDef.restitution = 0.3;
var starBodyDef:b2BodyDef = new b2BodyDef();
starBodyDef.position.Set( 0, pDistanceFromTop );
starBodyDef.angle = Math.random() * Math.PI * 2;
starBodyDef.linearDamping = 0.1;
starBodyDef.angularDamping = 0.9;
starBodyDef.massData.mass = 8;
starBodyDef.massData.I = 44;
var starBody:b2Body = world.CreateBody( starBodyDef );
starBody.CreateShape( starShapeDef );
ropeSegments.push( starBody );
star = starBody;
// the rope
var ropeShapeDef:b2CircleDef = new b2CircleDef();
ropeShapeDef.radius = 1;
ropeShapeDef.density = 1;
ropeShapeDef.filter.maskBits = 0;
var ropeBodyDef:b2BodyDef = new b2BodyDef();
ropeBodyDef.linearDamping = 0.2;
ropeBodyDef.angularDamping = 0.2;
ropeBodyDef.massData.mass = 1;
ropeBodyDef.massData.I = 100;
var ropeBody:b2Body;
var ropeStart:b2Body = starBody;
var jointDef:b2DistanceJointDef = new b2DistanceJointDef();
var joint:b2DistanceJoint;
var dY:Number = starBody.GetPosition().y - fittingBody.GetPosition().y;
var numSections:int = Math.ceil( dY / 55 );
for ( var i:Number = 0; i < numSections - 1; i++ )
{
// One rope section body
ropeBodyDef.position.Set( 0, starBody.GetPosition().y - dY * i / numSections );
ropeBodyDef.massData.mass = 0.8 + 0.8 * i / numSections;
//ropeBody.
ropeBody = world.CreateBody( ropeBodyDef );
ropeBody.CreateShape( ropeShapeDef );
ropeSegments.push( ropeBody );
// One rope section joint
jointDef.Initialize( ropeStart, ropeBody, ropeStart.GetPosition(), ropeBody.GetPosition() );
joint = world.CreateJoint( jointDef ) as b2DistanceJoint;
joint.m_length = dY / numSections; // this is probably something bad, but I need it for the whole thing to work.
// update startpoint for next joint
ropeStart = ropeBody;
}
// Final rope joint
jointDef.Initialize( fittingBody, ropeStart, fittingBody.GetPosition(), ropeStart.GetPosition() );
joint = world.CreateJoint( jointDef ) as b2DistanceJoint;
joint.m_length = dY / numSections;
ropeSegments.push( fitting );
// setup debug drawing
var dbgDraw:b2DebugDraw = new b2DebugDraw();
dbgDraw.m_sprite = owner;
dbgDraw.m_drawScale = 1;
dbgDraw.m_fillAlpha = 0.3;
dbgDraw.m_lineThickness = 1.0;
dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;
//world.SetDebugDraw(dbgDraw);
}
// ___________________________________________________________________________________________________________________________
// P U B L I C
public function updateSimulation():void
{
mouseDrag();
world.Step( timeStep, iterations );
}
public function destroy():void
{
owner = null;
}
// ___________________________________________________________________________________________________________________________
// G E T T E R S / S E T T E R S
private function get xMouse():Number
{
return owner.mouseX;
}
private function get yMouse():Number
{
return owner.mouseY;
}
// ___________________________________________________________________________________________________________________________
// E V E N T H A N D L E R S
// ___________________________________________________________________________________________________________________________
// P R O T E C T E D
// ___________________________________________________________________________________________________________________________
// P R I V A T E
private function mouseDrag():void
{
// Test for mouse hitting the star
var mouseOnStar:Boolean = isMouseAtStar();
// Establish mouseJoint it if doesn't exist and the mouse is ON the star
if ( !mouseJoint && mouseOnStar)
{
var md:b2MouseJointDef = new b2MouseJointDef();
md.body1 = world.GetGroundBody();
md.body2 = star;
md.target.Set( xMouse, yMouse );
md.maxForce = 100.0 * star.GetMass();
md.timeStep = timeStep;
mouseJoint = world.CreateJoint(md) as b2MouseJoint;
star.WakeUp();
}
// Destroy mouseJoint it if exists and the mouse is OFF the star
else if ( mouseJoint && !mouseOnStar )
{
world.DestroyJoint(mouseJoint);
mouseJoint = null;
}
// Update the position of the mouseJoint if we have one
if (mouseJoint)
{
var p2:b2Vec2 = new b2Vec2( xMouse, yMouse );
mouseJoint.SetTarget(p2);
}
}
private function isMouseAtStar():Boolean
{
mousePVec.Set( xMouse, yMouse );
var starShape:b2Shape = star.GetShapeList();
var inside:Boolean = starShape.TestPoint( starShape.GetBody().GetXForm(), mousePVec );
return inside;
}
}
}