fix(viewer): gizmo face clicks always snap to canonical orientation#145
fix(viewer): gizmo face clicks always snap to canonical orientation#145zachdive wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
41cb45e to
d1ca5dc
Compare
d1ca5dc to
825c87c
Compare
Greptile SummaryFixes the viewcube face-click drift bug (#128) by bypassing drei's frame-by-frame animation entirely and replacing it with a single deterministic snap in a new
Confidence Score: 5/5Safe to merge. The snap logic is deterministic, all three call sites are covered, and the OrbitControls reconciliation pattern is correct. The new component handles the degenerate TOP/BOTTOM up-vector case, uses a proper type guard instead of a cast, and the camera-snap sequence (set position then lookAt then controls.update) correctly reconciles OrbitControls internal state. No correctness issues remain; only cosmetic nits. No files require special attention beyond the two style nits in ViewGizmo.tsx. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant GizmoViewcube
participant ViewGizmo
participant Camera
participant OrbitControls
User->>GizmoViewcube: click face / edge / corner
GizmoViewcube->>ViewGizmo: onClick(ThreeEvent)
ViewGizmo->>ViewGizmo: recover direction (face normal or eventObject.position)
ViewGizmo->>OrbitControls: read target and camera distance
ViewGizmo->>Camera: set up vector (non-degenerate)
ViewGizmo->>Camera: "set position = target + direction x radius"
ViewGizmo->>Camera: lookAt(target)
ViewGizmo->>OrbitControls: update() reconcile spherical state
ViewGizmo->>Camera: invalidate() request render
Reviews (3): Last reviewed commit: "fix(viewer): harden view gizmo snap hand..." | Re-trigger Greptile |
…128) After clicking a viewcube face once, the camera snapped correctly. After orbiting and clicking the same face again, the camera landed at a drifted angle instead of the canonical orthographic orientation. Affected every face (TOP, FRONT, RIGHT, BACK, LEFT, BOTTOM) in both orthographic and perspective camera modes. Root cause: drei's GizmoHelper tween rotates camera.up along with the interpolated quaternion each animation frame and stops once the angle delta falls below ~0.01 rad. During the animation OrbitControls.update() is invoked with that transient rotated up, and its final camera.lookAt(target) bakes the tilted up into camera.quaternion. On completion drei resets camera.up to defaultUp without redoing a canonical lookAt, leaving camera.up, camera.position, and camera.quaternion subtly out of sync. Subsequent clicks of the same face either short-circuit at the threshold (no movement, drift preserved) or animate toward the still-tilted state. Fix: bypass drei's animation entirely. ViewGizmo passes a custom onClick to GizmoViewcube that recovers the clicked direction (face normal for face cubes, the cube's outward local position for edge/corner cubes) and snaps the main camera directly to target + direction * radius with camera.up pinned to world-Y. camera.lookAt(target) and controls.update() then leave OrbitControls' internal spherical state reconciled with the canonical orientation. Every face click now lands at the same deterministic axis-aligned view regardless of orbit history, in both orthographic and perspective modes. Closes #128 Authored by Eve (Zach's AI agent) on behalf of Adam. Co-authored-by: eve-app <280557408+eve-app@users.noreply.github.com>
bbd7b2c to
5e9aad0
Compare
Closes #128
Summary
Clicking a viewcube face once snapped the camera correctly, but after orbiting and clicking the same face again the camera landed at a drifted angle instead of the canonical orthographic orientation. Affected every face (TOP, FRONT, RIGHT, BACK, LEFT, BOTTOM) in both orthographic and perspective camera modes.
Root cause
@react-three/drei'sGizmoHelper(v10.7.7) animates the main camera toward the target orientation frame-by-frame and stops once the angle delta falls below ~0.01 rad:Two interacting failures produce the visible drift:
camera.upalong with the interpolated quaternion before callingOrbitControls.update(). Inside that update,camera.lookAt(target)derives the final orientation from the transient rotated up — socamera.quaternionends every frame off the canonical axis.q1.angleTo(q2) < 0.01, drei flipsanimatingoff and resetscamera.up = defaultUpbut does not redo a canonical lookAt. The camera keeps the slightly-tilted quaternion produced by the previous frame'scontrols.update()and a slightly-off position from the under-convergedq1. After an orbit, the next click of the same face either short-circuits at the threshold (no movement, drift preserved) or animates toward the still-tilted state.Because
OrbitControlsdamping is enabled, the up vector drifts further during orbit, which is why the bug only surfaces after the user has moved the camera.Fix
Bypass drei's animation entirely. A new
ViewGizmocomponent (src/components/viewer/ViewGizmo.tsx) wrapsGizmoHelper+GizmoViewcubeand passes a customonClicktoGizmoViewcube. The handler:e.face.normalfor the central face cube,eventObject.position.normalize()for edge/corner cubes (those carry a non-origin local position pointing outward).OrbitControls.target(preserving any panning) and the camera-to-target distance asradius.camera.up = (0, 1, 0), setscamera.position = target + direction * radius, callscamera.lookAt(target), and triggerscontrols.update()so OrbitControls' internal spherical state is reconciled.Every face/edge/corner click now lands on the same deterministic axis-aligned view in both orthographic and perspective modes, regardless of orbit history.
Files changed
src/components/viewer/ViewGizmo.tsx— drop-in wrapper aroundGizmoHelper/GizmoViewcubewith the canonical-snap onClick handler.src/components/viewer/ThreeScene.tsx— usesViewGizmo.src/components/viewer/MeshPreview.tsx— usesViewGizmo(same gizmo bug existed here).No new dependencies. No changes to OrbitControls configuration or initial camera positions.
Manual test steps
linear_extrude).MeshPreview, top-left gizmo).Validation
npm run typecheck— passes for the touched files. One pre-existing error insrc/utils/meshPrintProcessUtils.ts:968(DataView<ArrayBufferLike>not assignable toBlobPart) reproduces onmasterand is unrelated.npm run lint— 0 errors, same 12 pre-existing warnings asmaster.npm run build— fails on the same pre-existingmeshPrintProcessUtils.tstypecheck error asmaster. Unrelated to this change.Authored by Eve (Zach's AI agent) on behalf of Adam.