Files
Danny McGee 1687bfd6ed BVH animation loading / retargeting (#25)
This PR adds support for BVH animation files, with a custom animation
player and retargeting solution for our Blender Rigify-based skeletal
meshes.

---------

Co-authored-by: Brooks Palin <brookspalin710@gmail.com>
2024-01-18 17:28:25 -05:00

957 lines
28 KiB
Rust

#![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 <Root-name>
//! {
//! OFFSET <Root-offset-x> <Root-offset-y> <Root-offset-z>
//! CHANNELS <Num-root-joint-channels> Xposition Yposition <other-root-channels ...>
//! JOINT <Joint-1-name>
//! {
//! OFFSET <Joint-1-offset-x> <Joint-1-offset-y> <Joint-1-offset-z>
//! CHANNELS <Num-joint-1-channels> <Joint-1-channels ...>
//! JOINT <Joint-2-name>
//! {
//! OFFSET <Joint-2-offset-x> <Joint-2-offset-y> <Joint-2-offset-z>
//! CHANNELS <Num-joint-2-channels> <Joint-2-channels ...>
//! JOINT <Joint-with-end-site>
//! {
//! OFFSET ...
//! CHANNELS ...
//! End Site
//! {
//! OFFSET <end-site-offset-x> <end-site-offset-y> <end-site-offset-z>
//! }
//! }
//! ... More child joints
//! }
//! JOINT <Joint-3-name>
//! {
//! OFFSET <Joint-3-offset-x> <Joint-3-offset-y> <Joint-3-offset-z>
//! CHANNELS <Num-joint-3-channels> <Joint-3-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: <num-frames>
//! Frame Time: <frame-time>
//! <frame-0-channel-0-value> <frame-0-channel-1-value> <frame-0-channel-2-value> ...
//! <frame-1-channel-0-value> <frame-1-channel-1-value> <frame-1-channel-2-value> ...
//! <frame-2-channel-0-value> <frame-2-channel-1-value> <frame-2-channel-2-value> ...
//! ⋮
//! ```
//!
//! 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::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, JointData, JointMut, Joints, JointsMut};
#[doc(hidden)]
pub use macros::BvhLiteralBuilder;
struct CachedEnumerate<I> {
iter: Enumerate<I>,
last_enumerator: Option<usize>,
}
impl<I> CachedEnumerate<I> {
#[inline]
fn new(iter: Enumerate<I>) -> Self {
CachedEnumerate {
iter,
last_enumerator: None,
}
}
#[inline]
fn last_enumerator(&self) -> Option<usize> {
self.last_enumerator
}
}
impl<I: Iterator> Iterator for CachedEnumerate<I> {
type Item = <Enumerate<I> as Iterator>::Item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (curr, item) = self.iter.next()?;
self.last_enumerator = Some(curr);
Some((curr, item))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
type EnumeratedLines<'a> = CachedEnumerate<ByteLines<&'a mut dyn BufReadExt>>;
impl EnumeratedLines<'_> {
pub(crate) fn next_non_empty_line(&mut self) -> Option<<Self as Iterator>::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<R: BufReadExt>(data: R) -> Result<Bvh, LoadError> {
Bvh::from_reader(data)
}
/// Parse a sequence of bytes as if it were an in-memory `Bvh` file.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{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::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Bvh, LoadError> {
Bvh::from_bytes(bytes)
}
/// Parse a `str` as if it were an in-memory `Bvh` file.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{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::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_str(string: &str) -> Result<Bvh, LoadError> {
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<JointData>,
/// The motion values of the `Frame`.
motion_values: Vec<f32>,
/// 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 studio_bvh::{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::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, LoadError> {
Bvh::from_reader(Cursor::new(bytes))
}
/// Loads the `Bvh` from the `reader`.
#[inline]
pub fn from_reader<R: BufReadExt>(mut reader: R) -> Result<Self, LoadError> {
#[inline(never)]
fn from_reader_(reader: &mut dyn BufReadExt) -> Result<Bvh, LoadError> {
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 studio_bvh::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<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write::WriteOptions::default().write(self, writer)
}
/// Writes the `Bvh` using the `bvh` file format into a `Vec<u8>` 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<u8> {
write::WriteOptions::default().write_to_string(self)
}
/// Returns the root joint if it exists, or `None` if the skeleton is empty.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{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<Joint<'_>> {
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 studio_bvh::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 studio_bvh::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 studio_bvh::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<f32> {
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 studio_bvh::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 studio_bvh::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)
}
#[allow(missing_docs)]
pub fn get_joint(&self, index: usize) -> Option<Joint<'_>> {
if index < self.joints.len() {
Some(Joint {
index,
joints: &self.joints,
})
} else {
None
}
}
}
impl Default for Bvh {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl FromStr for Bvh {
type Err = LoadError;
#[inline]
fn from_str(string: &str) -> Result<Self, Self::Err> {
Bvh::from_bytes(string.as_bytes())
}
}
impl TryFrom<&'_ str> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(string: &'_ str) -> Result<Self, Self::Error> {
FromStr::from_str(string)
}
}
impl TryFrom<&'_ BStr> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(string: &'_ BStr) -> Result<Self, Self::Error> {
Bvh::from_bytes(string.as_bytes())
}
}
impl TryFrom<&'_ [u8]> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
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 studio_bvh::ChannelType;
/// assert_eq!(
/// ChannelType::from_bytes("Xrotation").unwrap(),
/// ChannelType::RotationX);
///
/// let err = ChannelType::from_bytes("Hello").unwrap_err();
/// assert_eq!(&err.into_inner(), b"Hello");
/// ```
#[inline]
pub fn from_bytes<B>(s: &B) -> Result<Self, ParseChannelError>
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 studio_bvh::ChannelType;
/// let channel_type = ChannelType::RotationX;
/// assert!(channel_type.is_rotation());
/// ```
#[inline]
pub const fn is_rotation(&self) -> bool {
matches!(
*self,
ChannelType::RotationX | ChannelType::RotationY | ChannelType::RotationZ
)
}
/// Returns `true` if this channel corresponds to a positional
/// transform, otherwise `false`.
///
/// # Example
///
/// ```
/// # use studio_bvh::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 studio_bvh::{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<Self, Self::Error> {
ChannelType::from_bytes(string)
}
}
impl TryFrom<&'_ str> for ChannelType {
type Error = ParseChannelError;
#[inline]
fn try_from(string: &str) -> Result<Self, Self::Error> {
ChannelType::from_bytes(string.as_bytes())
}
}
impl FromStr for ChannelType {
type Err = ParseChannelError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
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 studio_bvh::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)
}
}