fix(scene): isolate layout render sources
This commit is contained in:
parent
5909540ebf
commit
f39481ac49
3 changed files with 251 additions and 185 deletions
|
|
@ -792,10 +792,9 @@ impl Scene {
|
||||||
|
|
||||||
pub(super) fn synced_hatch_models(
|
pub(super) fn synced_hatch_models(
|
||||||
&self,
|
&self,
|
||||||
|
target_block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let layout_block = self.current_layout_block_handle();
|
|
||||||
|
|
||||||
let layer_hidden = |layer: &str| {
|
let layer_hidden = |layer: &str| {
|
||||||
self.document
|
self.document
|
||||||
.layers
|
.layers
|
||||||
|
|
@ -811,14 +810,9 @@ impl Scene {
|
||||||
// culling at draw time, which keeps the GPU upload set stable
|
// culling at draw time, which keeps the GPU upload set stable
|
||||||
// across pan/zoom.
|
// across pan/zoom.
|
||||||
//
|
//
|
||||||
// We INCLUDE hatches from blocks other than `current_layout`'s
|
// Every content viewport supplies the block it renders. Do not depend
|
||||||
// own block (specifically: paper-layout content viewports want
|
// on camera/frustum culling to separate paper and model coordinates:
|
||||||
// model-block hatches). Every hatch's `world_origin` is already
|
// overlapping coordinates otherwise make foreign fills visible.
|
||||||
// baked into the correct block coord-space at
|
|
||||||
// `populate_hatches_from_document` time (offset for model, 0 for
|
|
||||||
// paper), so projecting them through a camera built for the
|
|
||||||
// wrong block lands them outside the frustum and the per-vp
|
|
||||||
// scissor / LOD culls them out — no double-rendering.
|
|
||||||
let depth_map = self.draw_depth_map();
|
let depth_map = self.draw_depth_map();
|
||||||
let mut models: Vec<HatchModel> = self
|
let mut models: Vec<HatchModel> = self
|
||||||
.hatches
|
.hatches
|
||||||
|
|
@ -850,13 +844,7 @@ impl Scene {
|
||||||
// BLOCK record that's neither model nor a paper layout
|
// BLOCK record that's neither model nor a paper layout
|
||||||
// block) — they're tessellated separately via Insert
|
// block) — they're tessellated separately via Insert
|
||||||
// explosion and only the laid-out copies should appear.
|
// explosion and only the laid-out copies should appear.
|
||||||
self.belongs_to_visible_block(handle, c.owner_handle, layout_block)
|
self.belongs_to_visible_block(handle, c.owner_handle, target_block)
|
||||||
|| (self.block_edit_block.is_none()
|
|
||||||
&& self.belongs_to_visible_block(
|
|
||||||
handle,
|
|
||||||
c.owner_handle,
|
|
||||||
self.model_space_block_handle(),
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.flat_map(|(&handle, model)| {
|
.flat_map(|(&handle, model)| {
|
||||||
let contextual = self
|
let contextual = self
|
||||||
|
|
@ -980,21 +968,14 @@ impl Scene {
|
||||||
} else {
|
} else {
|
||||||
self.bg_color
|
self.bg_color
|
||||||
};
|
};
|
||||||
// Block-internal hatch fills: explode every visible INSERT of this
|
// Block-internal hatch fills: descend INSERTs owned by the requested
|
||||||
// layout block and materialize its fills at world position. Paper
|
// target only and materialize their fills at world position.
|
||||||
// content viewports also need the model block's INSERT fills; without
|
|
||||||
// them the screen showed an empty/white block while plotting correctly
|
|
||||||
// materialized its solid hatch. (tint_selected = true applies the
|
|
||||||
// screen selection highlight.)
|
|
||||||
models.extend(self.exploded_insert_hatch_models(layout_block, hatch_bg, true, frozen));
|
|
||||||
if self.current_layout != "Model" {
|
|
||||||
models.extend(self.exploded_insert_hatch_models(
|
models.extend(self.exploded_insert_hatch_models(
|
||||||
self.model_space_block_handle(),
|
target_block,
|
||||||
hatch_bg,
|
hatch_bg,
|
||||||
true,
|
true,
|
||||||
frozen,
|
frozen,
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
// Wide LwPolyline / Polyline2D bands are no longer hatch fills at
|
// Wide LwPolyline / Polyline2D bands are no longer hatch fills at
|
||||||
// model level: a flat band is drawn by expanding its centre-line wire
|
// model level: a flat band is drawn by expanding its centre-line wire
|
||||||
|
|
@ -1468,6 +1449,7 @@ impl Scene {
|
||||||
/// wipeouts correctly mask everything below them in the draw order.
|
/// wipeouts correctly mask everything below them in the draw order.
|
||||||
pub(crate) fn wipeout_models(
|
pub(crate) fn wipeout_models(
|
||||||
&self,
|
&self,
|
||||||
|
target_block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let is_paper = self.current_layout != "Model";
|
let is_paper = self.current_layout != "Model";
|
||||||
|
|
@ -1483,7 +1465,6 @@ impl Scene {
|
||||||
// pipeline's `wipeout_skip_flags` (compute_wipeout_lod) does
|
// pipeline's `wipeout_skip_flags` (compute_wipeout_lod) does
|
||||||
// the per-frame skip at draw time instead.
|
// the per-frame skip at draw time instead.
|
||||||
let depth_map = self.draw_depth_map();
|
let depth_map = self.draw_depth_map();
|
||||||
let layout_block = self.current_layout_block_handle();
|
|
||||||
let mut models = Vec::new();
|
let mut models = Vec::new();
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let EntityType::Wipeout(wo) = entity else {
|
let EntityType::Wipeout(wo) = entity else {
|
||||||
|
|
@ -1501,7 +1482,7 @@ impl Scene {
|
||||||
if !self.belongs_to_visible_block(
|
if !self.belongs_to_visible_block(
|
||||||
wo.common.handle,
|
wo.common.handle,
|
||||||
wo.common.owner_handle,
|
wo.common.owner_handle,
|
||||||
layout_block,
|
target_block,
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1574,7 +1555,7 @@ impl Scene {
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !self.belongs_to_visible_block(c.handle, c.owner_handle, layout_block) {
|
if !self.belongs_to_visible_block(c.handle, c.owner_handle, target_block) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if frozen.is_none()
|
if frozen.is_none()
|
||||||
|
|
|
||||||
203
src/scene/mod.rs
203
src/scene/mod.rs
|
|
@ -1515,10 +1515,10 @@ pub struct Scene {
|
||||||
pub document: CadDocument,
|
pub document: CadDocument,
|
||||||
/// File-open-prepared index for non-graphical semantic object lookups.
|
/// File-open-prepared index for non-graphical semantic object lookups.
|
||||||
pub(crate) object_data_cache: crate::entities::object_data::ObjectDataCache,
|
pub(crate) object_data_cache: crate::entities::object_data::ObjectDataCache,
|
||||||
/// Native AcDbLight/Sun inputs. Built once after document load, then
|
/// Native AcDbLight/Sun inputs, separated by rendered block and viewport
|
||||||
/// invalidated only when a light entity changes; ordinary geometry edits
|
/// frozen-layer signature. Invalidated only when a light entity changes;
|
||||||
/// never rescan a million-entity drawing just to rediscover its lights.
|
/// ordinary geometry edits never rescan the drawing's lights.
|
||||||
lighting_cache: RefCell<Option<Vec<SceneLight>>>,
|
lighting_cache: RefCell<HashMap<(Handle, u64), Vec<SceneLight>>>,
|
||||||
/// Currently selected entity handles.
|
/// Currently selected entity handles.
|
||||||
pub selected: HashSet<Handle>,
|
pub selected: HashSet<Handle>,
|
||||||
/// Session-only ISOLATEOBJECTS / HIDEOBJECTS state. Never written to DWG/DXF.
|
/// Session-only ISOLATEOBJECTS / HIDEOBJECTS state. Never written to DWG/DXF.
|
||||||
|
|
@ -1633,21 +1633,22 @@ pub struct Scene {
|
||||||
/// constants. `[depth, half]` retains a fixed child sub-range for block
|
/// constants. `[depth, half]` retains a fixed child sub-range for block
|
||||||
/// composition. Full sort/layout/block changes rebuild the labels.
|
/// composition. Full sort/layout/block changes rebuild the labels.
|
||||||
draw_depth_cache: RefCell<Option<DrawDepthCache>>,
|
draw_depth_cache: RefCell<Option<DrawDepthCache>>,
|
||||||
/// Cached hatch fill models, keyed by geometry_epoch. View culling
|
/// Cached hatch fill models, keyed by target block and geometry_epoch. View culling
|
||||||
/// is handled at draw time via `hatch_skip_flags` in the pipeline,
|
/// is handled at draw time via `hatch_skip_flags` in the pipeline,
|
||||||
/// not at build time — that lets the GPU buffer stay stable across
|
/// not at build time — that lets the GPU buffer stay stable across
|
||||||
/// pan/zoom while still skipping out-of-view hatches.
|
/// pan/zoom while still skipping out-of-view hatches.
|
||||||
/// Keyed by `(geometry_epoch, selection_generation)` — selected hatches
|
/// Keyed by `(geometry_epoch, selection_generation)` — selected hatches
|
||||||
/// are tinted, so a select/deselect must rebuild even when the geometry
|
/// are tinted, so a select/deselect must rebuild even when the geometry
|
||||||
/// is unchanged (issue #71).
|
/// is unchanged (issue #71).
|
||||||
hatch_cache: RefCell<HashMap<String, (u64, u64, Arc<Vec<HatchModel>>)>>,
|
hatch_cache: RefCell<HashMap<(Handle, String), (u64, u64, Arc<Vec<HatchModel>>)>>,
|
||||||
/// Cached wipeout fill models, keyed by geometry_epoch. Same
|
/// Cached wipeout fill models, keyed by target block and geometry_epoch. Same
|
||||||
/// reasoning as `hatch_cache`.
|
/// reasoning as `hatch_cache`.
|
||||||
wipeout_cache: RefCell<HashMap<String, (u64, Arc<Vec<HatchModel>>)>>,
|
wipeout_cache: RefCell<HashMap<(Handle, String), (u64, Arc<Vec<HatchModel>>)>>,
|
||||||
/// Cached image models, keyed by geometry_epoch. No camera key needed here.
|
/// Cached image models, keyed by target block and geometry_epoch. No camera
|
||||||
image_cache: RefCell<Option<(u64, Arc<Vec<ImageModel>>)>>,
|
/// key needed here.
|
||||||
/// Cached mesh models, keyed by geometry_epoch.
|
image_cache: RefCell<HashMap<Handle, (u64, Arc<Vec<ImageModel>>)>>,
|
||||||
mesh_cache: RefCell<HashMap<String, (u64, Arc<Vec<MeshLodSet>>)>>,
|
/// Cached mesh models, keyed by target block and geometry_epoch.
|
||||||
|
mesh_cache: RefCell<HashMap<(Handle, String), (u64, Arc<Vec<MeshLodSet>>)>>,
|
||||||
/// Picking mesh source for a non-model active space, keyed by geometry epoch
|
/// Picking mesh source for a non-model active space, keyed by geometry epoch
|
||||||
/// and interaction block. Model/MSPACE reuse `mesh_cache` directly.
|
/// and interaction block. Model/MSPACE reuse `mesh_cache` directly.
|
||||||
interaction_mesh_cache: RefCell<Option<(u64, u64, Arc<Vec<MeshLodSet>>)>>,
|
interaction_mesh_cache: RefCell<Option<(u64, u64, Arc<Vec<MeshLodSet>>)>>,
|
||||||
|
|
@ -1669,10 +1670,12 @@ pub struct Scene {
|
||||||
/// filtered variants. Viewports sharing a frozen set share one entry (like
|
/// filtered variants. Viewports sharing a frozen set share one entry (like
|
||||||
/// the resident wire set). Empty for a viewport with no frozen layers (it
|
/// the resident wire set). Empty for a viewport with no frozen layers (it
|
||||||
/// reuses the unfiltered `*_arc` sets directly).
|
/// reuses the unfiltered `*_arc` sets directly).
|
||||||
frozen_hatch_cache: RefCell<HashMap<(String, u64), (u64, u64, Arc<Vec<HatchModel>>)>>,
|
frozen_hatch_cache:
|
||||||
frozen_wipeout_cache: RefCell<HashMap<(String, u64), (u64, Arc<Vec<HatchModel>>)>>,
|
RefCell<HashMap<(Handle, String, u64), (u64, u64, Arc<Vec<HatchModel>>)>>,
|
||||||
frozen_image_cache: RefCell<HashMap<u64, (u64, Arc<Vec<ImageModel>>)>>,
|
frozen_wipeout_cache:
|
||||||
frozen_mesh_cache: RefCell<HashMap<(String, u64), (u64, Arc<Vec<MeshLodSet>>)>>,
|
RefCell<HashMap<(Handle, String, u64), (u64, Arc<Vec<HatchModel>>)>>,
|
||||||
|
frozen_image_cache: RefCell<HashMap<(Handle, u64), (u64, Arc<Vec<ImageModel>>)>>,
|
||||||
|
frozen_mesh_cache: RefCell<HashMap<(Handle, String, u64), (u64, Arc<Vec<MeshLodSet>>)>>,
|
||||||
/// Cached block-INSERT hatches for hit-testing, keyed by geometry_epoch.
|
/// Cached block-INSERT hatches for hit-testing, keyed by geometry_epoch.
|
||||||
/// Building this explodes every model-space INSERT, so without the cache a
|
/// Building this explodes every model-space INSERT, so without the cache a
|
||||||
/// heavy block-instanced drawing re-explodes thousands of inserts on every
|
/// heavy block-instanced drawing re-explodes thousands of inserts on every
|
||||||
|
|
@ -1915,7 +1918,7 @@ impl Scene {
|
||||||
selection: Rc::new(RefCell::new(SelectionState::default())),
|
selection: Rc::new(RefCell::new(SelectionState::default())),
|
||||||
document: CadDocument::new(),
|
document: CadDocument::new(),
|
||||||
object_data_cache: crate::entities::object_data::ObjectDataCache::default(),
|
object_data_cache: crate::entities::object_data::ObjectDataCache::default(),
|
||||||
lighting_cache: RefCell::new(None),
|
lighting_cache: RefCell::new(HashMap::default()),
|
||||||
selected: HashSet::default(),
|
selected: HashSet::default(),
|
||||||
object_isolation: ObjectIsolationState::default(),
|
object_isolation: ObjectIsolationState::default(),
|
||||||
preview_hidden: HashSet::default(),
|
preview_hidden: HashSet::default(),
|
||||||
|
|
@ -1941,7 +1944,7 @@ impl Scene {
|
||||||
draw_depth_cache: RefCell::new(None),
|
draw_depth_cache: RefCell::new(None),
|
||||||
hatch_cache: RefCell::new(HashMap::default()),
|
hatch_cache: RefCell::new(HashMap::default()),
|
||||||
wipeout_cache: RefCell::new(HashMap::default()),
|
wipeout_cache: RefCell::new(HashMap::default()),
|
||||||
image_cache: RefCell::new(None),
|
image_cache: RefCell::new(HashMap::default()),
|
||||||
mesh_cache: RefCell::new(HashMap::default()),
|
mesh_cache: RefCell::new(HashMap::default()),
|
||||||
interaction_mesh_cache: RefCell::new(None),
|
interaction_mesh_cache: RefCell::new(None),
|
||||||
mesh_pick_lookup_cache: RefCell::new(None),
|
mesh_pick_lookup_cache: RefCell::new(None),
|
||||||
|
|
@ -2382,7 +2385,7 @@ impl Scene {
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.is_some_and(|entity| matches!(entity, EntityType::Light(_)))
|
.is_some_and(|entity| matches!(entity, EntityType::Light(_)))
|
||||||
}) {
|
}) {
|
||||||
*self.lighting_cache.borrow_mut() = None;
|
self.lighting_cache.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
let mut changes: Vec<(Handle, ChangeKind)> = Vec::with_capacity(entities.len());
|
let mut changes: Vec<(Handle, ChangeKind)> = Vec::with_capacity(entities.len());
|
||||||
for (h, before, after) in entities {
|
for (h, before, after) in entities {
|
||||||
|
|
@ -2426,7 +2429,7 @@ impl Scene {
|
||||||
// stale ownership.
|
// stale ownership.
|
||||||
self.invalidate_dependency_index();
|
self.invalidate_dependency_index();
|
||||||
}
|
}
|
||||||
let cached_light_changed = self.lighting_cache.borrow().as_ref().is_some_and(|lights| {
|
let cached_light_changed = self.lighting_cache.borrow().values().any(|lights| {
|
||||||
changes
|
changes
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(handle, _)| lights.iter().any(|light| light.handle == *handle))
|
.any(|(handle, _)| lights.iter().any(|light| light.handle == *handle))
|
||||||
|
|
@ -2448,7 +2451,7 @@ impl Scene {
|
||||||
exists,
|
exists,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*self.lighting_cache.borrow_mut() = None;
|
self.lighting_cache.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
self.geometry_epoch = epoch;
|
self.geometry_epoch = epoch;
|
||||||
|
|
@ -2515,6 +2518,9 @@ impl Scene {
|
||||||
pub fn bump_geometry(&mut self) {
|
pub fn bump_geometry(&mut self) {
|
||||||
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
self.geometry_epoch = epoch;
|
self.geometry_epoch = epoch;
|
||||||
|
// A full structural change may move lights between blocks or alter
|
||||||
|
// layer visibility without naming the affected handles.
|
||||||
|
self.lighting_cache.borrow_mut().clear();
|
||||||
// Default: also invalidate block definitions. Safe for every caller;
|
// Default: also invalidate block definitions. Safe for every caller;
|
||||||
// operations that know blocks are untouched use `bump_geometry_no_blocks`.
|
// operations that know blocks are untouched use `bump_geometry_no_blocks`.
|
||||||
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
@ -2545,6 +2551,7 @@ impl Scene {
|
||||||
pub fn bump_geometry_no_blocks(&mut self) {
|
pub fn bump_geometry_no_blocks(&mut self) {
|
||||||
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
let epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
self.geometry_epoch = epoch;
|
self.geometry_epoch = epoch;
|
||||||
|
self.lighting_cache.borrow_mut().clear();
|
||||||
self.push_geometry_delta(epoch, Vec::new(), true);
|
self.push_geometry_delta(epoch, Vec::new(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5098,11 +5105,12 @@ impl Scene {
|
||||||
// drawings. Key on a signature of `selected` instead, so hover (which
|
// drawings. Key on a signature of `selected` instead, so hover (which
|
||||||
// never changes `selected`) keeps the cache warm.
|
// never changes `selected`) keeps the cache warm.
|
||||||
let sel_sig = self.selected_hatch_sig();
|
let sel_sig = self.selected_hatch_sig();
|
||||||
let space = self.current_layout.clone();
|
let target_block = self.content_render_block_handle();
|
||||||
|
let key = (target_block, self.current_layout.clone());
|
||||||
{
|
{
|
||||||
let reuse = {
|
let reuse = {
|
||||||
let cache = self.hatch_cache.borrow();
|
let cache = self.hatch_cache.borrow();
|
||||||
match cache.get(&space) {
|
match cache.get(&key) {
|
||||||
// Selection tint is baked in, so the selected set must also
|
// Selection tint is baked in, so the selected set must also
|
||||||
// match; category = a changed direct fill or an INSERT
|
// match; category = a changed direct fill or an INSERT
|
||||||
// whose expanded block tree can contain hatch fills.
|
// whose expanded block tree can contain hatch fills.
|
||||||
|
|
@ -5126,15 +5134,15 @@ impl Scene {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(arc) = reuse {
|
if let Some(arc) = reuse {
|
||||||
if let Some((e, _, _)) = self.hatch_cache.borrow_mut().get_mut(&space) {
|
if let Some((e, _, _)) = self.hatch_cache.borrow_mut().get_mut(&key) {
|
||||||
*e = self.geometry_epoch;
|
*e = self.geometry_epoch;
|
||||||
}
|
}
|
||||||
return arc;
|
return arc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.synced_hatch_models(None));
|
let arc = Arc::new(self.synced_hatch_models(target_block, None));
|
||||||
self.hatch_cache.borrow_mut().insert(
|
self.hatch_cache.borrow_mut().insert(
|
||||||
space,
|
key,
|
||||||
(self.geometry_epoch, sel_sig, Arc::clone(&arc)),
|
(self.geometry_epoch, sel_sig, Arc::clone(&arc)),
|
||||||
);
|
);
|
||||||
arc
|
arc
|
||||||
|
|
@ -5167,11 +5175,12 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn wipeout_models_arc(&self) -> Arc<Vec<HatchModel>> {
|
pub(super) fn wipeout_models_arc(&self) -> Arc<Vec<HatchModel>> {
|
||||||
let space = self.current_layout.clone();
|
let target_block = self.content_render_block_handle();
|
||||||
|
let key = (target_block, self.current_layout.clone());
|
||||||
{
|
{
|
||||||
let reuse = {
|
let reuse = {
|
||||||
let cache = self.wipeout_cache.borrow();
|
let cache = self.wipeout_cache.borrow();
|
||||||
match cache.get(&space) {
|
match cache.get(&key) {
|
||||||
Some((cached_epoch, arc))
|
Some((cached_epoch, arc))
|
||||||
// wipeout_models scans the whole document for Wipeout
|
// wipeout_models scans the whole document for Wipeout
|
||||||
// entities; relevance = the changed handle is a Wipeout.
|
// entities; relevance = the changed handle is a Wipeout.
|
||||||
|
|
@ -5192,27 +5201,28 @@ impl Scene {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(arc) = reuse {
|
if let Some(arc) = reuse {
|
||||||
if let Some((e, _)) = self.wipeout_cache.borrow_mut().get_mut(&space) {
|
if let Some((e, _)) = self.wipeout_cache.borrow_mut().get_mut(&key) {
|
||||||
*e = self.geometry_epoch;
|
*e = self.geometry_epoch;
|
||||||
}
|
}
|
||||||
return arc;
|
return arc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.wipeout_models(None));
|
let arc = Arc::new(self.wipeout_models(target_block, None));
|
||||||
self.wipeout_cache
|
self.wipeout_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(space, (self.geometry_epoch, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
arc
|
arc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn images_arc(&self) -> Arc<Vec<ImageModel>> {
|
pub(super) fn images_arc(&self) -> Arc<Vec<ImageModel>> {
|
||||||
|
let target_block = self.content_render_block_handle();
|
||||||
{
|
{
|
||||||
let reuse = {
|
let reuse = {
|
||||||
let cache = self.image_cache.borrow();
|
let cache = self.image_cache.borrow();
|
||||||
match *cache {
|
match cache.get(&target_block) {
|
||||||
Some((cached_epoch, ref arc))
|
Some((cached_epoch, arc))
|
||||||
if self.category_cache_valid(
|
if self.category_cache_valid(
|
||||||
cached_epoch,
|
*cached_epoch,
|
||||||
CACHE_CATEGORY_IMAGE,
|
CACHE_CATEGORY_IMAGE,
|
||||||
|h| self.images.contains_key(&h),
|
|h| self.images.contains_key(&h),
|
||||||
) =>
|
) =>
|
||||||
|
|
@ -5225,20 +5235,26 @@ impl Scene {
|
||||||
if let Some(arc) = reuse {
|
if let Some(arc) = reuse {
|
||||||
// No image changed since — keep it warm, just advance the sync
|
// No image changed since — keep it warm, just advance the sync
|
||||||
// epoch so the next replay window stays short.
|
// epoch so the next replay window stays short.
|
||||||
if let Some((ref mut e, _)) = *self.image_cache.borrow_mut() {
|
if let Some((e, _)) = self.image_cache.borrow_mut().get_mut(&target_block) {
|
||||||
*e = self.geometry_epoch;
|
*e = self.geometry_epoch;
|
||||||
}
|
}
|
||||||
return arc;
|
return arc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.image_models(None));
|
let arc = Arc::new(self.image_models(target_block, None));
|
||||||
*self.image_cache.borrow_mut() = Some((self.geometry_epoch, Arc::clone(&arc)));
|
self.image_cache
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(target_block, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
arc
|
arc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Model image / OLE-frame models, optionally dropping those whose layer is
|
/// Image / OLE-frame models owned by `target_block`, optionally dropping
|
||||||
/// frozen in a content viewport (`frozen`). `None` reproduces the full set.
|
/// those whose layer is frozen in a content viewport (`frozen`).
|
||||||
fn image_models(&self, frozen: Option<&HashSet<Handle>>) -> Vec<ImageModel> {
|
fn image_models(
|
||||||
|
&self,
|
||||||
|
target_block: Handle,
|
||||||
|
frozen: Option<&HashSet<Handle>>,
|
||||||
|
) -> Vec<ImageModel> {
|
||||||
let depth_map = self.draw_depth_map();
|
let depth_map = self.draw_depth_map();
|
||||||
self.images
|
self.images
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -5249,6 +5265,11 @@ impl Scene {
|
||||||
|| self.entity_temporarily_hidden(*handle)
|
|| self.entity_temporarily_hidden(*handle)
|
||||||
|| self.layer_hidden(&common.layer)
|
|| self.layer_hidden(&common.layer)
|
||||||
|| self.layer_frozen_in(&common.layer, frozen)
|
|| self.layer_frozen_in(&common.layer, frozen)
|
||||||
|
|| !self.belongs_to_visible_block(
|
||||||
|
*handle,
|
||||||
|
common.owner_handle,
|
||||||
|
target_block,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -5287,11 +5308,12 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn meshes_arc(&self) -> Arc<Vec<MeshLodSet>> {
|
pub(super) fn meshes_arc(&self) -> Arc<Vec<MeshLodSet>> {
|
||||||
let space = self.current_layout.clone();
|
let target_block = self.content_render_block_handle();
|
||||||
|
let key = (target_block, self.current_layout.clone());
|
||||||
{
|
{
|
||||||
let reuse = {
|
let reuse = {
|
||||||
let cache = self.mesh_cache.borrow();
|
let cache = self.mesh_cache.borrow();
|
||||||
match cache.get(&space) {
|
match cache.get(&key) {
|
||||||
// Top-level solids seed self.meshes; the instanced_block part
|
// Top-level solids seed self.meshes; the instanced_block part
|
||||||
// is driven by INSERTs, so an INSERT edit (e.g. a move) must
|
// is driven by INSERTs, so an INSERT edit (e.g. a move) must
|
||||||
// also invalidate. Block-definition edits route through
|
// also invalidate. Block-definition edits route through
|
||||||
|
|
@ -5315,19 +5337,28 @@ impl Scene {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(arc) = reuse {
|
if let Some(arc) = reuse {
|
||||||
if let Some((e, _)) = self.mesh_cache.borrow_mut().get_mut(&space) {
|
if let Some((e, _)) = self.mesh_cache.borrow_mut().get_mut(&key) {
|
||||||
*e = self.geometry_epoch;
|
*e = self.geometry_epoch;
|
||||||
}
|
}
|
||||||
return arc;
|
return arc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.mesh_models(None));
|
let arc = Arc::new(self.mesh_models(target_block, None));
|
||||||
self.mesh_cache
|
self.mesh_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(space, (self.geometry_epoch, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
arc
|
arc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Block rendered by model-content viewports. Paper layout entities belong
|
||||||
|
/// only to the sheet viewport; floating paper viewports always show model
|
||||||
|
/// space. BEDIT is the sole exception and renders the edited block itself.
|
||||||
|
fn content_render_block_handle(&self) -> Handle {
|
||||||
|
self.block_edit_block
|
||||||
|
.filter(|block| !block.is_null())
|
||||||
|
.unwrap_or_else(|| self.model_space_block_handle())
|
||||||
|
}
|
||||||
|
|
||||||
fn interaction_block_handle(&self) -> Handle {
|
fn interaction_block_handle(&self) -> Handle {
|
||||||
if self.block_edit_block.is_none() && self.active_viewport.is_some() {
|
if self.block_edit_block.is_none() && self.active_viewport.is_some() {
|
||||||
self.model_space_block_handle()
|
self.model_space_block_handle()
|
||||||
|
|
@ -5403,19 +5434,7 @@ impl Scene {
|
||||||
return Arc::clone(meshes);
|
return Arc::clone(meshes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut all: Vec<MeshLodSet> = self
|
let meshes = Arc::new(self.mesh_models(block, None));
|
||||||
.meshes
|
|
||||||
.iter()
|
|
||||||
.filter(|(&handle, _)| {
|
|
||||||
self.mesh_entity_visible(handle)
|
|
||||||
&& self.document.get_entity(handle).is_some_and(|entity| {
|
|
||||||
self.belongs_to_visible_block(handle, entity.common().owner_handle, block)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.map(|(_, set)| set.clone())
|
|
||||||
.collect();
|
|
||||||
all.extend(self.instanced_block_meshes(block, None));
|
|
||||||
let meshes = Arc::new(all);
|
|
||||||
*self.interaction_mesh_cache.borrow_mut() =
|
*self.interaction_mesh_cache.borrow_mut() =
|
||||||
Some((self.geometry_epoch, key, Arc::clone(&meshes)));
|
Some((self.geometry_epoch, key, Arc::clone(&meshes)));
|
||||||
meshes
|
meshes
|
||||||
|
|
@ -5458,7 +5477,11 @@ impl Scene {
|
||||||
/// Solid-mesh set (top-level + block-instanced), optionally dropping those
|
/// Solid-mesh set (top-level + block-instanced), optionally dropping those
|
||||||
/// whose layer is frozen in a content viewport (`frozen`). `None` reproduces
|
/// whose layer is frozen in a content viewport (`frozen`). `None` reproduces
|
||||||
/// the full set.
|
/// the full set.
|
||||||
fn mesh_models(&self, frozen: Option<&HashSet<Handle>>) -> Vec<MeshLodSet> {
|
fn mesh_models(
|
||||||
|
&self,
|
||||||
|
target_block: Handle,
|
||||||
|
frozen: Option<&HashSet<Handle>>,
|
||||||
|
) -> Vec<MeshLodSet> {
|
||||||
// Top-level solids: drop those whose layer is off/frozen or that are
|
// Top-level solids: drop those whose layer is off/frozen or that are
|
||||||
// flagged invisible / isolated-hidden, mirroring the 2D wire path, plus
|
// flagged invisible / isolated-hidden, mirroring the 2D wire path, plus
|
||||||
// any whose layer is frozen in the requesting viewport.
|
// any whose layer is frozen in the requesting viewport.
|
||||||
|
|
@ -5470,8 +5493,15 @@ impl Scene {
|
||||||
&& self
|
&& self
|
||||||
.document
|
.document
|
||||||
.get_entity(h)
|
.get_entity(h)
|
||||||
.map(|e| !self.layer_frozen_in(&e.common().layer, frozen))
|
.map(|e| {
|
||||||
.unwrap_or(true)
|
!self.layer_frozen_in(&e.common().layer, frozen)
|
||||||
|
&& self.belongs_to_visible_block(
|
||||||
|
h,
|
||||||
|
e.common().owner_handle,
|
||||||
|
target_block,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.map(|(_, set)| set.clone())
|
.map(|(_, set)| set.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -5479,13 +5509,7 @@ impl Scene {
|
||||||
// block so a block placed at an INSERT scale renders at the right size
|
// block so a block placed at an INSERT scale renders at the right size
|
||||||
// (#123) — model space normally, the edited block in a BEDIT editor so
|
// (#123) — model space normally, the edited block in a BEDIT editor so
|
||||||
// model-space solids don't leak into it (#261).
|
// model-space solids don't leak into it (#261).
|
||||||
all.extend(
|
all.extend(self.instanced_block_meshes(target_block, frozen));
|
||||||
self.instanced_block_meshes(
|
|
||||||
self.block_edit_block
|
|
||||||
.unwrap_or_else(|| self.model_space_block_handle()),
|
|
||||||
frozen,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
all
|
all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5535,15 +5559,16 @@ impl Scene {
|
||||||
if frozen.is_empty() {
|
if frozen.is_empty() {
|
||||||
return self.hatch_models_arc();
|
return self.hatch_models_arc();
|
||||||
}
|
}
|
||||||
|
let target_block = self.content_render_block_handle();
|
||||||
let sig = Self::frozen_layers_sig(frozen);
|
let sig = Self::frozen_layers_sig(frozen);
|
||||||
let key = (self.current_layout.clone(), sig);
|
let key = (target_block, self.current_layout.clone(), sig);
|
||||||
let sel = self.selected_hatch_sig();
|
let sel = self.selected_hatch_sig();
|
||||||
if let Some((e, s, arc)) = self.frozen_hatch_cache.borrow().get(&key) {
|
if let Some((e, s, arc)) = self.frozen_hatch_cache.borrow().get(&key) {
|
||||||
if *e == self.geometry_epoch && *s == sel {
|
if *e == self.geometry_epoch && *s == sel {
|
||||||
return Arc::clone(arc);
|
return Arc::clone(arc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.synced_hatch_models(Some(frozen)));
|
let arc = Arc::new(self.synced_hatch_models(target_block, Some(frozen)));
|
||||||
self.frozen_hatch_cache
|
self.frozen_hatch_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(key, (self.geometry_epoch, sel, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, sel, Arc::clone(&arc)));
|
||||||
|
|
@ -5558,14 +5583,15 @@ impl Scene {
|
||||||
if frozen.is_empty() {
|
if frozen.is_empty() {
|
||||||
return self.wipeout_models_arc();
|
return self.wipeout_models_arc();
|
||||||
}
|
}
|
||||||
|
let target_block = self.content_render_block_handle();
|
||||||
let sig = Self::frozen_layers_sig(frozen);
|
let sig = Self::frozen_layers_sig(frozen);
|
||||||
let key = (self.current_layout.clone(), sig);
|
let key = (target_block, self.current_layout.clone(), sig);
|
||||||
if let Some((e, arc)) = self.frozen_wipeout_cache.borrow().get(&key) {
|
if let Some((e, arc)) = self.frozen_wipeout_cache.borrow().get(&key) {
|
||||||
if *e == self.geometry_epoch {
|
if *e == self.geometry_epoch {
|
||||||
return Arc::clone(arc);
|
return Arc::clone(arc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.wipeout_models(Some(frozen)));
|
let arc = Arc::new(self.wipeout_models(target_block, Some(frozen)));
|
||||||
self.frozen_wipeout_cache
|
self.frozen_wipeout_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
|
|
@ -5577,16 +5603,18 @@ impl Scene {
|
||||||
if frozen.is_empty() {
|
if frozen.is_empty() {
|
||||||
return self.images_arc();
|
return self.images_arc();
|
||||||
}
|
}
|
||||||
|
let target_block = self.content_render_block_handle();
|
||||||
let sig = Self::frozen_layers_sig(frozen);
|
let sig = Self::frozen_layers_sig(frozen);
|
||||||
if let Some((e, arc)) = self.frozen_image_cache.borrow().get(&sig) {
|
let key = (target_block, sig);
|
||||||
|
if let Some((e, arc)) = self.frozen_image_cache.borrow().get(&key) {
|
||||||
if *e == self.geometry_epoch {
|
if *e == self.geometry_epoch {
|
||||||
return Arc::clone(arc);
|
return Arc::clone(arc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.image_models(Some(frozen)));
|
let arc = Arc::new(self.image_models(target_block, Some(frozen)));
|
||||||
self.frozen_image_cache
|
self.frozen_image_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(sig, (self.geometry_epoch, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
arc
|
arc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5595,14 +5623,15 @@ impl Scene {
|
||||||
if frozen.is_empty() {
|
if frozen.is_empty() {
|
||||||
return self.meshes_arc();
|
return self.meshes_arc();
|
||||||
}
|
}
|
||||||
|
let target_block = self.content_render_block_handle();
|
||||||
let sig = Self::frozen_layers_sig(frozen);
|
let sig = Self::frozen_layers_sig(frozen);
|
||||||
let key = (self.current_layout.clone(), sig);
|
let key = (target_block, self.current_layout.clone(), sig);
|
||||||
if let Some((e, arc)) = self.frozen_mesh_cache.borrow().get(&key) {
|
if let Some((e, arc)) = self.frozen_mesh_cache.borrow().get(&key) {
|
||||||
if *e == self.geometry_epoch {
|
if *e == self.geometry_epoch {
|
||||||
return Arc::clone(arc);
|
return Arc::clone(arc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arc = Arc::new(self.mesh_models(Some(frozen)));
|
let arc = Arc::new(self.mesh_models(target_block, Some(frozen)));
|
||||||
self.frozen_mesh_cache
|
self.frozen_mesh_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
.insert(key, (self.geometry_epoch, Arc::clone(&arc)));
|
||||||
|
|
@ -8260,7 +8289,15 @@ impl Scene {
|
||||||
// 3D solids render as meshes, not wires, so fold their
|
// 3D solids render as meshes, not wires, so fold their
|
||||||
// XY AABBs in too — otherwise ZOOM EXTENTS ignores them.
|
// XY AABBs in too — otherwise ZOOM EXTENTS ignores them.
|
||||||
for (&handle, set) in &self.meshes {
|
for (&handle, set) in &self.meshes {
|
||||||
if !self.mesh_entity_visible(handle) {
|
if !self.mesh_entity_visible(handle)
|
||||||
|
|| !self.document.get_entity(handle).is_some_and(|entity| {
|
||||||
|
self.belongs_to_visible_block(
|
||||||
|
handle,
|
||||||
|
entity.common().owner_handle,
|
||||||
|
model_block,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let [ax, ay, bx, by] = set.world_aabb;
|
let [ax, ay, bx, by] = set.world_aabb;
|
||||||
|
|
@ -8310,7 +8347,15 @@ impl Scene {
|
||||||
}
|
}
|
||||||
// Same mesh inclusion for the tessellate fallback path.
|
// Same mesh inclusion for the tessellate fallback path.
|
||||||
for (&handle, set) in &self.meshes {
|
for (&handle, set) in &self.meshes {
|
||||||
if !self.mesh_entity_visible(handle) {
|
if !self.mesh_entity_visible(handle)
|
||||||
|
|| !self.document.get_entity(handle).is_some_and(|entity| {
|
||||||
|
self.belongs_to_visible_block(
|
||||||
|
handle,
|
||||||
|
entity.common().owner_handle,
|
||||||
|
model_block,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let [ax, ay, bx, by] = set.world_aabb;
|
let [ax, ay, bx, by] = set.world_aabb;
|
||||||
|
|
|
||||||
|
|
@ -1243,7 +1243,11 @@ fn crop_view_proj(view_proj: glam::Mat4, uo: f32, vo: f32, us: f32, vs: f32) ->
|
||||||
// ── Render-style helpers (impl Scene) ────────────────────────────────────
|
// ── Render-style helpers (impl Scene) ────────────────────────────────────
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
fn build_lighting_cache(&self) -> Vec<SceneLight> {
|
fn build_lighting_cache(
|
||||||
|
&self,
|
||||||
|
target_block: Handle,
|
||||||
|
frozen: &rustc_hash::FxHashSet<Handle>,
|
||||||
|
) -> Vec<SceneLight> {
|
||||||
use acadrust::objects::{ClassObjectData, ObjectType};
|
use acadrust::objects::{ClassObjectData, ObjectType};
|
||||||
|
|
||||||
fn normalized(value: [f64; 3], fallback: [f32; 3]) -> [f32; 3] {
|
fn normalized(value: [f64; 3], fallback: [f32; 3]) -> [f32; 3] {
|
||||||
|
|
@ -1385,20 +1389,26 @@ impl Scene {
|
||||||
for &handle in crate::entities::object_data::light_entities(
|
for &handle in crate::entities::object_data::light_entities(
|
||||||
&self.object_data_cache,
|
&self.object_data_cache,
|
||||||
) {
|
) {
|
||||||
if lights.len() >= 4 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Some(EntityType::Light(light)) = self.document.get_entity(handle) {
|
if let Some(EntityType::Light(light)) = self.document.get_entity(handle) {
|
||||||
|
let common = &light.common;
|
||||||
|
if self.layer_frozen_in(&common.layer, Some(frozen))
|
||||||
|
|| !self.belongs_to_visible_block(
|
||||||
|
handle,
|
||||||
|
common.owner_handle,
|
||||||
|
target_block,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Some(light) = converted(self, light) {
|
if let Some(light) = converted(self, light) {
|
||||||
lights.push(light);
|
lights.push(light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lights.len() < 4 {
|
// SUN is document-global. Keep it after entity lights so the four-light
|
||||||
let geo = crate::entities::object_data::geo_objects(
|
// shader limit prefers visible lights owned by this viewport's block.
|
||||||
&self.object_data_cache,
|
let geo = crate::entities::object_data::geo_objects(&self.object_data_cache)
|
||||||
)
|
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|handle| match self.document.objects.get(handle) {
|
.find_map(|handle| match self.document.objects.get(handle) {
|
||||||
Some(ObjectType::GeoData(value))
|
Some(ObjectType::GeoData(value))
|
||||||
|
|
@ -1409,12 +1419,8 @@ impl Scene {
|
||||||
&& value.reference_point.y.abs() <= 90.0 => Some(value),
|
&& value.reference_point.y.abs() <= 90.0 => Some(value),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
for handle in crate::entities::object_data::sun_objects(
|
for handle in crate::entities::object_data::sun_objects(&self.object_data_cache) {
|
||||||
&self.object_data_cache,
|
let Some(ObjectType::ClassObject(value)) = self.document.objects.get(handle) else {
|
||||||
) {
|
|
||||||
let Some(ObjectType::ClassObject(value)) =
|
|
||||||
self.document.objects.get(handle)
|
|
||||||
else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let ClassObjectData::Sun(sun) = &value.data else {
|
let ClassObjectData::Sun(sun) = &value.data else {
|
||||||
|
|
@ -1446,24 +1452,55 @@ impl Scene {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
lights
|
lights
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_document_lighting(&self, uniforms: &mut Uniforms) {
|
fn apply_document_lighting(
|
||||||
if self.lighting_cache.borrow().is_none() {
|
&self,
|
||||||
let lights = self.build_lighting_cache();
|
uniforms: &mut Uniforms,
|
||||||
*self.lighting_cache.borrow_mut() = Some(lights);
|
target_block: Handle,
|
||||||
|
frozen: &rustc_hash::FxHashSet<Handle>,
|
||||||
|
) {
|
||||||
|
let key = (target_block, Self::frozen_layers_sig(frozen));
|
||||||
|
if !self.lighting_cache.borrow().contains_key(&key) {
|
||||||
|
let lights = self.build_lighting_cache(target_block, frozen);
|
||||||
|
self.lighting_cache.borrow_mut().insert(key, lights);
|
||||||
}
|
}
|
||||||
let cache = self.lighting_cache.borrow();
|
let cache = self.lighting_cache.borrow();
|
||||||
let lights = cache.as_deref().unwrap_or_default();
|
let lights = cache.get(&key).map(Vec::as_slice).unwrap_or_default();
|
||||||
|
let visible_lights: Vec<&SceneLight> = lights
|
||||||
|
.iter()
|
||||||
|
.filter(|light| match self.document.get_entity(light.handle) {
|
||||||
|
Some(EntityType::Light(entity)) => {
|
||||||
|
let common = &entity.common;
|
||||||
|
!common.invisible
|
||||||
|
&& !self.entity_temporarily_hidden(light.handle)
|
||||||
|
&& !self.layer_hidden(&common.layer)
|
||||||
|
&& !self.layer_frozen_in(&common.layer, Some(frozen))
|
||||||
|
&& self.belongs_to_visible_block(
|
||||||
|
light.handle,
|
||||||
|
common.owner_handle,
|
||||||
|
target_block,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(_) => false,
|
||||||
|
None => self.document.objects.get(&light.handle).is_some_and(|object| {
|
||||||
|
matches!(
|
||||||
|
object,
|
||||||
|
acadrust::objects::ObjectType::ClassObject(value)
|
||||||
|
if matches!(&value.data, acadrust::objects::ClassObjectData::Sun(_))
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.take(4)
|
||||||
|
.collect();
|
||||||
let eye = [
|
let eye = [
|
||||||
uniforms.eye_high[0] as f64 + uniforms.eye_low[0] as f64,
|
uniforms.eye_high[0] as f64 + uniforms.eye_low[0] as f64,
|
||||||
uniforms.eye_high[1] as f64 + uniforms.eye_low[1] as f64,
|
uniforms.eye_high[1] as f64 + uniforms.eye_low[1] as f64,
|
||||||
uniforms.eye_high[2] as f64 + uniforms.eye_low[2] as f64,
|
uniforms.eye_high[2] as f64 + uniforms.eye_low[2] as f64,
|
||||||
];
|
];
|
||||||
uniforms.lighting[0] = lights.len().min(4) as f32;
|
uniforms.lighting[0] = visible_lights.len() as f32;
|
||||||
for (index, light) in lights.iter().take(4).enumerate() {
|
for (index, light) in visible_lights.into_iter().enumerate() {
|
||||||
let color = light
|
let color = light
|
||||||
.color_layer
|
.color_layer
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|
@ -2124,6 +2161,26 @@ impl Scene {
|
||||||
Arc::new(Vec::new())
|
Arc::new(Vec::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Per-viewport frozen-layer set for a paper content viewport. Content
|
||||||
|
// viewports hide special fills, media, meshes and lights on VP-frozen
|
||||||
|
// layers too, matching the already-filtered resident wire set.
|
||||||
|
let vp_frozen: rustc_hash::FxHashSet<Handle> = if !inst.paper_sheet
|
||||||
|
&& inst.tile_idx.is_none()
|
||||||
|
&& inst.handle != acadrust::Handle::NULL
|
||||||
|
{
|
||||||
|
match self.document.get_entity(inst.handle) {
|
||||||
|
Some(EntityType::Viewport(vp)) => vp.frozen_layers.iter().cloned().collect(),
|
||||||
|
_ => rustc_hash::FxHashSet::default(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rustc_hash::FxHashSet::default()
|
||||||
|
};
|
||||||
|
let lighting_block = if inst.paper_sheet {
|
||||||
|
self.current_layout_block_handle()
|
||||||
|
} else {
|
||||||
|
self.content_render_block_handle()
|
||||||
|
};
|
||||||
|
|
||||||
// Build the camera at the *full* viewport's aspect so the ortho
|
// Build the camera at the *full* viewport's aspect so the ortho
|
||||||
// frustum matches what the viewport entity stores, then post-
|
// frustum matches what the viewport entity stores, then post-
|
||||||
// multiply by a clip-space "zoom into the visible sub-rect" that
|
// multiply by a clip-space "zoom into the visible sub-rect" that
|
||||||
|
|
@ -2159,7 +2216,7 @@ impl Scene {
|
||||||
uniforms.viewport_size = [visible_w, visible_h];
|
uniforms.viewport_size = [visible_w, visible_h];
|
||||||
uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 };
|
uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 };
|
||||||
uniforms.transparency_enable = if self.transparency_display { 1.0 } else { 0.0 };
|
uniforms.transparency_enable = if self.transparency_display { 1.0 } else { 0.0 };
|
||||||
self.apply_document_lighting(&mut uniforms);
|
self.apply_document_lighting(&mut uniforms, lighting_block, &vp_frozen);
|
||||||
|
|
||||||
// `screen_rect` carries the *visible* sub-rectangle in normalized
|
// `screen_rect` carries the *visible* sub-rectangle in normalized
|
||||||
// canvas coords — that's what `Pipeline::prepare` uses to size
|
// canvas coords — that's what `Pipeline::prepare` uses to size
|
||||||
|
|
@ -2179,25 +2236,8 @@ impl Scene {
|
||||||
// model-block hatches. Those belong inside the floating content
|
// model-block hatches. Those belong inside the floating content
|
||||||
// viewports; rendering them on the full-canvas sheet would let them
|
// viewports; rendering them on the full-canvas sheet would let them
|
||||||
// bleed past the viewport borders whenever model coords overlap the
|
// bleed past the viewport borders whenever model coords overlap the
|
||||||
// paper area. Content viewports keep the full set (the model camera +
|
// paper area. Content viewport model builders are block-filtered too;
|
||||||
// per-viewport scissor place / clip them correctly).
|
// the scissor only clips their already-correct Model Space set.
|
||||||
// Per-viewport frozen-layer set for a paper content viewport. Content
|
|
||||||
// viewports must hide FILLS / IMAGES / SOLIDS on their frozen layers too,
|
|
||||||
// not just wires (which `model_wires_for_viewport_arc` already filters).
|
|
||||||
// Empty for the sheet, model tiles and the implicit-model view — none of
|
|
||||||
// which carry a per-viewport freeze — so those reuse the shared sets.
|
|
||||||
let vp_frozen: rustc_hash::FxHashSet<Handle> = if !inst.paper_sheet
|
|
||||||
&& inst.tile_idx.is_none()
|
|
||||||
&& inst.handle != acadrust::Handle::NULL
|
|
||||||
{
|
|
||||||
match self.document.get_entity(inst.handle) {
|
|
||||||
Some(EntityType::Viewport(vp)) => vp.frozen_layers.iter().cloned().collect(),
|
|
||||||
_ => rustc_hash::FxHashSet::default(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rustc_hash::FxHashSet::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (hatches, wipeout_hatches, paper_images) = if inst.paper_sheet {
|
let (hatches, wipeout_hatches, paper_images) = if inst.paper_sheet {
|
||||||
let (hatches, wipeouts, images) = self.paper_sheet_render_models();
|
let (hatches, wipeouts, images) = self.paper_sheet_render_models();
|
||||||
(hatches, wipeouts, Some(images))
|
(hatches, wipeouts, Some(images))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue