fix(wipeout): keep fill boundary precise at UTM coordinates
`wipeout_boundary_2d` built the fill boundary from the wipeout's insertion point cast straight to f32, stored in a HatchModel with world_origin = [0, 0]. At UTM-scale coordinates (~1e6) f32 resolves to ~0.06, so the boundary collapsed and the relative-to-eye fill shader cancelled `boundary - eye` to noise — the mask rendered as squares. Return the boundary as small f32 offsets relative to a world_origin (the insertion point, kept in f64) and set HatchModel.world_origin to it at both the model-space and paper/viewport fill sites. Same fix as the wide-polyline and on-create hatch fills. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2672807c13
commit
4e8521010b
1 changed files with 25 additions and 21 deletions
|
|
@ -4843,7 +4843,7 @@ impl Scene {
|
||||||
// Per-entity world_offset selection so paper-layout content
|
// Per-entity world_offset selection so paper-layout content
|
||||||
// viewports still see model-block wipeouts at the right local
|
// viewports still see model-block wipeouts at the right local
|
||||||
// coordinates (same rationale as hatches).
|
// coordinates (same rationale as hatches).
|
||||||
let boundary = Self::wipeout_boundary_2d(wo);
|
let (fill_origin, boundary) = Self::wipeout_boundary_2d(wo);
|
||||||
if boundary.len() >= 3 {
|
if boundary.len() >= 3 {
|
||||||
let mut fill_color = bg_color;
|
let mut fill_color = bg_color;
|
||||||
if self.selected.contains(&wo.common.handle) {
|
if self.selected.contains(&wo.common.handle) {
|
||||||
|
|
@ -4856,7 +4856,7 @@ impl Scene {
|
||||||
color: fill_color,
|
color: fill_color,
|
||||||
angle_offset: 0.0,
|
angle_offset: 0.0,
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
world_origin: [0.0; 2],
|
world_origin: fill_origin,
|
||||||
vp_scissor: None,
|
vp_scissor: None,
|
||||||
draw_depth: 0.0,
|
draw_depth: 0.0,
|
||||||
});
|
});
|
||||||
|
|
@ -4866,24 +4866,29 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the 2D (XY) boundary polygon for a Wipeout entity.
|
/// Compute the 2D (XY) boundary polygon for a Wipeout entity.
|
||||||
|
/// Wipeout fill boundary as small f32 offsets from the returned `world_origin`
|
||||||
|
/// (the insertion point, kept in f64). Building it in absolute WCS f32
|
||||||
|
/// collapsed the boundary into squares at UTM-scale coordinates; the
|
||||||
|
/// relative-to-eye hatch fill reconstructs `world_origin + offset` precisely.
|
||||||
fn wipeout_boundary_2d(
|
fn wipeout_boundary_2d(
|
||||||
wo: &acadrust::entities::Wipeout,
|
wo: &acadrust::entities::Wipeout,
|
||||||
) -> Vec<[f32; 2]> {
|
) -> ([f64; 2], Vec<[f32; 2]>) {
|
||||||
use acadrust::entities::WipeoutClipType;
|
use acadrust::entities::WipeoutClipType;
|
||||||
|
|
||||||
|
let origin = [wo.insertion_point.x, wo.insertion_point.y];
|
||||||
|
|
||||||
let is_polygon = wo.clipping_enabled
|
let is_polygon = wo.clipping_enabled
|
||||||
&& wo.clip_boundary_vertices.len() >= 3
|
&& wo.clip_boundary_vertices.len() >= 3
|
||||||
&& matches!(wo.clip_type, WipeoutClipType::Polygonal);
|
&& matches!(wo.clip_type, WipeoutClipType::Polygonal);
|
||||||
|
|
||||||
if is_polygon {
|
if is_polygon {
|
||||||
let ox = (wo.insertion_point.x) as f32;
|
|
||||||
let oy = (wo.insertion_point.y) as f32;
|
|
||||||
// DXF clip vertices live in image-pixel space, centred on the
|
// DXF clip vertices live in image-pixel space, centred on the
|
||||||
// image (range −size/2 … +size/2). Image-bottom-left → insertion,
|
// image (range −size/2 … +size/2). Image-bottom-left → insertion,
|
||||||
// image-y-axis points DOWN (per the DXF "v_vector points down the
|
// image-y-axis points DOWN (per the DXF "v_vector points down the
|
||||||
// image" convention), so map:
|
// image" convention), so map:
|
||||||
// x_off = (clip.x + size.x/2) × u_vec
|
// x_off = (clip.x + size.x/2) × u_vec
|
||||||
// y_off = (size.y/2 − clip.y) × v_vec ← y flipped
|
// y_off = (size.y/2 − clip.y) × v_vec ← y flipped
|
||||||
|
// Offsets are relative to `origin` (the insertion point).
|
||||||
let cx_of = |v: &acadrust::types::Vector2| v.x + wo.size.x * 0.5;
|
let cx_of = |v: &acadrust::types::Vector2| v.x + wo.size.x * 0.5;
|
||||||
let cy_of = |v: &acadrust::types::Vector2| wo.size.y * 0.5 - v.y;
|
let cy_of = |v: &acadrust::types::Vector2| wo.size.y * 0.5 - v.y;
|
||||||
let mut poly: Vec<[f32; 2]> = wo
|
let mut poly: Vec<[f32; 2]> = wo
|
||||||
|
|
@ -4894,7 +4899,7 @@ impl Scene {
|
||||||
let cy = cy_of(v);
|
let cy = cy_of(v);
|
||||||
let wx = (wo.u_vector.x * cx + wo.v_vector.x * cy) as f32;
|
let wx = (wo.u_vector.x * cx + wo.v_vector.x * cy) as f32;
|
||||||
let wy = (wo.u_vector.y * cx + wo.v_vector.y * cy) as f32;
|
let wy = (wo.u_vector.y * cx + wo.v_vector.y * cy) as f32;
|
||||||
[ox + wx, oy + wy]
|
[wx, wy]
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// Close the loop: the GPU `in_polygon` ray-cast walks
|
// Close the loop: the GPU `in_polygon` ray-cast walks
|
||||||
|
|
@ -4906,28 +4911,27 @@ impl Scene {
|
||||||
poly.push(first);
|
poly.push(first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
poly
|
(origin, poly)
|
||||||
} else {
|
} else {
|
||||||
// Rectangular boundary from 4 corners.
|
// Rectangular boundary from 4 corners, as offsets from `origin`.
|
||||||
let ox = (wo.insertion_point.x) as f32;
|
|
||||||
let oy = (wo.insertion_point.y) as f32;
|
|
||||||
let oz = wo.insertion_point.z as f32;
|
|
||||||
let ux = (wo.u_vector.x * wo.size.x) as f32;
|
let ux = (wo.u_vector.x * wo.size.x) as f32;
|
||||||
let uy = (wo.u_vector.y * wo.size.x) as f32;
|
let uy = (wo.u_vector.y * wo.size.x) as f32;
|
||||||
let vx = (wo.v_vector.x * wo.size.y) as f32;
|
let vx = (wo.v_vector.x * wo.size.y) as f32;
|
||||||
let vy = (wo.v_vector.y * wo.size.y) as f32;
|
let vy = (wo.v_vector.y * wo.size.y) as f32;
|
||||||
let _ = oz;
|
|
||||||
// Close the loop (repeat corner 0): the GPU `in_polygon` ray-cast
|
// Close the loop (repeat corner 0): the GPU `in_polygon` ray-cast
|
||||||
// walks sequential vertex pairs and never wraps last→first, so an
|
// walks sequential vertex pairs and never wraps last→first, so an
|
||||||
// unclosed quad leaves the v3→v0 edge untested and the solid mask
|
// unclosed quad leaves the v3→v0 edge untested and the solid mask
|
||||||
// bleeds past the boundary — same reason the polygon branch closes.
|
// bleeds past the boundary — same reason the polygon branch closes.
|
||||||
vec![
|
(
|
||||||
[ox, oy],
|
origin,
|
||||||
[ox + ux, oy + uy],
|
vec![
|
||||||
[ox + ux + vx, oy + uy + vy],
|
[0.0, 0.0],
|
||||||
[ox + vx, oy + vy],
|
[ux, uy],
|
||||||
[ox, oy],
|
[ux + vx, uy + vy],
|
||||||
]
|
[vx, vy],
|
||||||
|
[0.0, 0.0],
|
||||||
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7885,7 +7889,7 @@ impl Scene {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Paper-block wipeouts live in paper coords — no `world_offset`.
|
// Paper-block wipeouts live in paper coords — no `world_offset`.
|
||||||
let boundary = Self::wipeout_boundary_2d(wo);
|
let (fill_origin, boundary) = Self::wipeout_boundary_2d(wo);
|
||||||
if boundary.len() < 3 {
|
if boundary.len() < 3 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -7900,7 +7904,7 @@ impl Scene {
|
||||||
color: fill_color,
|
color: fill_color,
|
||||||
angle_offset: 0.0,
|
angle_offset: 0.0,
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
world_origin: [0.0; 2],
|
world_origin: fill_origin,
|
||||||
vp_scissor: None,
|
vp_scissor: None,
|
||||||
draw_depth: 0.0,
|
draw_depth: 0.0,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue