Can you create a custom joint type in planck.js?

This article explores whether you can create a custom joint type within planck.js, a 2D JavaScript physics engine based on Box2D. We will examine the limitations of the framework regarding native joint extension, look at practical workarounds to achieve custom joint behavior, and review a code example utilizing distance constraints and custom forces.


Understanding planck.js Joint Limitations

In planck.js, you cannot natively register a completely new, low-level joint type by extending the internal solver structures without modifying the core source code of the library itself. The engine relies on highly optimized, pre-defined mathematical solvers for its built-in joints, such as RevoluteJoint, PrismaticJoint, and DistanceJoint.

Because these constraints are solved simultaneously in the physics island step, adding a truly custom joint requires deep integration into the internal island constraint solver, which is not exposed via a public “plugin” API.

Alternative Approaches to Custom Joints

If the built-in joints do not meet your specific mechanical requirements, you can simulate custom joint behavior using two primary strategies:

1. Combining Existing Joints

Most complex mechanical behaviors can be achieved by chaining multiple built-in joints together using invisible, dummy rigid bodies. For example, a custom “slot-revolute” joint can be created by attaching a PrismaticJoint to a small intermediate body, and then attaching a RevoluteJoint from that intermediate body to the target body.

2. Manual Force and Torque Application

For behaviors that cannot be modeled by standard constraints (such as custom springs, magnetic attractions, or soft-body connections), you can manually calculate and apply forces during the simulation loop. By listening to the world’s pre-step events, you can calculate the distance and angle between two bodies and apply equal and opposite forces to simulate a custom connection.

Example: Simulating a Custom Spring Joint

Below is an example of how to simulate a custom, non-linear spring joint using the manual force application method within the planck.js simulation loop.

import planck from 'planck-js';

const world = planck.World();

// Create two bodies to connect
const bodyA = world.createBody({ type: 'dynamic', position: planck.Vec2(0, 0) });
const bodyB = world.createBody({ type: 'dynamic', position: planck.Vec2(0, 5) });

// Custom joint configuration
const restLength = 3.0;
const kStiffness = 10.0; 

// Simulation loop / Pre-step hook
world.on('pre-step', () => {
  const posA = bodyA.getPosition();
  const posB = bodyB.getPosition();
  
  // Calculate the vector between the two connection points
  const forceDirection = planck.Vec2.sub(posB, posA);
  const currentLength = forceDirection.normalize(); // Modifies vector to unit length, returns original length
  
  // Custom non-linear hooke's law implementation
  const displacement = currentLength - restLength;
  const forceMagnitude = kStiffness * displacement;
  
  // Apply equal and opposite forces
  const forceToApply = planck.Vec2.mul(forceDirection, forceMagnitude);
  
  bodyA.applyForceToCenter(forceToApply, true);
  bodyB.applyForceToCenter(planck.Vec2.neg(forceToApply), true);
});

By leveraging these programmatic workarounds, developers can effectively bypass the lack of a native custom joint API and build complex, custom mechanical relationships between rigid bodies in planck.js.