1use std::collections::BTreeMap;
2
3use glam::{DMat4, DVec3};
4use pfx_load::scene::{Draws, Matrix, Scene};
5
6use crate::camera::{Bounds, EMPTY};
7use crate::lens::{Ray, slab, triangle};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct Selection {
11 pub index: usize,
12 pub id: u32,
13 pub name: String,
14}
15
16impl Selection {
17 pub fn of(scene: &Scene, index: usize) -> Option<Selection> {
18 let object = scene.objects.get(index)?;
19 Some(Selection {
20 index,
21 id: object.id,
22 name: object.name.clone(),
23 })
24 }
25
26 pub fn named(scene: &Scene, name: &str) -> Option<Selection> {
27 let index = scene
28 .objects
29 .iter()
30 .position(|object| object.name == name)?;
31 Selection::of(scene, index)
32 }
33
34 pub fn by_id(scene: &Scene, id: u32) -> Option<Selection> {
35 if id == 0 {
36 return None;
37 }
38 let index = scene.objects.iter().position(|object| object.id == id)?;
39 Selection::of(scene, index)
40 }
41}
42
43pub fn matrix(model: Matrix) -> DMat4 {
44 DMat4::from_cols_array_2d(&model.map(|column| column.map(f64::from)))
45}
46
47fn corners(low: DVec3, high: DVec3) -> [DVec3; 8] {
48 std::array::from_fn(|k| {
49 DVec3::new(
50 if k & 1 == 0 { low.x } else { high.x },
51 if k & 2 == 0 { low.y } else { high.y },
52 if k & 4 == 0 { low.z } else { high.z },
53 )
54 })
55}
56
57pub const EDGES: [[usize; 2]; 12] = [
58 [0, 1],
59 [2, 3],
60 [4, 5],
61 [6, 7],
62 [0, 2],
63 [1, 3],
64 [4, 6],
65 [5, 7],
66 [0, 4],
67 [1, 5],
68 [2, 6],
69 [3, 7],
70];
71
72#[derive(Clone, Debug)]
73struct Part {
74 object: Option<usize>,
75 model: DMat4,
76 inverse: DMat4,
77 low: DVec3,
78 high: DVec3,
79 drawn: bool,
80}
81
82pub type Extents = BTreeMap<[u8; 32], (DVec3, DVec3)>;
83
84#[derive(Clone, Debug)]
85pub struct Picker {
86 draws: Draws,
87 parts: Vec<Part>,
88 parents: Vec<Option<usize>>,
89 bounds: Bounds,
90}
91
92fn local_bounds(positions: &[[f32; 3]]) -> (DVec3, DVec3) {
93 let mut low = DVec3::INFINITY;
94 let mut high = DVec3::NEG_INFINITY;
95 for p in positions {
96 let p = DVec3::new(f64::from(p[0]), f64::from(p[1]), f64::from(p[2]));
97 low = low.min(p);
98 high = high.max(p);
99 }
100 if low.is_finite() && high.is_finite() {
101 (low, high)
102 } else {
103 (DVec3::ZERO, DVec3::ZERO)
104 }
105}
106
107impl Picker {
108 pub fn of(scene: &Scene, extents: &mut Extents) -> Picker {
109 let names: BTreeMap<&str, usize> = scene
110 .objects
111 .iter()
112 .enumerate()
113 .map(|(index, object)| (object.name.as_str(), index))
114 .collect();
115 let draws = scene.draws();
116 let parts: Vec<Part> = draws
117 .items
118 .iter()
119 .map(|draw| {
120 let (low, high) = *extents
121 .entry(draw.geometry.hash)
122 .or_insert_with(|| local_bounds(&draw.geometry.positions));
123 let model = matrix(draw.model);
124 Part {
125 object: names.get(draw.object.as_str()).copied(),
126 inverse: model.inverse(),
127 model,
128 low,
129 high,
130 drawn: draw.shadow.drawn(),
131 }
132 })
133 .collect();
134 let mut low = DVec3::INFINITY;
135 let mut high = DVec3::NEG_INFINITY;
136 for part in parts.iter().filter(|part| part.drawn) {
137 for corner in corners(part.low, part.high) {
138 let at = part.model.transform_point3(corner);
139 low = low.min(at);
140 high = high.max(at);
141 }
142 }
143 let bounds = if low.is_finite() && high.is_finite() {
144 [low.to_array(), high.to_array()]
145 } else {
146 EMPTY
147 };
148 let parents = scene
149 .objects
150 .iter()
151 .map(|object| {
152 object
153 .parent
154 .as_deref()
155 .and_then(|parent| names.get(parent).copied())
156 })
157 .collect();
158 Picker {
159 draws,
160 parts,
161 parents,
162 bounds,
163 }
164 }
165
166 pub fn draws(&self) -> &Draws {
167 &self.draws
168 }
169
170 pub fn bounds(&self) -> Bounds {
171 self.bounds
172 }
173
174 pub fn family(&self, object: usize) -> Vec<usize> {
175 let mut found = vec![object];
176 let mut index = 0;
177 while index < found.len() {
178 let parent = found[index];
179 for (child, above) in self.parents.iter().enumerate() {
180 if *above == Some(parent) && !found.contains(&child) {
181 found.push(child);
182 }
183 }
184 index += 1;
185 }
186 found
187 }
188
189 fn holds(&self, object: usize) -> impl Fn(&&Part) -> bool {
190 let family = self.family(object);
191 move |part| part.object.is_some_and(|owner| family.contains(&owner))
192 }
193
194 pub fn bounds_of(&self, object: usize) -> Option<Bounds> {
195 let mut low = DVec3::INFINITY;
196 let mut high = DVec3::NEG_INFINITY;
197 for part in self.parts.iter().filter(self.holds(object)) {
198 for corner in corners(part.low, part.high) {
199 let at = part.model.transform_point3(corner);
200 low = low.min(at);
201 high = high.max(at);
202 }
203 }
204 (low.is_finite() && high.is_finite()).then(|| [low.to_array(), high.to_array()])
205 }
206
207 pub fn outline(&self, object: usize) -> Vec<[DVec3; 8]> {
208 self.parts
209 .iter()
210 .filter(self.holds(object))
211 .filter(|part| part.drawn)
212 .map(|part| corners(part.low, part.high).map(|at| part.model.transform_point3(at)))
213 .collect()
214 }
215
216 pub fn cast(&self, ray: &Ray) -> Option<usize> {
217 let mut nearest: Option<(f64, usize)> = None;
218 for (part, draw) in self.parts.iter().zip(&self.draws.items) {
219 let (true, Some(object)) = (part.drawn, part.object) else {
220 continue;
221 };
222 let local = Ray {
223 origin: part.inverse.transform_point3(ray.origin),
224 direction: part.inverse.transform_vector3(ray.direction),
225 };
226 let Some(entry) = slab(&local, part.low, part.high) else {
227 continue;
228 };
229 if nearest.is_some_and(|(best, _)| entry > best) {
230 continue;
231 }
232 let geometry = &draw.geometry;
233 let corner = |index: u32| {
234 let p = geometry.positions[index as usize];
235 DVec3::new(f64::from(p[0]), f64::from(p[1]), f64::from(p[2]))
236 };
237 for face in geometry.indices.chunks_exact(3) {
238 if let Some(distance) =
239 triangle(&local, [corner(face[0]), corner(face[1]), corner(face[2])])
240 && nearest.is_none_or(|(best, _)| distance < best)
241 {
242 nearest = Some((distance, object));
243 }
244 }
245 }
246 nearest.map(|(_, object)| object)
247 }
248}