feat(viewcube): add compass ring, cardinals, and nav controls

Build the ViewCube out into a full navigation widget:

- Compass ring painted in the cube's ground plane (z = base) so it
  reads as a ground compass and foreshortens with the view.
- N/E/S/W cardinals painted flat onto the ring band; clicking one snaps
  to that side elevation. Added S/W glyphs to the bitmap atlas.
- Home button (top-down view), roll arrows (90° roll about the view
  axis), and four nudge triangles (90° tip/spin) as SVG iced controls
  laid out around the cube. New home.svg.
- WCS / named-UCS selector under the cube, switching the active UCS.

The cube hit area now shares one stack with the control buttons, so a
click on a button is caught by the button while a click on the cube
falls through. Face clicks snap to the highlighted region rather than a
cursor position the viewport's move handler can overwrite.

Camera gains home_view / roll_by / nudge_90; floating viewports drive
the same ops through mutate_active_viewport_camera, which re-encodes the
result to the stored view direction + twist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-25 23:46:43 +03:00
commit f00c48bebb
9 changed files with 633 additions and 40 deletions

View file

@ -390,6 +390,39 @@ impl Camera {
self.sync_yaw_pitch();
}
/// Jump to the default "home" view — top-down (looking along −Z), expressed
/// in the active UCS so it lands square to the user's coordinate frame.
pub fn home_view(&mut self, ucs: glam::Mat4) {
let dir = ucs.transform_vector3(Vec3::Z);
self.snap_to_direction(dir, ucs);
}
/// Roll the camera about its own view axis by `angle` radians. The gaze
/// direction is unchanged; only the up-sense twists.
pub fn roll_by(&mut self, angle: f32) {
self.rotation = (self.rotation * Quat::from_rotation_z(angle)).normalize();
self.sync_yaw_pitch();
}
/// Tip / spin the view 90° about a screen axis. `horizontal = false` tips
/// up/down (rotation about the camera's right axis); `true` spins
/// left/right (about the camera's up axis).
pub fn nudge_90(&mut self, horizontal: bool, positive: bool) {
let axis = if horizontal {
self.rotation * Vec3::Y
} else {
self.rotation * Vec3::X
};
let ang = if positive {
std::f32::consts::FRAC_PI_2
} else {
-std::f32::consts::FRAC_PI_2
};
let delta = Quat::from_axis_angle(axis, ang);
self.rotation = (delta * self.rotation).normalize();
self.sync_yaw_pitch();
}
// ── Internal helpers ───────────────────────────────────────────────────
/// Derive yaw and pitch from the current quaternion for the ViewCube