feat(viewport): unify in-viewport editing on the model-space path

Replace the leaky paper-projection approach to MSPACE editing with one
coordinate adapter that routes all interaction through the viewport's own
camera — the exact camera+rect the GPU draws the content with.

- Add Scene::viewport_edit_frame(canvas) -> (camera_for_viewport,
  viewport_screen_rect): the unified adapter. Inside a floating viewport
  the viewport is the active pane, so snap/pick/grip/preview/DYN run in
  model space and land on the pixels the GPU draws. Tracks pan/zoom/twist
  exactly (twist handled by camera_from_view); crop is pixel-preserving so
  full-rect projection + cursor offset matches.
- Route snap-move, click-pick, grip-drag, grip hit-test, grip render,
  hover (HoverDwellTick), selection (single/box/poly/cycle) and command
  entity-pick through it via pick_view / cursor_model_point helpers and an
  edit_cam-aware "active pane = viewport rect" override, so the existing
  tile offset plumbing just works. Fixes wrong-object hover/selection and
  stale snap after pan/zoom.
- DYN: project anchors with the viewport camera and shift to canvas space
  so the guide/labels sit correctly (also fixes a latent tiled-model bug).
- Separate grid snap from object snap entirely: new Snapper.grid_snap_on
  flag, Grid removed from the osnap mode set, evaluated independently of
  the osnap master. Object snap never catches grid; grid corners are
  picked only when grid snap is on, at lowest priority. Default off.
- grip.rs: project_rte + grips_to_screen_rte / find_hit_grip_rte taking
  (view_rot, eye) so grips project through the composed view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-24 17:06:59 +03:00
commit a66d873e51
5 changed files with 427 additions and 171 deletions

View file

@ -3677,10 +3677,10 @@ impl Scene {
/// Inverse of [`paper_to_model`]: map a model-space point to the paper
/// sheet through the active viewport. Returns the input unchanged when
/// there is no active viewport. Used to place overlays (dynamic-input
/// anchors, guides) that are stored in model coords while editing inside a
/// viewport — projecting the raw UTM model point with the paper camera puts
/// them millions of pixels away.
/// there is no active viewport. Kept as the inverse companion to
/// `paper_to_model`; in-viewport overlays now project via the viewport
/// camera ([`viewport_edit_frame`]) rather than mapping onto the sheet.
#[allow(dead_code)]
pub fn model_to_paper(&self, model_pt: glam::DVec3) -> glam::DVec3 {
let vp_handle = match self.active_viewport {
Some(h) => h,
@ -3698,6 +3698,33 @@ impl Scene {
)
}
/// In-viewport (MSPACE) editing frame: the active floating viewport's own
/// camera — *exactly* the one the GPU renders its content with
/// ([`camera_for_viewport`]) — together with the viewport's full screen
/// rectangle in canvas pixels ([`viewport_screen_rect`]).
///
/// This is the unified editing adapter (the "süzgeç"). Inside a viewport,
/// editing IS model-space: treat the returned camera as *the* camera, the
/// returned rect as *the* pane, and the cursor relative to that rect — then
/// the existing model-space snap / hit-test / grip / preview / plane-pick
/// code runs unchanged and lands on the same pixels the GPU draws. Results
/// come back as model coordinates directly (no paper round-trip).
///
/// Because the camera is the real GPU camera, this tracks the viewport's
/// pan / zoom / twist / oblique view correctly — unlike a linear
/// paper-projection, whose auto-fit / saved-view / crop divergence left the
/// snap stale after pan/zoom. Returns `None` when not editing inside a
/// floating viewport, or the camera / rect cannot be derived.
pub fn viewport_edit_frame(
&self,
canvas_px: (f32, f32),
) -> Option<(view::camera::Camera, iced::Rectangle)> {
let vp_handle = self.active_viewport?;
let cam = self.camera_for_viewport(vp_handle)?;
let full = self.viewport_screen_rect(vp_handle, canvas_px)?;
Some((cam, full))
}
/// Fold the active viewport's saved view onto the effective camera (the
/// auto-fit centre for stale UTM views) and persist it into `view_target` /
/// `view_height`. Called on entering MSPACE so pan/zoom, paper↔model and the