1use serde_json::{Value, json};
7
8use super::TextureSource;
9use super::encoding::NormalSpace;
10use super::lights::{LightListPart, LightValue};
11use super::roles::{
12 Attribute, BufferSource, BufferTable, ConstantRole, FrameParameter, FrameTexture, InstanceValue,
13 ItemParameter, ObjectMatrix, ObjectParameter, OutputRole, ViewParameter,
14};
15use crate::frame::SourceFrame;
16use crate::json::JsonError;
17use crate::world::{Resource, World};
18
19#[derive(Clone, Debug, PartialEq)]
26pub enum ProgramFrame {
27 Source(SourceFrame),
29 NotConverted {
33 why: String,
35 },
36}
37
38impl ProgramFrame {
39 pub fn frame(&self) -> Option<&SourceFrame> {
41 match self {
42 ProgramFrame::Source(frame) => Some(frame),
43 ProgramFrame::NotConverted { .. } => None,
44 }
45 }
46
47 pub fn state(self, world: &mut World) -> Result<(), String> {
51 match world.resource::<ProgramFrame>() {
52 None => {
53 world.insert_resource(self);
54 Ok(())
55 }
56 Some(stated) if *stated == self => Ok(()),
57 Some(stated) => Err(format!(
58 "the world's programs are already stated to compute in {stated:?}, not {self:?}"
59 )),
60 }
61 }
62}
63
64impl Resource for ProgramFrame {
65 const NAME: &'static str = "core.program_frame";
66 fn to_json(&self) -> Result<Option<Value>, JsonError> {
67 Ok(Some(match self {
68 ProgramFrame::Source(frame) => json!({
69 "source": {
70 "axes": frame.axes().map(|a| format!("{a:?}")),
71 "metres_per_unit": frame.metres_per_unit(),
72 }
73 }),
74 ProgramFrame::NotConverted { why } => json!({ "not_converted": why }),
75 }))
76 }
77 fn verify(&self, _w: &World) -> Result<(), String> {
79 match self {
80 ProgramFrame::Source(_) => Ok(()),
81 ProgramFrame::NotConverted { why } if why.trim().is_empty() => {
82 Err("a program frame that is not converted must say why".into())
83 }
84 ProgramFrame::NotConverted { .. } => Ok(()),
85 }
86 }
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94pub enum SpatialKind {
95 None,
97 Point,
100 Direction,
102 Tangent,
104 Plane,
106 Length,
108 InverseLength,
110 FromWorld,
113 ToWorld,
116 WithinWorld,
119 WithinWorldColumn,
122 LightRecord,
124 CubeByDirection,
126 VolumeByPosition,
128 ImageOfDirections,
130}
131
132impl Attribute {
133 pub fn spatial_kind(self) -> SpatialKind {
136 match self {
137 Attribute::Position | Attribute::Point(_) => SpatialKind::Point,
138 Attribute::Normal => SpatialKind::Direction,
139 Attribute::Tangent(_) => SpatialKind::Tangent,
140 Attribute::TexCoord(_)
141 | Attribute::Color(_)
142 | Attribute::Joints(_)
143 | Attribute::Weights(_)
144 | Attribute::ShaderInput(_) => SpatialKind::None,
145 }
146 }
147}
148
149impl InstanceValue {
150 pub fn spatial_kind(self) -> SpatialKind {
152 match self {
153 InstanceValue::Translation => SpatialKind::Point,
154 InstanceValue::Axis(_) => SpatialKind::WithinWorldColumn,
155 InstanceValue::Scale | InstanceValue::LodBlend => SpatialKind::None,
156 }
157 }
158}
159
160impl ViewParameter {
161 pub fn spatial_kind(self) -> SpatialKind {
163 match self {
164 ViewParameter::CameraPosition => SpatialKind::Point,
165 ViewParameter::View
166 | ViewParameter::ViewProjection
167 | ViewParameter::CameraRelativeViewProjection
168 | ViewParameter::PreviousViewProjection => SpatialKind::FromWorld,
169 ViewParameter::InverseView | ViewParameter::InverseViewProjection => SpatialKind::ToWorld,
170 ViewParameter::Projection
171 | ViewParameter::InverseProjection
172 | ViewParameter::ViewportSize
173 | ViewParameter::InverseViewportSize
174 | ViewParameter::NearPlane
175 | ViewParameter::FarPlane
176 | ViewParameter::InverseFarPlane
177 | ViewParameter::ProjectionFlip
178 | ViewParameter::ResolutionScale
179 | ViewParameter::Perspective
180 | ViewParameter::DepthRange
181 | ViewParameter::DeviceDepthOffset
182 | ViewParameter::DeviceDepthScale => SpatialKind::None,
183 }
184 }
185}
186
187impl ObjectMatrix {
188 pub fn spatial_kind(self) -> SpatialKind {
190 match self {
191 ObjectMatrix::World => SpatialKind::WithinWorld,
192 ObjectMatrix::WorldView | ObjectMatrix::WorldViewProjection => SpatialKind::FromWorld,
193 }
194 }
195}
196
197impl ObjectParameter {
198 pub fn spatial_kind(self) -> SpatialKind {
200 match self {
201 ObjectParameter::World
202 | ObjectParameter::InverseWorld
203 | ObjectParameter::BonePalette
204 | ObjectParameter::ClothCancelPalette => SpatialKind::WithinWorld,
205 ObjectParameter::WorldViewProjection | ObjectParameter::WorldView => SpatialKind::FromWorld,
206 ObjectParameter::ViewToObject => SpatialKind::ToWorld,
207 ObjectParameter::PreviousFrame(m)
208 | ObjectParameter::CameraRelative(m)
209 | ObjectParameter::CameraRelativePreviousFrame(m) => m.spatial_kind(),
210 ObjectParameter::ClipPlane => SpatialKind::Plane,
211 ObjectParameter::CameraFadeInverseRange => SpatialKind::InverseLength,
212 ObjectParameter::InstanceColour
213 | ObjectParameter::TextureScroll(_)
214 | ObjectParameter::EnvironmentData
215 | ObjectParameter::MatrixBase
216 | ObjectParameter::InstanceBase
217 | ObjectParameter::ClothSimulation
218 | ObjectParameter::TextureTransformRow(_)
219 | ObjectParameter::PaletteCoordinate
220 | ObjectParameter::CameraFadeEnabled
221 | ObjectParameter::CameraFadeEnd
222 | ObjectParameter::DitherAlphaEnabled
223 | ObjectParameter::DitherEnabled
224 | ObjectParameter::Fade
225 | ObjectParameter::WindOff => SpatialKind::None,
226 }
227 }
228}
229
230impl FrameParameter {
231 pub fn spatial_kind(self) -> SpatialKind {
233 match self {
234 FrameParameter::LightDirection | FrameParameter::DirectionToLight => SpatialKind::Direction,
235 FrameParameter::Time
236 | FrameParameter::TimeOfDay
237 | FrameParameter::PreExposure
238 | FrameParameter::InversePreExposure
239 | FrameParameter::Exposure
240 | FrameParameter::InverseExposure
241 | FrameParameter::ColourSlot
242 | FrameParameter::LightColour
243 | FrameParameter::LightSpecularColour
244 | FrameParameter::DirectionalAmbientColour
245 | FrameParameter::LightCount(_)
246 | FrameParameter::LightTileSize
247 | FrameParameter::LightTilesPerRow
248 | FrameParameter::LocalShadowAtlasSize
249 | FrameParameter::InverseLocalShadowAtlasSize => SpatialKind::None,
250 }
251 }
252}
253
254impl ItemParameter {
255 pub fn spatial_kind(self) -> SpatialKind {
257 match self {
258 ItemParameter::LightRecord => SpatialKind::LightRecord,
259 }
260 }
261}
262
263impl ConstantRole {
264 pub fn spatial_kind(&self) -> SpatialKind {
267 match self {
268 ConstantRole::View(v) => v.spatial_kind(),
269 ConstantRole::Object(o) => o.spatial_kind(),
270 ConstantRole::Frame(f) => f.spatial_kind(),
271 ConstantRole::Item(i) => i.spatial_kind(),
272 ConstantRole::Material(_)
273 | ConstantRole::Default { .. }
274 | ConstantRole::AlphaReference { .. }
275 | ConstantRole::Unknown { .. } => SpatialKind::None,
276 }
277 }
278}
279
280impl LightValue {
281 pub fn spatial_kind(&self) -> SpatialKind {
284 match self {
285 LightValue::ShadowCentre => SpatialKind::Point,
286 LightValue::ViewPosition
287 | LightValue::Range
288 | LightValue::InverseRangeSquared
289 | LightValue::InverseFalloffDistance
290 | LightValue::TranslucencyStrength
291 | LightValue::TranslucencyExponentScale
292 | LightValue::Colour
293 | LightValue::SpecularColour
294 | LightValue::ViewToCone
295 | LightValue::ViewBoundingSphere
296 | LightValue::VerticalConeSphere { .. }
297 | LightValue::ViewDepthMin
298 | LightValue::ViewDepthMax
299 | LightValue::Shadowed
300 | LightValue::ShadowProjection { .. }
301 | LightValue::ShadowFaces { .. }
302 | LightValue::Constant { .. }
303 | LightValue::Unknown { .. } => SpatialKind::None,
304 }
305 }
306}
307
308impl BufferTable {
309 pub fn spatial_kind(&self) -> SpatialKind {
312 match self {
313 BufferTable::ObjectMatrices { .. }
314 | BufferTable::HeaderedMatrices { .. }
315 | BufferTable::SkinnedMatrices { .. } => SpatialKind::WithinWorld,
316 BufferTable::Instances { .. } => SpatialKind::None,
317 }
318 }
319}
320
321impl BufferSource {
322 pub fn spatial_kind(&self) -> SpatialKind {
325 match self {
326 BufferSource::Table(t) => t.spatial_kind(),
327 BufferSource::Lights(_) => SpatialKind::LightRecord,
328 BufferSource::LightLists(LightListPart::Lookup(_) | LightListPart::Indices(_))
329 | BufferSource::Graph(_)
330 | BufferSource::Unknown { .. } => SpatialKind::None,
331 }
332 }
333}
334
335impl FrameTexture {
336 pub fn spatial_kind(self) -> SpatialKind {
338 match self {
339 FrameTexture::ReflectionProbe => SpatialKind::CubeByDirection,
340 FrameTexture::LightProbeVolume => SpatialKind::VolumeByPosition,
341 FrameTexture::DecalBuffer(1) => SpatialKind::ImageOfDirections,
342 FrameTexture::DecalBuffer(_)
343 | FrameTexture::SceneDepth
344 | FrameTexture::SceneColour
345 | FrameTexture::ShadowMap
346 | FrameTexture::ScreenSpaceShadows
347 | FrameTexture::AmbientOcclusion => SpatialKind::None,
348 }
349 }
350}
351
352impl TextureSource {
353 pub fn spatial_kind(&self) -> SpatialKind {
356 match self {
357 TextureSource::Frame(t) => t.spatial_kind(),
358 TextureSource::Texture { .. }
359 | TextureSource::Default { .. }
360 | TextureSource::Missing { .. }
361 | TextureSource::Target { .. } => SpatialKind::None,
362 }
363 }
364}
365
366impl OutputRole {
367 pub fn spatial_kind(&self) -> SpatialKind {
369 match self {
370 OutputRole::Normal {
371 space: NormalSpace::World,
372 ..
373 } => SpatialKind::Direction,
374 OutputRole::Normal {
375 space: NormalSpace::View | NormalSpace::Tangent,
376 ..
377 }
378 | OutputRole::Albedo
379 | OutputRole::Gloss { .. }
380 | OutputRole::Roughness
381 | OutputRole::Metallic
382 | OutputRole::Occlusion { .. }
383 | OutputRole::SpecularOcclusion
384 | OutputRole::Translucency
385 | OutputRole::Reflectance { .. }
386 | OutputRole::SpecularIntensity { .. }
387 | OutputRole::SpecularExponent { .. }
388 | OutputRole::Fresnel
389 | OutputRole::MotionVector
390 | OutputRole::Emission { .. }
391 | OutputRole::Colour
392 | OutputRole::Coverage
393 | OutputRole::Opacity
394 | OutputRole::Unknown { .. } => SpatialKind::None,
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::frame::Axis::{NegZ, PosX, PosY};
403
404 const MIRROR: SourceFrame = SourceFrame::new([PosX, PosY, NegZ], 1.0);
405
406 #[test]
407 fn a_frame_is_stated_and_one_not_converted_says_why() {
408 let w = World::new();
409 assert_eq!(ProgramFrame::Source(MIRROR).frame(), Some(&MIRROR));
410 assert_eq!(ProgramFrame::Source(MIRROR).verify(&w), Ok(()));
411 let none = ProgramFrame::NotConverted { why: " ".into() };
412 assert_eq!(none.frame(), None);
413 assert!(none.verify(&w).unwrap_err().contains("why"));
414 let json = ProgramFrame::Source(MIRROR).to_json().unwrap().unwrap();
415 assert_eq!(json["source"]["axes"], json!(["PosX", "PosY", "NegZ"]));
416 }
417
418 #[test]
420 fn a_world_states_one_frame() {
421 let mut w = World::new();
422 ProgramFrame::Source(MIRROR).state(&mut w).unwrap();
423 ProgramFrame::Source(MIRROR).state(&mut w).unwrap();
424 assert_eq!(w.resource::<ProgramFrame>(), Some(&ProgramFrame::Source(MIRROR)));
425 let other = ProgramFrame::NotConverted {
426 why: "a world".into(),
427 };
428 assert!(other.state(&mut w).unwrap_err().contains("already stated"));
429 assert_eq!(w.resource::<ProgramFrame>(), Some(&ProgramFrame::Source(MIRROR)));
430 }
431
432 #[test]
435 fn each_role_states_how_it_moves() {
436 assert_eq!(Attribute::Position.spatial_kind(), SpatialKind::Point);
437 assert_eq!(Attribute::Point(3).spatial_kind(), SpatialKind::Point);
438 assert_eq!(Attribute::Normal.spatial_kind(), SpatialKind::Direction);
439 assert_eq!(Attribute::Tangent(0).spatial_kind(), SpatialKind::Tangent);
440 assert_eq!(Attribute::Color(3).spatial_kind(), SpatialKind::None);
441 let constant = |r: ConstantRole| r.spatial_kind();
442 assert_eq!(
443 constant(ConstantRole::View(ViewParameter::CameraPosition)),
444 SpatialKind::Point
445 );
446 assert_eq!(
447 constant(ConstantRole::View(ViewParameter::ViewProjection)),
448 SpatialKind::FromWorld
449 );
450 assert_eq!(
451 constant(ConstantRole::View(ViewParameter::InverseView)),
452 SpatialKind::ToWorld
453 );
454 assert_eq!(
455 constant(ConstantRole::View(ViewParameter::Projection)),
456 SpatialKind::None
457 );
458 assert_eq!(
459 constant(ConstantRole::Object(ObjectParameter::World)),
460 SpatialKind::WithinWorld
461 );
462 assert_eq!(
463 constant(ConstantRole::Object(ObjectParameter::CameraRelative(
464 ObjectMatrix::WorldViewProjection
465 ))),
466 SpatialKind::FromWorld
467 );
468 assert_eq!(
469 constant(ConstantRole::Object(ObjectParameter::ClipPlane)),
470 SpatialKind::Plane
471 );
472 assert_eq!(
473 constant(ConstantRole::Object(ObjectParameter::CameraFadeInverseRange)),
474 SpatialKind::InverseLength
475 );
476 assert_eq!(
477 constant(ConstantRole::Object(ObjectParameter::CameraFadeEnd)),
478 SpatialKind::None
479 );
480 assert_eq!(
481 constant(ConstantRole::Frame(FrameParameter::DirectionToLight)),
482 SpatialKind::Direction
483 );
484 assert_eq!(
485 constant(ConstantRole::Material(vec![0, 0, 0x3f80_0000])),
486 SpatialKind::None
487 );
488 assert_eq!(
489 InstanceValue::Axis(2).spatial_kind(),
490 SpatialKind::WithinWorldColumn
491 );
492 assert_eq!(LightValue::ShadowCentre.spatial_kind(), SpatialKind::Point);
493 assert_eq!(LightValue::ViewPosition.spatial_kind(), SpatialKind::None);
494 assert_eq!(
495 FrameTexture::ReflectionProbe.spatial_kind(),
496 SpatialKind::CubeByDirection
497 );
498 }
499}