feat(ucs): align the model grid to the active UCS
The grid ruled along world X/Y/Z through the world origin. It now rules along the active UCS axes through the UCS origin (passed in render/wire space), so it lines up with the user's coordinate system. Paper space stays plain WCS; identity UCS reproduces the old world-aligned grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
746a2c97be
commit
88bf2e57dc
2 changed files with 46 additions and 14 deletions
|
|
@ -138,6 +138,20 @@ impl OpenCADStudio {
|
||||||
// stay independent: model tiles in model space; the sheet plus each
|
// stay independent: model tiles in model space; the sheet plus each
|
||||||
// floating viewport (clipped to its rectangle) in paper space. One
|
// floating viewport (clipped to its rectangle) in paper space. One
|
||||||
// enumeration for both, shared with the renderer.
|
// enumeration for both, shared with the renderer.
|
||||||
|
// Align the model grid to the active UCS (origin in wire space, world
|
||||||
|
// axis directions); paper space stays plain WCS.
|
||||||
|
let (grid_origin, grid_axes) = if is_paper {
|
||||||
|
(glam::Vec3::ZERO, (glam::Vec3::X, glam::Vec3::Y, glam::Vec3::Z))
|
||||||
|
} else {
|
||||||
|
let (o, ux, uy, uz) = tab.ucs_xform().axes();
|
||||||
|
let wo = tab.scene.world_offset;
|
||||||
|
let o_wire = glam::Vec3::new(
|
||||||
|
o.x - wo[0] as f32,
|
||||||
|
o.y - wo[1] as f32,
|
||||||
|
o.z - wo[2] as f32,
|
||||||
|
);
|
||||||
|
(o_wire, (ux, uy, uz))
|
||||||
|
};
|
||||||
let grid: Vec<overlay::GridParams> = tab
|
let grid: Vec<overlay::GridParams> = tab
|
||||||
.scene
|
.scene
|
||||||
.grid_views(vw, vh)
|
.grid_views(vw, vh)
|
||||||
|
|
@ -148,6 +162,8 @@ impl OpenCADStudio {
|
||||||
view_proj: cam.view_proj(bounds),
|
view_proj: cam.view_proj(bounds),
|
||||||
bounds,
|
bounds,
|
||||||
plane,
|
plane,
|
||||||
|
origin: grid_origin,
|
||||||
|
axes: grid_axes,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,11 @@ pub struct GridParams {
|
||||||
pub view_proj: Mat4,
|
pub view_proj: Mat4,
|
||||||
pub bounds: iced::Rectangle,
|
pub bounds: iced::Rectangle,
|
||||||
pub plane: GridPlane,
|
pub plane: GridPlane,
|
||||||
|
/// Grid origin in render (wire) space and the active UCS axis directions.
|
||||||
|
/// The grid rules along these instead of world X/Y/Z, so it aligns to the
|
||||||
|
/// user's coordinate system. Plain WCS passes `(ZERO, X, Y, Z)`.
|
||||||
|
pub origin: Vec3,
|
||||||
|
pub axes: (Vec3, Vec3, Vec3),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the adaptive grid step size (world units) that the grid renderer
|
/// Compute the adaptive grid step size (world units) that the grid renderer
|
||||||
|
|
@ -308,7 +313,9 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
};
|
};
|
||||||
// Project with the full viewport rect `gb` so the grid stays aligned;
|
// Project with the full viewport rect `gb` so the grid stays aligned;
|
||||||
// only the clip is clamped.
|
// only the clip is clamped.
|
||||||
frame.with_clip(clip, |f| draw_grid(f, g.view_proj, g.plane, gb));
|
frame.with_clip(clip, |f| {
|
||||||
|
draw_grid(f, g.view_proj, g.plane, gb, g.origin, g.axes)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(a), Some(b)) = (self.selection.box_anchor, self.selection.box_current) {
|
if let (Some(a), Some(b)) = (self.selection.box_anchor, self.selection.box_current) {
|
||||||
|
|
@ -855,7 +862,14 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
/// Minimum pixel gap between adjacent grid lines before stepping up to next spacing.
|
/// Minimum pixel gap between adjacent grid lines before stepping up to next spacing.
|
||||||
const MIN_GRID_PX: f32 = 20.0;
|
const MIN_GRID_PX: f32 = 20.0;
|
||||||
|
|
||||||
fn draw_grid(frame: &mut canvas::Frame, vp: Mat4, plane: GridPlane, bounds: iced::Rectangle) {
|
fn draw_grid(
|
||||||
|
frame: &mut canvas::Frame,
|
||||||
|
vp: Mat4,
|
||||||
|
plane: GridPlane,
|
||||||
|
bounds: iced::Rectangle,
|
||||||
|
grid_origin: Vec3,
|
||||||
|
grid_axes: (Vec3, Vec3, Vec3),
|
||||||
|
) {
|
||||||
// World → canvas screen: include bounds origin so the grid lands in the
|
// World → canvas screen: include bounds origin so the grid lands in the
|
||||||
// active tile's rectangle (the screen → world unproject below stays
|
// active tile's rectangle (the screen → world unproject below stays
|
||||||
// tile-local, which is what feeds the visible-extent computation).
|
// tile-local, which is what feeds the visible-extent computation).
|
||||||
|
|
@ -867,18 +881,20 @@ fn draw_grid(frame: &mut canvas::Frame, vp: Mat4, plane: GridPlane, bounds: iced
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plane-tangent axes: axis1 and axis2 span the grid plane.
|
// Plane-tangent axes: axis1 and axis2 span the grid plane, taken from the
|
||||||
|
// active UCS basis so the grid aligns to the user's coordinate system.
|
||||||
|
let (gx, gy, gz) = grid_axes;
|
||||||
let (axis1, axis2) = match plane {
|
let (axis1, axis2) = match plane {
|
||||||
GridPlane::Xz => (Vec3::X, Vec3::Z),
|
GridPlane::Xz => (gx, gz),
|
||||||
GridPlane::Xy => (Vec3::X, Vec3::Y),
|
GridPlane::Xy => (gx, gy),
|
||||||
GridPlane::Yz => (Vec3::Y, Vec3::Z),
|
GridPlane::Yz => (gy, gz),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adaptive spacing: measure pixels per 1-unit step along each axis,
|
// Adaptive spacing: measure pixels per 1-unit step along each axis,
|
||||||
// then find the smallest power-of-5 multiple that gives ≥ MIN_GRID_PX.
|
// then find the smallest power-of-5 multiple that gives ≥ MIN_GRID_PX.
|
||||||
let o = w2s(Vec3::ZERO);
|
let o = w2s(grid_origin);
|
||||||
let a1s = w2s(axis1);
|
let a1s = w2s(grid_origin + axis1);
|
||||||
let a2s = w2s(axis2);
|
let a2s = w2s(grid_origin + axis2);
|
||||||
let px1 = ((a1s.x - o.x).powi(2) + (a1s.y - o.y).powi(2)).sqrt();
|
let px1 = ((a1s.x - o.x).powi(2) + (a1s.y - o.y).powi(2)).sqrt();
|
||||||
let px2 = ((a2s.x - o.x).powi(2) + (a2s.y - o.y).powi(2)).sqrt();
|
let px2 = ((a2s.x - o.x).powi(2) + (a2s.y - o.y).powi(2)).sqrt();
|
||||||
let px_per_unit = px1.max(px2);
|
let px_per_unit = px1.max(px2);
|
||||||
|
|
@ -909,7 +925,7 @@ fn draw_grid(frame: &mut canvas::Frame, vp: Mat4, plane: GridPlane, bounds: iced
|
||||||
unproject(bounds.width, bounds.height),
|
unproject(bounds.width, bounds.height),
|
||||||
];
|
];
|
||||||
let range = |ax: Vec3| -> (f32, f32) {
|
let range = |ax: Vec3| -> (f32, f32) {
|
||||||
let vals: Vec<f32> = corners.iter().map(|p| p.dot(ax)).collect();
|
let vals: Vec<f32> = corners.iter().map(|p| (*p - grid_origin).dot(ax)).collect();
|
||||||
(
|
(
|
||||||
vals.iter().cloned().fold(f32::INFINITY, f32::min),
|
vals.iter().cloned().fold(f32::INFINITY, f32::min),
|
||||||
vals.iter().cloned().fold(f32::NEG_INFINITY, f32::max),
|
vals.iter().cloned().fold(f32::NEG_INFINITY, f32::max),
|
||||||
|
|
@ -941,8 +957,8 @@ fn draw_grid(frame: &mut canvas::Frame, vp: Mat4, plane: GridPlane, bounds: iced
|
||||||
// Lines parallel to axis2 (varying axis1 position)
|
// Lines parallel to axis2 (varying axis1 position)
|
||||||
for i in n1_s..=n1_e {
|
for i in n1_s..=n1_e {
|
||||||
let v = i as f32 * s;
|
let v = i as f32 * s;
|
||||||
let p0 = w2s(axis1 * v + axis2 * (min2 - s));
|
let p0 = w2s(grid_origin + axis1 * v + axis2 * (min2 - s));
|
||||||
let p1 = w2s(axis1 * v + axis2 * (max2 + s));
|
let p1 = w2s(grid_origin + axis1 * v + axis2 * (max2 + s));
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&canvas::Path::new(|b| {
|
&canvas::Path::new(|b| {
|
||||||
b.move_to(p0);
|
b.move_to(p0);
|
||||||
|
|
@ -954,8 +970,8 @@ fn draw_grid(frame: &mut canvas::Frame, vp: Mat4, plane: GridPlane, bounds: iced
|
||||||
// Lines parallel to axis1 (varying axis2 position)
|
// Lines parallel to axis1 (varying axis2 position)
|
||||||
for i in n2_s..=n2_e {
|
for i in n2_s..=n2_e {
|
||||||
let v = i as f32 * s;
|
let v = i as f32 * s;
|
||||||
let p0 = w2s(axis2 * v + axis1 * (min1 - s));
|
let p0 = w2s(grid_origin + axis2 * v + axis1 * (min1 - s));
|
||||||
let p1 = w2s(axis2 * v + axis1 * (max1 + s));
|
let p1 = w2s(grid_origin + axis2 * v + axis1 * (max1 + s));
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&canvas::Path::new(|b| {
|
&canvas::Path::new(|b| {
|
||||||
b.move_to(p0);
|
b.move_to(p0);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue