Building Physics Simulations with Pymunk and Pygame — Part III
Last Updated on July 23, 2026 by Editorial Team
Author(s): Sakshi Bhatia
Originally published on Towards AI.
Building Physics Simulations with Pymunk and Pygame — Part III

Damped Spring
It is a damped spring. (Without damping, the spring would oscillate forever once compressed or stretched.)
from pymunk.constraints import RotarySpring
spring = DampedSpring(
a,
b,
anchor_a,
anchor_b,
rest_length,
stiffness,
damping
)
Other than the parameters inherited from the base constraint class, it has the following extra parameters:
- anchor_a ((float, float)): It is where on body A the spring attaches relative to body A’s center.
- anchor_b ((float, float)): It is where on body B the spring attaches relative to body B’s center.
- rest_length (float): the distance the spring wants to be
- stiffness (float): spring constant, k
- damping (float)
- static spring_force: This is Pymunk’s default for spring restoration force, which is essentially Hooke’s law: force = (rest_length — dist) * stiffness
- force_func: This is Pymunk’s force callback function that is called each step and used to calculate spring force, excluding damping. It defaults to DampedSpring.springforce(), but can be overridden. For example, if we want to construct a spring that becomes stiff after stretching too far:
def custom_force(spring, dist):
stretch = dist - spring.rest_length
if stretch < 20:
return stretch * spring.stiffness
return stretch * spring.stiffness * 5
spring.force_func = custom_force

Damped Rotary Spring
Damped Rotary Spring is the rotational equivalent of Damped Spring. A damped spring resists linear displacement. A damped rotary spring resists angular displacement.

It behaves like a torsion spring (e.g., door hinge spring, car suspension stabilizer).
from pymunk.constraints import DampedRotarySpring
spring = DampedRotarySpring(
a,
b,
rest_angle,
stiffness,
damping
)
Its parameters are similar to a rotary spring, with angular displacement replacing linear displacement, and torque replacing force.
rest_angle (radians): It is the desired relative angle between the two bodies. The constraint will try to restore to this angle after the applied external force is removed.
Gear Joint
A gear joint enforces meshing of two connected bodies where the bodies act like gears. When one body rotates, the other body connected to it by a gear joint rotates in proportion, determined by a fixed ratio.
GearJoint keeps the angular velocity ratio of a pair of bodies constant.
GearJoint(bodyA, bodyB, phase, ratio)
- phase (float): This is the initial angle offset between the two bodies. If body A is at angle 0 and body B at angle pi, the phase between these two bodies is pi.
- ratio (float):

The gear joint tries to maintain

Differentiating both sides with respect to time gives,

from pymunk.constraints import GearJoint
gear = GearJoint(
wheel1,
wheel2,
phase=0,
ratio=-1
)
For the above constraint, if wheel 1 turns 10 degrees, wheel 2 will turn -10 degrees.
Simple Motor
SimpleMotor keeps the relative angular velocity constant.
It should not be confused with GearJoint, which keeps the ratio of angular velocity constant, while SimpleMotor keeps the relative (difference) of angular velocity constant.

Rate is the desired relative angular velocity.
from pymunk.constraints import SimpleMotor
motor = SimpleMotor(a, b, rate=5)
It is a good idea to cap the max_force:
motor.max_force = 1000
Let’s say the wheel hits a wall. Without an upper limit on force, the motor keeps increasing the force to maintain the specified rate, potentially generating unrealistically large torque, hence making the simulation unstable.
Pin Joint
A Pin Joint is a rigid, massless rod (fixed length) connecting two points on two bodies.
from pymunk.constraints import PinJoint
joint = pymunk.PinJoint(
body_a,
body_b,
)
It has additional parameters from the base constraint class for anchor points and distance. Anchor points are coordinates local to each body.
- anchor_a ((float, float)): Coordinates for attachment of pin joint in A’s local coordinates. Defaults to (0,0), i.e., center of mass of A.
- anchor_b ((float, float)): Coordinates for attachment of pin joint in B’s local coordinates. Defaults to (0,0), i.e., center of mass of B.
- distance (float): The distance can be specified explicitly, or Pymunk calculates this based on the distance between the anchor points in the space when the joint is created. For a Pin Joint, the simulator tries to keep this distance fixed.
joint = PinJoint(
body_a,
body_b,
anchor_a=(20, 0),
anchor_b=(-10, 5),
)
joint.distance = 120
A pin joint only fixes the distance between the two bodies. The bodies are free to rotate.
Slide Joint
A Slide joint is a generalization of a Pin joint. Instead of enforcing one exact distance, it allows the distance to vary within a specified range.
For example,
from pymunk.constraints import SlideJoint
joint = SlideJoint(
body_a,
body_b,
anchor_a=(0, 0),
anchor_b=(0, 0),
min=50,
max=150,
)
The anchors are allowed to be anywhere between 50 and 150 units apart. As long as the distance between the anchors is within this range, the joint does nothing. If the distance exceedsmax, it acts like a taut rope and pulls the bodies together. If the distance falls belowmin, it acts like a rigid spacer and pushes the bodies apart.
Pivot Joint
It connects two bodies so that one point on body A and one point on body B are always at the same world position. The bodies are then free to rotate around that shared point. Example: a door hinge, a wheel axle. A pivot joint fixes both the x and y coordinates of the anchor points.
Before the joint,

