//----------------------------------------------------------------------------//
//                                                                            //
// 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 "gtest/gtest.h"
#include "ozz/animation/runtime/blending_job.h"
#include "ozz/base/log.h"
#include "ozz/base/maths/gtest_math_helper.h"
#include "ozz/base/maths/soa_transform.h"

using ozz::animation::BlendingJob;

TEST(JobValidity, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
  const ozz::math::SimdFloat4 zero = ozz::math::simd_float4::zero();
  BlendingJob::Layer layers[2];
  const ozz::math::SoaTransform rest_poses[3] = {identity, identity, identity};
  const ozz::math::SoaTransform input_transforms[3] = {identity, identity,
                                                       identity};
  ozz::math::SoaTransform output_transforms[3] = {identity, identity, identity};
  ozz::math::SimdFloat4 joint_weights[3] = {zero, zero, zero};

  layers[0].transform = input_transforms;
  layers[1].transform = {input_transforms, input_transforms + 2};

  {  // Empty/default job.
    BlendingJob job;
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Invalid output.
    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }
  {  // Layers are optional.
    BlendingJob job;
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }
  {  // Invalid rest pose.
    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }
  {  // Invalid layer input range, too small.
    BlendingJob::Layer invalid_layers[2];
    invalid_layers[0].transform = {input_transforms, input_transforms + 1};
    invalid_layers[1].transform = {input_transforms, input_transforms + 2};

    BlendingJob job;
    job.layers = invalid_layers;
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }
  {  // Invalid output range, smaller output.
    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 1};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Invalid smaller input.
    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 3};
    job.output = {output_transforms, output_transforms + 3};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Invalid threshold.
    BlendingJob job;
    job.threshold = 0.f;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Invalid joint weights range.
    layers[0].joint_weights = {joint_weights, joint_weights + 1};

    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Valid job.
    layers[0].joint_weights = {nullptr, nullptr};

    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }

  {  // Valid joint weights range.
    layers[0].joint_weights = {joint_weights, joint_weights + 2};

    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }

  {  // Valid job, bigger output.
    layers[0].joint_weights = {joint_weights, joint_weights + 2};

    BlendingJob job;
    job.layers = {layers, layers + 2};
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 3};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }

  {  // Valid no layers.
    BlendingJob job;
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }
}

TEST(JobValidityAdditive, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();
  const ozz::math::SimdFloat4 zero = ozz::math::simd_float4::zero();
  BlendingJob::Layer layers[2];
  BlendingJob::Layer additive_layers[2];

  const ozz::math::SoaTransform rest_poses[3] = {identity, identity, identity};
  const ozz::math::SoaTransform input_transforms[3] = {identity, identity,
                                                       identity};
  ozz::math::SoaTransform output_transforms[3] = {identity, identity, identity};
  ozz::math::SimdFloat4 joint_weights[3] = {zero, zero, zero};

  layers[0].transform = input_transforms;
  layers[1].transform = input_transforms;

  additive_layers[0].transform = input_transforms;
  additive_layers[1].transform = input_transforms;

  {  // Valid additive job, no normal blending.
    BlendingJob job;
    job.additive_layers = additive_layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }

  {  // Valid additive job, with normal blending also.

    BlendingJob job;
    job.layers = layers;
    job.additive_layers = additive_layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }

  {  // Invalid layer input range, too small.
    BlendingJob::Layer invalid_layers[2];
    invalid_layers[0].transform = {input_transforms, input_transforms + 1};
    invalid_layers[1].transform = {input_transforms, input_transforms + 2};

    BlendingJob job;
    job.layers = layers;
    job.additive_layers = invalid_layers;
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_FALSE(job.Validate());
    EXPECT_FALSE(job.Run());
  }

  {  // Valid additive job, with per-joint weights.
    layers[0].joint_weights = {joint_weights, joint_weights + 2};

    BlendingJob job;
    job.additive_layers = additive_layers;
    job.rest_pose = {rest_poses, rest_poses + 2};
    job.output = {output_transforms, output_transforms + 2};
    EXPECT_TRUE(job.Validate());
    EXPECT_TRUE(job.Run());
  }
}

