use std::collections::{HashMap, HashSet};
use crate::{
Error, Result,
core::transform::Transform,
vlm::{Mat4, Vec2, Vec3, Vec4},
};
use super::{
material::{Material, MaterialId},
mesh::Aabb,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SceneMeshId(pub u64);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SceneNodeId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MeshVertex {
pub position: Vec3,
pub normal: Vec3,
pub uv: Vec2,
pub color: Vec4,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MeshData {
pub vertices: Vec<MeshVertex>,
pub indices: Vec<u32>,
pub bounds: Aabb,
pub bounds_dirty: bool,
}
impl Default for MeshData {
fn default() -> Self {
Self {
vertices: Vec::new(),
indices: Vec::new(),
bounds: Aabb::default(),
bounds_dirty: true,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SceneMesh {
pub id: SceneMeshId,
pub name: String,
pub data: MeshData,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SceneMaterial {
pub id: MaterialId,
pub name: String,
pub data: Material,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SceneNode {
pub id: SceneNodeId,
pub name: String,
pub parent: SceneNodeId,
pub mesh: SceneMeshId,
pub material: Option<MaterialId>,
pub local_transform: Transform,
pub visible: bool,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Scene {
pub meshes: Vec<SceneMesh>,
pub materials: Vec<SceneMaterial>,
pub nodes: Vec<SceneNode>,
}
pub fn validate_scene(scene: &Scene) -> Result<()> {
let mut meshes = HashSet::new();
for mesh in &scene.meshes {
if mesh.id.0 == 0 || !meshes.insert(mesh.id) {
return Err(Error::invalid_argument(
"render::Scene mesh IDs must be non-zero and unique",
));
}
validate_mesh(&mesh.data)?;
}
let mut materials = HashSet::new();
for mat in &scene.materials {
if mat.id.0 == 0 || !materials.insert(mat.id) {
return Err(Error::invalid_argument(
"render::Scene material IDs must be non-zero and unique",
));
}
mat.data.validate().map_err(|e| {
Error::invalid_argument(format!("render::Scene material '{}': {}", mat.name, e))
})?;
}
let mut nodes = HashSet::new();
for node in &scene.nodes {
if node.id.0 == 0 || !nodes.insert(node.id) || !node.local_transform.is_finite() {
return Err(Error::invalid_argument(
"render::Scene nodes require unique non-zero IDs and finite transforms",
));
}
if node.mesh.0 != 0 && !meshes.contains(&node.mesh) {
return Err(Error::invalid_argument(
"render::Scene node references a missing mesh",
));
}
if node
.material
.is_some_and(|mat_id| !materials.contains(&mat_id))
{
return Err(Error::invalid_argument(
"render::Scene node references a missing material",
));
}
}
for node in &scene.nodes {
if node.parent.0 != 0 && (!nodes.contains(&node.parent) || node.parent == node.id) {
return Err(Error::invalid_argument(
"render::Scene node parent is missing or self-referential",
));
}
let mut seen = HashSet::new();
let mut parent = node.parent;
while parent.0 != 0 {
if !seen.insert(parent) {
return Err(Error::invalid_argument(
"render::Scene hierarchy contains a cycle",
));
}
parent = scene
.nodes
.iter()
.find(|candidate| candidate.id == parent)
.expect("validated parent")
.parent;
}
}
Ok(())
}
pub fn compile_scene(scene: &Scene) -> Result<MeshData> {
let (mesh, _) = compile_scene_packets(scene)?;
Ok(mesh)
}
#[derive(Clone, Debug, PartialEq)]
pub struct SceneDrawPacket {
pub index_start: u32,
pub index_count: u32,
pub material_id: Option<MaterialId>,
}
pub fn compile_scene_packets(scene: &Scene) -> Result<(MeshData, Vec<SceneDrawPacket>)> {
validate_scene(scene)?;
let node_by_id: HashMap<_, _> = scene.nodes.iter().map(|node| (node.id, node)).collect();
let mesh_by_id: HashMap<_, _> = scene.meshes.iter().map(|mesh| (mesh.id, mesh)).collect();
let mut output = MeshData::default();
let mut packets: Vec<SceneDrawPacket> = Vec::new();
for node in &scene.nodes {
if !is_visible(node, &node_by_id) || node.mesh.0 == 0 {
continue;
}
let world = world_transform(node, &node_by_id);
let normal_matrix = world
.try_normal_matrix(crate::vlm::INVERSE_TOLERANCE)
.ok_or_else(|| Error::invalid_argument("render::Scene world transform is singular"))?;
let base = u32::try_from(output.vertices.len())
.map_err(|_| Error::out_of_range("render::Scene vertex count exceeds u32"))?;
let index_start = u32::try_from(output.indices.len())
.map_err(|_| Error::out_of_range("render::Scene index count exceeds u32"))?;
let mesh = mesh_by_id[&node.mesh];
output
.vertices
.extend(mesh.data.vertices.iter().map(|vertex| MeshVertex {
position: world.transform_point(vertex.position),
normal: vertex.normal * normal_matrix,
..*vertex
}));
let new_indices: Vec<u32> = mesh
.data
.indices
.iter()
.map(|index| {
base
.checked_add(*index)
.ok_or_else(|| Error::out_of_range("render::Scene index overflows u32"))
})
.collect::<Result<Vec<_>>>()?;
let index_count = u32::try_from(new_indices.len())
.map_err(|_| Error::out_of_range("render::Scene index count exceeds u32"))?;
output.indices.extend(new_indices);
packets.push(SceneDrawPacket {
index_start,
index_count,
material_id: node.material,
});
}
Ok((output, packets))
}
fn validate_mesh(mesh: &MeshData) -> Result<()> {
if !mesh.indices.len().is_multiple_of(3)
|| mesh
.indices
.iter()
.any(|index| *index as usize >= mesh.vertices.len())
|| mesh.vertices.iter().any(|v| {
!v.position.is_finite() || !v.normal.is_finite() || !v.uv.is_finite() || !v.color.is_finite()
}) {
return Err(Error::invalid_argument(
"render::MeshData requires finite vertices and in-range triangle indices",
));
}
Ok(())
}
fn world_transform(node: &SceneNode, nodes: &HashMap<SceneNodeId, &SceneNode>) -> Mat4 {
let mut result = node.local_transform.get_matrix();
let mut parent = node.parent;
while parent.0 != 0 {
let value = nodes[&parent];
result *= value.local_transform.get_matrix();
parent = value.parent;
}
result
}
fn is_visible(node: &SceneNode, nodes: &HashMap<SceneNodeId, &SceneNode>) -> bool {
if !node.visible {
return false;
}
let mut parent = node.parent;
while parent.0 != 0 {
let value = nodes[&parent];
if !value.visible {
return false;
}
parent = value.parent;
}
true
}