From d7eeaf4b709ef7f2230e15b3688811c365fb3608 Mon Sep 17 00:00:00 2001 From: Lee-Orr Date: Mon, 12 Feb 2024 16:13:15 -0500 Subject: [PATCH] add scaling, docs, and adjust example_mapping --- README.md | 74 ++++++++++++++++++++++++++++++++++++- assets/example_mapping.json | 9 +++-- src/lib.rs | 5 ++- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cb77b19..32a1aa1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,75 @@ # Bevy Facial Mocap -This is a project build for storyteller.ai, to provide support for importing ARKit data into bevy, and using it to drive shape keys. \ No newline at end of file +This is a project build for storyteller.ai, to provide support for importing ARKit data into bevy, and using it to drive shape keys. + +## Use + +Import the asset loader: + +```rust + app + .init_asset_loader::() +``` + +and in code, you can load assets just like any normal animation clip: + +```rust +let handle = asset_server.load("recorded_test.morph_anim.json"); +let mut player = AnimationPlayer::default(); +player.play(handle.clone()).repeat(); +``` + +## Asset Preparation + +To load a recording, you need 3 files: +- a `.csv` with the ARKit data +- a `.json` file with the mapping info +- a `.morph_anim.json` file that points to the `.csv`, the mapping file, and provides the name of the animation root. Ideally, it would also provide an FPS - though that can be computed if it is an integer value (so not for 60 or 30, but not for 29.97) + +### .morph_anim.json structure +This is the structure of the animation file itself: + +```json +{ + "csv": "./the_relative/path_to_the.csv", + "root_name": "pascal_Head_or_whatever_root_entity_you_want_here", + "fps": 4, + "mapping": "the_relative/path_to_the/mapping.json" +} +``` + +### Mapping .json structure + +The mapping .json is a json list/array. Each item is a sub-list where the first element is the mapping, and the second element is the name of the morph in the CSV file. + +For example: + +```json +[ + ["eyeBlinkLeft", "EyeBlinkLeft"], + ["eyeLookDownLeft", "EyeLookDownLeft"] +] +``` + +For morphs, the sub lists need to be in the same order as the morphs in the GLTF files. + +For bones, the first element of the sub-list is the mapping information. It provides the path to the bone (from root), the axis of the transform, and an optional offset or scale: + +```json + [{ + "path": ["pascal_Head"], + "axis": "Y", + "scale": 0.4 + }, "HeadYaw"], + [{ + "path": ["pascal_Head"], + "axis": "X", + "offset": -45 + }, "HeadPitch"], + [{ + "path": ["pascal_Head"], + "axis": [0, 0, 1] + }, "HeadRoll"] +``` + +The [example_mapping.json](assets/example_mapping.json) file contains a full example of the mapping. And the [simple example](examples/simple_example.rs) contains a full code sample you can try by running `cargo run --example simple_example`. \ No newline at end of file diff --git a/assets/example_mapping.json b/assets/example_mapping.json index 7924568..1591222 100644 --- a/assets/example_mapping.json +++ b/assets/example_mapping.json @@ -53,15 +53,18 @@ ["tongueOut", "TongueOut"], [{ "path": ["pascal_Head"], - "axis": "Y" + "axis": "Y", + "scale": 0.4 }, "HeadYaw"], [{ "path": ["pascal_Head"], "axis": "X", - "offset": -45 + "offset": -22, + "scale": 0.4 }, "HeadPitch"], [{ "path": ["pascal_Head"], - "axis": "Z" + "axis": "Z", + "scale": 0.4 }, "HeadRoll"] ] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b62c6a1..97f8de7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,7 +293,9 @@ impl CSVAnimation { }; let offset = mapping.offset.unwrap_or(0f32).to_radians(); - let result = result * 180f32.to_radians() + offset; + let scale = mapping.scale.unwrap_or(1f32); + + let result = result * 180f32.to_radians() * scale + offset; println!("Rotating {axis:?} by {result:?}"); println!("From {}", transform.rotation.xyz()); @@ -386,6 +388,7 @@ pub struct BoneMapping { path: Vec, axis: Axis, offset: Option, + scale: Option } #[derive(Debug, Clone, Serialize, Deserialize)]