Grip poses: per-hand primary grips, mirror mode, preview toggles (issue #42) #45
Loading…
Reference in a new issue
No description provided.
Delete branch "muse-lantern/da-game:grip-poses-per-hand"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Grip poses: per-hand primary grips, mirror mode, preview toggles (issue #42)
Slice 1 of the grip-pose plan (
dead-air-grip-pose-plan.md): primary grips, one per hand. The base pose (model.grip_offset/grip_rotate) stays the right/default pose;grip_posesentries override per side.The mirror rule, as implemented
ItemSpec.GRIP_LATERAL_AXIS = Vector3(1, 0, 0)).mirror_grip_pose(pose): position.x negated (y, z kept); rotation conjugatedM·R·Mwith M = diag(-1, 1, 1), via Basis math. Net effect: pitch kept, yaw and roll negated. Asserted on the basis (the Euler-triple assertion was dropped:get_euler()can return an equivalent representation of the same basis).is_grip_centered(bounds_center)=absf(bounds_center.x) <= 0.005(5 mm epsilon on the bounds centre's lateral component).ItemSpec.primary_grip_pose): right = base pose (default + authored-right entry +grip_offset/grip_rotate); left with an authored entry = its own composed pose; left without one + centered object = mirror of the fully composed right pose; left without one + non-centered = right pose as-is (mirrored=false).set_content_scale(s)scalesboundsand all GrabPoint positions by the relative factor (rotation unaffected);content_scalerecorded on the item.Runtime
GrabPrimaryR/GrabPrimaryL(mode PRIMARY,hand_sideset,da_mirrored/da_authoredmetas).Grabbable.best_point()takes a hand side, prefers PRIMARY points for that side, falls back to any PRIMARY (shared points keep working);grab()derives the side from the holder (HandFollower.hand_id, else grip node name).HandInteractorpasses its side.grip_posesentry replaces the default (default only as a.get()fallback), then offset/rotate compose on top (item_spec.gd:147-148). So entries storemarker - offset/rotate, not a delta from the default pose. Anyone authoring entries by hand: the entry is the pose relative to the default origin.Editor
grip_right/grip_left(schemagrip, replacinggun_grip— the rename rides here; vetoable before merge).mirror(right), live-snapped).grip_posesentry ({name, hands: ["left"], position, rotation_deg}), written only when the left differs from its derived pose, deleted when it matches again. JSON field names untouched.Verification (VM, Godot 4.6.3 pin)
--importafter.godotwipe: 0 ERROR both passes.tools/check.py: 6/6.terrain_checkhas no file on branch). Key suites:grip_pose_checkPASS 40 ok;item_editor_checkPASS 282 ok;item_editor_managePASS 110 ok;mag_interactionPASS 69 ok;sandbox_worldPASS 86 ok.volume_container(4 checks),spawn(1 check),mag_filo(1 check) — were confirmed byte-identical on pure base8d466cf. All are PR #44's stale-expectation fallout, not this branch's.level_loader.gd:47add_child-on-null (firearm, mag_filo, grip_pose) +firearm_check.gd:182is_trigger_down. Nothing else.sandbox_world_checkwas fixed on this branch: the bounds-scaling made its gizmo check fail — it compared the mesh's intrinsic AABB against the now-correctly-scaled bounds. The rendered gizmo was always correct (the gizmo node carries the content scale); the suite was scale-blind. It now compares rendered sizes (mesh AABB x gizmo node scale vs bounds + 0.02 x content_scale); passes on branch and base.Overlap with PR #44
item_editor_checkandmag_interaction_checkon this branch overlap PR #44's diff on the same lines (both read expectations from the item dict now; this branch additionally updates the mag check to the two-primary-point world). Whichever of #44 / this PR lands second needs a rebase and a reconciliation of those two files — the slice-1 versions supersede #44's for the lines they touch.Follow-ups (not in this PR)
best_pointdefaults the side to"right"), but the bridge'sbest_point(grip.global_position, true)passes no side — a left-hand bridge grab currently takes the right point instead of the mirrored left one. Wiring the side through the bridge is a small follow-up; the in-headset feel of the per-hand poses is unverifiable headlessly either way.Head:
muse-lantern:grip-poses-per-hand(242f438) — 3 commits, 16 files, +1402/-231, base main8d466cf.Runtime half of grip-pose slice 1. The grab point no longer hardcodes the right-hand pose for both hands: - ItemSpec: mirror_grip_pose() (lateral position negated, rotation conjugated M*R*M), is_grip_centered() (5 mm epsilon on the bounds centre lateral component), primary_grip_pose() resolving a side to {position, rotation_deg, mirrored, authored}. Right is the base pose (default + grip_poses entry + grip_offset/grip_rotate); left with an authored entry takes it, left without one mirrors the right pose for centered objects and takes the right pose as-is otherwise. - WorldItem: two primary GrabPoints, GrabPrimaryR / GrabPrimaryL, each with hand_side and da_mirrored/da_authored metas. set_content_scale now also scales bounds and the GrabPoint positions (poses are authored in model space). - Grabbable.best_point() takes a hand side and prefers PRIMARY points for that side, falling back to any PRIMARY (shared points keep working); grab() derives the side from the holder node (HandFollower hand_id, else grip node name). - HandInteractor passes its hand side to best_point. Tests: new grip_pose_check (mirror rule, pistol case, authored left, non-centered fallback, scale, side selection); mag_filo and mag_interaction adapted to the two points; mag_interaction now reads its rotation expectation from the definition (a mag with a grip_rotate composes it on the default, a mag without one keeps the default). grip_pose_check is listed in CI.c352da8a7615dc67a4c9