TEST(Empty, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[2] = {identity, identity};
  rest_poses[0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  rest_poses[0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 10.f, 20.f, 30.f),
      ozz::math::simd_float4::Load(40.f, 50.f, 60.f, 70.f),
      ozz::math::simd_float4::Load(80.f, 90.f, 100.f, 110.f));
  rest_poses[1].translation = rest_poses[0].translation *
                              ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);
  rest_poses[1].scale =
      rest_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);

  BlendingJob job;
  job.rest_pose = rest_poses;
  ozz::math::SoaTransform output_transforms[2];
  job.output = output_transforms;

  EXPECT_TRUE(job.Validate());
  EXPECT_TRUE(job.Run());

  EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 1.f, 2.f, 3.f, 4.f,
                      5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
  EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 0.f, 10.f, 20.f, 30.f, 40.f,
                      50.f, 60.f, 70.f, 80.f, 90.f, 100.f, 110.f);
  EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, 2.f, 4.f, 6.f, 8.f,
                      10.f, 12.f, 14.f, 16.f, 18.f, 20.f, 22.f);
  EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 0.f, 20.f, 40.f, 60.f, 80.f,
                      100.f, 120.f, 140.f, 160.f, 180.f, 200.f, 220.f);
}

TEST(Weight, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[2][2] = {{identity, identity},
                                                    {identity, identity}};
  input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  input_transforms[0][1].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
      ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
      ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
  input_transforms[1][0].translation = -input_transforms[0][0].translation;
  input_transforms[1][1].translation = -input_transforms[0][1].translation;

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[2] = {identity, identity};
  rest_poses[0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  rest_poses[1].scale =
      rest_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);

  {
    BlendingJob::Layer layers[2];
    layers[0].transform = input_transforms[0];
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[2];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    // Weight 0 (a bit less must give the same result) for the first layer,
    // 1 for the second.
    layers[0].weight = -.07f;
    layers[1].weight = 1.f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
                        -3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, -12.f, -13.f, -14.f,
                        -15.f, -16.f, -17.f, -18.f, -19.f, -20.f, -21.f, -22.f,
                        -23.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);

    // Weight 1 for the first layer, 0 for the second.
    layers[0].weight = 1.f;
    layers[1].weight = 1e-27f;  // Very low weight value.

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 1.f, 2.f, 3.f,
                        4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 12.f, 13.f, 14.f,
                        15.f, 16.f, 17.f, 18.f, 19.f, 20.f, 21.f, 22.f, 23.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);

    // Weight .5 for both layers.
    layers[0].weight = .5f;
    layers[1].weight = .5f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
                        0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, 0.f, 0.f, 0.f,
                        0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
}

