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>
56 lines
2.4 KiB
TypeScript
56 lines
2.4 KiB
TypeScript
import { hasConnectedAnchor, JOINT_CONNECTED_KEY } from '../core/model/joint';
|
||
import type { Mat } from '../core/model/matrix';
|
||
import { referenceCandidates, referencedId } from '../core/model/objectRefs';
|
||
import type { Vec2 } from '../core/model/Properties';
|
||
import { jointFarEnd, jointSettings, sliderAxis, sliderRail } from '../core/properties/joint';
|
||
import type { DrawItem } from './items';
|
||
|
||
/** What the scene view draws of a joint (#154): from where it sits to what it holds on to. */
|
||
export interface JointLink {
|
||
id: string;
|
||
/** Where the joint sits – the point of its own object it holds. */
|
||
from: Vec2;
|
||
/**
|
||
* Where the line ends: a spring's or a rope's connected anchor, for a hinge and a fixed joint the
|
||
* connected object itself. Equal to `from` when such a joint is held to the scene.
|
||
*/
|
||
to: Vec2;
|
||
/** The object it is connected to as the view shows it; '' for the scene, or for one that is gone. */
|
||
connectedId: string;
|
||
/** Whether `to` is a point of its own, which can be dragged. */
|
||
anchored: boolean;
|
||
/** The frame the connected anchor is counted in; null for the scene's. */
|
||
frame: Mat | null;
|
||
/** A slider's rail through where it sits; `limited` when its ends are the limits, which get a tick. */
|
||
rail: { from: Vec2; to: Vec2; limited: boolean } | null;
|
||
}
|
||
|
||
/** The item a joint's reference names, looked up from where the joint sits (see `objectRefs.ts`). */
|
||
function connectedItem(item: DrawItem, byId: ReadonlyMap<string, DrawItem>): DrawItem | null {
|
||
const ref = referencedId(item.props[JOINT_CONNECTED_KEY]);
|
||
for (const id of referenceCandidates(item.id, ref)) {
|
||
const found = byId.get(id);
|
||
if (found) return found;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
export function jointLinkOf(item: DrawItem, byId: ReadonlyMap<string, DrawItem>): JointLink | null {
|
||
if (item.type !== 'joint') return null;
|
||
const settings = jointSettings(item.props);
|
||
const connected = connectedItem(item, byId);
|
||
const from = { x: item.x, y: item.y };
|
||
const anchored = hasConnectedAnchor(settings.type);
|
||
const frame = connected?.m ?? null;
|
||
const to = anchored
|
||
? jointFarEnd(settings, from, frame)
|
||
: connected
|
||
? { x: connected.x, y: connected.y }
|
||
: from;
|
||
const rail =
|
||
settings.type === 'slider'
|
||
? { ...sliderRail(settings, from, sliderAxis(item.m)), limited: settings.distanceLimits !== null }
|
||
: null;
|
||
return { id: item.id, from, to, connectedId: connected?.id ?? '', anchored, frame, rail };
|
||
}
|