-
Notifications
You must be signed in to change notification settings - Fork 241
Home
This document is ported from Box2D documentation with modification for Planck.js with permission from Erin Catto.
Planck.js is JavaScript rewrite of Box2D C++ library, a 2D rigid body simulation library for games. Programmers can use it in their games to make objects move in realistic ways and make the game world more interactive. From the game engine's point of view, a physics engine is just a system for procedural animation.
In this document Box2D and Planck.js names are used interchangeably.
In this manual it is assumed you are familiar with basic physics concepts, such as mass, force, torque, and impulses. If not, please first consult Google search and Wikipedia.
Box2D was created as part of a physics tutorial at the Game Developer Conference. You can get these tutorials from the download section of box2d.org.
Since Planck.js is written in JavaScript, you are expected to be experienced in JavaScript programming. Planck.js should not be your first JavaScript programming project!
This manual covers the majority of the Planck.js API. However, not every aspect is covered. Please look at the examples included with Planck.js to learn more.
Planck.js works with several fundamental concepts and objects. We briefly define these objects here and more details are given later in this document.
A shape is 2D geometrical object, such as a circle or polygon.
A chunk of matter that is so strong that the distance between any two bits of matter on the chunk is constant. They are hard like a diamond. In the following discussion we use body interchangeably with rigid body.
A fixture binds a shape to a body and adds material properties such as density, friction, and restitution. A fixture puts a shape into the collision system (broad-phase) so that it can collide with other shapes.
A constraint is a physical connection that removes degrees of freedom from bodies. A 2D body has 3 degrees of freedom (two translation coordinates and one rotation coordinate). If we take a body and pin it to the wall (like a pendulum) we have constrained the body to the wall. At this point the body can only rotate about the pin, so the constraint has removed 2 degrees of freedom.
A special constraint designed to prevent penetration of rigid bodies and to simulate friction and restitution. You do not create contact constraints; they are created automatically by Planck.js.
This is a constraint used to hold two or more bodies together. Planck.js supports several joint types: revolute, prismatic, distance, and more. Some joints may have limits and motors.
A joint limit restricts the range of motion of a joint. For example, the human elbow only allows a certain range of angles.
A joint motor drives the motion of the connected bodies according to the joint's degrees of freedom. For example, you can use a motor to drive the rotation of an elbow.
A physics world is a collection of bodies, fixtures, and constraints that interact together. Planck.js supports the creation of multiple worlds, but this is usually not necessary.
The physics world has a solver that is used to advance time and to resolve contact and joint constraints. The Planck.js solver is a high performance iterative solver that operates in order N time, where N is the number of constraints.
The solver advances bodies in time using discrete time steps. Without intervention this can lead to tunneling.
Planck.js contains specialized algorithms to deal with tunneling. First, the collision algorithms can interpolate the motion of two bodies to find the first time of impact (TOI). Second, there is a sub-stepping solver that moves bodies to their first time of impact and then resolves the collision.
Planck.js works with floating point numbers and tolerances have to be used to make Planck.js perform well. These tolerances have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Planck.js has been tuned to work well with moving shapes between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well. Static shapes may be up to 50 meters long without trouble.
Being a 2D physics engine, it is tempting to use pixels as your units. Unfortunately this will lead to a poor simulation and possibly weird behavior. An object of length 200 pixels would be seen by Planck.js as the size of a 45 story building.
Caution: Planck.js is tuned for MKS units. Keep the size of moving objects roughly between 0.1 and 10 meters. You'll need to use some scaling system when you render your environment and actors. The Planck.js testbed does this by using stage.js viewbox transform. DO NOT USE PIXELS.
It is best to think of Planck.js bodies as moving billboards upon which you attach your artwork. The billboard may move in a unit system of meters, but you can convert that to pixel coordinates with a simple scaling factor. You can then use those pixel coordinates to place your sprites, etc. You can also account for flipped coordinate axes.
Planck.js uses radians for angles. The body rotation is stored in radians and may grow unbounded. Consider normalizing the angle of your bodies if the magnitude of the angle becomes too large (use body.setAngle).
Caution: Planck.js uses radians, not degrees.
Settings
file defines several constants, these are all documented in
the same file. Normally you do not need to adjust these constants.
Planck.js uses floating point math for collision and simulation. Due to round-off error some numerical tolerances are defined. Some tolerances are absolute and some are relative. Absolute tolerances use MKS units.
Planck.js includes a simple small vector and matrix module in lib/common
directory. This has been
designed to suit the internal needs of Planck.js and the API. All the
members are exposed, so you may use them freely in your application.
The math classes is kept simple to make Planck.js easy to port and maintain.
The dynamics classes are the most complex part of Planck.js and is the part you likely interact with the most. The dynamics classes sit on top of Math and Collision classes, so you should be somewhat familiar with those by now.
The Dynamics classes include:
- fixture class
- rigid body class
- contact class
- joint classes
- world class
There are many dependencies between these classes so it is difficult to describe one class without referring to another. In the following, you may see some references to classes that have not been described yet. Therefore, you may want to quickly skim this chapter before reading it closely.
To create a Body or a Joint, you need to call the factory functions on World:
body = world.createBody(bodyDef);
joint = world.createJoint(jointDef);
And there are corresponding destruction functions:
world.destroyBody(body)
world.destroyJoint(joint)
When you create a body or joint, you need to provide a definition. These definitions contain all the information needed to build the body or joint. By using this approach we can prevent construction errors, keep the number of function parameters small, provide sensible defaults, and reduce the number of accessors.
Since fixtures (shapes) must be parented to a body, they are created and destroyed using a factory method on Body:
let fixture = body.createFixture(fixtureDef);
body.destroyFixture(fixture);
There is also shortcut to create a fixture directly from the shape and density.
let fixture = body.createFixture(shape, density);
Often when using Planck.js you will create and destroy many bodies, shapes, and joints. Managing these entities is somewhat automated by Planck.js. If you destroy a body then all associated shapes and joints are automatically destroyed. This is called implicit destruction.
When you destroy a body, all its attached shapes, joints, and contacts are destroyed. This is called implicit destruction. Any body connected to one of those joints and/or contacts is woken. This process is usually convenient. However, you must be aware of one crucial issue:
Caution: When a body is destroyed, all fixtures and joints attached to the body are automatically destroyed. You must nullify any pointers you have to those shapes and joints. Otherwise, your program will die horribly if you try to use those shapes or joints later.
To help you nullify your joint pointers, Planck.js world publishes events
(remove-joint
, remove-fixture
, remove-body
) that you can listen
to. Then the world object will notify you when a joint is
going to be implicitly destroyed.
Planck.js provides a callback mechanism to inform your application when implicit destruction occurs. This gives your application a chance to nullify the orphaned pointers. This callback mechanism is described later in this manual.
You can add a listener that allows World to inform you when a shape or joint is implicitly destroyed because an associated body was destroyed. This will help prevent your code from accessing orphaned pointers.
world.on('remove-joint', function(joint) {
// remove all references to joint.
})
The Fixture
, Body
, and Joint
classes allow you to attach user data
as a void pointer. This is handy when you are examining Planck.js data
structures and you want to determine how they relate to the objects in
your game engine.
For example, it is typical to attach an actor pointer to the rigid body on that actor. This sets up a circular reference. If you have the actor, you can get the body. If you have the body, you can get the actor.
let actor = gameCreateActor();
actor.body = myWorld.createBody({
userData: actor
});
Here are some examples of cases where you would need the user data:
- Applying damage to an actor using a collision result.
- Playing a scripted event if the player is inside an axis-aligned box.
- Accessing a game structure when Planck.js notifies you that a joint is going to be destroyed.
Keep in mind that user data is optional and you can put anything in it. However, you should be consistent. For example, if you want to store an actor pointer on one body, you should keep an actor pointer on all bodies. Don't store an actor pointer on one body, and a foo pointer on another body. Casting an actor pointer to a foo pointer may lead to a crash.
User data pointers are null by default.
For fixtures you might consider defining a user data structure that lets you store game specific information, such as material type, effects hooks, sound hooks, etc.
let fixture = body.createFixture({
shape: someShape,
userData: { materialIndex: 2},
});
Planck.js uses several approximations to simulate rigid body physics efficiently. This brings some limitations.
Here are the current limitations:
- Stacking heavy bodies on top of much lighter bodies is not stable. Stability degrades as the mass ratio passes 10:1.
- Chains of bodies connected by joints may stretch if a lighter body is supporting a heavier body. For example, a wrecking ball connect to a chain of light weight bodies may not be stable. Stability degrades as the mass ratio passes 10:1.
- There is typically around 0.5cm of slop in shape versus shape collision.
- Continuous collision does not handle joints. So you may see joint stretching on fast moving objects.
- Planck.js uses the symplectic Euler integration scheme. It does not reproduce parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability.
- Planck.js uses an iterative solver to provide real-time performance. You will not get precisely rigid collisions or pixel perfect accuracy. Increasing the iterations will improve accuracy.
- Erin Catto's Publications
- Collision Detection in Interactive 3D Environments, Gino van den Bergen, 2004
- Real-Time Collision Detection, Christer Ericson, 2005
- dyn4j Blog Posts by William Bittle
- Solving Rigid Body Contacts by Richard Tonge (slides)