commit a12794d7d8cc22705776f1b59fed9a3ce3f1cb38 Author: Bram Buurlage Date: Wed Sep 14 09:27:29 2022 +0200 initial commit with very basic IK diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dd61be5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bevy_mod_inverse_kinematics" +version = "0.1.0" +edition = "2021" +categories = ["game-engines", "graphics", "rendering"] +description = "An inverse kinematics plugin for the bevy game engine" +keywords = [gamedev", "graphics", "bevy", "animation"] +license = "MIT OR Apache-2.0" + +[dependencies] +bevy = "0.8.1" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6cada3b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,128 @@ +use std::num::NonZeroUsize; +use std::ops::Range; + +use bevy::ecs::query::QueryEntityError; +use bevy::transform::TransformSystem; +use bevy::prelude::*; + +pub struct InverseKinematicsPlugin; + +/// Inverse kintematics constraint to be added to the tail joint of +/// the bone chain that should be constrained. +#[derive(Component)] +pub struct IkConstraint { + /// How many bones are included in the IK constraint. + pub chain_length: NonZeroUsize, + /// Maximum number of iterations to solve this constraint. + pub iterations: usize, + /// Target entity. The target must have a `Transform` and `GlobalTransform`. + pub target: Entity, + /// Target entity for pole rotation. + pub pole_target: Option, + /// Offset for pole rotation in radians. + pub pole_angle: f32, +} + +#[derive(Component)] +pub struct CopyLocationConstraint { + /// Target entity. The target must have a `Transform` and `GlobalTransform`. + pub target: Entity, +} + +#[derive(Component)] +pub struct RotationConstraint { + /// The allowable range for yaw rotation. + pub yaw: Range, + /// The allowable range for pitch rotation. + pub pitch: Range, + /// The allowable range for roll rotation. + pub roll: Range, +} + +pub fn inverse_kinematics_solver_system( + constraints_ik: Query<(Entity, &IkConstraint)>, + mut params: ParamSet<( + Query<&GlobalTransform>, + Query<(&mut Transform, &mut GlobalTransform, &Parent)>, + )>, +) { + for (entity, constraint) in constraints_ik.iter() { + if let Err(e) = constraint.solve(entity, &mut params) { + bevy::log::warn!("Failed to solve IK constraint: {e}"); + } + } +} + +impl IkConstraint { + fn solve( + &self, + entity: Entity, + params: &mut ParamSet<( + Query<&GlobalTransform>, + Query<(&mut Transform, &mut GlobalTransform, &Parent)>, + )>, + ) -> Result<(), QueryEntityError> { + let (bone, parent) = params.p1() + .get(entity) + .map(|(t, _, p)| (t.translation, p.get()))?; + let tail = params.p0().get(self.target).map(GlobalTransform::translation)?; + let pole = if let Some(entity) = self.pole_target { + Some(params.p0().get(entity).map(GlobalTransform::translation)?) + } else { + None + }; + Self::solve_recursive(parent, bone, tail, pole, &mut params.p1(), self.chain_length.get())?; + Ok(()) + } + + fn solve_recursive( + // the entity to rotate + entity: Entity, + // the translation vector of this bone + bone: Vec3, + // the desired tail of this bone + tail: Vec3, + // the desired up vector of this bone + pole: Option, + query: &mut Query<(&mut Transform, &mut GlobalTransform, &Parent)>, + chain: usize, + ) -> Result { + if chain == 0 { + let (_, &global_transform, _) = query.get(entity)?; + return Ok(global_transform); + } + + let (transform, global_transform, parent) = + query.get(entity).map(|(&t, &g, p)| (t, g, p.get()))?; + // the bone vector of the parent is the translation of this bone + let parent_bone = transform.translation; + + // calculate absolute rotation in order to point this bone at the desired tail + let rotation = Quat::from_rotation_arc( + bone.normalize(), + (tail - global_transform.translation()).normalize(), + ); + // calculate absolute translation so the tip of this bone touches the desired tail. + let head = tail - rotation.mul_vec3(bone); + + // pass the targets to the parent to obtain the final global transform of the parent + let parent_global_transform = + Self::solve_recursive(parent, parent_bone, head, pole, query, chain - 1)?; + + // determine the relative translation for this bone + let (mut transform, mut global_transform, _) = query.get_mut(entity).unwrap(); + transform.rotation = Quat::from_affine3(&parent_global_transform.affine().inverse()) * rotation; + *global_transform = parent_global_transform.mul_transform(*transform); + + Ok(*global_transform) + } +} + +impl Plugin for InverseKinematicsPlugin { + fn build(&self, app: &mut App) { + app.add_system_to_stage( + CoreStage::PostUpdate, + inverse_kinematics_solver_system.after(TransformSystem::TransformPropagate), + ); + } +}