//----------------------------------------------------------------------------//
//                                                                            //
// 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 <algorithm>

#include "framework/application.h"
#include "framework/imgui.h"
#include "framework/renderer.h"
#include "framework/utils.h"
#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"

// 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 rest 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_rest_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 rest pose
    for (size_t i = 0; i < locals_.size(); ++i) {
      locals_[i] = skeleton_.joint_rest_poses()[i];
    }

    return true;
  }

  virtual void OnDestroy() {}

  virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
    char label[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) {
        snprintf(label, sizeof(label), "Soften: %.2g", soften_);
        _im_gui->DoSlider(label, 0.f, 1.f, &soften_, 2.f);
        snprintf(label, sizeof(label), "Twist angle: %.0f",
                twist_angle_ * ozz::math::kRadianToDegree);
        _im_gui->DoSlider(label, -ozz::math::kPi, ozz::math::kPi, &twist_angle_);
        snprintf(label, sizeof(label), "Weight: %.2g", weight_);
        _im_gui->DoSlider(label, 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) {
            snprintf(label, sizeof(label), "x %.2g", pole_vector.x);
            _im_gui->DoSlider(label, -1.f, 1.f, &pole_vector.x);
            snprintf(label, sizeof(label), "y %.2g", pole_vector.y);
            _im_gui->DoSlider(label, -1.f, 1.f, &pole_vector.y);
            snprintf(label, sizeof(label), "z %.2g", pole_vector.z);
            _im_gui->DoSlider(label, -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");
        snprintf(label, sizeof(label), "%.2g", target_extent_);
        _im_gui->DoSlider(label, 0.f, 1.f, &target_extent_);

        _im_gui->DoLabel("Target Offset");
        const float kOffsetRange = 1.f;
        snprintf(label, sizeof(label), "x %.2g", target_offset_.x);
        _im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &target_offset_.x);
        snprintf(label, sizeof(label), "y %.2g", target_offset_.y);
        _im_gui->DoSlider(label, -kOffsetRange, kOffsetRange, &target_offset_.y);
        snprintf(label, sizeof(label), "z %.2g", target_offset_.z);
        _im_gui->DoSlider(label, -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");
        snprintf(label, sizeof(label), "x %.2g", root_translation_.x);
        _im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.x);
        snprintf(label, sizeof(label), "y %.2g", root_translation_.y);
        _im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.y);
        snprintf(label, sizeof(label), "z %.2g", root_translation_.z);
        _im_gui->DoSlider(label, -1.f, 1.f, &root_translation_.z);

        // Rotation (in euler form)
        _im_gui->DoLabel("Rotation");
        ozz::math::Float3 euler = root_euler_ * ozz::math::kRadianToDegree;
        snprintf(label, sizeof(label), "yaw %.3g", euler.x);
        _im_gui->DoSlider(label, -180.f, 180.f, &euler.x);
        snprintf(label, sizeof(label), "pitch %.3g", euler.y);
        _im_gui->DoSlider(label, -180.f, 180.f, &euler.y);
        snprintf(label, sizeof(label), "roll %.3g", euler.z);
        _im_gui->DoSlider(label, -180.f, 180.f, &euler.z);
        root_euler_ = euler * ozz::math::kDegreeToRadian;

        // Scale (must be uniform and not 0)
        _im_gui->DoLabel("Scale");
        snprintf(label, sizeof(label), "%.2g", root_scale_);
        _im_gui->DoSlider(label, -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);
}
