rapier icon indicating copy to clipboard operation
rapier copied to clipboard

Feature Request: Spring

Open nascheinkman opened this issue 2 years ago • 5 comments

Hi,

I've been wanting to create an open source simulation of tensegrity structures and I've been wanting to use rapier to do it because its been really fun to use compared to other physics libraries I've tried.

However, one of the things necessary for tensegrity simulation is some form of spring physics. I've noticed that rapier has a SpringModel but it seems like that's only used for joint motors.

Is supporting springs something that the rapier team is interested in? I'm willing to write the code, but I would still need direction on how you would like it implemented, and how robust you would like it (perhaps like the ForceGenerator in nphysics to allow for nonlinear springs?). I already have a bare-bones working example trying to quickly hack it in as a joint, but after learning how joints are implemented in rapier it does not seem like it belongs there.

I have already tried implementing a spring externally using body.apply_force after each physics step, but for some reason it added a bunch of energy to the system over time, and that issue did not happen when implemented the same way but internally as a joint.

nascheinkman avatar Oct 08 '21 22:10 nascheinkman

The ForceGenerator in nphysics wasn’t complete enough to handle non-linear springs properly. I believe it will be best to model a spring as a joint. My guess is that we could either:

  • Create a whole new joint just for that.
  • Or modify the BallJoint so it can work in "spring mode". This could, for example be achieved by doing the position correction at the velocity level instead of the position level (we already have code for the BallJoint to handle velocity-level position correction, but whether or not this is enabled depends on a global flag that affects the whole simulation.

sebcrozet avatar Oct 20 '21 08:10 sebcrozet

I wrote a SpringJoint implementation and included both the normal and ground constraint versions. I haven't written the wide version of the joint yet, but before I do that I want to ask about a couple implementation details. All the other joints have limits and motors, should the SpringJoint have those too? If so, what kind of limits and what kind of motors?

Here's my fork with the SpringJoint code in case you want to see: https://github.com/nascheinkman/rapier/tree/spring_joint I haven't submitted a Pull Request yet because it doesn't yet compile with simd-stable enabled.

nascheinkman avatar Oct 26 '21 04:10 nascheinkman

All the other joints have limits and motors, should the SpringJoint have those too? If so, what kind of limits and what kind of motors?

I don’t think that a motor really makes sense for a spring. A limit however sounds useful. That would control the min/max length of the spring.

Here's my fork with the SpringJoint code in case you want to see: https://github.com/nascheinkman/rapier/tree/spring_joint I haven't submitted a Pull Request yet because it doesn't yet compile with simd-stable enabled.

I suggest opening a PR anyways; it doesn’t matter if it doesn’t always compiles yet. This will be easier for me to see the diffs. You can set the PR as "work-in-progress" as long as you don’t think it is ready to be merged.

sebcrozet avatar Oct 26 '21 08:10 sebcrozet

I've added distance limits to the spring that seem to work pretty well and I fixed a few issues I found with my initial implementation. I still need to add the wide version, but I think the normal version is ready.

The draft pull request is here: https://github.com/dimforge/rapier/pull/255 I included an example for the spring joint that I've been using for testing and comparison.

nascheinkman avatar Oct 30 '21 06:10 nascheinkman

The SpringJoint should be ready now here: https://github.com/dimforge/rapier/pull/255 . It includes limits and supports SIMD constraints.

nascheinkman avatar Dec 09 '21 09:12 nascheinkman

Hey 👋 ,will this be merged in?

chrislicodes avatar Oct 29 '22 07:10 chrislicodes

Hello, wondering the same :)

marwie avatar Nov 04 '22 15:11 marwie

@chrislicodes @marwie I wouldn't count on this getting merged at all. The code was written before an overhaul of the engine happened and this code would need a lot of rework to get in now.

I just opened a PR for Rope Joints #415. Planning on working other Joint types that exist in Box2d, but have not been defined in Rapier yet. I'll add Spring Joints to my list.

Wolftousen avatar Nov 07 '22 02:11 Wolftousen

Thank you @Wolftousen!

Rope joints sound great too

marwie avatar Nov 07 '22 07:11 marwie

I too would love to see spring joints in rapier, @Wolftousen.