TEST(JointWeights, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[2][2] = {{identity, identity},
                                                    {identity, identity}};
  input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  input_transforms[0][1].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
      ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
      ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
  input_transforms[1][0].translation = -input_transforms[0][0].translation;
  input_transforms[1][1].translation = -input_transforms[0][1].translation;
  ozz::math::SimdFloat4 joint_weights[2][2] = {
      {ozz::math::simd_float4::Load(1.f, 1.f, 0.f, 0.f),
       ozz::math::simd_float4::Load(1.f, 0.f, 1.f, 1.f)},
      {ozz::math::simd_float4::Load(1.f, 1.f, 1.f, 0.f),
       ozz::math::simd_float4::Load(0.f, 1.f, 1.f, 1.f)}};
  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[2] = {identity, identity};
  rest_poses[0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f),
      ozz::math::simd_float4::Load(14.f, 15.f, 16.f, 17.f),
      ozz::math::simd_float4::Load(18.f, 19.f, 20.f, 21.f));
  rest_poses[0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  rest_poses[1].scale =
      rest_poses[0].scale * ozz::math::simd_float4::Load(2.f, 2.f, 2.f, 2.f);

  BlendingJob::Layer layers[2];
  layers[0].transform = input_transforms[0];
  layers[0].joint_weights = joint_weights[0];
  layers[1].transform = input_transforms[1];
  layers[1].joint_weights = joint_weights[1];

  {  // Weight .5 for both layers.
    ozz::math::SoaTransform output_transforms[3];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    layers[0].weight = .5f;
    layers[1].weight = .5f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, -2.f, 13.f,
                        0.f, 0.f, -6.f, 17.f, 0.f, 0.f, -10.f, 21.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 3.f, 1.f,
                        1.f, 1.f, 7.f, 1.f, 1.f, 1.f, 11.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 12.f, -13.f, 0.f, 0.f,
                        16.f, -17.f, 0.f, 0.f, 20.f, -21.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
  {  // Null weight for the first layer.
    ozz::math::SoaTransform output_transforms[2];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    layers[0].weight = 0.f;
    layers[1].weight = 1.f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
                        13.f, -4.f, -5.f, -6.f, 17.f, -8.f, -9.f, -10.f, 21.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 3.f, 1.f,
                        1.f, 1.f, 7.f, 1.f, 1.f, 1.f, 11.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].translation, 0.f, -13.f, -14.f,
                        -15.f, 0.f, -17.f, -18.f, -19.f, 0.f, -21.f, -22.f,
                        -23.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[1].scale, 0.f, 1.f, 1.f, 1.f, 8.f,
                        1.f, 1.f, 1.f, 16.f, 1.f, 1.f, 1.f);
  }
}

TEST(Normalize, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[2][1] = {{identity}, {identity}};

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[1] = {identity};
  rest_poses[0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));

  input_transforms[0][0].rotation = ozz::math::SoaQuaternion::Load(
      ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
      ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
      ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
      ozz::math::simd_float4::Load(.70710677f, 1.f, .70710677f, .9238795f));
  input_transforms[1][0].rotation = ozz::math::SoaQuaternion::Load(
      ozz::math::simd_float4::Load(0.f, .70710677f, -.70710677f, -.382683432f),
      ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
      ozz::math::simd_float4::Load(0.f, 0.f, -.70710677f, 0.f),
      ozz::math::simd_float4::Load(1.f, .70710677f, 0.f, -.9238795f));

  {  // Un-normalized weights < 1.f.
    input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(2.f, 3.f, 4.f, 5.f),
        ozz::math::simd_float4::Load(6.f, 7.f, 8.f, 9.f),
        ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f));
    input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(3.f, 4.f, 5.f, 6.f),
        ozz::math::simd_float4::Load(7.f, 8.f, 9.f, 10.f),
        ozz::math::simd_float4::Load(11.f, 12.f, 13.f, 14.f));

    BlendingJob::Layer layers[2];
    layers[0].weight = 0.2f;
    layers[0].transform = input_transforms[0];
    layers[1].weight = 0.3f;
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 2.6f, 3.6f, 4.6f,
                        5.6f, 6.6f, 7.6f, 8.6f, 9.6f, 10.6f, 11.6f, 12.6f,
                        13.6f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .30507791f,
                                .45761687f, -.58843851f, .38268352f, 0.f, 0.f,
                                .39229235f, 0.f, 0.f, 0.f, -.58843851f, 0.f,
                                .95224595f, .88906217f, .39229235f, .92387962f);
    EXPECT_TRUE(
        ozz::math::AreAllTrue(IsNormalizedEst(output_transforms[0].rotation)));
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
  {  // Un-normalized weights > 1.f.
    input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(5.f, 10.f, 15.f, 20.f),
        ozz::math::simd_float4::Load(25.f, 30.f, 35.f, 40.f),
        ozz::math::simd_float4::Load(45.f, 50.f, 55.f, 60.f));
    input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(10.f, 15.f, 20.f, 25.f),
        ozz::math::simd_float4::Load(30.f, 35.f, 40.f, 45.f),
        ozz::math::simd_float4::Load(50.f, 55.f, 60.f, 65.f));

    BlendingJob::Layer layers[2];
    layers[0].weight = 2.f;
    layers[0].transform = input_transforms[0];
    layers[1].weight = 3.f;
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 8.f, 13.f, 18.f, 23.f,
                        28.f, 33.f, 38.f, 43.f, 48.f, 53.f, 58.f, 63.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .30507791f,
                                .45761687f, -.58843851f, .38268352f, 0.f, 0.f,
                                .39229235f, 0.f, 0.f, 0.f, -.58843851f, 0.f,
                                .95224595f, .88906217f, .39229235f, .92387962f);
    EXPECT_TRUE(
        ozz::math::AreAllTrue(IsNormalizedEst(output_transforms[0].rotation)));
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
  {  // Un-normalized weights > 1.f, with per-joint weights.
    input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(5.f, 10.f, 15.f, 20.f),
        ozz::math::simd_float4::Load(25.f, 30.f, 35.f, 40.f),
        ozz::math::simd_float4::Load(45.f, 50.f, 55.f, 60.f));
    input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
        ozz::math::simd_float4::Load(10.f, 15.f, 20.f, 25.f),
        ozz::math::simd_float4::Load(30.f, 35.f, 40.f, 45.f),
        ozz::math::simd_float4::Load(50.f, 55.f, 60.f, 65.f));
    ozz::math::SimdFloat4 joint_weights[1] = {
        ozz::math::simd_float4::Load(1.f, -1.f, 2.f, .1f)};

    BlendingJob::Layer layers[2];
    layers[0].weight = 2.f;
    layers[0].transform = input_transforms[0];
    layers[1].weight = 3.f;
    layers[1].transform = input_transforms[1];
    layers[1].joint_weights = joint_weights;

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 8.f, 10.f,
                        150.f / 8.f, 47.5f / 2.3f, 28.f, 30.f, 310.f / 8.f,
                        93.5f / 2.3f, 48.f, 50.f, 470.f / 8.f, 139.5f / 2.3f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
}

