AnimTestbed/3rdparty/ozz-animation/samples/framework/utils.cc

545 lines
19 KiB
C++
Raw Normal View History

2021-11-11 21:22:24 +01:00
//----------------------------------------------------------------------------//
// //
// 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 "framework/utils.h"
#include <cassert>
#include <limits>
#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/animation/runtime/animation.h"
#include "ozz/animation/runtime/local_to_model_job.h"
#include "ozz/animation/runtime/skeleton.h"
#include "ozz/animation/runtime/track.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/geometry/runtime/skinning_job.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
#include "framework/imgui.h"
#include "framework/mesh.h"
namespace ozz {
namespace sample {
PlaybackController::PlaybackController()
: time_ratio_(0.f),
previous_time_ratio_(0.f),
playback_speed_(1.f),
play_(true),
loop_(true) {}
void PlaybackController::Update(const animation::Animation& _animation,
float _dt) {
float new_time = time_ratio_;
if (play_) {
new_time = time_ratio_ + _dt * playback_speed_ / _animation.duration();
}
// Must be called even if time doesn't change, in order to update previous
// frame time ratio. Uses set_time_ratio function in order to update
// previous_time_ an wrap time value in the unit interval (depending on loop
// mode).
set_time_ratio(new_time);
}
void PlaybackController::set_time_ratio(float _ratio) {
previous_time_ratio_ = time_ratio_;
if (loop_) {
// Wraps in the unit interval [0:1], even for negative values (the reason
// for using floorf).
time_ratio_ = _ratio - floorf(_ratio);
} else {
// Clamps in the unit interval [0:1].
time_ratio_ = math::Clamp(0.f, _ratio, 1.f);
}
}
// Gets animation current time.
float PlaybackController::time_ratio() const { return time_ratio_; }
// Gets animation time of last update.
float PlaybackController::previous_time_ratio() const {
return previous_time_ratio_;
}
void PlaybackController::Reset() {
previous_time_ratio_ = time_ratio_ = 0.f;
playback_speed_ = 1.f;
play_ = true;
}
bool PlaybackController::OnGui(const animation::Animation& _animation,
ImGui* _im_gui, bool _enabled,
bool _allow_set_time) {
bool time_changed = false;
if (_im_gui->DoButton(play_ ? "Pause" : "Play", _enabled)) {
play_ = !play_;
}
_im_gui->DoCheckBox("Loop", &loop_, _enabled);
char szLabel[64];
// Uses a local copy of time_ so that set_time is used to actually apply
// changes. Otherwise previous time would be incorrect.
float ratio = time_ratio();
std::sprintf(szLabel, "Animation time: %.2f", ratio * _animation.duration());
if (_im_gui->DoSlider(szLabel, 0.f, 1.f, &ratio, 1.f,
_enabled && _allow_set_time)) {
set_time_ratio(ratio);
// Pause the time if slider as moved.
play_ = false;
time_changed = true;
}
std::sprintf(szLabel, "Playback speed: %.2f", playback_speed_);
_im_gui->DoSlider(szLabel, -5.f, 5.f, &playback_speed_, 1.f, _enabled);
// Allow to reset speed if it is not the default value.
if (_im_gui->DoButton("Reset playback speed",
playback_speed_ != 1.f && _enabled)) {
playback_speed_ = 1.f;
}
return time_changed;
}
namespace {
bool OnRawSkeletonJointGui(
ozz::sample::ImGui* _im_gui,
ozz::animation::offline::RawSkeleton::Joint::Children* _children,
ozz::vector<bool>::iterator* _oc_state) {
char txt[255];
bool modified = false;
for (size_t i = 0; i < _children->size(); ++i) {
ozz::animation::offline::RawSkeleton::Joint& joint = _children->at(i);
bool opened = *(*_oc_state);
ozz::sample::ImGui::OpenClose oc(_im_gui, joint.name.c_str(), &opened);
*(*_oc_state)++ = opened; // Updates state and increment for next joint.
if (opened) {
// Translation
ozz::math::Float3& translation = joint.transform.translation;
_im_gui->DoLabel("Translation");
sprintf(txt, "x %.2g", translation.x);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.x);
sprintf(txt, "y %.2g", translation.y);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.y);
sprintf(txt, "z %.2g", translation.z);
modified |= _im_gui->DoSlider(txt, -1.f, 1.f, &translation.z);
// Rotation (in euler form)
ozz::math::Quaternion& rotation = joint.transform.rotation;
_im_gui->DoLabel("Rotation");
ozz::math::Float3 euler = ToEuler(rotation) * ozz::math::kRadianToDegree;
sprintf(txt, "x %.3g", euler.x);
bool euler_modified = _im_gui->DoSlider(txt, -180.f, 180.f, &euler.x);
sprintf(txt, "y %.3g", euler.y);
euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.y);
sprintf(txt, "z %.3g", euler.z);
euler_modified |= _im_gui->DoSlider(txt, -180.f, 180.f, &euler.z);
if (euler_modified) {
modified = true;
ozz::math::Float3 euler_rad = euler * ozz::math::kDegreeToRadian;
rotation = ozz::math::Quaternion::FromEuler(euler_rad.x, euler_rad.y,
euler_rad.z);
}
// Scale (must be uniform and not 0)
_im_gui->DoLabel("Scale");
ozz::math::Float3& scale = joint.transform.scale;
sprintf(txt, "%.2g", scale.x);
if (_im_gui->DoSlider(txt, -1.f, 1.f, &scale.x)) {
modified = true;
scale.y = scale.z = scale.x = scale.x != 0.f ? scale.x : .01f;
}
// Recurse children
modified |= OnRawSkeletonJointGui(_im_gui, &joint.children, _oc_state);
}
}
return modified;
}
} // namespace
bool RawSkeletonEditor::OnGui(animation::offline::RawSkeleton* _skeleton,
ImGui* _im_gui) {
open_close_states.resize(_skeleton->num_joints(), false);
ozz::vector<bool>::iterator begin = open_close_states.begin();
return OnRawSkeletonJointGui(_im_gui, &_skeleton->roots, &begin);
}
// Uses LocalToModelJob to compute skeleton model space posture, then forwards
// to ComputePostureBounds
void ComputeSkeletonBounds(const animation::Skeleton& _skeleton,
math::Box* _bound) {
using ozz::math::Float4x4;
assert(_bound);
// Set a default box.
*_bound = ozz::math::Box();
const int num_joints = _skeleton.num_joints();
if (!num_joints) {
return;
}
// Allocate matrix array, out of memory is handled by the LocalToModelJob.
ozz::vector<ozz::math::Float4x4> models(num_joints);
// Compute model space bind pose.
ozz::animation::LocalToModelJob job;
job.input = _skeleton.joint_bind_poses();
job.output = make_span(models);
job.skeleton = &_skeleton;
if (job.Run()) {
// Forwards to posture function.
ComputePostureBounds(job.output, _bound);
}
}
// Loop through matrices and collect min and max bounds.
void ComputePostureBounds(ozz::span<const ozz::math::Float4x4> _matrices,
math::Box* _bound) {
assert(_bound);
// Set a default box.
*_bound = ozz::math::Box();
if (_matrices.empty()) {
return;
}
// Loops through matrices and stores min/max.
// Matrices array cannot be empty, it was checked at the beginning of the
// function.
const ozz::math::Float4x4* current = _matrices.begin();
math::SimdFloat4 min = current->cols[3];
math::SimdFloat4 max = current->cols[3];
++current;
while (current < _matrices.end()) {
min = math::Min(min, current->cols[3]);
max = math::Max(max, current->cols[3]);
++current;
}
// Stores in math::Box structure.
math::Store3PtrU(min, &_bound->min.x);
math::Store3PtrU(max, &_bound->max.x);
return;
}
void MultiplySoATransformQuaternion(
int _index, const ozz::math::SimdQuaternion& _quat,
const ozz::span<ozz::math::SoaTransform>& _transforms) {
assert(_index >= 0 && static_cast<size_t>(_index) < _transforms.size() * 4 &&
"joint index out of bound.");
// Convert soa to aos in order to perform quaternion multiplication, and gets
// back to soa.
ozz::math::SoaTransform& soa_transform_ref = _transforms[_index / 4];
ozz::math::SimdQuaternion aos_quats[4];
ozz::math::Transpose4x4(&soa_transform_ref.rotation.x, &aos_quats->xyzw);
ozz::math::SimdQuaternion& aos_quat_ref = aos_quats[_index & 3];
aos_quat_ref = aos_quat_ref * _quat;
ozz::math::Transpose4x4(&aos_quats->xyzw, &soa_transform_ref.rotation.x);
}
bool LoadSkeleton(const char* _filename, ozz::animation::Skeleton* _skeleton) {
assert(_filename && _skeleton);
ozz::log::Out() << "Loading skeleton archive " << _filename << "."
<< std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open skeleton file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::animation::Skeleton>()) {
ozz::log::Err() << "Failed to load skeleton instance from file "
<< _filename << "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *_skeleton;
return true;
}
bool LoadAnimation(const char* _filename,
ozz::animation::Animation* _animation) {
assert(_filename && _animation);
ozz::log::Out() << "Loading animation archive: " << _filename << "."
<< std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open animation file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::animation::Animation>()) {
ozz::log::Err() << "Failed to load animation instance from file "
<< _filename << "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *_animation;
return true;
}
namespace {
template <typename _Track>
bool LoadTrackImpl(const char* _filename, _Track* _track) {
assert(_filename && _track);
ozz::log::Out() << "Loading track archive: " << _filename << "." << std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open track file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<_Track>()) {
ozz::log::Err() << "Failed to load float track instance from file "
<< _filename << "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *_track;
return true;
}
} // namespace
bool LoadTrack(const char* _filename, ozz::animation::FloatTrack* _track) {
return LoadTrackImpl(_filename, _track);
}
bool LoadTrack(const char* _filename, ozz::animation::Float2Track* _track) {
return LoadTrackImpl(_filename, _track);
}
bool LoadTrack(const char* _filename, ozz::animation::Float3Track* _track) {
return LoadTrackImpl(_filename, _track);
}
bool LoadTrack(const char* _filename, ozz::animation::Float4Track* _track) {
return LoadTrackImpl(_filename, _track);
}
bool LoadTrack(const char* _filename, ozz::animation::QuaternionTrack* _track) {
return LoadTrackImpl(_filename, _track);
}
bool LoadMesh(const char* _filename, ozz::sample::Mesh* _mesh) {
assert(_filename && _mesh);
ozz::log::Out() << "Loading mesh archive: " << _filename << "." << std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open mesh file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::sample::Mesh>()) {
ozz::log::Err() << "Failed to load mesh instance from file " << _filename
<< "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *_mesh;
return true;
}
bool LoadMeshes(const char* _filename,
ozz::vector<ozz::sample::Mesh>* _meshes) {
assert(_filename && _meshes);
ozz::log::Out() << "Loading meshes archive: " << _filename << "."
<< std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open mesh file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
while (archive.TestTag<ozz::sample::Mesh>()) {
_meshes->resize(_meshes->size() + 1);
archive >> _meshes->back();
}
return true;
}
namespace {
// MollerTrumbore intersection algorithm
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
bool RayIntersectsTriangle(const ozz::math::Float3& _ray_origin,
const ozz::math::Float3& _ray_direction,
const ozz::math::Float3& _p0,
const ozz::math::Float3& _p1,
const ozz::math::Float3& _p2,
ozz::math::Float3* _intersect,
ozz::math::Float3* _normal) {
const float kEpsilon = 0.0000001f;
const ozz::math::Float3 edge1 = _p1 - _p0;
const ozz::math::Float3 edge2 = _p2 - _p0;
const ozz::math::Float3 h = Cross(_ray_direction, edge2);
const float a = Dot(edge1, h);
if (a > -kEpsilon && a < kEpsilon) {
return false; // This ray is parallel to this triangle.
}
const float inv_a = 1.f / a;
const ozz::math::Float3 s = _ray_origin - _p0;
const float u = Dot(s, h) * inv_a;
if (u < 0.f || u > 1.f) {
return false;
}
const ozz::math::Float3 q = Cross(s, edge1);
const float v = ozz::math::Dot(_ray_direction, q) * inv_a;
if (v < 0.f || u + v > 1.f) {
return false;
}
// At this stage we can compute t to find out where the intersection point is
// on the line.
const float t = Dot(edge2, q) * inv_a;
if (t > kEpsilon) { // Ray intersection
*_intersect = _ray_origin + _ray_direction * t;
*_normal = Normalize(Cross(edge1, edge2));
return true;
} else { // This means that there is a line intersection but not a ray
// intersection.
return false;
}
}
} // namespace
bool RayIntersectsMesh(const ozz::math::Float3& _ray_origin,
const ozz::math::Float3& _ray_direction,
const ozz::sample::Mesh& _mesh,
ozz::math::Float3* _intersect,
ozz::math::Float3* _normal) {
assert(_mesh.parts.size() == 1 && !_mesh.skinned());
bool intersected = false;
ozz::math::Float3 intersect, normal;
const float* vertices = array_begin(_mesh.parts[0].positions);
const uint16_t* indices = array_begin(_mesh.triangle_indices);
for (int i = 0; i < _mesh.triangle_index_count(); i += 3) {
const float* pf0 = vertices + indices[i + 0] * 3;
const float* pf1 = vertices + indices[i + 1] * 3;
const float* pf2 = vertices + indices[i + 2] * 3;
ozz::math::Float3 lcl_intersect, lcl_normal;
if (RayIntersectsTriangle(_ray_origin, _ray_direction,
ozz::math::Float3(pf0[0], pf0[1], pf0[2]),
ozz::math::Float3(pf1[0], pf1[1], pf1[2]),
ozz::math::Float3(pf2[0], pf2[1], pf2[2]),
&lcl_intersect, &lcl_normal)) {
// Is it closer to start point than the previous intersection.
if (!intersected || LengthSqr(lcl_intersect - _ray_origin) <
LengthSqr(intersect - _ray_origin)) {
intersect = lcl_intersect;
normal = lcl_normal;
}
intersected = true;
}
}
// Copy output
if (intersected) {
if (_intersect) {
*_intersect = intersect;
}
if (_normal) {
*_normal = normal;
}
}
return intersected;
}
bool RayIntersectsMeshes(const ozz::math::Float3& _ray_origin,
const ozz::math::Float3& _ray_direction,
const ozz::span<const ozz::sample::Mesh>& _meshes,
ozz::math::Float3* _intersect,
ozz::math::Float3* _normal) {
bool intersected = false;
ozz::math::Float3 intersect, normal;
for (size_t i = 0; i < _meshes.size(); ++i) {
ozz::math::Float3 lcl_intersect, lcl_normal;
if (RayIntersectsMesh(_ray_origin, _ray_direction, _meshes[i],
&lcl_intersect, &lcl_normal)) {
// Is it closer to start point than the previous intersection.
if (!intersected || LengthSqr(lcl_intersect - _ray_origin) <
LengthSqr(intersect - _ray_origin)) {
intersect = lcl_intersect;
normal = lcl_normal;
}
intersected = true;
}
}
// Copy output
if (intersected) {
if (_intersect) {
*_intersect = intersect;
}
if (_normal) {
*_normal = normal;
}
}
return intersected;
}
} // namespace sample
} // namespace ozz