fix(scene): greek text OBB honors attachment point, propagates into blocks
Top-level OBB used insertion_point as anchor and hardcoded MText anchor to bottom-left, so greeked rects landed on the opposite side of the actual text whenever MText used a non-default attachment_point or TEXT used a non-Left/Baseline alignment. Block-defn subs never set or read `text_obb_local`, so greeked text inside Inserts fell back to the axis-aligned AABB and ignored entity rotation entirely. - Share OBB corner computation via `text_obb_corners_native`. - Map all 9 MText AttachmentPoint variants to (h_anchor, v_anchor). - For TEXT, anchor on `alignment_point` (DXF 11) when alignment != Left+Baseline. - Populate `LocalWire.text_obb_local` in `tessellate_sub_local`. - `emit_greeked_text` transforms OBB corners by `accum_xform`, falls back to AABB only when no OBB was precomputed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ab69203414
commit
ea87b3db76
2 changed files with 122 additions and 42 deletions
|
|
@ -304,6 +304,19 @@ fn tessellate_sub_local(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pre-compute the OBB corners (block-local 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`.
|
||||||
|
let text_obb_local: Option<[[f32; 3]; 4]> =
|
||||||
|
crate::scene::text_obb_corners_native(sub, anno_scale).map(|c| {
|
||||||
|
[
|
||||||
|
[c[0][0] as f32, c[0][1] as f32, c[0][2] as f32],
|
||||||
|
[c[1][0] as f32, c[1][1] as f32, c[1][2] as f32],
|
||||||
|
[c[2][0] as f32, c[2][1] as f32, c[2][2] as f32],
|
||||||
|
[c[3][0] as f32, c[3][1] as f32, c[3][2] as f32],
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
Some(LocalWire {
|
Some(LocalWire {
|
||||||
points: wire.points,
|
points: wire.points,
|
||||||
key_vertices: wire.key_vertices,
|
key_vertices: wire.key_vertices,
|
||||||
|
|
@ -321,6 +334,7 @@ fn tessellate_sub_local(
|
||||||
lw_is_byblock,
|
lw_is_byblock,
|
||||||
aabb_local,
|
aabb_local,
|
||||||
text_height_local,
|
text_height_local,
|
||||||
|
text_obb_local,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,21 +497,44 @@ pub fn expand_insert(
|
||||||
Some(batches.finalize(&name, selected))
|
Some(batches.finalize(&name, selected))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit a greeked rectangle for a text LocalWire — same color, AABB-sized
|
/// Emit a greeked rectangle for a text LocalWire — same color, OBB-sized
|
||||||
/// fill, no per-glyph stroke geometry. Mirrors the top-level text greek in
|
/// fill (rotated to match the entity), no per-glyph stroke geometry.
|
||||||
/// scene/mod.rs (including the 0.45 dim pre-boost the face3d pipeline
|
/// Mirrors the top-level text greek in scene/mod.rs (including the 0.45
|
||||||
/// applies to fill_tris).
|
/// dim pre-boost the face3d pipeline applies to fill_tris). Falls back to
|
||||||
fn emit_greeked_text(lw: &LocalWire, local_aabb: [f32; 4], ctx: &ExpandCtx, out: &mut Batches) {
|
/// the axis-aligned `local_aabb` when no OBB was precomputed.
|
||||||
|
fn emit_greeked_text(
|
||||||
|
lw: &LocalWire,
|
||||||
|
local_aabb: [f32; 4],
|
||||||
|
accum_xform: &Transform,
|
||||||
|
ctx: &ExpandCtx,
|
||||||
|
out: &mut Batches,
|
||||||
|
) {
|
||||||
|
let [ox, oy, oz] = ctx.world_offset;
|
||||||
|
let tris: [[f32; 3]; 6] = if let Some(obb) = lw.text_obb_local {
|
||||||
|
// Transform block-local corners → world via accum_xform, then
|
||||||
|
// subtract world_offset so the fill_tris sit in the same f32 space
|
||||||
|
// as everything else in the batch.
|
||||||
|
let xf = |p: [f32; 3]| -> [f32; 3] {
|
||||||
|
let w = accum_xform.apply(Vector3::new(p[0] as f64, p[1] as f64, p[2] as f64));
|
||||||
|
[(w.x - ox) as f32, (w.y - oy) as f32, (w.z - oz) as f32]
|
||||||
|
};
|
||||||
|
let p00 = xf(obb[0]);
|
||||||
|
let p10 = xf(obb[1]);
|
||||||
|
let p11 = xf(obb[2]);
|
||||||
|
let p01 = xf(obb[3]);
|
||||||
|
[p00, p10, p11, p00, p11, p01]
|
||||||
|
} else {
|
||||||
let [x0, y0, x1, y1] = local_aabb;
|
let [x0, y0, x1, y1] = local_aabb;
|
||||||
let z = 0.0_f32;
|
let z = 0.0_f32;
|
||||||
let tris = [
|
[
|
||||||
[x0, y0, z],
|
[x0, y0, z],
|
||||||
[x1, y0, z],
|
[x1, y0, z],
|
||||||
[x1, y1, z],
|
[x1, y1, z],
|
||||||
[x0, y0, z],
|
[x0, y0, z],
|
||||||
[x1, y1, z],
|
[x1, y1, z],
|
||||||
[x0, y1, z],
|
[x0, y1, z],
|
||||||
];
|
]
|
||||||
|
};
|
||||||
|
|
||||||
let final_color = if ctx.selected {
|
let final_color = if ctx.selected {
|
||||||
WireModel::SELECTED
|
WireModel::SELECTED
|
||||||
|
|
@ -734,7 +771,7 @@ fn expand_defn(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if h_px < 4.0 {
|
if h_px < 4.0 {
|
||||||
emit_greeked_text(lw, local, ctx, out);
|
emit_greeked_text(lw, local, accum_xform, ctx, out);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4191,18 +4191,19 @@ fn tessellate_entity(
|
||||||
vec![base]
|
vec![base]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the 6-vertex (2-triangle) fill for a greeked Text / MText entity
|
/// Build the 4 OBB corners (CCW: bl, br, tr, tl) of a Text / MText entity
|
||||||
/// in **world-offset-subtracted** f32 space, oriented to match the text's
|
/// in its **native frame** — for top-level entities this is world coords,
|
||||||
/// rotation. Width is approximated from the glyph height × character count
|
/// for block-defn subs it's block-local. No offset/transform applied.
|
||||||
/// (TEXT) or from `rectangle_width` (MTEXT).
|
/// Width is approximated from glyph height × character count (TEXT) or
|
||||||
fn text_greek_obb_tris(
|
/// from `rectangle_width` (MTEXT). Returns `None` for non-text entities.
|
||||||
|
pub(crate) fn text_obb_corners_native(
|
||||||
e: &EntityType,
|
e: &EntityType,
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
world_offset: [f64; 3],
|
) -> Option<[[f64; 3]; 4]> {
|
||||||
) -> Vec<[f32; 3]> {
|
use acadrust::entities::{
|
||||||
use acadrust::entities::{TextHorizontalAlignment, TextVerticalAlignment};
|
AttachmentPoint, TextHorizontalAlignment, TextVerticalAlignment,
|
||||||
|
};
|
||||||
|
|
||||||
let [ox, oy, oz] = world_offset;
|
|
||||||
let anno = anno_scale as f64;
|
let anno = anno_scale as f64;
|
||||||
|
|
||||||
let (ix, iy, iz, w, h, rot, h_anchor, v_anchor) = match e {
|
let (ix, iy, iz, w, h, rot, h_anchor, v_anchor) = match e {
|
||||||
|
|
@ -4225,10 +4226,24 @@ fn text_greek_obb_tris(
|
||||||
TextVerticalAlignment::Middle => 0.5,
|
TextVerticalAlignment::Middle => 0.5,
|
||||||
TextVerticalAlignment::Top => 1.0,
|
TextVerticalAlignment::Top => 1.0,
|
||||||
};
|
};
|
||||||
|
// AutoCAD writes `alignment_point` (DXF 11) whenever the text
|
||||||
|
// isn't simple Left+Baseline; in that case it — not the
|
||||||
|
// insertion_point — is the anchor the alignment fractions map to.
|
||||||
|
let use_alignment_pt = !matches!(
|
||||||
|
(t.horizontal_alignment, t.vertical_alignment),
|
||||||
(
|
(
|
||||||
t.insertion_point.x,
|
TextHorizontalAlignment::Left,
|
||||||
t.insertion_point.y,
|
TextVerticalAlignment::Baseline,
|
||||||
t.insertion_point.z,
|
)
|
||||||
|
);
|
||||||
|
let anchor = match (use_alignment_pt, t.alignment_point) {
|
||||||
|
(true, Some(p)) => p,
|
||||||
|
_ => t.insertion_point,
|
||||||
|
};
|
||||||
|
(
|
||||||
|
anchor.x,
|
||||||
|
anchor.y,
|
||||||
|
anchor.z,
|
||||||
w_world,
|
w_world,
|
||||||
h_world,
|
h_world,
|
||||||
t.rotation,
|
t.rotation,
|
||||||
|
|
@ -4247,6 +4262,20 @@ fn text_greek_obb_tris(
|
||||||
let total_h = m
|
let total_h = m
|
||||||
.rectangle_height
|
.rectangle_height
|
||||||
.unwrap_or(h_world * lines.max(1.0) * m.line_spacing_factor.max(0.5));
|
.unwrap_or(h_world * lines.max(1.0) * m.line_spacing_factor.max(0.5));
|
||||||
|
// MText `attachment_point` puts `insertion_point` at one of the
|
||||||
|
// 9 corners/midpoints of the text bbox. h_anchor / v_anchor are
|
||||||
|
// fractions from (left, bottom) of the bbox.
|
||||||
|
let (h_anchor, v_anchor) = match m.attachment_point {
|
||||||
|
AttachmentPoint::TopLeft => (0.0, 1.0),
|
||||||
|
AttachmentPoint::TopCenter => (0.5, 1.0),
|
||||||
|
AttachmentPoint::TopRight => (1.0, 1.0),
|
||||||
|
AttachmentPoint::MiddleLeft => (0.0, 0.5),
|
||||||
|
AttachmentPoint::MiddleCenter => (0.5, 0.5),
|
||||||
|
AttachmentPoint::MiddleRight => (1.0, 0.5),
|
||||||
|
AttachmentPoint::BottomLeft => (0.0, 0.0),
|
||||||
|
AttachmentPoint::BottomCenter => (0.5, 0.0),
|
||||||
|
AttachmentPoint::BottomRight => (1.0, 0.0),
|
||||||
|
};
|
||||||
(
|
(
|
||||||
m.insertion_point.x,
|
m.insertion_point.x,
|
||||||
m.insertion_point.y,
|
m.insertion_point.y,
|
||||||
|
|
@ -4254,35 +4283,49 @@ fn text_greek_obb_tris(
|
||||||
w_world,
|
w_world,
|
||||||
total_h,
|
total_h,
|
||||||
m.rotation,
|
m.rotation,
|
||||||
0.0,
|
h_anchor,
|
||||||
0.0,
|
v_anchor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => return vec![],
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local-frame corners with anchor offsets applied.
|
|
||||||
let x0 = -h_anchor * w;
|
let x0 = -h_anchor * w;
|
||||||
let x1 = (1.0 - h_anchor) * w;
|
let x1 = (1.0 - h_anchor) * w;
|
||||||
let y0 = -v_anchor * h;
|
let y0 = -v_anchor * h;
|
||||||
let y1 = (1.0 - v_anchor) * h;
|
let y1 = (1.0 - v_anchor) * h;
|
||||||
|
|
||||||
let (s, c) = (rot.sin(), rot.cos());
|
let (s, c) = (rot.sin(), rot.cos());
|
||||||
let rot_pt = |lx: f64, ly: f64| -> [f32; 3] {
|
let rot_pt = |lx: f64, ly: f64| -> [f64; 3] {
|
||||||
let rx = lx * c - ly * s;
|
let rx = lx * c - ly * s;
|
||||||
let ry = lx * s + ly * c;
|
let ry = lx * s + ly * c;
|
||||||
[
|
[ix + rx, iy + ry, iz]
|
||||||
(ix + rx - ox) as f32,
|
|
||||||
(iy + ry - oy) as f32,
|
|
||||||
(iz - oz) as f32,
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let p00 = rot_pt(x0, y0);
|
Some([
|
||||||
let p10 = rot_pt(x1, y0);
|
rot_pt(x0, y0),
|
||||||
let p11 = rot_pt(x1, y1);
|
rot_pt(x1, y0),
|
||||||
let p01 = rot_pt(x0, y1);
|
rot_pt(x1, y1),
|
||||||
|
rot_pt(x0, y1),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 6-vertex (2-triangle) fill for a greeked top-level Text / MText, in
|
||||||
|
/// world-offset-subtracted f32 space.
|
||||||
|
fn text_greek_obb_tris(
|
||||||
|
e: &EntityType,
|
||||||
|
anno_scale: f32,
|
||||||
|
world_offset: [f64; 3],
|
||||||
|
) -> Vec<[f32; 3]> {
|
||||||
|
let Some(corners) = text_obb_corners_native(e, anno_scale) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let [ox, oy, oz] = world_offset;
|
||||||
|
let cast = |p: [f64; 3]| -> [f32; 3] {
|
||||||
|
[(p[0] - ox) as f32, (p[1] - oy) as f32, (p[2] - oz) as f32]
|
||||||
|
};
|
||||||
|
let [p00, p10, p11, p01] = corners;
|
||||||
|
let (p00, p10, p11, p01) = (cast(p00), cast(p10), cast(p11), cast(p01));
|
||||||
vec![p00, p10, p11, p00, p11, p01]
|
vec![p00, p10, p11, p00, p11, p01]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue