use std::path::{Path, PathBuf};
use roxmltree::Node;
use crate::Pose;
use crate::assets::{Hfield, Material, Mesh, MeshInertia, Texture};
use crate::error::ParseError;
use crate::model::Model;
use glamx::glam::{DQuat, DVec3};
use super::parse_utils::{
absolutize_model_assets, asset_default_name, parse_f64, parse_f64_list, parse_quat, parse_u32,
parse_vec3, parse_vec4,
};
use super::state::ParseState;
impl ParseState {
pub(super) fn parse_asset(&mut self, root: Node) -> Result<(), ParseError> {
for child in root.children().filter(|n| n.is_element()) {
match child.tag_name().name() {
"mesh" => {
let class = child.attribute("class").map(|s| s.to_string());
let proto = self.merged_mesh_proto(class.as_deref());
let mut m = Mesh {
class,
scale: proto.scale.unwrap_or([1.0, 1.0, 1.0]),
inertia: proto.inertia.unwrap_or_default(),
refpose: proto.refpose.unwrap_or_default(),
max_hull_vert: proto.max_hull_vert,
smoothnormal: proto.smoothnormal.unwrap_or(0.0),
..Default::default()
};
let mut pose_pos = None;
let mut pose_rot = None;
for attr in child.attributes() {
match attr.name() {
"name" => m.name = Some(attr.value().to_string()),
"file" => m.file = Some(attr.value().to_string()),
"scale" => m.scale = parse_vec3(attr.value(), "mesh", "scale")?,
"refpos" => {
pose_pos = Some(parse_vec3(attr.value(), "mesh", "refpos")?)
}
"refquat" => {
pose_rot = Some(parse_quat(attr.value(), "mesh", "refquat")?)
}
"vertex" => m.inline_vertices = Some(parse_f64_list(attr.value())?),
"normal" => m.inline_normals = Some(parse_f64_list(attr.value())?),
"face" => {
let v = parse_f64_list(attr.value())?;
m.inline_faces = Some(v.iter().map(|x| *x as u32).collect());
}
"maxhullvert" => m.max_hull_vert = Some(parse_u32(attr.value())?),
"smoothnormal" => m.smoothnormal = parse_f64(attr.value())?,
"inertia" => {
m.inertia = match attr.value() {
"shell" => MeshInertia::Shell,
"convex" => MeshInertia::Convex,
"exact" => MeshInertia::Exact,
"legacy" => MeshInertia::Legacy,
v => {
return Err(ParseError::BadAttributeValue {
tag: "mesh".to_string(),
attr: "inertia".to_string(),
value: v.to_string(),
message: "expected shell/convex/exact/legacy"
.to_string(),
});
}
}
}
_ => {}
}
}
if let (Some(p), q) = (pose_pos, pose_rot) {
m.refpose =
Pose::from_parts(DVec3::from_array(p), q.unwrap_or(DQuat::IDENTITY));
} else if let Some(q) = pose_rot {
m.refpose.rotation = q;
}
if m.name.is_none() {
m.name = asset_default_name(m.file.as_deref());
}
self.model.assets.meshes.push(m);
}
"hfield" => {
let mut h = Hfield::default();
for attr in child.attributes() {
match attr.name() {
"name" => h.name = Some(attr.value().to_string()),
"class" => h.class = Some(attr.value().to_string()),
"nrow" => h.nrow = parse_u32(attr.value())?,
"ncol" => h.ncol = parse_u32(attr.value())?,
"size" => h.size = parse_vec4(attr.value(), "hfield", "size")?,
"file" => h.file = Some(attr.value().to_string()),
"elevation" => h.elevation = Some(parse_f64_list(attr.value())?),
_ => {}
}
}
if h.name.is_none() {
h.name = asset_default_name(h.file.as_deref());
}
self.model.assets.hfields.push(h);
}
"texture" => {
let mut t = Texture::default();
for attr in child.attributes() {
match attr.name() {
"name" => t.name = Some(attr.value().to_string()),
"class" => t.class = Some(attr.value().to_string()),
"type" => t.type_ = Some(attr.value().to_string()),
"file" => t.file = Some(attr.value().to_string()),
"builtin" => t.builtin = Some(attr.value().to_string()),
"rgb1" => t.rgb1 = Some(parse_vec3(attr.value(), "texture", "rgb1")?),
"rgb2" => t.rgb2 = Some(parse_vec3(attr.value(), "texture", "rgb2")?),
_ => {}
}
}
if t.name.is_none() {
t.name = asset_default_name(t.file.as_deref());
}
self.model.assets.textures.push(t);
}
"material" => {
let mut m = Material::default();
for attr in child.attributes() {
match attr.name() {
"name" => m.name = Some(attr.value().to_string()),
"class" => m.class = Some(attr.value().to_string()),
"texture" => m.texture = Some(attr.value().to_string()),
"rgba" => m.rgba = Some(parse_vec4(attr.value(), "material", "rgba")?),
"emission" => m.emission = parse_f64(attr.value())?,
"specular" => m.specular = parse_f64(attr.value())?,
"shininess" => m.shininess = parse_f64(attr.value())?,
"roughness" => m.roughness = parse_f64(attr.value())?,
"metallic" => m.metallic = parse_f64(attr.value())?,
_ => {}
}
}
self.model.assets.materials.push(m);
}
"include" => {
self.parse_include(child, true)?;
}
"model" => {
self.parse_asset_model(child)?;
}
_ => {}
}
}
Ok(())
}
pub(super) fn parse_asset_model(&mut self, node: Node) -> Result<(), ParseError> {
let Some(name) = node.attribute("name") else {
log::warn!("<asset><model>: missing `name` attribute; skipping");
return Ok(());
};
let Some(file) = node.attribute("file") else {
log::warn!("<asset><model name={name:?}>: missing `file` attribute; skipping");
return Ok(());
};
let full_path = self.base_dir.join(file);
let mut sub = match Model::from_file(&full_path) {
Ok(m) => m,
Err(e) => {
log::warn!(
"<asset><model name={name:?} file={file:?}>: failed to load \
sub-model: {e}"
);
return Ok(());
}
};
let sub_base_dir = full_path
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
absolutize_model_assets(&mut sub, &sub_base_dir);
self.sub_models.insert(name.to_string(), sub);
Ok(())
}
}