TEST(Threshold, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[2][1] = {{identity}, {identity}};

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[1] = {identity};
  rest_poses[0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));

  input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(2.f, 3.f, 4.f, 5.f),
      ozz::math::simd_float4::Load(6.f, 7.f, 8.f, 9.f),
      ozz::math::simd_float4::Load(10.f, 11.f, 12.f, 13.f));
  input_transforms[1][0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(3.f, 4.f, 5.f, 6.f),
      ozz::math::simd_float4::Load(7.f, 8.f, 9.f, 10.f),
      ozz::math::simd_float4::Load(11.f, 12.f, 13.f, 14.f));

  {  // Threshold is not reached.
    BlendingJob::Layer layers[2];
    layers[0].weight = 0.04f;
    layers[0].transform = input_transforms[0];
    layers[1].weight = 0.06f;
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.threshold = .1f;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 2.6f, 3.6f, 4.6f,
                        5.6f, 6.6f, 7.6f, 8.6f, 9.6f, 10.6f, 11.6f, 12.6f,
                        13.6f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);
  }
  {  // Threshold is reached at 100%.
    BlendingJob::Layer layers[2];
    layers[0].weight = 1e-27f;
    layers[0].transform = input_transforms[0];
    layers[1].weight = 0.f;
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.threshold = .1f;
    job.layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
                        0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 0.f, 1.f, 2.f, 3.f, 4.f,
                        5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f);
  }
}

