#![allow(clippy::wildcard_imports)] #![allow(clippy::many_single_char_names)]
mod effect;
mod error;
mod geometry;
mod image;
mod instance;
mod iter;
mod material;
mod scene;
use std::{
cmp,
collections::{BTreeMap, HashMap},
fmt, io,
marker::PhantomData,
ops,
path::Path,
str::{self, FromStr},
};
use self::{effect::*, geometry::*, image::*, material::*, scene::*};
use crate::{
common,
utils::{
float, hex,
utf16::decode_string,
xml::{self, XmlNodeExt},
},
Color4,
};
#[inline]
pub fn from_slice(bytes: &[u8]) -> io::Result<common::Scene> {
from_slice_internal(bytes, None)
}
#[inline]
pub fn from_str(s: &str) -> io::Result<common::Scene> {
from_str_internal(s, None)
}
#[inline]
pub(crate) fn from_slice_internal(bytes: &[u8], path: Option<&Path>) -> io::Result<common::Scene> {
let bytes = &decode_string(bytes)?;
from_str_internal(bytes, path)
}
#[inline]
pub(crate) fn from_str_internal(s: &str, path: Option<&Path>) -> io::Result<common::Scene> {
let xml = xml::Document::parse(s).map_err(crate::error::invalid_data)?;
let mut collada = Document::parse(&xml)?;
Ok(instance::build(&mut collada, path.and_then(Path::parent)))
}
trait Get<T> {
type Target;
fn get(&self, uri: &T) -> Option<&Self::Target>;
}
macro_rules! impl_get_by_uri {
($ty:ty, $($field:ident).*) => {
impl<'a> Get<Uri<'a, $ty>> for Document<'a> {
type Target = $ty;
fn get(&self, index: &Uri<'a, $ty>) -> Option<&Self::Target> {
self.$($field).*.get(&*index.0)
}
}
};
}
impl_get_by_uri!(Accessor<'a>, library_geometries.accessors);
impl_get_by_uri!(ArrayData<'a>, library_geometries.array_data);
impl_get_by_uri!(Effect<'a>, library_effects.effects);
impl_get_by_uri!(Geometry<'a>, library_geometries.geometries);
impl_get_by_uri!(Image<'a>, library_images.images);
impl_get_by_uri!(Material<'a>, library_materials.materials);
struct Uri<'a, T>(&'a str, PhantomData<fn() -> T>);
impl<'a, T> Uri<'a, T> {
fn parse(url: &'a str) -> io::Result<Self> {
if let Some(id) = url.strip_prefix('#') {
Ok(Self(id, PhantomData))
} else {
Err(format_err!("unknown reference format {:?}", url))
}
}
fn from_id(id: &'a str) -> Self {
Self(id, PhantomData)
}
fn cast<U>(self) -> Uri<'a, U> {
Uri(self.0, PhantomData)
}
fn as_str(&self) -> &'a 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 {
#[inline]
fn eq(&self, other: &Uri<'_, T>) -> bool {
self == other.0
}
}
impl<T> PartialEq<Uri<'_, T>> for &str {
#[inline]
fn eq(&self, other: &Uri<'_, T>) -> bool {
*self == other.0
}
}
trait ColladaXmlNodeExt<'a, 'input> {
fn parse_url<T>(&self, name: &str) -> io::Result<Uri<'a, T>>;
}
impl<'a, 'input> ColladaXmlNodeExt<'a, 'input> for xml::Node<'a, 'input> {
fn parse_url<T>(&self, name: &str) -> io::Result<Uri<'a, 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),
)
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Version {
minor: u32,
patch: u32,
}
impl Version {
const MIN: Self = Self::new(4, 0);
const fn new(minor: u32, patch: u32) -> Self {
Self { minor, patch }
}
fn is_1_4(self) -> bool {
self >= Self::new(4, 0) && self < Self::new(5, 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::new(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)
}
}
struct Context<'a> {
version: Version,
asset: Asset,
library_effects: LibraryEffects<'a>,
library_geometries: LibraryGeometries<'a>,
library_images: LibraryImages<'a>,
library_materials: LibraryMaterials<'a>,
library_visual_scenes: LibraryVisualScenes<'a>,
scene: Scene<'a>,
}
struct Document<'a> {
asset: Asset,
library_effects: LibraryEffects<'a>,
library_geometries: LibraryGeometries<'a>,
library_images: LibraryImages<'a>,
library_materials: LibraryMaterials<'a>,
library_visual_scenes: LibraryVisualScenes<'a>,
scene: Scene<'a>,
}
impl<'a> Document<'a> {
fn parse(doc: &'a 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 {
version,
asset: Asset {
unit: DEFAULT_UNIT_SIZE,
},
library_effects: LibraryEffects::default(),
library_geometries: LibraryGeometries::default(),
library_images: LibraryImages::default(),
library_materials: LibraryMaterials::default(),
library_visual_scenes: LibraryVisualScenes::default(),
scene: Scene::default(),
};
for node in node.element_children() {
match node.tag_name().name() {
"library_effects" => {
parse_library_effects(&mut cx, node)?;
}
"library_geometries" => {
parse_library_geometries(&mut cx, node)?;
}
"library_images" => {
parse_library_images(&mut cx, node)?;
}
"library_materials" => {
parse_library_materials(&mut cx, node)?;
}
"library_visual_scenes" => {
parse_library_visual_scenes(&mut cx, node)?;
}
"asset" => {
cx.asset = Asset::parse(node)?;
}
"scene" => {
cx.scene = parse_scene(&mut cx, node)?;
}
_name => {
}
}
}
Ok(Self {
asset: cx.asset,
library_effects: cx.library_effects,
library_geometries: cx.library_geometries,
library_images: cx.library_images,
library_materials: cx.library_materials,
library_visual_scenes: cx.library_visual_scenes,
scene: cx.scene,
})
}
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")
}
}
const DEFAULT_UNIT_SIZE: f32 = 1.;
struct Asset {
unit: f32,
}
impl Asset {
fn parse(node: xml::Node<'_, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "asset");
let mut unit = None;
for child in node.element_children() {
match child.tag_name().name() {
"unit" => {
if let Some(v) = child.attribute("meter") {
let v = xml::comma_to_period(v);
unit = Some(v.parse().map_err(|e| {
format_err!(
"{} in <{}> element at {}: {:?}",
e,
child.tag_name().name(),
child.attr_value_location("meter"),
v
)
})?);
}
}
"up_axis" => {} _ => { }
}
}
Ok(Self {
unit: unit.unwrap_or(DEFAULT_UNIT_SIZE),
})
}
}
struct Source<'a> {
id: &'a str,
array_element: Option<ArrayElement<'a>>,
accessor: Option<Accessor<'a>>,
}
impl<'a> Source<'a> {
fn parse(node: xml::Node<'a, '_>) -> 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,
array_element,
accessor,
})
}
}
struct ArrayElement<'a> {
id: &'a str,
data: ArrayData<'a>,
}
fn parse_array_element<'a>(node: xml::Node<'a, '_>) -> io::Result<ArrayElement<'a>> {
let name = node.tag_name().name();
let is_string_array = name == "IDREF_array" || name == "Name_array";
let id = node.required_attribute("id")?;
let count: u32 = node.parse_required_attribute("count")?;
let mut content = node.trimmed_text();
if content.is_empty() {
let data = if is_string_array {
ArrayData::String(vec![])
} else {
ArrayData::Float(vec![])
};
return Ok(ArrayElement {
id,
data,
});
}
if is_string_array {
let mut values = Vec::with_capacity(count as usize);
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| !xml::is_whitespace(b as char))
{
n += 1;
}
values.push(&content[..n]);
content = xml::trim_start(content.get(n..).unwrap_or_default());
}
Ok(ArrayElement {
id,
data: ArrayData::String(values),
})
} else {
let mut values = Vec::with_capacity(count as usize);
let content = xml::comma_to_period(content);
let map_err = |e| {
format_err!(
"{e} in <{}> element ({})",
node.tag_name().name(),
node.text_location(),
)
};
for res in xml::parse_float_array_exact(&content, count as usize) {
let value = res.map_err(map_err)?;
values.push(value);
}
Ok(ArrayElement {
id,
data: ArrayData::Float(values),
})
}
}
enum ArrayData<'a> {
Float(Vec<f32>),
String(
#[allow(dead_code)] Vec<&'a str>,
),
}
impl ArrayData<'_> {
fn as_float(&self) -> Option<&[f32]> {
match self {
Self::Float(v) => Some(v),
Self::String(..) => None,
}
}
}
struct Accessor<'a> {
count: u32,
source: Uri<'a, ArrayData<'a>>,
stride: u32,
params: Vec<Param<'a>>,
}
impl<'a> Accessor<'a> {
fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "accessor");
let count: u32 = node.parse_required_attribute("count")?;
let source = node.parse_url("source")?;
let _offset: u32 = node.parse_attribute("offset")?.unwrap_or(0);
let stride: u32 = 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,
source,
stride,
params,
})
}
}
struct Param<'a> {
ty: &'a str,
}
impl<'a> Param<'a> {
fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
let ty = node.required_attribute("type")?;
if let Some(child) = node.element_children().next() {
return Err(error::unexpected_child_elem(child));
}
Ok(Self {
ty,
})
}
}
struct SharedInput<'a, T = Accessor<'a>> {
offset: u32,
semantic: InputSemantic,
source: Uri<'a, T>,
set: u32,
}
impl<'a, T> SharedInput<'a, T> {
fn parse(node: xml::Node<'a, '_>) -> 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: u32 = node.parse_required_attribute("offset")?;
let set: u32 = node.parse_attribute("set")?.unwrap_or(0);
if let Some(child) = node.element_children().next() {
return Err(error::unexpected_child_elem(child));
}
Ok(Self {
offset,
semantic,
source,
set,
})
}
fn cast<U>(self) -> SharedInput<'a, U> {
SharedInput {
offset: self.offset,
semantic: self.semantic,
source: self.source.cast(),
set: self.set,
}
}
}
struct UnsharedInput<'a> {
semantic: InputSemantic,
source: Uri<'a, Accessor<'a>>,
}
impl<'a> UnsharedInput<'a> {
fn parse(node: xml::Node<'a, '_>) -> io::Result<Self> {
debug_assert_eq!(node.tag_name().name(), "input");
let semantic = node.parse_required_attribute("semantic")?;
let source = node.parse_url("source")?;
if let Some(child) = node.element_children().next() {
return Err(error::unexpected_child_elem(child));
}
Ok(Self { semantic, source })
}
}
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
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),
})
}
}