I have 3 Bodies (Nodes) that are connected in a triangualr shape using DistanceJoint's (Muscle). I'm increasing the length of the bottom Muscle(m). At a given point the "triangle" collapses and all 3 Nodes are in one line laying at the ground. When I'm decreasing the length of that Muscle(m) then I expect the nodes/muscles to snap back to some kind of triangle (given that the Muscle(m) is short enough). But that doesn't happen.

I think this is because all Nodes have the same Y-Coordinate. But how can change the behaviour so that I get a triangle again?

I possible solution would be to apply a very small impuls to the top Node. But the top node could change when the triangle falls on to one (other) Muscle.

public class Simulator implements ApplicationListener {

    public static final float PIXELS_TO_METER = 100f;

    private ShapeRenderer renderer;
    private OrthographicCamera camera;
    private World world;
    private Box2DDebugRenderer debugRenderer;

    private Body ground;

    private final Random random = new Random();
    Node n1;
    Node n2;
    Node n3;
    Muscle m;
    Muscle m2;
    Muscle m3;

    public void create() {
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 600);
        renderer = new ShapeRenderer();
        world = new World(new Vector2(0, -9.81f), true);
        debugRenderer = new Box2DDebugRenderer(true, true, false, true, true, true);

        final BodyDef groundBodyDef = new BodyDef();
        groundBodyDef.type = BodyType.StaticBody;
        groundBodyDef.position.set(0 / PIXELS_TO_METER, 5 / PIXELS_TO_METER);
        ground = world.createBody(groundBodyDef);

        final PolygonShape groundShape = new PolygonShape();
        groundShape.setAsBox(Gdx.graphics.getWidth() / PIXELS_TO_METER, 5 / PIXELS_TO_METER);

        final FixtureDef groundFixtureDef = new FixtureDef();
        groundFixtureDef.shape = groundShape;
        groundFixtureDef.density = 1f;
        groundFixtureDef.friction = 1f;


        n1 = new Node(300, 100, 10, 0.2f, world);
        n2 = new Node(350, 150, 10, 0.6f, world);
        n3 = new Node(325, 400, 10, 1f, world);

        m = new Muscle(n1, n2, 200, 1f, random.nextFloat(), world);
        m2 = new Muscle(n1, n3, 200, 1f, random.nextFloat(), world);
        m3 = new Muscle(n2, n3, 200, 1f, random.nextFloat(), world);


    public void resize(final int width, final int height) {


    public void render() {
        // m.update(Gdx.graphics.getDeltaTime());
        // m2.update(Gdx.graphics.getDeltaTime());
        // m3.update(Gdx.graphics.getDeltaTime());
        world.step(1f / 60f, 6, 2);
        if (Gdx.input.isKeyJustPressed(Input.Keys.UP)) {
        if (Gdx.input.isKeyJustPressed(Input.Keys.DOWN)) {

        Gdx.gl.glClearColor(1, 1, 1, 1);
        debugRenderer.render(world, camera.combined.cpy().scale(PIXELS_TO_METER, PIXELS_TO_METER, 0));

    public void pause() {

    public void resume() {

    public void dispose() {
        System.out.println("Disposing stuff!");


class Node

public class Node {

    private final Body body;

    public Node(final float x, final float y, final float radius, final float friction, final World world) {
        final BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.DynamicBody;
        bodyDef.position.set(x / Simulator.PIXELS_TO_METER, y / Simulator.PIXELS_TO_METER);
        bodyDef.fixedRotation = true;
        body = world.createBody(bodyDef);

        final CircleShape shape = new CircleShape();
        shape.setRadius(radius / Simulator.PIXELS_TO_METER);

        final FixtureDef fDef = new FixtureDef();
        fDef.shape = shape;
        fDef.friction = friction;
        // fDef.density = friction / 10;// 0.01f;

    public Body getBody() {
        return body;

    public void render(final ShapeRenderer r) {
        final Vector2 pos = body.getPosition();
        final float friction = body.getFixtureList().get(0).getFriction();
        r.setColor(friction, friction, friction, 1);
        r.circle(pos.x * Simulator.PIXELS_TO_METER, pos.y * Simulator.PIXELS_TO_METER, 10);

    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Node -->");
        sb.append("Position: ").append(body.getPosition().x).append(":").append(body.getPosition().y);
        return sb.toString();


class Muscle

public class Muscle {
    private static final float frequency = 2f;

    private final DistanceJoint joint;

    private final float minLength = 50 / Simulator.PIXELS_TO_METER;
    private final float maxLength;

    private final float contractionTime;
    private float internalTimer;

    public Muscle(final Node leftNode, final Node rightNode, final float length, final float strength,
            final float contractionTime, final World world) {
        final DistanceJointDef djDef = new DistanceJointDef();
        maxLength = length / Simulator.PIXELS_TO_METER;
        this.contractionTime = contractionTime;
        djDef.length = maxLength;

        djDef.dampingRatio = strength;
        djDef.frequencyHz = frequency;
        djDef.bodyA = leftNode.getBody();
        djDef.bodyB = rightNode.getBody();
        djDef.collideConnected = true;
        joint = (DistanceJoint) world.createJoint(djDef);

    public void update(final float delta) {
        internalTimer += delta;
        if (internalTimer >= 1) {
            internalTimer -= 1;
        if (internalTimer <= contractionTime) {
        } else {

    public void lengthen() {
        joint.setLength(joint.getLength() + 10 / Simulator.PIXELS_TO_METER);


    public void shorten() {
        joint.setLength(joint.getLength() - 10 / Simulator.PIXELS_TO_METER);


  • \$\begingroup\$ This happens with real physics too. If you flatten out a jointed bar and push straight inward on the two ends, your applied force points straight through the joint axis and exerts zero torque on the joint, so the joint locks instead of bending. You'll see real machines take pains to avoid this straight line configuration, by limiting how far the joint can open. \$\endgroup\$ – DMGregory Jan 7 at 11:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.