fix(render): share repeated block meshes
Keep placement transforms and bounds separate from immutable mesh data to prevent large drawings from exhausting memory or stalling selection.
This commit is contained in:
parent
5e773fbcce
commit
7b4af0aa75
6 changed files with 408 additions and 399 deletions
336
src/scene/mod.rs
336
src/scene/mod.rs
|
|
@ -89,6 +89,9 @@ fn hatch_interaction_aabb(hatch: &model::hatch_model::HatchModel) -> Option<[f64
|
|||
}
|
||||
|
||||
fn mesh_interaction_aabb(set: &model::mesh_model::MeshLodSet) -> Option<[f64; 6]> {
|
||||
if let Some(aabb) = set.instance_aabb {
|
||||
return Some(aabb);
|
||||
}
|
||||
let mut aabb = [
|
||||
f64::INFINITY,
|
||||
f64::INFINITY,
|
||||
|
|
@ -1213,95 +1216,63 @@ fn offset_mesh_lod_set(mut set: MeshLodSet) -> MeshLodSet {
|
|||
set
|
||||
}
|
||||
|
||||
/// Instance a block-local mesh into the render frame: apply the accumulated
|
||||
/// INSERT transform (block-local → world/DXF) then subtract world_offset, so a
|
||||
/// block scaled at the INSERT renders at the right size. Normals are rotated by
|
||||
/// the transform's linear part and re-normalized. (#123)
|
||||
/// Build a lightweight placement over immutable block-local geometry.
|
||||
fn transform_block_mesh_lod_set(
|
||||
set: &MeshLodSet,
|
||||
xform: &acadrust::types::Transform,
|
||||
) -> MeshLodSet {
|
||||
use acadrust::types::Vector3;
|
||||
let mut out = set.clone();
|
||||
out.instance_transform = Some(*xform);
|
||||
let mut min_x = f32::INFINITY;
|
||||
let mut min_y = f32::INFINITY;
|
||||
let mut max_x = f32::NEG_INFINITY;
|
||||
let mut max_y = f32::NEG_INFINITY;
|
||||
for lod in &mut out.lods {
|
||||
let has_low = lod.verts_low.len() == lod.verts.len();
|
||||
if !has_low {
|
||||
lod.verts_low = vec![[0.0; 3]; lod.verts.len()];
|
||||
}
|
||||
for (v, vl) in lod.verts.iter_mut().zip(lod.verts_low.iter_mut()) {
|
||||
// Reconstruct the block-local f64, apply the INSERT transform and
|
||||
// subtract world_offset in f64, then re-split into (high, low).
|
||||
let w = xform.apply(Vector3::new(
|
||||
v[0] as f64 + vl[0] as f64,
|
||||
v[1] as f64 + vl[1] as f64,
|
||||
v[2] as f64 + vl[2] as f64,
|
||||
));
|
||||
let ax = w.x;
|
||||
let ay = w.y;
|
||||
let az = w.z;
|
||||
let hx = ax as f32;
|
||||
let hy = ay as f32;
|
||||
let hz = az as f32;
|
||||
*v = [hx, hy, hz];
|
||||
*vl = [
|
||||
(ax - hx as f64) as f32,
|
||||
(ay - hy as f64) as f32,
|
||||
(az - hz as f64) as f32,
|
||||
];
|
||||
if hx < min_x {
|
||||
min_x = hx;
|
||||
}
|
||||
if hy < min_y {
|
||||
min_y = hy;
|
||||
}
|
||||
if hx > max_x {
|
||||
max_x = hx;
|
||||
}
|
||||
if hy > max_y {
|
||||
max_y = hy;
|
||||
}
|
||||
}
|
||||
for n in &mut lod.normals {
|
||||
let d = xform.apply_rotation(Vector3::new(n[0] as f64, n[1] as f64, n[2] as f64));
|
||||
let len = (d.x * d.x + d.y * d.y + d.z * d.z).sqrt();
|
||||
if len > 1e-12 {
|
||||
n[0] = (d.x / len) as f32;
|
||||
n[1] = (d.y / len) as f32;
|
||||
n[2] = (d.z / len) as f32;
|
||||
let source = set.instance_source.clone().unwrap_or_else(|| {
|
||||
std::sync::Arc::new(model::mesh_model::MeshInstanceSource {
|
||||
handle: set.entity_handle().unwrap_or(Handle::new(0)),
|
||||
lods: set.lods.clone(),
|
||||
edge_verts: set.edge_verts.clone(),
|
||||
edge_verts_low: set.edge_verts_low.clone(),
|
||||
curved_gens: set.curved_gens.clone(),
|
||||
})
|
||||
});
|
||||
let mut bounds = [
|
||||
f64::INFINITY,
|
||||
f64::INFINITY,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
];
|
||||
for x in [set.world_aabb[0], set.world_aabb[2]] {
|
||||
for y in [set.world_aabb[1], set.world_aabb[3]] {
|
||||
for z in [set.z_aabb[0], set.z_aabb[1]] {
|
||||
let point = xform.apply(Vector3::new(x as f64, y as f64, z as f64));
|
||||
for (axis, value) in [point.x, point.y, point.z].into_iter().enumerate() {
|
||||
bounds[axis] = bounds[axis].min(value);
|
||||
bounds[axis + 3] = bounds[axis + 3].max(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply the same INSERT transform to the feature edges.
|
||||
{
|
||||
let n = out.edge_verts.len();
|
||||
if out.edge_verts_low.len() != n {
|
||||
out.edge_verts_low = vec![[0.0; 3]; n];
|
||||
}
|
||||
for (v, vl) in out.edge_verts.iter_mut().zip(out.edge_verts_low.iter_mut()) {
|
||||
let w = xform.apply(Vector3::new(
|
||||
v[0] as f64 + vl[0] as f64,
|
||||
v[1] as f64 + vl[1] as f64,
|
||||
v[2] as f64 + vl[2] as f64,
|
||||
));
|
||||
let (hx, hy, hz) = (w.x as f32, w.y as f32, w.z as f32);
|
||||
*v = [hx, hy, hz];
|
||||
*vl = [
|
||||
(w.x - hx as f64) as f32,
|
||||
(w.y - hy as f64) as f32,
|
||||
(w.z - hz as f64) as f32,
|
||||
];
|
||||
}
|
||||
MeshLodSet {
|
||||
lods: Vec::new(),
|
||||
material: set.material.clone(),
|
||||
face_materials: set.face_materials.clone(),
|
||||
visual_style: set.visual_style.clone(),
|
||||
complete: set.complete,
|
||||
edge_verts: Vec::new(),
|
||||
edge_verts_low: Vec::new(),
|
||||
curved_gens: Vec::new(),
|
||||
metrics: set.metrics,
|
||||
world_aabb: [
|
||||
bounds[0] as f32,
|
||||
bounds[1] as f32,
|
||||
bounds[3] as f32,
|
||||
bounds[4] as f32,
|
||||
],
|
||||
z_aabb: [bounds[2] as f32, bounds[5] as f32],
|
||||
instance_source: Some(source),
|
||||
instance_transform: Some(*xform),
|
||||
instance_handle: None,
|
||||
instance_color: set.display_color(),
|
||||
instance_aabb: bounds.iter().all(|value| value.is_finite()).then_some(bounds),
|
||||
}
|
||||
if min_x.is_finite() {
|
||||
out.world_aabb = [min_x, min_y, max_x, max_y];
|
||||
}
|
||||
out.recompute_aabb();
|
||||
out
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -1538,8 +1509,7 @@ pub struct Scene {
|
|||
Arc<crate::scene::pick::interaction_index::InteractionIndex>,
|
||||
)>,
|
||||
>,
|
||||
/// Auxiliary broad phase for objects that have no wire representation:
|
||||
/// hatch-only and solid-only block instances.
|
||||
/// Auxiliary broad phase for hatch-only objects.
|
||||
interaction_handle_index_cache: RefCell<
|
||||
Option<(
|
||||
u64,
|
||||
|
|
@ -6169,15 +6139,11 @@ impl Scene {
|
|||
}
|
||||
let mut lookup: HashMap<Handle, Vec<u32>> = HashMap::default();
|
||||
for (index, set) in meshes.iter().enumerate() {
|
||||
let Some(value) = set
|
||||
.lods
|
||||
.first()
|
||||
.and_then(|mesh| mesh.name.parse::<u64>().ok())
|
||||
else {
|
||||
let Some(handle) = set.entity_handle() else {
|
||||
continue;
|
||||
};
|
||||
lookup
|
||||
.entry(Handle::new(value))
|
||||
.entry(handle)
|
||||
.or_default()
|
||||
.push(index as u32);
|
||||
}
|
||||
|
|
@ -6490,18 +6456,6 @@ impl Scene {
|
|||
true
|
||||
}
|
||||
|
||||
fn mesh_visible_for_interaction(&self, handle: Handle) -> bool {
|
||||
self.mesh_entity_visible(handle)
|
||||
&& self.document.get_entity(handle).is_some_and(|entity| {
|
||||
!self.interaction_layer_frozen(&entity.common().layer)
|
||||
&& self.belongs_to_visible_block(
|
||||
handle,
|
||||
entity.common().owner_handle,
|
||||
self.interaction_block_handle(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// One transformed mesh per block-definition solid instance reached from an
|
||||
/// INSERT owned by `layout_block`. Nested INSERTs accumulate their
|
||||
/// transform. Empty when no block solids exist. (#123)
|
||||
|
|
@ -6541,8 +6495,7 @@ impl Scene {
|
|||
return;
|
||||
};
|
||||
let inherit = self.mesh_inherit_for_path(&context.insert_path);
|
||||
let own_alpha =
|
||||
set.lods.first().map(|mesh| mesh.color[3]).unwrap_or(1.0);
|
||||
let own_alpha = set.display_color().map_or(1.0, |color| color[3]);
|
||||
let mut transformed =
|
||||
transform_block_mesh_lod_set(set, &context.transform);
|
||||
if let Some(color) = self.block_mesh_override_color(
|
||||
|
|
@ -6551,9 +6504,7 @@ impl Scene {
|
|||
inherit.as_ref(),
|
||||
own_alpha,
|
||||
) {
|
||||
for mesh in &mut transformed.lods {
|
||||
mesh.color = color;
|
||||
}
|
||||
transformed.instance_color = Some(color);
|
||||
if let Some(material) = transformed.material.as_mut() {
|
||||
if material.handle.is_none() {
|
||||
material.diffuse = color;
|
||||
|
|
@ -6569,10 +6520,7 @@ impl Scene {
|
|||
self.material_base_dir.as_deref(),
|
||||
);
|
||||
}
|
||||
let root_name = context.root_handle.value().to_string();
|
||||
for mesh in &mut transformed.lods {
|
||||
mesh.name = root_name.clone();
|
||||
}
|
||||
transformed.instance_handle = Some(context.root_handle);
|
||||
out.push(transformed);
|
||||
},
|
||||
);
|
||||
|
|
@ -7449,20 +7397,6 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
}
|
||||
for set in self.interaction_meshes_arc().iter() {
|
||||
let Some(mesh) = set.lods.first() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(handle) = mesh.name.parse::<u64>() else {
|
||||
continue;
|
||||
};
|
||||
if !self.mesh_visible_for_interaction(Handle::new(handle)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(aabb) = mesh_interaction_aabb(set) {
|
||||
entries.push((handle, aabb));
|
||||
}
|
||||
}
|
||||
let index =
|
||||
Arc::new(crate::scene::pick::interaction_index::InteractionHandleIndex::build(entries));
|
||||
*self.interaction_handle_index_cache.borrow_mut() =
|
||||
|
|
@ -7677,7 +7611,6 @@ impl Scene {
|
|||
.collect();
|
||||
let insert_hatches = self.insert_hatches_for_click();
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let mesh_lookup = self.mesh_pick_lookup(&meshes);
|
||||
handles.extend(
|
||||
self.interaction_handle_index()
|
||||
.query_xy(aabb)
|
||||
|
|
@ -7685,12 +7618,19 @@ impl Scene {
|
|||
.filter_map(|value| {
|
||||
let handle = Handle::new(value);
|
||||
(self.hatch_visible_for_interaction(handle)
|
||||
|| insert_hatches.contains_key(&handle)
|
||||
|| (mesh_lookup.contains_key(&handle)
|
||||
&& self.mesh_visible_for_interaction(handle)))
|
||||
|| insert_hatches.contains_key(&handle))
|
||||
.then_some(handle)
|
||||
}),
|
||||
);
|
||||
handles.extend(meshes.iter().filter_map(|set| {
|
||||
let handle = set.entity_handle()?;
|
||||
let bounds = mesh_interaction_aabb(set)?;
|
||||
(bounds[3] >= aabb[0]
|
||||
&& bounds[0] <= aabb[2]
|
||||
&& bounds[4] >= aabb[1]
|
||||
&& bounds[1] <= aabb[3])
|
||||
.then_some(handle)
|
||||
}));
|
||||
handles
|
||||
}
|
||||
|
||||
|
|
@ -7703,22 +7643,17 @@ impl Scene {
|
|||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
candidate_handles: Option<&HashSet<Handle>>,
|
||||
_candidate_handles: Option<&HashSet<Handle>>,
|
||||
) -> Vec<Handle> {
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let lookup = self.mesh_pick_lookup(&meshes);
|
||||
let handles: Vec<Handle> = candidate_handles.map_or_else(
|
||||
|| lookup.keys().copied().collect(),
|
||||
|candidates| candidates.iter().copied().collect(),
|
||||
);
|
||||
let handles: Vec<Handle> = lookup.keys().copied().collect();
|
||||
let mut out = Vec::new();
|
||||
for handle in handles {
|
||||
if !self.mesh_visible_for_interaction(handle)
|
||||
|| matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
)
|
||||
{
|
||||
if matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let Some(indices) = lookup.get(&handle) else {
|
||||
|
|
@ -7728,12 +7663,12 @@ impl Scene {
|
|||
.iter()
|
||||
.filter_map(|&index| meshes.get(index as usize))
|
||||
.any(|set| {
|
||||
set.lods.first().is_some_and(|mesh| {
|
||||
set.geometry_lods().first().is_some_and(|mesh| {
|
||||
!pick::hit_test::mesh_box_hit(
|
||||
a,
|
||||
b,
|
||||
crossing,
|
||||
std::iter::once((handle, mesh)),
|
||||
std::iter::once((handle, mesh, set.instance_transform)),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
|
|
@ -7877,22 +7812,17 @@ impl Scene {
|
|||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
candidate_handles: Option<&HashSet<Handle>>,
|
||||
_candidate_handles: Option<&HashSet<Handle>>,
|
||||
) -> Vec<Handle> {
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let lookup = self.mesh_pick_lookup(&meshes);
|
||||
let handles: Vec<Handle> = candidate_handles.map_or_else(
|
||||
|| lookup.keys().copied().collect(),
|
||||
|candidates| candidates.iter().copied().collect(),
|
||||
);
|
||||
let handles: Vec<Handle> = lookup.keys().copied().collect();
|
||||
let mut out = Vec::new();
|
||||
for handle in handles {
|
||||
if !self.mesh_visible_for_interaction(handle)
|
||||
|| matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
)
|
||||
{
|
||||
if matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let Some(indices) = lookup.get(&handle) else {
|
||||
|
|
@ -7902,11 +7832,11 @@ impl Scene {
|
|||
.iter()
|
||||
.filter_map(|&index| meshes.get(index as usize))
|
||||
.any(|set| {
|
||||
set.lods.first().is_some_and(|mesh| {
|
||||
set.geometry_lods().first().is_some_and(|mesh| {
|
||||
!pick::hit_test::mesh_poly_hit(
|
||||
poly,
|
||||
crossing,
|
||||
std::iter::once((handle, mesh)),
|
||||
std::iter::once((handle, mesh, set.instance_transform)),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
|
|
@ -7971,60 +7901,34 @@ impl Scene {
|
|||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
candidate_handles: Option<&HashSet<Handle>>,
|
||||
_candidate_handles: Option<&HashSet<Handle>>,
|
||||
coarse_lod: bool,
|
||||
) -> Option<Handle> {
|
||||
// Reuse the renderer's expanded mesh set (top-level solids + per-INSERT
|
||||
// block instances), cached per geometry epoch — so hover no longer
|
||||
// re-expands every block instance on each move. Every `MeshLodSet`
|
||||
// carries its handle (in `mesh.name`) and a 3D AABB.
|
||||
// Reuse the renderer's cached top-level and block-instance mesh set.
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let lookup = candidate_handles.map(|_| self.mesh_pick_lookup(&meshes));
|
||||
// Candidate handles already came from the precise f64 interaction BVH.
|
||||
// When no index is active the source is small/non-resident, so exact
|
||||
// triangles are safer than reintroducing the old f32 AABB precision loss.
|
||||
let mut sets: Vec<(Handle, &MeshLodSet)> = Vec::new();
|
||||
if let (Some(handles), Some(lookup)) = (candidate_handles, lookup.as_ref()) {
|
||||
for handle in handles {
|
||||
if !self.mesh_visible_for_interaction(*handle) {
|
||||
continue;
|
||||
}
|
||||
let Some(indices) = lookup.get(handle) else {
|
||||
continue;
|
||||
};
|
||||
sets.extend(
|
||||
indices
|
||||
.iter()
|
||||
.filter_map(|&index| meshes.get(index as usize).map(|set| (*handle, set))),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sets.extend(meshes.iter().filter_map(|set| {
|
||||
let handle = set
|
||||
.lods
|
||||
.first()?
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(Handle::new)?;
|
||||
self.mesh_visible_for_interaction(handle)
|
||||
.then_some((handle, set))
|
||||
}));
|
||||
}
|
||||
if coarse_lod {
|
||||
return pick::hit_test::mesh_click_hit(
|
||||
cursor,
|
||||
sets.iter()
|
||||
.filter_map(|(handle, set)| set.lods.last().map(|mesh| (*handle, mesh))),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
let sets: Vec<(Handle, &MeshLodSet)> = meshes
|
||||
.iter()
|
||||
.filter_map(|set| {
|
||||
let handle = set.entity_handle()?;
|
||||
Some((handle, set))
|
||||
})
|
||||
.collect();
|
||||
pick::hit_test::mesh_click_hit(
|
||||
cursor,
|
||||
sets.iter()
|
||||
.filter_map(|(handle, set)| set.lods.first().map(|mesh| (*handle, mesh))),
|
||||
sets.iter().filter_map(|(handle, set)| {
|
||||
let lods = set.geometry_lods();
|
||||
let mesh = if coarse_lod {
|
||||
lods.last()?
|
||||
} else {
|
||||
lods.first()?
|
||||
};
|
||||
Some((
|
||||
*handle,
|
||||
mesh,
|
||||
set.instance_transform,
|
||||
mesh_interaction_aabb(set)?,
|
||||
))
|
||||
}),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
|
|
@ -8043,24 +7947,20 @@ impl Scene {
|
|||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
candidate_handles: Option<&HashSet<Handle>>,
|
||||
_candidate_handles: Option<&HashSet<Handle>>,
|
||||
) -> Vec<Handle> {
|
||||
if self.block_meshes.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let lookup = self.mesh_pick_lookup(&meshes);
|
||||
let handles: Vec<Handle> = candidate_handles.map_or_else(
|
||||
|| lookup.keys().copied().collect(),
|
||||
|candidates| candidates.iter().copied().collect(),
|
||||
);
|
||||
let handles: Vec<Handle> = lookup.keys().copied().collect();
|
||||
let mut out = Vec::new();
|
||||
for handle in handles {
|
||||
if !matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
) || !self.mesh_visible_for_interaction(handle)
|
||||
{
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let Some(indices) = lookup.get(&handle) else {
|
||||
|
|
@ -8070,12 +7970,12 @@ impl Scene {
|
|||
.iter()
|
||||
.filter_map(|&index| meshes.get(index as usize))
|
||||
.any(|set| {
|
||||
set.lods.first().map_or(false, |m| {
|
||||
set.geometry_lods().first().is_some_and(|mesh| {
|
||||
!pick::hit_test::mesh_box_hit(
|
||||
a,
|
||||
b,
|
||||
crossing,
|
||||
std::iter::once((handle, m)),
|
||||
std::iter::once((handle, mesh, set.instance_transform)),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
|
|
@ -8098,24 +7998,20 @@ impl Scene {
|
|||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
candidate_handles: Option<&HashSet<Handle>>,
|
||||
_candidate_handles: Option<&HashSet<Handle>>,
|
||||
) -> Vec<Handle> {
|
||||
if self.block_meshes.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let meshes = self.interaction_meshes_arc();
|
||||
let lookup = self.mesh_pick_lookup(&meshes);
|
||||
let handles: Vec<Handle> = candidate_handles.map_or_else(
|
||||
|| lookup.keys().copied().collect(),
|
||||
|candidates| candidates.iter().copied().collect(),
|
||||
);
|
||||
let handles: Vec<Handle> = lookup.keys().copied().collect();
|
||||
let mut out = Vec::new();
|
||||
for handle in handles {
|
||||
if !matches!(
|
||||
self.document.get_entity(handle),
|
||||
Some(EntityType::Insert(_))
|
||||
) || !self.mesh_visible_for_interaction(handle)
|
||||
{
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let Some(indices) = lookup.get(&handle) else {
|
||||
|
|
@ -8125,11 +8021,11 @@ impl Scene {
|
|||
.iter()
|
||||
.filter_map(|&index| meshes.get(index as usize))
|
||||
.any(|set| {
|
||||
set.lods.first().map_or(false, |m| {
|
||||
set.geometry_lods().first().is_some_and(|mesh| {
|
||||
!pick::hit_test::mesh_poly_hit(
|
||||
poly,
|
||||
crossing,
|
||||
std::iter::once((handle, m)),
|
||||
std::iter::once((handle, mesh, set.instance_transform)),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
|
|
|
|||
|
|
@ -221,8 +221,12 @@ impl MeshMaterial {
|
|||
}
|
||||
|
||||
pub fn apply_to(&self, set: &mut super::mesh_model::MeshLodSet) {
|
||||
for lod in &mut set.lods {
|
||||
lod.color = self.diffuse;
|
||||
if set.instance_source.is_some() {
|
||||
set.instance_color = Some(self.diffuse);
|
||||
} else {
|
||||
for lod in &mut set.lods {
|
||||
lod.color = self.diffuse;
|
||||
}
|
||||
}
|
||||
set.material = Some(self.clone());
|
||||
}
|
||||
|
|
@ -236,7 +240,7 @@ impl MeshMaterial {
|
|||
self.apply_to(set);
|
||||
set.face_materials.clear();
|
||||
let handles: rustc_hash::FxHashSet<Handle> = set
|
||||
.lods
|
||||
.geometry_lods()
|
||||
.iter()
|
||||
.flat_map(|lod| lod.triangle_material_handles.iter().flatten().copied())
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ pub struct MeshLodSet {
|
|||
pub instance_source: Option<std::sync::Arc<MeshInstanceSource>>,
|
||||
/// Accumulated block-local → world transform for this rendered instance.
|
||||
pub instance_transform: Option<acadrust::types::Transform>,
|
||||
/// Parent INSERT selected for this rendered block instance.
|
||||
pub instance_handle: Option<acadrust::Handle>,
|
||||
/// Effective colour after INSERT inheritance, without copying the mesh.
|
||||
pub instance_color: Option<[f32; 4]>,
|
||||
/// Precise world bounds used by the interaction index.
|
||||
pub instance_aabb: Option<[f64; 6]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -112,6 +118,7 @@ pub struct MeshInstanceSource {
|
|||
pub lods: Vec<MeshModel>,
|
||||
pub edge_verts: Vec<[f32; 3]>,
|
||||
pub edge_verts_low: Vec<[f32; 3]>,
|
||||
pub curved_gens: Vec<CurvedGen>,
|
||||
}
|
||||
|
||||
/// 3D bounds of every LOD's vertices: `([min_x, min_y, max_x, max_y], [min_z, max_z])`.
|
||||
|
|
@ -225,6 +232,9 @@ impl MeshLodSet {
|
|||
z_aabb,
|
||||
instance_source: None,
|
||||
instance_transform: None,
|
||||
instance_handle: None,
|
||||
instance_color: None,
|
||||
instance_aabb: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +260,35 @@ impl MeshLodSet {
|
|||
lods: self.lods.clone(),
|
||||
edge_verts: self.edge_verts.clone(),
|
||||
edge_verts_low: self.edge_verts_low.clone(),
|
||||
curved_gens: self.curved_gens.clone(),
|
||||
}));
|
||||
self.instance_transform = None;
|
||||
self.instance_handle = None;
|
||||
self.instance_color = None;
|
||||
self.instance_aabb = None;
|
||||
}
|
||||
|
||||
pub fn geometry_lods(&self) -> &[MeshModel] {
|
||||
self.instance_source
|
||||
.as_ref()
|
||||
.map_or(self.lods.as_slice(), |source| source.lods.as_slice())
|
||||
}
|
||||
|
||||
pub fn entity_handle(&self) -> Option<acadrust::Handle> {
|
||||
self.instance_handle.or_else(|| {
|
||||
self.lods
|
||||
.first()
|
||||
.and_then(|mesh| mesh.name.parse::<u64>().ok())
|
||||
.map(acadrust::Handle::new)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn display_color(&self) -> Option<[f32; 4]> {
|
||||
self.instance_color.or_else(|| {
|
||||
self.geometry_lods()
|
||||
.iter()
|
||||
.find(|mesh| !mesh.indices.is_empty())
|
||||
.map(|mesh| mesh.color)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -658,64 +658,183 @@ pub fn click_hits_all<'a, W: WireSource + ?Sized>(
|
|||
hits.into_iter().map(|(_, name)| name).collect()
|
||||
}
|
||||
|
||||
pub fn mesh_click_hit<'a>(
|
||||
pub(crate) fn mesh_click_hit<'a>(
|
||||
cursor: Point,
|
||||
meshes: impl Iterator<Item = (Handle, &'a MeshModel)>,
|
||||
meshes: impl Iterator<
|
||||
Item = (
|
||||
Handle,
|
||||
&'a MeshModel,
|
||||
Option<acadrust::types::Transform>,
|
||||
[f64; 6],
|
||||
),
|
||||
>,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
) -> Option<Handle> {
|
||||
let mut best: Option<(f32, Handle)> = None;
|
||||
for (handle, mesh) in meshes {
|
||||
let v = &mesh.verts;
|
||||
let idx = &mesh.indices;
|
||||
let lo = &mesh.verts_low;
|
||||
// Indexed meshes reuse most vertices across several triangles.
|
||||
// Project once per vertex instead of three matrix transforms per
|
||||
// triangle; dense solid misses are otherwise the worst-case hover.
|
||||
let projected: Vec<(Point, f32)> = v
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &vertex)| {
|
||||
let ndc =
|
||||
view_rot.project_point3((mesh_vert(vertex, lo, i) - eye).as_vec3());
|
||||
(
|
||||
Point::new(
|
||||
(ndc.x + 1.0) * 0.5 * bounds.width,
|
||||
(1.0 - ndc.y) * 0.5 * bounds.height,
|
||||
let profile = crate::perf::enabled().then(std::time::Instant::now);
|
||||
let mut set_count = 0usize;
|
||||
let mut bound_hits = 0usize;
|
||||
let mut exact_triangles = 0usize;
|
||||
let ndc = glam::Vec3::new(
|
||||
cursor.x / bounds.width * 2.0 - 1.0,
|
||||
1.0 - cursor.y / bounds.height * 2.0,
|
||||
0.0,
|
||||
);
|
||||
let inverse_view = view_rot.inverse();
|
||||
let near = eye + inverse_view.project_point3(ndc).as_dvec3();
|
||||
let far = eye
|
||||
+ inverse_view
|
||||
.project_point3(glam::Vec3::new(ndc.x, ndc.y, 1.0))
|
||||
.as_dvec3();
|
||||
let world_direction = (far - near).normalize_or_zero();
|
||||
if !near.is_finite() || !world_direction.is_finite() || world_direction.length_squared() < 1e-18
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let mut best: Option<(f64, Handle)> = None;
|
||||
for (handle, mesh, transform, aabb) in meshes {
|
||||
set_count += 1;
|
||||
let Some((near_t, _)) = ray_aabb(near, world_direction, aabb) else {
|
||||
continue;
|
||||
};
|
||||
bound_hits += 1;
|
||||
exact_triangles += mesh.indices.len() / 3;
|
||||
if best.is_some_and(|(distance, _)| near_t > distance) {
|
||||
continue;
|
||||
}
|
||||
let model = transform.map(codec_transform_matrix);
|
||||
let (origin, direction) = if let Some(model) = model {
|
||||
if !model.is_finite() || model.determinant().abs() <= 1e-18 {
|
||||
continue;
|
||||
}
|
||||
let inverse = model.inverse();
|
||||
let origin = inverse.transform_point3(near);
|
||||
let direction = inverse.transform_vector3(world_direction).normalize_or_zero();
|
||||
(origin, direction)
|
||||
} else {
|
||||
(near, world_direction)
|
||||
};
|
||||
if direction.length_squared() < 1e-18 {
|
||||
continue;
|
||||
}
|
||||
let local_t = mesh
|
||||
.indices
|
||||
.chunks_exact(3)
|
||||
.filter_map(|triangle| {
|
||||
ray_triangle(
|
||||
origin,
|
||||
direction,
|
||||
mesh_vert(
|
||||
mesh.verts[triangle[0] as usize],
|
||||
&mesh.verts_low,
|
||||
triangle[0] as usize,
|
||||
),
|
||||
mesh_vert(
|
||||
mesh.verts[triangle[1] as usize],
|
||||
&mesh.verts_low,
|
||||
triangle[1] as usize,
|
||||
),
|
||||
mesh_vert(
|
||||
mesh.verts[triangle[2] as usize],
|
||||
&mesh.verts_low,
|
||||
triangle[2] as usize,
|
||||
),
|
||||
ndc.z,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let mut t = 0;
|
||||
while t + 2 < idx.len() {
|
||||
let tri = [idx[t] as usize, idx[t + 1] as usize, idx[t + 2] as usize];
|
||||
t += 3;
|
||||
let mut sp = [Point::ORIGIN; 3];
|
||||
let mut depth = 0.0f32;
|
||||
for (j, &k) in tri.iter().enumerate() {
|
||||
let (point, z) = projected[k];
|
||||
sp[j] = point;
|
||||
depth += z;
|
||||
}
|
||||
if point_in_polygon(cursor, &sp) {
|
||||
let d = depth / 3.0;
|
||||
if best.map_or(true, |(bd, _)| d < bd) {
|
||||
best = Some((d, handle));
|
||||
}
|
||||
break; // one hit per mesh is enough
|
||||
.min_by(f64::total_cmp);
|
||||
if let Some(local_t) = local_t {
|
||||
let local_hit = origin + direction * local_t;
|
||||
let world_hit = model.map_or(local_hit, |model| model.transform_point3(local_hit));
|
||||
let distance = (world_hit - near).dot(world_direction);
|
||||
if distance >= 0.0 && best.is_none_or(|(current, _)| distance < current) {
|
||||
best = Some((distance, handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(started) = profile {
|
||||
crate::perf_record!(
|
||||
"[perf] mesh-ray {:>7.1}ms sets={} bounds={} source_triangles={}",
|
||||
started.elapsed().as_secs_f64() * 1000.0,
|
||||
set_count,
|
||||
bound_hits,
|
||||
exact_triangles,
|
||||
);
|
||||
}
|
||||
best.map(|(_, h)| h)
|
||||
}
|
||||
|
||||
fn codec_transform_matrix(transform: acadrust::types::Transform) -> glam::DMat4 {
|
||||
let matrix = transform.matrix.m;
|
||||
glam::DMat4::from_cols_array(&[
|
||||
matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],
|
||||
matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],
|
||||
matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],
|
||||
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3],
|
||||
])
|
||||
}
|
||||
|
||||
fn ray_aabb(origin: glam::DVec3, direction: glam::DVec3, aabb: [f64; 6]) -> Option<(f64, f64)> {
|
||||
let mut near = 0.0_f64;
|
||||
let mut far = f64::INFINITY;
|
||||
for axis in 0..3 {
|
||||
let origin = origin[axis];
|
||||
let direction = direction[axis];
|
||||
if direction.abs() <= 1e-18 {
|
||||
if origin < aabb[axis] || origin > aabb[axis + 3] {
|
||||
return None;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let first = (aabb[axis] - origin) / direction;
|
||||
let second = (aabb[axis + 3] - origin) / direction;
|
||||
near = near.max(first.min(second));
|
||||
far = far.min(first.max(second));
|
||||
if far < near {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some((near, far))
|
||||
}
|
||||
|
||||
fn ray_triangle(
|
||||
origin: glam::DVec3,
|
||||
direction: glam::DVec3,
|
||||
a: glam::DVec3,
|
||||
b: glam::DVec3,
|
||||
c: glam::DVec3,
|
||||
) -> Option<f64> {
|
||||
let edge1 = b - a;
|
||||
let edge2 = c - a;
|
||||
let cross = direction.cross(edge2);
|
||||
let determinant = edge1.dot(cross);
|
||||
if determinant.abs() <= 1e-12 {
|
||||
return None;
|
||||
}
|
||||
let inverse = determinant.recip();
|
||||
let offset = origin - a;
|
||||
let u = offset.dot(cross) * inverse;
|
||||
if !(0.0..=1.0).contains(&u) {
|
||||
return None;
|
||||
}
|
||||
let q = offset.cross(edge1);
|
||||
let v = direction.dot(q) * inverse;
|
||||
if v < 0.0 || u + v > 1.0 {
|
||||
return None;
|
||||
}
|
||||
let distance = edge2.dot(q) * inverse;
|
||||
(distance >= 0.0).then_some(distance)
|
||||
}
|
||||
|
||||
/// Reconstruct a mesh vertex's absolute f64 position from its high/low pair —
|
||||
/// without the low residual the f32 high alone is ~0.5 m off at UTM scale and
|
||||
/// box / lasso / face selection lands on the wrong place.
|
||||
#[inline]
|
||||
fn mesh_vert(hi: [f32; 3], low: &[[f32; 3]], i: usize) -> glam::DVec3 {
|
||||
fn mesh_vert(
|
||||
hi: [f32; 3],
|
||||
low: &[[f32; 3]],
|
||||
i: usize,
|
||||
) -> glam::DVec3 {
|
||||
let l = low.get(i).copied().unwrap_or([0.0; 3]);
|
||||
glam::DVec3::new(
|
||||
hi[0] as f64 + l[0] as f64,
|
||||
|
|
@ -727,6 +846,7 @@ fn mesh_vert(hi: [f32; 3], low: &[[f32; 3]], i: usize) -> glam::DVec3 {
|
|||
/// Project a mesh's vertices to screen space.
|
||||
fn project_mesh_verts(
|
||||
mesh: &MeshModel,
|
||||
transform: Option<acadrust::types::Transform>,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
|
|
@ -735,7 +855,12 @@ fn project_mesh_verts(
|
|||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &w)| {
|
||||
let ndc = view_rot.project_point3((mesh_vert(w, &mesh.verts_low, i) - eye).as_vec3());
|
||||
let point = mesh_vert(w, &mesh.verts_low, i);
|
||||
let point = transform.map_or(point, |transform| {
|
||||
let point = transform.apply(acadrust::types::Vector3::new(point.x, point.y, point.z));
|
||||
glam::DVec3::new(point.x, point.y, point.z)
|
||||
});
|
||||
let ndc = view_rot.project_point3((point - eye).as_vec3());
|
||||
Point::new(
|
||||
(ndc.x + 1.0) * 0.5 * bounds.width,
|
||||
(1.0 - ndc.y) * 0.5 * bounds.height,
|
||||
|
|
@ -769,7 +894,13 @@ pub fn mesh_box_hit<'a>(
|
|||
a: Point,
|
||||
b: Point,
|
||||
crossing: bool,
|
||||
meshes: impl Iterator<Item = (Handle, &'a MeshModel)>,
|
||||
meshes: impl Iterator<
|
||||
Item = (
|
||||
Handle,
|
||||
&'a MeshModel,
|
||||
Option<acadrust::types::Transform>,
|
||||
),
|
||||
>,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
|
|
@ -784,8 +915,8 @@ pub fn mesh_box_hit<'a>(
|
|||
Point::new(min_x, max_y),
|
||||
];
|
||||
let mut out = Vec::new();
|
||||
for (h, mesh) in meshes {
|
||||
let proj = project_mesh_verts(mesh, view_rot, eye, bounds);
|
||||
for (h, mesh, transform) in meshes {
|
||||
let proj = project_mesh_verts(mesh, transform, view_rot, eye, bounds);
|
||||
if proj.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -807,7 +938,13 @@ pub fn mesh_box_hit<'a>(
|
|||
pub fn mesh_poly_hit<'a>(
|
||||
poly: &[Point],
|
||||
crossing: bool,
|
||||
meshes: impl Iterator<Item = (Handle, &'a MeshModel)>,
|
||||
meshes: impl Iterator<
|
||||
Item = (
|
||||
Handle,
|
||||
&'a MeshModel,
|
||||
Option<acadrust::types::Transform>,
|
||||
),
|
||||
>,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
|
|
@ -816,8 +953,8 @@ pub fn mesh_poly_hit<'a>(
|
|||
return Vec::new();
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for (h, mesh) in meshes {
|
||||
let proj = project_mesh_verts(mesh, view_rot, eye, bounds);
|
||||
for (h, mesh, transform) in meshes {
|
||||
let proj = project_mesh_verts(mesh, transform, view_rot, eye, bounds);
|
||||
if proj.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -975,8 +975,9 @@ fn mesh_spatial_key(set: &MeshLodSet, bounds: [f32; 6]) -> u64 {
|
|||
struct MeshBatchPart<'a> {
|
||||
set: &'a MeshLodSet,
|
||||
mesh: &'a MeshModel,
|
||||
display_mesh: &'a MeshModel,
|
||||
uv_mesh: &'a MeshModel,
|
||||
entity_handle: Option<acadrust::Handle>,
|
||||
display_color: [f32; 4],
|
||||
material: Option<&'a crate::scene::model::material_model::MeshMaterial>,
|
||||
color: [f32; 4],
|
||||
indices: std::sync::Arc<[u32]>,
|
||||
|
|
@ -1157,9 +1158,7 @@ fn build_instanced_chunk(
|
|||
.set
|
||||
.visual_style
|
||||
.as_ref()
|
||||
.map_or(first.display_mesh.color, |style| {
|
||||
style.edge_color(first.display_mesh.color)
|
||||
});
|
||||
.map_or(first.display_color, |style| style.edge_color(first.display_color));
|
||||
let edge_key = (source_handle, edge_color.map(f32::to_bits));
|
||||
let shared_edge_buffer = first
|
||||
.include_edges
|
||||
|
|
@ -1229,13 +1228,7 @@ fn build_instanced_chunk(
|
|||
bounds[3] = bounds[3].max(part.set.world_aabb[2]);
|
||||
bounds[4] = bounds[4].max(part.set.world_aabb[3]);
|
||||
bounds[5] = bounds[5].max(part.set.z_aabb[1]);
|
||||
if let Some(handle) = part
|
||||
.display_mesh
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(acadrust::Handle::new)
|
||||
{
|
||||
if let Some(handle) = part.entity_handle {
|
||||
handles.insert(handle);
|
||||
if first.include_faces {
|
||||
highlights.push(MeshBatchRange {
|
||||
|
|
@ -1332,24 +1325,6 @@ fn mesh_bounds(mesh: &MeshModel) -> [f32; 6] {
|
|||
bounds
|
||||
}
|
||||
|
||||
fn material_uses_model_mapper(
|
||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||
) -> bool {
|
||||
material.is_some_and(|material| {
|
||||
[
|
||||
&material.diffuse_map,
|
||||
&material.specular_map,
|
||||
&material.reflection_map,
|
||||
&material.opacity_map,
|
||||
&material.bump_map,
|
||||
&material.refraction_map,
|
||||
&material.normal_map,
|
||||
]
|
||||
.into_iter()
|
||||
.any(|map| map.image.is_some() && map.auto_transform & 4 != 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn material_is_transparent(
|
||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||
color: [f32; 4],
|
||||
|
|
@ -1557,34 +1532,30 @@ pub fn build_mesh_batch_filtered(
|
|||
let mut face_partitions: rustc_hash::FxHashMap<FacePartitionKey, Vec<CachedFacePart<'_>>> =
|
||||
rustc_hash::FxHashMap::default();
|
||||
for set in sets {
|
||||
let Some(display_mesh) = set.lods.iter().find(|mesh| !mesh.indices.is_empty()) else {
|
||||
let Some(mesh) = set
|
||||
.geometry_lods()
|
||||
.iter()
|
||||
.find(|mesh| !mesh.indices.is_empty())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let display_handle = display_mesh
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(acadrust::Handle::new);
|
||||
let display_handle = set.entity_handle();
|
||||
if handles.is_some_and(|wanted| {
|
||||
display_handle.is_none_or(|handle| !wanted.contains(&handle))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
let source_mesh = set
|
||||
.instance_source
|
||||
.as_ref()
|
||||
.and_then(|source| source.lods.iter().find(|mesh| !mesh.indices.is_empty()));
|
||||
let mesh = source_mesh.unwrap_or(display_mesh);
|
||||
let display_color = set.display_color().unwrap_or(mesh.color);
|
||||
let source_identity = set.instance_source.as_ref().map_or_else(
|
||||
|| (false, display_mesh as *const MeshModel as usize as u64),
|
||||
|| (false, mesh as *const MeshModel as usize as u64),
|
||||
|source| (true, source.handle.value()),
|
||||
);
|
||||
let triangle_count = display_mesh.indices.len() / 3;
|
||||
let triangle_count = mesh.indices.len() / 3;
|
||||
let has_face_materials =
|
||||
display_mesh.triangle_material_handles.len() == triangle_count
|
||||
mesh.triangle_material_handles.len() == triangle_count
|
||||
&& !set.face_materials.is_empty();
|
||||
let has_face_colors = display_mesh.triangle_colors.len() == triangle_count
|
||||
&& display_mesh.triangle_colors.iter().any(Option::is_some);
|
||||
let has_face_colors = mesh.triangle_colors.len() == triangle_count
|
||||
&& mesh.triangle_colors.iter().any(Option::is_some);
|
||||
let include_faces = set
|
||||
.visual_style
|
||||
.as_ref()
|
||||
|
|
@ -1595,7 +1566,7 @@ pub fn build_mesh_batch_filtered(
|
|||
.map_or(true, |style| style.edges_visible());
|
||||
if !has_face_materials && !has_face_colors {
|
||||
let base_color =
|
||||
set.material.as_ref().map_or(display_mesh.color, |material| material.diffuse);
|
||||
set.material.as_ref().map_or(display_color, |material| material.diffuse);
|
||||
let color = set
|
||||
.visual_style
|
||||
.as_ref()
|
||||
|
|
@ -1603,7 +1574,7 @@ pub fn build_mesh_batch_filtered(
|
|||
let (shared_indices, shared_hash) = source_indices
|
||||
.entry(source_identity)
|
||||
.or_insert_with(|| {
|
||||
let indices = std::sync::Arc::<[u32]>::from(display_mesh.indices.as_slice());
|
||||
let indices = std::sync::Arc::<[u32]>::from(mesh.indices.as_slice());
|
||||
let hash = index_hash(indices.as_ref());
|
||||
(indices, hash)
|
||||
})
|
||||
|
|
@ -1611,8 +1582,9 @@ pub fn build_mesh_batch_filtered(
|
|||
ordered.push(MeshBatchPart {
|
||||
set,
|
||||
mesh,
|
||||
display_mesh,
|
||||
uv_mesh: mesh,
|
||||
entity_handle: display_handle,
|
||||
display_color,
|
||||
material: set.material.as_ref(),
|
||||
color,
|
||||
indices: shared_indices,
|
||||
|
|
@ -1637,10 +1609,10 @@ pub fn build_mesh_batch_filtered(
|
|||
let partition_key = FacePartitionKey {
|
||||
instanced: source_identity.0,
|
||||
source: source_identity.1,
|
||||
base_material: material_key(set.material.as_ref(), display_mesh.color),
|
||||
base_material: material_key(set.material.as_ref(), display_color),
|
||||
face_materials,
|
||||
visual_style: visual_style_partition_key(set.visual_style.as_ref()),
|
||||
display_color: display_mesh.color.map(f32::to_bits),
|
||||
display_color: display_color.map(f32::to_bits),
|
||||
include_faces,
|
||||
include_edges,
|
||||
};
|
||||
|
|
@ -1655,18 +1627,18 @@ pub fn build_mesh_batch_filtered(
|
|||
Vec<u32>,
|
||||
),
|
||||
> = std::collections::BTreeMap::new();
|
||||
for (triangle, indices) in display_mesh.indices.chunks_exact(3).enumerate() {
|
||||
for (triangle, indices) in mesh.indices.chunks_exact(3).enumerate() {
|
||||
let material = if has_face_materials {
|
||||
display_mesh.triangle_material_handles[triangle]
|
||||
mesh.triangle_material_handles[triangle]
|
||||
.and_then(|handle| set.face_materials.get(&handle))
|
||||
.or(set.material.as_ref())
|
||||
} else {
|
||||
set.material.as_ref()
|
||||
};
|
||||
let base_color =
|
||||
material.map_or(display_mesh.color, |material| material.diffuse);
|
||||
material.map_or(display_color, |material| material.diffuse);
|
||||
let base_color = if has_face_colors {
|
||||
display_mesh.triangle_colors[triangle].unwrap_or(base_color)
|
||||
mesh.triangle_colors[triangle].unwrap_or(base_color)
|
||||
} else {
|
||||
base_color
|
||||
};
|
||||
|
|
@ -1700,8 +1672,9 @@ pub fn build_mesh_batch_filtered(
|
|||
ordered.push(MeshBatchPart {
|
||||
set,
|
||||
mesh,
|
||||
display_mesh,
|
||||
uv_mesh: mesh,
|
||||
entity_handle: display_handle,
|
||||
display_color,
|
||||
material: part.material,
|
||||
color: part.color,
|
||||
indices: part.indices,
|
||||
|
|
@ -1738,25 +1711,34 @@ pub fn build_mesh_batch_filtered(
|
|||
mesh_spatial_key(part.set, spatial_bounds),
|
||||
)
|
||||
});
|
||||
let storage_instancing =
|
||||
device.limits().max_storage_buffers_per_shader_stage > 0;
|
||||
let storage_instancing = device.limits().max_storage_buffers_per_shader_stage > 0;
|
||||
let mut instance_groups: std::collections::BTreeMap<
|
||||
InstanceGroupKey,
|
||||
Vec<MeshBatchPart<'_>>,
|
||||
> = std::collections::BTreeMap::new();
|
||||
let mut direct_parts = Vec::with_capacity(ordered.len());
|
||||
for mut part in ordered {
|
||||
let eligible = storage_instancing
|
||||
&& part.set.instance_transform.is_some()
|
||||
for part in ordered {
|
||||
let eligible = part.set.instance_transform.is_some()
|
||||
&& part.set.instance_source.is_some()
|
||||
&& !material_uses_model_mapper(part.material)
|
||||
&& part.mesh.verts.len() <= max_verts
|
||||
&& part.indices.len() / 3 <= max_tris
|
||||
&& part
|
||||
.mesh
|
||||
.verts
|
||||
.len()
|
||||
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
||||
<= hard_budget
|
||||
&& part.indices.len().saturating_mul(2 * std::mem::size_of::<u32>())
|
||||
<= hard_budget
|
||||
&& part
|
||||
.set
|
||||
.instance_source
|
||||
.as_ref()
|
||||
.is_some_and(|source| source.edge_verts.len() <= max_verts)
|
||||
.is_some_and(|source| {
|
||||
source
|
||||
.edge_verts
|
||||
.len()
|
||||
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
||||
<= hard_budget
|
||||
})
|
||||
&& part
|
||||
.indices
|
||||
.iter()
|
||||
|
|
@ -1782,46 +1764,9 @@ pub fn build_mesh_batch_filtered(
|
|||
.or_default()
|
||||
.push(part);
|
||||
} else {
|
||||
// Compatibility and non-instanced meshes keep their already
|
||||
// transformed display vertices in the ordinary static batch.
|
||||
part.mesh = part.display_mesh;
|
||||
direct_parts.push(part);
|
||||
}
|
||||
}
|
||||
let sparse_groups: Vec<_> = instance_groups
|
||||
.iter()
|
||||
.filter_map(|(key, parts)| {
|
||||
let first = parts.first()?;
|
||||
let geometry_bytes = first
|
||||
.mesh
|
||||
.verts
|
||||
.len()
|
||||
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
||||
.saturating_add(first.indices.len().saturating_mul(12))
|
||||
.saturating_add(
|
||||
first
|
||||
.set
|
||||
.instance_source
|
||||
.as_ref()
|
||||
.map_or(0, |source| {
|
||||
source
|
||||
.edge_verts
|
||||
.len()
|
||||
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
||||
}),
|
||||
);
|
||||
let saved_bytes = geometry_bytes.saturating_mul(parts.len().saturating_sub(1));
|
||||
(parts.len() < 4 && saved_bytes < 512 * 1024).then_some(*key)
|
||||
})
|
||||
.collect();
|
||||
for key in sparse_groups {
|
||||
if let Some(parts) = instance_groups.remove(&key) {
|
||||
for mut part in parts {
|
||||
part.mesh = part.display_mesh;
|
||||
direct_parts.push(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
direct_parts.sort_by_key(|part| {
|
||||
(
|
||||
material_key(part.material, part.color),
|
||||
|
|
@ -1838,12 +1783,7 @@ pub fn build_mesh_batch_filtered(
|
|||
let mesh = part.mesh;
|
||||
let material = part.material;
|
||||
let part_color = part.color;
|
||||
let entity_handle = part
|
||||
.display_mesh
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(acadrust::Handle::new);
|
||||
let entity_handle = part.entity_handle;
|
||||
let key = material_key(material, part_color);
|
||||
if active_key.is_some_and(|active| active != key)
|
||||
&& (!verts.is_empty() || !edge_verts.is_empty())
|
||||
|
|
@ -1887,7 +1827,7 @@ pub fn build_mesh_batch_filtered(
|
|||
let edge_color = set
|
||||
.visual_style
|
||||
.as_ref()
|
||||
.map_or(mesh.color, |style| style.edge_color(mesh.color));
|
||||
.map_or(part.display_color, |style| style.edge_color(part.display_color));
|
||||
let vtx = |vi: usize| {
|
||||
let normal = if has_normals {
|
||||
mesh.normals[vi]
|
||||
|
|
@ -2259,10 +2199,11 @@ pub fn build_mesh_batch_filtered(
|
|||
// granularity. Small block definitions get spatial clusters of roughly
|
||||
// 64–256 INSERTs; a huge source is duplicated only a few times.
|
||||
let max_clusters = ((64 * 1024 * 1024) / source_bytes).clamp(1, 64);
|
||||
let cluster_len = parts
|
||||
.len()
|
||||
.div_ceil(max_clusters)
|
||||
.max(64);
|
||||
let cluster_len = if storage_instancing {
|
||||
parts.len().div_ceil(max_clusters).max(64)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for cluster in parts.chunks(cluster_len) {
|
||||
if let Some((chunk, triangles, profile)) =
|
||||
build_instanced_chunk(
|
||||
|
|
|
|||
|
|
@ -2694,11 +2694,7 @@ impl Pipeline {
|
|||
let mut slots = rustc_hash::FxHashMap::default();
|
||||
let mut groups: Vec<Vec<&crate::scene::model::mesh_model::MeshLodSet>> = Vec::new();
|
||||
for (index, set) in sets.iter().enumerate() {
|
||||
let color = set
|
||||
.lods
|
||||
.first()
|
||||
.map(|mesh| mesh.color)
|
||||
.unwrap_or([0.0, 0.0, 0.0, 1.0]);
|
||||
let color = set.display_color().unwrap_or([0.0, 0.0, 0.0, 1.0]);
|
||||
let key = match (&set.instance_source, set.instance_transform) {
|
||||
(Some(source), Some(transform)) => {
|
||||
let matrix = &transform.matrix.m;
|
||||
|
|
@ -2771,11 +2767,7 @@ impl Pipeline {
|
|||
)
|
||||
})
|
||||
.collect();
|
||||
let color = source
|
||||
.lods
|
||||
.first()
|
||||
.map(|mesh| mesh.color)
|
||||
.unwrap_or([0.0, 0.0, 0.0, 1.0]);
|
||||
let color = source.display_color().unwrap_or([0.0, 0.0, 0.0, 1.0]);
|
||||
let mk = |w: glam::DVec3| -> SilhouetteVertex {
|
||||
let (hx, hy, hz) = (w.x as f32, w.y as f32, w.z as f32);
|
||||
SilhouetteVertex {
|
||||
|
|
@ -2813,7 +2805,13 @@ impl Pipeline {
|
|||
});
|
||||
}
|
||||
};
|
||||
for generator in &source.curved_gens {
|
||||
let generators = source
|
||||
.instance_source
|
||||
.as_ref()
|
||||
.map_or(source.curved_gens.as_slice(), |instance| {
|
||||
instance.curved_gens.as_slice()
|
||||
});
|
||||
for generator in generators {
|
||||
let transformed;
|
||||
let silhouette_source = if let Some(transform) = source.instance_transform {
|
||||
let origin = transform.apply(acadrust::types::Vector3::ZERO);
|
||||
|
|
@ -3259,12 +3257,7 @@ impl Pipeline {
|
|||
}
|
||||
let current_handles: rustc_hash::FxHashSet<_> = meshes
|
||||
.iter()
|
||||
.filter_map(|set| {
|
||||
set.lods
|
||||
.first()
|
||||
.and_then(|mesh| mesh.name.parse::<u64>().ok())
|
||||
.map(acadrust::Handle::new)
|
||||
})
|
||||
.filter_map(MeshLodSet::entity_handle)
|
||||
.collect();
|
||||
let changed: rustc_hash::FxHashSet<_> = changes
|
||||
.iter()
|
||||
|
|
|
|||
Loading…
Reference in a new issue