I am trying to use a 2D physics engine to model a rotating disc with a pin on its edge. The pin can collide with other objects, either preventing the disc from rotating further, or pushing the other object out of the way. The disc itself doesn't collide with anything (because it is on another plane).
I've struggled to achieve this in Matter.js, which I have not used before. I am open to using other libraries; I picked Matter.js based only on popularity and active development.
First, I set the disc's collisionFilter = { .mask: 0 }
and the pin and driven object to collisionFilter: { .category: 1, .mask: 1 }
. The disc should not collide with anything, and the pin and driven object should collide.
Then I tried two ways of forming the disc-and-pin:
Creating a compound body with two parts. However, according to this 2017 issue, "Parts can not have their own collisionFilter
, only their parents are respected."
Creating two separate bodies, with a stiff constraint between them to fix the relative positions of the disc and pin. This successfully achieves the desired effect that when the disc rotates, the pin moves with it (and vice versa).
However, the issue here is that the pin can still rotate around its center. If the pin is square-shaped it is most obvious.
(Also I'm not sure if this method lets me control the disc's moment of inertia?)
Is this achievable? LLMs are trying to get me to set a beforeUpdate
event that cancels out the pin's angular velocity. But my hunch is that what I'm doing shouldn't be so complicated?
After a few hours I could not determine how to do so with Matter.js. I switched to Planck.js which addresses my two problems with Matter.js:
I can define different collision properties on the fixtures (physical parts) that make up a single body.
I have different types of joints to work with, like WeldJoint
and RevoluteJoint
, that make it easier to model complex relationships between bodies.
In the following example which can be run on the Playground, I create three disc-and-pin bodies as described in my question to simulate a combination lock's wheel pack. The bodies are stacked concentrically. Each has a pin that connects with a fly (basically, another pin) on the next disc to drive it.
planck.testbed('WheelPack', function(testbed) {
let pl = planck, Vec2 = pl.Vec2, Transform = pl.Transform;
let world = new pl.World(Vec2(0, 0)); // no gravity
// unsure how to disable the mouse in testbed mode
testbed.mouseForce = 0.01;
let discRadius = 2;
let pinSize = 0.2;
let flySize = 0.2;
function createWheel(position, angle, index) {
let discFixture = pl.Circle(discRadius);
let pinFixture = pl.Box(pinSize, pinSize, Vec2(discRadius - pinSize, 0));
let flyFixture = pl.Box(flySize, flySize, Vec2(-(discRadius - flySize), 0));
let wheel = world.createBody({
type: 'dynamic',
position: position,
angle: angle,
// prevent wheels from spinning when force is removed
angularDamping: 20,
});
wheel.createFixture(discFixture, {
density: 1,
filterMaskBits: 0,
});
wheel.createFixture(pinFixture, {
density: 1,
filterCategoryBits: 1 << index,
filterMaskBits: 1 << index,
});
wheel.createFixture(flyFixture, {
density: 1,
filterCategoryBits: 1 << (index + 1),
filterMaskBits: 1 << (index + 1),
});
// Add a revolute joint to fix wheel in place
world.createJoint(pl.RevoluteJoint({
enableMotor: false
}, world.createBody(), wheel, wheel.getPosition()));
return wheel;
}
let wheel1 = createWheel(Vec2(0, 0), 0, 1);
let wheel2 = createWheel(Vec2(0, 0), Math.PI / 3, 2); // offset by 60 degrees
let wheel3 = createWheel(Vec2(0, 0), -Math.PI / 3, 3); // offset by -60 degrees
testbed.step = function() {
let format = (angle) => ((100 + 50 * angle / Math.PI) % 100).toFixed(1)
testbed.status('wheel 1', format(wheel1.getAngle()));
testbed.status('wheel 2', format(wheel2.getAngle()));
testbed.status('wheel 3', format(wheel3.getAngle()));
if (testbed.activeKeys.right) {
wheel3.setAngularVelocity(-2);
} else if (testbed.activeKeys.left) {
wheel3.setAngularVelocity(2);
} else {
wheel3.setAngularVelocity(0);
}
};
testbed.info('Use arrow keys to rotate wheel 3.');
return world;
})
Some additional comments:
Adjust the angularDamping
of the body to control how quickly the disc decelerates after the pin is no longer being driven.
I use filterMaskBits: 0
to prevent any collision with a given fixture (the disc).
I use filterCategoryBits
and filterMaskBits
to control which objects are driven by a given pin, to act as though these objects are on different planes.
The RevoluteJoint
on the wheel body allows it to rotate around its center point like a pinwheel, without spinning off into space.