mirror of
https://github.com/storytold/bevy-mocap.git
synced 2026-10-09 00:09:45 +00:00
added comments
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/// This module contains the parser for the CSV Animation itself.
|
||||
use std::num::ParseFloatError;
|
||||
|
||||
use bevy::{asset::Asset, reflect::Reflect};
|
||||
@@ -67,6 +68,9 @@ impl CSVAnimation {
|
||||
.ok_or(CSVParseError::NoData)?
|
||||
.map_err(CSVParseError::InvalidCSV)?;
|
||||
|
||||
// The header row contains the header for the timestamp column, a column we ignore (the number of morphs)
|
||||
// and then the names of every morph in order. We need to keep this information for later, when we
|
||||
// handle mapping the animation to another set of morphs.
|
||||
let morph_names: Vec<String> = header_row
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -84,8 +88,11 @@ impl CSVAnimation {
|
||||
}
|
||||
|
||||
let mut frames = Vec::new();
|
||||
// The CSV doesn't contain information about FPS, but we can guess by looking at the
|
||||
// largest frame number we reach - since the frame numbers reset every second
|
||||
let mut fps: u8 = 0;
|
||||
|
||||
// here we iterate over every frame, and set up it's morph data and timestamp
|
||||
for result in records {
|
||||
let record = result.map_err(CSVParseError::InvalidCSV)?;
|
||||
|
||||
@@ -99,7 +106,6 @@ impl CSVAnimation {
|
||||
let _ = iter.next().ok_or(CSVParseError::MisssingKnownHeaders)?;
|
||||
// similarly, we already validate that there are a single morph in the CSV, so the skipped columns shouldn't be valid CSVs.
|
||||
}
|
||||
|
||||
let morphs = iter
|
||||
.map(|v| {
|
||||
v.trim()
|
||||
@@ -109,7 +115,7 @@ impl CSVAnimation {
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
if fps < timestamp.frame {
|
||||
fps = timestamp.frame;
|
||||
fps = timestamp.frame; // this is used to guess the FPS
|
||||
}
|
||||
|
||||
frames.push(CSVFrame { timestamp, morphs });
|
||||
@@ -122,7 +128,7 @@ impl CSVAnimation {
|
||||
Ok(CSVAnimation {
|
||||
frames,
|
||||
morph_names,
|
||||
fps: (fps + 1) as f32,
|
||||
fps: (fps + 1) as f32, // Since frame numbers start at 0, we need to add 1 to the largest frame number to get the FPS
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,16 @@ pub enum MappingType {
|
||||
Bone(BoneMapping),
|
||||
}
|
||||
|
||||
/// A Bone Mapping maps the value from the CSV to a rotation in a specific axis on a bone
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BoneMapping {
|
||||
/// the hierarchy path to the bone
|
||||
path: Vec<String>,
|
||||
/// the axis of rotation
|
||||
axis: Axis,
|
||||
/// an offset - in case "0" in the CSV is different from "0" in the bone hierarchy
|
||||
offset: Option<f32>,
|
||||
/// scale - to allow you to tone the animation up or down
|
||||
scale: Option<f32>,
|
||||
}
|
||||
|
||||
@@ -39,11 +44,13 @@ pub enum Axis {
|
||||
|
||||
impl CSVAnimation {
|
||||
pub fn generate_clip(self, name: &str, mapping: Option<&CSVAnimationMapping>) -> AnimationClip {
|
||||
// we find the path for the root entity and create a clip
|
||||
let path = EntityPath {
|
||||
parts: vec![Name::new(name.to_owned())],
|
||||
};
|
||||
let mut clip = AnimationClip::default();
|
||||
|
||||
// we note the order and name of every morph in the CSV
|
||||
let csv_morph_order: HashMap<&str, usize> = self
|
||||
.morph_names
|
||||
.iter()
|
||||
@@ -51,8 +58,10 @@ impl CSVAnimation {
|
||||
.map(|(i, val)| (val.as_str(), i))
|
||||
.collect();
|
||||
|
||||
// we generate the animation curve for the morphs themselves
|
||||
clip.add_curve_to_path(path, self.generate_morph_curve(mapping, &csv_morph_order));
|
||||
|
||||
// if we have a mapping file, we also generate curves for any bones we're mapped to
|
||||
if let Some(mapping) = mapping {
|
||||
self.generate_bone_curves(&mut clip, mapping, &csv_morph_order);
|
||||
}
|
||||
@@ -66,6 +75,7 @@ impl CSVAnimation {
|
||||
mapping: &CSVAnimationMapping,
|
||||
csv_morph_order: &HashMap<&str, usize>,
|
||||
) {
|
||||
// we group all our mappings by the name of the bone, and throw out the non-bone mappings
|
||||
let bones = mapping
|
||||
.0
|
||||
.iter()
|
||||
@@ -78,6 +88,7 @@ impl CSVAnimation {
|
||||
.group_by(|(key, _)| key.clone());
|
||||
|
||||
for (bone, mappings) in bones.into_iter() {
|
||||
// for any bone, we generate the relevant curve and add it to the clip
|
||||
let curve = self.generate_bone_curve(mappings.map(|(_, v)| v), csv_morph_order);
|
||||
let path = EntityPath {
|
||||
parts: bone.into_iter().map(Name::new).collect(),
|
||||
@@ -100,9 +111,12 @@ impl CSVAnimation {
|
||||
let time = (frame as f32) * seconds_per_frame;
|
||||
keyframe_timestamps.push(time);
|
||||
|
||||
// we set up a transform at rest
|
||||
let mut transform =
|
||||
Transform::from_rotation(Quat::from_euler(bevy::math::EulerRot::XYZ, 0., 0., 0.));
|
||||
|
||||
// for every mapping - we grab that data from the CSV and
|
||||
// rotate the transform accordingly.
|
||||
for (mapping, csv_key) in mappings.iter() {
|
||||
let result = csv_morph_order
|
||||
.get(csv_key.as_str())
|
||||
@@ -125,7 +139,6 @@ impl CSVAnimation {
|
||||
|
||||
transform.rotate_axis(axis, result);
|
||||
}
|
||||
|
||||
keyframes.push(transform.rotation)
|
||||
}
|
||||
|
||||
@@ -140,6 +153,7 @@ impl CSVAnimation {
|
||||
mapping: Option<&CSVAnimationMapping>,
|
||||
csv_morph_order: &HashMap<&str, usize>,
|
||||
) -> VariableCurve {
|
||||
// we figure out how we need to re-order morphs, going from the mesh order to the csv order
|
||||
let (num_mesh_morphs, mesh_to_csv_morph_order) = match mapping {
|
||||
Some(mapping) => {
|
||||
let mesh_to_csv_morph_order: Vec<Option<usize>> = mapping
|
||||
@@ -169,9 +183,11 @@ impl CSVAnimation {
|
||||
let mut keyframes = Vec::with_capacity(self.frames.len() * num_mesh_morphs);
|
||||
|
||||
let seconds_per_frame = 1.0 / self.fps;
|
||||
|
||||
for (frame, data) in self.frames.iter().enumerate() {
|
||||
let time = (frame as f32) * seconds_per_frame;
|
||||
keyframe_timestamps.push(time);
|
||||
// we iterate over all the morphs in the mesh, and if they have a matching morph in the CSV - we grab the data for that morph
|
||||
for i in 0..num_mesh_morphs {
|
||||
let result = mesh_to_csv_morph_order
|
||||
.get(i)
|
||||
|
||||
@@ -4,6 +4,9 @@ use bevy::reflect::Reflect;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Neither the chrono crate nor the standard library has a good way of parsing SMPT timestamps.
|
||||
// As a result, we set one up here - and we're sticking to u8s since recording data at above 255 FPS
|
||||
// is unlikely in this context, and none of the other values should go beyond 60 (or 24 for hours)
|
||||
#[derive(
|
||||
Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Reflect, Default,
|
||||
)]
|
||||
@@ -51,6 +54,7 @@ impl FromStr for Timestamp {
|
||||
return Err(TimestampParseError::TooManySegments);
|
||||
}
|
||||
|
||||
// The segments start with hours, but we want to start with frames and be robust to missing segments
|
||||
segments.reverse();
|
||||
|
||||
let Some(frame) = segments.first() else {
|
||||
|
||||
+11
-3
@@ -1,6 +1,13 @@
|
||||
pub mod csv_animation;
|
||||
pub mod csv_animation_mapping;
|
||||
pub mod csv_frame;
|
||||
/// This library converts Unreal's LiveLink CSV format to Bevy animation clips.
|
||||
/// It exports the [`CSVAnimationLoader`] struct, which is an asset loader for these animations.
|
||||
|
||||
/// The main module contains the asset loader itself, and a struct defining a clip info JSON file,
|
||||
/// which is used to point to the CSV and contain additional information to assist with generating
|
||||
/// the animation clip - such as the name of the root object in the animation clip, the FPS, and
|
||||
/// the path of a mapping file
|
||||
mod csv_animation;
|
||||
mod csv_animation_mapping;
|
||||
mod csv_frame;
|
||||
|
||||
use std::{path::PathBuf, string::FromUtf8Error};
|
||||
|
||||
@@ -9,6 +16,7 @@ use bevy::{
|
||||
asset::{AssetLoader, AsyncReadExt, ReadAssetBytesError},
|
||||
log::info,
|
||||
};
|
||||
|
||||
use csv_animation::*;
|
||||
use csv_animation_mapping::*;
|
||||
use csv_frame::*;
|
||||
|
||||
Reference in New Issue
Block a user