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}