mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
1687bfd6ed
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>
627 lines
20 KiB
Rust
627 lines
20 KiB
Rust
#![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<f32>,
|
|
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<f32>,
|
|
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<JointData>> {
|
|
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<f32>,
|
|
channels: SmallVec<[Channel; 6]>,
|
|
end_site: Option<Vector3<f32>>,
|
|
}
|
|
|
|
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<f32>, SmallVec<[Channel; 6]>)> {
|
|
do_parse!(data, offst: offset >> chans: channels >> (offst, chans))
|
|
};
|
|
|
|
let opt_end_site = |data: &'a [u8]| -> IResult<&'a [u8], Option<Vector3<f32>>> {
|
|
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<f32>)> {
|
|
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);
|
|
|
|
for tok in tokens {
|
|
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::<usize, _>(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).copied() {
|
|
// 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).copied() {
|
|
// 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::<f64, _>(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::<f32, _>(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(())
|
|
}
|
|
}
|