TEST(AdditiveWeight, BlendingJob) {
  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[2][1];
  input_transforms[0][0].translation = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
      ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
      ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f));
  input_transforms[0][0].rotation = ozz::math::SoaQuaternion::Load(
      ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
      ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
      ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
      ozz::math::simd_float4::Load(.70710677f, 1.f, -.70710677f, .9238795f));
  input_transforms[0][0].scale = ozz::math::SoaFloat3::Load(
      ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
      ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
      ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f));
  input_transforms[1][0].translation = -input_transforms[0][0].translation;
  input_transforms[1][0].rotation = Conjugate(input_transforms[0][0].rotation);
  input_transforms[1][0].scale = -input_transforms[0][0].scale;

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[1] = {
      {ozz::math::SoaFloat3::Load(
           ozz::math::simd_float4::Load(-0.f, -1.f, -2.f, -3.f),
           ozz::math::simd_float4::Load(-4.f, -5.f, -6.f, -7.f),
           ozz::math::simd_float4::Load(-8.f, -9.f, -10.f, -11.f)),
       ozz::math::SoaQuaternion::Load(
           ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
           ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
           ozz::math::simd_float4::Load(.70710677f, 1.f, -.70710677f,
                                        .9238795f),
           ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f)),
       ozz::math::SoaFloat3::Load(
           ozz::math::simd_float4::Load(-12.f, -13.f, -14.f, -15.f),
           ozz::math::simd_float4::Load(-16.f, -17.f, -18.f, -19.f),
           ozz::math::simd_float4::Load(-20.f, -21.f, -22.f, -23.f))}};

  {
    BlendingJob::Layer layers[1];
    layers[0].transform = input_transforms[0];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.additive_layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    // No weight for the 1st layer.
    layers[0].weight = 0.f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
                        -3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f,
                                .70710677f, 0.f, .70710677f, 0.f, 0.f,
                                .382683432f, .70710677f, 1.f, -.70710677f,
                                .9238795f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, -12.f, -13.f, -14.f, -15.f,
                        -16.f, -17.f, -18.f, -19.f, -20.f, -21.f, -22.f, -23.f);

    // .5 weight for the 1st layer.
    layers[0].weight = .5f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, -.5f, -1.f,
                        -1.5f, -2.f, -2.5f, -3.f, -3.5f, -4.f, -4.5f, -5.f,
                        -5.5f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f,
                                .3826834f, 0.f, .9238795f, 0.f, 0.f,
                                .555570245f, .3826834f, 1.f, -.9238795f,
                                .831469595f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, -78.f, -91.f, -105.f,
                        -120.f, -136.f, -153.f, -171.f, -190.f, -210.f, -231.f,
                        -253.f, -276.f);

    // Full weight for the 1st layer.
    layers[0].weight = 1.f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
                        0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
                                0.f, 1.f, 0.f, 0.f, .70710677f, 0.f, 1.f, -1.f,
                                .70710677f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, -144.f, -169.f, -196.f,
                        -225.f, -256.f, -289.f, -324.f, -361.f, -400.f, -441.f,
                        -484.f, -529.f);
  }

  {
    BlendingJob::Layer layers[2];
    layers[0].transform = input_transforms[0];
    layers[1].transform = input_transforms[1];

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.additive_layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    // No weight for the 1st layer.
    layers[0].weight = 0.f;
    layers[1].weight = 1.f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -2.f, -4.f,
                        -6.f, -8.f, -10.f, -12.f, -14.f, -16.f, -18.f, -20.f,
                        -22.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 1.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 1.f,
                                0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 144.f, 169.f, 196.f, 225.f,
                        256.f, 289.f, 324.f, 361.f, 400.f, 441.f, 484.f, 529.f);

    // Full weight for the both layer.
    layers[0].weight = 1.f;
    layers[1].weight = 1.f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
                        -3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f,
                                .70710677f, 0.f, .70710677f, 0.f, 0.f,
                                .3826834f, .70710677f, 1.f, -.70710677f,
                                .9238795f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1728.f, 2197.f, 2744.f,
                        3375.f, 4096.f, 4913.f, 5832.f, 6859.f, 8000.f, 9261.f,
                        10648.f, 12167.f);

    // Subtract second layer.
    layers[0].weight = .5f;
    layers[1].transform = input_transforms[0];
    layers[1].weight = -.5f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -1.f, -2.f,
                        -3.f, -4.f, -5.f, -6.f, -7.f, -8.f, -9.f, -10.f, -11.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f,
                                .70710677f, 0.f, .70710677f, 0.f, 0.f,
                                .3826834f, .70710677f, 1.f, -.70710677f,
                                .9238795f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAFLOAT3_EQ_EST(output_transforms[0].scale, -12.f, -13.f, -14.f,
                            -15.f, -16.f, -17.f, -18.f, -19.f, -20.f, -21.f,
                            -22.f, -23.f);
  }
}

