From 0dc07e9608dd72afdf27935f88ed733c97e358c4 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Tue, 19 Dec 2023 17:12:14 -0500 Subject: [PATCH] Fork `bvh_anim` --- Cargo.lock | 122 ++++- Cargo.toml | 8 +- studio-bvh/Cargo.toml | 14 + studio-bvh/LICENSE | 18 + studio-bvh/project.json | 32 ++ studio-bvh/src/errors.rs | 507 ++++++++++++++++++ studio-bvh/src/frame_cursor.rs | 600 +++++++++++++++++++++ studio-bvh/src/frame_iter.rs | 325 ++++++++++++ studio-bvh/src/joint.rs | 852 +++++++++++++++++++++++++++++ studio-bvh/src/joint_cursor.rs | 42 ++ studio-bvh/src/lib.rs | 944 +++++++++++++++++++++++++++++++++ studio-bvh/src/macros.rs | 611 +++++++++++++++++++++ studio-bvh/src/parse.rs | 626 ++++++++++++++++++++++ studio-bvh/src/write.rs | 510 ++++++++++++++++++ 14 files changed, 5202 insertions(+), 9 deletions(-) create mode 100644 studio-bvh/Cargo.toml create mode 100644 studio-bvh/LICENSE create mode 100644 studio-bvh/project.json create mode 100644 studio-bvh/src/errors.rs create mode 100644 studio-bvh/src/frame_cursor.rs create mode 100644 studio-bvh/src/frame_iter.rs create mode 100644 studio-bvh/src/joint.rs create mode 100644 studio-bvh/src/joint_cursor.rs create mode 100644 studio-bvh/src/lib.rs create mode 100644 studio-bvh/src/macros.rs create mode 100644 studio-bvh/src/parse.rs create mode 100644 studio-bvh/src/write.rs diff --git a/Cargo.lock b/Cargo.lock index a584ec8..b8ec6ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "approx" version = "0.5.1" @@ -208,6 +214,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.4" @@ -1413,6 +1425,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.5.0" @@ -1420,7 +1444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.4", "cc", "cfg-if", "constant_time_eq", @@ -1467,6 +1491,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata 0.1.10", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -1527,7 +1562,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -2063,6 +2098,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures-core" version = "0.3.28" @@ -2553,6 +2594,29 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "lexical" +version = "5.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f404a90a744e32e8be729034fc33b90cf2a56418fbf594d69aa3c0214ad414e5" +dependencies = [ + "cfg-if", + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec 0.5.2", + "bitflags 1.3.2", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.148" @@ -2842,6 +2906,19 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "nom" version = "7.1.3" @@ -3142,7 +3219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b55dc0e6db79bddbc5fd583569f7356cdcc63e1e9b2b93a9ab70dd8e717160e0" dependencies = [ "approx", - "arrayvec", + "arrayvec 0.7.4", "bitflags 1.3.2", "downcast-rs", "either", @@ -3269,6 +3346,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "radsort" version = "0.1.0" @@ -3288,7 +3371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62a8a0bd9d3135f7b4eb45d0796540e7bab47b6b7c974f90567ccc5a0454f42b" dependencies = [ "approx", - "arrayvec", + "arrayvec 0.7.4", "bit-vec", "bitflags 1.3.2", "crossbeam", @@ -3609,6 +3692,7 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" name = "studio" version = "0.1.0" dependencies = [ + "anyhow", "bevy", "bevy-inspector-egui", "bevy_mod_outline", @@ -3626,6 +3710,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "studio-bvh" +version = "0.1.0" +dependencies = [ + "bstr", + "lexical", + "nom 6.1.2", + "smallvec", +] + [[package]] name = "svg_fmt" version = "0.4.1" @@ -3674,12 +3768,18 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82870da09c57a8a5a50f830ce8993a6637b9a4932f69257f12aea3fa68f588c9" dependencies = [ - "arrayvec", + "arrayvec 0.7.4", "grid", "num-traits", "slotmap", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.3.0" @@ -4098,7 +4198,7 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed547920565c56c7a29afb4538ac5ae5048865a5d2f05bff3ad4fbeb921a9a2c" dependencies = [ - "arrayvec", + "arrayvec 0.7.4", "cfg-if", "js-sys", "log", @@ -4122,7 +4222,7 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f8a44dd301a30ceeed3c27d8c0090433d3da04d7b2a4042738095a424d12ae7" dependencies = [ - "arrayvec", + "arrayvec 0.7.4", "bit-vec", "bitflags 2.4.0", "codespan-reporting", @@ -4146,7 +4246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a80bf0e3c77399bb52850cb0830af9bad073d5cfcb9dd8253bef8125c42db17" dependencies = [ "android_system_properties", - "arrayvec", + "arrayvec 0.7.4", "ash", "bit-set", "bitflags 2.4.0", @@ -4488,6 +4588,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "x11-dl" version = "2.21.0" diff --git a/Cargo.toml b/Cargo.toml index 0251c2b..a7b75e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,12 @@ [workspace] resolver = "2" -members = ["./studio"] +members = [ + "./studio", + "./studio-bvh", +] + +[workspace.dependencies] +anyhow = "1.0.75" [workspace.dependencies.bevy] version = "0.12.0" diff --git a/studio-bvh/Cargo.toml b/studio-bvh/Cargo.toml new file mode 100644 index 0000000..de82921 --- /dev/null +++ b/studio-bvh/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "studio-bvh" +version = "0.1.0" +edition = "2021" +authors = [ + "George Burton ", + "Storyteller.ai", +] + +[dependencies] # TODO: Update dependencies +bstr = "0.2" +lexical = "5.2" +nom = "6" +smallvec = "1.5" diff --git a/studio-bvh/LICENSE b/studio-bvh/LICENSE new file mode 100644 index 0000000..3ecad07 --- /dev/null +++ b/studio-bvh/LICENSE @@ -0,0 +1,18 @@ +Copyright 2019-2020 George Burton + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/studio-bvh/project.json b/studio-bvh/project.json new file mode 100644 index 0000000..27936fb --- /dev/null +++ b/studio-bvh/project.json @@ -0,0 +1,32 @@ +{ + "name": "studio-bvh", + "$schema": "../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "./studio-bvh/src", + "targets": { + "build": { + "executor": "@nxrs/cargo:build", + "options": { + "release": false + }, + "configurations": { + "production": { + "release": true + } + } + }, + "test": { + "executor": "@nxrs/cargo:test", + "options": {} + }, + "lint": { + "executor": "@nxrs/cargo:clippy", + "options": { + "fix": false, + "failOnWarnings": true, + "noDeps": true + } + } + }, + "tags": [] +} diff --git a/studio-bvh/src/errors.rs b/studio-bvh/src/errors.rs new file mode 100644 index 0000000..1359c52 --- /dev/null +++ b/studio-bvh/src/errors.rs @@ -0,0 +1,507 @@ +//! Errors which may occur when manipulating `Bvh` files. + +use crate::Axis; +use lexical::Error as LexicalError; +use std::{error::Error as StdError, fmt, io}; + +/// Errors which may arise when loading a `Bvh` file from +/// a `Reader`. +#[derive(Debug)] +pub struct LoadError { + /// The error kind. + kind: LoadErrorKind, +} + +impl LoadError { + /// Get the line where the error occurred, or `None` if there is + /// no associated line number. + #[inline] + pub fn line(&self) -> Option { + match self.kind { + LoadErrorKind::Joints(ref e) => e.line(), + LoadErrorKind::Motion(ref e) => e.line(), + } + } + + /// Returns the `LoadError` kind. + #[inline] + pub fn kind(&self) -> &LoadErrorKind { + &self.kind + } + + /// Unwraps the `LoadErrorKind` from the `LoadError`. + #[inline] + pub fn into_kind(self) -> LoadErrorKind { + self.kind + } +} + +impl> From for LoadError { + #[inline] + fn from(kind: K) -> Self { + LoadError { kind: kind.into() } + } +} + +impl fmt::Display for LoadError { + #[inline] + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + let desc = match self.kind { + LoadErrorKind::Joints(_) => "Could not load hierarchy", + LoadErrorKind::Motion(_) => "Could not load motion", + }; + + write!(fmtr, "{}: {}", desc, self.source().unwrap()) + } +} + +impl StdError for LoadError { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self.kind { + LoadErrorKind::Joints(ref e) => Some(e), + LoadErrorKind::Motion(ref e) => Some(e), + } + } +} + +/// The kind of the `LoadError`. +#[derive(Debug)] +pub enum LoadErrorKind { + /// An error occurred when loading the joints hierarchy. + Joints(LoadJointsError), + /// An error occurred when loading the motion values. + Motion(LoadMotionError), +} + +impl From for LoadErrorKind { + #[inline] + fn from(e: LoadJointsError) -> Self { + LoadErrorKind::Joints(e) + } +} + +impl From for LoadErrorKind { + #[inline] + fn from(e: LoadMotionError) -> Self { + LoadErrorKind::Motion(e) + } +} + +/// Represents an error which occurred when loading the `Joints` of the +/// bvh file. +#[derive(Debug)] +pub enum LoadJointsError { + /// An I/O error occurred. + Io(io::Error), + /// The skeletal hierarchy is missing the `Root` joint. + MissingRoot, + /// A name could not be found for the `Joint`. + MissingJointName { + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// A `CHANNELS` section was encountered in the wrong location. + UnexpectedChannelsSection { + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// The number of channels could not be parsed in a `CHANNELS` section. + ParseNumChannelsError { + /// The parse error, if there was a malformed string to parse. + error: Option, + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// A channel type could not be parsed in the `CHANNELS` section. + ParseChannelError { + /// The parse error. + error: ParseChannelError, + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// An `OFFSET` section was encountered in the wrong location. + UnexpectedOffsetSection { + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// An axis in the `OFFSET` section could not be parsed into a value. + ParseOffsetError { + /// The parse error. + parse_float_error: LexicalError, + /// The axis of the offset which could not be parsed. + axis: Axis, + /// Line number in the source bvh where the error occurred. + line: usize, + }, + /// An `OFFSET` section was missing an axis in the offset vector. + MissingOffsetAxis { + /// The smallest axis which was missing. + axis: Axis, + /// Line number in the source bvh where the error occurred. + line: usize, + }, +} + +impl LoadJointsError { + /// Get the line where the error occurred, or `None` if there is + /// no associated line number. + #[inline] + pub fn line(&self) -> Option { + match *self { + LoadJointsError::MissingJointName { line } + | LoadJointsError::UnexpectedChannelsSection { line } + | LoadJointsError::ParseNumChannelsError { line, .. } + | LoadJointsError::ParseChannelError { line, .. } + | LoadJointsError::UnexpectedOffsetSection { line } + | LoadJointsError::ParseOffsetError { line, .. } + | LoadJointsError::MissingOffsetAxis { line, .. } => Some(line), + _ => None, + } + } +} + +impl From for LoadJointsError { + #[inline] + fn from(e: io::Error) -> Self { + LoadJointsError::Io(e) + } +} + +impl fmt::Display for LoadJointsError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + LoadJointsError::Io(ref e) => fmt::Display::fmt(&e, f), + LoadJointsError::MissingRoot => f.write_str("The root heirarchy could not be found"), + LoadJointsError::MissingJointName { line } => { + write!(f, "{}: the name is missing from the joints section", line) + } + LoadJointsError::UnexpectedChannelsSection { line } => write!( + f, + "{}: unexpectedly encountered a \"CHANNELS\" section", + line + ), + LoadJointsError::ParseNumChannelsError { line, .. } => { + write!(f, "{}: could not find the number of channels", line) + } + LoadJointsError::ParseChannelError { line, .. } => { + write!(f, "{}: could not parse channel", line) + } + LoadJointsError::UnexpectedOffsetSection { line } => write!( + f, + "{}: unexpectedly encountered an \"OFFSET\" section", + line + ), + LoadJointsError::ParseOffsetError { axis, line, .. } => { + write!(f, "{}: could not parse the {}-axis offset", line, axis,) + } + LoadJointsError::MissingOffsetAxis { axis, line } => { + write!(f, "{}: the {}-axis offset value is missing", line, axis) + } + } + } +} + +impl StdError for LoadJointsError { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match *self { + LoadJointsError::Io(ref e) => Some(e), + /* + LoadJointsError::ParseNumChannelsError { ref error, .. } => { + error.as_ref().map(|e| e as &(dyn StdError + 'static)) + } + */ + LoadJointsError::ParseChannelError { ref error, .. } => Some(error), + /* + LoadJointsError::ParseOffsetError { + ref parse_float_error, + .. + } => Some(parse_float_error), + */ + _ => None, + } + } +} + +/// Represents an error which occurred when loading the motion of the +/// bvh file. +#[derive(Debug)] +pub enum LoadMotionError { + /// An I/O error occurred. + Io(io::Error), + /// The `MOTION` section is missing in the bvh. + MissingMotionSection { + /// The line where the error occurred. + line: usize, + }, + /// The "Number of Frames" section could not be parsed in the bvh. + MissingNumFrames { + /// The parse error, or `None` if there was no number to be parsed. + parse_error: Option, + /// The line where the error occurred. + line: usize, + }, + /// The "Frame Time" section could not be parsed in the bvh. + MissingFrameTime { + /// The parse error, or `None` if there was no number to be parsed. + parse_error: Option, + /// The line where the error occurred. + line: usize, + }, + /// The motion values section could not be parsed in the bvh. + ParseMotionSection { + /// The parse error, or `None` if there was no number to be parsed. + parse_error: LexicalError, + /// The index of the motion value where the error occurred. + channel_index: usize, + /// The line where the error occurred. + line: usize, + }, + /// There was a discrepancy between the number of motion values promised + /// by the file and the actual amount. + MotionCountMismatch { + /// Actual number of motion values parsed. + actual_total_motion_values: usize, + /// Expected number of motion values. + expected_total_motion_values: usize, + /// Expected number of frames. + expected_num_frames: usize, + /// Expected number of clips. + expected_num_clips: usize, + }, +} + +impl LoadMotionError { + /// Get the line where the error occurred, or `None` if there is + /// no associated line number. + pub fn line(&self) -> Option { + match *self { + LoadMotionError::MissingMotionSection { line } + | LoadMotionError::MissingNumFrames { line, .. } + | LoadMotionError::MissingFrameTime { line, .. } + | LoadMotionError::ParseMotionSection { line, .. } => Some(line), + _ => None, + } + } +} + +impl fmt::Display for LoadMotionError { + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + LoadMotionError::Io(ref e) => fmt::Display::fmt(e, fmtr), + LoadMotionError::MissingMotionSection { line } => { + write!( + fmtr, + "{}: The 'MOTION' section of the bvh file is missing", + line + ) + } + LoadMotionError::MissingNumFrames { + ref parse_error, + line, + } => { + if let Some(_) = parse_error { + write!(fmtr, "{}: could not parse the num frames value", line) + } else { + write!( + fmtr, + "{}: The number of frames section is missing from the bvh file", + line + ) + } + } + LoadMotionError::MissingFrameTime { + ref parse_error, + line, + } => { + if let Some(_) = parse_error { + write!(fmtr, "{}: could not parse the frame time", line) + } else { + write!(fmtr, "{}: Could not parse the frame time", line) + } + } + LoadMotionError::ParseMotionSection { line, .. } => { + write!(fmtr, "{}: Could not parse the motion value", line) + } + LoadMotionError::MotionCountMismatch { + actual_total_motion_values, + expected_total_motion_values, + expected_num_frames, + expected_num_clips, + } => { + write!( + fmtr, + "expected to find {} motion values, found {} values (num frames = {}, num clips = {})", + expected_total_motion_values, + actual_total_motion_values, + expected_num_frames, + expected_num_clips) + } + } + } +} + +impl StdError for LoadMotionError { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match *self { + LoadMotionError::Io(ref e) => Some(e), + /* + LoadMotionError::MissingFrameTime { + parse_error: Some(ref e), + .. + } => Some(e), + LoadMotionError::ParseMotionSection { + ref parse_error, .. + } => Some(parse_error), + LoadMotionError::MissingNumFrames { + parse_error: Some(ref e), + .. + } => Some(e), + */ + _ => None, + } + } +} + +impl From for LoadMotionError { + #[inline] + fn from(e: io::Error) -> Self { + LoadMotionError::Io(e) + } +} + +/// An error which may occurr when setting a motion which is out +/// of bounds. +#[derive(Clone, Debug, PartialEq)] +pub enum SetMotionError { + /// The frame was out of bounds. + BadFrame(usize), + /// The channel was out of bounds. + BadChannel(usize), +} + +impl fmt::Display for SetMotionError { + #[inline] + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + SetMotionError::BadFrame(frame) => write!(fmtr, "Frame {} was out of bounds", frame), + SetMotionError::BadChannel(channel) => { + write!(fmtr, "Channel {} of the bvh was out of bounds", channel,) + } + } + } +} + +impl StdError for SetMotionError { + #[inline] + fn description(&self) -> &'static str { + match *self { + SetMotionError::BadFrame(_) => "The frame was out of bounds", + SetMotionError::BadChannel(_) => "The channel was out of bounds", + } + } +} + +/// Represents an error which may occur when attempting to parse a +/// `BString` into a `ChannelType`. +#[derive(Debug)] +pub struct ParseChannelError { + // @TODO(burtonageo): Borrow the erroneous string when hrts + // land. + bad_string: Vec, +} + +impl ParseChannelError { + /// Get the `BString` which caused the parse error. + #[inline] + pub fn into_inner(self) -> Vec { + self.bad_string + } +} + +impl>> From for ParseChannelError { + #[inline] + fn from(bad_string: S) -> Self { + ParseChannelError { + bad_string: bad_string.into(), + } + } +} + +impl fmt::Display for ParseChannelError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "The channel could not be parsed from the given string: {:?}", + &self.bad_string + ) + } +} + +impl StdError for ParseChannelError {} + +/// An error which may occur when attempting to insert a frame into a +/// `Bvh`. +#[derive(Debug)] +pub struct FrameInsertError { + kind: FrameInsertErrorKind, +} + +impl FrameInsertError { + pub(crate) const fn incorrect_len(expected: usize, actual: usize) -> Self { + Self { + kind: FrameInsertErrorKind::IncorrectFrameLength { expected, actual }, + } + } +} + +impl fmt::Display for FrameInsertError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + FrameInsertErrorKind::IncorrectFrameLength { expected, actual } => { + write!( + f, + "Attempted to insert a frame with {} channels into a bvh containing {} channels", + actual, + expected, + ) + } + } + } +} + +impl StdError for FrameInsertError {} + +#[derive(Debug)] +enum FrameInsertErrorKind { + IncorrectFrameLength { expected: usize, actual: usize }, +} + +/// An error which may occur when attempting to remove a frame from a +/// `Bvh`. +#[derive(Debug)] +pub struct FrameRemoveError { + index: usize, +} + +impl FrameRemoveError { + pub(crate) const fn new(index: usize) -> Self { + Self { index } + } +} + +impl fmt::Display for FrameRemoveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Attempted to remove a frame at index {} which does not exist", + self.index + ) + } +} + +impl StdError for FrameRemoveError {} diff --git a/studio-bvh/src/frame_cursor.rs b/studio-bvh/src/frame_cursor.rs new file mode 100644 index 0000000..7835fe2 --- /dev/null +++ b/studio-bvh/src/frame_cursor.rs @@ -0,0 +1,600 @@ +use crate::{ + errors::{FrameInsertError, FrameRemoveError}, + frames::{Frame, FrameMut, Frames, FramesMut}, + Bvh, +}; +use std::cmp::min; + +/// A `FrameCursor` is used to insert frames into a [`Bvh`]. It operates +/// similarly to a [`linked_list::Cursor`]: methods are provided to move +/// through frames, as well as insert and delete frames. +/// +/// You can create a `FrameCursor` using the [`Bvh::frame_cursor`] method. +/// +/// [`Bvh`]: ../struct.Bvh.html +/// [`linked_list::Cursor`]: http://doc.rust-lang.org/stable/std/collections/linked_list/struct.Cursor.html +/// [`Bvh::frame_cursor`]: ../struct.Bvh.html#method.frame_cursor +#[derive(Debug)] +pub struct FrameCursor<'bvh> { + bvh: &'bvh mut Bvh, + index: usize, +} + +impl<'bvh> FrameCursor<'bvh> { + /// Returns the current index of the `FrameCursor`. + #[inline] + pub const fn index(&self) -> usize { + self.index + } + + /// Returns the number of frames in the [`Bvh`] the cursor is is currently + /// pointing to. + /// + /// [`Bvh`]: ../struct.Bvh.html + #[inline] + pub fn len(&self) -> usize { + self.bvh.frames().len() + } + + /// Returns the number of channels in the [`Bvh`] the cursor is is currently + /// pointing to. + /// + /// [`Bvh`]: ../struct.Bvh.html + #[inline] + pub const fn num_channels(&self) -> usize { + self.bvh.num_channels + } + + /// Move the `FrameCursor` to the next frame of the `Bvh`'s motion values. + /// + /// If the `FrameCursor` is currently at the last frame, then this method will + /// do nothing. + #[inline] + pub fn move_next(&mut self) -> &mut Self { + self.index = min(self.index + 1, self.bvh.frames().len()); + self + } + + /// Move the `FrameCursor` to the previous frame of the `Bvh`'s motion values. + /// + /// If the `FrameCursor` is currently at the first frame, then this method will + /// do nothing. + #[inline] + pub fn move_prev(&mut self) -> &mut Self { + self.index = self.index.saturating_sub(1); + self + } + + /// Move the `FrameCursor` to the first frame of the `Bvh`'s motion values. + /// + /// If the `FrameCursor` is currently at the first frame, then this method will + /// do nothing. + #[inline] + pub fn move_first(&mut self) -> &mut Self { + self.index = 0; + self + } + + /// Move the `FrameCursor` to the last frame of the `Bvh`'s motion values. + /// + /// If the `FrameCursor` is currently at the last frame, then this method will + /// do nothing. + #[inline] + pub fn move_last(&mut self) -> &mut Self { + self.index = self.len(); + self + } + + /// Returns the frame next to the current index of the `FrameCursor`. + /// + /// Returns `None` if there is no frame available. + #[inline] + pub fn peek_next(&self) -> Option> { + self.bvh.frames().nth(self.index) + } + + /// Returns the frame before the current index of the `FrameCursor`. + /// + /// Returns `None` if there is no frame available. + #[inline] + pub fn peek_prev(&self) -> Option> { + if self.index() > 0 { + self.bvh.frames().nth(self.index - 1) + } else { + None + } + } + + /// Returns a mutable reference to the frame next to the current + /// index of the `FrameCursor`. + /// + /// Returns `None` if there is no frame available. + #[inline] + pub fn peek_next_mut(&mut self) -> Option> { + self.bvh.frames_mut().nth(self.index) + } + + /// Returns a mutable reference to the frame before the current index + /// of the `FrameCursor`. + /// + /// Returns `None` if there is no frame available. + #[inline] + pub fn peek_prev_mut(&mut self) -> Option> { + if self.index() > 0 { + self.bvh.frames_mut().nth(self.index - 1) + } else { + None + } + } + + /// Returns the frames surrounding the current index of the `FrameCursor`. + /// + /// If either of the frames is not available, then `None` will be returned + /// for that frame. + #[inline] + pub fn surrounding_frames(&self) -> (Option>, Option>) { + (self.peek_prev(), self.peek_next()) + } + + /// Attempt to insert a new frame of animation after the current index. + /// The index of the cursor will be advanced to the end of the frames inserted. + /// + /// # Errors + /// + /// If the frame could not be inserted (because it was an incorrect size), + /// then an error will be returned. + /// + /// # Examples + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// let mut simple_skeleton = bvh! { + /// HIERARCHY + /// ROOT Base + /// { + /// // Hierarchy omitted... + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// # JOINT Middle1 + /// # { + /// # OFFSET 0.0 0.0 15.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip1 + /// # { + /// # OFFSET 0.0 0.0 30.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 45.0 + /// # } + /// # } + /// # } + /// # JOINT Middle2 + /// # { + /// # OFFSET 0.0 15.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip2 + /// # { + /// # OFFSET 0.0 30.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 45.0 0.0 + /// # } + /// # } + /// # } + /// } + /// + /// MOTION + /// Frames: 3 + /// Frame Time: 0.033333333333 + /// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + /// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + /// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 + /// }; + /// + /// let mut frame_cursor = simple_skeleton.frame_cursor(); + /// frame_cursor.move_last(); + /// + /// assert_eq!(frame_cursor.index(), 3); + /// + /// frame_cursor + /// .try_insert_frame(&[3.0; 18]) + /// .expect("Could not insert frame"); + /// + /// assert_eq!(frame_cursor.index(), 4); + /// # drop(frame_cursor); + /// assert_eq!(simple_skeleton.frames().len(), 4); + /// + /// # for (i, frame) in simple_skeleton.frames().enumerate() { + /// # for motion in &frame.as_slice()[..] { + /// # assert_eq!(*motion, i as f32); + /// # } + /// # } + /// # } // fn main() + /// ``` + pub fn try_insert_frame(&mut self, frame: &F) -> Result<&mut Self, FrameInsertError> + where + F: AsRef<[f32]> + ?Sized, + { + let frame = frame.as_ref(); + if frame.len() != self.num_channels() { + return Err(FrameInsertError::incorrect_len( + self.num_channels(), + frame.len(), + )); + } + + let idx = self.index * self.num_channels(); + vec_insert_slice(&mut self.bvh.motion_values, idx, frame); + + Ok(self.move_next()) + } + + /// Insert multiple frames contiguously at the current index. This is generally more + /// efficient than calling [`try_insert_frame`] multiple times because this method will + /// preallocate space for the multiple frames. + /// + /// The index of the cursor will be advanced to the end of the frames inserted. + /// + /// # Errors + /// + /// If any of the frames could not be inserted (because they were an incorrect size), + /// then an error will be returned. + /// + /// # Examples + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// let mut simple_skeleton = bvh! { + /// HIERARCHY + /// ROOT Base + /// { + /// // Hierarchy omitted... + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// # JOINT Middle1 + /// # { + /// # OFFSET 0.0 0.0 15.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip1 + /// # { + /// # OFFSET 0.0 0.0 30.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 45.0 + /// # } + /// # } + /// # } + /// # JOINT Middle2 + /// # { + /// # OFFSET 0.0 15.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip2 + /// # { + /// # OFFSET 0.0 30.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 45.0 0.0 + /// # } + /// # } + /// # } + /// } + /// + /// MOTION + /// Frames: 3 + /// Frame Time: 0.033333333333 + /// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + /// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + /// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 + /// }; + /// + /// simple_skeleton + /// .frame_cursor() + /// .move_last() + /// .try_insert_frames(&[[3.0; 18], [4.0; 18]]) + /// .expect("Could not insert frames"); + /// + /// assert_eq!(simple_skeleton.frames().len(), 5); + /// + /// # for (i, frame) in simple_skeleton.frames().enumerate() { + /// # for motion in &frame.as_slice()[..] { + /// # assert_eq!(*motion, i as f32); + /// # } + /// # } + /// # } // fn main() + /// ``` + /// + ///[`try_insert_frame`]: ./struct.FrameInserter.html#method.try_insert_frame + pub fn try_insert_frames<'f, I, F>(&mut self, frames: I) -> Result<&mut Self, FrameInsertError> + where + I: IntoIterator, + F: 'f + AsRef<[f32]> + ?Sized, + { + let frames = frames.into_iter(); + let num_channels = self.num_channels(); + let num_frames = { + let (low, up) = frames.size_hint(); + up.unwrap_or(low) + }; + + self.bvh.motion_values.reserve(num_frames * num_channels); + + for frame in frames.into_iter() { + let frame = frame.as_ref(); + self.try_insert_frame(&frame)?; + } + + Ok(self) + } + + /// Attempt to remove a frame of animation at the current index. + /// + /// # Errors + /// + /// If the frame could not be removed, then an error will be returned. + /// + /// # Examples + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// let mut simple_skeleton = bvh! { + /// HIERARCHY + /// ROOT Base + /// { + /// // Hierarchy omitted... + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// # JOINT Middle1 + /// # { + /// # OFFSET 0.0 0.0 15.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip1 + /// # { + /// # OFFSET 0.0 0.0 30.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 45.0 + /// # } + /// # } + /// # } + /// # JOINT Middle2 + /// # { + /// # OFFSET 0.0 15.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip2 + /// # { + /// # OFFSET 0.0 30.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 45.0 0.0 + /// # } + /// # } + /// # } + /// } + /// + /// MOTION + /// Frames: 3 + /// Frame Time: 0.033333333333 + /// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + /// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + /// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 + /// }; + /// + /// simple_skeleton + /// .frame_cursor() + /// .remove_frame() + /// .expect("Could not remove frame"); + /// + /// assert_eq!(simple_skeleton.frames().len(), 2); + /// + /// # for (i, frame) in simple_skeleton.frames().enumerate() { + /// # for motion in &frame.as_slice()[..] { + /// # assert_eq!(*motion, (i + 1) as f32); + /// # } + /// # } + /// # } // fn main() + /// ``` + pub fn remove_frame(&mut self) -> Result<&mut Self, FrameRemoveError> { + if self.len() == 0 { + return Err(FrameRemoveError::new(self.index)); + } + + let bnd = |idx| idx * self.num_channels(); + let frame_bound = bnd(self.index)..bnd(self.index + 1); + + let mut i = 0; + self.bvh + .motion_values + .retain(|_| (!frame_bound.contains(&i), i += 1).0); + + Ok(self) + } + + /// Removes every frame in the [`Bvh`]. + // + /// # Examples + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// let mut simple_skeleton = bvh! { + /// HIERARCHY + /// ROOT Base + /// { + /// // Hierarchy omitted... + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// # JOINT Middle1 + /// # { + /// # OFFSET 0.0 0.0 15.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip1 + /// # { + /// # OFFSET 0.0 0.0 30.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 45.0 + /// # } + /// # } + /// # } + /// # JOINT Middle2 + /// # { + /// # OFFSET 0.0 15.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # JOINT Tip2 + /// # { + /// # OFFSET 0.0 30.0 0.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 45.0 0.0 + /// # } + /// # } + /// # } + /// } + /// + /// MOTION + /// Frames: 3 + /// Frame Time: 0.033333333333 + /// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + /// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + /// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 + /// }; + /// + /// let mut frame_cursor = simple_skeleton.frame_cursor(); + /// assert_eq!(frame_cursor.len(), 3); + /// frame_cursor.remove_all_frames(); + /// assert_eq!(frame_cursor.len(), 0); + /// # } // fn main() + /// ``` + /// + /// [`Bvh`]: ../struct.Bvh.html + #[inline] + pub fn remove_all_frames(&mut self) -> &mut Self { + self.bvh.motion_values.clear(); + self + } + + /// Shrinks the capacity of the motion values in the [`Bvh`] as much as possible. + /// + /// [`Bvh`]: ../struct.Bvh.html + #[inline] + pub fn shrink_to_fit(&mut self) { + self.bvh.motion_values.shrink_to_fit(); + } + + /// Create a new `Frames` iterator from the `FramesCursor`, starting at the current + /// index. + #[inline] + pub fn into_frames(self) -> Frames<'bvh> { + // @TODO: Replace with `Iterator::advance_by` when stable. + let mut frames = self.bvh.frames(); + for _ in 0..self.index { + if frames.next().is_none() { + break; + } + } + + frames + } + + /// Create a new `FramesMut` iterator from the `FramesCursor`, starting at the current + /// index. + #[inline] + pub fn into_frames_mut(self) -> FramesMut<'bvh> { + // @TODO: Replace with `Iterator::advance_by` when stable. + let mut frames = self.bvh.frames_mut(); + for _ in 0..self.index { + if frames.next().is_none() { + break; + } + } + + frames + } +} + +impl<'bvh> From<&'bvh mut Bvh> for FrameCursor<'bvh> { + #[inline] + fn from(bvh: &'bvh mut Bvh) -> Self { + Self { bvh, index: 0 } + } +} + +impl<'bvh> From> for Frames<'bvh> { + #[inline] + fn from(cursor: FrameCursor<'bvh>) -> Self { + cursor.into_frames() + } +} + +impl<'bvh> From> for FramesMut<'bvh> { + #[inline] + fn from(cursor: FrameCursor<'bvh>) -> Self { + cursor.into_frames_mut() + } +} + +/// A utility function which inserts the `slice` into the `vec` at the given `index`. +/// +/// # Panics +/// +/// Panics if `index` is out of bounds. +fn vec_insert_slice(vec: &mut Vec, index: usize, slice: &[T]) { + assert!( + index <= vec.len(), + "attempted to insert a slice at index {} into a vec of length {}", + index, + vec.len(), + ); + + let spare_capacity = vec.capacity().saturating_sub(vec.len()); + if slice.len() > spare_capacity { + vec.reserve(slice.len() - spare_capacity); + } + + let old_len = vec.len(); + let new_len = vec.len() + slice.len(); + vec.resize_with(new_len, T::default); + + // Move the existing items to the end + for (to_move, end_index) in (index..old_len).into_iter().zip(old_len + index..new_len) { + vec.swap(to_move, end_index); + } + + // Clone the slice into the new area + for (i, item) in slice.iter().enumerate() { + vec[i + index] = item.clone(); + } +} + +#[test] +fn test_vec_insert_slice() { + let mut v; + let sl = &[100, 200, 300]; + + v = vec![1, 2, 3]; + vec_insert_slice(&mut v, 0, sl); + assert_eq!(v, &[100, 200, 300, 1, 2, 3]); + + v = vec![1, 2, 3]; + vec_insert_slice(&mut v, 1, sl); + assert_eq!(v, &[1, 100, 200, 300, 2, 3]); + + v = vec![1, 2, 3]; + vec_insert_slice(&mut v, 2, sl); + assert_eq!(v, &[1, 2, 100, 200, 300, 3]); + + v = vec![1, 2, 3]; + vec_insert_slice(&mut v, 3, sl); + assert_eq!(v, &[1, 2, 3, 100, 200, 300]); +} diff --git a/studio-bvh/src/frame_iter.rs b/studio-bvh/src/frame_iter.rs new file mode 100644 index 0000000..d86aa27 --- /dev/null +++ b/studio-bvh/src/frame_iter.rs @@ -0,0 +1,325 @@ +use crate::{errors::SetMotionError, Channel}; +use std::{ + borrow::{Borrow, BorrowMut}, + iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator}, + mem, + ops::{Index, IndexMut}, + slice::{ChunksExact, ChunksExactMut, SliceIndex}, +}; + +/// An iterator over the frames of a `Bvh`. +/// +/// This type is created using the [`Bvh::frames`] method. +/// +/// [`Bvh::frames`]: ../struct.Bvh.html#method.frames +#[derive(Debug)] +pub struct Frames<'a> { + /// Note: `chunks` is wrapped in an option because having a `ChunksExact` + /// iterator over 0-length chunks panics, and empty `Bvh`s have empty + /// frames. + pub(crate) chunks: Option>, +} + +impl<'a> Iterator for Frames<'a> { + type Item = Frame<'a>; + + #[inline] + fn next(&mut self) -> Option { + self.chunks.as_mut().and_then(|c| c.next().map(Frame)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.chunks + .as_ref() + .map(|c| c.size_hint()) + .unwrap_or((0, Some(0))) + } +} + +impl<'a> DoubleEndedIterator for Frames<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.chunks.as_mut().and_then(|c| c.next_back().map(Frame)) + } +} + +impl<'a> ExactSizeIterator for Frames<'a> { + #[inline] + fn len(&self) -> usize { + self.chunks.as_ref().map(|c| c.len()).unwrap_or(0) + } +} + +impl<'a> FusedIterator for Frames<'a> {} + +/// A mutable iterator over the frames of a [`Bvh`]. +/// +/// This type is created using the [`Bvh::frames_mut`] method. +/// +/// [`Bvh`]: ../struct.Bvh.html +/// [`Bvh::frames_mut`]: ../struct.Bvh.html#method.frames_mut +#[derive(Debug)] +pub struct FramesMut<'a> { + /// Note: `chunks` is wrapped in an option for the same reason + /// that `Frames<'_>`'s `chunks` is wrapped. + pub(crate) chunks: Option>, +} + +impl<'a> Iterator for FramesMut<'a> { + type Item = FrameMut<'a>; + + #[inline] + fn next(&mut self) -> Option { + self.chunks.as_mut().and_then(|c| c.next().map(FrameMut)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.chunks + .as_ref() + .map(|c| c.size_hint()) + .unwrap_or((0, Some(0))) + } +} + +impl<'a> DoubleEndedIterator for FramesMut<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.chunks + .as_mut() + .and_then(|c| c.next_back().map(FrameMut)) + } +} + +impl<'a> ExactSizeIterator for FramesMut<'a> { + #[inline] + fn len(&self) -> usize { + self.chunks.as_ref().map(|c| c.len()).unwrap_or(0) + } +} + +impl<'a> FusedIterator for FramesMut<'a> {} + +/// A wrapper for a slice of motion values, so that they can be indexed by [`Channel`]. +/// +/// [`Channel`]: ./struct.Channel.html +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Frame<'a>(&'a [f32]); + +impl<'a> Frame<'a> { + /// Return the number of values in the `Frame`. + #[inline] + pub const fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if there are `0` values in the `FrameMut`. Otherwise, + /// returns `false`. + #[inline] + pub const fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Convert the `Frame` into a `&[f32]`. + #[inline] + pub const fn as_slice(&self) -> &[f32] { + self.0 + } + + /// Attempts to get the motion value at `index`. Otherwise, returns `None`. + #[inline] + pub fn get(&self, index: I) -> Option<&Output> { + self.0.get(index.to_slice_index()) + } +} + +impl<'a> AsRef<[f32]> for Frame<'a> { + #[inline] + fn as_ref(&self) -> &[f32] { + self.0 + } +} + +impl<'a> Borrow<[f32]> for Frame<'a> { + #[inline] + fn borrow(&self) -> &[f32] { + &*self.0 + } +} + +impl<'a, I: FrameIndex> Index for Frame<'a> { + type Output = Output; + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index.to_slice_index()) + } +} + +/// A wrapper for a mutable slice of motion values, so that they can be indexed by [`Channel`]. +/// +/// [`Channel`]: ./struct.Channel.html +#[derive(Debug, PartialEq)] +pub struct FrameMut<'a>(&'a mut [f32]); + +impl<'a> FrameMut<'a> { + /// Return the number of values in the `FrameMut`. + #[inline] + pub const fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if there are `0` values in the `FrameMut`. Otherwise, + /// returns `false`. + #[inline] + pub const fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Convert the `FrameMut` into a `&[f32]`. + #[inline] + pub const fn as_slice(&self) -> &[f32] { + &*self.0 + } + + /// Convert the `FrameMut` into a `&mut [f32]`. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [f32] { + self.0 + } + + /// Attempts to get the motion value at `index`. Otherwise, returns `None`. + #[inline] + pub fn get(&self, index: I) -> Option<&Output> { + self.0.get(index.to_slice_index()) + } + + /// Attempts to return a mutable reference to the motion value at `channel`. + /// Otherwise, returns `None`. + #[inline] + pub fn get_mut(&'a mut self, index: I) -> Option<&mut Output> { + self.0.get_mut(index.to_slice_index()) + } + + /// Updates the `motion` value at `channel` to `new_motion`. + /// + /// # Notes + /// + /// Returns the previous motion value if the operation was successful, and `Err(_)` if + /// the operation was out of bounds. + pub fn try_set_motion(&mut self, index: I, new_motion: f32) -> Result + where + I: FrameIndex, + { + let index = index.to_slice_index(); + let motion = self + .0 + .get_mut(index) + .ok_or(SetMotionError::BadChannel(index))?; + Ok(mem::replace(motion, new_motion)) + } +} + +impl<'a> Borrow<[f32]> for FrameMut<'a> { + #[inline] + fn borrow(&self) -> &[f32] { + &*self.0 + } +} + +impl<'a> BorrowMut<[f32]> for FrameMut<'a> { + #[inline] + fn borrow_mut(&mut self) -> &mut [f32] { + self.0 + } +} + +impl<'a> AsRef<[f32]> for FrameMut<'a> { + #[inline] + fn as_ref(&self) -> &[f32] { + &*self.0 + } +} + +impl<'a> AsMut<[f32]> for FrameMut<'a> { + #[inline] + fn as_mut(&mut self) -> &mut [f32] { + self.0 + } +} + +impl<'a, I: FrameIndex> Index for FrameMut<'a> { + type Output = Output; + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index.to_slice_index()) + } +} + +impl<'a, I: FrameIndex> IndexMut for FrameMut<'a> { + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.0.index_mut(index.to_slice_index()) + } +} + +impl<'a> From<&'a FrameMut<'a>> for Frame<'a> { + #[inline] + fn from(frame_mut: &'a FrameMut<'a>) -> Self { + Frame(&*frame_mut.0) + } +} + +impl<'a> From> for Frame<'a> { + #[inline] + fn from(frame_mut: FrameMut<'a>) -> Self { + Frame(&*frame_mut.0) + } +} + +mod private { + pub trait Sealed {} +} + +/// A helper trait used for indexing into a frame. +pub trait FrameIndex: Sized + private::Sealed { + /// The type of the index generated. + type SliceIndex: SliceIndex<[f32]>; + /// Convert `Self` to a `SliceIndex`. + fn to_slice_index(self) -> Self::SliceIndex; +} + +/// Type alias for the result of a `FrameIndex` operation. +pub type Output = <::SliceIndex as SliceIndex<[f32]>>::Output; + +impl> FrameIndex for T { + type SliceIndex = Self; + + #[inline] + fn to_slice_index(self) -> Self::SliceIndex { + self + } +} + +// @TODO: Combine these into one impl for `impl Borrow` when specialization lands. +macro_rules! impl_channel_frame_index { + ($($chn:ty),* $(,)?) => { + $( + impl FrameIndex for $chn { + type SliceIndex = usize; + + #[inline] + #[inline] + fn to_slice_index(self) -> Self::SliceIndex { + self.motion_index + } + } + )* + } +} + +impl_channel_frame_index!(Channel, &'_ Channel); + +impl private::Sealed for Channel {} +impl private::Sealed for &'_ Channel {} +impl> private::Sealed for T {} diff --git a/studio-bvh/src/joint.rs b/studio-bvh/src/joint.rs new file mode 100644 index 0000000..63afa62 --- /dev/null +++ b/studio-bvh/src/joint.rs @@ -0,0 +1,852 @@ +#![allow(dead_code, missing_docs)] + +use crate::Channel; +use bstr::{BStr, BString, ByteSlice}; +use smallvec::SmallVec; +use std::{ + cmp::{Ordering, PartialEq, PartialOrd}, + ffi::{CStr, CString}, + fmt, mem, + ops::{Deref, DerefMut}, + str, +}; + +/// A 3-element point representing the location of a joint offset or +/// end site. +pub type Offset = [f32; 3]; + +/// Internal representation of a joint. +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum JointData { + /// Root of the skeletal heirarchy. + Root { + /// Name of the root `Joint`. + name: JointName, + /// Positional offset of this `Joint` relative to the parent. + offset: Offset, + /// The channels applicable to this `Joint`. + channels: SmallVec<[Channel; 6]>, + }, + /// A child joint in the skeleton. + Child { + /// Name of the `Joint`. + name: JointName, + /// Positional offset of this `Joint` relative to the parent. + offset: Offset, + /// The channels applicable to this `Joint`. + channels: SmallVec<[Channel; 3]>, + /// End site offset. + end_site_offset: Option, + /// Private data. + private: JointPrivateData, + }, +} + +impl JointData { + #[inline] + pub(crate) const fn is_root(&self) -> bool { + match *self { + JointData::Root { .. } => true, + _ => false, + } + } + + #[inline] + pub(crate) const fn is_child(&self) -> bool { + match *self { + JointData::Child { .. } => true, + _ => false, + } + } + + #[inline] + pub(crate) fn name(&self) -> &[u8] { + let name = match *self { + JointData::Root { ref name, .. } | JointData::Child { ref name, .. } => name, + }; + + name.as_bytes() + } + + #[inline] + pub(crate) const fn offset(&self) -> &Offset { + match *self { + JointData::Child { ref offset, .. } | JointData::Root { ref offset, .. } => offset, + } + } + + #[inline] + pub(crate) const fn end_site(&self) -> Option<&Offset> { + match *self { + JointData::Child { + ref end_site_offset, + .. + } => end_site_offset.as_ref(), + _ => None, + } + } + + #[inline] + pub fn channels(&self) -> &[Channel] { + match *self { + JointData::Child { ref channels, .. } => &channels[..], + JointData::Root { ref channels, .. } => &channels[..], + } + } + + /// Returns a mutable reference to ordered array of `Channel`s of this `JointData`. + #[inline] + pub fn channels_mut(&mut self) -> &mut [Channel] { + match *self { + JointData::Child { + ref mut channels, .. + } => &mut channels[..], + JointData::Root { + ref mut channels, .. + } => &mut channels[..], + } + } + + /// Return the index of this `Joint` in the array. + #[inline] + pub fn index(&self) -> usize { + self.private_data().map(|d| d.self_index).unwrap_or(0) + } + + /// Returns the index of this joint's parent in the `Bvh`'s array of joints, + /// or `None` if this `Joint` is the root joint. + #[inline] + pub fn parent_index(&self) -> Option { + self.private_data().map(|d| d.parent_index) + } + + /// Returns a reference to the `JointPrivateData` of the `JointData` if it + /// exists, or `None`. + #[inline] + pub(crate) const fn private_data(&self) -> Option<&JointPrivateData> { + match *self { + JointData::Child { ref private, .. } => Some(private), + _ => None, + } + } + + /// Get the depth of the `JointData` in the heirarchy. + #[inline] + pub(crate) const fn depth(&self) -> usize { + match *self { + JointData::Child { ref private, .. } => private.depth, + _ => 0, + } + } + + #[inline] + pub(crate) fn empty_root() -> Self { + JointData::Root { + name: Default::default(), + offset: [0.0, 0.0, 0.0].into(), + channels: Default::default(), + } + } + + #[inline] + pub(crate) fn empty_child() -> Self { + JointData::Child { + name: Default::default(), + offset: [0.0, 0.0, 0.0].into(), + channels: Default::default(), + end_site_offset: Default::default(), + private: JointPrivateData::empty(), + } + } + + #[inline] + pub(crate) fn set_name>(&mut self, new_name: S) { + match *self { + JointData::Root { ref mut name, .. } => *name = new_name.into(), + JointData::Child { ref mut name, .. } => *name = new_name.into(), + } + } + + pub(crate) fn set_offset(&mut self, new_offset: impl Into, is_site: bool) { + let new_offset = new_offset.into(); + match *self { + JointData::Root { ref mut offset, .. } => *offset = new_offset, + JointData::Child { + ref mut offset, + ref mut end_site_offset, + .. + } => { + if is_site { + *end_site_offset = Some(new_offset); + } else { + *offset = new_offset; + } + } + } + } + + #[inline] + pub(crate) fn set_channels(&mut self, new_channels: SmallVec<[Channel; 6]>) { + match *self { + JointData::Root { + ref mut channels, .. + } => *channels = new_channels, + JointData::Child { + ref mut channels, .. + } => *channels = new_channels.iter().map(|c| *c).collect(), + } + } + + /// Returns a mutable reference to the `JointPrivateData` of the `JointData` if it + /// exists, or `None`. + #[inline] + pub(crate) fn private_data_mut(&mut self) -> Option<&mut JointPrivateData> { + match *self { + JointData::Child { + ref mut private, .. + } => Some(private), + _ => None, + } + } +} + +/// A string type for the `Joint` name. A `SmallVec` is used for +/// better data locality. +pub(crate) type JointNameInner = SmallVec<[u8; mem::size_of::()]>; + +/// Wrapper struct for the `Joint` name type. +#[derive(Clone, Default, Eq, Hash, Ord)] +pub(crate) struct JointName(pub(crate) JointNameInner); + +impl JointName { + pub(crate) fn as_bytes(&self) -> &[u8] { + self.as_ref() + } +} + +impl Deref for JointName { + type Target = JointNameInner; + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for JointName { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl> PartialEq for JointName { + #[inline] + fn eq(&self, rhs: &B) -> bool { + AsRef::<[u8]>::as_ref(self) == rhs.as_ref() + } +} + +impl> PartialOrd for JointName { + #[inline] + fn partial_cmp(&self, rhs: &B) -> Option { + AsRef::<[u8]>::as_ref(self).partial_cmp(rhs.as_ref()) + } +} + +macro_rules! impl_from { + () => {}; + (;) => {}; + ($t:ty; $($rest:tt)*) => { + impl From<$t> for JointName { + #[inline] + fn from(b: $t) -> Self { + JointName(b.bytes().collect()) + } + } + impl_from!($($rest)*); + } +} + +impl From for JointName { + #[inline] + fn from(s: CString) -> Self { + From::from(s.into_bytes()) + } +} + +impl From<&'_ CStr> for JointName { + #[inline] + fn from(s: &'_ CStr) -> Self { + From::from(s.to_bytes()) + } +} + +impl From> for JointName { + #[inline] + fn from(s: Vec) -> Self { + JointName(JointNameInner::from(s)) + } +} + +impl From<&'_ [u8]> for JointName { + #[inline] + fn from(s: &'_ [u8]) -> Self { + JointName(s.into_iter().copied().collect::()) + } +} + +impl_from! { + String; &'_ str; + BString; &'_ BStr; +} + +macro_rules! impl_as_ref { + ($( $t:ty { ref => $method:path, mut => $mut_method:path } )*) => { + $( + impl AsRef<$t> for JointName { + #[inline] + fn as_ref(&self) -> &$t { + $method(&self.0[..]) + } + } + impl AsMut<$t> for JointName { + #[inline] + fn as_mut(&mut self) -> &mut $t { + $mut_method(&mut self.0[..]) + } + } + )* + } +} + +impl_as_ref! { + BStr { ref => AsRef::::as_ref, mut => AsMut::::as_mut } + [u8] { ref => AsRef::<[u8]>::as_ref, mut => AsMut::<[u8]>::as_mut } +} + +impl From for JointName { + #[inline] + fn from(s: JointNameInner) -> Self { + JointName(s) + } +} + +impl From for JointNameInner { + #[inline] + fn from(j: JointName) -> Self { + j.0 + } +} + +impl fmt::Debug for JointName { + #[inline] + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(AsRef::::as_ref(self), fmtr) + } +} + +impl fmt::Display for JointName { + #[inline] + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(AsRef::::as_ref(self), fmtr) + } +} + +/// Data private to joints. +#[doc(hidden)] +#[derive(Clone, Eq, PartialEq)] +pub(crate) struct JointPrivateData { + /// Index of this `Joint` in the array. + pub(crate) self_index: usize, + /// The parent index in the array of `JointPrivateData`s in the `Bvh`. + pub(crate) parent_index: usize, + /// Depth of the `Joint`. A depth of `1` signifies a `Joint` attached to + /// the root. + pub(crate) depth: usize, +} + +impl JointPrivateData { + #[inline] + pub(crate) const fn new(self_index: usize, parent_index: usize, depth: usize) -> Self { + JointPrivateData { + self_index, + parent_index, + depth, + } + } + + #[inline] + pub(crate) const fn empty() -> Self { + Self::new(0, 0, 0) + } +} + +impl fmt::Debug for JointPrivateData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("JointPrivateData { .. }") + } +} + +/// An iterator over the `Joint`s of a `Bvh` skeleton. +pub struct Joints<'a> { + pub(crate) joints: &'a [JointData], + // pub(crate) motion_values: &'a [f32], + pub(crate) current_joint: usize, + pub(crate) from_child: Option, +} + +impl fmt::Debug for Joints<'_> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Joints { .. }") + } +} + +impl<'a> Joints<'a> { + /// Create a `Joints` iterator over all the `joints` in a `Bvh` file. + pub(crate) const fn iter_root(joints: &'a [JointData]) -> Self { + Joints { + joints, + // clips, + current_joint: 0, + from_child: None, + } + } + + /// Create a `Joints` iterator over all the child joints of `joint`. + /// + /// # Notes + /// + /// This function only iterates over the direct children of `joint`. If you + /// need to iterate through to the end sites of all children, you will + /// need to continually call `iter_children` on each `Joint` in the iterator. + pub(crate) fn iter_children(joint: &Joint<'a>) -> Self { + let first_child = joint + .joints + .iter() + .find(|jd| { + if let Some(p) = jd.private_data() { + p.parent_index == joint.index + } else { + false + } + }) + .map(JointData::index) + .unwrap(); + + Joints { + joints: joint.joints, + // clips: joint.clips, + current_joint: joint.data().index(), + from_child: Some(first_child), + } + } + + /// Finds the `Joint` named `joint_name`, or `None` if it doesn't exist. + #[inline] + pub fn find_by_name(&mut self, joint_name: &B) -> Option> + where + B: ?Sized + AsRef<[u8]>, + { + self.find(|b| b.data().name() == joint_name.as_ref()) + } + + #[allow(unused)] + pub(crate) fn nth_child(joint: &Joint<'a>, child: usize) -> Option { + Joints::iter_children(joint) + .nth(child) + .map(|joint| joint.index) + } +} + +impl<'a> Iterator for Joints<'a> { + type Item = Joint<'a>; + fn next(&mut self) -> Option { + if self.current_joint >= self.joints.len() { + return None; + } + + let joint = Some(Joint { + index: self.current_joint, + joints: self.joints, + }); + + if self.from_child.is_none() { + self.current_joint += 1; + } else { + unimplemented!() + } + + joint + } +} + +/// A mutable iterator over the `Joint`s of a `Bvh` skeleton. +#[allow(unused)] +pub struct JointsMut<'a> { + pub(crate) joints: &'a mut [JointData], + pub(crate) current_joint: usize, + pub(crate) from_child: Option, +} + +impl<'a> JointsMut<'a> { + pub(crate) fn iter_root(joints: &'a mut [JointData]) -> Self { + JointsMut { + joints, + // clips, + current_joint: 0, + from_child: None, + } + } +} + +impl<'a> Iterator for JointsMut<'a> { + type Item = JointMut<'a>; + #[inline] + fn next(&mut self) -> Option { + None + } +} + +impl fmt::Debug for JointsMut<'_> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("JointsMut { .. }") + } +} + +/* + +impl<'a> Iterator for JointsMut<'a> { + type Item = JointMut<'a>; + #[inline] + fn next(&mut self) -> Option { + self.joints.next().map(JointMut::from_joint) + } +} + +*/ + +/// A view of a joint which provides access to various relevant data. +pub struct Joint<'a> { + /// Index of the `Joint` in the skeleton. + pub(crate) index: usize, + /// `Joints` array which the joint is part of. + pub(crate) joints: &'a [JointData], +} + +impl PartialEq for Joint<'_> { + #[inline] + fn eq(&self, rhs: &Self) -> bool { + let (d0, d1) = (self.data(), rhs.data()); + PartialEq::eq(d0, d1) + } +} + +impl Joint<'_> { + /// Returns `true` if the `Joint` is the root `Joint`, or `false` if it isn't. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Hips + /// { + /// OFFSET 0.0 0.0 0.0 + /// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// End Site { + /// OFFSET 0.0 0.0 30.0 + /// } + /// } + /// MOTION + /// Frames: 0 + /// Frame Time: 0.0333333 + /// }; + /// + /// let root = bvh.root_joint().unwrap(); + /// assert!(root.is_root()); + /// ``` + #[inline] + pub const fn is_root(&self) -> bool { + self.data().is_root() + } + + /// Returns `true` if the `Joint` is a child `Joint`, or `false` if it isn't. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// let bvh = bvh! { + /// # HIERARCHY + /// # ROOT Base + /// # { + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// # JOINT End + /// # { + /// # OFFSET 0.0 0.0 15.0 + /// # CHANNELS 3 Zrotation Xrotation Yrotation + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 30.0 + /// # } + /// # } + /// # } + /// # MOTION + /// # Frames: 0 + /// # Frame Time: 0.0333333 + /// // bvh hierarchy unspecified .. + /// }; + /// + /// for joint in bvh.joints().skip(1) { + /// assert!(joint.is_child()); + /// } + /// ``` + #[inline] + pub const fn is_child(&self) -> bool { + self.data().is_child() + } + + /// Returns the name of the `JointData`. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Hips + /// { + /// OFFSET 0.0 0.0 0.0 + /// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// // ... + /// # End Site { + /// # OFFSET 0.0 0.0 30.0 + /// # } + /// } + /// MOTION + /// // ... + /// # Frames: 0 + /// # Frame Time: 0.0333333 + /// }; + /// + /// let root = bvh.root_joint().unwrap(); + /// assert_eq!(root.name(), b"Hips"); + /// ``` + #[inline] + pub fn name(&self) -> &[u8] { + self.data().name() + } + + /// Returns the offset of the `JointData` if it exists, or `None`. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Hips + /// { + /// OFFSET 1.2 3.4 5.6 + /// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// // ... + /// # End Site { + /// # OFFSET 0.0 0.0 30.0 + /// # } + /// } + /// MOTION + /// // ... + /// # Frames: 0 + /// # Frame Time: 0.0333333 + /// }; + /// + /// let root = bvh.root_joint().unwrap(); + /// assert_eq!(root.offset(), &[1.2, 3.4, 5.6]); + /// ``` + #[inline] + pub const fn offset(&self) -> &Offset { + self.data().offset() + } + + /// Returns the `end_site` position if this `Joint` has an end site, or `None` if + /// it doesn't. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Base + /// { + /// OFFSET 0.0 0.0 0.0 + /// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// JOINT Tip + /// { + /// OFFSET 0.0 0.0 5.0 + /// CHANNELS 3 Zrotation Xrotation Yrotation + /// End Site + /// { + /// OFFSET 5.4 3.2 1.0 + /// } + /// } + /// } + /// MOTION + /// // ... + /// # Frames: 0 + /// # Frame Time: 0.0333333 + /// }; + /// let mut joints = bvh.joints(); + /// let base = joints.next().unwrap(); + /// assert!(base.end_site().is_none()); + /// + /// let tip = joints.next().unwrap(); + /// assert_eq!(tip.end_site(), Some([5.4, 3.2, 1.0].into()).as_ref()); + /// ``` + #[inline] + pub const fn end_site(&self) -> Option<&Offset> { + self.data().end_site() + } + + /// Returns `true` if the `Joint` has an `end_site_offset`, or `false` if it doesn't. + #[inline] + pub const fn has_end_site(&self) -> bool { + self.end_site().is_some() + } + + /// Returns the ordered array of `Channel`s of this `JointData`. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::{bvh, ChannelType}; + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Hips + /// { + /// OFFSET 0.0 0.0 0.0 + /// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + /// // ... + /// # End Site { + /// # OFFSET 0.0 0.0 30.0 + /// # } + /// } + /// MOTION + /// // ... + /// # Frames: 0 + /// # Frame Time: 0.0333333 + /// }; + /// + /// let root = bvh.root_joint().unwrap(); + /// let expected_channels = &[ + /// ChannelType::PositionX, + /// ChannelType::PositionY, + /// ChannelType::PositionZ, + /// ChannelType::RotationZ, + /// ChannelType::RotationX, + /// ChannelType::RotationY, + /// ]; + /// + /// for (channel, &expected) in root + /// .channels() + /// .iter() + /// .map(|c| c.channel_type()) + /// .zip(expected_channels.iter()) + /// { + /// assert_eq!(channel, expected); + /// } + /// ``` + #[inline] + pub fn channels(&self) -> &[Channel] { + self.data().channels() + } + + /// Returns the index of this joint in the `Bvh`'s array of joints. + #[inline] + pub const fn index(&self) -> usize { + self.index + } + + /// Returns the index of this joint's parent in the `Bvh`'s array of joints, + /// or `None` if this `Joint` is the root joint. + #[inline] + pub fn parent_index(&self) -> Option { + self.data().parent_index() + } + + /// Return the parent `Joint` if it exists, or `None` if it doesn't. + #[inline] + pub fn parent(&self) -> Option> { + self.data().parent_index().map(|idx| Joint { + index: idx, + joints: self.joints, + }) + } + + /// Returns an iterator over the children of `self`. + #[inline] + pub fn children(&self) -> Joints<'_> { + Joints::iter_children(&self) + } + + /// Access a read-only view of the internal data of the `Joint`. + #[inline] + pub(crate) const fn data(&self) -> &JointData { + &self.joints[self.index] + } +} + +impl fmt::Debug for Joint<'_> { + #[inline] + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + fmtr.debug_struct("Joint") + .field("index", &self.index) + .field("data", &self.data()) + .finish() + } +} + +/// A view of a joint which provides mutable access. +pub struct JointMut<'a> { + /// Index of the `Joint` in the skeleton. + pub(crate) index: usize, + /// `Joints` array which the joint is part of. + pub(crate) joints: &'a mut [JointData], +} + +impl<'a> JointMut<'a> { + /* + /// Return the parent `Joint` if it exists, or `None` if it doesn't. + #[inline] + pub fn parent(&self) -> Option> { + self.data().parent_index().map(|idx| Joint { + self_index: idx, + skeleton: self.skeleton, + clips: self.clips, + }) + } + + pub fn + + /// Returns an iterator over the children of `self`. + #[inline] + pub fn children(&self) -> Joints<'_> { + Joints::iter_children(&self) + } + + /// Access a read-only view of the internal data of the `Joint`. + #[inline] + pub fn data(&self) -> &JointData { + &self.joints[self_index] + } + */ + /// Mutable access to the internal data of the `JointMut`. + #[inline] + pub(crate) fn data_mut(&mut self) -> &mut JointData { + &mut self.joints[self.index] + } +} diff --git a/studio-bvh/src/joint_cursor.rs b/studio-bvh/src/joint_cursor.rs new file mode 100644 index 0000000..45db9ac --- /dev/null +++ b/studio-bvh/src/joint_cursor.rs @@ -0,0 +1,42 @@ +use crate::Bvh; +use mint::Vector3; + +#[derive(Debug)] +pub struct JointCursor<'a> { + bvh: &'a mut Bvh, + index: usize, +} + +impl<'a> JointCursor<'a> { + #[inline] + pub const fn index(&self) -> usize { + self.index + } + + #[inline] + pub fn push_joints(&mut self) -> PushJointsChain<'a> { + PushJointsChain { cursor: self } + } +} + +#[derive(Debug)] +pub struct PushJointsChain<'a> { + cursor: &'a mut JointCursor<'a>, +} + +impl<'a> PushJointsChain<'a> { + pub fn push_joint, V: Into>>( + &mut self, + name: B, + offset: V, + ) -> Self { + todo!() + } +} + +impl<'a> From<&'a mut Bvh> for JointCursor<'a> { + #[inline] + fn from(bvh: &'a mut Bvh) -> Self { + Self { bvh, index: 0 } + } +} diff --git a/studio-bvh/src/lib.rs b/studio-bvh/src/lib.rs new file mode 100644 index 0000000..3b8cebe --- /dev/null +++ b/studio-bvh/src/lib.rs @@ -0,0 +1,944 @@ +#![warn(missing_docs)] +#![deny(bare_trait_objects, unsafe_code)] + +//! # About this library +//! +//! A small library for loading and manipulating BioVision motion files. +//! +//! ## The bvh file format +//! +//! The `Bvh` file format is comprised of two main sections: the 'Heirarchy' section, +//! which defines the joints of the skeleton, and the 'Motion' section, which defines +//! the motion values for each channel. +//! +//! This project contains some samples in the [`data` directory][`data` directory]. +//! +//! ### Heierarchy +//! +//! The 'Heirarchy' section defines the skeleton as a tree of joints, where there is +//! a single root joint, and a chain of child joints extending out from each joint, +//! terminated by an 'End Site' section. +//! +//! Each joint has: +//! +//! * A list of channels, which are the degrees of freedom in which the joint may move. +//! Channels are listed in the order in which the transformation should be applied +//! to the global transform for the root. +//! * An offset, which is the vector distance from the parent joint. +//! * An optional end site, which is used to cap off a chain of joints. This is only used +//! to calculate the length of the final bone in the chain. +//! +//! ```text +//! HEIRARCHY +//! ROOT +//! { +//! OFFSET +//! CHANNELS Xposition Yposition +//! JOINT +//! { +//! OFFSET +//! CHANNELS +//! JOINT +//! { +//! OFFSET +//! CHANNELS +//! JOINT +//! { +//! OFFSET ... +//! CHANNELS ... +//! End Site +//! { +//! OFFSET +//! } +//! } +//! ... More child joints +//! } +//! JOINT +//! { +//! OFFSET +//! CHANNELS +//! ... More child joints +//! } +//! ... More child joints +//! } +//! ... More child joints +//! } +//! ``` +//! +//! Note that the bvh data is defined in terms of a right-handed coordinate system, where +//! the positive y-axis is the up vector. +//! +//! ### Motion +//! +//! The `MOTION` section of the bvh file records the number of frames, the frame time, and +//! defines the full range of motions for each channel, frame by frame. +//! +//! ```text +//! MOTION +//! Frames: +//! Frame Time: +//! ... +//! ... +//! ... +//! â‹® +//! ``` +//! +//! The frame time is recorded in seconds, and tells the animation system how long each frame +//! of the animation should last for. This value is usually around 0.033333333, which is close +//! to 30 frames per second. +//! +//! The list of motion values is a matrix, where each row represents a frame. Each column of +//! the row represents a transformation around the channel axis - for example a motion value +//! of 130.0 for an `Xposition` channel would correspond to a rotation of 130.0 degrees around +//! the x-axis. +//! +//! Note that rotations are conventionally in degrees, although it will be up to your application +//! how to interpret each motion's value. +//! +//! ## Using this library. +//! +//! ### Creating a [`Bvh`][`Bvh`] struct: +//! +//! There are a few ways to create a [`Bvh`][`Bvh`] struct: +//! +//! * You can use the [`from_reader`][`from_reader`] function, which will parse a `BufRead` +//! as a bvh file. The [`from_bytes`][`from_bytes`] function is a convenient wrapper function +//! to parse an in-memory slice of bytes as a `bvh` file. Note that the file does not need to +//! be strictly UTF-8, although it should be an ascii-compatible encoding. These functions are +//! also available as associated methods on the `Bvh` type directly as [`Bvh::from_reader`] +//! [`Bvh::from_reader`] and [`Bvh::from_bytes`][`Bvh::from_bytes`] +//! +//! * You can use the [`bvh!`][`bvh!`] macro to construct a [`Bvh`][`Bvh`] instance in your source files +//! using the same syntax as you would use for a standard bvh file. +//! +//! * You can use the [`builder`][`builder`] module to dynamically construct a bvh. This is useful +//! for converting data from other formats into a [`Bvh`][`Bvh`] struct. +//! +//! * You can create an empty [`Bvh`][`Bvh`] using the [`Bvh::new`][`Bvh::new`] or [`Default::default`] +//! [`Default::default`] methods. +//! +//! ### Other operations: +//! +//! Once you have a valid [`Bvh`][`Bvh`] struct, there are a number of ways you can inspect and +//! manipulate it: +//! +//! * The [`Bvh::joints`][`Bvh::joints`] method can be used to iterate through each [`Joint`][`Joint`] +//! of the [`Bvh`][`Bvh`]. Each [`Joint`][`Joint`] can be inspected through its [`JointData`] +//! [`JointData`], which can be obtained with the [`Joint::data`][`Joint::data`] method. +//! +//! * The [`Bvh::frames`][`Bvh::frames`] method returns a [`Frames`][`Frames`] iterator over each +//! frame of the animation. A [`Frame`][`Frame`] can only be indexed by a [`Channel`][`Channel`] +//! belonging to an associated [`Joint`][`Joint`] of the [`Bvh`][`Bvh`], although you can convert +//! it into an [`&[`][`slice`][`f32`][`f32`][`]`][`slice`] using the [`Frame::as_slice`][`Frame::as_slice`] method. +//! +//! * You can serialise the [`Bvh`][`Bvh`] into a [`Write`][`Write`] type using the [`Bvh::write_to`] +//! [`Bvh::write_to`] method. There is also the [`Bvh::to_bstring`][`Bvh::to_bstring`] method, which +//! converts the [`Bvh`][`Bvh`] into a [`BString`][`BString`]. Various aspects of the formatting +//! can be customised using the [`WriteOptions`][`WriteOptions`] type, such as the line termination +//! style, indentation method, and floating point accuracy. +//! +//! ## Examples +//! +//! This library comes with some example applications, which can be viewed on [Github][Github]. +//! +//! ## Other resources +//! +//! * More information on this file format can be found [here][bvh_html]. +//! * A large library of bvh files is freely available from [CMU's motion capture database] +//! [CMU's motion capture database]. +//! +//! [`data` directory]: https://github.com/burtonageo/bvh_anim/tree/master/data +//! [`bvh`]: struct.Bvh.html +//! [`from_reader`]: fn.from_reader.html +//! [`from_bytes`]: fn.from_bytes.html +//! [`Bvh::from_reader`]: struct.Bvh.html#method.from_reader +//! [`Bvh::from_bytes`]: struct.Bvh.html#method.from_bytes +//! [`bvh!`]: macro.bvh.html +//! [`builder`]: builder/index.html +//! [`Bvh::new`]: struct.Bvh.html#method.new +//! [`Default::default`]: https://doc.rust-lang.org/stable/std/default/trait.Default.html#tymethod.default +//! [`Bvh::joints`]: struct.Bvh.html#method.joints +//! [`Joint`]: struct.Joint.html +//! [`JointData`]: enum.JointData.html +//! [`Joint::data`]: struct.Joint.html#method.data +//! [`Bvh::frames`]: struct.Bvh.html#method.frames +//! [`Frames`]: struct.Frames.html +//! [`Frame`]: struct.Frame.html +//! [`slice`]: https://doc.rust-lang.org/std/primitive.slice.html +//! [`f32`]: https://doc.rust-lang.org/stable/std/primitive.f32.html +//! [`Channel`]: struct.Channel.html +//! [`Frame::as_slice`]: struct.Frame.html#method.as_slice +//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +//! [`Bvh::write_to`]: struct.Bvh.html#method.write_to +//! [`Bvh::to_bstring`]: struct.Bvh.html#method.to_bstring +//! [`BString`]: https://docs.rs/bstr/0.1.2/bstr/struct.BString.html +//! [`WriteOptions`]: write/struct.WriteOptions.html +//! [Github]: https://github.com/burtonageo/bvh_anim/tree/master/examples +//! [bvh_html]: https://research.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html +//! [CMU's motion capture database]: https://sites.google.com/a/cgspeed.com/cgspeed/motion-capture/daz-friendly-release + +#[macro_use] +mod macros; + +pub mod errors; + +pub mod write; + +mod frame_cursor; +mod frame_iter; +pub mod joint; +mod parse; + +use crate::{ + errors::{LoadError, ParseChannelError}, + frames::{FrameCursor, Frames, FramesMut}, + joint::{JointData, Offset}, +}; +use bstr::{ + io::{BufReadExt, ByteLines}, + BStr, ByteSlice, +}; +use std::{ + convert::TryFrom, + fmt, + io::{self, Cursor, Write}, + iter::Enumerate, + mem, + num::NonZeroUsize, + str::{self, FromStr}, + time::Duration, +}; + +pub mod frames { + //! This module contains types and functions used for accessing and modifying + //! frame data. + + pub use crate::frame_cursor::FrameCursor; + pub use crate::frame_iter::{Frame, FrameIndex, FrameMut, Frames, FramesMut}; +} + +pub use joint::{Joint, JointMut, Joints, JointsMut}; +#[doc(hidden)] +pub use macros::BvhLiteralBuilder; + +struct CachedEnumerate { + iter: Enumerate, + last_enumerator: Option, +} + +impl CachedEnumerate { + #[inline] + fn new(iter: Enumerate) -> Self { + CachedEnumerate { + iter, + last_enumerator: None, + } + } + + #[inline] + fn last_enumerator(&self) -> Option { + self.last_enumerator + } +} + +impl Iterator for CachedEnumerate { + type Item = as Iterator>::Item; + #[inline] + fn next(&mut self) -> Option { + let (curr, item) = self.iter.next()?; + self.last_enumerator = Some(curr); + Some((curr, item)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +type EnumeratedLines<'a> = CachedEnumerate>; + +impl EnumeratedLines<'_> { + pub(crate) fn next_non_empty_line(&mut self) -> Option<::Item> { + let mut next = self.next(); + loop { + match next { + None => return None, + Some((idx, result)) => { + let string = match result { + Ok(s) => s, + Err(e) => return Some((idx, Err(e))), + }; + if string.trim().is_empty() { + next = self.next() + } else { + return Some((idx, Ok(string))); + } + } + } + } + } +} + +/// Loads the `Bvh` from the `reader`. +#[inline] +pub fn from_reader(data: R) -> Result { + Bvh::from_reader(data) +} + +/// Parse a sequence of bytes as if it were an in-memory `Bvh` file. +/// +/// # Examples +/// +/// ``` +/// # use bvh_anim::{self, from_bytes}; +/// let bvh_string = br#" +/// HIERARCHY +/// ROOT Hips +/// { +/// OFFSET 0.0 0.0 0.0 +/// CHANNELS 3 Xposition Yposition Zposition +/// End Site +/// { +/// OFFSET 0.0 0.0 0.0 +/// } +/// } +/// MOTION +/// Frames: 1 +/// Frame Time: 0.033333333 +/// 0.0 0.0 0.0 +/// "#; +/// +/// let bvh = from_bytes(&bvh_string[..])?; +/// # let _ = bvh; +/// # Result::<(), bvh_anim::errors::LoadError>::Ok(()) +/// ``` +#[inline] +pub fn from_bytes>(bytes: B) -> Result { + Bvh::from_bytes(bytes) +} + +/// Parse a `str` as if it were an in-memory `Bvh` file. +/// +/// # Examples +/// +/// ``` +/// # use bvh_anim::{self, from_str}; +/// let bvh_string = " +/// HIERARCHY +/// ROOT Hips +/// { +/// OFFSET 0.0 0.0 0.0 +/// CHANNELS 3 Xposition Yposition Zposition +/// End Site +/// { +/// OFFSET 0.0 0.0 0.0 +/// } +/// } +/// MOTION +/// Frames: 1 +/// Frame Time: 0.033333333 +/// 0.0 0.0 0.0 +/// "; +/// +/// let bvh = from_str(bvh_string)?; +/// # let _ = bvh; +/// # Result::<(), bvh_anim::errors::LoadError>::Ok(()) +/// ``` +#[inline] +pub fn from_str(string: &str) -> Result { + Bvh::from_str(string) +} + +/// A complete `bvh` file. +/// +/// See the [module documentation](index.html#using-this-library) +/// for more information. +#[derive(Clone, Debug, PartialEq)] +pub struct Bvh { + /// The list of joints. If the root joint exists, it is always at + /// index `0`. + joints: Vec, + /// The motion values of the `Frame`. + motion_values: Vec, + /// The number of `Channel`s in the bvh. + num_channels: usize, + /// The total time it takes to play one frame. + frame_time: Duration, +} + +impl Bvh { + /// Create an empty `Bvh`. + #[inline] + pub const fn new() -> Self { + Self { + joints: Vec::new(), + motion_values: Vec::new(), + num_channels: 0, + frame_time: Duration::from_secs(0), + } + } + + /// Parse a sequence of bytes as if it were an in-memory `Bvh` file. + /// + /// # Examples + /// + /// ``` + /// # use bvh_anim::{self, Bvh}; + /// let bvh_string = br#" + /// HIERARCHY + /// ROOT Hips + /// { + /// OFFSET 0.0 0.0 0.0 + /// CHANNELS 3 Xposition Yposition Zposition + /// End Site + /// { + /// OFFSET 0.0 0.0 0.0 + /// } + /// } + /// MOTION + /// Frames: 1 + /// Frame Time: 0.033333333 + /// 0.0 0.0 0.0 + /// "#; + /// + /// let bvh = Bvh::from_bytes(&bvh_string[..])?; + /// # let _ = bvh; + /// # Result::<(), bvh_anim::errors::LoadError>::Ok(()) + /// ``` + #[inline] + pub fn from_bytes>(bytes: B) -> Result { + Bvh::from_reader(Cursor::new(bytes)) + } + + /// Loads the `Bvh` from the `reader`. + #[inline] + pub fn from_reader(mut reader: R) -> Result { + #[inline(never)] + fn from_reader_(reader: &mut dyn BufReadExt) -> Result { + let mut lines = CachedEnumerate::new(reader.byte_lines().enumerate()); + + let mut bvh = Bvh::default(); + + bvh.read_joints(&mut lines)?; + bvh.read_motion(&mut lines)?; + + Ok(bvh) + } + + from_reader_(reader.by_ref()) + } + + /// Writes the `Bvh` using the `bvh` file format to the `writer`, with + /// the default formatting options. + /// + /// # Notes + /// + /// To customise the formatting, see the [`WriteOptions`][`WriteOptions`] type. + /// + /// # Examples + /// + /// ```no_run + /// # use bvh_anim::bvh; + /// # use std::io; + /// # use std::fs::File; + /// let bvh = bvh! { + /// // fields unspecified + /// }; + /// + /// let mut out_file = File::create("./out_file.bvh")?; + /// bvh.write_to(&mut out_file)?; + /// # Result::<(), io::Error>::Ok(()) + /// ``` + /// + /// [`WriteOptions`]: write/struct.WriteOptions.html + #[inline] + pub fn write_to(&self, writer: &mut W) -> io::Result<()> { + write::WriteOptions::default().write(self, writer) + } + + /// Writes the `Bvh` using the `bvh` file format into a `Vec` with + /// the default formatting options. + /// + /// The data is guaranteed to be `ASCII`, except for the joint names, which + /// may contain arbitrary bytes. + /// + /// # Notes + /// + /// To customise the formatting, see the [`WriteOptions`][`WriteOptions`] type. + /// + /// [`WriteOptions`]: write/struct.WriteOptions.html + #[inline] + pub fn to_string(&self) -> Vec { + write::WriteOptions::default().write_to_string(self) + } + + /// Returns the root joint if it exists, or `None` if the skeleton is empty. + /// + /// # Examples + /// + /// ``` + /// # use bvh_anim::{Bvh, bvh}; + /// let bvh = Bvh::new(); + /// assert!(bvh.root_joint().is_none()); + /// + /// let bvh = bvh! { + /// HIERARCHY + /// ROOT Hips + /// { + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 0 + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 0.0 + /// # } + /// // Joints... + /// } + /// MOTION + /// # Frames: 0 + /// # Frame Time: 0.033333333 + /// // Frames... + /// }; + /// + /// assert!(bvh.root_joint().is_some()); + /// ``` + #[inline] + pub fn root_joint(&self) -> Option> { + if self.joints.is_empty() { + None + } else { + Some(Joint { + index: 0, + joints: &self.joints[..], + }) + } + } + + /// Returns an iterator over all the `Joint`s in the `Bvh`. + #[inline] + pub fn joints(&self) -> Joints<'_> { + Joints::iter_root(&self.joints[..]) + } + + /// Returns a mutable iterator over all the joints in the `Bvh`. + #[inline] + pub fn joints_mut(&mut self) -> JointsMut<'_> { + JointsMut::iter_root(&mut self.joints[..]) + } + + /// Returns a `Frames` iterator over the frames of the bvh. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::Bvh; + /// # fn use_bvh(bvh: Bvh) { + /// let bvh = // ... + /// # bvh; + /// for frame in bvh.frames() { + /// // use frame... + /// # let _ = frame; + /// } + /// # } // fn use_bvh() + /// ``` + #[inline] + pub fn frames(&self) -> Frames<'_> { + Frames { + chunks: NonZeroUsize::new(self.num_channels).map(|_| { + self.motion_values + .as_slice() + .chunks_exact(self.num_channels) + }), + } + } + + /// Returns a mutable iterator over the frames of the bvh. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::Bvh; + /// # fn use_bvh(bvh: Bvh) { + /// let mut bvh = // ... + /// # bvh; + /// for frame in bvh.frames_mut() { + /// // use frame... + /// # let _ = frame; + /// } + /// # } // fn use_bvh() + /// ``` + #[inline] + pub fn frames_mut(&mut self) -> FramesMut<'_> { + FramesMut { + chunks: NonZeroUsize::new(self.num_channels).map(move |_| { + self.motion_values + .as_mut_slice() + .chunks_exact_mut(self.num_channels) + }), + } + } + + /// Removes all frame data from the `Bvh`, returning the previous frames. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::bvh; + /// let mut bvh = bvh! { + /// HIERARCHY + /// // hierarchy omitted... + /// # ROOT Hips + /// # { + /// # OFFSET 0.0 0.0 0.0 + /// # CHANNELS 3 Xposition Yposition Zposition + /// # End Site + /// # { + /// # OFFSET 0.0 0.0 0.0 + /// # } + /// # } + /// MOTION + /// Frames: 1 + /// Frame Time: 0.033333333 + /// 0.0 0.0 0.0 + /// }; + /// + /// let frames = bvh.extract_frames(); + /// assert_eq!(bvh.num_frames(), 0); + /// assert_eq!(bvh.frames().len(), 0); + /// assert_eq!(frames, &[0.0, 0.0, 0.0]); + /// ``` + #[inline] + pub fn extract_frames(&mut self) -> Vec { + mem::take(&mut self.motion_values) + } + + /// Get the number of frames in the `Bvh`. + #[inline] + #[deprecated(note = "Please use `frames().len()` instead,")] + pub fn num_frames(&self) -> usize { + self.frames().len() + } + + /// Get the number of channels in the `Bvh`. + #[inline] + pub const fn num_channels(&self) -> usize { + self.num_channels + } + + /// Get the duration each frame should play for in the `Bvh`. + /// + /// # Example + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// # use std::time::Duration; + /// let bvh = bvh! { + /// # HIERARCHY + /// # MOTION + /// # Frames: 0 + /// // ... + /// Frame Time: 1 + /// // ... + /// }; + /// + /// assert_eq!(*bvh.frame_time(), Duration::from_secs(1)); + /// # } // fn main() + /// ``` + #[inline] + pub const fn frame_time(&self) -> &Duration { + &self.frame_time + } + + /// Set the duration each frame should play for in the `Bvh` to `new_frame_time`. + /// + /// # Example + /// + /// ``` + /// # fn main() { + /// # use bvh_anim::bvh; + /// # use std::time::Duration; + /// let mut bvh = bvh! { + /// // ... + /// }; + /// + /// let new_frame_time = Duration::from_secs(21); + /// bvh.set_frame_time(new_frame_time); + /// + /// assert_eq!(*bvh.frame_time(), new_frame_time); + /// # } // fn main() + /// ``` + #[inline] + pub fn set_frame_time(&mut self, new_frame_time: Duration) { + self.frame_time = new_frame_time; + } + + /// Create a new `FrameCursor` for inserting and removing frames. + #[inline] + pub fn frame_cursor(&mut self) -> FrameCursor<'_> { + From::from(self) + } +} + +impl Default for Bvh { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl FromStr for Bvh { + type Err = LoadError; + #[inline] + fn from_str(string: &str) -> Result { + Bvh::from_bytes(string.as_bytes()) + } +} + +impl TryFrom<&'_ str> for Bvh { + type Error = LoadError; + #[inline] + fn try_from(string: &'_ str) -> Result { + FromStr::from_str(string) + } +} + +impl TryFrom<&'_ BStr> for Bvh { + type Error = LoadError; + #[inline] + fn try_from(string: &'_ BStr) -> Result { + Bvh::from_bytes(string.as_bytes()) + } +} + +impl TryFrom<&'_ [u8]> for Bvh { + type Error = LoadError; + #[inline] + fn try_from(bytes: &'_ [u8]) -> Result { + Bvh::from_bytes(bytes) + } +} + +/// A `Channel` composed of a `ChannelType` and an index into the +/// corresponding motion data. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Channel { + /// The type of the `Channel`. + channel_type: ChannelType, + /// The index into the `Frame` which corresponds to this `Channel`. + motion_index: usize, +} + +impl Channel { + #[inline] + const fn new(channel_type: ChannelType, motion_index: usize) -> Self { + Channel { + channel_type, + motion_index, + } + } + + /// Returns the `ChannelType` to which this `Channel` corresponds. + #[inline] + pub const fn channel_type(&self) -> ChannelType { + self.channel_type + } + + /// Returns the index of the motion value to which this `Channel` corresponds. + #[inline] + pub const fn motion_index(&self) -> usize { + self.motion_index + } +} + +/// The available degrees of freedom along which a `Joint` may be manipulated. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum ChannelType { + /// Can be translated along the `x` axis. + PositionX, + /// Can be translated along the `y` axis. + PositionY, + /// Can be translated along the `z` axis. + PositionZ, + /// Can be rotated along the `x` axis. + RotationX, + /// Can be rotated along the `y` axis. + RotationY, + /// Can be rotated along the `z` axis. + RotationZ, +} + +impl ChannelType { + /// Attempt to parse a bvh channel byte string into a `ChannelType`. + /// Returns `Err` if the string cannot be parsed. + /// + /// # Examples + /// + /// ``` + /// # use bvh_anim::ChannelType; + /// assert_eq!( + /// ChannelType::from_bytes("Xrotation").unwrap(), + /// ChannelType::RotationX); + /// + /// let err = ChannelType::from_bytes("Hello").unwrap_err(); + /// assert_eq!(err.into_inner(), "Hello"); + /// ``` + #[inline] + pub fn from_bytes(s: &B) -> Result + where + B: AsRef<[u8]> + ?Sized, + { + let s = s.as_ref(); + match s { + b"Xrotation" => Ok(ChannelType::RotationX), + b"Yrotation" => Ok(ChannelType::RotationY), + b"Zrotation" => Ok(ChannelType::RotationZ), + + b"Xposition" => Ok(ChannelType::PositionX), + b"Yposition" => Ok(ChannelType::PositionY), + b"Zposition" => Ok(ChannelType::PositionZ), + + _ => Err(ParseChannelError::from(s)), + } + } + + /// Returns `true` if this channel corresponds to a rotational + /// transform, otherwise `false`. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::ChannelType; + /// let channel_type = ChannelType::RotationX; + /// assert!(channel_type.is_rotation()); + /// ``` + #[inline] + pub const fn is_rotation(&self) -> bool { + match *self { + ChannelType::RotationX | ChannelType::RotationY | ChannelType::RotationZ => true, + _ => false, + } + } + + /// Returns `true` if this channel corresponds to a positional + /// transform, otherwise `false`. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::ChannelType; + /// let channel_type = ChannelType::PositionZ; + /// assert!(channel_type.is_position()); + /// ``` + #[inline] + pub const fn is_position(&self) -> bool { + !self.is_rotation() + } + + /// Get the `Axis` about which this `Channel` transforms. + /// + /// # Example + /// + /// ``` + /// # use bvh_anim::{Axis, ChannelType}; + /// let channel_type = ChannelType::PositionX; + /// assert_eq!(channel_type.axis(), Axis::X); + /// ``` + #[inline] + pub const fn axis(&self) -> Axis { + match *self { + ChannelType::RotationX | ChannelType::PositionX => Axis::X, + ChannelType::RotationY | ChannelType::PositionY => Axis::Y, + ChannelType::RotationZ | ChannelType::PositionZ => Axis::Z, + } + } + + /// Returns the string representation of the `ChannelType`. + #[inline] + pub const fn as_str(&self) -> &'static str { + match *self { + ChannelType::RotationX => "Xrotation", + ChannelType::RotationY => "Yrotation", + ChannelType::RotationZ => "Zrotation", + + ChannelType::PositionX => "Xposition", + ChannelType::PositionY => "Yposition", + ChannelType::PositionZ => "Zposition", + } + } +} + +impl TryFrom<&'_ [u8]> for ChannelType { + type Error = ParseChannelError; + #[inline] + fn try_from(string: &[u8]) -> Result { + ChannelType::from_bytes(string) + } +} + +impl TryFrom<&'_ str> for ChannelType { + type Error = ParseChannelError; + #[inline] + fn try_from(string: &str) -> Result { + ChannelType::from_bytes(string.as_bytes()) + } +} + +impl FromStr for ChannelType { + type Err = ParseChannelError; + #[inline] + fn from_str(s: &str) -> Result { + ChannelType::from_bytes(s) + } +} + +impl fmt::Display for ChannelType { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// An enum which represents an axis along a direction in 3D space. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Axis { + /// `x` axis. + X, + /// `y` axis. + Y, + /// `z` axis. + Z, +} + +impl Axis { + /// Returns the `Offset` which represents the axis. + /// + /// # Examples + /// + /// ``` + /// # use bvh_anim::Axis; + /// assert_eq!(Axis::X.vector(), [1.0, 0.0, 0.0]); + /// assert_eq!(Axis::Y.vector(), [0.0, 1.0, 0.0]); + /// assert_eq!(Axis::Z.vector(), [0.0, 0.0, 1.0]); + /// ``` + #[inline] + pub fn vector(&self) -> Offset { + match *self { + Axis::X => [1.0, 0.0, 0.0], + Axis::Y => [0.0, 1.0, 0.0], + Axis::Z => [0.0, 0.0, 1.0], + } + } +} + +impl fmt::Display for Axis { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match *self { + Axis::X => "x", + Axis::Y => "y", + Axis::Z => "z", + }; + f.write_str(s) + } +} diff --git a/studio-bvh/src/macros.rs b/studio-bvh/src/macros.rs new file mode 100644 index 0000000..fef8550 --- /dev/null +++ b/studio-bvh/src/macros.rs @@ -0,0 +1,611 @@ +use crate::{ + joint::{JointData, Offset}, + Bvh, Channel, ChannelType, +}; + +#[doc(hidden)] +#[macro_export] +macro_rules! match_channels { + ($builder:ident; ) => { + }; + ($builder:ident; Xposition $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::PositionX); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:ident; Yposition $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::PositionY); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:ident; Zposition $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::PositionZ); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:ident; Xrotation $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::RotationX); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:ident; Yrotation $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::RotationY); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:ident; Zrotation $($rest:ident)*) => { + $builder.push_channel($crate::ChannelType::RotationZ); + $crate::match_channels!($builder; $($rest)*); + }; + ($builder:expr; $($other:tt)*) => { + compile_error!("Unknown tokens"); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! parse_offset { + ($builder:ident ($ofst_x:literal $ofst_y:literal $ofst_z:literal)) => { + let offset = [f32::from($ofst_x), f32::from($ofst_y), f32::from($ofst_z)]; + $builder.push_joint_offset(offset.into(), false); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! parse_joints_internal { + ($builder:ident ( + $( + JOINT $joint_nm:ident + { + $( $children:tt )* + } + )* + )) => { + $( + $builder.push_joint(stringify!($joint_nm)); + + $builder.current_depth += 1; + $crate::parse_joints_internal!($builder ( $( $children )* )); + $builder.current_depth -= 1; + )* + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 0 + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 1 $ch0:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; $ch0); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 2 $ch0:ident $ch1:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; $ch0 $ch1); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 3 $ch0:ident $ch1:ident $ch2:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; $ch0 $ch1 $ch2); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 4 $ch0:ident $ch1:ident $ch2:ident $ch3:ident + $($rest:tt)* + )) => { + bvh_anim$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + bvh_anim$crate::match_channels!($builder ; $ch0 $ch1 $ch2 $ch3); + bvh_anim$crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 5 + $ch0:ident + $ch1:ident + $ch2:ident + $ch3:ident + $ch4:ident + $($rest:tt)* + )) => { + bvh_anim::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + bvh_anim::match_channels!($builder ; $ch0 $ch1 $ch2 $ch3 $ch4); + bvh_anim::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 6 + $ch0:ident + $ch1:ident + $ch2:ident + $ch3:ident + $ch4:ident + $ch5:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; + $ch0 $ch1 $ch2 $ch3 $ch4 $ch5); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + CHANNELS 7 + $ch0:ident + $ch1:ident + $ch2:ident + $ch3:ident + $ch4:ident + $ch5:ident + $ch6:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; + $ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 8 + $ch0:ident + $ch1:ident + $ch2:ident + $ch3:ident + $ch4:ident + $ch5:ident + $ch6:ident + $ch7:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; + $ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6 $ch7); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS 9 + $ch0:ident + $ch1:ident + $ch2:ident + $ch3:ident + $ch4:ident + $ch5:ident + $ch6:ident + $ch7:ident + $ch8:ident + $($rest:tt)* + )) => { + $crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z)); + $crate::match_channels!($builder ; + $ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6 $ch7 $ch8); + $crate::parse_joints_internal!($builder ( $($rest)* )); + }; + + ($builder:ident ( + OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal + CHANNELS $unsupported:literal $($rest:tt)* + )) => { + compile_error!("No more than 9 channels supported in CHANNELS statement"); + }; + + ($builder:ident ( + End Site + { + OFFSET $end_x:literal $end_y:literal $end_z:literal + } + )) => { + let offset = [ + f32::from($end_x), + f32::from($end_y), + f32::from($end_z), + ]; + + $builder.push_joint_offset(offset.into() , true); + }; +} + +/// Create a new [`Bvh`][`Bvh`] object using a macro literal. Useful for +/// testing. +/// +/// # Notes +/// +/// If you have a very complex `Bvh` file with a large number of joints and frames, then +/// this macro will scale badly to it, and compilation time will suffer. +/// +/// # Example +/// +/// ``` +/// # use bvh_anim::bvh; +/// let simple_skeleton = bvh! { +/// HIERARCHY +/// ROOT Base +/// { +/// OFFSET 0.0 0.0 0.0 +/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation +/// JOINT Middle1 +/// { +/// OFFSET 0.0 0.0 15.0 +/// CHANNELS 3 Zrotation Xrotation Yrotation +/// JOINT Tip1 +/// { +/// OFFSET 0.0 0.0 30.0 +/// CHANNELS 3 Zrotation Xrotation Yrotation +/// End Site +/// { +/// OFFSET 0.0 0.0 45.0 +/// } +/// } +/// } +/// JOINT Middle2 +/// { +/// OFFSET 0.0 15.0 0.0 +/// CHANNELS 3 Zrotation Xrotation Yrotation +/// JOINT Tip2 +/// { +/// OFFSET 0.0 30.0 0.0 +/// CHANNELS 3 Zrotation Xrotation Yrotation +/// End Site +/// { +/// OFFSET 0.0 45.0 0.0 +/// } +/// } +/// } +/// } +/// +/// MOTION +/// Frames: 3 +/// // Time in seconds. +/// Frame Time: 0.033333333333 +/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 +/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 +/// }; +/// ``` +/// +/// You can use the `bvh` macro to create empty `Bvh` instances: +/// +/// ``` +/// # use bvh_anim::bvh; +/// let empty = bvh!{}; +/// +/// let empty = bvh! { +/// HIERARCHY +/// MOTION +/// }; +/// +/// let empty = bvh! { +/// HIERARCHY +/// MOTION +/// Frames: 0 +/// }; +/// ``` +/// +/// [`bvh`]: struct.Bvh.html +#[macro_export] +macro_rules! bvh { + () => { + $crate::Bvh::default() + }; + + ( + HIERARCHY + MOTION + ) => { + $crate::Bvh::default() + }; + + ( + HIERARCHY + MOTION + Frames: 0 + ) => { + $crate::Bvh::default() + }; + + ( + HIERARCHY + MOTION + Frames: 0 + Frame Time: $frame_time:literal + ) => { + { + use std::time::Duration; + let mut bvh = $crate::Bvh::default(); + bvh.set_frame_time(Duration::from_secs_f64(f64::from($frame_time))); + bvh + } + }; + + ( + HIERARCHY + ROOT $root_name:ident + { + $( $joints:tt )* + } + MOTION + Frames: 0 + Frame Time: $frame_time:literal + ) => { + { + use std::time::Duration; + use $crate::parse_joints_internal; + + let mut builder = $crate::BvhLiteralBuilder::default(); + builder.push_root(stringify!($root_name)); + + builder.current_depth += 1; + parse_joints_internal!(builder ($($joints)*)); + builder.current_depth -= 1; + + builder.set_num_channels(); + builder.bvh.set_frame_time(Duration::from_secs_f64(f64::from($frame_time))); + + builder.bvh + } + }; + + ( + HIERARCHY + ROOT $root_name:ident + { + $( $joints:tt )* + } + MOTION + Frames: $num_frames:literal + Frame Time: $frame_time:literal + $( + $motion:literal + )+ + ) => { + { + use $crate::bvh; + + let mut new_bvh = bvh! { + HIERARCHY + ROOT $root_name + { + $( $joints )* + } + MOTION + Frames: 0 + Frame Time: $frame_time + }; + + let num_channels = new_bvh.num_channels(); + + new_bvh + .frame_cursor() + .try_insert_frames([ $( f32::from($motion) ),+ ].chunks(num_channels)) + .expect("Could not create bvh type from macro literal"); + + assert_eq!(new_bvh.frames().len(), $num_frames as usize); + + new_bvh + } + }; +} + +// @TODO: refactor this into a general `Builder` so that we have an +// easy interface to get other animation/skeleton data into a `bvh` +// file. +/// Helper struct to build a `Bvh` from the macro without exposing +/// too many internals. +#[doc(hidden)] +#[derive(Default)] +pub struct BvhLiteralBuilder { + pub bvh: Bvh, + pub current_channel_index: usize, + pub current_depth: usize, + pub current_index: usize, +} + +#[doc(hidden)] +impl BvhLiteralBuilder { + pub fn push_root(&mut self, name: &str) { + let mut root = JointData::empty_root(); + root.set_name(name); + self.bvh.joints.push(root); + self.current_index += 1; + } + + pub fn push_joint(&mut self, name: &str) { + // @TODO: make this shared + #[inline] + fn get_parent_index(joints: &[JointData], for_depth: usize) -> usize { + joints + .iter() + .rev() + .find(|jd| jd.depth() == for_depth.saturating_sub(1)) + .and_then(|jd| jd.private_data().map(|p| p.self_index)) + .unwrap_or(0) + } + + let idx = self.current_index; + let dpth = self.current_depth; + let parent = get_parent_index(&self.bvh.joints[..], dpth); + + let mut joint = JointData::empty_child(); + joint.set_name(name); + if let Some(ref mut private) = joint.private_data_mut() { + private.self_index = idx; + private.depth = dpth; + private.parent_index = parent; + }; + + self.bvh.joints.push(joint); + + self.current_index += 1; + } + + pub fn push_channel(&mut self, channel: ChannelType) { + let channel = Channel::new(channel, self.current_channel_index); + self.last_joint().map(|joint| match *joint { + JointData::Root { + ref mut channels, .. + } => { + channels.push(channel); + } + JointData::Child { + ref mut channels, .. + } => { + channels.push(channel); + } + }); + self.current_channel_index += 1; + } + + pub fn push_joint_offset(&mut self, offset: Offset, is_end_site: bool) { + self.last_joint().map(|joint| { + joint.set_offset(offset, is_end_site); + }); + } + + #[inline] + pub fn set_num_channels(&mut self) { + self.bvh.num_channels = self.current_channel_index; + } + + #[inline] + fn last_joint(&mut self) -> Option<&mut JointData> { + self.bvh.joints.last_mut() + } +} + +#[cfg(test)] +mod tests { + // Needed for macros + use std::time::Duration; + + #[test] + fn macro_create() { + let bvh = bvh! { + HIERARCHY + ROOT Base + { + OFFSET 0.0 0.0 0.0 + CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation + JOINT End + { + OFFSET 0.0 0.0 15.0 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.0 0.0 30.0 + } + } + } + + MOTION + Frames: 1 + Frame Time: 0.033333333333 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + }; + + { + use super::{ChannelType, JointData, Offset}; + + fn check_joint>>( + joint: &JointData, + expected_name: &str, + expected_offset: Offset, + channels: &[ChannelType], + end_site: O, + ) { + assert_eq!(joint.name(), expected_name); + assert_eq!(*joint.offset(), expected_offset); + for (chan, expected_chan) in joint + .channels() + .iter() + .map(|c| c.channel_type()) + .zip(channels.iter()) + { + assert_eq!(chan, *expected_chan); + } + let end_site = end_site.into().map(Into::into); + assert_eq!(joint.end_site(), end_site.as_ref()); + } + + let mut joints = bvh.joints(); + + check_joint( + joints.next().unwrap().data(), + "Base", + [0.0, 0.0, 0.0], + &[ + ChannelType::PositionX, + ChannelType::PositionY, + ChannelType::PositionZ, + ChannelType::RotationZ, + ChannelType::RotationX, + ChannelType::RotationY, + ], + None, + ); + + check_joint( + joints.next().unwrap().data(), + "End", + [0.0, 0.0, 15.0], + &[ + ChannelType::RotationZ, + ChannelType::RotationX, + ChannelType::RotationY, + ], + [0.0, 0.0, 30.0], + ); + } + + assert_eq!(*bvh.frame_time(), Duration::from_nanos(33333333)); + + let frames = bvh.frames(); + assert_eq!(frames.len(), 1); + for frame in frames { + assert_eq!(frame.len(), 9); + for channel in frame.as_slice().iter() { + assert_eq!(*channel, 0.0); + } + } + } + + #[test] + fn test_empty_create() { + macro_rules! assert_empty { + ($bvh:expr) => { + assert!($bvh.joints().next().is_none()); + assert_eq!(*$bvh.frame_time(), Duration::default()); + assert!($bvh.frames().next().is_none()); + }; + } + + let empty = bvh! {}; + assert_empty!(empty); + + let empty_2 = bvh! { + HIERARCHY + MOTION + }; + assert_empty!(empty_2); + } +} diff --git a/studio-bvh/src/parse.rs b/studio-bvh/src/parse.rs new file mode 100644 index 0000000..48d7f0e --- /dev/null +++ b/studio-bvh/src/parse.rs @@ -0,0 +1,626 @@ +#![allow(unused)] + +use crate::{ + errors::{LoadJointsError, LoadMotionError}, + joint::{JointData, JointName}, + Axis, Bvh, Channel, ChannelType, EnumeratedLines, +}; +use bstr::ByteSlice; +use lexical::parse; +use std::time::Duration; + +/* +use nom::{ + alt, char, delimited, digit, do_parse, map, map_res, named, opt, pair, recognize, space, tag, + take_while, parse, ws, Err as NomErr, IResult, +}; +*/ +use smallvec::{smallvec, SmallVec}; +use std::{convert::TryFrom, mem, str}; + +/* +named! { + unsigned_float(&[u8]) -> f64, + map!( + recognize!(alt!( + delimited!(digit, tag!("."), opt!(digit)) + | delimited!(opt!(digit), tag!("."), digit) + )), + lexical::parse + ) +} + +named! { + float(&[u8]) -> f64, + map!( + pair!( + opt!(alt!(tag!("+") | tag!("-"))), + unsigned_float + ), + |(sign, value): (Option<&[u8]>, f64)| { + sign.and_then(|s| + if s[0] == (b'-') { + Some(-1f64) + } else { + None + }) + .unwrap_or(1f64) * value + } + ) +} + +named! { + unsigned_int(&[u8]) -> u64, + map!( + recognize!( + delimited!(space, digit, space) + ), + lexical::parse + ) +} + +named! { + token(&[u8]) -> &[u8], + delimited!( + space, + take_while!(|ch| char::from(ch).is_alphanumeric()), + space + ) +} + +named!(single_channel(&[u8]) -> ChannelType, + map_res!( + alt!( + tag!("Xposition") | + tag!("Yposition") | + tag!("Zposition") | + tag!("Xrotation") | + tag!("Yrotation") | + tag!("Zrotation") + ), + ChannelType::from_bytes + ) +); + +named! { + offset(&[u8]) -> Vector3, + ws!( + do_parse!( + tag!(b"OFFSET") >> + x: float >> + y: float >> + z: float >> + (Vector3 { x: x as f32, y: y as f32, z: z as f32 }) + ) + ) +} + +named! { + end_site(&[u8]) -> Vector3, + ws!( + do_parse!( + tag!(b"End") >> + tag!(b"Site") >> + end_site: delimited!(char!('{'), offset, char!('}')) >> + (end_site) + ) + ) +} +*/ +/* +fn heirarchy<'a>(data: &'a [u8]) -> IResult<&'a [u8], Vec> { + let mut joints = Vec::new(); + let mut num_channels = 0usize; + let mut joint_index = 0usize; + + let mut channels = |data: &'a [u8]| -> IResult<&'a [u8], SmallVec<[Channel; 6]>> { + let (mut data, expected_num_channels) = do_parse!( + data, + tag!(b"CHANNELS") >> num_channels: unsigned_int >> (num_channels) + )?; + + let mut out_channels = smallvec![]; + for i in 0..expected_num_channels { + let (new_data, channel_ty) = single_channel(data)?; + let channel = Channel::new(channel_ty, num_channels); + num_channels += 1; + data = new_data; + } + + Ok((&*data, out_channels)) + }; + + struct JointMembers { + name: JointName, + offset: Vector3, + channels: SmallVec<[Channel; 6]>, + end_site: Option>, + } + + let joint_members = |mut data: &'a [u8]| -> IResult<&'a [u8], JointMembers> { + let (new_data, name) = + do_parse!(data, tag!(b"Joint") >> joint_name: token >> (joint_name))?; + + let mut offset_and_channels = + |data: &'a [u8]| -> IResult<&'a [u8], (Vector3, SmallVec<[Channel; 6]>)> { + do_parse!(data, offst: offset >> chans: channels >> (offst, chans)) + }; + + let opt_end_site = |data: &'a [u8]| -> IResult<&'a [u8], Option>> { + match end_site(data) { + Ok((data, site)) => Ok((data, Some(site))), + Err(NomErr::Error(_)) => Ok((data, None)), + // @TODO: is this right? + Err(e) => Err(e), + } + }; + + let (data, members) = do_parse!( + data, + name: token + >> char!('{') + >> offst_and_chans: offset_and_channels + >> end_site: opt_end_site + >> (name, offst_and_chans.0, offst_and_chans.1, end_site) + )?; + + let members = JointMembers { + name: JointName::from(members.0), + offset: members.1, + channels: members.2, + end_site: members.3, + }; + + Ok((data, members)) + }; + + /* + parse!(data, + do_parse!( + tag!("HIERARCHY") >> + opt!( + tag!("Root") >> + root_joint_name: token >> + char!('{') >> + root_offset: offset >> + root_channels: channels >> + char!('}') >> + () + ) >> + () + ) + ); + */ +Ok((data, joints)) +} + +named! { +frame_metadata(&[u8]) -> (std::time::Duration, usize), +ws!(do_parse!( +tag!("Frames") >> +char!(':') >> +num_frames: unsigned_int >> +tag!("Frame") >> +tag!("Time") >> +char!(':') >> +frame_time: unsigned_float >> +(Duration::from_secs_f64(frame_time), num_frames as usize) +)) +} + +fn motion_section( +data: &[u8], +num_channels: usize, +) -> IResult<&[u8], (std::time::Duration, usize, Vec)> { +let (frame_time, num_frames) = frame_metadata(data)?; +let motion_data = Vec::with_capacity(num_channels * num_frames); + +for f in 0..num_frames { +let chomped = do_parse!(data, take_while!(|ch| char::from(ch).is_ascii_whitespace())); +let (output, motion_val) = float(chomped); +motion_data.push(motion_val); +data = output; +} + +Ok((data, (frame_time, num_frames, motion_data))) +} +*/ + +impl Bvh { + // @TODO: Remove panics + /// Logic for parsing the data from a `BufRead`. + pub(crate) fn read_joints( + &mut self, + lines: &mut EnumeratedLines<'_>, + ) -> Result<(), LoadJointsError> { + const HEIRARCHY_KEYWORD: &[u8] = b"HIERARCHY"; + + const ROOT_KEYWORD: &[u8] = b"ROOT"; + const JOINT_KEYWORD: &[u8] = b"JOINT"; + const ENDSITE_KEYWORDS: &[&[u8]] = &[b"End", b"Site"]; + + const OPEN_BRACE: &[u8] = b"{"; + const CLOSE_BRACE: &[u8] = b"}"; + + const OFFSET_KEYWORD: &[u8] = b"OFFSET"; + const CHANNELS_KEYWORD: &[u8] = b"CHANNELS"; + + #[derive(Debug, Eq, PartialEq)] + enum ParseMode { + NotStarted, + InHeirarchy, + Finished, + } + + #[allow(unused)] + #[derive(Eq, PartialEq)] + enum NextExpectedLine { + Hierarchy, + Channels, + Offset, + OpeningBrace, + ClosingBrace, + JointName, + RootName, + } + + let mut joints = vec![]; + let mut curr_mode = ParseMode::NotStarted; + let mut curr_channel = 0usize; + let (mut curr_index, mut curr_depth) = (0usize, 0usize); + let mut next_expected_line = NextExpectedLine::Hierarchy; + + let mut curr_joint = JointData::empty_root(); + let mut in_end_site = false; + let mut pushed_end_site_joint = false; + + #[inline] + fn get_parent_index(joints: &[JointData], for_depth: usize) -> usize { + joints + .iter() + .rev() + .find(|jd| jd.depth() == for_depth.saturating_sub(2)) + .and_then(|jd| jd.private_data().map(|p| p.self_index)) + .unwrap_or(0) + } + + for (line_num, line) in lines { + let line = line?; + let line = line.trim(); + + let mut tokens = line.fields_with(|c: char| c.is_ascii_whitespace() || c == ':'); + + let first_token = match tokens.next() { + Some(tok) => tok, + None => continue, + }; + + match first_token.as_bytes() { + HEIRARCHY_KEYWORD => { + if curr_mode != ParseMode::NotStarted { + panic!("Unexpected hierarchy"); + } + curr_mode = ParseMode::InHeirarchy; + next_expected_line = NextExpectedLine::RootName; + } + ROOT_KEYWORD => { + if curr_mode != ParseMode::InHeirarchy + || next_expected_line != NextExpectedLine::RootName + { + panic!("Unexpected root: {:?}", curr_mode); + } + + if let Some(name) = tokens.next() { + curr_joint.set_name(name); + } else { + panic!("Missing root name!"); + } + } + OPEN_BRACE => { + curr_depth += 1; + } + CLOSE_BRACE => { + curr_depth -= 1; + if curr_depth == 0 { + // We have closed the brace of the root joint. + curr_mode = ParseMode::Finished; + } + + if in_end_site { + if let JointData::Child { + ref mut private, .. + } = curr_joint + { + private.self_index = curr_index; + private.parent_index = get_parent_index(&joints, curr_depth); + private.depth = curr_depth - 1; + } + + let new_joint = mem::replace(&mut curr_joint, JointData::empty_child()); + joints.push(new_joint); + curr_index += 1; + in_end_site = false; + pushed_end_site_joint = true; + } + } + kw if kw == ENDSITE_KEYWORDS[0] => { + if tokens.next() == Some(ENDSITE_KEYWORDS[1]) { + in_end_site = true; + } else { + panic!("Unexpected end keyword"); + } + } + JOINT_KEYWORD => { + if curr_mode != ParseMode::InHeirarchy { + panic!("Unexpected Joint"); + } + + if !pushed_end_site_joint { + if let JointData::Child { + ref mut private, .. + } = curr_joint + { + private.self_index = curr_index; + private.parent_index = get_parent_index(&joints, curr_depth); + private.depth = curr_depth - 1; + } + + let new_joint = mem::replace(&mut curr_joint, JointData::empty_child()); + joints.push(new_joint); + + curr_index += 1; + } else { + pushed_end_site_joint = false; + } + + if let Some(mut name) = tokens.next() { + curr_joint.set_name(name); + } else { + panic!("Missing joint name!"); + } + } + OFFSET_KEYWORD => { + if curr_mode != ParseMode::InHeirarchy { + return Err(LoadJointsError::UnexpectedOffsetSection { line: line_num }); + } + + let mut offset = [0.0, 0.0, 0.0]; + + macro_rules! parse_axis { + ($axis_field:literal, $axis_enum:ident) => { + if let Some(tok) = tokens.next() { + offset[$axis_field] = + parse(tok).map_err(|e| LoadJointsError::ParseOffsetError { + parse_float_error: e, + axis: Axis::$axis_enum, + line: line_num, + })?; + } else { + return Err(LoadJointsError::MissingOffsetAxis { + axis: Axis::$axis_enum, + line: line_num, + }); + } + }; + } + + parse_axis!(0, X); + parse_axis!(1, Y); + parse_axis!(2, Z); + + curr_joint.set_offset(offset, in_end_site); + } + CHANNELS_KEYWORD => { + if curr_mode != ParseMode::InHeirarchy { + return Err(LoadJointsError::UnexpectedChannelsSection { line: line_num }); + } + + let num_channels: usize = tokens + .next() + .ok_or(LoadJointsError::ParseNumChannelsError { + error: None, + line: line_num, + }) + .and_then(|tok| match parse(tok) { + Ok(c) => Ok(c), + Err(e) => Err(LoadJointsError::ParseNumChannelsError { + error: Some(e), + line: line_num, + }), + })?; + + let mut channels: SmallVec<[Channel; 6]> = Default::default(); + channels.reserve(num_channels); + + while let Some(tok) = tokens.next() { + let channel_ty = ChannelType::try_from(tok).map_err(|e| { + LoadJointsError::ParseChannelError { + error: e, + line: line_num, + } + })?; + let channel = Channel::new(channel_ty, curr_channel); + curr_channel += 1; + channels.push(channel); + } + + curr_joint.set_channels(channels); + } + _ => {} + } + + if curr_mode == ParseMode::Finished { + break; + } + } + + if curr_mode != ParseMode::Finished { + return Err(LoadJointsError::MissingRoot); + } + + self.joints = joints; + self.num_channels = curr_channel; + + Ok(()) + } + + pub(crate) fn read_motion( + &mut self, + lines: &mut EnumeratedLines<'_>, + ) -> Result<(), LoadMotionError> { + const MOTION_KEYWORD: &[u8] = b"MOTION"; + const FRAMES_KEYWORD: &[u8] = b"Frames"; + const FRAME_TIME_KEYWORDS: &[&[u8]] = &[b"Frame", b"Time:"]; + + macro_rules! last_line_num { + () => { + lines.last_enumerator().unwrap_or(0) + }; + } + + lines + .next_non_empty_line() + .ok_or(LoadMotionError::MissingMotionSection { + line: last_line_num!(), + }) + .and_then(|(line_num, line)| { + let line = line?; + let line = line.trim(); + if line == MOTION_KEYWORD { + Ok(()) + } else { + Err(LoadMotionError::MissingMotionSection { line: line_num }) + } + })?; + + let num_frames = lines + .next_non_empty_line() + .ok_or(LoadMotionError::MissingNumFrames { + parse_error: None, + line: last_line_num!(), + }) + .and_then(|(line_num, line)| { + let line = line?; + let line = line.trim(); + let mut tokens = line.fields_with(|c: char| c.is_ascii_whitespace() || c == ':'); + + if tokens.next() != Some(FRAMES_KEYWORD) { + return Err(LoadMotionError::MissingNumFrames { + parse_error: None, + line: line_num, + }); + } + + let parse_num_frames = |token: Option<&[u8]>| { + if let Some(num_frames) = token.and_then(|b| str::from_utf8(b).ok()) { + parse::(num_frames) + .map_err(|e| LoadMotionError::MissingNumFrames { + parse_error: Some(e), + line: line_num, + }) + .map_err(Into::into) + } else { + Err(LoadMotionError::MissingNumFrames { + parse_error: None, + line: line_num, + }) + } + }; + + match tokens.next() { + Some(tok) if tok == b":" => parse_num_frames(tokens.next()), + Some(tok) => parse_num_frames(Some(tok)), + None => Err(LoadMotionError::MissingNumFrames { + parse_error: None, + line: line_num, + }), + } + })?; + + self.frame_time = lines + .next_non_empty_line() + .ok_or(LoadMotionError::MissingFrameTime { + parse_error: None, + line: last_line_num!(), + }) + .and_then(|(line_num, line)| { + let line = line?; + let mut tokens = line.fields(); + + let frame_time_kw = tokens.next(); + if frame_time_kw == FRAME_TIME_KEYWORDS.get(0).map(|b| *b) { + // do nothing + } else { + return Err(LoadMotionError::MissingFrameTime { + parse_error: None, + line: line_num, + }); + } + + let frame_time_kw = tokens.next(); + if frame_time_kw == FRAME_TIME_KEYWORDS.get(1).map(|b| *b) { + // do nothing + } else { + return Err(LoadMotionError::MissingFrameTime { + parse_error: None, + line: line_num, + }); + } + + let parse_frame_time = |token: Option<&[u8]>| { + if let Some(frame_time) = token { + let frame_time_secs = parse::(frame_time).map_err(|e| { + LoadMotionError::MissingFrameTime { + parse_error: Some(e), + line: line_num, + } + })?; + Ok(Duration::from_secs_f64(frame_time_secs)) + } else { + Err(LoadMotionError::MissingFrameTime { + parse_error: None, + line: line_num, + }) + } + }; + + match tokens.next() { + Some(tok) if tok == b":" => parse_frame_time(tokens.next()), + Some(tok) => parse_frame_time(Some(tok)), + None => Err(LoadMotionError::MissingNumFrames { + parse_error: None, + line: line_num, + }), + } + })?; + + let expected_total_motion_values = self.num_channels * num_frames; + + self.motion_values.reserve(expected_total_motion_values); + + for (line_num, line) in lines { + let line = line?; + let tokens = line.fields(); + for (channel_index, token) in tokens.enumerate() { + let motion = + parse::(token).map_err(|e| LoadMotionError::ParseMotionSection { + parse_error: e, + channel_index, + line: line_num, + })?; + self.motion_values.push(motion); + } + } + + if self.motion_values.len() != self.num_channels * num_frames { + return Err(LoadMotionError::MotionCountMismatch { + actual_total_motion_values: self.motion_values.len(), + expected_total_motion_values, + expected_num_frames: num_frames, + expected_num_clips: self.num_channels, + }); + } + + Ok(()) + } +} diff --git a/studio-bvh/src/write.rs b/studio-bvh/src/write.rs new file mode 100644 index 0000000..688356c --- /dev/null +++ b/studio-bvh/src/write.rs @@ -0,0 +1,510 @@ +//! Contains options for `bvh` file formatting. + +use crate::{frames::Frames, Bvh, Joint, Joints}; +use smallvec::SmallVec; +use std::{ + fmt, + io::{self, Write}, + iter, mem, + num::NonZeroUsize, +}; + +/// Specify formatting options for writing a `Bvh`. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct WriteOptions { + /// Which indentation style to use for nested bones. + pub indent: IndentStyle, + /// Which style new line terminator to use when writing the `bvh`. + pub line_terminator: LineTerminator, + /// Number of significant figures to use when writing `OFFSET` values. + /// + /// If this is `None`, then the minimum precision required will be used + pub offset_significant_figures: Option, + /// Number of significant figures to use when writing the `Frame Time` value. + /// + /// If this is `None`, then the minimum precision required will be used + pub frame_time_significant_figures: Option, + /// Number of significant figures to use when writing `MOTION` values. + /// + /// If this is `None`, then the minimum precision required will be used + pub motion_values_significant_figures: Option, + #[doc(hidden)] + _nonexhaustive: (), +} + +impl Default for WriteOptions { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl WriteOptions { + /// Create a new `WriteOptions` with default values. + #[inline] + pub const fn new() -> Self { + WriteOptions { + indent: IndentStyle::Tabs, + line_terminator: LineTerminator::native(), + offset_significant_figures: None, + frame_time_significant_figures: None, + motion_values_significant_figures: None, + _nonexhaustive: (), + } + } + + /// Output the `Bvh` file to the `writer` with the given options. + pub fn write(&self, bvh: &Bvh, writer: &mut W) -> io::Result<()> { + let mut curr_chunk = vec![]; + let mut curr_bytes_written = 0usize; + let mut curr_string_len = 0usize; + let mut iter_state = WriteOptionsIterState::new(); + + while self.next_chunk(bvh, &mut curr_chunk, &mut iter_state) != false { + let bytes: &[u8] = curr_chunk.as_ref(); + curr_string_len += bytes.len(); + curr_bytes_written += writer.write(bytes)?; + + if curr_bytes_written != curr_string_len { + return Err(io::Error::new( + io::ErrorKind::Other, + "Data has been dropped while writing to file", + )); + } + } + writer.flush() + } + + /// Output the `Bvh` file to the `string` with the given options. + pub fn write_to_string(&self, bvh: &Bvh) -> Vec { + let mut curr_chunk = vec![]; + let mut out_string = vec![]; + let mut iter_state = WriteOptionsIterState::new(); + + while self.next_chunk(bvh, &mut curr_chunk, &mut iter_state) != false { + out_string.extend(curr_chunk.drain(..)); + } + + out_string + } + + /// Sets `indent` on `self` to the new `IndentStyle`. + #[inline] + pub const fn with_indent(self, indent: IndentStyle) -> Self { + Self { indent, ..self } + } + + /// Sets `line_terminator` on `self` to the new `LineTerminator`. + #[inline] + pub const fn with_line_terminator(self, line_terminator: LineTerminator) -> Self { + Self { + line_terminator, + ..self + } + } + + /// Sets `offset_significant_figures` on `self` to the new `offset_significant_figures`. + #[inline] + pub fn with_offset_significant_figures(self, offset_significant_figures: N) -> Self + where + N: Into>, + { + Self { + offset_significant_figures: offset_significant_figures.into(), + ..self + } + } + + /// Sets `motion_values_significant_figures` on `self` to the new `motion_values_significant_figures`. + #[inline] + pub fn with_frame_time_significant_figures(self, frame_time_significant_figures: N) -> Self + where + N: Into>, + { + Self { + frame_time_significant_figures: frame_time_significant_figures.into(), + ..self + } + } + + /// Sets `motion_values_significant_figures` on `self` to the new `motion_values_significant_figures`. + #[inline] + pub fn with_motion_values_significant_figures( + self, + motion_values_significant_figures: N, + ) -> Self + where + N: Into>, + { + Self { + motion_values_significant_figures: motion_values_significant_figures.into(), + ..self + } + } + + // @TODO: Refactor all of this + /// Get the next text chunk of the written bvh file. This function is + /// structured so that the `chunk` string can be continually + /// re-used without allocating and de-allocating memory. + /// + /// # Returns + /// + /// Returns `true` when there are still more lines available, + /// `false` when all lines have been extracted. + fn next_chunk<'a, 'b: 'a>( + &self, + bvh: &'b Bvh, + chunk: &mut Vec, + iter_state: &'a mut WriteOptionsIterState<'b>, + ) -> bool { + chunk.clear(); + + let terminator = self.line_terminator.as_bytes(); + + match *iter_state { + WriteOptionsIterState::WriteHierarchy { ref mut written } => { + if !*written { + *chunk = b"HIERARCHY".to_vec(); + chunk.extend_from_slice(self.line_terminator.as_bytes()); + *written = true; + } else { + let mut joints = bvh.joints(); + *iter_state = WriteOptionsIterState::WriteJoints { + current_joint: joints.next(), + joints, + wrote_name: false, + wrote_offset: false, + wrote_channels: false, + }; + } + } + WriteOptionsIterState::WriteJoints { + current_joint: None, + .. + } => { + *iter_state = WriteOptionsIterState::WriteMotion { written: false }; + } + WriteOptionsIterState::WriteJoints { + ref mut joints, + ref mut current_joint, + ref mut wrote_name, + ref mut wrote_offset, + ref mut wrote_channels, + } => { + if let Some(ref joint) = current_joint { + let joint_data = joint.data(); + let mut depth = joint_data.depth(); + if *wrote_name { + depth += 1 + } + + match (&mut *wrote_name, &mut *wrote_offset, &mut *wrote_channels) { + (&mut false, _, _) => { + chunk.extend(self.indent.prefix_chars(depth)); + if joint_data.is_root() { + chunk.extend_from_slice(b"ROOT "); + } else { + chunk.extend_from_slice(b"JOINT "); + } + chunk.extend_from_slice(joint_data.name().as_ref()); + chunk.extend_from_slice(terminator); + chunk.extend(self.indent.prefix_chars(depth)); + chunk.push(b'{'); + chunk.extend_from_slice(terminator); + + *wrote_name = true; + } + (&mut true, &mut false, _) => { + chunk.extend(self.indent.prefix_chars(depth)); + + let [x, y, z] = joint_data.offset(); + let offset_str = match self.offset_significant_figures { + Some(sf) => { + format!("OFFSET {:.*} {:.*} {:.*}", sf, x, sf, y, sf, z,) + } + None => format!("OFFSET {:.} {:.} {:.}", x, y, z), + }; + chunk.extend_from_slice(offset_str.as_bytes()); + chunk.extend_from_slice(terminator); + *wrote_offset = true; + } + (&mut true, &mut true, &mut false) => { + chunk.extend(self.indent.prefix_chars(depth)); + + let channels = joint_data.channels(); + let channels_str = channels + .iter() + .map(|ch| ch.channel_type().as_str()) + .collect::>() + .join(" "); + + let channels_str = + format!("CHANNELS {} {}", channels.len(), channels_str); + chunk.extend_from_slice(channels_str.as_bytes()); + chunk.extend_from_slice(terminator); + *wrote_channels = true; + } + (&mut true, &mut true, &mut true) => { + if let Some([x, y, z]) = joint_data.end_site() { + chunk.extend(self.indent.prefix_chars(depth)); + chunk.extend_from_slice(b"End Site"); + chunk.extend_from_slice(terminator); + + chunk.extend(self.indent.prefix_chars(depth)); + chunk.push(b'{'); + chunk.extend_from_slice(terminator); + + chunk.extend(self.indent.prefix_chars(depth + 1)); + let offset_str = match self.offset_significant_figures { + Some(sf) => { + format!("OFFSET {:.*} {:.*} {:.*}", sf, x, sf, y, sf, z) + } + None => format!("OFFSET {:.} {:.} {:.}", x, y, z), + }; + chunk.extend_from_slice(offset_str.as_bytes()); + chunk.extend_from_slice(terminator); + + chunk.extend(self.indent.prefix_chars(depth)); + chunk.push(b'}'); + chunk.extend_from_slice(terminator); + + let next_joint = joints.next(); + let prev_joint = mem::replace(current_joint, next_joint).unwrap(); + + let (curr_depth, mut depth_difference) = + if let Some(ref curr_j) = *current_joint { + let curr_depth = curr_j.data().depth(); + (curr_depth, Some(prev_joint.data().depth() - curr_depth)) + } else { + (0, Some(prev_joint.data().depth())) + }; + + while let Some(d) = depth_difference { + chunk.extend(self.indent.prefix_chars(curr_depth + d)); + chunk.push(b'}'); + chunk.extend_from_slice(terminator); + depth_difference = + depth_difference.and_then(|d| d.checked_sub(1)); + } + } else { + *current_joint = joints.next(); + } + *wrote_name = false; + *wrote_offset = false; + *wrote_channels = false; + } + } + } + } + WriteOptionsIterState::WriteMotion { ref mut written } => { + if !*written { + *chunk = b"MOTION".to_vec(); + chunk.extend_from_slice(terminator); + *written = true; + } else { + *iter_state = WriteOptionsIterState::WriteNumFrames { written: false }; + } + } + WriteOptionsIterState::WriteNumFrames { ref mut written } => { + if !*written { + *chunk = format!("Frames: {}", bvh.frames().len()).into_bytes(); + chunk.extend_from_slice(terminator); + *written = true; + } else { + *iter_state = WriteOptionsIterState::WriteFrameTime { written: false }; + } + } + WriteOptionsIterState::WriteFrameTime { ref mut written } => { + if !*written { + *chunk = match self.frame_time_significant_figures { + Some(sf) => { + format!("Frame Time: {:.*}", sf, bvh.frame_time().as_secs_f64()) + .into_bytes() + } + None => { + format!("Frame Time: {:.}", bvh.frame_time().as_secs_f64()).into_bytes() + } + }; + chunk.extend_from_slice(terminator); + *written = true; + } else { + let frames = bvh.frames(); + *iter_state = WriteOptionsIterState::WriteFrames { frames }; + } + } + WriteOptionsIterState::WriteFrames { ref mut frames } => { + return frames + .next() + .map(|frame| { + let motion_values = frame + .as_slice() + .iter() + .map(|motion| match self.motion_values_significant_figures { + Some(sf) => format!("{:.*}", sf, motion), + None => format!("{:.}", motion), + }) + .collect::>() + .join(" "); + *chunk = motion_values.into_bytes(); + chunk.extend_from_slice(terminator); + true + }) + .unwrap_or_default(); + } + } + + true + } +} + +enum WriteOptionsIterState<'a> { + WriteHierarchy { + written: bool, + }, + WriteJoints { + joints: Joints<'a>, + current_joint: Option>, + wrote_name: bool, + wrote_offset: bool, + wrote_channels: bool, + }, + WriteMotion { + written: bool, + }, + WriteNumFrames { + written: bool, + }, + WriteFrameTime { + written: bool, + }, + WriteFrames { + frames: Frames<'a>, + }, +} + +impl WriteOptionsIterState<'_> { + #[inline] + const fn new() -> Self { + WriteOptionsIterState::WriteHierarchy { written: false } + } +} + +impl Default for WriteOptionsIterState<'_> { + #[inline] + fn default() -> Self { + Self::new() + } +} + +/// Specify indentation style to use when writing the `Bvh` joints. +/// +/// By default, this value is set to 1 tab. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum IndentStyle { + /// Do not indent nested joints. + NoIndentation, + /// Use a single tab (`'\t'`) for indentation. + Tabs, + /// Use `n` spaces for indentation. + Spaces(NonZeroUsize), +} + +impl IndentStyle { + /// Create a new `IndentStyle` with `n` preceeding spaces. + /// + /// If `n` is `0`, then `IndentStyle::NoIndentation` is returned. + #[inline] + pub fn with_spaces(n: usize) -> Self { + NonZeroUsize::new(n) + .map(IndentStyle::Spaces) + .unwrap_or(IndentStyle::NoIndentation) + } + + /// Return an `Iterator` which yields bytes corresponding to the ascii + /// chars which form the `String` this indentation style would take. + #[inline] + fn prefix_chars(&self, depth: usize) -> impl Iterator { + match *self { + IndentStyle::NoIndentation => iter::repeat(b'\0').take(0), + IndentStyle::Tabs => iter::repeat(b'\t').take(depth), + IndentStyle::Spaces(n) => iter::repeat(b' ').take(n.get() * depth), + } + } +} + +/// Create a new `IndentStyle` using a single tab. +impl Default for IndentStyle { + #[inline] + fn default() -> Self { + IndentStyle::Tabs + } +} + +/// Represents which line terminator style to use when writing a `Bvh` file. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum LineTerminator { + /// Use Unix-style line endings (`'\n'`). + Unix, + /// Use Windows-style line endings (`'\r\n'`). + Windows, +} + +impl LineTerminator { + /// Get the line terminator style native to the current OS: + /// + /// * On Windows, this returns `LineTerminator::Windows`. + /// * Otherwise, this returns `LineTerminator::Unix`. + #[inline] + pub const fn native() -> Self { + if cfg!(windows) { + LineTerminator::Windows + } else { + LineTerminator::Unix + } + } + + /// Return the characters of the `LineTerminator` as a `&str`. + #[inline] + pub const fn as_str(&self) -> &str { + match *self { + LineTerminator::Unix => "\n", + LineTerminator::Windows => "\r\n", + } + } + + /// Returns the escaped characters of the `LineTerminator` as a `&str`. + #[inline] + pub const fn as_escaped_str(&self) -> &str { + match *self { + LineTerminator::Unix => r"\n", + LineTerminator::Windows => r"\r\n", + } + } + + /// Return the characters of the `LineTerminator` as a `&[u8]`. + #[inline] + pub const fn as_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + + /// Returns the escaped characters of the `LineTerminator` as a `&[u8]`. + #[inline] + pub const fn as_escaped_bytes(&self) -> &[u8] { + self.as_escaped_str().as_bytes() + } +} + +/// Returns the native line terminator for the current OS. +impl Default for LineTerminator { + #[inline] + fn default() -> Self { + LineTerminator::native() + } +} + +impl fmt::Display for LineTerminator { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +}