Let a joint glide on a rail and drive it with a motor (#154)
Step 2 of the joints: a door that opens by itself, a lift and a piston need a joint that slides along one line and a motor that turns or pushes. A graph also needs to read how far such a joint stands. - Slider: a fifth joint type on Rapier's prismatic joint. Its rail is the joint's own x axis, so turning the joint turns the rail. Joints now carry Scale, Rotation and Mirror like a sprite. - Limit distance with Min and Max, in world units from where the object was placed. - Motor on hinge and slider: Off, Speed or Target, with Motor speed, Motor target and Max force (0 = no limit). Target goes there at no more than the speed and slows down for the last stretch. - Changing speed, target or force keeps the engine joint. Switching the motor on or off builds it again. - New node Joint position: value and speed of a hinge (degrees, counting past a full round) or a slider (world units). - Scene view draws a slider's rail dashed, with a tick at each limit. - shownFor in the schema takes a list of choices that must all hold. Findings, measured with a probe against Rapier 0.20: - A prismatic joint holds both bodies at one angle. Held to the scene or to an object without a rigidbody, the joint's own anchor body is turned by the placed difference, so nothing jumps. Between two rigidbodies the held one is turned to match, and the console says so once. - Rapier reads no joint angle back. The position is worked out from the two bodies. - A hinge motor is weak on a small body at the end of a long arm (box 20 on an arm of 100 does not lift itself). More solver iterations would help but cost every scene. A door of 100 x 20 reaches -90 and holds it. This is written in the wiki. Tests: - Unit: 16 new in joints.test.ts, 1 in joint-links, 3 catalog cases. - e2e 41: 27 checks (25 + 2), block 41f with a real mouse in the inspector and the motor in play (59.1 of 60 units in 1 s). - 9 of 9 unit counter-probes fail as they should. - Gate: 220 files, 3436 tests (3416 + 20), CHECK EXIT 0. - e2e total 1701 is counted (1699 + 2), the full suite did not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4716726b6b
commit
f1e3bd8378
16 files changed
+738
-34
No files matched your search
@@ -661,6 +661,61 @@ export async function physics(t) {
|
||||
hints: consoleLog.filter((line) => line.includes('[physics]')).slice(jointHints),
|
||||
}),
|
||||
);
|
||||
|
||||
// 41f. The same joint made a slider with a motor (#154, step 2): its rows follow the choices, its
|
||||
// rail is drawn along the joint as it is turned, and in play the motor drives the bob up it.
|
||||
await selectObject(joint.id);
|
||||
await pressAt(await inView(t, '__gm.inspectorRect()', "__gm.inspectorField('Type')"), 'the Type field');
|
||||
await pressAt(await json("__gm.locate('Slider', true)"), 'Slider in the menu');
|
||||
await pressAt(await inView(t, '__gm.inspectorRect()', "__gm.inspectorField('Motor')"), 'the Motor field');
|
||||
await pressAt(await json("__gm.locate('Speed', true)"), 'Speed in the menu');
|
||||
await mutate(`
|
||||
const o = __gm.state.activeScene.objects.find((o) => o.id === '${joint.id}');
|
||||
Object.assign(o.overrides, { rotation: 90, motorSpeed: -60 })`);
|
||||
await settle();
|
||||
const railLink = (await links()).links.find((l) => l.id === joint.id);
|
||||
check(
|
||||
'a slider with a speed motor shows its rows and draws its rail along the turned joint',
|
||||
(await jointObject())?.overrides?.jointType === 'slider' &&
|
||||
(await jointObject())?.overrides?.motor === 'speed' &&
|
||||
(await has('Limit distance')) &&
|
||||
(await has('Motor speed')) &&
|
||||
(await has('Max force (0 = no limit)')) &&
|
||||
!(await has('Motor target')) &&
|
||||
!(await has('Connected anchor X')) &&
|
||||
railLink?.rail?.limited === false &&
|
||||
Math.abs(railLink.rail.from.x) < 0.01 &&
|
||||
Math.abs(railLink.rail.to.y - railLink.rail.from.y - 120) < 0.01,
|
||||
JSON.stringify({ railLink, overrides: (await jointObject())?.overrides }),
|
||||
);
|
||||
await select(null);
|
||||
await play();
|
||||
await evaluate(`(() => {
|
||||
const out = (window.__sliderSamples = []);
|
||||
const start = performance.now();
|
||||
const sample = () => {
|
||||
const w = __gm.world();
|
||||
if (!w) return;
|
||||
const bob = w.entities.find((e) => e.objectId === 'phys-bob');
|
||||
out.push({ t: performance.now() - start, x: bob?.worldX, y: bob?.worldY });
|
||||
if (performance.now() - start < 1000) requestAnimationFrame(sample);
|
||||
};
|
||||
requestAnimationFrame(sample);
|
||||
})()`);
|
||||
await sleep(1300);
|
||||
const slid = await json('window.__sliderSamples');
|
||||
await stop();
|
||||
// From the first sample to the last, at 60 units a second up the rail.
|
||||
const span = slid.length > 1 ? (slid.at(-1).t - slid[0].t) / 1000 : 0;
|
||||
const climbed = slid.length > 1 ? slid[0].y - slid.at(-1).y : 0;
|
||||
check(
|
||||
'in the played scene the motor drives the bob up its rail at its speed, and not off it',
|
||||
slid.length >= 5 &&
|
||||
span > 0.8 &&
|
||||
Math.abs(climbed - 60 * span) < 6 &&
|
||||
slid.every((s) => Math.abs(s.x) < 0.5),
|
||||
JSON.stringify({ frames: slid.length, span, climbed, first: slid[0], last: slid.at(-1) }),
|
||||
);
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const scene = __gm.state.activeScene;
|
||||
|
||||
Reference in new issue
Block a user