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;
|
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
|
/// 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
|
/// 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
|
/// doesn't waste time on geometry that contributes a few sub-pixel marks
|
||||||
@@ -150,6 +155,9 @@ pub struct BlockMetrics {
|
|||||||
pub struct BlockDefn {
|
pub struct BlockDefn {
|
||||||
pub subs: Vec<LocalSub>,
|
pub subs: Vec<LocalSub>,
|
||||||
pub metrics: BlockMetrics,
|
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()`).
|
/// Raw entity count of the source block record (`entity_handles.len()`).
|
||||||
/// Divisor for nested depth composition: a nested insert's children get a
|
/// Divisor for nested depth composition: a nested insert's children get a
|
||||||
/// sub-range of `parent_scale / (child_count + 1)`, shared with the scene
|
/// sub-range of `parent_scale / (child_count + 1)`, shared with the scene
|
||||||
@@ -317,22 +325,60 @@ impl BlockCache {
|
|||||||
use crate::par::prelude::*;
|
use crate::par::prelude::*;
|
||||||
// Definitions are complete, so each recursive read can run independently.
|
// Definitions are complete, so each recursive read can run independently.
|
||||||
let this: &Self = self;
|
let this: &Self = self;
|
||||||
let resolved: Vec<(&String, BlockMetrics)> = names
|
let resolved: Vec<(&String, BlockMetrics, Option<usize>)> = names
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let mut visited: Vec<String> = Vec::new();
|
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();
|
.collect();
|
||||||
for (name, metrics) in resolved {
|
for (name, metrics, inline_point_cost) in resolved {
|
||||||
if let Some(defn_arc) = self.defns.get_mut(name) {
|
if let Some(defn_arc) = self.defns.get_mut(name) {
|
||||||
let mut defn = (**defn_arc).clone();
|
let mut defn = (**defn_arc).clone();
|
||||||
defn.metrics = metrics;
|
defn.metrics = metrics;
|
||||||
|
defn.inline_point_cost = inline_point_cost;
|
||||||
*defn_arc = Arc::new(defn);
|
*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(
|
fn defn_metrics_recursive(
|
||||||
&self,
|
&self,
|
||||||
block_name: &str,
|
block_name: &str,
|
||||||
@@ -585,10 +631,27 @@ fn build_defn(
|
|||||||
BlockDefn {
|
BlockDefn {
|
||||||
subs,
|
subs,
|
||||||
metrics: BlockMetrics::default(),
|
metrics: BlockMetrics::default(),
|
||||||
|
inline_point_cost: None,
|
||||||
child_count: br.entity_handles.len(),
|
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(
|
fn build_nested_ref(
|
||||||
nested_ins: &acadrust::entities::Insert,
|
nested_ins: &acadrust::entities::Insert,
|
||||||
scale_policy: crate::scene::BlockScalePolicy,
|
scale_policy: crate::scene::BlockScalePolicy,
|
||||||
@@ -1898,7 +1961,20 @@ fn expand_defn(
|
|||||||
let composed_for = |offset: &[f64; 3]| {
|
let composed_for = |offset: &[f64; 3]| {
|
||||||
nested_instance_transform(nref, *offset).then(accum_xform)
|
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_composed = nref.xform.then(accum_xform);
|
||||||
let base_translation = transform_translation(&base_composed);
|
let base_translation = transform_translation(&base_composed);
|
||||||
let base_poly: Vec<[f64; 2]> = cp
|
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