use serde::{Deserialize, Serialize};
use super::animation::ImportedAnimation;
use super::mesh::ImportedMesh;
use crate::game_object::Transform;
use crate::material::LitMaterialDesc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneNode {
pub name: String,
pub transform: Transform,
pub mesh_index: Option<usize>,
pub material_index: Option<usize>,
pub children: Vec<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportedMaterial {
pub name: String,
pub desc: LitMaterialDesc,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportedScene {
pub name: String,
pub nodes: Vec<SceneNode>,
pub roots: Vec<usize>,
pub meshes: Vec<ImportedMesh>,
pub materials: Vec<ImportedMaterial>,
pub animations: Vec<ImportedAnimation>,
}
impl ImportedScene {
pub fn total_vertices(&self) -> usize {
self.meshes.iter().map(|m| m.vertex_count()).sum()
}
pub fn total_triangles(&self) -> usize {
self.meshes.iter().map(|m| m.triangle_count()).sum()
}
pub fn validate(&self) -> Result<(), String> {
let mesh_count = self.meshes.len();
let material_count = self.materials.len();
let node_count = self.nodes.len();
for (i, node) in self.nodes.iter().enumerate() {
if let Some(mi) = node.mesh_index {
if mi >= mesh_count {
return Err(format!(
"content_scene_mesh_oob:node[{i}] references mesh {mi}, only {mesh_count} meshes"
));
}
}
if let Some(mi) = node.material_index {
if mi >= material_count {
return Err(format!(
"content_scene_material_oob:node[{i}] references material {mi}, only {material_count} materials"
));
}
}
for &child in &node.children {
if child >= node_count {
return Err(format!(
"content_scene_child_oob:node[{i}] references child {child}, only {node_count} nodes"
));
}
}
}
for (i, mesh) in self.meshes.iter().enumerate() {
mesh.validate().map_err(|e| format!("mesh[{i}] '{}': {e}", mesh.name))?;
}
for anim in &self.animations {
anim.validate()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::content::mesh::ImportedMesh;
fn simple_scene() -> ImportedScene {
let mesh = ImportedMesh {
name: "Cube".into(),
positions: vec![[0.0; 3]; 3],
normals: vec![[0.0, 0.0, 1.0]; 3],
uvs: vec![[0.0; 2]; 3],
indices: vec![0, 1, 2],
};
let mat = ImportedMaterial {
name: "Default".into(),
desc: LitMaterialDesc::default(),
};
let node = SceneNode {
name: "Root".into(),
transform: Transform::default(),
mesh_index: Some(0),
material_index: Some(0),
children: vec![],
};
ImportedScene {
name: "TestScene".into(),
nodes: vec![node],
roots: vec![0],
meshes: vec![mesh],
materials: vec![mat],
animations: vec![],
}
}
#[test]
fn valid_scene() {
let scene = simple_scene();
assert!(scene.validate().is_ok());
assert_eq!(scene.total_vertices(), 3);
assert_eq!(scene.total_triangles(), 1);
}
#[test]
fn mesh_oob() {
let mut scene = simple_scene();
scene.nodes[0].mesh_index = Some(99);
assert!(scene.validate().unwrap_err().contains("mesh_oob"));
}
#[test]
fn material_oob() {
let mut scene = simple_scene();
scene.nodes[0].material_index = Some(99);
assert!(scene.validate().unwrap_err().contains("material_oob"));
}
#[test]
fn child_oob() {
let mut scene = simple_scene();
scene.nodes[0].children.push(99);
assert!(scene.validate().unwrap_err().contains("child_oob"));
}
#[test]
fn empty_scene() {
let scene = ImportedScene::default();
assert!(scene.validate().is_ok());
assert_eq!(scene.total_vertices(), 0);
}
#[test]
fn imported_material_default() {
let mat = ImportedMaterial::default();
assert!(mat.name.is_empty());
assert_eq!(mat.desc.roughness, 0.5);
}
}