forked from 1b-agent/opencadstudio-upstream
refactor: remove block_cache local_offset (fully absolute + double-single)
With the relative-to-eye double-single render path, the per-defn `local_offset` precision origin is redundant: block geometry can be stored absolute and stays precise via the high/low residual. Remove it end-to-end: - Drop the Pass-1 centroid computation, the BlockDefn.local_offset field, and the `defn_lo` parameter threaded through emit_wire / emit_greeked_text / emit_text_baseline / transform_tangent and the AABB helper (transform_offset_aabb_xy → transform_aabb_xy), plus the nested parent/child offset re-basing. - Store text_obb_local as absolute f64 (was offset-relative f32) so the greek rectangles stay precise; emit splits to double-single. Give the greek-baseline points a low residual too. Geometry is now absolute + double-single throughout the block cache, matching the top-level path. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
62c7e20f42
commit
bbf148f601
1 file changed
+82
-266
Vendored
+82
-266
@@ -65,7 +65,7 @@ pub struct LocalWire {
|
||||
/// (rotation, anchor offsets and width-approximation already applied).
|
||||
/// Emitted at greek time so the rect matches the text's orientation
|
||||
/// instead of falling back to the axis-aligned bbox.
|
||||
pub text_obb_local: Option<[[f32; 3]; 4]>,
|
||||
pub text_obb_local: Option<[[f64; 3]; 4]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -102,16 +102,8 @@ pub struct BlockDefn {
|
||||
/// Union of every sub's local AABB (including nested-INSERT contributions
|
||||
/// resolved at expand time via their own defn's `aabb_local`). XY only —
|
||||
/// the wire renderer is 2D-dominant. Expressed in this defn's *offset*
|
||||
/// frame (i.e. `local_offset` already subtracted).
|
||||
/// Absolute world-space XY (the double-single render path keeps it precise).
|
||||
pub aabb_local: [f32; 4],
|
||||
/// f64 constant subtracted from every coordinate stored inside this
|
||||
/// defn (points, AABBs, text OBB corners, fill_tris, snap_pts, …).
|
||||
/// Picks an origin near the defn's geometric centre so f32 storage
|
||||
/// retains millimetre precision even when the source content lives at
|
||||
/// large coordinates (xref data authored at UTM, MGRS, etc.). When the
|
||||
/// defn is expanded the offset is re-added in f64 BEFORE the Insert
|
||||
/// transform is applied — that's where precision matters.
|
||||
pub local_offset: [f64; 3],
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -200,27 +192,14 @@ impl BlockCache {
|
||||
let Some(defn) = self.defns.get(block_name) else {
|
||||
return [0.0, 0.0, 0.0, 0.0];
|
||||
};
|
||||
let parent_lo = defn.local_offset;
|
||||
visited.push(block_name.to_string());
|
||||
let mut acc = [0.0_f32, 0.0, 0.0, 0.0];
|
||||
for sub in &defn.subs {
|
||||
let aabb = match sub {
|
||||
LocalSub::Wire(lw) => lw.aabb_local,
|
||||
LocalSub::Nested(nref) => {
|
||||
let nested_local = self.defn_aabb_recursive(&nref.block_name, visited);
|
||||
let child_lo = self
|
||||
.defns
|
||||
.get(&nref.block_name)
|
||||
.map(|d| d.local_offset)
|
||||
.unwrap_or([0.0; 3]);
|
||||
let parent_native =
|
||||
transform_offset_aabb_xy(nested_local, child_lo, &nref.xform);
|
||||
[
|
||||
parent_native[0] - parent_lo[0] as f32,
|
||||
parent_native[1] - parent_lo[1] as f32,
|
||||
parent_native[2] - parent_lo[0] as f32,
|
||||
parent_native[3] - parent_lo[1] as f32,
|
||||
]
|
||||
let nested = self.defn_aabb_recursive(&nref.block_name, visited);
|
||||
transform_aabb_xy(nested, &nref.xform)
|
||||
}
|
||||
};
|
||||
acc = aabb_union(acc, aabb);
|
||||
@@ -281,85 +260,6 @@ fn build_defn(
|
||||
None => return BlockDefn::default(),
|
||||
};
|
||||
|
||||
// ── Pass 1: compute a defn-level offset near the geometric centre.
|
||||
// Falling back to [0,0,0] for empty / nested-only defns is fine —
|
||||
// precision is only at risk when stored points have large magnitudes,
|
||||
// and those come from direct wire entities. Walks Insert sub-entities
|
||||
// too: nested Inserts' insertion_point contribute to the centroid so a
|
||||
// composite block whose only direct children are Inserts still gets a
|
||||
// useful offset.
|
||||
//
|
||||
// Bbox values that aren't finite (corrupt entities) or that exceed
|
||||
// `SANE_EXTENT` are dropped — otherwise a single bad entity would
|
||||
// poison the centroid for the entire block. Tracked per-axis so a
|
||||
// missing-z entity doesn't taint the x/y choice (would have produced
|
||||
// `INFINITY + NEG_INFINITY = NaN` otherwise).
|
||||
let mut min = [f64::INFINITY; 3];
|
||||
let mut max = [f64::NEG_INFINITY; 3];
|
||||
let mut have = [false; 3];
|
||||
for &eh in &br.entity_handles {
|
||||
let Some(entity) = doc.get_entity(eh) else {
|
||||
continue;
|
||||
};
|
||||
if matches!(
|
||||
entity,
|
||||
EntityType::Block(_)
|
||||
| EntityType::BlockEnd(_)
|
||||
| EntityType::AttributeDefinition(_)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Invisible sub-entities never render (see pass 2), so keep them out
|
||||
// of the offset centroid too — otherwise a dynamic block's hidden
|
||||
// states would drag the precision origin off the visible geometry.
|
||||
if entity.common().invisible {
|
||||
continue;
|
||||
}
|
||||
// For Inserts, take the insertion_point itself (acadrust's
|
||||
// `Insert::bounding_box` returns just that). One level deep is
|
||||
// enough for an offset-picker — nested Inserts inside that block
|
||||
// contribute via their own defns.
|
||||
let (bmin, bmax) = match entity {
|
||||
EntityType::Insert(ins) => (ins.insert_point, ins.insert_point),
|
||||
_ => {
|
||||
let bb = entity.as_entity().bounding_box();
|
||||
(bb.min, bb.max)
|
||||
}
|
||||
};
|
||||
// Skip the "placeholder ZERO bbox" that empty Polyline / Hatch /
|
||||
// Spline / Mesh / etc. return when they have no vertices —
|
||||
// including these would pull the centroid toward origin and ruin
|
||||
// the precision-preservation for UTM-authored content.
|
||||
let is_zero_placeholder = bmin.x == 0.0
|
||||
&& bmin.y == 0.0
|
||||
&& bmin.z == 0.0
|
||||
&& bmax.x == 0.0
|
||||
&& bmax.y == 0.0
|
||||
&& bmax.z == 0.0;
|
||||
if is_zero_placeholder {
|
||||
continue;
|
||||
}
|
||||
let lo_arr = [bmin.x, bmin.y, bmin.z];
|
||||
let hi_arr = [bmax.x, bmax.y, bmax.z];
|
||||
for i in 0..3 {
|
||||
let lo = lo_arr[i];
|
||||
let hi = hi_arr[i];
|
||||
if !lo.is_finite() || !hi.is_finite() {
|
||||
continue;
|
||||
}
|
||||
if lo.abs() > SANE_EXTENT || hi.abs() > SANE_EXTENT {
|
||||
continue;
|
||||
}
|
||||
if lo < min[i] { min[i] = lo }
|
||||
if hi > max[i] { max[i] = hi }
|
||||
have[i] = true;
|
||||
}
|
||||
}
|
||||
let local_offset: [f64; 3] = [
|
||||
if have[0] { (min[0] + max[0]) * 0.5 } else { 0.0 },
|
||||
if have[1] { (min[1] + max[1]) * 0.5 } else { 0.0 },
|
||||
if have[2] { (min[2] + max[2]) * 0.5 } else { 0.0 },
|
||||
];
|
||||
|
||||
// ── Pass 2: tessellate each sub with the chosen offset so stored
|
||||
// coordinates fit into f32 without precision loss.
|
||||
@@ -393,7 +293,7 @@ fn build_defn(
|
||||
}
|
||||
_ => {
|
||||
if let Some(lw) =
|
||||
tessellate_sub_local(doc, entity, anno_scale, bg_color, local_offset)
|
||||
tessellate_sub_local(doc, entity, anno_scale, bg_color)
|
||||
{
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
@@ -403,7 +303,6 @@ fn build_defn(
|
||||
BlockDefn {
|
||||
subs,
|
||||
aabb_local: [0.0; 4],
|
||||
local_offset,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +344,6 @@ fn tessellate_sub_local(
|
||||
sub: &EntityType,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
local_offset: [f64; 3],
|
||||
) -> Option<LocalWire> {
|
||||
let h = sub.common().handle;
|
||||
|
||||
@@ -502,54 +400,8 @@ fn tessellate_sub_local(
|
||||
return None;
|
||||
}
|
||||
|
||||
// Block-defn geometry is stored relative to `local_offset` (re-added in f64
|
||||
// at expand time) so the f32 high half stays precise at UTM-scale source
|
||||
// coordinates. `tessellate()` emits absolute coordinates, so subtract the
|
||||
// offset here and re-split the double-single residual.
|
||||
if local_offset != [0.0_f64; 3] {
|
||||
let [lo_x, lo_y, lo_z] = local_offset;
|
||||
let resplit = |pts: &mut Vec<[f32; 3]>, low: &mut Vec<[f32; 3]>| {
|
||||
let mut new_low = Vec::with_capacity(pts.len());
|
||||
for (i, p) in pts.iter_mut().enumerate() {
|
||||
if p[0].is_nan() {
|
||||
new_low.push([0.0; 3]);
|
||||
continue;
|
||||
}
|
||||
let l = low.get(i).copied().unwrap_or([0.0; 3]);
|
||||
let (hx, lx) = WireModel::split_ds(p[0] as f64 + l[0] as f64 - lo_x);
|
||||
let (hy, ly) = WireModel::split_ds(p[1] as f64 + l[1] as f64 - lo_y);
|
||||
let (hz, lz) = WireModel::split_ds(p[2] as f64 + l[2] as f64 - lo_z);
|
||||
*p = [hx, hy, hz];
|
||||
new_low.push([lx, ly, lz]);
|
||||
}
|
||||
*low = new_low;
|
||||
};
|
||||
resplit(&mut wire.points, &mut wire.points_low);
|
||||
resplit(&mut wire.fill_tris, &mut wire.fill_tris_low);
|
||||
for kv in &mut wire.key_vertices {
|
||||
kv[0] -= lo_x;
|
||||
kv[1] -= lo_y;
|
||||
kv[2] -= lo_z;
|
||||
}
|
||||
for (p, _) in &mut wire.snap_pts {
|
||||
p.x -= lo_x;
|
||||
p.y -= lo_y;
|
||||
p.z -= lo_z;
|
||||
}
|
||||
let (lxf, lyf, lzf) = (lo_x as f32, lo_y as f32, lo_z as f32);
|
||||
for tg in &mut wire.tangent_geoms {
|
||||
match tg {
|
||||
TangentGeom::Line { p1, p2 } => {
|
||||
p1[0] -= lxf; p1[1] -= lyf; p1[2] -= lzf;
|
||||
p2[0] -= lxf; p2[1] -= lyf; p2[2] -= lzf;
|
||||
}
|
||||
TangentGeom::Circle { center, .. } => {
|
||||
center[0] -= lxf; center[1] -= lyf; center[2] -= lzf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Geometry is stored absolute; the double-single (high/low) render path
|
||||
// keeps it precise at UTM scale, so no per-defn offset is subtracted.
|
||||
let aabb_local = aabb_from_points_iter(
|
||||
wire.points.iter().copied().chain(wire.fill_tris.iter().copied()),
|
||||
);
|
||||
@@ -560,28 +412,20 @@ fn tessellate_sub_local(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Pre-compute the OBB corners (block-offset-frame f32) for Text / MText
|
||||
// so the greek path can emit a rect that matches the entity's rotation
|
||||
// instead of falling back to the axis-aligned `aabb_local`. Subtract the
|
||||
// defn's `local_offset` in f64 first so distant text retains precision.
|
||||
// For MText the OBB height is built from the wrap-expanded line count
|
||||
// so emit_greeked_text can split it into per-line rows.
|
||||
let [lo_x, lo_y, lo_z] = local_offset;
|
||||
// Pre-compute the OBB corners (absolute f64) for Text / MText so the greek
|
||||
// path can emit a rect that matches the entity's rotation instead of
|
||||
// falling back to the axis-aligned `aabb_local`. Kept in f64 so it stays
|
||||
// precise at UTM scale (the emit path splits to double-single). For MText
|
||||
// the OBB height is built from the wrap-expanded line count so
|
||||
// emit_greeked_text can split it into per-line rows.
|
||||
let mtext_lines = match sub {
|
||||
EntityType::MText(m) => Some(
|
||||
crate::entities::text_support::mtext_line_count(m, doc, anno_scale),
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
let text_obb_local: Option<[[f32; 3]; 4]> =
|
||||
crate::entities::text_support::text_obb_corners_native(sub, anno_scale, mtext_lines).map(|c| {
|
||||
[
|
||||
[(c[0][0] - lo_x) as f32, (c[0][1] - lo_y) as f32, (c[0][2] - lo_z) as f32],
|
||||
[(c[1][0] - lo_x) as f32, (c[1][1] - lo_y) as f32, (c[1][2] - lo_z) as f32],
|
||||
[(c[2][0] - lo_x) as f32, (c[2][1] - lo_y) as f32, (c[2][2] - lo_z) as f32],
|
||||
[(c[3][0] - lo_x) as f32, (c[3][1] - lo_y) as f32, (c[3][2] - lo_z) as f32],
|
||||
]
|
||||
});
|
||||
let text_obb_local: Option<[[f64; 3]; 4]> =
|
||||
crate::entities::text_support::text_obb_corners_native(sub, anno_scale, mtext_lines);
|
||||
|
||||
Some(LocalWire {
|
||||
points: wire.points,
|
||||
@@ -634,20 +478,16 @@ fn aabb_from_points_iter<I: IntoIterator<Item = [f32; 3]>>(pts: I) -> [f32; 4] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a local-space XY AABB by `t` and return the world-space XY AABB
|
||||
/// of the transformed corners. Re-adds an f64 `defn_lo` to each corner
|
||||
/// before transforming — when the input AABB is stored in a defn's *offset
|
||||
/// frame* (precision-preservation trick) the offset has to be applied in
|
||||
/// f64 to keep the world result accurate even for distant content. Pass
|
||||
/// `[0.0; 3]` for callers that don't use an offset frame.
|
||||
fn transform_offset_aabb_xy(local: [f32; 4], defn_lo: [f64; 3], t: &Transform) -> [f32; 4] {
|
||||
/// Transform an absolute XY AABB by `t` and return the world-space XY AABB of
|
||||
/// the transformed corners (computed in f64 so it stays accurate for distant
|
||||
/// content).
|
||||
fn transform_aabb_xy(local: [f32; 4], t: &Transform) -> [f32; 4] {
|
||||
let [x0, y0, x1, y1] = local;
|
||||
let [lo_x, lo_y, lo_z] = defn_lo;
|
||||
let corners = [
|
||||
Vector3::new(x0 as f64 + lo_x, y0 as f64 + lo_y, lo_z),
|
||||
Vector3::new(x1 as f64 + lo_x, y0 as f64 + lo_y, lo_z),
|
||||
Vector3::new(x1 as f64 + lo_x, y1 as f64 + lo_y, lo_z),
|
||||
Vector3::new(x0 as f64 + lo_x, y1 as f64 + lo_y, lo_z),
|
||||
Vector3::new(x0 as f64, y0 as f64, 0.0),
|
||||
Vector3::new(x1 as f64, y0 as f64, 0.0),
|
||||
Vector3::new(x1 as f64, y1 as f64, 0.0),
|
||||
Vector3::new(x0 as f64, y1 as f64, 0.0),
|
||||
];
|
||||
let mut min_x = f64::INFINITY;
|
||||
let mut min_y = f64::INFINITY;
|
||||
@@ -725,7 +565,7 @@ pub fn expand_insert(
|
||||
// `defn.aabb_local` is in the defn's offset frame — re-add
|
||||
// `defn.local_offset` (f64) before transforming so the world AABB is
|
||||
// accurate for distant content.
|
||||
let insert_world = transform_offset_aabb_xy(defn.aabb_local, defn.local_offset, &xform);
|
||||
let insert_world = transform_aabb_xy(defn.aabb_local, &xform);
|
||||
let insert_local = [
|
||||
insert_world[0] as f32,
|
||||
insert_world[1] as f32,
|
||||
@@ -785,20 +625,13 @@ fn emit_greeked_text(
|
||||
lw: &LocalWire,
|
||||
local_aabb: [f32; 4],
|
||||
accum_xform: &Transform,
|
||||
defn_lo: [f64; 3],
|
||||
ctx: &ExpandCtx,
|
||||
out: &mut Batches,
|
||||
) {
|
||||
let [lo_x, lo_y, lo_z] = defn_lo;
|
||||
// Re-add the defn's `local_offset` in f64 before composing with
|
||||
// `accum_xform`, and keep the result in f64 so the per-vertex
|
||||
// double-single split below stays precise at UTM-scale coordinates.
|
||||
let xf = |p: [f32; 3]| -> [f64; 3] {
|
||||
let w = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + lo_x,
|
||||
p[1] as f64 + lo_y,
|
||||
p[2] as f64 + lo_z,
|
||||
));
|
||||
// Transform absolute f64 corners and keep the result in f64 so the
|
||||
// per-vertex double-single split below stays precise at UTM-scale coords.
|
||||
let xf = |p: [f64; 3]| -> [f64; 3] {
|
||||
let w = accum_xform.apply(Vector3::new(p[0], p[1], p[2]));
|
||||
[w.x, w.y, w.z]
|
||||
};
|
||||
|
||||
@@ -811,6 +644,7 @@ fn emit_greeked_text(
|
||||
if h_local <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let h = h_local as f64;
|
||||
let (ux, uy, uz) = (
|
||||
obb[3][0] - obb[0][0],
|
||||
obb[3][1] - obb[0][1],
|
||||
@@ -820,16 +654,14 @@ fn emit_greeked_text(
|
||||
if ulen < 1e-9 {
|
||||
return;
|
||||
}
|
||||
let n_lines = ((ulen / h_local).round() as usize).max(1);
|
||||
let n_lines = ((ulen / h).round() as usize).max(1);
|
||||
let (nx, ny, nz) = (ux / ulen, uy / ulen, uz / ulen);
|
||||
let bl_local = obb[0];
|
||||
let br_local = obb[1];
|
||||
let mut acc = Vec::with_capacity(n_lines * 6);
|
||||
for i in 0..n_lines {
|
||||
let top_off = (i as f32) * h_local;
|
||||
let bot_off = ((i + 1) as f32) * h_local;
|
||||
let top_along = (ulen - top_off).max(0.0);
|
||||
let bot_along = (ulen - bot_off).max(0.0);
|
||||
let top_along = (ulen - (i as f64) * h).max(0.0);
|
||||
let bot_along = (ulen - ((i + 1) as f64) * h).max(0.0);
|
||||
let tl = xf([
|
||||
bl_local[0] + nx * top_along,
|
||||
bl_local[1] + ny * top_along,
|
||||
@@ -855,11 +687,10 @@ fn emit_greeked_text(
|
||||
acc
|
||||
} else {
|
||||
let [x0, y0, x1, y1] = local_aabb;
|
||||
let z = 0.0_f32;
|
||||
let bl = xf([x0, y0, z]);
|
||||
let br = xf([x1, y0, z]);
|
||||
let tr = xf([x1, y1, z]);
|
||||
let tl = xf([x0, y1, z]);
|
||||
let bl = xf([x0 as f64, y0 as f64, 0.0]);
|
||||
let br = xf([x1 as f64, y0 as f64, 0.0]);
|
||||
let tr = xf([x1 as f64, y1 as f64, 0.0]);
|
||||
let tl = xf([x0 as f64, y1 as f64, 0.0]);
|
||||
vec![bl, br, tr, bl, tr, tl]
|
||||
};
|
||||
if tris.is_empty() {
|
||||
@@ -913,7 +744,6 @@ fn emit_greeked_text(
|
||||
fn emit_text_baseline(
|
||||
lw: &LocalWire,
|
||||
accum_xform: &Transform,
|
||||
defn_lo: [f64; 3],
|
||||
ctx: &ExpandCtx,
|
||||
out: &mut Batches,
|
||||
wpp: f32,
|
||||
@@ -927,14 +757,9 @@ fn emit_text_baseline(
|
||||
if h_local <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let [lo_x, lo_y, lo_z] = defn_lo;
|
||||
let xf = |p: [f32; 3]| -> [f32; 3] {
|
||||
let w = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + lo_x,
|
||||
p[1] as f64 + lo_y,
|
||||
p[2] as f64 + lo_z,
|
||||
));
|
||||
[(w.x) as f32, (w.y) as f32, (w.z) as f32]
|
||||
let xf = |p: [f64; 3]| -> [f64; 3] {
|
||||
let w = accum_xform.apply(Vector3::new(p[0], p[1], p[2]));
|
||||
[w.x, w.y, w.z]
|
||||
};
|
||||
|
||||
// Single line: just obb[0] → obb[1]. Skip when the projected length
|
||||
@@ -944,7 +769,7 @@ fn emit_text_baseline(
|
||||
let p1_world = xf(obb[1]);
|
||||
let dx = p1_world[0] - p0_world[0];
|
||||
let dy = p1_world[1] - p0_world[1];
|
||||
let len_px = (dx * dx + dy * dy).sqrt() / wpp;
|
||||
let len_px = (dx * dx + dy * dy).sqrt() / wpp as f64;
|
||||
if len_px < 2.0 {
|
||||
return;
|
||||
}
|
||||
@@ -961,7 +786,7 @@ fn emit_text_baseline(
|
||||
let n_lines = if ulen < 1e-9 {
|
||||
1
|
||||
} else {
|
||||
((ulen / h_local).round() as usize).max(1)
|
||||
((ulen / h_local as f64).round() as usize).max(1)
|
||||
};
|
||||
let (nx, ny, nz) = if ulen < 1e-9 {
|
||||
(0.0, 0.0, 0.0)
|
||||
@@ -994,7 +819,7 @@ fn emit_text_baseline(
|
||||
let along = if ulen < 1e-9 {
|
||||
0.0
|
||||
} else {
|
||||
(ulen - ((i + 1) as f32) * h_local).max(0.0)
|
||||
(ulen - ((i + 1) as f64) * h_local as f64).max(0.0)
|
||||
};
|
||||
let p0 = xf([
|
||||
bl_local[0] + nx * along,
|
||||
@@ -1013,24 +838,24 @@ fn emit_text_baseline(
|
||||
entry.points.push([f32::NAN; 3]);
|
||||
entry.points_low.push([0.0; 3]);
|
||||
}
|
||||
// Greek-substitute lines come from the LOD hatch path; their
|
||||
// positions already sit at the same magnitude as other batched
|
||||
// points, so a zero low component matches the f32 cast `xf` did.
|
||||
for q in [p0, p1] {
|
||||
if q[0] < entry.min_x {
|
||||
entry.min_x = q[0];
|
||||
let (hx, lx) = WireModel::split_ds(q[0]);
|
||||
let (hy, ly) = WireModel::split_ds(q[1]);
|
||||
let (hz, lz) = WireModel::split_ds(q[2]);
|
||||
if hx < entry.min_x {
|
||||
entry.min_x = hx;
|
||||
}
|
||||
if q[1] < entry.min_y {
|
||||
entry.min_y = q[1];
|
||||
if hy < entry.min_y {
|
||||
entry.min_y = hy;
|
||||
}
|
||||
if q[0] > entry.max_x {
|
||||
entry.max_x = q[0];
|
||||
if hx > entry.max_x {
|
||||
entry.max_x = hx;
|
||||
}
|
||||
if q[1] > entry.max_y {
|
||||
entry.max_y = q[1];
|
||||
if hy > entry.max_y {
|
||||
entry.max_y = hy;
|
||||
}
|
||||
entry.points.push(q);
|
||||
entry.points_low.push([0.0; 3]);
|
||||
entry.points.push([hx, hy, hz]);
|
||||
entry.points_low.push([lx, ly, lz]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1262,14 +1087,13 @@ fn expand_defn(
|
||||
eprintln!("block_cache: nested-block depth > {MAX_NESTING_DEPTH}, truncating");
|
||||
return;
|
||||
}
|
||||
let defn_lo = defn.local_offset;
|
||||
for sub in &defn.subs {
|
||||
match sub {
|
||||
LocalSub::Wire(lw) => {
|
||||
// `lw.aabb_local` is in the defn's offset frame; re-add
|
||||
// `defn_lo` (in f64) before composing with `accum_xform`
|
||||
// so culling uses correct world-space corners.
|
||||
let world = transform_offset_aabb_xy(lw.aabb_local, defn_lo, accum_xform);
|
||||
let world = transform_aabb_xy(lw.aabb_local, accum_xform);
|
||||
let local = [
|
||||
world[0] as f32,
|
||||
world[1] as f32,
|
||||
@@ -1302,16 +1126,16 @@ fn expand_defn(
|
||||
let h_world = h_local * sy;
|
||||
let h_px = h_world / wpp;
|
||||
if h_px < 1.0 {
|
||||
emit_text_baseline(lw, accum_xform, defn_lo, ctx, out, wpp);
|
||||
emit_text_baseline(lw, accum_xform, ctx, out, wpp);
|
||||
continue;
|
||||
}
|
||||
if h_px < 5.0 {
|
||||
emit_greeked_text(lw, local, accum_xform, defn_lo, ctx, out);
|
||||
emit_greeked_text(lw, local, accum_xform, ctx, out);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
emit_wire(lw, accum_xform, defn_lo, ctx, out);
|
||||
emit_wire(lw, accum_xform, ctx, out);
|
||||
}
|
||||
LocalSub::Nested(nref) => {
|
||||
if visited.iter().any(|n| n == &nref.block_name) {
|
||||
@@ -1327,11 +1151,7 @@ fn expand_defn(
|
||||
// frame — re-add `nested_defn.local_offset` in f64 before
|
||||
// composing with the parent transforms.
|
||||
let composed = nref.xform.then(accum_xform);
|
||||
let world = transform_offset_aabb_xy(
|
||||
nested_defn.aabb_local,
|
||||
nested_defn.local_offset,
|
||||
&composed,
|
||||
);
|
||||
let world = transform_aabb_xy(nested_defn.aabb_local, &composed);
|
||||
let local = [
|
||||
world[0] as f32,
|
||||
world[1] as f32,
|
||||
@@ -1428,14 +1248,12 @@ fn expand_defn(
|
||||
fn emit_wire(
|
||||
lw: &LocalWire,
|
||||
accum_xform: &Transform,
|
||||
defn_lo: [f64; 3],
|
||||
ctx: &ExpandCtx,
|
||||
out: &mut Batches,
|
||||
) {
|
||||
if lw.points.is_empty() && lw.fill_tris.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [lo_x, lo_y, lo_z] = defn_lo;
|
||||
|
||||
// Resolve final style for this LocalWire against the outer Insert ctx
|
||||
// before we hash it into a batch.
|
||||
@@ -1518,9 +1336,9 @@ fn emit_wire(
|
||||
// Reconstruct the f64 source from (high, low) before applying the
|
||||
// insert transform — otherwise the low half is silently dropped.
|
||||
let v = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + pl[0] as f64 + lo_x,
|
||||
p[1] as f64 + pl[1] as f64 + lo_y,
|
||||
p[2] as f64 + pl[2] as f64 + lo_z,
|
||||
p[0] as f64 + pl[0] as f64,
|
||||
p[1] as f64 + pl[1] as f64,
|
||||
p[2] as f64 + pl[2] as f64,
|
||||
));
|
||||
let qx = (v.x) as f32;
|
||||
let qy = (v.y) as f32;
|
||||
@@ -1548,17 +1366,17 @@ fn emit_wire(
|
||||
|
||||
for p in &lw.key_vertices {
|
||||
let v = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + lo_x,
|
||||
p[1] as f64 + lo_y,
|
||||
p[2] as f64 + lo_z,
|
||||
p[0] as f64,
|
||||
p[1] as f64,
|
||||
p[2] as f64,
|
||||
));
|
||||
entry.key_vertices.push([v.x, v.y, v.z]);
|
||||
}
|
||||
for (p, hint) in &lw.snap_pts {
|
||||
let v = accum_xform.apply(Vector3::new(
|
||||
p.x as f64 + lo_x,
|
||||
p.y as f64 + lo_y,
|
||||
p.z as f64 + lo_z,
|
||||
p.x as f64,
|
||||
p.y as f64,
|
||||
p.z as f64,
|
||||
));
|
||||
entry.snap_pts.push((
|
||||
glam::DVec3::new(v.x, v.y, v.z),
|
||||
@@ -1568,13 +1386,13 @@ fn emit_wire(
|
||||
for tg in &lw.tangent_geoms {
|
||||
entry
|
||||
.tangent_geoms
|
||||
.push(transform_tangent(tg, accum_xform, defn_lo));
|
||||
.push(transform_tangent(tg, accum_xform));
|
||||
}
|
||||
for p in &lw.fill_tris {
|
||||
let v = accum_xform.apply(Vector3::new(
|
||||
p[0] as f64 + lo_x,
|
||||
p[1] as f64 + lo_y,
|
||||
p[2] as f64 + lo_z,
|
||||
p[0] as f64,
|
||||
p[1] as f64,
|
||||
p[2] as f64,
|
||||
));
|
||||
let (hx, lx) = WireModel::split_ds(v.x);
|
||||
let (hy, ly) = WireModel::split_ds(v.y);
|
||||
@@ -1588,20 +1406,18 @@ fn emit_wire(
|
||||
fn transform_tangent(
|
||||
tg: &TangentGeom,
|
||||
t: &Transform,
|
||||
defn_lo: [f64; 3],
|
||||
) -> TangentGeom {
|
||||
let [lo_x, lo_y, lo_z] = defn_lo;
|
||||
match tg {
|
||||
TangentGeom::Line { p1, p2 } => {
|
||||
let q1 = t.apply(Vector3::new(
|
||||
p1[0] as f64 + lo_x,
|
||||
p1[1] as f64 + lo_y,
|
||||
p1[2] as f64 + lo_z,
|
||||
p1[0] as f64,
|
||||
p1[1] as f64,
|
||||
p1[2] as f64,
|
||||
));
|
||||
let q2 = t.apply(Vector3::new(
|
||||
p2[0] as f64 + lo_x,
|
||||
p2[1] as f64 + lo_y,
|
||||
p2[2] as f64 + lo_z,
|
||||
p2[0] as f64,
|
||||
p2[1] as f64,
|
||||
p2[2] as f64,
|
||||
));
|
||||
TangentGeom::Line {
|
||||
p1: [(q1.x) as f32, (q1.y) as f32, (q1.z) as f32],
|
||||
@@ -1610,9 +1426,9 @@ fn transform_tangent(
|
||||
}
|
||||
TangentGeom::Circle { center, radius } => {
|
||||
let c = t.apply(Vector3::new(
|
||||
center[0] as f64 + lo_x,
|
||||
center[1] as f64 + lo_y,
|
||||
center[2] as f64 + lo_z,
|
||||
center[0] as f64,
|
||||
center[1] as f64,
|
||||
center[2] as f64,
|
||||
));
|
||||
let m = &t.matrix.m;
|
||||
let sx = ((m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]) as f64).sqrt();
|
||||
|
||||
Reference in new issue
Block a user