Skip to main content

pfx_load/scene/
draws.rs

1use std::collections::BTreeSet;
2use std::sync::{Arc, LazyLock};
3
4use pfx_materials::Material;
5
6use super::{
7    ALPHA_CUTOFF, DEFAULT_MATERIAL, Geometry, IDENTITY, Matrix, Object, Part, Scene, Shadow,
8    multiply, pose,
9};
10
11pub const EMITTER_RINGS: u32 = 12;
12pub const EMITTER_SEGMENTS: u32 = 24;
13
14#[derive(Clone, Debug, PartialEq)]
15pub struct Draw {
16    pub object: String,
17    pub node: String,
18    pub joint: Option<usize>,
19    pub geometry: Arc<Geometry>,
20    pub model: Matrix,
21    pub material: u32,
22    pub id: u32,
23    pub shadow: Shadow,
24    pub two_sided: bool,
25    pub clip: [[f32; 4]; 2],
26    pub content: Option<String>,
27    pub alpha_cutoff: f32,
28    pub face_camera: bool,
29    pub owner: Option<usize>,
30    pub part: Matrix,
31}
32
33#[derive(Clone, Debug, Default, PartialEq)]
34pub struct Draws {
35    pub names: Vec<String>,
36    pub materials: Vec<Material>,
37    pub items: Vec<Draw>,
38}
39
40impl Draws {
41    pub fn material(&self, name: &str) -> Option<u32> {
42        self.names
43            .iter()
44            .position(|candidate| candidate == name)
45            .map(|index| index as u32)
46    }
47
48    pub fn models_posed(
49        &self,
50        objects: &[Matrix],
51        palettes: &std::collections::BTreeMap<usize, Vec<Matrix>>,
52    ) -> Vec<Matrix> {
53        self.items
54            .iter()
55            .map(|draw| {
56                let Some(world) = draw.owner.and_then(|owner| objects.get(owner)) else {
57                    return draw.model;
58                };
59                let joint = draw.joint.and_then(|joint| {
60                    palettes
61                        .get(&draw.owner?)
62                        .and_then(|palette| palette.get(joint))
63                });
64                match joint {
65                    Some(joint) => multiply(*world, *joint),
66                    None => multiply(*world, draw.part),
67                }
68            })
69            .collect()
70    }
71
72    pub fn models(&self, posed: &[Matrix]) -> Vec<Matrix> {
73        self.items
74            .iter()
75            .map(|draw| match draw.owner.and_then(|owner| posed.get(owner)) {
76                Some(world) => multiply(*world, draw.part),
77                None => draw.model,
78            })
79            .collect()
80    }
81}
82
83pub fn emitter_material(name: &str) -> String {
84    format!("emitter {name}")
85}
86
87static SPHERE: LazyLock<Arc<Geometry>> = LazyLock::new(|| Arc::new(sphere()));
88
89pub fn emitter_sphere() -> Arc<Geometry> {
90    SPHERE.clone()
91}
92
93fn sphere() -> Geometry {
94    let mut positions = Vec::new();
95    let mut uvs = Vec::new();
96    for ring in 0..=EMITTER_RINGS {
97        let theta = std::f32::consts::PI * ring as f32 / EMITTER_RINGS as f32;
98        for segment in 0..=EMITTER_SEGMENTS {
99            let phi = std::f32::consts::TAU * segment as f32 / EMITTER_SEGMENTS as f32;
100            positions.push([
101                theta.sin() * phi.cos(),
102                theta.cos(),
103                theta.sin() * phi.sin(),
104            ]);
105            uvs.push([
106                segment as f32 / EMITTER_SEGMENTS as f32,
107                ring as f32 / EMITTER_RINGS as f32,
108            ]);
109        }
110    }
111    let row = EMITTER_SEGMENTS + 1;
112    let mut indices = Vec::new();
113    for ring in 0..EMITTER_RINGS {
114        for segment in 0..EMITTER_SEGMENTS {
115            let a = ring * row + segment;
116            let b = a + row;
117            if ring != 0 {
118                indices.extend([a, a + 1, b]);
119            }
120            if ring != EMITTER_RINGS - 1 {
121                indices.extend([a + 1, b + 1, b]);
122            }
123        }
124    }
125    let count = positions.len();
126    Geometry::new(
127        positions.clone(),
128        positions,
129        vec![[1.0, 0.0, 0.0, 1.0]; count],
130        uvs,
131        None,
132        indices,
133    )
134}
135
136pub(super) fn pick<'a>(scene: &'a Scene, object: &'a Object, part: &'a Part) -> &'a str {
137    if let Some(material) = object.materials.get(&part.node) {
138        return material;
139    }
140    if let Some(material) = &object.material {
141        return material;
142    }
143    if let Some(material) = &part.overrides.material {
144        return material;
145    }
146    if let Some(name) = &part.material {
147        if scene.library.contains(name) {
148            return name;
149        }
150        if let Some((head, _)) = name.split_once('.')
151            && scene.library.contains(head)
152        {
153            return head;
154        }
155    }
156    scene.fallback.as_deref().unwrap_or(DEFAULT_MATERIAL)
157}
158
159pub(super) fn draws(scene: &Scene) -> Draws {
160    let mut used = BTreeSet::new();
161    let mut picked = Vec::new();
162    for (owner, object) in scene.objects.iter().enumerate() {
163        let Some(mesh) = scene.meshes.get(&object.mesh) else {
164            continue;
165        };
166        for part in &mesh.parts {
167            if object.hidden || part.overrides.hidden {
168                continue;
169            }
170            let name = pick(scene, object, part);
171            used.insert(name.to_string());
172            picked.push((owner, object, part, name));
173        }
174    }
175    for name in scene.emitters.keys() {
176        used.insert(emitter_material(name));
177    }
178    let names: Vec<String> = used.into_iter().collect();
179    let materials = names
180        .iter()
181        .map(|name| match scene.library.get(name) {
182            Some(material) => *material,
183            None => match name
184                .strip_prefix("emitter ")
185                .and_then(|key| scene.emitters.get(key))
186            {
187                Some(emitter) => {
188                    let area = std::f32::consts::PI * emitter.radius * emitter.radius;
189                    Material {
190                        base: [0.0; 3],
191                        roughness: 1.0,
192                        specular: 0.0,
193                        emission: emitter.color.map(|c| c * emitter.intensity / area),
194                        ..Material::default()
195                    }
196                }
197                None => Material::default(),
198            },
199        })
200        .collect();
201    let index = |name: &str| {
202        names
203            .iter()
204            .position(|candidate| candidate == name)
205            .unwrap_or(0) as u32
206    };
207    let mut items = Vec::new();
208    for (owner, object, part, name) in picked {
209        let mut clip = [[0.0; 4]; 2];
210        for (slot, plane) in clip.iter_mut().zip(pose::clips(scene, owner)) {
211            *slot = plane;
212        }
213        items.push(Draw {
214            object: object.name.clone(),
215            node: part.node.clone(),
216            joint: part.joint,
217            geometry: part.geometry.clone(),
218            model: multiply(object.model, part.transform),
219            material: index(name),
220            id: object.id,
221            shadow: part.overrides.shadow.unwrap_or(object.shadow),
222            two_sided: part.overrides.two_sided.unwrap_or(object.two_sided),
223            clip,
224            content: object.content.clone(),
225            alpha_cutoff: object.alpha_cutoff,
226            face_camera: object.face_camera,
227            owner: Some(owner),
228            part: part.transform,
229        });
230    }
231    let first_id = scene
232        .objects
233        .iter()
234        .map(|object| object.id)
235        .max()
236        .unwrap_or(0)
237        + 1;
238    for (place, (name, emitter)) in scene.emitters.iter().enumerate() {
239        let mut model = IDENTITY;
240        for (axis, column) in model.iter_mut().take(3).enumerate() {
241            column[axis] = emitter.radius;
242        }
243        model[3] = [
244            emitter.position[0],
245            emitter.position[1],
246            emitter.position[2],
247            1.0,
248        ];
249        items.push(Draw {
250            object: name.clone(),
251            node: String::new(),
252            joint: None,
253            geometry: emitter_sphere(),
254            model,
255            material: index(&emitter_material(name)),
256            id: first_id + place as u32,
257            shadow: Shadow::None,
258            two_sided: false,
259            clip: [[0.0; 4]; 2],
260            content: None,
261            alpha_cutoff: ALPHA_CUTOFF,
262            face_camera: false,
263            owner: None,
264            part: IDENTITY,
265        });
266    }
267    Draws {
268        names,
269        materials,
270        items,
271    }
272}