ifc_lite_processing/
prepass_styled.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Styled-item resolution helpers for the pre-pass (split from `prepass.rs`).
6//! The loop here is byte-identical to the historic inline loop in
7//! `resolve_prepass`; the sharded pre-pass fans slices of it across workers.
8
9use ifc_lite_core::EntityDecoder;
10use rustc_hash::FxHashMap;
11
12use crate::prepass::{collect_geometry_style_info, extract_style_info_from_styled_item, surface_style_from_styled_item, Span};
13use crate::style::{GeometryStyleInfo, SpecularMaterial};
14
15/// Pre-resolved styled maps: `(orphan id -> rgba, geometry id -> style info)`.
16pub type StyleSeeds = (FxHashMap<u32, [f32; 4]>, FxHashMap<u32, GeometryStyleInfo>);
17
18/// The styled-item classification/resolution loop of [`resolve_prepass`],
19/// exposed so the browser's SHARDED pre-pass can fan slices of the (file-
20/// ordered) styled-item span list across workers: each worker resolves its
21/// contiguous slice with this exact loop, and the host merges shard results in
22/// shard order with first-wins per geometry id — reproducing the serial
23/// resolver's file-order first-wins precedence (`collect_geometry_style_info`
24/// skips ids already present).
25pub fn resolve_styled_items_into(
26    styled_items: &[Span],
27    decoder: &mut EntityDecoder,
28    defer_attached_styles: bool,
29    orphan_styled_items: &mut FxHashMap<u32, [f32; 4]>,
30    geometry_style_index: &mut FxHashMap<u32, GeometryStyleInfo>,
31    deferred_attached_styled_spans: &mut Vec<(usize, usize)>,
32) {
33    for &(id, start, end) in styled_items {
34        let Ok(styled_item) = decoder.decode_at_with_id(id, start, end) else {
35            if defer_attached_styles {
36                // Undecodable now — let the replay try again later, matching
37                // the historic defer behaviour.
38                deferred_attached_styled_spans.push((start, end));
39            }
40            continue;
41        };
42        if styled_item.get_ref(0).is_none() {
43            // Orphan styled item (null Item) = a material appearance (#407).
44            // Always resolved up front — even in defer mode — or
45            // material-only-styled elements render default-gray (#913 §2c).
46            if let Some(info) = extract_style_info_from_styled_item(&styled_item, decoder) {
47                orphan_styled_items.insert(id, info.color);
48            }
49        } else if defer_attached_styles {
50            deferred_attached_styled_spans.push((start, end));
51        } else {
52            collect_geometry_style_info(geometry_style_index, &styled_item, decoder);
53        }
54    }
55}
56
57
58/// IFC-authored metallic/roughness per styled geometry item (#5582), keyed
59/// exactly like the colour index [`resolve_styled_items_into`] builds: the
60/// FIRST styled item (file order) that yields a style claims its geometry id,
61/// and the finish comes from that same chosen rendering style, so a mesh never
62/// pairs one style's colour with another's finish. A claim whose style authors
63/// no specular evidence is kept as an empty [`SpecularMaterial`] (it encodes
64/// to `[NAN, NAN]`), so a first-wins merge of sharded results cannot take a
65/// later item's finish for it. Orphan (null-item) styled items are skipped,
66/// as they colour materials, not geometry. Fed to
67/// [`crate::prepass::flat_styles_with_finishes`].
68///
69/// A separate pass instead of a field on `GeometryStyleInfo` / `ResolvedPrepass`
70/// so this crate's public structs stay unchanged (semver-additive, #5582).
71pub fn resolve_geometry_finishes(
72    styled_items: &[Span],
73    decoder: &mut EntityDecoder,
74) -> FxHashMap<u32, SpecularMaterial> {
75    let mut finishes: FxHashMap<u32, SpecularMaterial> = FxHashMap::default();
76    let mut claimed_without_finish: rustc_hash::FxHashSet<u32> = Default::default();
77    for &(id, start, end) in styled_items {
78        let Ok(styled_item) = decoder.decode_at_with_id(id, start, end) else { continue };
79        let Some(geometry_id) = styled_item.get_ref(0) else { continue };
80        if finishes.contains_key(&geometry_id) || claimed_without_finish.contains(&geometry_id) {
81            continue;
82        }
83        match surface_style_from_styled_item(&styled_item, decoder) {
84            Some((style_id, _)) => {
85                let finish = crate::style::extract_surface_style_specular(style_id, decoder).unwrap_or_default();
86                finishes.insert(geometry_id, finish);
87            }
88            // A fill-only style claims the colour but has no rendering finish.
89            None if extract_style_info_from_styled_item(&styled_item, decoder).is_some() => {
90                claimed_without_finish.insert(geometry_id);
91            }
92            None => {}
93        }
94    }
95    finishes
96}
97
98/// [`crate::prepass::flat_styles_rgba8`] with the (dominant) geometry-style
99/// source supplied as PRE-MERGED columns instead of a map — the sharded
100/// finalize path. `geom_ids` are unique (the host merged shard results
101/// first-wins) with `geom_colors` as rgba f32 quads; `resolved` carries the
102/// support resolution (indexed colours, materials, element colours) and MUST
103/// have an empty `geometry_style_index`. Output is byte-identical to the
104/// serial flatten: same precedence (geometry > indexed colour > material >
105/// element material), same id-ascending wire order, same rgba8 conversion —
106/// without ever building the 4M-entry geometry hashmap.
107pub fn flat_styles_rgba8_from_geometry_columns(
108    geom_ids: &[u32],
109    geom_colors: &[f32],
110    resolved: &crate::prepass::ResolvedPrepass,
111    decoder: &mut EntityDecoder,
112) -> (Vec<u32>, Vec<u8>) {
113    debug_assert!(resolved.geometry_style_index.is_empty());
114    // Overlay sources in serial precedence order, or_insert among themselves.
115    let mut overlay: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
116    for (&geometry_id, &color) in &resolved.indexed_colour_index {
117        overlay.entry(geometry_id).or_insert(color);
118    }
119    let material_styles = crate::style::build_material_style_index(
120        &resolved.material_def_reprs,
121        &resolved.orphan_styled_items,
122        decoder,
123    );
124    for (&mat_id, &color) in crate::style::flatten_material_color_index(&material_styles).iter() {
125        overlay.entry(mat_id).or_insert(color);
126    }
127    for (&element_id, colors) in &resolved.element_material_colors {
128        if let Some(&color) = colors.first() {
129            overlay.entry(element_id).or_insert(color);
130        }
131    }
132
133    // Sort both sides by id and merge, geometry winning on ties.
134    let mut geom_order: Vec<u32> = (0..geom_ids.len() as u32).collect();
135    geom_order.sort_unstable_by_key(|&i| geom_ids[i as usize]);
136    let mut overlay_sorted: Vec<(u32, [f32; 4])> = overlay.into_iter().collect();
137    overlay_sorted.sort_unstable_by_key(|&(id, _)| id);
138
139    let mut ids: Vec<u32> = Vec::with_capacity(geom_ids.len() + overlay_sorted.len());
140    let mut rgba: Vec<u8> = Vec::with_capacity((geom_ids.len() + overlay_sorted.len()) * 4);
141    let push = |id: u32, color: [f32; 4], ids: &mut Vec<u32>, rgba: &mut Vec<u8>| {
142        ids.push(id);
143        rgba.extend_from_slice(&crate::style::Rgba::from_array(color).to_rgba8());
144    };
145    let mut oi = 0;
146    for &gi in &geom_order {
147        let id = geom_ids[gi as usize];
148        while oi < overlay_sorted.len() && overlay_sorted[oi].0 < id {
149            let (oid, oc) = overlay_sorted[oi];
150            push(oid, oc, &mut ids, &mut rgba);
151            oi += 1;
152        }
153        if oi < overlay_sorted.len() && overlay_sorted[oi].0 == id {
154            oi += 1; // geometry wins the tie, overlay entry dropped
155        }
156        let c = gi as usize * 4;
157        push(
158            id,
159            [geom_colors[c], geom_colors[c + 1], geom_colors[c + 2], geom_colors[c + 3]],
160            &mut ids,
161            &mut rgba,
162        );
163    }
164    while oi < overlay_sorted.len() {
165        let (oid, oc) = overlay_sorted[oi];
166        push(oid, oc, &mut ids, &mut rgba);
167        oi += 1;
168    }
169    (ids, rgba)
170}