mod geometry;
mod instance;
mod iter;
use std::{cmp, collections::HashMap, fmt, io, marker::PhantomData, ops, str, str::FromStr};
use indexmap::IndexMap;
use self::geometry::*;
use crate::{
utils::{
float, int,
xml::{self, XmlNodeExt},
},
Scene,
};
pub fn from_slice(bytes: &[u8]) -> io::Result<Scene> {
from_str(str::from_utf8(bytes).map_err(crate::error::invalid_data)?)
}
pub fn from_str(s: &str) -> io::Result<Scene> {
let xml = xml::Document::parse(s).map_err(crate::error::invalid_data)?;
let collada = Document::parse(&xml)?;
Ok(Scene {
meshes: instance::build_meshes(&collada),
})
}
pub(crate) trait Get<T> {
type Target;
fn get(&self, uri: &T) -> Option<&Self::Target>;
}
macro_rules! impl_get_by_uri {
($ty:ty, $($field:ident).*) => {
impl Get<Uri<$ty>> for Document {
type Target = $ty;
fn get(&self, index: &Uri<$ty>) -> Option<&Self::Target> {
self.$($field).*.get(&index.0)
}
}
};
}
impl_get_by_uri!(Accessor, library_geometries.accessors);
impl_get_by_uri!(ArrayData, library_geometries.array_data);
impl_get_by_uri!(Geometry, library_geometries.geometries);
#[derive(Debug)]
pub(crate) struct Uri<T>(String, PhantomData<fn() -> T>);
impl<T> Uri<T> {
pub(crate) fn parse(url: &str) -> io::Result<Self> {
if let Some(url) = url.strip_prefix('#') {
Ok(Self(url.to_owned(), PhantomData))
} else {
Err(format_err!("unknown reference format {:?}", url))
}
}
pub(crate) fn cast<U>(self) -> Uri<U> {
Uri(self.0, PhantomData)
}
#[allow(dead_code)] pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl<T> PartialEq for Uri<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Uri<T> {}
impl<T, S> PartialEq<S> for Uri<T>
where
S: ?Sized + AsRef<str>,
{
fn eq(&self, other: &S) -> bool {
self.0 == other.as_ref()
}
}
impl<T> PartialEq<Uri<T>> for str {
fn eq(&self, other: &Uri<T>) -> bool {
self == other.0
}
}
impl<T> PartialEq<Uri<T>> for String {
fn eq(&self, other: &Uri<T>) -> bool {
*self == other.0
}
}
pub(crate) trait ColladaXmlNodeExt<'a, 'input> {
fn parse_url<T>(&self, name: &str) -> io::Result<Uri<T>>;
fn parse_url_opt<T>(&self, name: &str) -> io::Result<Option<Uri<T>>>;
}
impl<'a, 'input> ColladaXmlNodeExt<'a, 'input> for xml::Node<'a, 'input> {
fn parse_url<T>(&self, name: &str) -> io::Result<Uri<T>> {
let url = self.required_attribute(name)?;
Uri::parse(url).map_err(|e| {
format_err!(
"{} in {} attribute of <{}> element at {}",
e,
name,
self.tag_name().name(),
self.attr_value_location(name),
)
})
}
fn parse_url_opt<T>(&self, name: &str) -> io::Result<Option<Uri<T>>> {
if let Some(url) = self.attribute(name) {
Uri::parse(url).map(Some).map_err(|e| {
format_err!(
"{} in {} attribute of <{}> element at {}",
e,
name,
self.tag_name().name(),
self.attr_value_location(name),
)
})
} else {
Ok(None)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Version {
pub(crate) minor: u32,
pub(crate) patch: u32,
}
impl Version {
pub(crate) const MIN: Self = Self { minor: 4, patch: 0 };
}
impl FromStr for Version {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(|| {
let mut digits = s.splitn(3, '.');
let major = digits.next()?;
if major != "1" {
return None;
}
let minor = digits.next()?.parse().ok()?;
let patch = digits.next()?.parse().ok()?;
Some(Self { minor, patch })
})()
.ok_or_else(|| format_err!("unrecognized version format {:?}", s))
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "1.{}.{}", self.minor, self.patch)
}
}
pub(crate) struct Context {
pub(crate) library_geometries: LibraryGeometries,
}
#[derive(Debug)]
pub(crate) struct Document {
pub(crate) library_geometries: LibraryGeometries,
}
impl Document {
pub(crate) fn parse(doc: &xml::Document<'_>) -> io::Result<Self> {
let node = doc.root_element();
if node.tag_name().name() != "COLLADA" {
bail!("root element is not <COLLADA>");
}
let version: Version = node.required_attribute("version")?.parse()?;
if version < Version::MIN {
bail!("collada schema version {} is not supported", version);
};
let mut cx = Context {
library_geometries: LibraryGeometries::default(),
};
for node in node.element_children() {
match node.tag_name().name() {
"library_geometries" => {
parse_library_geometries(&mut cx, node)?;
}
_name => {
}
}
}
Ok(Self {
library_geometries: cx.library_geometries,
})
}
pub(crate) fn get<T>(&self, url: &T) -> Option<&<Self as Get<T>>::Target>
where
Self: Get<T>,
{
<Self as Get<T>>::get(self, url)
}
}
impl<T> ops::Index<&T> for Document
where
Self: Get<T>,
{
type Output = <Self as Get<T>>::Target;
#[track_caller]
fn index(&self, url: &T) -> &Self::Output {
self.get(url).expect("no entry found for key")
}
}
#[derive(Debug)]
pub(crate) struct Source {
pub(crate) id: String,
#[allow(dead_code)]
pub(crate) name: Option<String>,
pub(crate) array_element: Option<ArrayElement>,
pub(crate) accessor: Option<Accessor>,
}
impl Source {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "source");
let id = node.required_attribute("id")?;
let mut array_element = None;
let mut accessor = None;
for child in node.element_children() {
match child.tag_name().name() {
"float_array" | "IDREF_array" | "Name_array" => {
array_element = Some(parse_array_element(child)?);
}
"technique_common" => {
for technique in child.element_children() {
match technique.tag_name().name() {
"accessor" => {
accessor = Some(Accessor::parse(technique)?);
}
_ => return Err(error::unexpected_child_elem(technique)),
}
}
}
"bool_array" | "int_array" | "SIDREF_array" | "token_array" => {
}
"asset" | "technique" => { }
_ => return Err(error::unexpected_child_elem(child)),
}
}
Ok(Self {
id: id.into(),
name: node.attribute("name").map(Into::into),
array_element,
accessor,
})
}
}
#[derive(Debug)]
pub(crate) struct ArrayElement {
pub(crate) id: String,
#[allow(dead_code)]
pub(crate) count: u32,
pub(crate) data: ArrayData,
}
fn parse_array_element(node: xml::Node<'_, '_>) -> io::Result<ArrayElement> {
let name = node.tag_name().name();
let is_string_array = name == "IDREF_array" || name == "Name_array";
let id = node.required_attribute("id")?;
let count = node.parse_required_attribute("count")?;
let mut content = node.text().unwrap_or_default().trim();
if content.is_empty() {
let data = if is_string_array {
ArrayData::String(vec![])
} else {
ArrayData::Float(vec![])
};
return Ok(ArrayElement {
id: id.into(),
count,
data,
});
}
if is_string_array {
let mut values = Vec::with_capacity(count as _);
for _ in 0..count {
if content.is_empty() {
bail!(
"expected more values while reading <{}> contents at {}",
node.tag_name().name(),
node.node_location()
);
}
let mut n = 0;
while content
.as_bytes()
.first()
.map_or(false, |b| !b.is_ascii_whitespace())
{
n += 1;
}
values.push(content[..n].into());
content = content.get(n..).unwrap_or_default().trim_start();
}
Ok(ArrayElement {
id: id.into(),
count,
data: ArrayData::String(values),
})
} else {
let mut values = Vec::with_capacity(count as _);
let content = content.replace(',', ".");
for res in float::parse_array_exact(&content, count as _) {
let value = res.map_err(|e| {
format_err!(
"{} in <{}> element ({})",
e,
node.tag_name().name(),
node.node_location(),
)
})?;
values.push(value);
}
Ok(ArrayElement {
id: id.into(),
count,
data: ArrayData::Float(values),
})
}
}
#[derive(Debug)]
pub(crate) enum ArrayData {
Float(Vec<f32>),
String(Vec<String>),
}
#[allow(dead_code)] impl ArrayData {
pub(crate) fn is_float(&self) -> bool {
matches!(self, Self::Float(..))
}
pub(crate) fn is_string(&self) -> bool {
matches!(self, Self::String(..))
}
pub(crate) fn as_float(&self) -> Option<&[f32]> {
match self {
Self::Float(v) => Some(v),
Self::String(..) => None,
}
}
pub(crate) fn as_string(&self) -> Option<&[String]> {
match self {
Self::Float(..) => None,
Self::String(v) => Some(v),
}
}
pub(crate) fn len(&self) -> usize {
match self {
Self::Float(v) => v.len(),
Self::String(v) => v.len(),
}
}
pub(crate) fn is_empty(&self) -> bool {
match self {
Self::Float(v) => v.is_empty(),
Self::String(v) => v.is_empty(),
}
}
}
#[derive(Debug)]
pub(crate) struct Accessor {
pub(crate) count: u32,
#[allow(dead_code)] pub(crate) offset: u32,
pub(crate) source: Uri<ArrayData>,
pub(crate) stride: u32,
#[allow(dead_code)] pub(crate) params: Vec<Param>,
}
impl Accessor {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "accessor");
let count = node.parse_required_attribute("count")?;
let source = node.parse_url("source")?;
let offset = node.parse_attribute("offset")?.unwrap_or(0);
let stride = node.parse_attribute("stride")?.unwrap_or(1);
let mut params = vec![];
for child in node.element_children() {
match child.tag_name().name() {
"param" => {
params.push(Param::parse(child)?);
}
_ => return Err(error::unexpected_child_elem(child)),
}
}
Ok(Self {
count,
offset,
source,
stride,
params,
})
}
}
#[allow(dead_code)] #[derive(Debug)]
pub(crate) struct Param {
pub(crate) name: Option<String>,
pub(crate) sid: Option<String>,
pub(crate) ty: String,
pub(crate) semantic: Option<String>,
}
impl Param {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
let ty = node.required_attribute("type")?;
let name = node.attribute("name");
let sid = node.attribute("sid");
let semantic = node.attribute("semantic");
Ok(Self {
name: name.map(Into::into),
sid: sid.map(Into::into),
ty: ty.into(),
semantic: semantic.map(Into::into),
})
}
}
#[derive(Debug)]
pub(crate) struct SharedInput<T = Accessor> {
pub(crate) offset: u32,
pub(crate) semantic: InputSemantic,
pub(crate) source: Uri<T>,
pub(crate) set: u32,
}
impl<T> SharedInput<T> {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "input");
let semantic = node.parse_required_attribute("semantic")?;
let source = node.parse_url("source")?;
let offset = node.parse_required_attribute("offset")?;
let set = node.parse_attribute("set")?.unwrap_or(0);
Ok(Self {
offset,
semantic,
source,
set,
})
}
pub(crate) fn cast<U>(self) -> SharedInput<U> {
SharedInput {
offset: self.offset,
semantic: self.semantic,
source: self.source.cast(),
set: self.set,
}
}
}
#[derive(Debug)]
pub(crate) struct UnsharedInput {
pub(crate) semantic: InputSemantic,
pub(crate) source: Uri<Accessor>,
}
impl UnsharedInput {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "input");
let semantic = node.parse_required_attribute("semantic")?;
let source = node.parse_url("source")?;
Ok(Self { semantic, source })
}
}
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum InputSemantic {
BINORMAL,
COLOR,
CONTINUITY,
IMAGE,
INPUT,
IN_TANGENT,
INTERPOLATION,
INV_BIND_MATRIX,
JOINT,
LINEAR_STEPS,
MORPH_TARGET,
MORPH_WEIGHT,
NORMAL,
OUTPUT,
OUT_TANGENT,
POSITION,
TANGENT,
TEXBINORMAL,
TEXCOORD,
TEXTANGENT,
UV,
VERTEX,
WEIGHT,
}
impl FromStr for InputSemantic {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"BINORMAL" => Self::BINORMAL,
"COLOR" => Self::COLOR,
"CONTINUITY" => Self::CONTINUITY,
"IMAGE" => Self::IMAGE,
"INPUT" => Self::INPUT,
"IN_TANGENT" => Self::IN_TANGENT,
"INTERPOLATION" => Self::INTERPOLATION,
"INV_BIND_MATRIX" => Self::INV_BIND_MATRIX,
"JOINT" => Self::JOINT,
"LINEAR_STEPS" => Self::LINEAR_STEPS,
"MORPH_TARGET" => Self::MORPH_TARGET,
"MORPH_WEIGHT" => Self::MORPH_WEIGHT,
"NORMAL" => Self::NORMAL,
"OUTPUT" => Self::OUTPUT,
"OUT_TANGENT" => Self::OUT_TANGENT,
"POSITION" => Self::POSITION,
"TANGENT" => Self::TANGENT,
"TEXBINORMAL" => Self::TEXBINORMAL,
"TEXCOORD" => Self::TEXCOORD,
"TEXTANGENT" => Self::TEXTANGENT,
"UV" => Self::UV,
"VERTEX" => Self::VERTEX,
"WEIGHT" => Self::WEIGHT,
_ => bail!("unknown input semantic {:?}", s),
})
}
}
mod error {
use super::*;
#[cold]
pub(crate) fn one_or_more_elems(node: xml::Node<'_, '_>, name: &str) -> io::Error {
format_err!(
"<{}> element must be contain one or more <{}> elements ({})",
node.tag_name().name(),
name,
node.node_location()
)
}
#[cold]
pub(crate) fn exactly_one_elem(node: xml::Node<'_, '_>, name: &str) -> io::Error {
format_err!(
"<{}> element must be contain exactly one <{}> element ({})",
node.tag_name().name(),
name,
node.node_location()
)
}
#[cold]
pub(crate) fn multiple_elems(node: xml::Node<'_, '_>) -> io::Error {
format_err!(
"multiple <{}> elements ({})",
node.tag_name().name(),
node.node_location()
)
}
#[cold]
pub(crate) fn unexpected_child_elem(child: xml::Node<'_, '_>) -> io::Error {
format_err!(
"unexpected child element <{}> in <{}> element ({})",
child.tag_name().name(),
child.parent_element().unwrap().tag_name().name(),
child.node_location()
)
}
}
mod warn {
use super::*;
#[cold]
pub(crate) fn unsupported_child_elem(_child: xml::Node<'_, '_>) {
}
}