AnimTestbed/3rdparty/ozz-animation/samples/two_bone_ik/sample_two_bone_ik.cc

437 lines
16 KiB
C++

//----------------------------------------------------------------------------//
// //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
// and distributed under the MIT License (MIT). //
// //
// Copyright (c) Guillaume Blanc //
// //
// Permission is hereby granted, free of charge, to any person obtaining a //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation //
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
// and/or sell copies of the Software, and to permit persons to whom the //
// Software is furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
// DEALINGS IN THE SOFTWARE. //
// //
//----------------------------------------------------------------------------//
#include "ozz/animation/runtime/ik_two_bone_job.h"
#include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/box.h"
#include "ozz/base/maths/simd_math.h"
#include "ozz/base/maths/simd_quaternion.h"
#include "ozz/base/maths/soa_transform.h"
#include "ozz/base/memory/allocator.h"
#include "ozz/options/options.h"
#include "framework/application.h"
#include "framework/imgui.h"
#include "framework/renderer.h"
#include "framework/utils.h"
#include <algorithm>
// Skeleton archive can be specified as an option.
OZZ_OPTIONS_DECLARE_STRING(skeleton,
"Path to the skeleton (ozz archive format).",
"media/skeleton.ozz", false)
class TwoBoneIKSampleApplication : public ozz::sample::Application {
public:
TwoBoneIKSampleApplication()
: start_joint_(-1),
mid_joint_(-1),
end_joint_(-1),
pole_vector(0.f, 1.f, 0.f),
weight_(1.f),
soften_(.97f),
twist_angle_(0.f),
reached_(false),
fix_initial_transform_(true),
two_bone_ik_(true),
show_target_(true),
show_joints_(false),
show_pole_vector_(false),
root_translation_(0.f, 0.f, 0.f),
root_euler_(0.f, 0.f, 0.f),
root_scale_(1.f),
target_extent_(.5f),
target_offset_(0.f, .2f, .1f),
target_(0.f, 0.f, 0.f) {}
protected:
bool ApplyTwoBoneIK() {
// Target and pole should be in model-space, so they must be converted from
// world-space using character inverse root matrix.
// IK jobs must support non invertible matrices (like 0 scale matrices).
ozz::math::SimdInt4 invertible;
const ozz::math::Float4x4 invert_root =
Invert(GetRootTransform(), &invertible);
const ozz::math::SimdFloat4 target_ms = TransformPoint(
invert_root, ozz::math::simd_float4::Load3PtrU(&target_.x));
const ozz::math::SimdFloat4 pole_vector_ms = TransformVector(
invert_root, ozz::math::simd_float4::Load3PtrU(&pole_vector.x));
// Setup IK job.
ozz::animation::IKTwoBoneJob ik_job;
ik_job.target = target_ms;
ik_job.pole_vector = pole_vector_ms;
ik_job.mid_axis = ozz::math::simd_float4::z_axis(); // Middle joint
// rotation axis is
// fixed, and depends
// on skeleton rig.
ik_job.weight = weight_;
ik_job.soften = soften_;
ik_job.twist_angle = twist_angle_;
// Provides start, middle and end joints model space matrices.
ik_job.start_joint = &models_[start_joint_];
ik_job.mid_joint = &models_[mid_joint_];
ik_job.end_joint = &models_[end_joint_];
// Setup output pointers.
ozz::math::SimdQuaternion start_correction;
ik_job.start_joint_correction = &start_correction;
ozz::math::SimdQuaternion mid_correction;
ik_job.mid_joint_correction = &mid_correction;
ik_job.reached = &reached_;
if (!ik_job.Run()) {
return false;
}
// Apply IK quaternions to their respective local-space transforms.
ozz::sample::MultiplySoATransformQuaternion(start_joint_, start_correction,
make_span(locals_));
ozz::sample::MultiplySoATransformQuaternion(mid_joint_, mid_correction,
make_span(locals_));
// Updates model-space matrices now IK has been applied to local transforms.
// All the ancestors of the start of the IK chain must be computed.
ozz::animation::LocalToModelJob ltm_job;
ltm_job.skeleton = &skeleton_;
ltm_job.input = make_span(locals_);
ltm_job.output = make_span(models_);
ltm_job.from =
start_joint_; // Local transforms haven't changed before start_joint_.
ltm_job.to = ozz::animation::Skeleton::kMaxJoints;
if (!ltm_job.Run()) {
return false;
}
return true;
}
virtual bool OnUpdate(float, float _time) {
// Updates sample target position.
if (!MoveTarget(_time)) {
return false;
}
// Reset locals to skeleton bind pose if option is true.
// This allows to always start IK from a fix position (required to test
// weighting), or do IK from the latest computed pose
if (fix_initial_transform_) {
for (size_t i = 0; i < locals_.size(); ++i) {
locals_[i] = skeleton_.joint_bind_poses()[i];
}
}
// Updates model-space matrices from current local-space setup.
// Model-space matrices needs to be updated up to the end joint. Any joint
// after that will need to be recomputed after IK indeed.
ozz::animation::LocalToModelJob ltm_job;
ltm_job.skeleton = &skeleton_;
ltm_job.input = make_span(locals_);
ltm_job.output = make_span(models_);
if (!ltm_job.Run()) {
return false;
}
// Setup and run IK job.
if (two_bone_ik_ && !ApplyTwoBoneIK()) {
return false;
}
return true;
}
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
bool success = true;
// Get skeleton root transform.
const ozz::math::Float4x4 root = GetRootTransform();
if (show_target_ && two_bone_ik_) {
// Displays target
const ozz::sample::Color colors[2][2] = {
{ozz::sample::kRed, ozz::sample::kBlack},
{ozz::sample::kGreen, ozz::sample::kBlack}};
const float kBoxHalfSize = .006f;
const ozz::math::Box box(ozz::math::Float3(-kBoxHalfSize),
ozz::math::Float3(kBoxHalfSize));
success &= _renderer->DrawBoxIm(
box,
ozz::math::Float4x4::Translation(
ozz::math::simd_float4::Load3PtrU(&target_.x)),
colors[reached_]);
}
// Displays pole vector
if (show_pole_vector_) {
ozz::math::Float3 begin;
ozz::math::Store3PtrU(TransformPoint(root, models_[mid_joint_].cols[3]),
&begin.x);
success &= _renderer->DrawSegment(begin, begin + pole_vector,
ozz::sample::kWhite,
ozz::math::Float4x4::identity());
}
// Showing joints
if (show_joints_) {
const float kAxeScale = .1f;
const float kSphereRadius = .009f;
const ozz::math::Float4x4 kAxesScale = ozz::math::Float4x4::Scaling(
ozz::math::simd_float4::Load1(kAxeScale));
for (size_t i = 0; i < 3; ++i) {
const int joints[3] = {start_joint_, mid_joint_, end_joint_};
const ozz::math::Float4x4& transform = root * models_[joints[i]];
success &= _renderer->DrawAxes(transform * kAxesScale);
success &= _renderer->DrawSphereIm(kSphereRadius, transform,
ozz::sample::kWhite);
}
}
// Draws the animated skeleton posture.
success &= _renderer->DrawPosture(skeleton_, make_span(models_), root);
return success;
}
virtual bool OnInitialize() {
// Loads skeleton.
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
return false;
}
// Allocates runtime buffers.
const int num_soa_joints = skeleton_.num_soa_joints();
locals_.resize(num_soa_joints);
const int num_joints = skeleton_.num_joints();
models_.resize(num_joints);
// Find the 3 joints in skeleton hierarchy.
start_joint_ = mid_joint_ = end_joint_ = -1;
for (int i = 0; i < skeleton_.num_joints(); i++) {
const char* joint_name = skeleton_.joint_names()[i];
if (std::strcmp(joint_name, "shoulder") == 0) {
start_joint_ = i;
} else if (std::strcmp(joint_name, "forearm") == 0) {
mid_joint_ = i;
} else if (std::strcmp(joint_name, "wrist") == 0) {
end_joint_ = i;
}
}
// Fails if a joint is missing.
if (start_joint_ < 0 || mid_joint_ < 0 || end_joint_ < 0) {
ozz::log::Err() << "Failed to find required joints." << std::endl;
return false;
}
// Initialize locals from skeleton bind pose
for (size_t i = 0; i < locals_.size(); ++i) {
locals_[i] = skeleton_.joint_bind_poses()[i];
}
return true;
}
virtual void OnDestroy() {}
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
char txt[32];
// IK parameters
_im_gui->DoCheckBox("Fix initial transform", &fix_initial_transform_);
_im_gui->DoCheckBox("Enable two bone ik", &two_bone_ik_);
{
static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "IK parameters", &opened);
if (opened) {
sprintf(txt, "Soften: %.2g", soften_);
_im_gui->DoSlider(txt, 0.f, 1.f, &soften_, 2.f);
sprintf(txt, "Twist angle: %.0f",
twist_angle_ * ozz::math::kRadianToDegree);
_im_gui->DoSlider(txt, -ozz::math::kPi, ozz::math::kPi, &twist_angle_);
sprintf(txt, "Weight: %.2g", weight_);
_im_gui->DoSlider(txt, 0.f, 1.f, &weight_);
{
// Pole vector
static bool pole_opened = true;
ozz::sample::ImGui::OpenClose oc_pole(_im_gui, "Pole vector",
&pole_opened);
if (pole_opened) {
sprintf(txt, "x %.2g", pole_vector.x);
_im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.x);
sprintf(txt, "y %.2g", pole_vector.y);
_im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.y);
sprintf(txt, "z %.2g", pole_vector.z);
_im_gui->DoSlider(txt, -1.f, 1.f, &pole_vector.z);
}
}
}
}
{ // Target position
static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Target position", &opened);
if (opened) {
_im_gui->DoLabel("Target animation extent");
sprintf(txt, "%.2g", target_extent_);
_im_gui->DoSlider(txt, 0.f, 1.f, &target_extent_);
_im_gui->DoLabel("Target Offset");
const float kOffsetRange = 1.f;
sprintf(txt, "x %.2g", target_offset_.x);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.x);
sprintf(txt, "y %.2g", target_offset_.y);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.y);
sprintf(txt, "z %.2g", target_offset_.z);
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &target_offset_.z);
}
}
{ // Root
static bool opened = false;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Root transformation", &opened);
if (opened) {
// Translation
_im_gui->DoLabel("Translation");
sprintf(txt, "x %.2g", root_translation_.x);
_im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.x);
sprintf(txt, "y %.2g", root_translation_.y);
_im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.y);
sprintf(txt, "z %.2g", root_translation_.z);
_im_gui->DoSlider(txt, -1.f, 1.f, &root_translation_.z);
// Rotation (in euler form)
_im_gui->DoLabel("Rotation");
ozz::math::Float3 euler = root_euler_ * ozz::math::kRadianToDegree;
sprintf(txt, "yaw %.3g", euler.x);
_im_gui->DoSlider(txt, -180.f, 180.f, &euler.x);
sprintf(txt, "pitch %.3g", euler.y);
_im_gui->DoSlider(txt, -180.f, 180.f, &euler.y);
sprintf(txt, "roll %.3g", euler.z);
_im_gui->DoSlider(txt, -180.f, 180.f, &euler.z);
root_euler_ = euler * ozz::math::kDegreeToRadian;
// Scale (must be uniform and not 0)
_im_gui->DoLabel("Scale");
sprintf(txt, "%.2g", root_scale_);
_im_gui->DoSlider(txt, -1.f, 1.f, &root_scale_);
}
}
{ // Display options
static bool opened = true;
ozz::sample::ImGui::OpenClose oc(_im_gui, "Display options", &opened);
if (opened) {
_im_gui->DoCheckBox("Show target", &show_target_);
_im_gui->DoCheckBox("Show joints", &show_joints_);
_im_gui->DoCheckBox("Show pole vector", &show_pole_vector_);
}
}
return true;
}
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
const ozz::math::Float3 radius(target_extent_ * .5f);
_bound->min = target_offset_ - radius;
_bound->max = target_offset_ + radius;
}
private:
bool MoveTarget(float _time) {
const float anim_extent = (1.f - std::cos(_time)) * .5f * target_extent_;
const int floor = static_cast<int>(std::fabs(_time) / ozz::math::k2Pi);
target_ = target_offset_;
(&target_.x)[floor % 3] += anim_extent;
return true;
}
ozz::math::Float4x4 GetRootTransform() const {
return ozz::math::Float4x4::Translation(
ozz::math::simd_float4::Load3PtrU(&root_translation_.x)) *
ozz::math::Float4x4::FromEuler(
ozz::math::simd_float4::Load3PtrU(&root_euler_.x)) *
ozz::math::Float4x4::Scaling(
ozz::math::simd_float4::Load1(root_scale_));
}
// Runtime skeleton.
ozz::animation::Skeleton skeleton_;
// Buffer of local transforms as sampled from animation_.
ozz::vector<ozz::math::SoaTransform> locals_;
// Buffer of model space matrices.
ozz::vector<ozz::math::Float4x4> models_;
// Two bone IK setup. Indices of the relevant joints in the chain.
int start_joint_;
int mid_joint_;
int end_joint_;
// Two bone IK parameters.
ozz::math::Float3 pole_vector;
float weight_;
float soften_;
float twist_angle_;
// Two bone IK job "reched" output value.
bool reached_;
// Sample options
bool fix_initial_transform_;
bool two_bone_ik_;
// Sample display options
bool show_target_;
bool show_joints_;
bool show_pole_vector_;
// Root transformation.
ozz::math::Float3 root_translation_;
ozz::math::Float3 root_euler_;
float root_scale_;
// Target positioning and animation.
float target_extent_;
ozz::math::Float3 target_offset_;
ozz::math::Float3 target_;
};
int main(int _argc, const char** _argv) {
const char* title = "Ozz-animation sample: Two bone IK";
return TwoBoneIKSampleApplication().Run(_argc, _argv, "1.0", title);
}