use serde_json::{Value, json};
use super::TextureSource;
use super::encoding::NormalSpace;
use super::lights::{LightListPart, LightValue};
use super::roles::{
Attribute, BufferSource, BufferTable, ConstantRole, FrameParameter, FrameTexture, InstanceValue,
ItemParameter, ObjectMatrix, ObjectParameter, OutputRole, ViewParameter,
};
use crate::frame::SourceFrame;
use crate::json::JsonError;
use crate::world::{Resource, World};
#[derive(Clone, Debug, PartialEq)]
pub enum ProgramFrame {
Source(SourceFrame),
NotConverted {
why: String,
},
}
impl ProgramFrame {
pub fn frame(&self) -> Option<&SourceFrame> {
match self {
ProgramFrame::Source(frame) => Some(frame),
ProgramFrame::NotConverted { .. } => None,
}
}
pub fn state(self, world: &mut World) -> Result<(), String> {
match world.resource::<ProgramFrame>() {
None => {
world.insert_resource(self);
Ok(())
}
Some(stated) if *stated == self => Ok(()),
Some(stated) => Err(format!(
"the world's programs are already stated to compute in {stated:?}, not {self:?}"
)),
}
}
}
impl Resource for ProgramFrame {
const NAME: &'static str = "core.program_frame";
fn to_json(&self) -> Result<Option<Value>, JsonError> {
Ok(Some(match self {
ProgramFrame::Source(frame) => json!({
"source": {
"axes": frame.axes().map(|a| format!("{a:?}")),
"metres_per_unit": frame.metres_per_unit(),
}
}),
ProgramFrame::NotConverted { why } => json!({ "not_converted": why }),
}))
}
fn verify(&self, _w: &World) -> Result<(), String> {
match self {
ProgramFrame::Source(_) => Ok(()),
ProgramFrame::NotConverted { why } if why.trim().is_empty() => {
Err("a program frame that is not converted must say why".into())
}
ProgramFrame::NotConverted { .. } => Ok(()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SpatialKind {
None,
Point,
Direction,
Tangent,
Plane,
Length,
InverseLength,
FromWorld,
ToWorld,
WithinWorld,
WithinWorldColumn,
LightRecord,
CubeByDirection,
VolumeByPosition,
ImageOfDirections,
}
impl Attribute {
pub fn spatial_kind(self) -> SpatialKind {
match self {
Attribute::Position | Attribute::Point(_) => SpatialKind::Point,
Attribute::Normal => SpatialKind::Direction,
Attribute::Tangent(_) => SpatialKind::Tangent,
Attribute::TexCoord(_)
| Attribute::Color(_)
| Attribute::Joints(_)
| Attribute::Weights(_)
| Attribute::ShaderInput(_) => SpatialKind::None,
}
}
}
impl InstanceValue {
pub fn spatial_kind(self) -> SpatialKind {
match self {
InstanceValue::Translation => SpatialKind::Point,
InstanceValue::Axis(_) => SpatialKind::WithinWorldColumn,
InstanceValue::Scale | InstanceValue::LodBlend => SpatialKind::None,
}
}
}
impl ViewParameter {
pub fn spatial_kind(self) -> SpatialKind {
match self {
ViewParameter::CameraPosition => SpatialKind::Point,
ViewParameter::View
| ViewParameter::ViewProjection
| ViewParameter::CameraRelativeViewProjection
| ViewParameter::PreviousViewProjection => SpatialKind::FromWorld,
ViewParameter::InverseView | ViewParameter::InverseViewProjection => SpatialKind::ToWorld,
ViewParameter::Projection
| ViewParameter::InverseProjection
| ViewParameter::ViewportSize
| ViewParameter::InverseViewportSize
| ViewParameter::NearPlane
| ViewParameter::FarPlane
| ViewParameter::InverseFarPlane
| ViewParameter::ProjectionFlip
| ViewParameter::ResolutionScale
| ViewParameter::Perspective
| ViewParameter::DepthRange
| ViewParameter::DeviceDepthOffset
| ViewParameter::DeviceDepthScale => SpatialKind::None,
}
}
}
impl ObjectMatrix {
pub fn spatial_kind(self) -> SpatialKind {
match self {
ObjectMatrix::World => SpatialKind::WithinWorld,
ObjectMatrix::WorldView | ObjectMatrix::WorldViewProjection => SpatialKind::FromWorld,
}
}
}
impl ObjectParameter {
pub fn spatial_kind(self) -> SpatialKind {
match self {
ObjectParameter::World
| ObjectParameter::InverseWorld
| ObjectParameter::BonePalette
| ObjectParameter::ClothCancelPalette => SpatialKind::WithinWorld,
ObjectParameter::WorldViewProjection | ObjectParameter::WorldView => SpatialKind::FromWorld,
ObjectParameter::ViewToObject => SpatialKind::ToWorld,
ObjectParameter::PreviousFrame(m)
| ObjectParameter::CameraRelative(m)
| ObjectParameter::CameraRelativePreviousFrame(m) => m.spatial_kind(),
ObjectParameter::ClipPlane => SpatialKind::Plane,
ObjectParameter::CameraFadeInverseRange => SpatialKind::InverseLength,
ObjectParameter::InstanceColour
| ObjectParameter::TextureScroll(_)
| ObjectParameter::EnvironmentData
| ObjectParameter::MatrixBase
| ObjectParameter::InstanceBase
| ObjectParameter::ClothSimulation
| ObjectParameter::TextureTransformRow(_)
| ObjectParameter::PaletteCoordinate
| ObjectParameter::CameraFadeEnabled
| ObjectParameter::CameraFadeEnd
| ObjectParameter::DitherAlphaEnabled
| ObjectParameter::DitherEnabled
| ObjectParameter::Fade
| ObjectParameter::WindOff => SpatialKind::None,
}
}
}
impl FrameParameter {
pub fn spatial_kind(self) -> SpatialKind {
match self {
FrameParameter::LightDirection | FrameParameter::DirectionToLight => SpatialKind::Direction,
FrameParameter::Time
| FrameParameter::TimeOfDay
| FrameParameter::PreExposure
| FrameParameter::InversePreExposure
| FrameParameter::Exposure
| FrameParameter::InverseExposure
| FrameParameter::ColourSlot
| FrameParameter::LightColour
| FrameParameter::LightSpecularColour
| FrameParameter::DirectionalAmbientColour
| FrameParameter::LightCount(_)
| FrameParameter::LightTileSize
| FrameParameter::LightTilesPerRow
| FrameParameter::LocalShadowAtlasSize
| FrameParameter::InverseLocalShadowAtlasSize => SpatialKind::None,
}
}
}
impl ItemParameter {
pub fn spatial_kind(self) -> SpatialKind {
match self {
ItemParameter::LightRecord => SpatialKind::LightRecord,
}
}
}
impl ConstantRole {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
ConstantRole::View(v) => v.spatial_kind(),
ConstantRole::Object(o) => o.spatial_kind(),
ConstantRole::Frame(f) => f.spatial_kind(),
ConstantRole::Item(i) => i.spatial_kind(),
ConstantRole::Material(_)
| ConstantRole::Default { .. }
| ConstantRole::AlphaReference { .. }
| ConstantRole::Unknown { .. } => SpatialKind::None,
}
}
}
impl LightValue {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
LightValue::ShadowCentre => SpatialKind::Point,
LightValue::ViewPosition
| LightValue::Range
| LightValue::InverseRangeSquared
| LightValue::InverseFalloffDistance
| LightValue::TranslucencyStrength
| LightValue::TranslucencyExponentScale
| LightValue::Colour
| LightValue::SpecularColour
| LightValue::ViewToCone
| LightValue::ViewBoundingSphere
| LightValue::VerticalConeSphere { .. }
| LightValue::ViewDepthMin
| LightValue::ViewDepthMax
| LightValue::Shadowed
| LightValue::ShadowProjection { .. }
| LightValue::ShadowFaces { .. }
| LightValue::Constant { .. }
| LightValue::Unknown { .. } => SpatialKind::None,
}
}
}
impl BufferTable {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
BufferTable::ObjectMatrices { .. }
| BufferTable::HeaderedMatrices { .. }
| BufferTable::SkinnedMatrices { .. } => SpatialKind::WithinWorld,
BufferTable::Instances { .. } => SpatialKind::None,
}
}
}
impl BufferSource {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
BufferSource::Table(t) => t.spatial_kind(),
BufferSource::Lights(_) => SpatialKind::LightRecord,
BufferSource::LightLists(LightListPart::Lookup(_) | LightListPart::Indices(_))
| BufferSource::Graph(_)
| BufferSource::Unknown { .. } => SpatialKind::None,
}
}
}
impl FrameTexture {
pub fn spatial_kind(self) -> SpatialKind {
match self {
FrameTexture::ReflectionProbe => SpatialKind::CubeByDirection,
FrameTexture::LightProbeVolume => SpatialKind::VolumeByPosition,
FrameTexture::DecalBuffer(1) => SpatialKind::ImageOfDirections,
FrameTexture::DecalBuffer(_)
| FrameTexture::SceneDepth
| FrameTexture::SceneColour
| FrameTexture::ShadowMap
| FrameTexture::ScreenSpaceShadows
| FrameTexture::AmbientOcclusion => SpatialKind::None,
}
}
}
impl TextureSource {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
TextureSource::Frame(t) => t.spatial_kind(),
TextureSource::Texture { .. }
| TextureSource::Default { .. }
| TextureSource::Missing { .. }
| TextureSource::Target { .. } => SpatialKind::None,
}
}
}
impl OutputRole {
pub fn spatial_kind(&self) -> SpatialKind {
match self {
OutputRole::Normal {
space: NormalSpace::World,
..
} => SpatialKind::Direction,
OutputRole::Normal {
space: NormalSpace::View | NormalSpace::Tangent,
..
}
| OutputRole::Albedo
| OutputRole::Gloss { .. }
| OutputRole::Roughness
| OutputRole::Metallic
| OutputRole::Occlusion { .. }
| OutputRole::SpecularOcclusion
| OutputRole::Translucency
| OutputRole::Reflectance { .. }
| OutputRole::SpecularIntensity { .. }
| OutputRole::SpecularExponent { .. }
| OutputRole::Fresnel
| OutputRole::MotionVector
| OutputRole::Emission { .. }
| OutputRole::Colour
| OutputRole::Coverage
| OutputRole::Opacity
| OutputRole::Unknown { .. } => SpatialKind::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::Axis::{NegZ, PosX, PosY};
const MIRROR: SourceFrame = SourceFrame::new([PosX, PosY, NegZ], 1.0);
#[test]
fn a_frame_is_stated_and_one_not_converted_says_why() {
let w = World::new();
assert_eq!(ProgramFrame::Source(MIRROR).frame(), Some(&MIRROR));
assert_eq!(ProgramFrame::Source(MIRROR).verify(&w), Ok(()));
let none = ProgramFrame::NotConverted { why: " ".into() };
assert_eq!(none.frame(), None);
assert!(none.verify(&w).unwrap_err().contains("why"));
let json = ProgramFrame::Source(MIRROR).to_json().unwrap().unwrap();
assert_eq!(json["source"]["axes"], json!(["PosX", "PosY", "NegZ"]));
}
#[test]
fn a_world_states_one_frame() {
let mut w = World::new();
ProgramFrame::Source(MIRROR).state(&mut w).unwrap();
ProgramFrame::Source(MIRROR).state(&mut w).unwrap();
assert_eq!(w.resource::<ProgramFrame>(), Some(&ProgramFrame::Source(MIRROR)));
let other = ProgramFrame::NotConverted {
why: "a world".into(),
};
assert!(other.state(&mut w).unwrap_err().contains("already stated"));
assert_eq!(w.resource::<ProgramFrame>(), Some(&ProgramFrame::Source(MIRROR)));
}
#[test]
fn each_role_states_how_it_moves() {
assert_eq!(Attribute::Position.spatial_kind(), SpatialKind::Point);
assert_eq!(Attribute::Point(3).spatial_kind(), SpatialKind::Point);
assert_eq!(Attribute::Normal.spatial_kind(), SpatialKind::Direction);
assert_eq!(Attribute::Tangent(0).spatial_kind(), SpatialKind::Tangent);
assert_eq!(Attribute::Color(3).spatial_kind(), SpatialKind::None);
let constant = |r: ConstantRole| r.spatial_kind();
assert_eq!(
constant(ConstantRole::View(ViewParameter::CameraPosition)),
SpatialKind::Point
);
assert_eq!(
constant(ConstantRole::View(ViewParameter::ViewProjection)),
SpatialKind::FromWorld
);
assert_eq!(
constant(ConstantRole::View(ViewParameter::InverseView)),
SpatialKind::ToWorld
);
assert_eq!(
constant(ConstantRole::View(ViewParameter::Projection)),
SpatialKind::None
);
assert_eq!(
constant(ConstantRole::Object(ObjectParameter::World)),
SpatialKind::WithinWorld
);
assert_eq!(
constant(ConstantRole::Object(ObjectParameter::CameraRelative(
ObjectMatrix::WorldViewProjection
))),
SpatialKind::FromWorld
);
assert_eq!(
constant(ConstantRole::Object(ObjectParameter::ClipPlane)),
SpatialKind::Plane
);
assert_eq!(
constant(ConstantRole::Object(ObjectParameter::CameraFadeInverseRange)),
SpatialKind::InverseLength
);
assert_eq!(
constant(ConstantRole::Object(ObjectParameter::CameraFadeEnd)),
SpatialKind::None
);
assert_eq!(
constant(ConstantRole::Frame(FrameParameter::DirectionToLight)),
SpatialKind::Direction
);
assert_eq!(
constant(ConstantRole::Material(vec![0, 0, 0x3f80_0000])),
SpatialKind::None
);
assert_eq!(
InstanceValue::Axis(2).spatial_kind(),
SpatialKind::WithinWorldColumn
);
assert_eq!(LightValue::ShadowCentre.spatial_kind(), SpatialKind::Point);
assert_eq!(LightValue::ViewPosition.spatial_kind(), SpatialKind::None);
assert_eq!(
FrameTexture::ReflectionProbe.spatial_kind(),
SpatialKind::CubeByDirection
);
}
}