package physics;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javafx.scene.Node;
import layout.PhysLayout;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;

/**
 * Manage a JBox2D simulation of multiple JavaFX nodes. Nodes have no collision,
 * and are moved by applying forces (from mechanical springs and forcefields).
 *
 * @author Christoph Burschka &lt;christoph@burschka.de&gt;
 */
public class Box2DSpringSimulation {

    private final PhysLayout layout;
    private final Map<Node, Body> bodies;
    private final World world;
    private final Set<ForceField> fields;
    private double friction;

    public Box2DSpringSimulation(PhysLayout layout) {
        this.layout = layout;
        bodies = new HashMap<>();
        fields = new HashSet<>();

        // New zero-gravity world:
        world = new World(new Vec2(0, 0));
        BodyDef def = new BodyDef();

        layout.getNodes().stream().forEach((node) -> {
            def.position.set((float) node.getLayoutX(), (float) node.getLayoutY());
            Body body = world.createBody(def);
            // Attach an infinitely dense point mass to the body:
            body.createFixture(new ShapelessShape((layout.getMass(node))), Float.POSITIVE_INFINITY);
            bodies.put(node, body);
        });
    }

    /**
     * Applies spring force between a and b.
     *
     * @param a
     * @param b
     * @param spring
     */
    private void applySpring(Body a, Body b, Spring spring) {
        Vec2 pA = a.getWorldCenter();
        Vec2 pB = b.getWorldCenter();
        Vec2 force = spring.getForce(pA, pB);
        a.applyForceToCenter(force);
        b.applyForceToCenter(force.negate());
    }

    private void applyFriction(Body a) {
        Vec2 v = a.getLinearVelocity();
        a.applyForceToCenter(v.mul((float) -friction));
    }

    private void applyAllForces() {
        layout.getAllConnections().stream().forEach((e) -> {
            applySpring(bodies.get(e.getKey().getA()), bodies.get(e.getKey().getB()), e.getValue());
        });
        bodies.values().stream().forEach((a) -> {
            applyFriction(a);
            fields.stream().forEach((field) -> {
                a.applyForceToCenter(field.force(a.getWorldCenter()));
            });
        });
    }
}

@@ -103,24 +103,64 @@
 a.util.Set;%0A
+import javafx.animation.AnimationTimer;%0A
 import javaf
@@ -173,16 +173,16 @@
 e.Node;%0A
-
 import l
@@ -783,16 +783,44 @@
 riction;
+%0A    private long timeStamp;
 %0A%0A    pu
@@ -1471,16 +1471,16 @@
  body);%0A
-
         
@@ -1486,24 +1486,1959 @@
  %7D);%0A    %7D%0A%0A
+    public void step(double dt) %7B%0A        // Box2D physics work by applying a fixed force on every timestep.%0A        applyAllForces();%0A        // 6 iterations of u' and 3 iterations of u (recommended value).%0A        world.step((float) dt, 6, 3);%0A    %7D%0A%0A    public void runSimulation(double dt) %7B%0A        final long nanoTimeStep = (long) (dt * 1e9);%0A        timeStamp = System.nanoTime();%0A%0A        AnimationTimer frameListener = new AnimationTimer() %7B%0A            @Override%0A            public void handle(long now) %7B%0A                long nextTimeStamp = timeStamp + nanoTimeStep;%0A%0A                // Simulate in dt-sized steps until caught up.%0A                while (nextTimeStamp %3C now) %7B%0A                    bodies.entrySet().stream().forEach((e) -%3E %7B%0A                        Vec2 relative = new Vec2((float) e.getKey().getLayoutX(), (float) e.getKey().getLayoutY());%0A                        relative.subLocal(e.getValue().getWorldCenter());%0A                        // If the node has been moved externally or pressed, update.%0A                        // Tolerance is 1e-6f to account for rounding between JBox2D float and JavaFX double.%0A                        if (relative.length() %3E 1e-6f %7C%7C e.getKey().isPressed()) %7B%0A                            e.getValue().getTransform().p.addLocal(relative);%0A                            // Reset its momentum, since the user is %22holding%22 it.%0A                            e.getValue().setLinearVelocity(new Vec2());%0A                        %7D%0A                    %7D);%0A%0A                    step(dt);%0A%0A                    bodies.entrySet().stream().forEach((e) -%3E %7B%0A                        Vec2 p = e.getValue().getWorldCenter();%0A                        e.getKey().relocate(p.x, p.y);%0A                    %7D);%0A%0A                    timeStamp = nextTimeStamp;%0A                    nextTimeStamp = timeStamp + nanoTimeStep;%0A                %7D%0A            %7D%0A        %7D;%0A%0A        frameListener.start();%0A    %7D%0A%0A
     /**%0A    

Rewrite the AnimationTimer code for JBox2D.