After the joint,

Example: a door hinge

We can create a pivot joint in Pymunk in two ways:
- Method 1: Specify pivot’s world coordinates
from pymunk.constraints import PivotJoint
joint = PivotJoint(body_a, body_b, (300, 200))
Here, (300,200) is a point in the world. Pymunk computes the corresponding local anchor points automatically.
- Method 2: Specify local anchors
These anchors are in local body coordinates. Ensure that the anchors overlap for a stable simulation.
body_a.position = (90, 50) # so local (10, 0) is at (100, 50)
body_b.position = (120, 50) # so local (-20, 0) is at (100, 50)
joint = PivotJoint(
body_a,
body_b,
(10, 0),
(-20, 0),
)
When the simulation starts, Pymunk moves the bodies (through constraint forces) until those two anchor points coincide. If the initial mismatch is small, this is usually fine. If it’s large, you may see a noticeable snap or violent movement as the solver corrects the error.
Groove Joint
A groove joint is a pivot joint with one DOF added.
Degrees of freedom: A pivot joint removes two translational degrees of freedom between the anchor points, fixing them together at one location. A groove joint removes only the motion perpendicular to the groove, while allowing motion along the groove. The attached body can also rotate freely unless another constraint restricts it.
A groove joint is useful for mechanisms where a part must slide along a predefined path, such as:
- Sliding drawers
- Piston rods in a straight slot
Imagine a bolt sliding inside a straight slot:
Body A (slot)
groove_a ---------------------- groove_b
| |
+------------------------------+
● <-- anchor on Body B
^
can slide anywhere
along the groove
anchor_b is attached to the groove. It cannot leave the groove, but it can move freely along its length.
GrooveJoint class has the following parameters:
- groove_a ((float, float)): One endpoint of the groove on body A
- groove_b ((float, float)): Other endpoint of the groove on body A
- anchor_b ((float, float)): coordinates of point on body B that is constrained
Like almost every Pymunk constraint, groove_a, groove_b, and anchor_b are all expressed in their own body’s local coordinates.
For example, anchor_b = (0,0) means the center of body B slides in the groove, and anchor_b = (20,0) means a point 20 pixels to the right of body B’s center slides in the groove.
Unlike some other Pymunk objects, GrooveJoint does not have default values for its geometric parameters. A common convention is:
groove_a = (-L/2, 0)
groove_b = ( L/2, 0)
anchor_b = (0, 0)
Example,
from pymunk.constraints import GrooveJoint
joint = GrooveJoint(
body_a,
body_b,
(-50, 0), # groove starts here
(50, 0), # groove ends here
(0, 0) # body B's center slides in the groove
)
Ratchet Joint
Imagine tightening a bolt with a socket wrench.
- You rotate the wrench clockwise and every few degrees you hear a click.
- You can keep tightening. If you try to rotate backward, the wrench locks (depending on the ratchet direction).
- It allows relative rotation only in discrete angular steps.
Constructor:
from pymunk.constraints import RatchetJoint
RatchetJoint(a, b, phase, ratchet)
- ratchet (float): angular spacing between clicks (in radians). If ratchet = math.pi/6 (=30°), the joint locks every 0°, 30°, 60°, 90°, and so on, relative to the phase.
- phase(float): It is the angular offset from the beginning.
Example,
joint = pymunk.constraints.RatchetJoint(
body1,
body2,
phase=15,
ratchet=math.pi/4
)
The joint locks every 15°, 60°, 105°, and so on.
Rotary Limit Joint
It restricts the relative angle between two bodies to stay within a specified range; the bodies can rotate freely within the allowed range, but cannot rotate beyond the minimum or maximum angle.
Constructor:
from pymunk.constraints import RotaryLimitJoint
RotaryLimitJoint(a, b, min, max)
- min (float): minimum allowed relative angle (radians)
- max (float): maximum allowed relative angle (radians)
These were all the essential details we need to know about constraints in Pymunk. In the next story, we will start building small systems. Until then, goodbye!
Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.
Published via Towards AI
Towards AI Academy
We Build Enterprise-Grade AI. We'll Teach You to Master It Too.
15 engineers. 100,000+ students. Towards AI Academy teaches what actually survives production.
Start free — no commitment:
→ 6-Day Agentic AI Engineering Email Guide — one practical lesson per day
→ Agents Architecture Cheatsheet — 3 years of architecture decisions in 6 pages
Our courses:
→ AI Engineering Certification — 90+ lessons from project selection to deployed product. The most comprehensive practical LLM course out there.
→ Agent Engineering Course — Hands on with production agent architectures, memory, routing, and eval frameworks — built from real enterprise engagements.
→ AI for Work — Understand, evaluate, and apply AI for complex work tasks.
Note: Article content contains the views of the contributing authors and not Towards AI.