├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── scripts ├── id_rsa.enc └── travis-doc-upload.cfg └── src ├── animation.rs ├── blend_tree.rs ├── controller.rs ├── dlb_skinning_150.glslv ├── lbs_skinning_150.glslv ├── lib.rs ├── manager.rs ├── math.rs ├── skeleton.rs ├── skinned_renderer.rs ├── skinning_150.glslf └── transform.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | Cargo.lock 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | script: 3 | - cargo build -v 4 | - cargo doc -v 5 | after_success: 6 | - curl http://docs.piston.rs/travis-doc-upload.sh | sh 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "skeletal_animation" 4 | version = "0.47.0" 5 | authors = ["stjahns "] 6 | keywords = ["animation", "character", "skeletal", "gfx", "piston"] 7 | description = "Skeletal character animation library, using gfx-rs" 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/PistonDevelopers/skeletal_animation" 11 | homepage = "https://github.com/PistonDevelopers/skeletal_animation" 12 | documentation = "https://docs.rs/skeletal_animation" 13 | 14 | [lib] 15 | 16 | name = "skeletal_animation" 17 | path = "src/lib.rs" 18 | 19 | [dependencies] 20 | 21 | rustc-serialize = "0.3.16" 22 | collada = "0.15.0" 23 | vecmath = "1.0.0" 24 | quaternion = "1.0.0" 25 | dual_quaternion = "0.2.0" 26 | gfx = "0.18.1" 27 | piston-gfx_texture = "0.44.0" 28 | interpolation = "0.3.0" 29 | piston-float = "1.0.0" 30 | gfx_debug_draw = "0.33.0" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 PistonDevelopers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skeletal_animation 2 | 3 | [![Build Status](https://travis-ci.org/PistonDevelopers/skeletal_animation.png?branch=master)](https://travis-ci.org/PistonDevelopers/skeletal_animation) 4 | 5 | A Rust library for data-driven skeletal animation. 6 | 7 | [Documentation](http://docs.piston.rs/skeletal_animation/skeletal_animation/) 8 | 9 | [Example Demo](https://github.com/stjahns/skeletal_animation_demo) 10 | 11 | ## Overview 12 | 13 | This library allows you to define animation clips, state machines, and blend trees in JSON to be loaded and reloaded at runtime without needing to recompile your Rust project. 14 | 15 | ## Usage 16 | 17 | ### Asset Definition File 18 | 19 | Animation assets, which currently include AnimationClips, DifferenceClips, and AnimationControllers are declared in defined in a JSON file with the following format: 20 | 21 | ```json 22 | { 23 | "animation_clips": [], 24 | "difference_clips": [], 25 | "animation_controllers": [] 26 | } 27 | ``` 28 | 29 | At runtime, assets can be loaded from one or more definition files through the `AssetManager` as follows: 30 | 31 | ```Rust 32 | let mut asset_manager = AssetManager::::new(); // To use QVTransforms (Quaternions for rotations, Vector3s for translations) 33 | let mut asset_manager = AssetManager::>::new(); // To use DualQuaternions 34 | asset_manager.load_animations("assets/animation_assets.json"); 35 | asset_manager.load_animations("assets/more_animation_assets.json"); 36 | ``` 37 | 38 | #### Animation Clips 39 | 40 | Animation clips are declared as follows: 41 | 42 | ```json 43 | { 44 | "animation_clips": [{ 45 | "name": "walk-forward", 46 | "source": "assets/walk.dae", 47 | }, { 48 | "name": "run-forward", 49 | "source": "assets/run.dae", 50 | }] 51 | } 52 | ``` 53 | 54 | #### Difference Clips 55 | 56 | Difference Clips are animation clips defined by the _difference_ between two animation clips. They are intended to be used by additive blend nodes, 57 | where an additive clip (e.g. head turning to the left) is added to the output of another node (e.g. a walking animation). 58 | 59 | ```json 60 | { 61 | "difference_clips": [{ 62 | "name": "head-look-left-additive", 63 | "source_clip": "head-look-left", 64 | "reference_clip": "reference-pose" 65 | }] 66 | } 67 | ``` 68 | 69 | where: 70 | * `name` should be a unique identifier for the animation clip that can be referenced from the animation controller definition. 71 | * `source_clip` is the path to a COLLADA file containing the desired animation, e.g. a character in "T-Pose" with the head turned left 72 | * `reference_clip` is the path to a COLLADA file containing the desired reference animation, e.g. a character in "T-Pose" 73 | 74 | #### Animation Controllers 75 | 76 | Animation controllers are state machines, which consist of: 77 | * A list of all parameters that will be referenced by state transition conditions and blend tree nodes within this controller. 78 | * A list of states, where each state consists of: 79 | * A uniquely identifying name for the state. 80 | * A blend tree that blends one or more animation clips together according to some parameter values. 81 | * A list of transitions to other states within the same controller, where each transition has: 82 | * A target state name. 83 | * A condition based on some parameter value. 84 | * A duration for the transition. 85 | * The name of the initial state the controller should start in. 86 | 87 | An example controller definition: 88 | 89 | ```json 90 | { 91 | "animation_controllers": [{ 92 | 93 | "name": "human-controller", 94 | "parameters": [ 95 | "forward-speed", 96 | "forward-to-strafe", 97 | "walk-to-run", 98 | "left-to-right" 99 | ], 100 | 101 | "states": [ { 102 | "name": "walking-forward", 103 | "blend_tree": { 104 | "type": "LerpNode", 105 | "param": "forward-to-strafe", 106 | "inputs": [ { 107 | "type": "LerpNode", 108 | "param": "walk-to-run", 109 | "inputs": [{ 110 | "type": "ClipNode", 111 | "clip_source": "walk-forward" 112 | }, { 113 | "type": "ClipNode", 114 | "clip_source": "run-forward" 115 | }] 116 | 117 | }, { 118 | "type": "LerpNode", 119 | "param": "left-to-right", 120 | "inputs": [{ 121 | "type": "ClipNode", 122 | "clip_source": "walk-left", 123 | }, { 124 | "type": "ClipNode", 125 | "clip_source": "walk-right" 126 | }] 127 | }] 128 | }, 129 | 130 | "transitions": [ { 131 | "target_state": "stand-idle", 132 | "duration": 0.5, 133 | "condition": { 134 | "parameter": "forward-speed", 135 | "operator": "<", 136 | "value": 0.1 137 | } 138 | }] 139 | 140 | }, { 141 | "name": "stand-idle", 142 | "blend_tree": { 143 | "type": "ClipNode", 144 | "clip_source": "stand-idle" 145 | }, 146 | "transitions": [ { 147 | "target_state": "walking-forward", 148 | "duration": 0.5, 149 | "condition": { 150 | "parameter": "forward-speed", 151 | "operator": ">", 152 | "value": 0.1 153 | } 154 | } ] 155 | } ], 156 | 157 | "initial_state": "stand-idle" 158 | }] 159 | } 160 | 161 | ``` 162 | 163 | At runtime, after loading into the AssetManger, an `AnimationController` can be initialized as follows: 164 | 165 | ```Rust 166 | // First, need to load the shared skeleton object(eg from a COLLADA document) 167 | // This will become more elegant, i promise :) 168 | let skeleton = { 169 | let collada_document = ColladaDocument::from_path(&Path::new("assets/suit_guy.dae")).unwrap(); 170 | let skeleton_set = collada_document.get_skeletons().unwrap(); 171 | Rc::new(Skeleton::from_collada(&skeleton_set[0])) 172 | } 173 | 174 | // Create the AnimationController from the definition, the skeleton, and the clips previously loaded 175 | // by the animation manager 176 | let controller_def = asset_manager.controller_defs["human-controller"].clone(); 177 | let controller = AnimationController::new(controller_def, skeleton.clone(), &asset_manager.animation_clips); 178 | ``` 179 | 180 | Currently, `skeletal_animation` assumes a Piston-style event loop, where we have separate `update` (with delta-time) and `render` (with extrapolated delta-time since last update) events, so on each `update` in the game loop we need to: 181 | 182 | ```Rust 183 | // Set any relevant parameters on the controller: 184 | controller.set_param_value("forward-speed", 1.8); 185 | 186 | // Update the controller's local clock 187 | controller.update(delta_time); 188 | ``` 189 | 190 | Then, on `render`, we can get the current skeletal pose represented with either matrices or dual-quaternions with: 191 | 192 | ```Rust 193 | // Matrices: 194 | let mut global_poses: [Matrix4; 64] = [ mat4_id(); 64 ]; 195 | controller.get_output_pose(args.ext_dt, &mut global_poses[0 .. skeleton.joints.len()]); 196 | 197 | // DualQuaternions: 198 | let mut global_poses: [DualQuaternion; 64] = [ dual_quaternion::id(); 64 ]; 199 | controller.get_output_pose(args.ext_dt, &mut global_poses[0 .. skeleton.joints.len()]); 200 | ``` 201 | 202 | where `args.ext_dt` is the extrapolated time since the last update. To actually render something with the skeletal pose, you can: 203 | 204 | * Draw the posed skeleton with [gfx_debug_draw](https://github.com/PistonDevelopers/gfx-debug-draw): 205 | ```Rust 206 | skeleton.draw( 207 | &global_poses, // The joint poses output from the controller 208 | &mut debug_renderer, // gfx_debug_draw::DebugRenderer 209 | true, // True to label each joint with their name 210 | ); 211 | ``` 212 | where `skeleton` is the shared skeleton instance. Will work with both `Matrix4` and `DualQuaternion`. 213 | 214 | * Draw a smoothly-skinned, textured mesh with skeletal_animation::SkinnedRenderer: 215 | ```Rust 216 | // On initialization... 217 | 218 | // To use matrices with a Linear Blend Skinning (LBS) shader 219 | let skinned_renderer = SkinnedRenderer::<_, _, Matrix4>::from_collada( 220 | factory, // gfx::Factory instance, to be owned by SkinnedRenderer 221 | collada_document, // the parsed Collada document for the rigged mesh 222 | ["assets/skin.png", "assets/hair.png", "assets/eyes.png"], // Textures for each submesh in the Collada source 223 | ).unwrap(); 224 | 225 | // To use dual-quaternions with a Dual-Quaternion Linear Blend Skinning (DLB) shader 226 | let skinned_renderer = SkinnedRenderer::<_, _, DualQuaternion>::from_collada( 227 | factory, // gfx::Factory instance, to be owned by SkinnedRenderer 228 | collada_document, // the parsed Collada document for the rigged mesh 229 | ["assets/skin.png", "assets/hair.png", "assets/eyes.png"], // Textures for each submesh in the Collada source 230 | ).unwrap(); 231 | 232 | ... 233 | 234 | // Later in event loop... 235 | skinned_renderer.render( 236 | &mut stream, // gfx::Stream 237 | camera_view, // Matrix4 238 | camera_projection, // 239 | &global_poses // The output poses from the controller 240 | ); 241 | ``` 242 | 243 | See the [example demo](https://github.com/stjahns/skeletal_animation_demo) for a more thorough example of usage. 244 | -------------------------------------------------------------------------------- /scripts/id_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/skeletal_animation/c8938b127fe2a89657a23544b270ad5ddba2a0c0/scripts/id_rsa.enc -------------------------------------------------------------------------------- /scripts/travis-doc-upload.cfg: -------------------------------------------------------------------------------- 1 | PROJECT_NAME=skeletal_animation 2 | DOCS_REPO=PistonDevelopers/docs.git 3 | SSH_KEY_TRAVIS_ID=9b26efacdc62 4 | -------------------------------------------------------------------------------- /src/animation.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | use std::rc::Rc; 4 | 5 | use collada::document::ColladaDocument; 6 | use collada; 7 | use float::Radians; 8 | 9 | use math::*; 10 | use skeleton::Skeleton; 11 | use transform::Transform; 12 | 13 | /// A single skeletal pose 14 | #[derive(Debug)] 15 | pub struct AnimationSample 16 | { 17 | 18 | /// Local pose transforms for each joint in the targeted skeleton 19 | /// (relative to parent joint) 20 | pub local_poses: Vec, 21 | 22 | } 23 | 24 | /// A sequence of skeletal pose samples at some sample rate 25 | #[derive(Debug)] 26 | pub struct AnimationClip { 27 | 28 | /// The sequence of skeletal poses 29 | pub samples: Vec>, 30 | 31 | /// Sample rate for the clip. Assumes a constant sample rate. 32 | pub samples_per_second: f32, 33 | 34 | } 35 | 36 | #[derive(Debug, RustcDecodable)] 37 | pub struct AnimationClipDef { 38 | pub name: String, 39 | pub source: String, 40 | pub duration: f32, 41 | pub rotate_z: f32, 42 | } 43 | 44 | #[derive(Debug, RustcDecodable)] 45 | pub struct DifferenceClipDef { 46 | pub name: String, 47 | pub source_clip: String, 48 | pub reference_clip: String, 49 | } 50 | 51 | impl AnimationClip { 52 | 53 | /// `parent_folder` is the folder to search for the `AnimationClip`'s source file 54 | pub fn from_def(clip_def: &AnimationClipDef, parent_folder: PathBuf) -> AnimationClip { 55 | 56 | // Wacky. Shouldn't it be an error if the struct field isn't present? 57 | // FIXME - use an Option 58 | let adjust = if !clip_def.rotate_z.is_nan() { 59 | mat4_rotate_z(clip_def.rotate_z.deg_to_rad()) 60 | } else { 61 | mat4_id() 62 | }; 63 | 64 | // FIXME - load skeleton separately? 65 | let mut source_path = parent_folder; 66 | source_path.push(&clip_def.source); 67 | let collada_document = ColladaDocument::from_path(&source_path).unwrap(); 68 | let animations = collada_document.get_animations().unwrap(); 69 | let skeleton_set = collada_document.get_skeletons().unwrap(); 70 | let skeleton = Skeleton::from_collada(&skeleton_set[0]); 71 | 72 | let mut clip = AnimationClip::from_collada(&skeleton, &animations, &adjust); 73 | 74 | if !clip_def.duration.is_nan() { 75 | clip.set_duration(clip_def.duration); 76 | } 77 | clip 78 | 79 | } 80 | 81 | /// Overrides the sampling rate of the clip to give the given duration (in seconds). 82 | pub fn set_duration(&mut self, duration: f32) { 83 | self.samples_per_second = self.samples.len() as f32 / duration; 84 | } 85 | 86 | /// Return the duration of the clip in seconds 87 | pub fn get_duration(&self) -> f32 { 88 | self.samples.len() as f32 / self.samples_per_second 89 | } 90 | 91 | /// Obtains the interpolated skeletal pose at the given sampling time. 92 | /// 93 | /// # Arguments 94 | /// 95 | /// * `time` - The time to sample with, relative to the start of the animation 96 | /// * `blended_poses` - The output array slice of joint transforms that will be populated 97 | /// for each joint in the skeleton. 98 | pub fn get_pose_at_time(&self, elapsed_time: f32, blended_poses: &mut [T]) { 99 | 100 | let interpolated_index = elapsed_time * self.samples_per_second; 101 | 102 | let index_1 = interpolated_index.floor() as usize; 103 | let index_2 = interpolated_index.ceil() as usize; 104 | 105 | let blend_factor = interpolated_index - index_1 as f32; 106 | 107 | let index_1 = index_1 % self.samples.len(); 108 | let index_2 = index_2 % self.samples.len(); 109 | 110 | let sample_1 = &self.samples[index_1]; 111 | let sample_2 = &self.samples[index_2]; 112 | 113 | 114 | for i in 0 .. sample_1.local_poses.len() { 115 | 116 | let pose_1 = sample_1.local_poses[i]; 117 | let pose_2 = sample_2.local_poses[i]; 118 | 119 | let blended_pose = &mut blended_poses[i]; 120 | *blended_pose = pose_1.lerp(pose_2, blend_factor); 121 | } 122 | 123 | } 124 | 125 | /// Create a difference clip from a source and reference clip for additive blending. 126 | pub fn as_difference_clip(source_clip: &AnimationClip, reference_clip: &AnimationClip) -> AnimationClip { 127 | 128 | let samples = (0 .. source_clip.samples.len()).map(|sample_index| { 129 | 130 | let ref source_sample = source_clip.samples[sample_index]; 131 | 132 | // Extrapolate reference clip by wrapping, if reference clip is shorter than source clip 133 | let ref reference_sample = reference_clip.samples[sample_index % reference_clip.samples.len()]; 134 | 135 | let difference_poses = (0 .. source_sample.local_poses.len()).map(|joint_index| { 136 | let source_pose = source_sample.local_poses[joint_index]; 137 | let reference_pose = reference_sample.local_poses[joint_index]; 138 | reference_pose.inverse().concat(source_pose) 139 | }).collect(); 140 | 141 | AnimationSample { 142 | local_poses: difference_poses, 143 | } 144 | 145 | }).collect(); 146 | 147 | AnimationClip { 148 | samples_per_second: source_clip.samples_per_second, 149 | samples: samples, 150 | } 151 | } 152 | 153 | /// Creates an `AnimationClip` from a collection of `collada::Animation`. 154 | /// 155 | /// # Arguments 156 | /// 157 | /// * `skeleton` - The `Skeleton` that the `AnimationClip` will be created for. 158 | /// * `animations` - The collection of `collada::Animation`s that will be converted into an 159 | /// `AnimationClip`, using the given `Skeleton`. 160 | /// * `transform` - An offset transform to apply to the root pose of each animation sample, 161 | /// useful for applying rotation, translation, or scaling when loading an 162 | /// animation. 163 | pub fn from_collada(skeleton: &Skeleton, animations: &Vec, transform: &Matrix4) -> AnimationClip { 164 | use std::f32::consts::PI; 165 | 166 | // Z-axis is 'up' in COLLADA, so need to rotate root pose about x-axis so y-axis is 'up' 167 | let rotate_on_x = 168 | [ 169 | [1.0, 0.0, 0.0, 0.0], 170 | [0.0, (PI/2.0).cos(), (PI/2.0).sin(), 0.0], 171 | [0.0, (-PI/2.0).sin(), (PI/2.0).cos(), 0.0], 172 | [0.0, 0.0, 0.0, 1.0], 173 | ]; 174 | 175 | let transform = row_mat4_mul(rotate_on_x, *transform); 176 | 177 | // Build an index of joint names to anims 178 | let mut joint_animations = HashMap::new(); 179 | for anim in animations.iter() { 180 | let joint_name = anim.target.split('/').next().unwrap(); 181 | joint_animations.insert(joint_name, anim); 182 | } 183 | 184 | // Assuming all ColladaAnims have the same number of samples.. 185 | let sample_count = animations[0].sample_times.len(); 186 | 187 | // Assuming all ColladaAnims have the same duration.. 188 | let duration = *animations[0].sample_times.last().unwrap(); 189 | 190 | // Assuming constant sample rate 191 | let samples_per_second = sample_count as f32 / duration; 192 | 193 | let samples = (0 .. sample_count).map(|sample_index| { 194 | 195 | // Grab local poses for each joint from COLLADA animation if available, 196 | // falling back to identity matrix 197 | let local_poses: Vec> = skeleton.joints.iter().map(|joint| { 198 | match joint_animations.get(&joint.name[..]) { 199 | Some(a) if joint.is_root() => row_mat4_mul(transform, a.sample_poses[sample_index]), 200 | Some(a) => a.sample_poses[sample_index], 201 | None => mat4_id(), 202 | } 203 | }).collect(); 204 | 205 | // Convert local poses to Transforms (for interpolation) 206 | let local_poses: Vec = local_poses.iter().map(|pose_matrix| { 207 | T::from_matrix(*pose_matrix) 208 | }).collect(); 209 | 210 | AnimationSample { 211 | local_poses: local_poses, 212 | } 213 | }).collect(); 214 | 215 | AnimationClip { 216 | samples_per_second: samples_per_second, 217 | samples: samples, 218 | } 219 | } 220 | 221 | } 222 | 223 | /// An instance of an AnimationClip which tracks playback parameters 224 | pub struct ClipInstance { 225 | /// Shared clip reference 226 | pub clip: Rc>, 227 | 228 | /// Controller clock time at animation start 229 | pub start_time: f32, 230 | 231 | /// Playback rate modifier, where 1.0 is original speed 232 | pub playback_rate: f32, 233 | 234 | /// Used to account for changes in playback rate 235 | pub time_offset: f32, 236 | } 237 | 238 | impl ClipInstance { 239 | 240 | pub fn new(clip: Rc>) -> ClipInstance { 241 | ClipInstance { 242 | clip: clip, 243 | start_time: 0.0, 244 | playback_rate: 1.0, 245 | time_offset: 0.0, 246 | } 247 | } 248 | 249 | /// Adjust the playback rate of the clip without affecting the 250 | /// value of get_local_time for a given global time. 251 | pub fn set_playback_rate(&mut self, global_time: f32, new_rate: f32) { 252 | if self.playback_rate != new_rate { 253 | let local_time = self.get_local_time(global_time); 254 | self.time_offset = local_time - (global_time - self.start_time) * new_rate; 255 | self.playback_rate = new_rate; 256 | } 257 | } 258 | 259 | pub fn get_pose_at_time(&self, global_time: f32, blended_poses: &mut [T]) { 260 | self.clip.get_pose_at_time(self.get_local_time(global_time), blended_poses); 261 | } 262 | 263 | pub fn get_duration(&self) -> f32 { 264 | self.clip.get_duration() 265 | } 266 | 267 | fn get_local_time(&self, global_time: f32) -> f32 { 268 | (global_time - self.start_time) * self.playback_rate + self.time_offset 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/blend_tree.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use rustc_serialize::{Decodable, Decoder}; 5 | 6 | use animation::{AnimationClip, ClipInstance}; 7 | use skeleton::{Skeleton, JointIndex}; 8 | 9 | use transform::Transform; 10 | use math::*; 11 | 12 | /// Identifier for an AnimationClip within a BlendTreeNodeDef 13 | pub type ClipId = String; 14 | 15 | /// Identifier for animation controller parameter, within a LerpNode 16 | pub type ParamId = String; 17 | 18 | /// Definition of a blend tree, used by AnimationController to construct an AnimBlendTree 19 | #[derive(Debug, Clone)] 20 | pub enum BlendTreeNodeDef { 21 | LerpNode(Box, Box, ParamId), 22 | AdditiveNode(Box, Box, ParamId), 23 | IKNode(Box, String, ParamId, ParamId, ParamId, ParamId, ParamId, ParamId, ParamId), 24 | ClipNode(ClipId), 25 | } 26 | 27 | impl Decodable for BlendTreeNodeDef { 28 | fn decode(decoder: &mut D) -> Result { 29 | decoder.read_struct("root", 0, |decoder| { 30 | 31 | let node_type = decoder.read_struct_field("type", 0, |decoder| { Ok(decoder.read_str()?) })?; 32 | 33 | match &node_type[..] { 34 | "LerpNode" => { 35 | 36 | let (input_1, input_2) = decoder.read_struct_field("inputs", 0, |decoder| { 37 | decoder.read_seq(|decoder, _len| { 38 | Ok(( 39 | decoder.read_seq_elt(0, Decodable::decode)?, 40 | decoder.read_seq_elt(1, Decodable::decode)? 41 | )) 42 | }) 43 | })?; 44 | 45 | let blend_param_name = decoder.read_struct_field("param", 0, |decoder| { Ok(decoder.read_str()?) })?; 46 | 47 | Ok(BlendTreeNodeDef::LerpNode(Box::new(input_1), Box::new(input_2), blend_param_name)) 48 | 49 | }, 50 | "AdditiveNode" => { 51 | 52 | let (input_1, input_2) = decoder.read_struct_field("inputs", 0, |decoder| { 53 | decoder.read_seq(|decoder, _len| { 54 | Ok(( 55 | decoder.read_seq_elt(0, Decodable::decode)?, 56 | decoder.read_seq_elt(1, Decodable::decode)? 57 | )) 58 | }) 59 | })?; 60 | 61 | let blend_param_name = decoder.read_struct_field("param", 0, |decoder| { Ok(decoder.read_str()?) })?; 62 | 63 | Ok(BlendTreeNodeDef::AdditiveNode(Box::new(input_1), Box::new(input_2), blend_param_name)) 64 | 65 | }, 66 | "IKNode" => { 67 | 68 | let input = decoder.read_struct_field("input", 0, Decodable::decode)?; 69 | 70 | let effector_name = decoder.read_struct_field("effector", 0, |decoder| { Ok(decoder.read_str()?) })?; 71 | 72 | let blend_param_name = decoder.read_struct_field("blend_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 73 | 74 | let target_x_name = decoder.read_struct_field("target_x_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 75 | let target_y_name = decoder.read_struct_field("target_y_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 76 | let target_z_name = decoder.read_struct_field("target_z_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 77 | 78 | let bend_x_name = decoder.read_struct_field("bend_x_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 79 | let bend_y_name = decoder.read_struct_field("bend_y_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 80 | let bend_z_name = decoder.read_struct_field("bend_z_param", 0, |decoder| { Ok(decoder.read_str()?) })?; 81 | 82 | 83 | Ok(BlendTreeNodeDef::IKNode(Box::new(input), 84 | effector_name, 85 | blend_param_name, 86 | target_x_name, 87 | target_y_name, 88 | target_z_name, 89 | bend_x_name, 90 | bend_y_name, 91 | bend_z_name)) 92 | 93 | }, 94 | "ClipNode" => { 95 | let clip_source = decoder.read_struct_field("clip_source", 0, |decoder| { Ok(decoder.read_str()?) })?; 96 | Ok(BlendTreeNodeDef::ClipNode(clip_source)) 97 | } 98 | _ => panic!("Unexpected blend node type") 99 | } 100 | }) 101 | } 102 | } 103 | 104 | /// A tree of AnimNodes 105 | pub struct AnimBlendTree { 106 | root_node: AnimNodeHandle, 107 | lerp_nodes: Vec, 108 | additive_nodes: Vec, 109 | ik_nodes: Vec, 110 | clip_nodes: Vec>, 111 | skeleton: Rc, 112 | } 113 | 114 | impl AnimBlendTree { 115 | 116 | /// Initialize a new AnimBlendTree from the root BlendTreeNodeDef and 117 | /// a mapping from animation names to AnimationClip 118 | /// 119 | /// # Arguments 120 | /// 121 | /// * `def` - The root BlendTreeNodeDef 122 | /// * `animations` - A mapping from ClipIds to shared AnimationClip instances 123 | pub fn from_def( 124 | def: BlendTreeNodeDef, 125 | animations: &HashMap>>, 126 | skeleton: Rc, 127 | ) -> AnimBlendTree { 128 | 129 | let mut tree = AnimBlendTree { 130 | root_node: AnimNodeHandle::None, 131 | lerp_nodes: Vec::new(), 132 | additive_nodes: Vec::new(), 133 | ik_nodes: Vec::new(), 134 | clip_nodes: Vec::new(), 135 | skeleton: skeleton.clone() 136 | }; 137 | 138 | tree.root_node = tree.add_node(def, animations, &skeleton); 139 | tree 140 | } 141 | 142 | /// Get the output skeletal pose from the blend tree for the given time and parameters 143 | /// 144 | /// # Arguments 145 | /// 146 | /// * `time` - The time to sample from any AnimationClips 147 | /// * `params` - A mapping from ParamIds to their current parameter values 148 | /// * `output_poses` - The output array slice of joint transforms that will be populated 149 | /// according to the defined output for this BlendTreeNode 150 | pub fn get_output_pose(&self, time: f32, params: &HashMap, output_poses: &mut [T]) { 151 | if let Some(ref node) = self.get_node(self.root_node.clone()) { 152 | node.get_output_pose(self, time, params, output_poses); 153 | } 154 | } 155 | 156 | /// For each LerpNode with two animation clips, synchronize their playback rates according to the blend parameter 157 | /// 158 | /// # Arguments 159 | /// 160 | /// * `global_time` - The current global clock time from the controller 161 | /// * `params` - A mapping from ParamIds to their current parameter values 162 | pub fn synchronize(&mut self, global_time: f32, params: &HashMap) { 163 | for lerp_node in self.lerp_nodes.iter() { 164 | if let (AnimNodeHandle::ClipAnimNodeHandle(clip_1), AnimNodeHandle::ClipAnimNodeHandle(clip_2)) = (lerp_node.input_1.clone(), lerp_node.input_2.clone()) { 165 | let blend_parameter = params[&lerp_node.blend_param[..]]; 166 | 167 | let target_length = { 168 | let clip_1 = &self.clip_nodes[clip_1].clip; 169 | let clip_2 = &self.clip_nodes[clip_2].clip; 170 | 171 | let length_1 = clip_1.get_duration(); 172 | let length_2 = clip_2.get_duration(); 173 | 174 | (1.0 - blend_parameter) * length_1 + blend_parameter * length_2 175 | }; 176 | 177 | { 178 | let clip_1 = &mut self.clip_nodes[clip_1].clip; 179 | let length = clip_1.get_duration(); 180 | clip_1.set_playback_rate(global_time, length / target_length); 181 | } 182 | 183 | { 184 | let clip_2 = &mut self.clip_nodes[clip_2].clip; 185 | let length = clip_2.get_duration(); 186 | clip_2.set_playback_rate(global_time, length / target_length); 187 | } 188 | } 189 | } 190 | } 191 | 192 | fn add_node( 193 | &mut self, 194 | def: BlendTreeNodeDef, 195 | animations: &HashMap>>, 196 | skeleton: &Skeleton 197 | ) -> AnimNodeHandle { 198 | match def { 199 | BlendTreeNodeDef::LerpNode(input_1, input_2, param_id) => { 200 | let input_1_handle = self.add_node(*input_1, animations, skeleton); 201 | let input_2_handle = self.add_node(*input_2, animations, skeleton); 202 | self.lerp_nodes.push(LerpAnimNode { 203 | input_1: input_1_handle, 204 | input_2: input_2_handle, 205 | blend_param: param_id.clone() 206 | }); 207 | AnimNodeHandle::LerpAnimNodeHandle(self.lerp_nodes.len() - 1) 208 | } 209 | BlendTreeNodeDef::AdditiveNode(input_1, input_2, param_id) => { 210 | let input_1_handle = self.add_node(*input_1, animations, skeleton); 211 | let input_2_handle = self.add_node(*input_2, animations, skeleton); 212 | self.additive_nodes.push(AdditiveAnimNode { 213 | base_input: input_1_handle, 214 | additive_input: input_2_handle, 215 | blend_param: param_id.clone() 216 | }); 217 | AnimNodeHandle::AdditiveAnimNodeHandle(self.additive_nodes.len() - 1) 218 | } 219 | BlendTreeNodeDef::IKNode(input, effector_name, blend_param, target_x_param, target_y_param, target_z_param, bend_x_param, bend_y_param, bend_z_param) => { 220 | let input_handle = self.add_node(*input, animations, skeleton); 221 | self.ik_nodes.push(IKNode { 222 | input: input_handle, 223 | blend_param: blend_param.clone(), 224 | target_x_param: target_x_param.clone(), 225 | target_y_param: target_y_param.clone(), 226 | target_z_param: target_z_param.clone(), 227 | bend_x_param: bend_x_param.clone(), 228 | bend_y_param: bend_y_param.clone(), 229 | bend_z_param: bend_z_param.clone(), 230 | effector_bone_index: skeleton.get_joint_index(&effector_name).unwrap(), 231 | 232 | }); 233 | AnimNodeHandle::IKAnimNodeHandle(self.ik_nodes.len() - 1) 234 | } 235 | BlendTreeNodeDef::ClipNode(clip_id) => { 236 | let clip = animations.get(&clip_id[..]).expect(&format!("Missing animation clip: {}", clip_id)[..]); 237 | self.clip_nodes.push(ClipAnimNode { 238 | clip: ClipInstance::new(clip.clone()) 239 | }); 240 | AnimNodeHandle::ClipAnimNodeHandle(self.clip_nodes.len() - 1) 241 | } 242 | } 243 | } 244 | 245 | fn get_node(&self, handle: AnimNodeHandle) -> Option<&dyn AnimNode> { 246 | match handle { 247 | AnimNodeHandle::LerpAnimNodeHandle(i) => Some(&self.lerp_nodes[i]), 248 | AnimNodeHandle::AdditiveAnimNodeHandle(i) => Some(&self.additive_nodes[i]), 249 | AnimNodeHandle::ClipAnimNodeHandle(i) => Some(&self.clip_nodes[i]), 250 | AnimNodeHandle::IKAnimNodeHandle(i) => Some(&self.ik_nodes[i]), 251 | AnimNodeHandle::None => None, 252 | } 253 | } 254 | } 255 | 256 | pub trait AnimNode { 257 | fn get_output_pose(&self, tree: &AnimBlendTree, time: f32, params: &HashMap, output_poses: &mut [T]); 258 | } 259 | 260 | #[derive(Clone)] 261 | pub enum AnimNodeHandle { 262 | None, 263 | LerpAnimNodeHandle(usize), 264 | AdditiveAnimNodeHandle(usize), 265 | ClipAnimNodeHandle(usize), 266 | IKAnimNodeHandle(usize), 267 | } 268 | 269 | /// An AnimNode where pose output is linear blend between the output of the two input AnimNodes, 270 | /// with blend factor according the blend_param value 271 | pub struct LerpAnimNode { 272 | input_1: AnimNodeHandle, 273 | input_2: AnimNodeHandle, 274 | blend_param: ParamId 275 | } 276 | 277 | impl AnimNode for LerpAnimNode { 278 | fn get_output_pose(&self, tree: &AnimBlendTree, time: f32, params: &HashMap, output_poses: &mut [T]) { 279 | 280 | let mut input_poses = [ T::identity(); 64 ]; 281 | let sample_count = output_poses.len(); 282 | 283 | let blend_parameter = params[&self.blend_param[..]]; 284 | 285 | if let Some(ref node) = tree.get_node(self.input_1.clone()) { 286 | node.get_output_pose(tree, time, params, &mut input_poses[0 .. sample_count]); 287 | } 288 | 289 | if let Some(ref node) = tree.get_node(self.input_2.clone()) { 290 | node.get_output_pose(tree, time, params, output_poses); 291 | } 292 | 293 | for i in 0 .. output_poses.len() { 294 | let pose_1 = input_poses[i]; 295 | let pose_2 = &mut output_poses[i]; 296 | (*pose_2) = pose_1.lerp(pose_2.clone(), blend_parameter); 297 | } 298 | } 299 | } 300 | 301 | /// An AnimNode where pose output is additive blend with output of additive_input 302 | /// added to base_input, with blend factor according to value of blend_param 303 | pub struct AdditiveAnimNode { 304 | base_input: AnimNodeHandle, 305 | additive_input: AnimNodeHandle, 306 | blend_param: ParamId 307 | } 308 | 309 | impl AnimNode for AdditiveAnimNode { 310 | fn get_output_pose(&self, tree: &AnimBlendTree, time: f32, params: &HashMap, output_poses: &mut [T]) { 311 | 312 | let mut input_poses = [ T::identity(); 64 ]; 313 | let sample_count = output_poses.len(); 314 | 315 | let blend_parameter = params[&self.blend_param[..]]; 316 | 317 | if let Some(ref node) = tree.get_node(self.base_input.clone()) { 318 | node.get_output_pose(tree, time, params, &mut input_poses[0 .. sample_count]); 319 | } 320 | 321 | if let Some(ref node) = tree.get_node(self.additive_input.clone()) { 322 | node.get_output_pose(tree, time, params, output_poses); 323 | } 324 | 325 | for i in 0 .. output_poses.len() { 326 | let pose_1 = input_poses[i]; 327 | let pose_2 = &mut output_poses[i]; 328 | let additive_pose = T::identity().lerp(pose_2.clone(), blend_parameter); 329 | (*pose_2) = pose_1.concat(additive_pose); 330 | } 331 | } 332 | } 333 | 334 | /// An AnimNode where pose output is from an animation ClipInstance 335 | pub struct ClipAnimNode { 336 | clip: ClipInstance 337 | } 338 | 339 | impl AnimNode for ClipAnimNode { 340 | fn get_output_pose(&self, _tree: &AnimBlendTree, time: f32, _params: &HashMap, output_poses: &mut [T]) { 341 | self.clip.get_pose_at_time(time, output_poses); 342 | } 343 | } 344 | 345 | pub struct IKNode { 346 | input: AnimNodeHandle, 347 | blend_param: ParamId, 348 | target_x_param: ParamId, 349 | target_y_param: ParamId, 350 | target_z_param: ParamId, 351 | bend_x_param: ParamId, 352 | bend_y_param: ParamId, 353 | bend_z_param: ParamId, 354 | effector_bone_index: JointIndex, 355 | } 356 | 357 | impl AnimNode for IKNode { 358 | fn get_output_pose(&self, tree: &AnimBlendTree, time: f32, params: &HashMap, output_poses: &mut [T]) { 359 | 360 | // Get input pose 361 | if let Some(ref node) = tree.get_node(self.input.clone()) { 362 | node.get_output_pose(tree, time, params, output_poses); 363 | } 364 | 365 | // Target position should be in model-space 366 | let effector_target_position = [params[&self.target_x_param[..]], 367 | params[&self.target_y_param[..]], 368 | params[&self.target_z_param[..]]]; 369 | 370 | 371 | let effector_bone_index = self.effector_bone_index; 372 | let middle_bone_index = tree.skeleton.joints[effector_bone_index as usize].parent_index; 373 | let root_bone_index = tree.skeleton.joints[middle_bone_index as usize].parent_index; 374 | let root_bone_parent_index = tree.skeleton.joints[root_bone_index as usize].parent_index; 375 | 376 | // Get bone positions in model-space by calculating global poses 377 | let mut global_poses = [ Matrix4::::identity(); 64 ]; 378 | tree.skeleton.calculate_global_poses(output_poses, &mut global_poses); 379 | 380 | let root_bone_position = global_poses[root_bone_index as usize].transform_vector([0.0, 0.0, 0.0]); 381 | let middle_bone_position = global_poses[middle_bone_index as usize].transform_vector([0.0, 0.0, 0.0]); 382 | let effector_bone_position = global_poses[effector_bone_index as usize].transform_vector([0.0, 0.0, 0.0]); 383 | 384 | let length_1 = vec3_len(vec3_sub(root_bone_position, middle_bone_position)); 385 | let length_2 = vec3_len(vec3_sub(middle_bone_position, effector_bone_position)); 386 | 387 | // get effector target position on a 2D bend plane, 388 | // with coordinates relative to root bone position 389 | 390 | // x axis of bend plane 391 | let root_to_effector = vec3_normalized(vec3_sub(effector_target_position, root_bone_position)); 392 | 393 | // z axis of bend plane 394 | let plane_normal = { 395 | let bend_direction = [params[&self.bend_x_param[..]], 396 | params[&self.bend_y_param[..]], 397 | params[&self.bend_z_param[..]]]; 398 | if vec3_len(bend_direction) == 0.0 { 399 | // Choose a somewhat arbitary bend normal: 400 | vec3_normalized(vec3_cross(vec3_sub(middle_bone_position, root_bone_position), 401 | root_to_effector)) 402 | } else { 403 | // Use desired bend direction: 404 | let desired_bend_direction = vec3_normalized(bend_direction); 405 | vec3_normalized(vec3_cross(desired_bend_direction, 406 | root_to_effector)) 407 | } 408 | }; 409 | 410 | // y axis of bend plane 411 | let plane_y_direction = vec3_normalized(vec3_cross(root_to_effector, plane_normal)); 412 | 413 | let plane_rotation = [ 414 | root_to_effector, 415 | plane_y_direction, 416 | plane_normal 417 | ]; 418 | 419 | // Convert to 2D coords on the plane, where parent root is at (0,0) and target is at (x,y) 420 | let plane_target = row_mat3_transform(plane_rotation, 421 | vec3_sub(effector_target_position, 422 | root_bone_position)); 423 | 424 | if let Some(elbow_target) = solve_ik_2d(length_1, length_2, [plane_target[0], plane_target[1]]) { 425 | 426 | // Copy input poses into IK target poses 427 | let mut target_poses = [ T::identity(); 64 ]; 428 | for i in 0 .. 64 { 429 | target_poses[i] = output_poses[i]; 430 | } 431 | 432 | let middle_bone_plane = [elbow_target[0], elbow_target[1], 0.0]; 433 | 434 | let middle_bone_target = vec3_add(row_mat3_transform(mat3_inv(plane_rotation), 435 | middle_bone_plane), 436 | root_bone_position); 437 | 438 | // calculate root bone pose 439 | { 440 | let original_direction = vec3_normalized(vec3_sub(middle_bone_position, root_bone_position)); 441 | let target_direction = vec3_normalized(vec3_sub(middle_bone_target, root_bone_position)); 442 | let rotation_change = quaternion::rotation_from_to(target_direction, original_direction); 443 | let original_rotation = global_poses[root_bone_index as usize].get_rotation(); 444 | let new_rotation = quaternion::mul(original_rotation, rotation_change); 445 | 446 | global_poses[root_bone_index as usize].set_rotation(new_rotation); 447 | 448 | let local_pose = row_mat4_mul(mat4_inv(global_poses[root_bone_parent_index as usize]), global_poses[root_bone_index as usize]); 449 | target_poses[root_bone_index as usize] = T::from_matrix(local_pose); 450 | } 451 | 452 | // calculate middle bone pose 453 | { 454 | let original_direction = vec3_normalized(vec3_sub(effector_bone_position, middle_bone_position)); 455 | let target_direction = vec3_normalized(vec3_sub(effector_target_position, middle_bone_target)); 456 | let rotation_change = quaternion::rotation_from_to(target_direction, original_direction); 457 | let original_rotation = global_poses[middle_bone_index as usize].get_rotation(); 458 | let new_rotation = quaternion::mul(original_rotation, rotation_change); 459 | 460 | global_poses[middle_bone_index as usize].set_rotation(new_rotation); 461 | global_poses[middle_bone_index as usize].set_translation(middle_bone_target); 462 | 463 | let local_pose = row_mat4_mul(mat4_inv(global_poses[root_bone_index as usize]), global_poses[middle_bone_index as usize]); 464 | target_poses[middle_bone_index as usize] = T::from_matrix(local_pose); 465 | } 466 | 467 | // Blend between input and IK target poses 468 | 469 | let blend_parameter = params[&self.blend_param[..]]; 470 | for i in 0 .. output_poses.len() { 471 | let ik_pose = target_poses[i]; 472 | let output_pose = &mut output_poses[i]; 473 | (*output_pose) = output_pose.lerp(ik_pose.clone(), blend_parameter); 474 | } 475 | } 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/controller.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use rustc_serialize::{Decodable, Decoder}; 5 | 6 | use animation::AnimationClip; 7 | use transform::{Transform, FromTransform}; 8 | use blend_tree::{AnimBlendTree, BlendTreeNodeDef, ClipId}; 9 | use skeleton::Skeleton; 10 | 11 | const MAX_JOINTS: usize = 64; 12 | 13 | /// A state that an AnimationController can be in, consisting 14 | /// of a blend tree and a collection of transitions to other states 15 | pub struct AnimationState { 16 | 17 | /// The blend tree used to determine the final blended pose for this state 18 | pub blend_tree: AnimBlendTree, 19 | 20 | /// Transitions from this state to other AnimationStates 21 | pub transitions: Vec, 22 | } 23 | 24 | /// Representation of a state transition to a target state, with a condition and a duration 25 | #[derive(Debug, Clone, RustcDecodable)] 26 | pub struct AnimationTransition { 27 | /// The name of the target state to transition to 28 | pub target_state: String, 29 | 30 | /// The condition that will be checked in order to determine 31 | /// if the controller should transition to the target state 32 | pub condition: TransitionCondition, 33 | 34 | /// The duration of the transition, during which a linear blend 35 | /// transition between the current and target states should occur 36 | pub duration: f32, 37 | } 38 | 39 | /// Representation of a condition to check for an AnimationTransition 40 | #[derive(Debug, Clone, RustcDecodable)] 41 | pub struct TransitionCondition { 42 | 43 | /// The name of the controller parameter to compare with 44 | pub parameter: String, 45 | 46 | /// The comparision operator to use 47 | pub operator: Operator, 48 | 49 | /// The constant value to compare with the controller parameter value 50 | pub value: f32, 51 | } 52 | 53 | impl TransitionCondition { 54 | /// Returns true if the condition is satisfied 55 | pub fn is_true(&self, parameters: &HashMap) -> bool { 56 | match self.operator { 57 | Operator::LessThan => parameters[&self.parameter[..]] < self.value, 58 | Operator::GreaterThan => parameters[&self.parameter[..]] > self.value, 59 | Operator::LessThanEqual => parameters[&self.parameter[..]] <= self.value, 60 | Operator::GreaterThanEqual => parameters[&self.parameter[..]] >= self.value, 61 | Operator::Equal => parameters[&self.parameter[..]] == self.value, 62 | Operator::NotEqual => parameters[&self.parameter[..]] != self.value, 63 | } 64 | } 65 | } 66 | 67 | #[derive(Debug, Clone)] 68 | pub enum Operator { 69 | LessThan, 70 | LessThanEqual, 71 | GreaterThan, 72 | GreaterThanEqual, 73 | Equal, 74 | NotEqual, 75 | } 76 | 77 | impl Decodable for Operator { 78 | fn decode(decoder: &mut D) -> Result { 79 | match &decoder.read_str()?[..] { 80 | "<" => Ok(Operator::LessThan), 81 | ">" => Ok(Operator::GreaterThan), 82 | "<=" => Ok(Operator::LessThanEqual), 83 | ">=" => Ok(Operator::GreaterThanEqual), 84 | "=" => Ok(Operator::Equal), 85 | "!=" => Ok(Operator::NotEqual), 86 | _ => Ok(Operator::Equal), // FIXME -- figure out how to throw a D::Error... 87 | } 88 | } 89 | } 90 | 91 | /// Definition struct for an AnimationController, which can be deserialized from JSON 92 | /// and converted to an AnimationController instance at runtime 93 | #[derive(Clone, Debug, RustcDecodable)] 94 | pub struct AnimationControllerDef { 95 | 96 | /// Identifying name for the controller definition 97 | pub name: String, 98 | 99 | /// Declaration list of all parameters that are used by the AnimationController, 100 | /// including state transition conditions and blend tree parameters 101 | pub parameters: Vec, 102 | 103 | /// List of animation state definitions 104 | pub states: Vec, 105 | 106 | /// The name of the state that the AnimationController should start in 107 | pub initial_state: String, 108 | } 109 | 110 | /// Definition struct for an AnimationState, which can be deserialized from JSON 111 | /// and converted to an AnimationState instance at runtime 112 | #[derive(Clone, Debug)] 113 | pub struct AnimationStateDef { 114 | 115 | /// The identifying name for the state 116 | pub name: String, 117 | 118 | /// The blend tree definition for this state 119 | pub blend_tree: BlendTreeNodeDef, 120 | 121 | /// The transitions to other states that can occur from this state 122 | pub transitions: Vec, 123 | } 124 | 125 | impl Decodable for AnimationStateDef { 126 | fn decode(decoder: &mut D) -> Result { 127 | decoder.read_struct("root", 0, |decoder| { 128 | 129 | let name = decoder.read_struct_field("name", 0, |decoder| { 130 | Ok(decoder.read_str()?) 131 | })?; 132 | 133 | let blend_tree = decoder.read_struct_field("blend_tree", 0, Decodable::decode)?; 134 | 135 | let transitions = decoder.read_struct_field("transitions", 0, |decoder| { 136 | decoder.read_seq(|decoder, len| { 137 | let mut transitions = Vec::new(); 138 | for i in 0 .. len { 139 | transitions.push(decoder.read_seq_elt(i, Decodable::decode)?); 140 | } 141 | Ok(transitions) 142 | }) 143 | })?; 144 | 145 | Ok(AnimationStateDef { 146 | name: name, 147 | blend_tree: blend_tree, 148 | transitions: transitions, 149 | }) 150 | }) 151 | } 152 | } 153 | 154 | 155 | /// A runtime representation of an Animation State Machine, consisting of one or more 156 | /// AnimationStates connected by AnimationTransitions, where the output animation 157 | /// pose depends on the current state or any active transitions between states. 158 | pub struct AnimationController { 159 | 160 | /// Parameters that will be referenced by blend tree nodes and animation states 161 | parameters: HashMap, 162 | 163 | /// Shared reference to the skeleton this controller is using 164 | skeleton: Rc, 165 | 166 | /// Tracks seconds since controller started running 167 | local_clock: f64, 168 | 169 | /// Playback speed multiplier. 170 | playback_speed: f64, 171 | 172 | /// Mapping of all animation state names to their instances 173 | states: HashMap>, 174 | 175 | /// The name of the current active AnimationState 176 | current_state: String, 177 | 178 | /// The current active AnimationTransition and its start time, if any 179 | transition: Option<(f64, AnimationTransition)>, 180 | } 181 | 182 | 183 | 184 | impl AnimationController { 185 | 186 | /// Create an AnimationController instance from its definition, the desired skeleton, and a 187 | /// collection of currently loaded animation clips. 188 | pub fn new(controller_def: AnimationControllerDef, skeleton: Rc, animations: &HashMap>>) -> AnimationController { 189 | 190 | let mut parameters = HashMap::new(); 191 | 192 | for parameter in controller_def.parameters.iter() { 193 | parameters.insert(parameter.clone(), 0.0); 194 | }; 195 | 196 | let mut states = HashMap::new(); 197 | for state_def in controller_def.states.iter() { 198 | 199 | let mut blend_tree = AnimBlendTree::from_def(state_def.blend_tree.clone(), animations, skeleton.clone()); 200 | blend_tree.synchronize(0.0, ¶meters); 201 | 202 | states.insert(state_def.name.clone(), AnimationState { 203 | blend_tree: blend_tree, 204 | transitions: state_def.transitions.clone() 205 | }); 206 | 207 | } 208 | 209 | AnimationController { 210 | parameters: parameters, 211 | skeleton: skeleton.clone(), 212 | local_clock: 0.0, 213 | playback_speed: 1.0, 214 | states: states, 215 | current_state: controller_def.initial_state, 216 | transition: None, 217 | } 218 | } 219 | 220 | /// Update the controller's local clock with the given time delta 221 | pub fn update(&mut self, delta_time: f64) { 222 | self.local_clock += delta_time * self.playback_speed; 223 | } 224 | 225 | /// Checks if controller should transition to a different state, or if currently 226 | /// in a transition, checks if the transition is complete 227 | fn update_state(&mut self, ext_dt: f64) { 228 | match self.transition.clone() { 229 | Some((ref start_time, ref transition)) => { 230 | // If transition is finished, switch state to new transition 231 | if self.local_clock + ext_dt >= start_time + transition.duration as f64{ 232 | self.current_state = transition.target_state.clone(); 233 | self.transition = None; 234 | } 235 | }, 236 | None => { 237 | 238 | // Check for any transitions with passing conditions 239 | let current_state = &self.states[&self.current_state[..]]; 240 | for transition in current_state.transitions.iter() { 241 | 242 | if transition.condition.is_true(&self.parameters) { 243 | self.transition = Some((self.local_clock + ext_dt, transition.clone())); 244 | break; 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | /// Set the playback speed for the controller 252 | pub fn set_playback_speed(&mut self, speed: f64) { 253 | self.playback_speed = speed; 254 | } 255 | 256 | /// Set the value for the given controller parameter 257 | pub fn set_param_value(&mut self, name: &str, value: f32) { 258 | self.parameters.insert(name.to_string(), value); // :( 259 | } 260 | 261 | /// Return the value for the given controller parameter 262 | pub fn get_param_value(&self, name: &str) -> f32 { 263 | self.parameters[name] 264 | } 265 | 266 | /// Return a read-only reference to the controller parameter map 267 | pub fn get_parameters(&self) -> &HashMap { 268 | &self.parameters 269 | } 270 | 271 | /// Calculate global skeletal joint poses for the given time since last update 272 | pub fn get_output_pose>(&mut self, ext_dt: f64, output_poses: &mut [TOutput]) { 273 | 274 | self.update_state(ext_dt); 275 | 276 | let elapsed_time = self.local_clock + ext_dt * self.playback_speed; 277 | 278 | let mut local_poses = [ T::identity(); MAX_JOINTS ]; 279 | 280 | { 281 | let current_state = self.states.get_mut(&self.current_state[..]).unwrap(); 282 | current_state.blend_tree.synchronize(elapsed_time as f32, &self.parameters); 283 | current_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut local_poses[..]); 284 | } 285 | 286 | if let Some((transition_start_time, ref transition)) = self.transition { 287 | 288 | // Blend with the target state ... 289 | 290 | let mut target_poses = [ T::identity(); MAX_JOINTS ]; 291 | 292 | let target_state = self.states.get_mut(&transition.target_state[..]).unwrap(); 293 | target_state.blend_tree.synchronize(elapsed_time as f32, &self.parameters); 294 | target_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut target_poses[..]); 295 | 296 | let blend_parameter = ((self.local_clock + ext_dt - transition_start_time) / transition.duration as f64) as f32; 297 | 298 | for i in 0 .. output_poses.len() { 299 | let pose_1 = &mut local_poses[i]; 300 | let pose_2 = target_poses[i]; 301 | *pose_1 = pose_1.lerp(pose_2, blend_parameter); 302 | } 303 | 304 | } 305 | 306 | self.calculate_global_poses(&local_poses[..], output_poses); 307 | } 308 | 309 | /// Calculate global poses from the controller's skeleton and the given local poses 310 | fn calculate_global_poses>( 311 | &self, 312 | local_poses: &[T], 313 | global_poses: &mut [TOutput], 314 | ) { 315 | 316 | for (joint_index, joint) in self.skeleton.joints.iter().enumerate() { 317 | 318 | let parent_pose = if !joint.is_root() { 319 | global_poses[joint.parent_index as usize] 320 | } else { 321 | TOutput::identity() 322 | }; 323 | 324 | let local_pose = local_poses[joint_index]; 325 | global_poses[joint_index] = parent_pose.concat(TOutput::from_transform(local_pose)); 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/dlb_skinning_150.glslv: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | // Dual-Quaternion Linear Blend Skinning 4 | // Reference: http://www.seas.upenn.edu/~ladislav/kavan07skinning/kavan07skinning.pdf 5 | 6 | uniform mat4 u_model_view_proj; 7 | uniform mat4 u_model_view; 8 | 9 | const int MAX_JOINTS = 64; 10 | 11 | uniform u_skinning_transforms { 12 | mat2x4 skinning_transforms[MAX_JOINTS]; 13 | }; 14 | 15 | in vec3 pos, normal; 16 | in vec2 uv; 17 | 18 | in ivec4 joint_indices; 19 | in vec4 joint_weights; 20 | 21 | out vec3 v_normal; 22 | out vec2 v_TexCoord; 23 | 24 | mat4 dualQuaternionToMatrix(vec4 qReal, vec4 qDual) { 25 | 26 | mat4 M = mat4(1.0); 27 | 28 | float len2 = dot(qReal, qReal); 29 | float w = qReal.x, x = qReal.y, y = qReal.z, z = qReal.w; 30 | float t0 = qDual.x, t1 = qDual.y, t2 = qDual.z, t3 = qDual.w; 31 | 32 | M[0][0] = w*w + x*x - y*y - z*z; M[0][1] = 2*x*y - 2*w*z; M[0][2] = 2*x*z + 2*w*y; 33 | M[1][0] = 2*x*y + 2*w*z; M[1][1] = w*w + y*y - x*x - z*z; M[1][2] = 2*y*z - 2*w*x; 34 | M[2][0] = 2*x*z - 2*w*y; M[2][1] = 2*y*z + 2*w*x; M[2][2] = w*w + z*z - x*x - y*y; 35 | 36 | M[0][3] = -2*t0*x + 2*w*t1 - 2*t2*z + 2*y*t3; 37 | M[1][3] = -2*t0*y + 2*t1*z - 2*x*t3 + 2*w*t2; 38 | M[2][3] = -2*t0*z + 2*x*t2 + 2*w*t3 - 2*t1*y; 39 | 40 | M /= len2; 41 | 42 | return M; 43 | } 44 | 45 | void main() { 46 | v_TexCoord = vec2(uv.x, 1 - uv.y); 47 | 48 | float wx = joint_weights.x; 49 | float wy = joint_weights.y; 50 | float wz = joint_weights.z; 51 | float wa = joint_weights.a; 52 | 53 | if (dot(skinning_transforms[joint_indices.x][0], 54 | skinning_transforms[joint_indices.y][0]) < 0.0) { wy *= -1; } 55 | 56 | if (dot(skinning_transforms[joint_indices.x][0], 57 | skinning_transforms[joint_indices.z][0]) < 0.0) { wz *= -1; } 58 | 59 | if (dot(skinning_transforms[joint_indices.x][0], 60 | skinning_transforms[joint_indices.a][0]) < 0.0) { wa *= -1; } 61 | 62 | mat2x4 blendedSkinningDQ = skinning_transforms[joint_indices.x] * wx; 63 | blendedSkinningDQ += skinning_transforms[joint_indices.y] * wy; 64 | blendedSkinningDQ += skinning_transforms[joint_indices.z] * wz; 65 | blendedSkinningDQ += skinning_transforms[joint_indices.a] * wa; 66 | blendedSkinningDQ /= length(blendedSkinningDQ[0]); 67 | 68 | mat4 blendedSkinningMatrix = dualQuaternionToMatrix(blendedSkinningDQ[0], blendedSkinningDQ[1]); 69 | vec4 bindPoseVertex = vec4(pos, 1.0); 70 | vec4 bindPoseNormal = vec4(normal, 0.0); 71 | 72 | vec4 adjustedVertex = bindPoseVertex * blendedSkinningMatrix; 73 | vec4 adjustedNormal = bindPoseNormal * blendedSkinningMatrix; 74 | 75 | gl_Position = u_model_view_proj * adjustedVertex; 76 | v_normal = normalize(u_model_view * adjustedNormal).xyz; 77 | } 78 | -------------------------------------------------------------------------------- /src/lbs_skinning_150.glslv: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | // Linear Blend Skinning 4 | 5 | uniform mat4 u_model_view_proj; 6 | uniform mat4 u_model_view; 7 | 8 | const int MAX_JOINTS = 64; 9 | 10 | uniform u_skinning_transforms { 11 | mat4 skinning_transforms[MAX_JOINTS]; 12 | }; 13 | 14 | in vec3 pos, normal; 15 | in vec2 uv; 16 | 17 | in ivec4 joint_indices; 18 | in vec4 joint_weights; 19 | 20 | out vec3 v_normal; 21 | out vec2 v_TexCoord; 22 | 23 | void main() { 24 | v_TexCoord = vec2(uv.x, 1 - uv.y); // this feels like a bug with gfx? 25 | 26 | vec4 adjustedVertex; 27 | vec4 adjustedNormal; 28 | 29 | vec4 bindPoseVertex = vec4(pos, 1.0); 30 | vec4 bindPoseNormal = vec4(normal, 0.0); 31 | 32 | adjustedVertex = bindPoseVertex * skinning_transforms[joint_indices.x] * joint_weights.x; 33 | adjustedNormal = bindPoseNormal * skinning_transforms[joint_indices.x] * joint_weights.x; 34 | 35 | adjustedVertex = adjustedVertex + bindPoseVertex * skinning_transforms[joint_indices.y] * joint_weights.y; 36 | adjustedNormal = adjustedNormal + bindPoseNormal * skinning_transforms[joint_indices.y] * joint_weights.y; 37 | 38 | adjustedVertex = adjustedVertex + bindPoseVertex * skinning_transforms[joint_indices.z] * joint_weights.z; 39 | adjustedNormal = adjustedNormal + bindPoseNormal * skinning_transforms[joint_indices.z] * joint_weights.z; 40 | 41 | // TODO just use remainder for this weight? 42 | adjustedVertex = adjustedVertex + bindPoseVertex * skinning_transforms[joint_indices.a] * joint_weights.a; 43 | adjustedNormal = adjustedNormal + bindPoseNormal * skinning_transforms[joint_indices.a] * joint_weights.a; 44 | 45 | gl_Position = u_model_view_proj * adjustedVertex; 46 | v_normal = normalize(u_model_view * adjustedNormal).xyz; 47 | } 48 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for data-driven skeletal animation. 2 | 3 | extern crate collada; 4 | #[macro_use] 5 | extern crate gfx; 6 | extern crate gfx_debug_draw; 7 | extern crate gfx_texture; 8 | pub extern crate quaternion; 9 | pub extern crate dual_quaternion; 10 | extern crate vecmath; 11 | extern crate interpolation; 12 | extern crate rustc_serialize; 13 | extern crate float; 14 | 15 | pub mod animation; 16 | pub mod skinned_renderer; 17 | pub mod blend_tree; 18 | pub mod controller; 19 | pub mod manager; 20 | pub mod skeleton; 21 | pub mod math; 22 | mod transform; 23 | 24 | pub use animation::{ 25 | AnimationClip, 26 | AnimationSample, 27 | }; 28 | 29 | pub use transform::{Transform, QVTransform, FromTransform}; 30 | 31 | pub use skeleton::{ 32 | Skeleton, 33 | }; 34 | 35 | pub use manager::{ 36 | AssetManager, 37 | AssetDefs, 38 | }; 39 | 40 | pub use controller::AnimationController; 41 | 42 | pub use skinned_renderer::{SkinnedRenderer, HasShaderSources}; 43 | -------------------------------------------------------------------------------- /src/manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::rc::Rc; 5 | use std::path::Path; 6 | use std::{fmt, error}; 7 | 8 | use rustc_serialize::{Decodable, json}; 9 | 10 | use animation::{AnimationClip, AnimationClipDef, DifferenceClipDef}; 11 | use transform::Transform; 12 | use controller::AnimationControllerDef; 13 | 14 | /// A collection of asset definitions, to be loaded from a JSON definition file 15 | #[derive(Debug, RustcDecodable)] 16 | pub struct AssetDefs { 17 | animation_clips: Option>, 18 | difference_clips: Option>, 19 | animation_controllers: Option>, 20 | } 21 | 22 | /// 23 | /// Asset manager - manages memory for loaded assets...? 24 | /// 25 | pub struct AssetManager { 26 | pub animation_clips: HashMap>>, 27 | pub controller_defs: HashMap 28 | } 29 | 30 | /// Created when attempting to load assets from a path that does not have a parent folder (see 31 | /// [`Path::parent` documentation][0]) 32 | /// 33 | /// [0]: https://doc.rust-lang.org/std/path/struct.Path.html#method.parent 34 | #[derive(Debug)] 35 | pub struct InvalidAssetPathError; 36 | 37 | impl fmt::Display for InvalidAssetPathError { 38 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 39 | writeln!(fmt, "Asset path must have a parent folder") 40 | } 41 | } 42 | 43 | impl error::Error for InvalidAssetPathError {} 44 | 45 | impl AssetManager { 46 | 47 | pub fn new() -> AssetManager { 48 | AssetManager { 49 | animation_clips: HashMap::new(), 50 | controller_defs: HashMap::new(), 51 | } 52 | } 53 | 54 | pub fn load_assets(&mut self, path: &str) -> Result<(), InvalidAssetPathError> { 55 | 56 | let asset_defs: AssetDefs = AssetManager::::load_def_from_path(path).unwrap(); 57 | let parent_path = Path::new(path).parent().ok_or(InvalidAssetPathError)?; 58 | 59 | if let Some(animation_clips) = asset_defs.animation_clips { 60 | for clip_def in animation_clips.iter() { 61 | let clip = AnimationClip::from_def(clip_def, parent_path.to_owned()); 62 | self.animation_clips.insert(clip_def.name.clone(), Rc::new(clip)); 63 | } 64 | } 65 | 66 | if let Some(difference_clips) = asset_defs.difference_clips { 67 | for difference_clip_def in difference_clips.iter() { 68 | 69 | let clip = { 70 | let ref source_clip = self.animation_clips[&difference_clip_def.source_clip[..]]; 71 | let ref reference_clip = self.animation_clips[&difference_clip_def.reference_clip[..]]; 72 | AnimationClip::as_difference_clip(source_clip, reference_clip) 73 | }; 74 | 75 | self.animation_clips.insert(difference_clip_def.name.clone(), Rc::new(clip)); 76 | } 77 | } 78 | 79 | if let Some(animation_controllers) = asset_defs.animation_controllers { 80 | for controller_def in animation_controllers.iter() { 81 | self.controller_defs.insert(controller_def.name.clone(), controller_def.clone()); 82 | } 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | pub fn load_def_from_path(path: &str) -> Result 89 | where D: Decodable 90 | { 91 | let file_result = File::open(path); 92 | 93 | let mut file = match file_result { 94 | Ok(file) => file, 95 | Err(_) => return Err("Failed to open definition file at path.") 96 | }; 97 | 98 | let mut json_string = String::new(); 99 | match file.read_to_string(&mut json_string) { 100 | Ok(_) => {}, 101 | Err(_) => return Err("Failed to read definition file.") 102 | }; 103 | 104 | Ok(json::decode(&json_string[..]).unwrap()) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | pub use vecmath::*; 4 | 5 | pub use quaternion::id as quaternion_id; 6 | pub use quaternion::mul as quaternion_mul; 7 | pub use quaternion::conj as quaternion_conj; 8 | pub use quaternion::{self, Quaternion}; 9 | 10 | pub use dual_quaternion::{self, DualQuaternion}; 11 | 12 | pub fn lerp_quaternion(q1: &Quaternion, q2: &Quaternion, blend_factor: &f32) -> Quaternion { 13 | 14 | let dot = q1.0 * q2.0 + q1.1[0] * q2.1[0] + q1.1[1] * q2.1[1] + q1.1[2] * q2.1[2]; 15 | 16 | let s = 1.0 - blend_factor; 17 | let t: f32 = if dot > 0.0 { *blend_factor } else { -blend_factor }; 18 | 19 | let w = s * q1.0 + t * q2.0; 20 | let x = s * q1.1[0] + t * q2.1[0]; 21 | let y = s * q1.1[1] + t * q2.1[1]; 22 | let z = s * q1.1[2] + t * q2.1[2]; 23 | 24 | let inv_sqrt_len = inv_sqrt(w * w + x * x + y * y + z * z); 25 | (w * inv_sqrt_len, [x * inv_sqrt_len, y * inv_sqrt_len, z * inv_sqrt_len]) 26 | } 27 | 28 | /// Dual-quaternion linear blending. See http://dcgi.felk.cvut.cz/home/zara/papers/TCD-CS-2006-46.pdf 29 | pub fn lerp_dual_quaternion(q1: DualQuaternion, q2: DualQuaternion, blend_factor: f32) -> DualQuaternion { 30 | let dot = dual_quaternion::dot(q1, q2); 31 | 32 | let s = 1.0 - blend_factor; 33 | let t: f32 = if dot > 0.0 { blend_factor } else { -blend_factor }; 34 | 35 | let blended_sum = dual_quaternion::add(dual_quaternion::scale(q1, s), dual_quaternion::scale(q2, t)); 36 | dual_quaternion::normalize(blended_sum) 37 | } 38 | 39 | /// rotation matrix for `a` radians about z 40 | pub fn mat4_rotate_z(a: f32) -> Matrix4 { 41 | [ 42 | [a.cos(), -a.sin(), 0.0, 0.0], 43 | [a.sin(), a.cos(), 0.0, 0.0], 44 | [0.0, 0.0, 1.0, 0.0], 45 | [0.0, 0.0, 0.0, 1.0], 46 | ] 47 | } 48 | 49 | pub fn matrix_to_quaternion(m: &Matrix4) -> Quaternion { 50 | 51 | let mut q = [0.0, 0.0, 0.0, 0.0]; 52 | 53 | let next = [1, 2, 0]; 54 | 55 | let trace = m[0][0] + m[1][1] + m[2][2]; 56 | 57 | if trace > 0.0 { 58 | 59 | let t = trace + 1.0; 60 | let s = inv_sqrt(t) * 0.5; 61 | 62 | q[3] = s * t; 63 | q[0] = (m[1][2] - m[2][1]) * s; 64 | q[1] = (m[2][0] - m[0][2]) * s; 65 | q[2] = (m[0][1] - m[1][0]) * s; 66 | 67 | } else { 68 | 69 | let mut i = 0; 70 | 71 | if m[1][1] > m[0][0] { 72 | i = 1; 73 | } 74 | 75 | if m[2][2] > m[i][i] { 76 | i = 2; 77 | } 78 | 79 | let j = next[i]; 80 | let k = next[j]; 81 | 82 | let t = (m[i][i] - (m[j][j] + m[k][k])) + 1.0; 83 | let s = inv_sqrt(t) * 0.5; 84 | 85 | q[i] = s * t; 86 | q[3] = (m[j][k] - m[k][j]) * s; 87 | q[j] = (m[i][j] + m[j][i]) * s; 88 | q[k] = (m[i][k] + m[k][i]) * s; 89 | 90 | } 91 | 92 | (q[3], [q[0], q[1], q[2]]) 93 | } 94 | 95 | /// 96 | /// See http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ 97 | /// 98 | pub fn quaternion_to_matrix(q: Quaternion) -> Matrix4 { 99 | 100 | let w = q.0; 101 | let x = q.1[0]; 102 | let y = q.1[1]; 103 | let z = q.1[2]; 104 | 105 | let x2 = x + x; 106 | let y2 = y + y; 107 | let z2 = z + z; 108 | 109 | let xx2 = x2 * x; 110 | let xy2 = x2 * y; 111 | let xz2 = x2 * z; 112 | 113 | let yy2 = y2 * y; 114 | let yz2 = y2 * z; 115 | let zz2 = z2 * z; 116 | 117 | let wy2 = y2 * w; 118 | let wz2 = z2 * w; 119 | let wx2 = x2 * w; 120 | 121 | [ 122 | [1.0 - yy2 - zz2, xy2 + wz2, xz2 - wy2, 0.0], 123 | [xy2 - wz2, 1.0 - xx2 - zz2, yz2 + wx2, 0.0], 124 | [xz2 + wy2, yz2 - wx2, 1.0 - xx2 - yy2, 0.0], 125 | [0.0, 0.0, 0.0, 1.0] 126 | ] 127 | 128 | } 129 | 130 | pub fn inv_sqrt(x: f32) -> f32 { 131 | 132 | let x2: f32 = x * 0.5; 133 | let mut y: f32 = x; 134 | 135 | let mut i: i32 = unsafe { mem::transmute(y) }; 136 | i = 0x5f3759df - (i >> 1); 137 | y = unsafe { mem::transmute(i) }; 138 | 139 | y = y * (1.5 - (x2 * y * y)); 140 | y 141 | 142 | } 143 | 144 | pub fn solve_ik_2d(length_1: f32, length_2: f32, target: [f32; 2]) -> Option<[f32; 2]> 145 | { 146 | let x = target[0]; 147 | let y = target[1]; 148 | let distance_squared = x * x + y * y; 149 | let distance = distance_squared.sqrt(); 150 | 151 | let l1_squared = length_1 * length_1; 152 | let l2_squared = length_2 * length_2; 153 | 154 | if (length_1 - length_2).abs() > distance { 155 | // target too close for solution 156 | return None; 157 | } 158 | 159 | if (length_1 + length_2) < distance { 160 | // target too far to reach, just reach in direction of target 161 | return Some(vec2_scale(vec2_normalized(target), length_1)); 162 | } 163 | 164 | let alpha_2 = target[1].atan2(target[0]); 165 | let alpha_1 = ((l1_squared + distance_squared - l2_squared) 166 | / (2.0 * length_1 * distance_squared.sqrt())).acos(); 167 | 168 | let angle = alpha_1 + alpha_2; 169 | 170 | // Either +angle or -angle would be valid solutions 171 | // TODO: take current middle joint position as a param, 172 | // choose solution closest to current positi 173 | 174 | Some([length_1 * angle.cos(), 175 | length_1 * angle.sin()]) 176 | } 177 | 178 | #[cfg(test)] 179 | mod test { 180 | 181 | static EPSILON: f32 = 0.000001; 182 | 183 | #[test] 184 | fn test_ik() { 185 | 186 | let m = super::solve_ik_2d(2.0, 3.0, [0.0, 5.0]).unwrap(); 187 | println!("{:?}", m); 188 | assert!((m[0] - 0.0).abs() < EPSILON); 189 | assert!((m[1] - 2.0).abs() < EPSILON); 190 | 191 | let m = super::solve_ik_2d(2.0, 3.0, [5.0, 0.0]).unwrap(); 192 | println!("{:?}", m); 193 | assert!((m[0] - 2.0).abs() < EPSILON); 194 | assert!((m[1] - 0.0).abs() < EPSILON); 195 | 196 | let m = super::solve_ik_2d(2.0, 3.0, [0.0, -5.0]).unwrap(); 197 | println!("{:?}", m); 198 | assert!((m[0] - 0.0).abs() < EPSILON); 199 | assert!((m[1] + 2.0).abs() < EPSILON); 200 | 201 | let m = super::solve_ik_2d(2f32.sqrt(), 2f32.sqrt(), [-3.0, -3.0]).unwrap(); 202 | println!("{:?}", m); 203 | assert!((m[0] + 1.0).abs() < EPSILON); 204 | assert!((m[1] + 1.0).abs() < EPSILON); 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/skeleton.rs: -------------------------------------------------------------------------------- 1 | use gfx; 2 | use gfx_debug_draw; 3 | 4 | use collada; 5 | use math::*; 6 | use transform::{Transform, FromTransform}; 7 | 8 | pub type JointIndex = u8; 9 | pub const ROOT_JOINT_PARENT_INDEX: JointIndex = 255u8; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Skeleton { 13 | /// 14 | /// All joints in the skeleton 15 | /// 16 | pub joints: Vec, 17 | } 18 | 19 | impl Skeleton { 20 | 21 | /// 22 | /// Build a skeleton fromm a Collada skeleton 23 | /// 24 | pub fn from_collada(skeleton: &collada::Skeleton) -> Skeleton { 25 | Skeleton { 26 | joints: skeleton.joints.iter().map(|j| { 27 | Joint { 28 | name: j.name.clone(), 29 | parent_index: j.parent_index, 30 | inverse_bind_pose: j.inverse_bind_pose, 31 | } 32 | }).collect() 33 | } 34 | } 35 | 36 | pub fn calculate_global_poses>( 37 | &self, 38 | local_poses: &[T], 39 | global_poses: &mut [TOutput], 40 | ) { 41 | 42 | for (joint_index, joint) in self.joints.iter().enumerate() { 43 | 44 | let parent_pose = if !joint.is_root() { 45 | global_poses[joint.parent_index as usize] 46 | } else { 47 | TOutput::identity() 48 | }; 49 | 50 | let local_pose = local_poses[joint_index]; 51 | global_poses[joint_index] = parent_pose.concat(TOutput::from_transform(local_pose)); 52 | } 53 | } 54 | 55 | pub fn get_joint_index(&self, joint_name: &str) -> Option { 56 | 57 | for (index, joint) in self.joints.iter().enumerate() { 58 | if joint.name == joint_name { 59 | return Some(index as JointIndex); 60 | } 61 | } 62 | 63 | None 64 | } 65 | 66 | pub fn draw, T: Transform> ( 67 | &self, 68 | global_poses: &[T], 69 | debug_renderer: &mut gfx_debug_draw::DebugRenderer, 70 | draw_labels: bool) 71 | { 72 | 73 | for (joint_index, joint) in self.joints.iter().enumerate() { 74 | 75 | let joint_position = global_poses[joint_index].transform_vector([0.0, 0.0, 0.0]); 76 | let leaf_end = global_poses[joint_index].transform_vector([0.0, 1.0, 0.0]); 77 | 78 | if !joint.is_root() { 79 | 80 | let parent_position = global_poses[joint.parent_index as usize].transform_vector([0.0, 0.0, 0.0]); 81 | 82 | // Draw bone (between joint and parent joint) 83 | 84 | debug_renderer.draw_line( 85 | [parent_position[0], parent_position[1], parent_position[2]], 86 | [joint_position[0], joint_position[1], joint_position[2]], 87 | [0.2, 0.2, 0.2, 1.0] 88 | ); 89 | 90 | if !self.joints.iter().any(|j| j.parent_index as usize == joint_index) { 91 | 92 | // Draw extension along joint's y-axis... 93 | debug_renderer.draw_line( 94 | [joint_position[0], joint_position[1], joint_position[2]], 95 | [leaf_end[0], leaf_end[1], leaf_end[2]], 96 | [0.2, 0.2, 0.2, 1.0] 97 | ); 98 | } 99 | } 100 | 101 | if draw_labels { 102 | // Label joint 103 | debug_renderer.draw_text_at_position( 104 | &joint.name[..], 105 | [leaf_end[0], leaf_end[1], leaf_end[2]], 106 | [1.0, 1.0, 1.0, 1.0] 107 | ); 108 | } 109 | 110 | // Draw joint-relative axes 111 | let p_x_axis = global_poses[joint_index].transform_vector([1.0, 0.0, 0.0]); 112 | let p_y_axis = global_poses[joint_index].transform_vector([0.0, 1.0, 0.0]); 113 | let p_z_axis = global_poses[joint_index].transform_vector([0.0, 0.0, 1.0]); 114 | 115 | debug_renderer.draw_line( 116 | [joint_position[0], joint_position[1], joint_position[2]], 117 | [p_x_axis[0], p_x_axis[1], p_x_axis[2]], 118 | [1.0, 0.2, 0.2, 1.0] 119 | ); 120 | 121 | debug_renderer.draw_line( 122 | [joint_position[0], joint_position[1], joint_position[2]], 123 | [p_y_axis[0], p_y_axis[1], p_y_axis[2]], 124 | [0.2, 1.0, 0.2, 1.0] 125 | ); 126 | 127 | debug_renderer.draw_line( 128 | [joint_position[0], joint_position[1], joint_position[2]], 129 | [p_z_axis[0], p_z_axis[1], p_z_axis[2]], 130 | [0.2, 0.2, 1.0, 1.0] 131 | ); 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug, Clone)] 137 | pub struct Joint { 138 | /// 139 | /// Name of joint 140 | /// 141 | pub name: String, 142 | 143 | /// 144 | /// Index of parent joint in Skeleton's 'joints' vector 145 | /// 146 | pub parent_index: JointIndex, 147 | 148 | /// 149 | /// Matrix transforming vertex coordinates from model-space to joint-space 150 | /// Column-major. 151 | /// 152 | pub inverse_bind_pose: Matrix4, 153 | } 154 | 155 | impl Joint { 156 | pub fn is_root(&self) -> bool { 157 | self.parent_index == ROOT_JOINT_PARENT_INDEX 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/skinned_renderer.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use std::path::Path; 3 | 4 | use collada; 5 | use gfx; 6 | use gfx::memory::Typed; 7 | use gfx::traits::*; 8 | use gfx_texture; 9 | 10 | use gfx_texture::TextureContext; 11 | 12 | use math::*; 13 | use skeleton::Skeleton; 14 | use transform::Transform; 15 | 16 | const MAX_JOINTS: usize = 64; 17 | 18 | pub struct SkinnedRenderBatch { 19 | skinning_transforms_buffer: gfx::handle::Buffer, 20 | slice: gfx::Slice, 21 | vertex_buffer: gfx::handle::Buffer, 22 | texture: (gfx::handle::ShaderResourceView, gfx::handle::Sampler), 23 | } 24 | 25 | pub struct SkinnedRenderer { 26 | pso: gfx::PipelineState, 27 | skeleton: Skeleton, // TODO Should this be a ref? Should this just be the joints? 28 | render_batches: Vec>, 29 | } 30 | 31 | pub trait HasShaderSources<'a> { 32 | fn vertex_shader_source() -> &'a [u8]; 33 | fn fragment_shader_source() -> &'a [u8]; 34 | } 35 | 36 | impl<'a> HasShaderSources<'a> for Matrix4 { 37 | fn vertex_shader_source() -> &'a [u8] { 38 | include_bytes!("lbs_skinning_150.glslv") 39 | } 40 | fn fragment_shader_source() -> &'a [u8] { 41 | include_bytes!("skinning_150.glslf") 42 | } 43 | } 44 | 45 | impl<'a> HasShaderSources<'a> for DualQuaternion { 46 | fn vertex_shader_source() -> &'a [u8] { 47 | include_bytes!("dlb_skinning_150.glslv") 48 | } 49 | fn fragment_shader_source() -> &'a [u8] { 50 | include_bytes!("skinning_150.glslf") 51 | } 52 | } 53 | 54 | impl<'a, R: gfx::Resources, T: Transform + HasShaderSources<'a>> SkinnedRenderer { 55 | 56 | pub fn from_collada, C: gfx::CommandBuffer>( 57 | tcx: &mut TextureContext, 58 | collada_document: collada::document::ColladaDocument, 59 | texture_paths: Vec<&str>, // TODO - read from the COLLADA document (if available) 60 | ) -> Result, gfx::shade::ProgramError> { 61 | use gfx::format::Formatted; 62 | 63 | let program = { 64 | let vs = T::vertex_shader_source(); 65 | let fs = T::fragment_shader_source(); 66 | match tcx.factory.link_program(vs, fs) { 67 | Ok(program_handle) => program_handle, 68 | Err(e) => return Err(e), 69 | } 70 | }; 71 | 72 | // TODO: Pass in format as parameter. 73 | let format = gfx::format::Srgba8::get_format(); 74 | let init = pipe::Init { 75 | vertex: (), 76 | u_model_view_proj: "u_model_view_proj", 77 | u_model_view: "u_model_view", 78 | u_skinning_transforms: "u_skinning_transforms", 79 | u_texture: "u_texture", 80 | out_color: ("out_color", format, gfx::state::ColorMask::all(), None), 81 | out_depth: gfx::preset::depth::LESS_EQUAL_WRITE, 82 | }; 83 | let pso = tcx.factory.create_pipeline_from_program( 84 | &program, 85 | gfx::Primitive::TriangleList, 86 | gfx::state::Rasterizer::new_fill(), 87 | init 88 | ).unwrap(); 89 | 90 | let sampler = tcx.factory.create_sampler( 91 | gfx::texture::SamplerInfo::new( 92 | gfx::texture::FilterMethod::Trilinear, 93 | gfx::texture::WrapMode::Clamp 94 | ) 95 | ); 96 | 97 | let obj_set = collada_document.get_obj_set().unwrap(); 98 | 99 | let skeleton_set = collada_document.get_skeletons().unwrap(); 100 | let skeleton = Skeleton::from_collada(&skeleton_set[0]); 101 | 102 | let mut render_batches = Vec::new(); 103 | 104 | for (i, object) in obj_set.objects.iter().enumerate().take(6) { 105 | 106 | let mut vertex_data: Vec = Vec::new(); 107 | let mut index_data: Vec = Vec::new(); 108 | 109 | get_vertex_index_data(&object, &mut vertex_data, &mut index_data); 110 | 111 | let (vbuf, slice) = tcx.factory.create_vertex_buffer_with_slice 112 | (&vertex_data, &index_data[..]); 113 | 114 | let skinning_transforms_buffer = tcx.factory.create_buffer::( 115 | MAX_JOINTS, 116 | gfx::buffer::Role::Constant, 117 | gfx::memory::Usage::Dynamic, 118 | gfx::memory::Bind::empty() 119 | ).unwrap(); 120 | 121 | let texture = gfx_texture::Texture::from_path( 122 | tcx, 123 | &Path::new(&texture_paths[i]), 124 | gfx_texture::Flip::None, 125 | &gfx_texture::TextureSettings::new() 126 | ).unwrap(); 127 | 128 | render_batches.push(SkinnedRenderBatch { 129 | slice: slice, 130 | vertex_buffer: vbuf, 131 | skinning_transforms_buffer: skinning_transforms_buffer, 132 | texture: (texture.view.clone(), sampler.clone()), 133 | }); 134 | } 135 | 136 | 137 | Ok(SkinnedRenderer { 138 | pso: pso, 139 | render_batches: render_batches, 140 | skeleton: skeleton.clone(), 141 | }) 142 | } 143 | 144 | pub fn render, Rf: gfx::format::RenderFormat> ( 145 | &mut self, 146 | encoder: &mut gfx::Encoder, 147 | out_color: &gfx::handle::RenderTargetView, 148 | out_depth: &gfx::handle::DepthStencilView, 149 | view: [[f32; 4]; 4], 150 | projection: [[f32; 4]; 4], 151 | joint_poses: &[T] 152 | ) 153 | where T: gfx::traits::Pod 154 | { 155 | 156 | let skinning_transforms = self.calculate_skinning_transforms(&joint_poses); 157 | 158 | for material in self.render_batches.iter_mut() { 159 | // FIXME -- should all be able to share the same buffer 160 | encoder.update_buffer(&material.skinning_transforms_buffer, &skinning_transforms[..], 0).unwrap(); 161 | 162 | let data = pipe::Data { 163 | vertex: material.vertex_buffer.clone(), 164 | u_model_view_proj: projection, 165 | u_model_view: view, 166 | u_skinning_transforms: material.skinning_transforms_buffer.raw().clone(), 167 | u_texture: material.texture.clone(), 168 | out_color: out_color.raw().clone(), 169 | out_depth: out_depth.clone(), 170 | }; 171 | 172 | encoder.draw(&material.slice, &self.pso, &data); 173 | } 174 | } 175 | 176 | /// 177 | /// TODO - don't allocate a new vector 178 | /// 179 | pub fn calculate_skinning_transforms(&self, global_poses: &[T]) -> Vec { 180 | self.skeleton.joints.iter().enumerate().map(|(i, joint)| { 181 | // TODO avoid conversion... 182 | global_poses[i].concat(T::from_matrix(joint.inverse_bind_pose)) 183 | }).collect() 184 | } 185 | } 186 | 187 | gfx_pipeline_base!( pipe { 188 | vertex: gfx::VertexBuffer, 189 | u_model_view_proj: gfx::Global<[[f32; 4]; 4]>, 190 | u_model_view: gfx::Global<[[f32; 4]; 4]>, 191 | u_skinning_transforms: gfx::RawConstantBuffer, 192 | u_texture: gfx::TextureSampler<[f32; 4]>, 193 | out_color: gfx::RawRenderTarget, 194 | out_depth: gfx::DepthTarget, 195 | }); 196 | 197 | /* 198 | gfx_pipeline!( pipe { 199 | u_model_view_proj: gfx::Global<[[f32; 4]; 4]> = "u_model_view_proj", 200 | u_model_view: gfx::Global<[[f32; 4]; 4]> = "u_model_view", 201 | u_skinning_transforms: gfx::RawVertexBuffer = &[], 202 | u_texture: gfx::TextureSampler<[f32; 4]> = "u_texture", 203 | // out_color: gfx::RenderTarget = "o_Color", 204 | out_color: gfx::RawRenderTarget = "o_Color", 205 | out_depth: gfx::DepthTarget = 206 | gfx::preset::depth::LESS_EQUAL_WRITE, 207 | }); 208 | */ 209 | 210 | gfx_vertex_struct!(SkinnedVertex { 211 | pos: [f32; 3] = "pos", 212 | normal: [f32; 3] = "normal", 213 | uv: [f32; 2] = "uv", 214 | joint_indices: [i32; 4] = "joint_indices", 215 | joint_weights: [f32; 4] = "joint_weights", // TODO last weight is redundant 216 | }); 217 | 218 | impl Default for SkinnedVertex { 219 | fn default() -> SkinnedVertex { 220 | SkinnedVertex { 221 | pos: [0.0; 3], 222 | normal: [0.0; 3], 223 | uv: [0.0; 2], 224 | joint_indices: [0; 4], 225 | joint_weights: [0.0; 4], 226 | } 227 | } 228 | } 229 | 230 | fn vtn_to_vertex(a: collada::VTNIndex, obj: &collada::Object) -> SkinnedVertex 231 | { 232 | let mut vertex: SkinnedVertex = Default::default(); 233 | let position = obj.vertices[a.0]; 234 | 235 | vertex.pos = [position.x as f32, position.y as f32, position.z as f32]; 236 | 237 | if obj.joint_weights.len() == obj.vertices.len() { 238 | let weights = obj.joint_weights[a.0]; 239 | vertex.joint_weights = weights.weights; 240 | vertex.joint_indices = [ 241 | weights.joints[0] as i32, 242 | weights.joints[1] as i32, 243 | weights.joints[2] as i32, 244 | weights.joints[3] as i32, 245 | ]; 246 | } 247 | 248 | if let Some(uv) = a.1 { 249 | let uv = obj.tex_vertices[uv]; 250 | vertex.uv = [uv.x as f32, uv.y as f32]; 251 | } 252 | 253 | if let Some(normal) = a.2 { 254 | let normal = obj.normals[normal]; 255 | vertex.normal = [normal.x as f32, normal.y as f32, normal.z as f32]; 256 | } 257 | 258 | vertex 259 | } 260 | 261 | fn get_vertex_index_data(obj: &collada::Object, vertex_data: &mut Vec, index_data: &mut Vec) { 262 | for geom in obj.geometry.iter() { 263 | let mut i = vertex_data.len() as u32; 264 | let mut uvs: u32 = 0; 265 | let mut normals: u32 = 0; 266 | { 267 | let mut add = |a: collada::VTNIndex| { 268 | if let Some(_) = a.1 { uvs += 1; } 269 | if let Some(_) = a.2 { normals += 1; } 270 | vertex_data.push(vtn_to_vertex(a, obj)); 271 | index_data.push(i); 272 | i += 1; 273 | }; 274 | for mesh in geom.mesh.iter() { 275 | match mesh { 276 | &collada::PrimitiveElement::Triangles(ref triangles) => { 277 | for j in 0..triangles.vertices.len() { 278 | add(( 279 | triangles.vertices[j].0, 280 | triangles.tex_vertices.as_ref().map(|te| te[j].0), 281 | triangles.normals.as_ref().map(|no| no[j].0) 282 | )); 283 | add(( 284 | triangles.vertices[j].1, 285 | triangles.tex_vertices.as_ref().map(|te| te[j].1), 286 | triangles.normals.as_ref().map(|no| no[j].1) 287 | )); 288 | add(( 289 | triangles.vertices[j].2, 290 | triangles.tex_vertices.as_ref().map(|te| te[j].2), 291 | triangles.normals.as_ref().map(|no| no[j].2) 292 | )); 293 | } 294 | } 295 | &collada::PrimitiveElement::Polylist(ref polylist) => { 296 | for shape in &polylist.shapes { 297 | match shape { 298 | &collada::Shape::Triangle(a, b, c) => { 299 | add(a); 300 | add(b); 301 | add(c); 302 | } 303 | _ => {} 304 | } 305 | } 306 | } 307 | } 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/skinning_150.glslf: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D u_texture; 4 | 5 | in vec3 v_normal; 6 | out vec4 out_color; 7 | 8 | in vec2 v_TexCoord; 9 | 10 | void main() { 11 | vec4 texColor = texture(u_texture, v_TexCoord); 12 | 13 | // unidirectional light in direction as camera 14 | vec3 light = vec3(0.0, 0.0, 1.0); 15 | light = normalize(light); 16 | float intensity = max(dot(v_normal, light), 0.0); 17 | 18 | out_color = vec4(intensity, intensity, intensity, 1.0) * texColor; 19 | } -------------------------------------------------------------------------------- /src/transform.rs: -------------------------------------------------------------------------------- 1 | use interpolation; 2 | use math::*; 3 | 4 | pub trait Transform: Copy { 5 | fn identity() -> Self; 6 | fn concat(self, other: Self) -> Self; 7 | fn inverse(self) -> Self; 8 | fn lerp(self, other: Self, parameter: f32) -> Self; 9 | fn transform_vector(self, v: Vector3) -> Vector3; 10 | fn to_matrix(self) -> Matrix4; 11 | fn from_matrix(m: Matrix4) -> Self; 12 | fn set_rotation(&mut self, rotation: Quaternion); 13 | fn get_rotation(self) -> Quaternion; 14 | fn set_translation(&mut self, translation: Vector3); 15 | fn get_translation(self) -> Vector3; 16 | } 17 | 18 | /// Transformation represented by separate scaling, translation, and rotation factors. 19 | #[derive(Debug, Copy, Clone)] 20 | pub struct QVTransform 21 | { 22 | /// Translation 23 | pub translation: Vector3, 24 | 25 | /// Uniform scale factor. 26 | pub scale: f32, 27 | 28 | /// Rotation 29 | pub rotation: Quaternion 30 | 31 | } 32 | 33 | impl Transform for QVTransform { 34 | 35 | fn identity() -> QVTransform { 36 | QVTransform { 37 | translation: [0.0, 0.0, 0.0], 38 | scale: 1.0, 39 | rotation: quaternion_id(), 40 | } 41 | } 42 | 43 | fn set_rotation(&mut self, rotation: Quaternion) { 44 | self.rotation = rotation; 45 | } 46 | 47 | fn get_rotation(self) -> Quaternion { 48 | self.rotation 49 | } 50 | 51 | fn set_translation(&mut self, translation: Vector3) { 52 | self.translation = translation; 53 | } 54 | 55 | fn get_translation(self) -> Vector3 { 56 | self.translation 57 | } 58 | 59 | fn concat(self, other: QVTransform) -> QVTransform { 60 | QVTransform::from_matrix(self.to_matrix().concat(other.to_matrix())) 61 | } 62 | 63 | fn inverse(self) -> QVTransform { 64 | QVTransform::from_matrix(self.to_matrix().inverse()) 65 | } 66 | 67 | fn lerp(self, other: QVTransform, parameter: f32) -> QVTransform { 68 | QVTransform { 69 | translation: interpolation::lerp(&self.translation, &other.translation, ¶meter), 70 | scale: interpolation::lerp(&self.scale, &other.scale, ¶meter), 71 | rotation: lerp_quaternion(&self.rotation, &other.rotation, ¶meter), 72 | } 73 | } 74 | 75 | fn transform_vector(self, v: Vector3) -> Vector3 { 76 | let v = quaternion::rotate_vector(self.rotation, v); 77 | let v = vec3_add(v, self.translation); 78 | vec3_scale(v, self.scale) 79 | } 80 | 81 | fn to_matrix(self) -> Matrix4 { 82 | let mut m = quaternion_to_matrix(self.rotation); 83 | 84 | m[0][3] = self.translation[0]; 85 | m[1][3] = self.translation[1]; 86 | m[2][3] = self.translation[2]; 87 | 88 | m 89 | } 90 | 91 | fn from_matrix(m: Matrix4) -> QVTransform { 92 | 93 | let rotation = matrix_to_quaternion(&m); 94 | 95 | let translation = [m[0][3], 96 | m[1][3], 97 | m[2][3]]; 98 | 99 | QVTransform { 100 | rotation: rotation, 101 | scale: 1.0, 102 | translation: translation, 103 | } 104 | } 105 | 106 | } 107 | 108 | impl Transform for DualQuaternion { 109 | 110 | fn identity() -> DualQuaternion { 111 | dual_quaternion::id() 112 | } 113 | 114 | fn set_rotation(&mut self, rotation: Quaternion) { 115 | let t = dual_quaternion::get_translation(*self); 116 | *self = dual_quaternion::from_rotation_and_translation(rotation, t); 117 | } 118 | 119 | fn get_rotation(self) -> Quaternion { 120 | dual_quaternion::get_rotation(self) 121 | } 122 | 123 | fn set_translation(&mut self, translation: Vector3) { 124 | let rotation = dual_quaternion::get_rotation(*self); 125 | *self = dual_quaternion::from_rotation_and_translation(rotation, translation); 126 | } 127 | 128 | fn get_translation(self) -> Vector3 { 129 | dual_quaternion::get_translation(self) 130 | } 131 | 132 | fn concat(self, other: DualQuaternion) -> DualQuaternion { 133 | dual_quaternion::mul(self, other) 134 | } 135 | 136 | fn inverse(self) -> DualQuaternion { 137 | dual_quaternion::conj(self) 138 | } 139 | 140 | fn lerp(self, other: DualQuaternion, parameter: f32) -> DualQuaternion { 141 | lerp_dual_quaternion(self, other, parameter) 142 | } 143 | 144 | fn transform_vector(self, v: Vector3) -> Vector3 { 145 | let t = dual_quaternion::get_translation(self); 146 | let r = dual_quaternion::get_rotation(self); 147 | vec3_add(quaternion::rotate_vector(r, v), t) 148 | } 149 | 150 | fn to_matrix(self) -> Matrix4 { 151 | 152 | let rotation = dual_quaternion::get_rotation(self); 153 | let translation = dual_quaternion::get_translation(self); 154 | 155 | let mut m = mat4_transposed(quaternion_to_matrix(rotation)); 156 | 157 | m[0][3] = translation[0]; 158 | m[1][3] = translation[1]; 159 | m[2][3] = translation[2]; 160 | 161 | m 162 | } 163 | 164 | fn from_matrix(m: Matrix4) -> DualQuaternion { 165 | 166 | let rotation = matrix_to_quaternion(&mat4_transposed(m)); 167 | 168 | let translation = [m[0][3], 169 | m[1][3], 170 | m[2][3]]; 171 | 172 | dual_quaternion::from_rotation_and_translation(rotation, translation) 173 | 174 | } 175 | } 176 | 177 | impl Transform for Matrix4 { 178 | 179 | fn identity() -> Matrix4 { 180 | mat4_id() 181 | } 182 | 183 | fn set_rotation(&mut self, rotation: Quaternion) { 184 | 185 | let rotation = quaternion_to_matrix(rotation); 186 | 187 | self[0][0] = rotation[0][0]; 188 | self[1][0] = rotation[1][0]; 189 | self[2][0] = rotation[2][0]; 190 | 191 | self[0][1] = rotation[0][1]; 192 | self[1][1] = rotation[1][1]; 193 | self[2][1] = rotation[2][1]; 194 | 195 | self[0][2] = rotation[0][2]; 196 | self[1][2] = rotation[1][2]; 197 | self[2][2] = rotation[2][2]; 198 | } 199 | 200 | fn get_rotation(self) -> Quaternion { 201 | matrix_to_quaternion(&self) 202 | } 203 | 204 | fn set_translation(&mut self, translation: Vector3) { 205 | self[0][3] = translation[0]; 206 | self[1][3] = translation[1]; 207 | self[2][3] = translation[2]; 208 | } 209 | 210 | fn get_translation(self) -> Vector3 { 211 | [self[0][3], 212 | self[1][3], 213 | self[2][3]] 214 | } 215 | 216 | fn concat(self, other: Matrix4) -> Matrix4 { 217 | row_mat4_mul(self, other) 218 | } 219 | 220 | fn inverse(self) -> Matrix4 { 221 | mat4_inv(self) 222 | } 223 | 224 | fn lerp(self, other: Matrix4, parameter: f32) -> Matrix4 { 225 | let q1 = DualQuaternion::from_matrix(self); 226 | let q2 = DualQuaternion::from_matrix(other); 227 | q1.lerp(q2, parameter).to_matrix() 228 | } 229 | 230 | fn transform_vector(self, v: Vector3) -> Vector3 { 231 | let t = row_mat4_transform(self, [v[0], v[1], v[2], 1.0]); 232 | [t[0], t[1], t[2]] 233 | } 234 | 235 | fn to_matrix(self) -> Matrix4 { self } 236 | 237 | fn from_matrix(m: Matrix4) -> Matrix4 { m } 238 | } 239 | 240 | pub trait FromTransform { 241 | fn from_transform(t: T) -> Self; 242 | } 243 | 244 | impl FromTransform> for DualQuaternion { 245 | fn from_transform(t: DualQuaternion) -> DualQuaternion { t } 246 | } 247 | 248 | impl FromTransform for Matrix4 { 249 | fn from_transform(t: T) -> Matrix4 { 250 | t.to_matrix() 251 | } 252 | } 253 | 254 | #[cfg(test)] 255 | mod test { 256 | 257 | use vecmath; 258 | use quaternion; 259 | use dual_quaternion; 260 | 261 | use super::Transform; 262 | 263 | static EPSILON: f32 = 0.000001; 264 | 265 | #[test] 266 | fn test_dual_quaternion_to_matrix() { 267 | 268 | let a = [1.0, 0.0, 0.0]; 269 | let b = [0.0, 1.0, 0.0]; 270 | 271 | let q = quaternion::rotation_from_to(a, b); 272 | 273 | let dq = dual_quaternion::from_rotation_and_translation(q, [0.0, 0.0, 0.0]); 274 | println!("{:?}", dq.transform_vector(a)); 275 | assert!(vecmath::vec3_len(vecmath::vec3_sub(b, dq.transform_vector(a))) < EPSILON); 276 | 277 | let m = dq.to_matrix(); 278 | println!("{:?}", m.transform_vector(a)); 279 | assert!(vecmath::vec3_len(vecmath::vec3_sub(b, m.transform_vector(a))) < EPSILON); 280 | } 281 | 282 | #[test] 283 | fn test_dual_quaternion_set_rotation() { 284 | 285 | let a = [1.0, 0.0, 0.0]; 286 | let b = [0.0, 1.0, 0.0]; 287 | 288 | let q = quaternion::rotation_from_to(a, b); 289 | let q2 = quaternion::rotation_from_to(b, a); 290 | 291 | let mut dq = dual_quaternion::from_rotation_and_translation(q, [0.0, 1.0, 0.0]); 292 | 293 | println!("{:?}", dq.transform_vector(a)); 294 | assert!(vecmath::vec3_len(vecmath::vec3_sub([0.0, 2.0, 0.0], 295 | dq.transform_vector(a))) < EPSILON); 296 | 297 | dq.set_rotation(q2); 298 | println!("{:?}", dq.transform_vector(b)); 299 | assert!(vecmath::vec3_len(vecmath::vec3_sub([1.0, 1.0, 0.0], 300 | dq.transform_vector(b))) < EPSILON); 301 | } 302 | } 303 | --------------------------------------------------------------------------------