feat: world-space rotation axis rings - #60
Open
Teagan42 wants to merge 2 commits into
Open
Conversation
Adds three colored axis rings (R/G/B for X/Y/Z) around the current selection so rotation handles are discoverable — the corner rotate icons are small and low-contrast at 60% opacity, and users had trouble finding them. Phase 1 is visuals-only: rings render, size to the selection's bounding sphere (clamped to a 40–160 px screen-space range), fade during camera motion, and tilt slightly toward the camera when within 15° of edge-on so they never fully degenerate into a line. The corner icons remain the drag target for now. Also wires the mode-derivation infrastructure that the follow-up phase will use to gate the corner icons: a "Use classic rotate handles" preference in the workspace settings modal (off by default), a matchMedia hook that flips to classic mode on touch or narrow viewports, and a ?rotationMode=classic|rings URL override for tests and Storybook. Precedence: URL > preference > media. Phase 2 (not in this commit) will add pointer-driven drag on the rings and hide the corner icons on desktop under the default mode — those two changes belong together so desktop users are never left without a way to rotate. Design decisions and the reasoning behind them live in the wayfinder map under .scratch/rotation-handle-visibility/. Test plan: - New unit tests (27) cover ring radius clamping, camera-facing bias math at threshold boundaries, opacity easing, target-opacity precedence, and mode-derivation precedence. - Full suite: 38 files, 209 tests, all pass. - Typecheck: clean.
Completes the rotation-handle-visibility spec. The axis rings from phase 1
are now the primary drag target on desktop; the corner rotate-handle icons
render only in classic mode or on touch / narrow viewports.
Wires ring meshes into the existing transform-handle infrastructure by
tagging them with the same `userData.transformHandle = "rotate"` pattern
that other 3D helper handles use. A ring hit produces a synthesized
handleKey like `rotate-ring-x`, which `rotationAxisForHandle` maps to the
matching axis via its existing `endsWith("-x" | "-z")` fallback — no new
codepath in the rotate branch.
`pickTransformHandle` gets a small relaxation: it still bails on
multi-selection for non-ring handles (which are bound to a specific shape
id), but rings are allowed for multi-select and default their id to the
first selected shape.
Hover: raycasts on pointermove when nothing else is grabbing the pointer
and updates a ref that feeds `syncRotationRings`. Cleared on pointer leave.
Active-drag dimming: derived in the animate loop from `transformRef`
(handleKey starts with `rotate-ring-`), so the non-dragged rings ease to
28% while the active one stays at full opacity.
Visibility on the meshes: had to mirror `group.visible` onto each ring
mesh because three.js's `Raycaster.intersectObjects` doesn't skip
descendants of an invisible parent — otherwise rings hidden in classic mode
would still be hit-testable.
TransformOverlay gains one prop: `hideCornerRotateHandles`. Wired to
`rotationMode === "rings"`.
Test plan:
- All 209 existing tests still pass; typecheck clean.
- Manual verification of drag feel is the next step (I can't run the app).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TransformControlsstyle) as the primary rotation affordance on desktop.?rotationMode=classic|ringsoverrides both for tests.Test plan
npm run typechecknpm test(209 passing, includes 27 new tests inrotationMode.test.tsandrotationRings.test.ts)?rotationMode=classic/?rotationMode=ringsto the URL → override wins.Spec:
.scratch/rotation-handle-visibility/map.md