TEST(AdditiveJointWeight, BlendingJob) {
  const ozz::math::SoaTransform identity = ozz::math::SoaTransform::identity();

  // Initialize inputs.
  ozz::math::SoaTransform input_transforms[1] = {
      {input_transforms[0].translation = ozz::math::SoaFloat3::Load(
           ozz::math::simd_float4::Load(0.f, 1.f, 2.f, 3.f),
           ozz::math::simd_float4::Load(4.f, 5.f, 6.f, 7.f),
           ozz::math::simd_float4::Load(8.f, 9.f, 10.f, 11.f)),
       input_transforms[0].rotation = ozz::math::SoaQuaternion::Load(
           ozz::math::simd_float4::Load(.70710677f, 0.f, 0.f, .382683432f),
           ozz::math::simd_float4::Load(0.f, 0.f, .70710677f, 0.f),
           ozz::math::simd_float4::Load(0.f, 0.f, 0.f, 0.f),
           ozz::math::simd_float4::Load(.70710677f, 1.f, -.70710677f,
                                        .9238795f)),
       input_transforms[0].scale = ozz::math::SoaFloat3::Load(
           ozz::math::simd_float4::Load(12.f, 13.f, 14.f, 15.f),
           ozz::math::simd_float4::Load(16.f, 17.f, 18.f, 19.f),
           ozz::math::simd_float4::Load(20.f, 21.f, 22.f, 23.f))}};

  ozz::math::SimdFloat4 joint_weights[1] = {
      ozz::math::simd_float4::Load(1.f, .5f, 0.f, -1.f)};

  // Initialize rest pose.
  ozz::math::SoaTransform rest_poses[1] = {identity};

  {
    BlendingJob::Layer layers[1];
    layers[0].transform = input_transforms;
    layers[0].joint_weights = joint_weights;

    ozz::math::SoaTransform output_transforms[1];

    BlendingJob job;
    job.additive_layers = layers;
    job.rest_pose = rest_poses;
    job.output = output_transforms;

    // No weight for the 1st layer.
    layers[0].weight = 0.f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, 0.f, 0.f, 0.f,
                        0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, 0.f, 0.f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                1.f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 1.f, 1.f, 1.f, 1.f, 1.f,
                        1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f);

    // .5 weight for the 1st layer.
    layers[0].weight = .5f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, .25f, 0.f, 0.f,
                        2.f, 1.25f, 0.f, 0.f, 4.f, 2.25f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .3826834f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                0.f, .9238795f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 6.5f, 4.f, 1.f, 1.f, 8.5f,
                        5.f, 1.f, 1.f, 10.5f, 6.f, 1.f, 1.f);

    // Full weight for the 1st layer.
    layers[0].weight = 1.f;
    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, 0.f, .5f, 0.f, 0.f,
                        4.f, 2.5f, 0.f, 0.f, 8.f, 4.5f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, .70710677f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                0.f, .70710677f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ(output_transforms[0].scale, 12.f, 7.f, 1.f, 1.f, 16.f,
                        9.f, 1.f, 1.f, 20.f, 11.f, 1.f, 1.f);

    // Subtract layer.
    layers[0].weight = -1.f;

    EXPECT_TRUE(job.Run());

    EXPECT_SOAFLOAT3_EQ(output_transforms[0].translation, -0.f, -.5f, 0.f, 0.f,
                        -4.f, -2.5f, 0.f, 0.f, -8.f, -4.5f, 0.f, 0.f);
    EXPECT_SOAQUATERNION_EQ_EST(output_transforms[0].rotation, -.70710677f, 0.f,
                                0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
                                0.f, .70710677f, 1.f, 1.f, 1.f);
    EXPECT_SOAFLOAT3_EQ_EST(output_transforms[0].scale, 1.f / 12.f, 1.f / 7.f,
                            1.f, 1.f, 1.f / 16.f, 1.f / 9.f, 1.f, 1.f,
                            1.f / 20.f, 1.f / 11.f, 1.f, 1.f);
  }
}
