use bevy::{
ecs::{
component::{ComponentHooks, ComponentId, StorageType},
query::QueryEntityError,
system::QueryLens,
world::DeferredWorld,
},
math::{Affine3A, Vec3A},
prelude::*,
};
use crate::constraint::*;
use crate::util::QuatExt;
const DEFAULT_TARGET_SQ_THRESHOLD: f32 = 0.001;
const DEFAULT_MAX_ITTERATIONS: usize = 10;
const BONE_AXIS: Vec3 = Vec3::Y;
#[derive(Clone, Copy)]
pub enum IkChainError {
IkComponentQueryError,
}
impl core::error::Error for IkChainError {}
impl core::fmt::Display for IkChainError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Failed to query required IK chain components")
}
}
impl core::fmt::Debug for IkChainError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "IkChainError")
}
}
impl From<QueryEntityError<'_>> for IkChainError {
fn from(_: QueryEntityError) -> Self {
Self::IkComponentQueryError
}
}
struct JointData {
position: Vec3,
rotation: Quat,
length: f32,
forward_axis: Vec3,
up_axis: Vec3,
}
#[derive(Reflect)]
pub struct IkChain {
pub enabled: bool,
pub target: Vec3,
pub threshold: f32,
pub max_iterations: usize,
pub(crate) joint_count: usize,
pub(crate) joint_lengths: Vec<f32>,
}
impl Component for IkChain {
const STORAGE_TYPE: StorageType = StorageType::Table;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_insert(ik_chain_insert_hook);
}
}
impl IkChain {
pub fn new(joint_count: usize) -> Self {
Self {
enabled: true,
target: Vec3::ZERO,
threshold: DEFAULT_TARGET_SQ_THRESHOLD,
max_iterations: DEFAULT_MAX_ITTERATIONS,
joint_count,
joint_lengths: vec![],
}
}
pub(crate) fn solve(
&self,
entity: &Entity,
constraint_query: &mut Query<(Option<&SwingConstraint>, Option<&TwistConstraint>)>,
transform_query: &mut Query<(Option<&Parent>, &mut Transform, &GlobalTransform)>,
) -> Result<(), IkChainError> {
if !self.enabled {
return Ok(());
}
let dist = transform_query
.get(*entity)
.map(|(_, _, gt)| gt.translation().distance_squared(self.target))?;
if dist < self.threshold {
return Ok(());
}
let mut constraint_lens: QueryLens<(
Option<&Parent>,
&GlobalTransform,
Option<&SwingConstraint>,
Option<&TwistConstraint>,
)> = constraint_query.join_filtered(transform_query);
let lens_query = constraint_lens.query();
let mut joints: Vec<JointData> = Vec::with_capacity(self.joint_count);
let mut constraint_refs: Vec<Vec<&dyn Constraint>> = Vec::with_capacity(self.joint_count);
let mut current_joint = *entity;
for i in 0..self.joint_count {
let (parent, global_transform, swing_constraint, twist_constraint) =
lens_query.get(current_joint)?;
let transform = global_transform.compute_transform();
let mut cs: Vec<&dyn Constraint> = Vec::new();
if let Some(sc) = swing_constraint {
cs.push(sc);
}
if let Some(tc) = twist_constraint {
cs.push(tc);
}
let forward_axis = if i < self.joint_count - 1 {
let local_transform = transform_query.get(current_joint).map(|(_, t, _)| t)?;
local_transform.translation.normalize_or(BONE_AXIS)
} else {
BONE_AXIS
};
joints.push(JointData {
position: global_transform.translation(),
rotation: transform.rotation,
length: if i < self.joint_lengths.len() {
self.joint_lengths[i]
} else {
0.0
},
forward_axis,
up_axis: Vec3::Z,
});
constraint_refs.push(cs);
if i < self.joint_count - 1 {
current_joint = **parent.unwrap();
}
}
for _ in 0..self.max_iterations {
if self.target.distance_squared(joints[0].position) < self.threshold {
break;
}
self.backward_pass(&mut joints);
self.forward_pass(&mut joints, &constraint_refs);
}
let mut current_joint = *entity;
for i in 0..self.joint_count - 1 {
let (parent, mut transform, _) = transform_query.get_mut(current_joint)?;
let parent_gt: GlobalTransform = Affine3A {
matrix3: bevy::math::Mat3A::from_quat(joints[i + 1].rotation),
translation: Vec3A::from(joints[i + 1].position),
}
.into();
let child_gt: GlobalTransform = Affine3A {
matrix3: bevy::math::Mat3A::from_quat(joints[i].rotation),
translation: Vec3A::from(joints[i].position),
}
.into();
let local = child_gt.reparented_to(&parent_gt);
transform.translation = local.translation;
transform.rotation = local.rotation;
current_joint = **parent.unwrap();
}
Ok(())
}
fn backward_pass(&self, joints: &mut [JointData]) {
let root_idx = joints.len() - 1;
let root_origin = joints[root_idx].position;
joints[0].position = self.target;
for i in 1..joints.len() {
let direction = (joints[i].position - joints[i - 1].position).normalize();
joints[i].position = joints[i - 1].position + direction * joints[i - 1].length;
}
joints[root_idx].position = root_origin;
}
fn forward_pass(&self, joints: &mut [JointData], constraints: &[Vec<&dyn Constraint>]) {
let root_idx = joints.len() - 1;
joints[root_idx - 1].position = joints[root_idx].position
+ joints[root_idx].rotation * BONE_AXIS * joints[root_idx - 1].length;
for i in (1..root_idx).rev() {
let direction = (joints[i - 1].position - joints[i].position).normalize();
let (lower, upper) = joints.split_at_mut(i + 1);
let parent_rotation = upper[0].rotation;
let joint = &mut lower[i];
apply_constraints(joint, parent_rotation, direction, &constraints[i]);
joints[i - 1].position =
joints[i].position + joints[i].rotation * BONE_AXIS * joints[i - 1].length;
}
let tip_dir = (joints[0].position - joints[1].position).normalize();
if tip_dir.length_squared() > 0.0 {
joints[0].rotation = Quat::look_rotation_y(tip_dir, joints[1].rotation * Vec3::Z);
}
}
}
fn apply_constraints(
joint: &mut JointData,
parent_rotation: Quat,
direction: Vec3,
constraints: &[&dyn Constraint],
) {
if constraints.is_empty() {
joint.rotation = Quat::look_rotation_y(direction, Vec3::Z);
return;
}
let constraint_forward = parent_rotation * joint.forward_axis;
let constraint_up = parent_rotation * joint.up_axis;
let rotation_global = Quat::look_rotation_y(constraint_forward, constraint_up);
let target_rotation = Quat::look_rotation_y(direction, Vec3::Z);
let rotation_local = rotation_global.inverse() * target_rotation;
let (mut twist, mut swing) = rotation_local.decompose(BONE_AXIS);
constraints.iter().for_each(|constraint| {
(swing, twist) = constraint.apply(swing, twist);
});
joint.rotation = rotation_global * swing * twist;
}
fn ik_chain_insert_hook(mut world: DeferredWorld, entity: Entity, _component_id: ComponentId) {
let joint_count = { world.get::<IkChain>(entity).unwrap().joint_count };
if joint_count < 2 {
panic!("IK chain must have at least 2 joints.");
}
let mut tail = entity;
let mut lengths = Vec::<f32>::with_capacity(joint_count - 1);
for _ in 1..joint_count {
let joint = {
let Some(joint) = world.get::<Parent>(tail) else {
panic!(
"Parent not found for entity {:?} while building IK chain (length {:?})",
tail, joint_count
);
};
**joint
};
let joint_pos = {
let Some(transform) = world.get::<GlobalTransform>(joint) else {
panic!("Parent transform not found for entity {:?} while building IK chain (length {:?})", joint, joint_count);
};
transform.translation()
};
let tail_pos = {
let Some(transform) = world.get::<GlobalTransform>(tail) else {
panic!(
"Transform not found for entity {:?} while building IK chain (length {:?})",
tail, joint_count
);
};
transform.translation()
};
let diff = tail_pos - joint_pos;
lengths.push(diff.length());
tail = joint;
}
let Some(mut chain) = world.get_mut::<IkChain>(entity) else {
panic!(
"IkChain component not found for entity {:?} while building IK chain.",
entity
);
};
chain.joint_lengths = lengths;
}