use std::collections::HashMap;
use std::io::{Seek, Read};
use glam::{Affine3A, Vec3A, Vec4};
use smallvec::SmallVec;
use thiserror::Error;
use crate::pxml::{self, Value, Element};
pub fn from_reader<R: Read + Seek>(reader: R) -> Result<Box<Visual>, DeError> {
let root_elt = pxml::from_reader(reader)?;
let root_node_value = root_elt.get_child("node").ok_or(DeError::MissingRootNode)?;
let root_node_elt = root_node_value.as_element().ok_or(DeError::MissingRootNode)?;
let root_node = read_node(root_node_elt).ok_or(DeError::InvalidNode)?;
let bb_elt = root_elt
.get_child("boundingBox").ok_or(DeError::MissingBoundingBox)?
.as_element().ok_or(DeError::MissingBoundingBox)?;
let bb_min = bb_elt
.get_child("min").ok_or(DeError::MissingBoundingBox)?
.as_vec3().ok_or(DeError::MissingBoundingBox)?;
let bb_max = bb_elt
.get_child("max").ok_or(DeError::MissingBoundingBox)?
.as_vec3().ok_or(DeError::MissingBoundingBox)?;
let geometry_size = root_elt
.get_child("geometrySize").ok_or(DeError::MissingGeometrySize)?
.as_integer().ok_or(DeError::MissingGeometrySize)? as u32;
let min_uv_density = root_elt
.get_child("minUVDensity").ok_or(DeError::MissingUvDensity)?
.as_float().ok_or(DeError::MissingUvDensity)?;
let mut render_sets = SmallVec::new();
for child in root_elt.iter_children("renderSet") {
if let Value::Element(child_elt) = child {
render_sets.push(read_render_set(&**&child_elt).ok_or(DeError::InvalidRenderSet)?);
}
}
Ok(Box::new(Visual {
root_node,
render_sets,
bb_min,
bb_max,
geometry_size,
min_uv_density,
}))
}
fn read_node(element: &Element) -> Option<Node> {
let identifier = element.get_child("identifier")?.as_string()?;
let transform = element.get_child("transform")?.as_affine3()?;
let mut children = Vec::new();
for child in element.iter_children("node") {
if let Value::Element(child_elt) = child {
children.push(read_node(&**child_elt)?);
}
}
Some(Node {
identifier: identifier.clone(),
transform,
children,
})
}
fn read_render_set(element: &Element) -> Option<RenderSet> {
let node = element.get_child("node")?.as_string()?;
let treat_as_world_space_object = element.get_child("treatAsWorldSpaceObject")?.as_boolean()?;
let geometry_elt = element.get_child("geometry")?.as_element()?;
let geometry_vertices = geometry_elt.get_child("vertices")?.as_string()?;
let geometry_indices = geometry_elt.get_child("primitive")?.as_string()?;
let mut primitive_groups = SmallVec::new();
for group_val in geometry_elt.iter_children("primitiveGroup") {
if let Value::Element(group_elt) = group_val {
let group_index = group_elt.value.as_integer()? as u32;
let group_origin = group_elt.get_child("groupOrigin")?.as_vec3()?;
let mat_elt = group_elt.get_child("material")?.as_element()?;
let mat_identifier = mat_elt.get_child("identifier")?.as_string()?;
let mat_collision_flags = mat_elt.get_child("collisionFlags")?.as_integer()? as u32;
let mat_kind = mat_elt.get_child("materialKind")?.as_integer()? as u32;
let mat_fx = mat_elt.get_child("fx")?.as_string()?;
let mut mat_properties = HashMap::new();
for prop_val in mat_elt.iter_children("property") {
if let Value::Element(prop_elt) = prop_val {
let prop_name = prop_elt.value.as_string()?;
let prop_value = if let Some(val) = prop_elt.get_child("Texture") {
MaterialProperty::Texture(val.as_string()?.clone())
} else if let Some(val) = prop_elt.get_child("Bool") {
MaterialProperty::Boolean(val.as_boolean()?)
} else if let Some(val) = prop_elt.get_child("Int") {
MaterialProperty::Integer(val.as_integer()?)
} else if let Some(val) = prop_elt.get_child("Float") {
let n = match val {
&Value::Integer(n) => n as f32,
&Value::Float(n) => n,
Value::String(s) => s.parse().ok()?,
_ => return None
};
MaterialProperty::Float(n)
} else if let Some(val) = prop_elt.get_child("Vector4") {
let mut raw = val.as_string()?
.splitn(4, ' ')
.filter_map(|s| s.parse::<f32>().ok());
let x = raw.next()?;
let y = raw.next()?;
let z = raw.next()?;
let w = raw.next()?;
MaterialProperty::Vec4(Vec4::new(x, y, z, w))
} else {
return None;
};
mat_properties.insert(prop_name.clone(), prop_value);
}
}
primitive_groups.push(PrimitiveGroup {
index: group_index,
origin: group_origin,
material: Material {
identifier: mat_identifier.clone(),
properties: mat_properties,
collision_flags: mat_collision_flags,
material_kind: mat_kind,
fx: mat_fx.clone(),
},
})
}
}
Some(RenderSet {
node: node.clone(),
geometry: Geometry {
vertices_section: geometry_vertices.clone(),
indices_section: geometry_indices.clone(),
primitive_groups,
},
treat_as_world_space_object,
})
}
#[derive(Debug)]
pub struct Visual {
pub root_node: Node,
pub render_sets: SmallVec<[RenderSet; 4]>,
pub bb_min: Vec3A,
pub bb_max: Vec3A,
pub geometry_size: u32,
pub min_uv_density: f32,
}
#[derive(Debug)]
pub struct Node {
pub identifier: String,
pub transform: Affine3A,
pub children: Vec<Node>,
}
#[derive(Debug)]
pub struct RenderSet {
pub node: String,
pub geometry: Geometry,
pub treat_as_world_space_object: bool,
}
#[derive(Debug)]
pub struct Geometry {
pub vertices_section: String,
pub indices_section: String,
pub primitive_groups: SmallVec<[PrimitiveGroup; 1]>,
}
#[derive(Debug)]
pub struct PrimitiveGroup {
pub index: u32,
pub origin: Vec3A,
pub material: Material,
}
#[derive(Debug)]
pub struct Material {
pub identifier: String,
pub properties: HashMap<String, MaterialProperty>,
pub collision_flags: u32,
pub material_kind: u32,
pub fx: String,
}
#[derive(Debug)]
pub enum MaterialProperty {
Texture(String),
Boolean(bool),
Integer(i64),
Float(f32),
Vec4(Vec4),
}
#[derive(Debug, Error)]
pub enum DeError {
#[error("the root node is missing")]
MissingRootNode,
#[error("a node is missing either identifier or transform")]
InvalidNode,
#[error("values are missing in a render set")]
InvalidRenderSet,
#[error("the bounding box is missing or invalid")]
MissingBoundingBox,
#[error("the geometry size is missing or invalid")]
MissingGeometrySize,
#[error("the uv density is missing or invalid")]
MissingUvDensity,
#[error("pxml error: {0}")]
Pxml(#[from] pxml::DeError),
}