shanecelis avatar Apr 08 '23 22:04 shanecelis

springs are the only thing missing for this to be able to replace box2d for me

aMyTimed avatar Aug 29 '23 00:08 aMyTimed

Hello, we are using spring joints in soft-body dynamics for the medical field, we would LOVE to see this implemented in the Needle engine!

pyrate avatar Oct 09 '23 15:10 pyrate

A while ago I successfully implemented springs externally in Rapier.js, I could send the code here if it would help anyone for the time being

aMyTimed avatar Oct 09 '23 18:10 aMyTimed

How would this Spring Joint be implemented? Does it have some parameters for the simulation of the spring, eg. Stiffness and Damping?

@aMySour I am interested in ur implementation. @sebcrozet What are your thoughts on this after the big rewrite, has something changed? How would someone go about implementing this?

Ughuuu avatar Nov 03 '23 16:11 Ughuuu

@aMySour I am interested in ur implementation.

Here's my spring in TypeScript (Rapier.js):

    applySpringForce(spring: SimuloSpringDesc) {
        const pointAWorld = this.getWorldPoint(spring.getBodyAPosition(), spring.getBodyARotation(), spring.localAnchorA);
        const pointBWorld = this.getWorldPoint(spring.getBodyBPosition(), spring.getBodyBRotation(), spring.localAnchorB);

        const velA = spring.getBodyAVelocity();
        const velB = spring.getBodyBVelocity();

        const springVector = this.sub(pointBWorld, pointAWorld);
        const distance = this.magnitude(springVector);
        if (distance == 0) return;

        const direction = this.normalize(springVector);

        const u = this.sub(velB, velA);
        const rj = this.crossZV(spring.getBodyBAngularVelocity(), spring.localAnchorB);
        const ri = this.crossZV(spring.getBodyAAngularVelocity(), spring.localAnchorA);
        const tmp = this.add(u, rj, ri);
        const f = this.multiply(direction, -spring.stiffness * (distance - spring.targetLength) - spring.damping * this.dot(u, direction));
        const forceA = this.multiply(f, -1);
        const forceB = f;

        spring.applyBodyAImpulse(forceA, pointAWorld);
        spring.applyBodyBImpulse(forceB, pointBWorld);
    }

I was initially trying to use body.addForceAtPoint, since intuitively springs add a force on an object, and the spring formula F=-kx has F for force, but I found that applyImpulse achieves the intended result, while addForce does not

My SimuloSpringDesc interface is this:

/** Simulo Rapier creates fake spring joint with `applyImpulseAtPoint` on two bodies.
 * 
 * Since you provide your own functions like `getBodyAPosition`, this is general-purpose, and you can do things like attach one end to a mouse cursor. */

interface SimuloSpringDesc {
    bodyA: string | null;
    bodyB: string | null;

    getBodyAPosition: () => Rapier.Vector2;
    getBodyBPosition: () => Rapier.Vector2;

    getBodyARotation: () => number;
    getBodyBRotation: () => number;

    getBodyAVelocity: () => Rapier.Vector2;
    getBodyBVelocity: () => Rapier.Vector2;

    getBodyAAngularVelocity: () => number;
    getBodyBAngularVelocity: () => number;

    applyBodyAImpulse: (impulse: Rapier.Vector2, worldPoint: Rapier.Vector2) => void;
    applyBodyBImpulse: (impulse: Rapier.Vector2, worldPoint: Rapier.Vector2) => void;

    localAnchorA: Rapier.Vector2;
    localAnchorB: Rapier.Vector2;

    /** Multiplier of spring impulse */
    stiffness: number;

    /** Dampens spring, stopping it from infinitely oscillating */
    damping: number;

    /** Target length is also known as rest length. We chose to call it target length because it's more descriptive of how it's used. */
    targetLength: number;
}

Now you can simply run applySpringForce every physics step. In my game, I just used an Object of springs (springs: { [id: string]: SimuloSpringDesc }) and applied forces for all values in those.

If anyone wants to see a live demo, at simulo.org/client I deployed a basic prototype of this, and when you drag objects around with your mouse you'll see the springs in action

aMyTimed avatar Nov 03 '23 19:11 aMyTimed