Skip to main content

pfx_editor_viewport/
pick.rs

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}