forked from 1b-agent/opencadstudio-upstream
fix(export): keep plot coordinates in f64 until they cancel
A UTM drawing plotted deformed — both geometry and text — while the same drawing near the origin came out clean. Every pass of the exporter narrowed an absolute world coordinate to f32 before the offset had a chance to cancel it, and f32 resolution at UTM is ~0.03 m on easting and ~0.5 m on northing, so the whole plot snapped onto that grid. Four places did it, all now folding in f64 and narrowing only once the result is a single-digit sheet-mm value: - The wire pass never read `points_low` at all — the word did not appear in the file — so it plotted the double-single "high" half alone and dropped every residual. - emit_hatch cast the f64 `world_origin` to f32, re-introducing at the last step exactly the quantisation the boundary-relative encoding exists to avoid. - pattern_segments rasterised entirely in absolute f32. The pattern is anchored at world (0,0), so a hatch at UTM runs a line index k of ~1e7: `origin + k · step` and the `edge − line` intersections both fall apart there. k also becomes i64 — `as i32` saturates silently, which would collapse the range to a bogus run from i32::MIN rather than drop the fill. - emit_text folded `pos + pos_low` in f32, which rounds straight back to `pos`: the residual the pair exists to carry was discarded at precisely the coordinates it was carried for. The offsets themselves were also narrowed by the callers, so the value meant to cancel a UTM coordinate was quantised before it could. Widen ox/oy to f64 through export_pdf, build_pdf, print_wires and the two plot-param builders; printing shared the same path and the same bug. Pre-existing, not from #390 — that change only routed text through the same faulty pipe the wires were already in. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
5018963d3d
commit
f452ae9211
4 files changed
+120
-75
No files matched your search
+16
-12
@@ -1243,8 +1243,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
wipeouts.as_slice(),
|
||||
eff_w,
|
||||
eff_h,
|
||||
draw_ox as f32,
|
||||
draw_oy as f32,
|
||||
draw_ox,
|
||||
draw_oy,
|
||||
rotation_deg,
|
||||
1.0,
|
||||
None,
|
||||
@@ -1313,8 +1313,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
// scale (rotation_deg == 0 adds no further CTM translation).
|
||||
// window_to_sheet's (ox, oy) is the sheet-mm target for the window's
|
||||
// min corner, so: scale * (x0 + offset_x) = ox => offset_x = ox/scale - x0.
|
||||
let offset_x = ((ox / scale) - x0) as f32;
|
||||
let offset_y = ((oy / scale) - y0) as f32;
|
||||
let offset_x = (ox / scale) - x0;
|
||||
let offset_y = (oy / scale) - y0;
|
||||
// Clip rect in pre-scale space (build_pdf's CTM applies `scale` to it,
|
||||
// same as the wires) so the final sheet-mm rect lands at (ox, oy).
|
||||
let clip = Some(((ox / scale) as f32, (oy / scale) as f32, win_w as f32, win_h as f32));
|
||||
@@ -1359,8 +1359,10 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
f64,
|
||||
f64,
|
||||
f32,
|
||||
f32,
|
||||
// Draw offsets stay f64: they cancel an absolute world coordinate that
|
||||
// an f32 cannot hold at UTM scale.
|
||||
f64,
|
||||
f64,
|
||||
i32,
|
||||
) {
|
||||
let i = self.active_tab;
|
||||
@@ -1411,8 +1413,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
wipeouts,
|
||||
eff_w,
|
||||
eff_h,
|
||||
draw_ox as f32,
|
||||
draw_oy as f32,
|
||||
draw_ox,
|
||||
draw_oy,
|
||||
rotation_deg,
|
||||
)
|
||||
}
|
||||
@@ -2076,8 +2078,10 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
f64,
|
||||
f64,
|
||||
f32,
|
||||
f32,
|
||||
// Offsets stay f64 (they cancel a UTM-scale world coordinate); the plot
|
||||
// scale is a small ratio and stays f32.
|
||||
f64,
|
||||
f64,
|
||||
f32,
|
||||
Option<(f32, f32, f32, f32)>,
|
||||
)> {
|
||||
@@ -2113,8 +2117,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
.collect();
|
||||
let hatches: Vec<_> = scene.paper_canvas_hatches().as_ref().clone();
|
||||
let wipeouts: Vec<_> = scene.paper_canvas_wipeouts().as_ref().clone();
|
||||
let offset_x = ((ox / scale) - x0) as f32;
|
||||
let offset_y = ((oy / scale) - y0) as f32;
|
||||
let offset_x = (ox / scale) - x0;
|
||||
let offset_y = (oy / scale) - y0;
|
||||
let clip = Some((
|
||||
(ox / scale) as f32,
|
||||
(oy / scale) as f32,
|
||||
|
||||
+54
-22
@@ -32,8 +32,8 @@ pub fn export_pdf(
|
||||
_wipeouts: &[HatchModel],
|
||||
_paper_w: f64,
|
||||
_paper_h: f64,
|
||||
_offset_x: f32,
|
||||
_offset_y: f32,
|
||||
_offset_x: f64,
|
||||
_offset_y: f64,
|
||||
_rotation_deg: i32,
|
||||
_scale: f32,
|
||||
_clip: Option<(f32, f32, f32, f32)>,
|
||||
@@ -72,8 +72,8 @@ pub fn export_pdf(
|
||||
wipeouts: &[HatchModel],
|
||||
paper_w: f64,
|
||||
paper_h: f64,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
offset_x: f64,
|
||||
offset_y: f64,
|
||||
rotation_deg: i32,
|
||||
scale: f32,
|
||||
clip: Option<(f32, f32, f32, f32)>,
|
||||
@@ -119,8 +119,11 @@ fn build_pdf(
|
||||
wipeouts: &[HatchModel],
|
||||
paper_w: f32,
|
||||
paper_h: f32,
|
||||
ox: f32,
|
||||
oy: f32,
|
||||
// Absolute-world offsets, kept in f64: at UTM the drawing sits at ~5e5/4.5e6
|
||||
// where an f32 has ~0.03 m / ~0.5 m of resolution, so an f32 offset is itself
|
||||
// already quantised before it can cancel the coordinate it is meant to cancel.
|
||||
ox: f64,
|
||||
oy: f64,
|
||||
rotation_deg: i32,
|
||||
scale: f32,
|
||||
clip: Option<(f32, f32, f32, f32)>,
|
||||
@@ -306,15 +309,24 @@ fn build_pdf(
|
||||
last_dash = Some(dash_arr);
|
||||
}
|
||||
|
||||
// Emit segments (NaN = pen-up).
|
||||
// Emit segments (NaN = pen-up). Points are the "high" half of a
|
||||
// double-single pair; fold in the `points_low` residual and cancel the
|
||||
// offset in f64 before narrowing. Dropping the residual (or narrowing
|
||||
// first) snaps a UTM drawing onto the f32 grid — ~3 cm across, ~50 cm
|
||||
// along northing — which is exactly the distortion the plot showed while
|
||||
// low-coordinate drawings came out clean. The result is a sheet-mm value
|
||||
// in single digits, so f32 is lossless from here.
|
||||
let mut segment: Vec<LinePoint> = Vec::new();
|
||||
for &[x, y, _z] in &wire.points {
|
||||
for (pi, &[x, y, _z]) in wire.points.iter().enumerate() {
|
||||
if x.is_nan() || y.is_nan() {
|
||||
flush_line(&mut ops, &segment);
|
||||
segment.clear();
|
||||
} else {
|
||||
let lo = wire.points_low.get(pi).copied().unwrap_or([0.0; 3]);
|
||||
let wx = (x as f64 + lo[0] as f64 + ox) as f32;
|
||||
let wy = (y as f64 + lo[1] as f64 + oy) as f32;
|
||||
segment.push(LinePoint {
|
||||
p: Point::new(Mm(x + ox), Mm(y + oy)),
|
||||
p: Point::new(Mm(wx), Mm(wy)),
|
||||
bezier: false,
|
||||
});
|
||||
}
|
||||
@@ -384,7 +396,7 @@ fn flush_line(ops: &mut Vec<Op>, pts: &[LinePoint]) {
|
||||
/// Mirrors `scene::paper_canvas::draw_hatch`: solid → fill, pattern → outline,
|
||||
/// gradient → solid fill of the averaged colour.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
||||
fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f64, oy: f64) {
|
||||
if hatch.boundary.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -411,8 +423,11 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
||||
b = 0.50;
|
||||
}
|
||||
}
|
||||
let world_ox = hatch.world_origin[0] as f32;
|
||||
let world_oy = hatch.world_origin[1] as f32;
|
||||
// `boundary` holds f32 offsets from the f64 `world_origin`, so resolve the
|
||||
// pair in f64 and only narrow once the offset has cancelled — casting
|
||||
// `world_origin` to f32 first re-introduces the ~0.5 m UTM quantisation the
|
||||
// boundary-relative encoding exists to avoid.
|
||||
let (world_ox, world_oy) = (hatch.world_origin[0], hatch.world_origin[1]);
|
||||
|
||||
// Split the boundary into rings on every NaN-NaN separator.
|
||||
let mut rings: Vec<PolygonRing> = Vec::new();
|
||||
@@ -426,8 +441,10 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let px = (bx as f64 + world_ox + ox) as f32;
|
||||
let py = (by as f64 + world_oy + oy) as f32;
|
||||
current.push(LinePoint {
|
||||
p: Point::new(Mm(bx + world_ox + ox), Mm(by + world_oy + oy)),
|
||||
p: Point::new(Mm(px), Mm(py)),
|
||||
bezier: false,
|
||||
});
|
||||
}
|
||||
@@ -475,15 +492,19 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
||||
}),
|
||||
});
|
||||
for [a, b_pt] in segments {
|
||||
// `pattern_segments` returns absolute world f64; cancel the offset
|
||||
// before narrowing, as everywhere else in this file.
|
||||
let (ax, ay) = ((a[0] + ox) as f32, (a[1] + oy) as f32);
|
||||
let (bx, by) = ((b_pt[0] + ox) as f32, (b_pt[1] + oy) as f32);
|
||||
ops.push(Op::DrawLine {
|
||||
line: Line {
|
||||
points: vec![
|
||||
LinePoint {
|
||||
p: Point::new(Mm(a[0] + ox), Mm(a[1] + oy)),
|
||||
p: Point::new(Mm(ax), Mm(ay)),
|
||||
bezier: false,
|
||||
},
|
||||
LinePoint {
|
||||
p: Point::new(Mm(b_pt[0] + ox), Mm(b_pt[1] + oy)),
|
||||
p: Point::new(Mm(bx), Mm(by)),
|
||||
bezier: false,
|
||||
},
|
||||
],
|
||||
@@ -517,9 +538,16 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
||||
// ── Text (SDF glyph quads → vector strokes / fills) ────────────────────────
|
||||
|
||||
/// Absolute world XY of a glyph vertex (double-single high + low parts folded).
|
||||
///
|
||||
/// The fold must happen in f64: the pair exists because the absolute coordinate
|
||||
/// does not fit an f32, so `pos + pos_low` evaluated in f32 rounds straight back
|
||||
/// to `pos` and throws away the residual it was carrying.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn glyph_world_xy(v: &crate::scene::pipeline::text_gpu::TextVertex) -> [f32; 2] {
|
||||
[v.pos[0] + v.pos_low[0], v.pos[1] + v.pos_low[1]]
|
||||
fn glyph_world_xy(v: &crate::scene::pipeline::text_gpu::TextVertex) -> [f64; 2] {
|
||||
[
|
||||
v.pos[0] as f64 + v.pos_low[0] as f64,
|
||||
v.pos[1] as f64 + v.pos_low[1] as f64,
|
||||
]
|
||||
}
|
||||
|
||||
/// Adapt a text colour to the white sheet, mirroring the wire/hatch passes:
|
||||
@@ -550,8 +578,8 @@ fn adapt_text_color([r, g, b]: [f32; 3]) -> [f32; 3] {
|
||||
fn emit_text(
|
||||
ops: &mut Vec<Op>,
|
||||
wires: &[WireModel],
|
||||
ox: f32,
|
||||
oy: f32,
|
||||
ox: f64,
|
||||
oy: f64,
|
||||
scale: f32,
|
||||
plot_style: Option<&PlotStyleTable>,
|
||||
) {
|
||||
@@ -621,18 +649,22 @@ fn emit_text(
|
||||
// `tl` carries uv = (uv_min.x, uv_min.y) — the atlas tile key.
|
||||
let key = sdf_atlas::uv_key([quad[5].uv[0], quad[5].uv[1]]);
|
||||
|
||||
let point = |wx: f32, wy: f32| Point::new(Mm(wx + ox), Mm(wy + oy));
|
||||
// Cancel the offset in f64, then narrow: the sheet-mm result is a
|
||||
// small number even when the world coordinate is UTM-scale.
|
||||
let point = |wx: f64, wy: f64| Point::new(Mm((wx + ox) as f32), Mm((wy + oy) as f32));
|
||||
|
||||
if let Some(ge) = table.get(&key) {
|
||||
// Affine basis of the quad: plane_min → bl, +x → br, +y → tl.
|
||||
// The glyph-space maths is small and stays f32; only the lift into
|
||||
// world coordinates needs f64.
|
||||
let (pmin, pmax) = (ge.plane_min, ge.plane_max);
|
||||
let (sx, sy) = (pmax[0] - pmin[0], pmax[1] - pmin[1]);
|
||||
if sx.abs() < 1e-9 || sy.abs() < 1e-9 {
|
||||
continue;
|
||||
}
|
||||
let map = |p: [f32; 2]| -> Point {
|
||||
let u = (p[0] - pmin[0]) / sx;
|
||||
let v = (p[1] - pmin[1]) / sy;
|
||||
let u = ((p[0] - pmin[0]) / sx) as f64;
|
||||
let v = ((p[1] - pmin[1]) / sy) as f64;
|
||||
let wx = bl[0] + u * (br[0] - bl[0]) + v * (tl[0] - bl[0]);
|
||||
let wy = bl[1] + u * (br[1] - bl[1]) + v * (tl[1] - bl[1]);
|
||||
point(wx, wy)
|
||||
|
||||
@@ -41,8 +41,8 @@ pub async fn print_wires(
|
||||
_wipeouts: Vec<HatchModel>,
|
||||
_paper_w: f64,
|
||||
_paper_h: f64,
|
||||
_offset_x: f32,
|
||||
_offset_y: f32,
|
||||
_offset_x: f64,
|
||||
_offset_y: f64,
|
||||
_rotation_deg: i32,
|
||||
_plot_style: Option<PlotStyleTable>,
|
||||
) -> Result<String, String> {
|
||||
@@ -62,8 +62,8 @@ pub async fn print_wires_with(
|
||||
_wipeouts: Vec<HatchModel>,
|
||||
_paper_w: f64,
|
||||
_paper_h: f64,
|
||||
_offset_x: f32,
|
||||
_offset_y: f32,
|
||||
_offset_x: f64,
|
||||
_offset_y: f64,
|
||||
_rotation_deg: i32,
|
||||
_plot_style: Option<PlotStyleTable>,
|
||||
_opts: PrintOptions,
|
||||
@@ -92,8 +92,8 @@ pub async fn print_wires(
|
||||
wipeouts: Vec<HatchModel>,
|
||||
paper_w: f64,
|
||||
paper_h: f64,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
offset_x: f64,
|
||||
offset_y: f64,
|
||||
rotation_deg: i32,
|
||||
plot_style: Option<PlotStyleTable>,
|
||||
) -> Result<String, String> {
|
||||
@@ -155,8 +155,8 @@ pub async fn print_wires_with(
|
||||
wipeouts: Vec<HatchModel>,
|
||||
paper_w: f64,
|
||||
paper_h: f64,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
offset_x: f64,
|
||||
offset_y: f64,
|
||||
rotation_deg: i32,
|
||||
plot_style: Option<PlotStyleTable>,
|
||||
opts: PrintOptions,
|
||||
|
||||
@@ -96,26 +96,30 @@ impl HatchModel {
|
||||
/// `paper_canvas`, print preview) can draw the actual pattern instead
|
||||
/// of just the outline.
|
||||
///
|
||||
/// Coordinate frame: each emitted segment is in the same
|
||||
/// boundary-relative WCS that callers already use, i.e. `world_origin +
|
||||
/// boundary[i]`. Solid / gradient hatches return an empty vec —
|
||||
/// callers fall back to their solid-fill path.
|
||||
pub fn pattern_segments(&self) -> Vec<[[f32; 2]; 2]> {
|
||||
/// Coordinate frame: each emitted segment is absolute WCS in f64, i.e.
|
||||
/// `world_origin + boundary[i]` resolved. Solid / gradient hatches return an
|
||||
/// empty vec — callers fall back to their solid-fill path.
|
||||
///
|
||||
/// The whole rasterisation is f64 because the pattern is anchored at world
|
||||
/// (0, 0): a hatch at UTM sits ~5e5 away from that anchor, so the line index
|
||||
/// `k` runs to ~1e7 and `origin + k · step` in f32 quantises the family lines
|
||||
/// into visible garbage, as does the `edge − line` cancellation below.
|
||||
pub fn pattern_segments(&self) -> Vec<[[f64; 2]; 2]> {
|
||||
let HatchPattern::Pattern(families) = &self.pattern else {
|
||||
return Vec::new();
|
||||
};
|
||||
if self.boundary.is_empty() || families.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let ox = self.world_origin[0] as f32;
|
||||
let oy = self.world_origin[1] as f32;
|
||||
let ox = self.world_origin[0];
|
||||
let oy = self.world_origin[1];
|
||||
|
||||
// ── Build edge list from boundary, splitting on NaN sentinels.
|
||||
// Each sub-path is closed (last → first edge) so even-odd
|
||||
// inside-tests work for islands / holes.
|
||||
let mut edges: Vec<([f32; 2], [f32; 2])> = Vec::new();
|
||||
let mut sub_start: Option<[f32; 2]> = None;
|
||||
let mut prev: Option<[f32; 2]> = None;
|
||||
let mut edges: Vec<([f64; 2], [f64; 2])> = Vec::new();
|
||||
let mut sub_start: Option<[f64; 2]> = None;
|
||||
let mut prev: Option<[f64; 2]> = None;
|
||||
for &[bx, by] in self.boundary.iter() {
|
||||
if bx.is_nan() || by.is_nan() {
|
||||
if let (Some(s), Some(p)) = (sub_start, prev) {
|
||||
@@ -127,7 +131,7 @@ impl HatchModel {
|
||||
prev = None;
|
||||
continue;
|
||||
}
|
||||
let pt = [bx + ox, by + oy];
|
||||
let pt = [bx as f64 + ox, by as f64 + oy];
|
||||
match (sub_start, prev) {
|
||||
(None, _) => {
|
||||
sub_start = Some(pt);
|
||||
@@ -150,10 +154,10 @@ impl HatchModel {
|
||||
}
|
||||
|
||||
// ── AABB of the boundary in world coords.
|
||||
let mut min_x = f32::INFINITY;
|
||||
let mut max_x = f32::NEG_INFINITY;
|
||||
let mut min_y = f32::INFINITY;
|
||||
let mut max_y = f32::NEG_INFINITY;
|
||||
let mut min_x = f64::INFINITY;
|
||||
let mut max_x = f64::NEG_INFINITY;
|
||||
let mut min_y = f64::INFINITY;
|
||||
let mut max_y = f64::NEG_INFINITY;
|
||||
for &(a, b) in &edges {
|
||||
for [x, y] in [a, b] {
|
||||
min_x = min_x.min(x);
|
||||
@@ -163,25 +167,29 @@ impl HatchModel {
|
||||
}
|
||||
}
|
||||
|
||||
let scale = self.scale.max(1e-6);
|
||||
let angle_offset = self.angle_offset;
|
||||
let mut segments: Vec<[[f32; 2]; 2]> = Vec::new();
|
||||
let scale = self.scale.max(1e-6) as f64;
|
||||
let angle_offset = self.angle_offset as f64;
|
||||
let mut segments: Vec<[[f64; 2]; 2]> = Vec::new();
|
||||
|
||||
// Hard cap to keep pathological patterns / huge boundaries bounded.
|
||||
const MAX_LINES_PER_FAMILY: i32 = 4096;
|
||||
// `k` is i64: at UTM with a fine spacing the index legitimately reaches
|
||||
// ~1e7 and an out-of-range `as i32` saturates silently, collapsing the
|
||||
// range to a bogus run of lines from i32::MIN.
|
||||
const MAX_LINES_PER_FAMILY: i64 = 4096;
|
||||
const MAX_SEGMENTS_TOTAL: usize = 200_000;
|
||||
|
||||
let cos_off = angle_offset.cos();
|
||||
let sin_off = angle_offset.sin();
|
||||
for family in families {
|
||||
let angle = family.angle_deg.to_radians() + angle_offset;
|
||||
let angle = (family.angle_deg as f64).to_radians() + angle_offset;
|
||||
let cos_a = angle.cos();
|
||||
let sin_a = angle.sin();
|
||||
// PAT local frame: dx = along-line phase, dy = perpendicular
|
||||
// spacing. Lines step in world by k · (dx, dy)_local rotated
|
||||
// into the family's frame.
|
||||
let step_x = (family.dx * cos_a - family.dy * sin_a) * scale;
|
||||
let step_y = (family.dx * sin_a + family.dy * cos_a) * scale;
|
||||
let (fdx, fdy) = (family.dx as f64, family.dy as f64);
|
||||
let step_x = (fdx * cos_a - fdy * sin_a) * scale;
|
||||
let step_y = (fdx * sin_a + fdy * cos_a) * scale;
|
||||
let perp_x = -sin_a;
|
||||
let perp_y = cos_a;
|
||||
let step_perp = step_x * perp_x + step_y * perp_y;
|
||||
@@ -195,12 +203,13 @@ impl HatchModel {
|
||||
// same convention as the GPU shader, so PAT patterns whose
|
||||
// `x0/y0` are non-zero (e.g. brick offsets) line up with the
|
||||
// on-screen render.
|
||||
let (fx0, fy0) = (family.x0 as f64, family.y0 as f64);
|
||||
let origin = [
|
||||
(family.x0 * cos_off - family.y0 * sin_off) * scale,
|
||||
(family.x0 * sin_off + family.y0 * cos_off) * scale,
|
||||
(fx0 * cos_off - fy0 * sin_off) * scale,
|
||||
(fx0 * sin_off + fy0 * cos_off) * scale,
|
||||
];
|
||||
let mut p_min = f32::INFINITY;
|
||||
let mut p_max = f32::NEG_INFINITY;
|
||||
let mut p_min = f64::INFINITY;
|
||||
let mut p_max = f64::NEG_INFINITY;
|
||||
for &[cx, cy] in &[
|
||||
[min_x, min_y],
|
||||
[max_x, min_y],
|
||||
@@ -211,8 +220,8 @@ impl HatchModel {
|
||||
p_min = p_min.min(p);
|
||||
p_max = p_max.max(p);
|
||||
}
|
||||
let mut k_lo = (p_min / step_perp).floor() as i32 - 1;
|
||||
let mut k_hi = (p_max / step_perp).ceil() as i32 + 1;
|
||||
let mut k_lo = (p_min / step_perp).floor() as i64 - 1;
|
||||
let mut k_hi = (p_max / step_perp).ceil() as i64 + 1;
|
||||
if k_lo > k_hi {
|
||||
std::mem::swap(&mut k_lo, &mut k_hi);
|
||||
}
|
||||
@@ -226,21 +235,21 @@ impl HatchModel {
|
||||
k_hi = k_lo.saturating_add(MAX_LINES_PER_FAMILY);
|
||||
}
|
||||
|
||||
let period: f32 = family.dashes.iter().map(|d| d.abs()).sum::<f32>() * scale;
|
||||
let period: f64 = family.dashes.iter().map(|d| d.abs() as f64).sum::<f64>() * scale;
|
||||
let has_dashes = !family.dashes.is_empty() && period > 1e-6;
|
||||
|
||||
for k in k_lo..=k_hi {
|
||||
if segments.len() >= MAX_SEGMENTS_TOTAL {
|
||||
return segments;
|
||||
}
|
||||
let kf = k as f32;
|
||||
let kf = k as f64;
|
||||
let lx = origin[0] + kf * step_x;
|
||||
let ly = origin[1] + kf * step_y;
|
||||
|
||||
// Intersect line P(t) = L + t·(cos_a, sin_a) against each
|
||||
// boundary edge; collect t-values where the edge actually
|
||||
// crosses (s ∈ [0,1]).
|
||||
let mut ts: Vec<f32> = Vec::with_capacity(8);
|
||||
let mut ts: Vec<f64> = Vec::with_capacity(8);
|
||||
for &(a, b) in &edges {
|
||||
let ex = b[0] - a[0];
|
||||
let ey = b[1] - a[1];
|
||||
@@ -285,7 +294,7 @@ impl HatchModel {
|
||||
// a dot — rendered as a short mark so dot patterns
|
||||
// (e.g. DOTS) are visible instead of drawing nothing.
|
||||
let n = family.dashes.len();
|
||||
let dot_len = (period * 0.06).max(1e-3);
|
||||
let dot_len: f64 = (period * 0.06).max(1e-3);
|
||||
let phase = t0.rem_euclid(period);
|
||||
// Start at the period boundary at or before t0; the
|
||||
// span clip below drops anything before t0.
|
||||
@@ -294,7 +303,7 @@ impl HatchModel {
|
||||
let max_iters = (((t1 - t0) / period).ceil() as usize + 2) * n + 8;
|
||||
let mut iters = 0usize;
|
||||
while seg_t < t1 && iters < max_iters {
|
||||
let d = family.dashes[idx];
|
||||
let d = family.dashes[idx] as f64;
|
||||
let dl = d.abs() * scale;
|
||||
if d > 0.0 {
|
||||
let a = seg_t.max(t0);
|
||||
|
||||
Reference in new issue
Block a user