forked from 1b-agent/opencadstudio-upstream
fix: compact repeated nested block geometry
This commit is contained in:
1 parent
84e85ae693
commit
bb29b987c2
1 file changed
+193
-4
Vendored
+193
-4
@@ -29,6 +29,11 @@ use crate::scene::model::wire_model::{
|
||||
};
|
||||
|
||||
const MAX_NESTING_DEPTH: usize = 32;
|
||||
/// Small line-only nested definitions cost more as separate instance records
|
||||
/// than as part of their parent's existing style batches. Folding these tiny
|
||||
/// subtrees at expansion time keeps exact geometry while avoiding one
|
||||
/// heavyweight `WireModel` per leaf occurrence.
|
||||
const INLINE_NESTED_POINT_BUDGET: usize = 128;
|
||||
/// Skip wires whose world-AABB projects to fewer than this many pixels in
|
||||
/// the active view. Picks up tiny detail at zoom-out so the tessellator
|
||||
/// doesn't waste time on geometry that contributes a few sub-pixel marks
|
||||
@@ -150,6 +155,9 @@ pub struct BlockMetrics {
|
||||
pub struct BlockDefn {
|
||||
pub subs: Vec<LocalSub>,
|
||||
pub metrics: BlockMetrics,
|
||||
/// Point cost when this whole subtree is safe and cheap to fold into its
|
||||
/// parent's batches. `None` keeps the regular instanced expansion path.
|
||||
inline_point_cost: Option<usize>,
|
||||
/// Raw entity count of the source block record (`entity_handles.len()`).
|
||||
/// Divisor for nested depth composition: a nested insert's children get a
|
||||
/// sub-range of `parent_scale / (child_count + 1)`, shared with the scene
|
||||
@@ -317,22 +325,60 @@ impl BlockCache {
|
||||
use crate::par::prelude::*;
|
||||
// Definitions are complete, so each recursive read can run independently.
|
||||
let this: &Self = self;
|
||||
let resolved: Vec<(&String, BlockMetrics)> = names
|
||||
let resolved: Vec<(&String, BlockMetrics, Option<usize>)> = names
|
||||
.par_iter()
|
||||
.map(|name| {
|
||||
let mut visited: Vec<String> = Vec::new();
|
||||
(name, this.defn_metrics_recursive(name, &mut visited))
|
||||
let metrics = this.defn_metrics_recursive(name, &mut visited);
|
||||
let inline_point_cost =
|
||||
this.defn_inline_point_cost_recursive(name, &mut Vec::new());
|
||||
(name, metrics, inline_point_cost)
|
||||
})
|
||||
.collect();
|
||||
for (name, metrics) in resolved {
|
||||
for (name, metrics, inline_point_cost) in resolved {
|
||||
if let Some(defn_arc) = self.defns.get_mut(name) {
|
||||
let mut defn = (**defn_arc).clone();
|
||||
defn.metrics = metrics;
|
||||
defn.inline_point_cost = inline_point_cost;
|
||||
*defn_arc = Arc::new(defn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn defn_inline_point_cost_recursive(
|
||||
&self,
|
||||
block_name: &str,
|
||||
visited: &mut Vec<String>,
|
||||
) -> Option<usize> {
|
||||
if visited.iter().any(|name| name == block_name) {
|
||||
return None;
|
||||
}
|
||||
let defn = self.defns.get(block_name)?;
|
||||
visited.push(block_name.to_string());
|
||||
let result = (|| {
|
||||
let mut cost = 0usize;
|
||||
for sub in &defn.subs {
|
||||
let sub_cost = match sub {
|
||||
LocalSub::Wire(wire) => inline_wire_point_cost(wire)?,
|
||||
LocalSub::Nested(nested)
|
||||
if nested.clip_poly.is_none() && nested.attachments.is_empty() =>
|
||||
{
|
||||
self.defn_inline_point_cost_recursive(&nested.block_name, visited)?
|
||||
.checked_mul(nested.instance_offsets.len())?
|
||||
}
|
||||
LocalSub::Nested(_) => return None,
|
||||
};
|
||||
cost = cost.checked_add(sub_cost)?;
|
||||
if cost > INLINE_NESTED_POINT_BUDGET {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(cost)
|
||||
})();
|
||||
visited.pop();
|
||||
result
|
||||
}
|
||||
|
||||
fn defn_metrics_recursive(
|
||||
&self,
|
||||
block_name: &str,
|
||||
@@ -585,10 +631,27 @@ fn build_defn(
|
||||
BlockDefn {
|
||||
subs,
|
||||
metrics: BlockMetrics::default(),
|
||||
inline_point_cost: None,
|
||||
child_count: br.entity_handles.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_wire_point_cost(wire: &LocalWire) -> Option<usize> {
|
||||
let line_tangents_only = wire
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.all(|tangent| matches!(tangent, TangentGeom::Line { .. }));
|
||||
(line_tangents_only
|
||||
&& !wire.is_point
|
||||
&& wire.point_marker.is_none()
|
||||
&& wire.text_verts.is_empty()
|
||||
&& wire.fill_tris.is_empty()
|
||||
&& wire.pick_tris.is_empty()
|
||||
&& wire.pattern_stations.is_empty()
|
||||
&& wire.world_width == 0.0)
|
||||
.then_some(wire.points.len())
|
||||
}
|
||||
|
||||
fn build_nested_ref(
|
||||
nested_ins: &acadrust::entities::Insert,
|
||||
scale_policy: crate::scene::BlockScalePolicy,
|
||||
@@ -1898,7 +1961,20 @@ fn expand_defn(
|
||||
let composed_for = |offset: &[f64; 3]| {
|
||||
nested_instance_transform(nref, *offset).then(accum_xform)
|
||||
};
|
||||
if let Some(cp) = &nref.clip_poly {
|
||||
if nested_defn.inline_point_cost.is_some() && nref.clip_poly.is_none() {
|
||||
for offset in &nref.instance_offsets {
|
||||
expand_defn(
|
||||
nested_defn,
|
||||
&composed_for(offset),
|
||||
&inner_ctx,
|
||||
out,
|
||||
visited,
|
||||
depth + 1,
|
||||
nref.suppress_root_points,
|
||||
nested_range,
|
||||
);
|
||||
}
|
||||
} else if let Some(cp) = &nref.clip_poly {
|
||||
let base_composed = nref.xform.then(accum_xform);
|
||||
let base_translation = transform_translation(&base_composed);
|
||||
let base_poly: Vec<[f64; 2]> = cp
|
||||
@@ -3036,3 +3112,116 @@ mod bg_resolution_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod compact_nested_tests {
|
||||
use super::*;
|
||||
use crate::scene::view::render::InheritStyle;
|
||||
use acadrust::entities::{Insert, Line};
|
||||
use acadrust::tables::BlockRecord;
|
||||
|
||||
fn add_block(document: &mut CadDocument, name: &str) -> Handle {
|
||||
let mut block = BlockRecord::new(name);
|
||||
block.handle = document.allocate_handle();
|
||||
let handle = block.handle;
|
||||
document.block_records.add(block).unwrap();
|
||||
handle
|
||||
}
|
||||
|
||||
fn add_owned(document: &mut CadDocument, owner: Handle, mut entity: EntityType) {
|
||||
entity.common_mut().owner_handle = owner;
|
||||
document.add_entity(entity).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_small_nested_lines_fold_into_parent_batches() {
|
||||
let mut document = CadDocument::new();
|
||||
let leaf = add_block(&mut document, "LEAF");
|
||||
for y in 0..6 {
|
||||
add_owned(
|
||||
&mut document,
|
||||
leaf,
|
||||
EntityType::Line(Line::from_points(
|
||||
Vector3::new(0.0, y as f64, 0.0),
|
||||
Vector3::new(1.0, y as f64, 0.0),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
let twig = add_block(&mut document, "TWIG");
|
||||
for x in 0..5 {
|
||||
add_owned(
|
||||
&mut document,
|
||||
twig,
|
||||
EntityType::Insert(Insert::new(
|
||||
"LEAF",
|
||||
Vector3::new(x as f64 * 2.0, 0.0, 0.0),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
let root = add_block(&mut document, "ROOT");
|
||||
for x in 0..100 {
|
||||
add_owned(
|
||||
&mut document,
|
||||
root,
|
||||
EntityType::Insert(Insert::new(
|
||||
"TWIG",
|
||||
Vector3::new(x as f64 * 20.0, 0.0, 0.0),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
let root_insert = Insert::new("ROOT", Vector3::ZERO);
|
||||
let root_handle = document
|
||||
.add_entity(EntityType::Insert(root_insert.clone()))
|
||||
.unwrap();
|
||||
let cache = BlockCache::build(
|
||||
&document,
|
||||
1.0,
|
||||
None,
|
||||
true,
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
None,
|
||||
&HashMap::default(),
|
||||
);
|
||||
let wires = expand_insert(
|
||||
&document,
|
||||
&cache,
|
||||
&root_insert,
|
||||
root_handle,
|
||||
[1.0; 4],
|
||||
0,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
1.0,
|
||||
InheritStyle {
|
||||
color: [1.0; 4],
|
||||
pat_len: 0.0,
|
||||
pat: [0.0; 8],
|
||||
lw_px: 1.0,
|
||||
},
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
1.0,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
1.0,
|
||||
crate::scene::BlockScalePolicy::FromInsert,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let finite_points = wires
|
||||
.iter()
|
||||
.flat_map(|wire| &wire.points)
|
||||
.filter(|point| point[0].is_finite())
|
||||
.count();
|
||||
assert_eq!(finite_points, 6_000);
|
||||
assert_eq!(wires.len(), 1);
|
||||
assert!(wires[0].render_instance.is_none());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user