forked from 1b-agent/opencadstudio-upstream
refactor(tess): move per-entity tessellation into entities/
Each entity's tessellation now lives alongside its definition in
entities/*.rs, attached to the entity type via a local trait:
LegacyTess — Viewport / Insert / Hatch / Ole2Frame
(fallback path for entities not on the truck pipeline)
LeaderTess — Leader
MultiLeaderTess — MultiLeader
DimensionTess — Dimension (carries its dim-only helpers too:
~1700 lines of dim_geometry, dim_text_*, format_*,
tolerance, suppression, etc.)
scene/tessellate.rs keeps only the cross-entity dispatcher plus the
shared dim machinery reused by leader/multileader/dimension
(ArrowKind, DimGeom, append_arrow, arrow_from_block, add_segment,
add_polyline, normalized_or, push_tri, color_or_inherit, entity_z,
offset_snap_pts). Cross-entity arc helpers + aci_to_rgba moved to
scene/tess_util.rs.
scene/tessellate.rs shrinks from ~3300 to 809 lines. Compile clean,
all 9 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
163725ca22
commit
aadb64860d
12 files changed
+3016
-2832
No files matched your search
+26
-25
@@ -23,9 +23,10 @@ pub(super) struct EntityIndex {
|
||||
pub tree: quadtree::QuadTree,
|
||||
pub unbounded_handles: Vec<Handle>,
|
||||
}
|
||||
mod render;
|
||||
pub(crate) mod render;
|
||||
mod selection;
|
||||
pub mod solid3d_tess;
|
||||
pub mod tess_util;
|
||||
pub mod tessellate;
|
||||
pub mod transform;
|
||||
pub mod truck_tess;
|
||||
@@ -178,11 +179,11 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
||||
};
|
||||
let model = match e {
|
||||
EntityType::Hatch(dxf) => {
|
||||
let color = tessellate::aci_to_rgba(&dxf.common.color);
|
||||
let color = tess_util::aci_to_rgba(&dxf.common.color);
|
||||
Scene::hatch_model_from_dxf(dxf, color, offset)
|
||||
}
|
||||
EntityType::Solid(solid) => {
|
||||
let color = tessellate::aci_to_rgba(&solid.common.color);
|
||||
let color = tess_util::aci_to_rgba(&solid.common.color);
|
||||
Some(Scene::solid_hatch_model(solid, color, offset))
|
||||
}
|
||||
_ => None,
|
||||
@@ -211,7 +212,7 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
||||
.par_iter()
|
||||
.filter_map(|&handle| {
|
||||
let e = doc.get_entity(handle)?;
|
||||
let color = tessellate::aci_to_rgba(&e.common().color);
|
||||
let color = tess_util::aci_to_rgba(&e.common().color);
|
||||
let model = match e {
|
||||
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color, facet_res),
|
||||
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res),
|
||||
@@ -2445,7 +2446,7 @@ impl Scene {
|
||||
let color = layer_entry
|
||||
.map(|l| &l.color)
|
||||
.unwrap_or(&acadrust::types::Color::WHITE);
|
||||
let [r, g, b, _] = crate::scene::tessellate::aci_to_rgba(color);
|
||||
let [r, g, b, _] = crate::scene::tess_util::aci_to_rgba(color);
|
||||
[r, g, b, 1.0]
|
||||
}
|
||||
|
||||
@@ -2888,10 +2889,10 @@ impl Scene {
|
||||
if sweep.abs() < 1e-9 {
|
||||
sweep = if bulge > 0.0 { TAU } else { -TAU };
|
||||
}
|
||||
let segs = tessellate::arc_segments(
|
||||
let segs = tess_util::arc_segments(
|
||||
r,
|
||||
sweep.abs(),
|
||||
tessellate::fill_chord_tol(r),
|
||||
tess_util::fill_chord_tol(r),
|
||||
);
|
||||
for j in 0..segs {
|
||||
let a = a0 + sweep * (j as f64 / segs as f64);
|
||||
@@ -2910,15 +2911,15 @@ impl Scene {
|
||||
boundary.push(to_xy(line.end.x, line.end.y));
|
||||
}
|
||||
BoundaryEdge::CircularArc(arc) => {
|
||||
let (sa, span) = tessellate::arc_signed_span(
|
||||
let (sa, span) = tess_util::arc_signed_span(
|
||||
arc.start_angle,
|
||||
arc.end_angle,
|
||||
arc.counter_clockwise,
|
||||
);
|
||||
let segs = tessellate::arc_segments(
|
||||
let segs = tess_util::arc_segments(
|
||||
arc.radius,
|
||||
span.abs(),
|
||||
tessellate::fill_chord_tol(arc.radius),
|
||||
tess_util::fill_chord_tol(arc.radius),
|
||||
);
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
@@ -2937,15 +2938,15 @@ impl Scene {
|
||||
.major_axis_endpoint
|
||||
.y
|
||||
.atan2(ell.major_axis_endpoint.x);
|
||||
let (sa, span) = tessellate::arc_signed_span(
|
||||
let (sa, span) = tess_util::arc_signed_span(
|
||||
ell.start_angle,
|
||||
ell.end_angle,
|
||||
ell.counter_clockwise,
|
||||
);
|
||||
let segs = tessellate::arc_segments(
|
||||
let segs = tess_util::arc_segments(
|
||||
r_maj,
|
||||
span.abs(),
|
||||
tessellate::fill_chord_tol(r_maj),
|
||||
tess_util::fill_chord_tol(r_maj),
|
||||
);
|
||||
let (cr, sr) = (rot.cos(), rot.sin());
|
||||
for i in 0..=segs {
|
||||
@@ -2999,7 +3000,7 @@ impl Scene {
|
||||
let diag = ((sp_max_x - sp_min_x).powi(2)
|
||||
+ (sp_max_y - sp_min_y).powi(2))
|
||||
.sqrt();
|
||||
let tol = tessellate::fill_chord_tol(diag.max(1.0));
|
||||
let tol = tess_util::fill_chord_tol(diag.max(1.0));
|
||||
|
||||
let mut sampled = false;
|
||||
if knot_ok {
|
||||
@@ -3211,11 +3212,11 @@ impl Scene {
|
||||
};
|
||||
let model = match &kind {
|
||||
EntityType::Hatch(dxf) => {
|
||||
let color = tessellate::aci_to_rgba(&dxf.common.color);
|
||||
let color = tess_util::aci_to_rgba(&dxf.common.color);
|
||||
Self::hatch_model_from_dxf(dxf, color, offset)
|
||||
}
|
||||
EntityType::Solid(solid) => {
|
||||
let color = tessellate::aci_to_rgba(&solid.common.color);
|
||||
let color = tess_util::aci_to_rgba(&solid.common.color);
|
||||
Some(Self::solid_hatch_model(solid, color, offset))
|
||||
}
|
||||
_ => None,
|
||||
@@ -3241,7 +3242,7 @@ impl Scene {
|
||||
.entities()
|
||||
.filter_map(|e| match e {
|
||||
EntityType::Solid3D(_) | EntityType::Region(_) | EntityType::Body(_) => {
|
||||
let color = tessellate::aci_to_rgba(&e.common().color);
|
||||
let color = tess_util::aci_to_rgba(&e.common().color);
|
||||
Some((e.common().handle, e.clone(), color))
|
||||
}
|
||||
_ => None,
|
||||
@@ -3644,7 +3645,7 @@ impl Scene {
|
||||
let h = self.document.add_entity(entity).unwrap_or(Handle::NULL);
|
||||
if !h.is_null() {
|
||||
let new_model = if let Some(EntityType::Hatch(dxf)) = self.document.get_entity(h) {
|
||||
let color = tessellate::aci_to_rgba(&dxf.common.color);
|
||||
let color = tess_util::aci_to_rgba(&dxf.common.color);
|
||||
Self::hatch_model_from_dxf(dxf, color, hatch_offset)
|
||||
} else {
|
||||
None
|
||||
@@ -3736,7 +3737,7 @@ impl Scene {
|
||||
};
|
||||
match self.document.get_entity(handle) {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
let color = tessellate::aci_to_rgba(&dxf.common.color);
|
||||
let color = tess_util::aci_to_rgba(&dxf.common.color);
|
||||
if let Some(model) = Self::hatch_model_from_dxf(dxf, color, hatch_offset) {
|
||||
self.hatches.insert(handle, model);
|
||||
} else {
|
||||
@@ -3744,7 +3745,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
Some(EntityType::Solid(solid)) => {
|
||||
let color = tessellate::aci_to_rgba(&solid.common.color);
|
||||
let color = tess_util::aci_to_rgba(&solid.common.color);
|
||||
self.hatches
|
||||
.insert(handle, Self::solid_hatch_model(solid, color, hatch_offset));
|
||||
}
|
||||
@@ -4691,7 +4692,7 @@ fn clip_polyline_to_rect(
|
||||
/// so it picks up the standard text LOD ladder (baseline / greek / full),
|
||||
/// then re-color the returned wires with the dimension's resolved text colour
|
||||
/// (so DIMCLRT / DIMSTYLE colours win over the synthetic Text's defaults).
|
||||
pub(super) fn tessellate_entity_dim_text(
|
||||
pub(crate) fn tessellate_entity_dim_text(
|
||||
document: &acadrust::CadDocument,
|
||||
selected: &HashSet<Handle>,
|
||||
active_viewport: Option<Handle>,
|
||||
@@ -4899,10 +4900,10 @@ fn tessellate_entity(
|
||||
|
||||
if let EntityType::Dimension(dim) = e {
|
||||
let aabb = entity_aabb(e, world_offset);
|
||||
let mut wires = tessellate::tessellate_dimension(
|
||||
use crate::entities::dimension::DimensionTess;
|
||||
let mut wires = dim.tessellate(
|
||||
document,
|
||||
h,
|
||||
dim,
|
||||
sel,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
@@ -4923,10 +4924,10 @@ fn tessellate_entity(
|
||||
|
||||
if let EntityType::MultiLeader(ml) = e {
|
||||
let aabb = entity_aabb(e, world_offset);
|
||||
let mut wires = tessellate::tessellate_multileader(
|
||||
use crate::entities::multileader::MultiLeaderTess;
|
||||
let mut wires = ml.tessellate(
|
||||
document,
|
||||
h,
|
||||
ml,
|
||||
sel,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@ use std::sync::Arc;
|
||||
|
||||
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
|
||||
use super::pipeline::Pipeline;
|
||||
use super::tessellate;
|
||||
use super::tess_util;
|
||||
use super::{HatchModel, ImageModel, MeshLodSet, Scene, Uniforms, WireModel};
|
||||
|
||||
// ── PaperViewportPipeline / PaperViewportPrimitive ────────────────────────
|
||||
@@ -242,7 +242,7 @@ pub(super) fn render_style_for(
|
||||
AcadColor::Index(i) => *i,
|
||||
_ => 0,
|
||||
};
|
||||
let [r, g, b, _] = tessellate::aci_to_rgba(resolved);
|
||||
let [r, g, b, _] = tess_util::aci_to_rgba(resolved);
|
||||
let alpha = 1.0 - e.common().transparency.as_percent() as f32;
|
||||
([r, g, b, alpha], aci)
|
||||
};
|
||||
@@ -473,7 +473,7 @@ impl Scene {
|
||||
|
||||
// ── Linetype pattern helper ───────────────────────────────────────────────
|
||||
|
||||
pub(super) fn resolve_pattern(
|
||||
pub(crate) fn resolve_pattern(
|
||||
table: &acadrust::tables::Table<LineType>,
|
||||
name: &str,
|
||||
scale: f32,
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Shared helpers used by per-entity tessellation impls in `crate::entities`
|
||||
// and the dispatcher in `crate::scene::tessellate`.
|
||||
//
|
||||
// Anything cross-entity (colour resolution, arc sampling, chord tolerance)
|
||||
// lives here. Entity-specific helpers (dim format strings, mleader path
|
||||
// types, etc.) stay with their entity file.
|
||||
|
||||
use acadrust::types::Color as AcadColor;
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::scene::wire_model::{SnapHint, TangentGeom, WireModel};
|
||||
|
||||
/// Output of the legacy per-entity geometry path used by entities not
|
||||
/// (yet) covered by the truck topology pipeline (Viewport, Insert,
|
||||
/// Hatch outline, Ole2Frame, …). Tuple form preserved to avoid
|
||||
/// touching every callsite when the dispatcher wraps these into a
|
||||
/// WireModel.
|
||||
///
|
||||
/// Layout: `(points, snap_pts, tangent_geoms, key_vertices)`.
|
||||
pub type LegacyGeometry = (
|
||||
Vec<[f32; 3]>,
|
||||
Vec<(Vec3, SnapHint)>,
|
||||
Vec<TangentGeom>,
|
||||
Vec<[f32; 3]>,
|
||||
);
|
||||
|
||||
// ── Arc tessellation helpers ─────────────────────────────────────────────
|
||||
|
||||
/// Convert hatch-boundary arc `(start, end, ccw)` into a
|
||||
/// `(start, signed_span)` ready for the sampling loop. Matches the
|
||||
/// legacy `(TAU - sa, TAU - ea)` flip used here for years — direction
|
||||
/// semantics are preserved on real files. (Wrap-through-2π is a known
|
||||
/// edge case in that convention; do not "fix" it without a wider audit
|
||||
/// of how upstream writers emit CW boundary arcs.)
|
||||
pub fn arc_signed_span(start: f64, end: f64, ccw: bool) -> (f64, f64) {
|
||||
const TAU: f64 = std::f64::consts::TAU;
|
||||
let (sa, ea) = if ccw { (start, end) } else { (TAU - start, TAU - end) };
|
||||
(sa, ea - sa)
|
||||
}
|
||||
|
||||
/// Segment count for an arc, targeting `chord_tol_world` chord-height
|
||||
/// error in world units. Floor 8, cap 512.
|
||||
///
|
||||
/// Two production callers:
|
||||
/// - hatch fill polygon (built at load / on edit) passes a radius-
|
||||
/// relative tol via [`fill_chord_tol`] — ~0.1% radius, zoom-free so
|
||||
/// the polygon stays sharp at extreme zoom-in without re-tessellation.
|
||||
/// - hatch wire outline (re-tessellated every frame inside the render
|
||||
/// scope) passes a zoom-adaptive tol via [`wire_chord_tol`] — pulls
|
||||
/// from the per-frame override set by `Scene::wires_for_block` so
|
||||
/// far-out arcs collapse to a handful of segments.
|
||||
pub fn arc_segments(radius: f64, span_abs: f64, chord_tol_world: f64) -> u32 {
|
||||
if span_abs < 1e-9 || radius < 1e-9 {
|
||||
return 1;
|
||||
}
|
||||
let tol = chord_tol_world.max(1e-9).min(radius * 0.99);
|
||||
// θ where r * (1 - cos(θ/2)) = tol → θ = 2 * acos(1 - tol/r).
|
||||
let max_step = (2.0 * (1.0 - tol / radius).acos()).max(1e-6);
|
||||
((span_abs / max_step).ceil() as u32).clamp(8, 512)
|
||||
}
|
||||
|
||||
/// Chord tolerance for the load-time fill polygon: 0.1% of radius,
|
||||
/// floor 1 µm so degenerate radii still produce a workable count.
|
||||
pub fn fill_chord_tol(radius: f64) -> f64 {
|
||||
(radius * 0.001).max(1e-6)
|
||||
}
|
||||
|
||||
/// Chord tolerance for the per-frame wire outline: pulls the active
|
||||
/// `truck_tess::set_curve_tol_override` value (Scene sets it to
|
||||
/// `world_per_pixel × 0.5` so curves stay at ~half-pixel chord error at
|
||||
/// the current zoom). When no override is active (snap / hit-test
|
||||
/// passes, load-time builds), falls back to [`fill_chord_tol`] so we
|
||||
/// never under-sample.
|
||||
pub fn wire_chord_tol(radius: f64) -> f64 {
|
||||
match crate::scene::truck_tess::active_curve_tol() {
|
||||
Some(t) => t.min(fill_chord_tol(radius)).max(1e-9),
|
||||
None => fill_chord_tol(radius),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Colour helper ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Convert an acadrust Color (ACI index or true-color) to a GPU RGBA value.
|
||||
pub fn aci_to_rgba(color: &AcadColor) -> [f32; 4] {
|
||||
if let Some((r, g, b)) = color.rgb() {
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
} else {
|
||||
WireModel::WHITE
|
||||
}
|
||||
}
|
||||
+29
-2803
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user