diff --git a/Cargo.lock b/Cargo.lock index b852a34a..f9dd7de8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.3.4" -source = "git+https://github.com/HakanSeven12/acadrust?branch=fix%2Fhatch-boundary-handle-cap#62716f51e05aba084533f65e0eb3cfd3c7cc1dfa" +source = "git+https://github.com/HakanSeven12/acadrust?branch=fix%2Fwipeout-clip-vertices#15e434f733a4c970d5d680de89acc2cb381accbe" dependencies = [ "ahash", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index b2cf9500..e70a9f4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,8 @@ ureq = { version = "2", default-features = false, features = ["tls"] } windows-sys = { version = "0.59", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } [patch.crates-io] -# TEMP: tracks https://github.com/hakanaktt/acadrust/pull/27 (hatch -# boundary-handle cap). When that PR lands on hakanaktt/main, revert to -# `git = "https://github.com/hakanaktt/acadrust", branch = "main"`. -acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "fix/hatch-boundary-handle-cap" } +# TEMP: tracks fix/wipeout-clip-vertices on HakanSeven12/acadrust, which +# stacks the wipeout/raster-image clip-vertex fix on top of the earlier +# hatch boundary-handle cap. Revert to `branch = "main"` once both +# upstream PRs land. +acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "fix/wipeout-clip-vertices" } diff --git a/src/entities/raster_image.rs b/src/entities/raster_image.rs index 44e060fe..1152d20f 100644 --- a/src/entities/raster_image.rs +++ b/src/entities/raster_image.rs @@ -277,8 +277,12 @@ impl TruckConvertible for Wipeout { self.clip_type, acadrust::entities::WipeoutClipType::Polygonal ) { - // Convert pixel-space boundary vertices to world space: - // world = insertion_point + u_vector * v.x * size.x + v_vector * v.y * size.y + // Clip vertices are stored in image-pixel space, centred on the + // image (range ±size/2). The image's bottom-left corner sits at + // `insertion_point`, the image-Y axis points DOWN (per DXF + // "v_vector points down the image"), so map: + // x_off = (clip.x + size.x/2) × u_vector + // y_off = (size.y/2 − clip.y) × v_vector ← y flipped let ox = self.insertion_point.x; let oy = self.insertion_point.y; let oz = self.insertion_point.z; @@ -286,12 +290,11 @@ impl TruckConvertible for Wipeout { .clip_boundary_vertices .iter() .map(|v| { - let wx = self.u_vector.x * v.x * self.size.x - + self.v_vector.x * v.y * self.size.y; - let wy = self.u_vector.y * v.x * self.size.x - + self.v_vector.y * v.y * self.size.y; - let wz = self.u_vector.z * v.x * self.size.x - + self.v_vector.z * v.y * self.size.y; + let cx = v.x + self.size.x * 0.5; + let cy = self.size.y * 0.5 - v.y; + let wx = self.u_vector.x * cx + self.v_vector.x * cy; + let wy = self.u_vector.y * cx + self.v_vector.y * cy; + let wz = self.u_vector.z * cx + self.v_vector.z * cy; [ox + wx, oy + wy, oz + wz] }) .collect(); @@ -329,16 +332,17 @@ impl Grippable for Wipeout { let ox = self.insertion_point.x as f32; let oy = self.insertion_point.y as f32; let oz = self.insertion_point.z as f32; + // Same image-pixel-space → WCS mapping as `to_truck` so grips + // sit exactly on the rendered polygon vertices. self.clip_boundary_vertices .iter() .enumerate() .map(|(i, v)| { - let wx = (self.u_vector.x * v.x * self.size.x - + self.v_vector.x * v.y * self.size.y) as f32; - let wy = (self.u_vector.y * v.x * self.size.x - + self.v_vector.y * v.y * self.size.y) as f32; - let wz = (self.u_vector.z * v.x * self.size.x - + self.v_vector.z * v.y * self.size.y) as f32; + let cx = v.x + self.size.x * 0.5; + let cy = self.size.y * 0.5 - v.y; + let wx = (self.u_vector.x * cx + self.v_vector.x * cy) as f32; + let wy = (self.u_vector.y * cx + self.v_vector.y * cy) as f32; + let wz = (self.u_vector.z * cx + self.v_vector.z * cy) as f32; if i == 0 { square_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz)) } else { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 146e75c6..0ff9fe2d 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2744,16 +2744,35 @@ impl Scene { if is_polygon { let ox = (wo.insertion_point.x - wox) as f32; let oy = (wo.insertion_point.y - woy) as f32; - wo.clip_boundary_vertices + // DXF clip vertices live in image-pixel space, centred on the + // image (range −size/2 … +size/2). Image-bottom-left → insertion, + // image-y-axis points DOWN (per the DXF "v_vector points down the + // image" convention), so map: + // x_off = (clip.x + size.x/2) × u_vec + // y_off = (size.y/2 − clip.y) × v_vec ← y flipped + 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 mut poly: Vec<[f32; 2]> = wo + .clip_boundary_vertices .iter() .map(|v| { - let wx = - (wo.u_vector.x * v.x * wo.size.x + wo.v_vector.x * v.y * wo.size.y) as f32; - let wy = - (wo.u_vector.y * v.x * wo.size.x + wo.v_vector.y * v.y * wo.size.y) as f32; + let cx = cx_of(v); + let cy = cy_of(v); + 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; [ox + wx, oy + wy] }) - .collect() + .collect(); + // Close the loop: the GPU `in_polygon` ray-cast walks + // sequential pairs and doesn't wrap, so without an explicit + // closing vertex the last edge (vN-1 → v0) is never tested and + // the fill bleeds far past the boundary. + if let Some(&first) = poly.first() { + if poly.last() != Some(&first) { + poly.push(first); + } + } + poly } else { // Rectangular boundary from 4 corners. let ox = (wo.insertion_point.x - wox) as f32;