Initial commit
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
# Adds the sample framework to the include path.
|
||||
include_directories(./)
|
||||
|
||||
# Adds framework.
|
||||
add_subdirectory(framework)
|
||||
|
||||
# Adds samples and helper libraries.
|
||||
add_subdirectory(additive)
|
||||
add_subdirectory(attach)
|
||||
add_subdirectory(baked)
|
||||
add_subdirectory(blend)
|
||||
add_subdirectory(foot_ik)
|
||||
add_subdirectory(look_at)
|
||||
add_subdirectory(millipede)
|
||||
add_subdirectory(multithread)
|
||||
add_subdirectory(optimize)
|
||||
add_subdirectory(partial_blend)
|
||||
add_subdirectory(playback)
|
||||
add_subdirectory(skinning)
|
||||
add_subdirectory(two_bone_ik)
|
||||
add_subdirectory(user_channel)
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
$<$<BOOL:${ozz_build_fbx}>:BUILD_DATA_SAMPLE>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_walk.ozz"
|
||||
"${ozz_media_directory}/bin/pab_crackhead_additive.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_additive.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation_base.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crackhead_additive.ozz" "./media/animation_additive.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_additive
|
||||
sample_additive.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_additive.ozz")
|
||||
target_link_libraries(sample_additive
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_additive
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_additive
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_additive.wasm
|
||||
DESTINATION bin/samples/additive)
|
||||
else()
|
||||
install(TARGETS sample_additive DESTINATION bin/samples/additive)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/additive)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/additive)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_additive COMMAND sample_additive "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_additive_path COMMAND sample_additive "--skeleton=media/skeleton.ozz" "--animation=media/animation_base.ozz" "--additive_animation=media/animation_additive.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_additive_invalid_skeleton_path COMMAND sample_additive "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_additive_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_additive_invalid_animation_path1 COMMAND sample_additive "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_additive_invalid_animation_path1 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_additive_invalid_animation_path2 COMMAND sample_additive "--additive_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_additive_invalid_animation_path2 PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,33 @@
|
||||
# Ozz-animation sample: Additive animation blending
|
||||
|
||||
## Description
|
||||
|
||||
Additive blending is a key concept in run-time animation. By superimposing a movement on top of a playing animation, it allows to add variety while lessening animation count and complexity.
|
||||
In this sample, a "cracking head" animation is added to a "walk" cycle. Note that no synchronization is required between the two animations.
|
||||
|
||||
## Concept
|
||||
|
||||
Additive blending is different to normal blending because it does not interpolate from a pose to an other. Instead it combines poses, meaning you can see the two animations at the same time. In this example the walk cycle is never altered. The cracking head and shoulder movements from the additive animation are simply (visually speaking) added.
|
||||
|
||||
Additive blending is performed by ozz::animation::BlendingJob. ozz::animation::BlendingJob exposes additive layers with the same input as normal blending layers: per joint local transforms, a global layer weight and optional per-joint weights. The main differences are that additive blending is done at the end of the normal blending pass, with a different equation.
|
||||
|
||||
The additive (or delta) animation is created by subtracting a reference pose from a source animation. ozz proposes a ozz::animation::offline::AdditiveAnimationBuilder utility to build additive animations. It uses the first frame of the animation as the reference pose. fbx2ozz supports additive configuration option to export delta animations from a source file.
|
||||
|
||||
Additionally, the sample uses an optional mask to blend the additive animation on the character upper body only. See partial blending sample for more details.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample proposes to modify main and additive layers weights:
|
||||
- Normal blending layer weight.
|
||||
- Additive blending layer weight.
|
||||
- Upper body masking activation and base joint selection.
|
||||
|
||||
Both main and additive animation playback parameters are exposed.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Loads main, additive animations, and their skeleton. See "playback" sample for more details.
|
||||
2. Samples each animation to get local-space transformations. Sampling an additive animation is not different from a standard one.
|
||||
3. Setups ozz::animation::BlendingJob object. BlendingJob object takes as input two arrays of BlendingJob::Layer, one for standard blending, the other for additive blending. Each layer is setup with its weights and the local-space transforms outputted from the sampling stage.
|
||||
4. Convert local-space transformations to model-space matrices using ozz::animation::LocalToModelJob. It takes as input the skeleton (to know about joint's hierarchy) and local-space transforms outputted from the blending pass. Output is model-space matrices array.
|
||||
5. Model-space matrices array is then used for rendering skeleton posture.
|
||||
@@ -0,0 +1,382 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 <cstring>
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/blending_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/animation/runtime/skeleton_utils.h"
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.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)
|
||||
|
||||
// MAin animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the main animation(ozz archive format).",
|
||||
"media/animation_base.ozz", false)
|
||||
|
||||
// Additive animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(
|
||||
additive_animation, "Path to the additive animation (ozz archive format).",
|
||||
"media/animation_additive.ozz", false)
|
||||
|
||||
class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
AdditiveBlendSampleApplication()
|
||||
: upper_body_root_(0),
|
||||
upper_body_mask_enable_(true),
|
||||
upper_body_joint_weight_setting_(1.f),
|
||||
threshold_(ozz::animation::BlendingJob().threshold) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates and samples both animations to their respective local space
|
||||
// transform buffers.
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
// Updates animations time.
|
||||
sampler.controller.Update(sampler.animation, _dt);
|
||||
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &sampler.animation;
|
||||
sampling_job.cache = &sampler.cache;
|
||||
sampling_job.ratio = sampler.controller.time_ratio();
|
||||
sampling_job.output = make_span(sampler.locals);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Blends animations.
|
||||
// Blends the local spaces transforms computed by sampling all animations
|
||||
// (1st stage just above), and outputs the result to the local space
|
||||
// transform buffer blended_locals_
|
||||
|
||||
// Prepares standard blending layers.
|
||||
ozz::animation::BlendingJob::Layer layers[1];
|
||||
layers[0].transform = make_span(samplers_[kMainAnimation].locals);
|
||||
layers[0].weight = samplers_[kMainAnimation].weight_setting;
|
||||
|
||||
// Prepares additive blending layers.
|
||||
ozz::animation::BlendingJob::Layer additive_layers[1];
|
||||
additive_layers[0].transform =
|
||||
make_span(samplers_[kAdditiveAnimation].locals);
|
||||
additive_layers[0].weight = samplers_[kAdditiveAnimation].weight_setting;
|
||||
|
||||
// Set per-joint weights for the additive blended layer.
|
||||
if (upper_body_mask_enable_) {
|
||||
additive_layers[0].joint_weights = make_span(upper_body_joint_weights_);
|
||||
}
|
||||
|
||||
// Setups blending job.
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = threshold_;
|
||||
blend_job.layers = layers;
|
||||
blend_job.additive_layers = additive_layers;
|
||||
blend_job.bind_pose = skeleton_.joint_bind_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
if (!blend_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gets the output of the blending stage, and converts it to model space.
|
||||
|
||||
// Setup local-to-model conversion job.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(blended_locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
|
||||
// Run ltm job.
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
return _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
|
||||
// Reading animations.
|
||||
const char* filenames[] = {OPTIONS_animation, OPTIONS_additive_animation};
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
if (!ozz::sample::LoadAnimation(filenames[i], &sampler.animation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates sampler runtime buffers.
|
||||
sampler.locals.resize(num_soa_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
sampler.cache.Resize(num_joints);
|
||||
}
|
||||
|
||||
// Default weight settings.
|
||||
samplers_[kMainAnimation].weight_setting = 1.f;
|
||||
|
||||
upper_body_joint_weight_setting_ = 1.f;
|
||||
samplers_[kAdditiveAnimation].weight_setting = 1.f;
|
||||
|
||||
// Allocates local space runtime buffers of blended data.
|
||||
blended_locals_.resize(num_soa_joints);
|
||||
|
||||
// Allocates model space runtime buffers of blended data.
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates per-joint weights used for the partial additive animation.
|
||||
// Note that this is a Soa structure.
|
||||
upper_body_joint_weights_.resize(num_soa_joints);
|
||||
|
||||
// Finds the "Spine1" joint in the joint hierarchy.
|
||||
for (int i = 0; i < num_joints; ++i) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "Spine1")) {
|
||||
upper_body_root_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetupPerJointWeights();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper functor used to set weights while traversing joints hierarchy.
|
||||
struct WeightSetupIterator {
|
||||
WeightSetupIterator(ozz::vector<ozz::math::SimdFloat4>* _weights,
|
||||
float _weight_setting)
|
||||
: weights(_weights), weight_setting(_weight_setting) {}
|
||||
void operator()(int _joint, int) {
|
||||
ozz::math::SimdFloat4& soa_weight = weights->at(_joint / 4);
|
||||
soa_weight = ozz::math::SetI(
|
||||
soa_weight, ozz::math::simd_float4::Load1(weight_setting),
|
||||
_joint % 4);
|
||||
}
|
||||
ozz::vector<ozz::math::SimdFloat4>* weights;
|
||||
float weight_setting;
|
||||
};
|
||||
|
||||
void SetupPerJointWeights() {
|
||||
// Setup partial animation mask. This mask is defined by a weight_setting
|
||||
// assigned to each joint of the hierarchy. Joint to disable are set to a
|
||||
// weight_setting of 0.f, and enabled joints are set to 1.f.
|
||||
|
||||
// Disables all joints: set all weights to 0.
|
||||
for (int i = 0; i < skeleton_.num_soa_joints(); ++i) {
|
||||
upper_body_joint_weights_[i] = ozz::math::simd_float4::zero();
|
||||
}
|
||||
|
||||
// Extracts the list of children of the shoulder
|
||||
WeightSetupIterator it(&upper_body_joint_weights_,
|
||||
upper_body_joint_weight_setting_);
|
||||
IterateJointsDF(skeleton_, it, upper_body_root_);
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char label[64];
|
||||
|
||||
// Exposes blending parameters.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
|
||||
if (open) {
|
||||
_im_gui->DoLabel("Main layer:");
|
||||
std::sprintf(label, "Layer weight: %.2f",
|
||||
samplers_[kMainAnimation].weight_setting);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f,
|
||||
&samplers_[kMainAnimation].weight_setting, 1.f);
|
||||
_im_gui->DoLabel("Additive layer:");
|
||||
std::sprintf(label, "Layer weight: %.2f",
|
||||
samplers_[kAdditiveAnimation].weight_setting);
|
||||
_im_gui->DoSlider(label, -1.f, 1.f,
|
||||
&samplers_[kAdditiveAnimation].weight_setting, 1.f);
|
||||
_im_gui->DoLabel("Global settings:");
|
||||
std::sprintf(label, "Threshold: %.2f", threshold_);
|
||||
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
|
||||
}
|
||||
}
|
||||
// Exposes selection of the root of the partial blending hierarchy.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Upper body masking", &open);
|
||||
|
||||
if (open) {
|
||||
bool rebuild_joint_weights = false;
|
||||
rebuild_joint_weights |=
|
||||
_im_gui->DoCheckBox("Enable mask", &upper_body_mask_enable_);
|
||||
|
||||
std::sprintf(label, "Joints weight: %.2f",
|
||||
upper_body_joint_weight_setting_);
|
||||
rebuild_joint_weights |= _im_gui->DoSlider(
|
||||
label, 0.f, 1.f, &upper_body_joint_weight_setting_, 1.f,
|
||||
upper_body_mask_enable_);
|
||||
|
||||
if (skeleton_.num_joints() != 0) {
|
||||
_im_gui->DoLabel("Root of the upper body hierarchy:",
|
||||
ozz::sample::ImGui::kLeft, false);
|
||||
std::sprintf(label, "%s (%d)",
|
||||
skeleton_.joint_names()[upper_body_root_],
|
||||
upper_body_root_);
|
||||
|
||||
rebuild_joint_weights |= _im_gui->DoSlider(
|
||||
label, 0, skeleton_.num_joints() - 1, &upper_body_root_, 1.f,
|
||||
upper_body_mask_enable_);
|
||||
}
|
||||
|
||||
// Rebuilds per-joint weights if something has changed.
|
||||
if (rebuild_joint_weights) {
|
||||
SetupPerJointWeights();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exposes animations runtime playback controls.
|
||||
{
|
||||
static bool oc_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &oc_open);
|
||||
if (oc_open) {
|
||||
static bool open[kNumLayers] = {true, true};
|
||||
const char* oc_names[kNumLayers] = {"Main animation",
|
||||
"Additive animation"};
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
ozz::sample::ImGui::OpenClose loc(_im_gui, oc_names[i], nullptr);
|
||||
if (open[i]) {
|
||||
sampler.controller.OnGui(sampler.animation, _im_gui);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// The number of layers to blend.
|
||||
enum {
|
||||
kMainAnimation = 0,
|
||||
kAdditiveAnimation = 1,
|
||||
kNumLayers = 2,
|
||||
};
|
||||
|
||||
// Sampler structure contains all the data required to sample a single
|
||||
// animation.
|
||||
struct Sampler {
|
||||
// Constructor, default initialization.
|
||||
Sampler() : weight_setting(1.f) {}
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
|
||||
// Blending weight_setting for the layer.
|
||||
float weight_setting;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
|
||||
} samplers_[kNumLayers]; // kNumLayers animations to blend.
|
||||
|
||||
// Index of the joint at the base of the upper body hierarchy.
|
||||
int upper_body_root_;
|
||||
|
||||
// Enables upper boddy per-joint weights.
|
||||
bool upper_body_mask_enable_;
|
||||
|
||||
// Blending weight_setting setting of the joints of this layer that are
|
||||
// affected
|
||||
// by the masking.
|
||||
float upper_body_joint_weight_setting_;
|
||||
|
||||
// Per-joint weights used to define the partial animation mask. Allows to
|
||||
// select which joints are considered during blending, and their individual
|
||||
// weight_setting.
|
||||
ozz::vector<ozz::math::SimdFloat4> upper_body_joint_weights_;
|
||||
|
||||
// Blending job bind pose threshold.
|
||||
float threshold_;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> blended_locals_;
|
||||
|
||||
// Buffer of model space matrices. These are computed by the local-to-model
|
||||
// job after the blending stage.
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Additive animations blending";
|
||||
return AdditiveBlendSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_walk.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_attach
|
||||
sample_attach.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
|
||||
target_link_libraries(sample_attach
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_attach
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_attach
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_attach.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_attach.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_attach.wasm
|
||||
DESTINATION bin/samples/attach)
|
||||
else()
|
||||
install(TARGETS sample_attach DESTINATION bin/samples/attach)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/attach)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/attach)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_attach COMMAND sample_attach "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_attach_load_path COMMAND sample_attach "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_attach_load_invalid_skeleton_path COMMAND sample_attach "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_attach_load_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_attach_load_invalid_animation_path COMMAND sample_attach "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_attach_load_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Ozz-animation sample: Attachment to animated skeleton joints
|
||||
|
||||
## Description
|
||||
|
||||
Demonstrates how to attach an object to an animated skeleton's joint. This feature allows for example to render a sword in the hand of a character. This could also be used to attach an animated skeleton to a joint of an other animated object.
|
||||
|
||||
## Concept
|
||||
|
||||
The idea is to get the joint matrix from the model-space matrices array (for the selected "hand" joint), and use this matrix to build transformation matrix of the attached object (the "sword").
|
||||
Model-space matrices are outputted by LocalToModelJob. An offset (from the joint) can be applied to the joint transformation by concatenating an offset matrix to joint matrix.
|
||||
|
||||
## Sample usage
|
||||
|
||||
Some parameters can be tuned from sample UI to move the attached object from a join to an other, or change the offset from that joint:
|
||||
- Select the joint where the object must be attached: "Select joint" slider.
|
||||
- Offset of the attached object from the joint: "Attachment offset" x/y/z sliders.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animation and skeleton, sample animation to get local-space transformations, optionally blend with other animations, and finally convert local-space transformations to model-space matrices. See Playback sample for more details about these steps.
|
||||
2. Concatenate object offset transformation (offset relative to the joint) with joint model-space matrix to compute final model-space transformation of the "attached object".
|
||||
3. Use resulting transformation matrix to render "attached object" or update the scene graph.
|
||||
@@ -0,0 +1,221 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_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/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
class AttachSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
AttachSampleApplication() : attachment_(0), offset_(-.02f, .03f, .05f) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
if (!_renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepares attached object transformation.
|
||||
// Gets model space transformation of the joint.
|
||||
const ozz::math::Float4x4& joint = models_[attachment_];
|
||||
|
||||
// Builds offset transformation matrix.
|
||||
const ozz::math::SimdFloat4 translation =
|
||||
ozz::math::simd_float4::Load3PtrU(&offset_.x);
|
||||
|
||||
// Concatenates joint and offset transformations.
|
||||
const ozz::math::Float4x4 transform =
|
||||
joint * ozz::math::Float4x4::Translation(translation);
|
||||
|
||||
// Prepare rendering.
|
||||
const float thickness = .01f;
|
||||
const float length = .5f;
|
||||
const ozz::math::Box box(ozz::math::Float3(-thickness, -thickness, -length),
|
||||
ozz::math::Float3(thickness, thickness, 0.f));
|
||||
const ozz::sample::Color colors[2] = {ozz::sample::kRed,
|
||||
ozz::sample::kGreen};
|
||||
|
||||
return _renderer->DrawBoxIm(box, transform, colors);
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Finds the joint where the object should be attached.
|
||||
for (int i = 0; i < num_joints; i++) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "LeftHandMiddle")) {
|
||||
attachment_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes selection of the attachment joint.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Attachment joint", &open);
|
||||
if (open && skeleton_.num_joints() != 0) {
|
||||
_im_gui->DoLabel("Select joint:");
|
||||
char label[64];
|
||||
std::sprintf(label, "%s (%d)", skeleton_.joint_names()[attachment_],
|
||||
attachment_);
|
||||
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &attachment_);
|
||||
|
||||
_im_gui->DoLabel("Attachment offset:");
|
||||
sprintf(label, "x: %02f", offset_.x);
|
||||
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.x);
|
||||
sprintf(label, "y: %02f", offset_.y);
|
||||
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.y);
|
||||
sprintf(label, "z: %02f", offset_.z);
|
||||
_im_gui->DoSlider(label, -1.f, 1.f, &offset_.z);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Joint where the object is attached.
|
||||
int attachment_;
|
||||
|
||||
// Offset, translation of the attached object from the joint.
|
||||
ozz::math::Float3 offset_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title =
|
||||
"Ozz-animation sample: Attachment to animated skeleton joints";
|
||||
return AttachSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Generates sample data
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/baked_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/baked_animation.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/baked_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/baked_animation.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_baked
|
||||
sample_baked.cc
|
||||
"${CMAKE_CURRENT_LIST_DIR}/config.json"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
|
||||
target_link_libraries(sample_baked
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_baked
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_baked
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_baked.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_baked.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_baked.wasm
|
||||
DESTINATION bin/samples/baked)
|
||||
else()
|
||||
install(TARGETS sample_baked DESTINATION bin/samples/baked)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/baked)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/baked)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_baked COMMAND sample_baked "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_baked_load_path COMMAND sample_baked "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_baked_load_invalid_skeleton_path COMMAND sample_baked "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_baked_load_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_baked_load_invalid_animation_path COMMAND sample_baked "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_baked_load_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Ozz-animation sample: Baked physic simulation.
|
||||
|
||||
## Description
|
||||
|
||||
This samples shows a physic simulation baked into an animation. This scene contains more than 1000 cuboids. Baking complex scenes offline into animations in a common technique to render cpu intensive simulations.
|
||||
|
||||
## Concept
|
||||
|
||||
This sample has two fundamental parts:
|
||||
1. Extract a skeleton from the baked scene using fxb2ozz. There's no hierarchy between objects, but it's still required to define a skeleton to be able to animate it. The baked scene is made of animated meshes/cubes, which are considered as joints while importing the skeleton. See [config.json](config.json) for more details of this setting.
|
||||
2. Animation extraction is using fbx2ozz as usual.
|
||||
3. Render animated meshes. The original scene is made of cuboids of different sizes. The sample doesn't import scene meshes, but renders unit size cubes instead. Each cube is scaled by its joint animation scale track.
|
||||
|
||||
This sample also introduces camera animation. The camera is considered as a joint in the scene skeleton. Camera animation track is extracted in the same way meshes animations are. The sample code then forwards animated camera matrix to the renderer each frame.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample exposes animation time and playback speed.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animation and skeleton, sample animation to get local-space transformations, and convert local-space transformations to model-space matrices. See Playback sample for more details about these steps.
|
||||
2. Render a unit size cube for each joint, transformed by each joint matrix which contains the scale to restore original mesh size. The sample uses instanced rendering to improve performances.
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"skeleton":
|
||||
{
|
||||
"filename":"baked_skeleton.ozz",
|
||||
"import":
|
||||
{
|
||||
"enable":true, // Skeleton needs to be imported/created from the source file.
|
||||
"types":
|
||||
{
|
||||
"geometry":true // Geometry nodes will be considered as joints for the skeleton we're creating.
|
||||
}
|
||||
}
|
||||
},
|
||||
"animations":
|
||||
[
|
||||
{
|
||||
"filename":"baked_animation.ozz",
|
||||
"optimization_settings": // Camera is very sensitive to optimizations.
|
||||
{
|
||||
"tolerance" : 0.002, // Global setting, for all boxes.
|
||||
"distance" : 0.707, // Every box is 1m wide, so a corner is at .707m from center.
|
||||
"override" :
|
||||
[
|
||||
{
|
||||
"name" : "camera", // Specific setting for the camera.
|
||||
"tolerance" : 0.001,
|
||||
"distance" : 20. // 2m, x10 as camera is scaled to .1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_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/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
class BakedSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
BakedSampleApplication() : camera_index_(-1) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Look for a "camera" joint.
|
||||
for (int i = 0; i < num_joints; i++) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "camera")) {
|
||||
camera_index_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
// Render a 1m size boxes for every joint. The scale of each box come from
|
||||
// the animation.
|
||||
const float size = .5f;
|
||||
const ozz::math::Box box(ozz::math::Float3(-size), ozz::math::Float3(size));
|
||||
return _renderer->DrawBoxShaded(box, make_span(models_),
|
||||
ozz::sample::kWhite);
|
||||
}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool GetCameraOverride(ozz::math::Float4x4* _transform) const {
|
||||
// Early out if no camera joint was found.
|
||||
if (camera_index_ == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*_transform = models_[camera_index_];
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Camera joint index. -1 if not found.
|
||||
int camera_index_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Baked rigid bodies";
|
||||
return BakedSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
# Generates sample data
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_walk.ozz"
|
||||
"${ozz_media_directory}/bin/pab_jog.ozz"
|
||||
"${ozz_media_directory}/bin/pab_run.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation1.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation2.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation3.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation1.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_jog.ozz" "./media/animation2.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_run.ozz" "./media/animation3.ozz"
|
||||
VERBATIM)
|
||||
|
||||
# Adds sample executable
|
||||
add_executable(sample_blend
|
||||
sample_blend.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation1.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation2.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation3.ozz")
|
||||
|
||||
target_link_libraries(sample_blend
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_blend
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_blend
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_blend.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_blend.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_blend.wasm
|
||||
DESTINATION bin/samples/blend)
|
||||
else()
|
||||
install(TARGETS sample_blend DESTINATION bin/samples/blend)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/blend)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/blend)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_blend COMMAND sample_blend "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_blend_path COMMAND sample_blend "--skeleton=media/skeleton.ozz" "--animation1=media/animation1.ozz" "--animation2=media/animation2.ozz" "--animation3=media/animation3.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_blend_invalid_skeleton_path COMMAND sample_blend "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_blend_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_blend_invalid_animation_path1 COMMAND sample_blend "--animation1=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_blend_invalid_animation_path1 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_blend_invalid_animation_path2 COMMAND sample_blend "--animation2=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_blend_invalid_animation_path2 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_blend_invalid_animation_path3 COMMAND sample_blend "--animation3=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_blend_invalid_animation_path3 PROPERTIES WILL_FAIL true)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Ozz-animation sample: Animation blending
|
||||
|
||||
## Description
|
||||
|
||||
Blending is the key concept in run-time animation:
|
||||
1. To create smooth transitions between animations. In this case blending is used to go from an "idle" animation to a "walk" animation for example. Blending coefficient will go from 0 (idle) to 1 (walk) from the beginning to the end of the transition.
|
||||
2. To compose an animation from multiple animations, like a "fast walk" animation that would result from a "walk" blent with a "run" animation. In this case the two animations must be play-backed in sync (adapt playback speed to equalize animations' duration).
|
||||
This sample demonstrates the composition blending case (b). It blends three animations (walk, jog and run) according to a single "speed" coefficient: The closer the speed is from 0, the more the result is close to the walk animation. The closer the speed is from 1, the more the result is close to the run animation.
|
||||
|
||||
## Concept
|
||||
|
||||
Runtime blending is performed using a ozz::animation::BlendingJob to process the blending of multiple animation layers. Each layer is assigned an animation and a weight that sets its influence in the final blended result.
|
||||
The idea is to compute the blending weight of each animation layer (walk, jog and run) according to the single "speed" parameter. This can done with a simple proportional calculation. Those blending coefficients are provided as input to the ozz::animation::BlendingJob, along with local-space transformations for the 3 layers. Output will also be in local-space.
|
||||
Note that the 3 animations duration also need to be synchronized such that foot steps remain synced. This is done by adapting each animation's playback speed according to their respecting weight.
|
||||
|
||||
## Sample usage
|
||||
|
||||
There are two ways to use the sample:
|
||||
1. The first one is to use the single blending parameter provided by the "Blend ratio" slider. This parameter automatically controls all the other parameters to perfectly match/synchronize all animation layers: playback speeds, blending weights...
|
||||
2. Setting "manual setting" option to "on" will allow to manually tweak all the sampling and blending parameters from the GUI, bypassing automatic weighting and synchronization.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animations and skeleton. See "playback" sample for more details.
|
||||
2. Compute each animation time (in order to sync their duration) and samples each of them to get local-space transformations.
|
||||
3. Compute each animation (layer) blend weight and fills ozz::animation::BlendingJob object. BlendingJob object takes as input an array of BlendingJob::Layer representing each layer to blend: weight and local-space transformations (as outputed from the sampling stage). It also takes as input the skeleton bind-pose, which represents the default transformation of each joint. It is used by the blending algorithm as a fall-back when the accumulated layer weight is too small (under a threshold value which is also an input) to be used. The output of the blending job is a set of local-space transformations.
|
||||
4. Convert local-space transformations to model-space matrices using ozz::animation::LocalToModelJob. It takes as input the skeleton (to know about joint's hierarchy) and local-space transforms. Output is model-space matrices array.
|
||||
5. Model-space matrices array can then be used for rendering (to skin a mesh) or updating the scene graph.
|
||||
@@ -0,0 +1,343 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/blending_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.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)
|
||||
|
||||
// First animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation1,
|
||||
"Path to the first animation (ozz archive format).",
|
||||
"media/animation1.ozz", false)
|
||||
|
||||
// Second animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation2,
|
||||
"Path to the second animation (ozz archive format).",
|
||||
"media/animation2.ozz", false)
|
||||
|
||||
// Third animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation3,
|
||||
"Path to the second animation (ozz archive format).",
|
||||
"media/animation3.ozz", false)
|
||||
|
||||
class BlendSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
BlendSampleApplication()
|
||||
: blend_ratio_(.3f),
|
||||
manual_(false),
|
||||
threshold_(ozz::animation::BlendingJob().threshold) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates blending parameters and synchronizes animations if control mode
|
||||
// is not manual.
|
||||
if (!manual_) {
|
||||
UpdateRuntimeParameters();
|
||||
}
|
||||
|
||||
// Updates and samples all animations to their respective local space
|
||||
// transform buffers.
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
// Updates animations time.
|
||||
sampler.controller.Update(sampler.animation, _dt);
|
||||
|
||||
// Early out if this sampler weight makes it irrelevant during blending.
|
||||
if (samplers_[i].weight <= 0.f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &sampler.animation;
|
||||
sampling_job.cache = &sampler.cache;
|
||||
sampling_job.ratio = sampler.controller.time_ratio();
|
||||
sampling_job.output = make_span(sampler.locals);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Blends animations.
|
||||
// Blends the local spaces transforms computed by sampling all animations
|
||||
// (1st stage just above), and outputs the result to the local space
|
||||
// transform buffer blended_locals_
|
||||
|
||||
// Prepares blending layers.
|
||||
ozz::animation::BlendingJob::Layer layers[kNumLayers];
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
layers[i].transform = make_span(samplers_[i].locals);
|
||||
layers[i].weight = samplers_[i].weight;
|
||||
}
|
||||
|
||||
// Setups blending job.
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = threshold_;
|
||||
blend_job.layers = layers;
|
||||
blend_job.bind_pose = skeleton_.joint_bind_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
if (!blend_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
// Gets the output of the blending stage, and converts it to model space.
|
||||
|
||||
// Setup local-to-model conversion job.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(blended_locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
|
||||
// Runs ltm job.
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Computes blending weight and synchronizes playback speed when the "manual"
|
||||
// option is off.
|
||||
void UpdateRuntimeParameters() {
|
||||
// Computes weight parameters for all samplers.
|
||||
const float kNumIntervals = kNumLayers - 1;
|
||||
const float kInterval = 1.f / kNumIntervals;
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
const float med = i * kInterval;
|
||||
const float x = blend_ratio_ - med;
|
||||
const float y = ((x < 0.f ? x : -x) + kInterval) * kNumIntervals;
|
||||
samplers_[i].weight = ozz::math::Max(0.f, y);
|
||||
}
|
||||
|
||||
// Synchronizes animations.
|
||||
// First computes loop cycle duration. Selects the 2 samplers that define
|
||||
// interval that contains blend_ratio_.
|
||||
// Uses a maximum value smaller that 1.f (-epsilon) to ensure that
|
||||
// (relevant_sampler + 1) is always valid.
|
||||
const int relevant_sampler =
|
||||
static_cast<int>((blend_ratio_ - 1e-3f) * (kNumLayers - 1));
|
||||
assert(relevant_sampler + 1 < kNumLayers);
|
||||
Sampler& sampler_l = samplers_[relevant_sampler];
|
||||
Sampler& sampler_r = samplers_[relevant_sampler + 1];
|
||||
|
||||
// Interpolates animation durations using their respective weights, to
|
||||
// find the loop cycle duration that matches blend_ratio_.
|
||||
const float loop_duration =
|
||||
sampler_l.animation.duration() * sampler_l.weight +
|
||||
sampler_r.animation.duration() * sampler_r.weight;
|
||||
|
||||
// Finally finds the speed coefficient for all samplers.
|
||||
const float inv_loop_duration = 1.f / loop_duration;
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
const float speed = sampler.animation.duration() * inv_loop_duration;
|
||||
sampler.controller.set_playback_speed(speed);
|
||||
}
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
return _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
|
||||
// Reading animations.
|
||||
const char* filenames[] = {OPTIONS_animation1, OPTIONS_animation2,
|
||||
OPTIONS_animation3};
|
||||
static_assert(OZZ_ARRAY_SIZE(filenames) == kNumLayers, "Arrays mistmatch.");
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
if (!ozz::sample::LoadAnimation(filenames[i], &sampler.animation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates sampler runtime buffers.
|
||||
sampler.locals.resize(num_soa_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
sampler.cache.Resize(num_joints);
|
||||
}
|
||||
|
||||
// Allocates local space runtime buffers of blended data.
|
||||
blended_locals_.resize(num_soa_joints);
|
||||
|
||||
// Allocates model space runtime buffers of blended data.
|
||||
models_.resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes blending parameters.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
|
||||
if (open) {
|
||||
if (_im_gui->DoCheckBox("Manual settings", &manual_) && !manual_) {
|
||||
// Check-box state was changed, reset parameters.
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
sampler.controller.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
char label[64];
|
||||
std::sprintf(label, "Blend ratio: %.2f", blend_ratio_);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &blend_ratio_, 1.f, !manual_);
|
||||
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
std::sprintf(label, "Weight %d: %.2f", i, sampler.weight);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &sampler.weight, 1.f, manual_);
|
||||
}
|
||||
|
||||
std::sprintf(label, "Threshold: %.2f", threshold_);
|
||||
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
|
||||
}
|
||||
}
|
||||
// Exposes animations runtime playback controls.
|
||||
{
|
||||
static bool oc_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &oc_open);
|
||||
if (oc_open) {
|
||||
static bool open[] = {true, true, true};
|
||||
static_assert(OZZ_ARRAY_SIZE(open) == kNumLayers,
|
||||
"Arrays size mismatch");
|
||||
const char* oc_names[] = {"Animation 1", "Animation 2", "Animation 3"};
|
||||
static_assert(OZZ_ARRAY_SIZE(oc_names) == kNumLayers,
|
||||
"Arrays size mismatch");
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
ozz::sample::ImGui::OpenClose loc(_im_gui, oc_names[i], nullptr);
|
||||
if (open[i]) {
|
||||
sampler.controller.OnGui(sampler.animation, _im_gui, manual_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Global blend ratio in range [0,1] that controls all blend parameters and
|
||||
// synchronizes playback speeds. A value of 0 gives full weight to the first
|
||||
// animation, and 1 to the last.
|
||||
float blend_ratio_;
|
||||
|
||||
// Switch to manual control of animations and blending parameters.
|
||||
bool manual_;
|
||||
|
||||
// The number of layers to blend.
|
||||
enum {
|
||||
kNumLayers = 3,
|
||||
};
|
||||
|
||||
// Sampler structure contains all the data required to sample a single
|
||||
// animation.
|
||||
struct Sampler {
|
||||
// Constructor, default initialization.
|
||||
Sampler() : weight(1.f) {}
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
|
||||
// Blending weight for the layer.
|
||||
float weight;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
} samplers_[kNumLayers]; // kNumLayers animations to blend.
|
||||
|
||||
// Blending job bind pose threshold.
|
||||
float threshold_;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> blended_locals_;
|
||||
|
||||
// Buffer of model space matrices. These are computed by the local-to-model
|
||||
// job after the blending stage.
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Animation blending";
|
||||
return BlendSampleApplication().Run(_argc, _argv, "1.2", title);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
$<$<BOOL:${ozz_build_fbx}>:BUILD_DATA_SAMPLE>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_crossarms.ozz"
|
||||
"${ozz_media_directory}/bin/arnaud_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/floor.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/floor.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crossarms.ozz" "./media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/arnaud_mesh.ozz" "./media/mesh.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/floor.ozz" "./media/floor.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_foot_ik
|
||||
sample_foot_ik.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/floor.ozz")
|
||||
target_link_libraries(sample_foot_ik
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_foot_ik
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_foot_ik
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_foot_ik.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_foot_ik.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_foot_ik.wasm
|
||||
DESTINATION bin/samples/foot_ik)
|
||||
else()
|
||||
install(TARGETS sample_foot_ik DESTINATION bin/samples/foot_ik)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/foot_ik)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/foot_ik)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_foot_ik COMMAND sample_foot_ik "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_foot_ik_path1 COMMAND sample_foot_ik "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--mesh=media/mesh.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_foot_ik_invalid_skeleton_path COMMAND sample_foot_ik "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_foot_ik_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_foot_ik_invalid_animation_path COMMAND sample_foot_ik "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_foot_ik_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_foot_ik_invalid_mesh_path COMMAND sample_foot_ik "--mesh=media/bad_mesh.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_foot_ik_invalid_mesh_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,39 @@
|
||||
# Ozz-animation sample: Foot IK
|
||||
|
||||
## Description
|
||||
|
||||
Foot IK, or foot planting, is a technique used to correct character legs and ankles procedurally at runtime, as well as character/pelvis height, so that the feet can touch and adapt to the ground.
|
||||
|
||||
## Concept
|
||||
|
||||
The core concept is to apply two bone IK to the hip/knee/ankle joint chains of the character so each foot is at the correct height from the ground, and aim IK to the ankle so that it's aligned to the ground. Raycast (from ankle position, directed down to the ground) is used to find the point and normal where each foot should be on ground. Foot height (heel, sole) and slope angle must be considered when computing ankle position, to be sure the orientated foot remains precisely on ground.
|
||||
The second important aspect is to adjust character height, so that the lowest foot touches the ground. The over leg(s) will be bent by IK.
|
||||
|
||||
This sample focuses on IK application and core maths. It's not a complete foot planting implementation, as it would require more:
|
||||
- Activate/deactivate foot planting, blending in and out when feet are not supposed to be on ground, like during walk cycle.
|
||||
- Detect not reachable foot positions: too high, too low, too steep... and deactivate IK.
|
||||
- A layer of animation should also be added, to cope with major leg elevation differences. This would allow IK to correct more natural positions.
|
||||
- ...
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample allows to move and rotate the character on the collision mesh, with y value being automatically computed if "auto character height" option is enabled.
|
||||
Pelvis correction, two bone IK and aim IK can be activated independently.
|
||||
Debug settings are exposed to help understanding: showing joints, raycasts, IK targets...
|
||||
|
||||
## Implementation
|
||||
|
||||
1. At initialization time:
|
||||
1. Loads skeleton, animation and mesh. See "playback" sample for more details.
|
||||
2. Locates hip, knee and ankle joints for each leg, searching them by name.
|
||||
2. At run time:
|
||||
1. Updates base animation and skeleton joints model-space matrices. See "playback" sample for more details.
|
||||
2. Estimates character height on the floor, evaluted at its root position.
|
||||
3. For each leg, raycasts a vector going down from the ankle position. This allows to find the intersection point (I) with the floor.
|
||||
4. Comptutes ankle target position (C), so that the foot is in contact with the floor. Because of floor slope (defined by raycast intersection normal), ankle position cannot be simply be offseted by foot offset. See diagram below.
|
||||
|
||||

|
||||
|
||||
6. Offsets the character down, so that the lowest ankle (lowest from its original position) reaches its targetted position. The other leg(s) will be ik-ed.
|
||||
7. Applies two bone IK to each leg, so the ankles reache their targetted position. This in computed in character model-space.
|
||||
8. Applies aim IK to each ankle, so the footis correctly aligned to the floor. Uses floor normal as a reference to align ankle up vector. The forward direction of the foot is then driven by the pole vector, which polls the foot (ankle forward vector) toward it's original (animated) direction.
|
||||
@@ -0,0 +1,802 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/ik_aim_job.h"
|
||||
#include "ozz/animation/runtime/ik_two_bone_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/maths/math_ex.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/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/mesh.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
"Path to the skeleton (ozz archive format).",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Mesh archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(mesh,
|
||||
"Path to the skinned mesh (ozz archive format).",
|
||||
"media/mesh.ozz", false)
|
||||
|
||||
// Mesh archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(floor,
|
||||
"Path to the floor mesh (ozz archive format).",
|
||||
"media/floor.ozz", false)
|
||||
|
||||
const char* kLeftJointNames[] = {"LeftUpLeg", "LeftLeg", "LeftFoot"};
|
||||
const char* kRightJointNames[] = {"RightUpLeg", "RightLeg", "RightFoot"};
|
||||
|
||||
const ozz::math::SimdFloat4 kKneeAxis = ozz::math::simd_float4::z_axis();
|
||||
|
||||
const ozz::math::SimdFloat4 kAnkleForward = -ozz::math::simd_float4::x_axis();
|
||||
const ozz::math::SimdFloat4 kAnkleUp = ozz::math::simd_float4::y_axis();
|
||||
|
||||
// Structure used to store each leg setup data.
|
||||
struct LegSetup {
|
||||
int hip;
|
||||
int knee;
|
||||
int ankle;
|
||||
};
|
||||
|
||||
struct LegRayInfo {
|
||||
ozz::math::Float3 start;
|
||||
ozz::math::Float3 dir;
|
||||
|
||||
bool hit;
|
||||
ozz::math::Float3 hit_point;
|
||||
ozz::math::Float3 hit_normal;
|
||||
};
|
||||
|
||||
// Constants
|
||||
static const ozz::math::Float3 kDown(0.f, -1.f, 0.f);
|
||||
static const ozz::math::Float3 kCharacterRayHeightOffset(0.f, 10.f, 0.f);
|
||||
static const ozz::math::Float3 kFootRayHeightOffset(0.f, .5f, 0.f);
|
||||
|
||||
class FootIKSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
FootIKSampleApplication()
|
||||
: pelvis_offset_(0.f, 0.f, 0.f),
|
||||
root_translation_(2.17f, 2.f, -2.06f),
|
||||
root_yaw_(2.f),
|
||||
foot_heigh_(.12f),
|
||||
weight_(1.f),
|
||||
soften_(1.f),
|
||||
auto_character_height_(true),
|
||||
pelvis_correction_(true),
|
||||
two_bone_ik_(true),
|
||||
aim_ik_(true),
|
||||
show_skin_(true),
|
||||
show_joints_(false),
|
||||
show_raycast_(false),
|
||||
show_ankle_target_(false),
|
||||
show_root_(false),
|
||||
show_offsetted_root_(false) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and foot ik.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// 1. Updates character main animation.
|
||||
if (!UpdateBaseAnimation(_dt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Finds character height on the floor, evaluted at its root position.
|
||||
if (!UpdateCharacterHeight()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. For each leg, raycasts a vector going down from the ankle position.
|
||||
// This allows to find the intersection point with the floor.
|
||||
if (!RaycastLegs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Computes targetted ankles positions, taking floor steepness and foot
|
||||
// height in consideration.
|
||||
if (!UpdateAnklesTarget()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Offsets the character down, so that the lowest ankle (lowest from its
|
||||
// original position) reaches its targetted position. The other leg(s) will
|
||||
// be ik-ed.
|
||||
if (!UpdatePelvisOffset()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. Updates legs and ankles transforms, so they reach thei targetted
|
||||
// position and orientation.
|
||||
if (!UpdateFootIK()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Raycast down from the current position to find character height on the
|
||||
// floor. It directly updates root translation as output.
|
||||
bool UpdateCharacterHeight() {
|
||||
if (!auto_character_height_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Starts the ray from above (kCharacterRayHeightOffset) current character
|
||||
// position.
|
||||
ozz::sample::RayIntersectsMeshes(
|
||||
root_translation_ + kCharacterRayHeightOffset, kDown,
|
||||
make_span(floors_), &root_translation_, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateBaseAnimation(float _dt) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RaycastLegs() {
|
||||
// Pelvis offset isn't updated yet, it shouldn't be considered. So we're
|
||||
// using "unoffsetted" root transform.
|
||||
const ozz::math::Float4x4 root = GetRootTransform();
|
||||
|
||||
// Raycast down for each leg to find the intersection point with the floor.
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegSetup& leg = legs_setup_[l];
|
||||
LegRayInfo& ray = rays_info_[l];
|
||||
|
||||
// Finds ankle initial world space position
|
||||
ozz::math::Store3PtrU(TransformPoint(root, models_[leg.ankle].cols[3]),
|
||||
&ankles_initial_ws_[l].x);
|
||||
|
||||
// Builds ray, from above ankle (kFootRayHeightOffset) and going downward.
|
||||
ray.start = ankles_initial_ws_[l] + kFootRayHeightOffset;
|
||||
ray.dir = kDown;
|
||||
ray.hit = ozz::sample::RayIntersectsMeshes(
|
||||
ray.start, ray.dir, make_span(floors_), &ray.hit_point,
|
||||
&ray.hit_normal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Comptutes ankle target position (C), so that the foot is in contact with
|
||||
// the floor. Because of floor slope (defined by raycast intersection normal),
|
||||
// ankle position cannot be simply be offseted by foot offset. See geogebra
|
||||
// diagram for more details: media/doc/samples/foot_ik_ankle.ggb
|
||||
bool UpdateAnklesTarget() {
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegRayInfo& ray = rays_info_[l];
|
||||
if (!ray.hit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Computes projection of the ray AI (from start to floor intersection
|
||||
// point) onto floor normal. This gives the length of segment AB.
|
||||
// Note that ray.hit_normal is normalized already.
|
||||
const float ABl = Dot(ray.start - ray.hit_point, ray.hit_normal);
|
||||
if (ABl == 0.f) {
|
||||
// Early out if the two are perpandicular.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Knowing A, AB length and direction, we can compute B position.
|
||||
const ozz::math::Float3 B = ray.start - ray.hit_normal * ABl;
|
||||
|
||||
// Computes sebgment IB and its length (IBl)
|
||||
const ozz::math::Float3 IB = B - ray.hit_point;
|
||||
const float IBl = Length(IB);
|
||||
|
||||
if (IBl <= 0.f) {
|
||||
// If B is at raycast intersection (I), then we still need to update
|
||||
// corrected ankle position (world-space) to take into account foot
|
||||
// height.
|
||||
ankles_target_ws_[l] = ray.hit_point + ray.hit_normal * foot_heigh_;
|
||||
} else {
|
||||
// HC length is known (as foot height). So we're using Thales theorem to
|
||||
// find H position.
|
||||
const float IHl = IBl * foot_heigh_ / ABl;
|
||||
const ozz::math::Float3 IH = IB * (IHl / IBl);
|
||||
const ozz::math::Float3 H = ray.hit_point + IH;
|
||||
|
||||
// C (Corrected ankle) position can now be found.
|
||||
const ozz::math::Float3 C = H + ray.hit_normal * foot_heigh_;
|
||||
|
||||
// Override ankle position with result.
|
||||
ankles_target_ws_[l] = C;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recomputes pelvis offset.
|
||||
// Strategy is to move the pelvis along "down" axis (ray axis), enough for
|
||||
// the lowest foot (lowest from its original position) to reaches ankle
|
||||
// target. The other foot will be ik-ed.
|
||||
bool UpdatePelvisOffset() {
|
||||
pelvis_offset_ = ozz::math::Float3(0.f, 0.f, 0.f);
|
||||
|
||||
float max_dot = -std::numeric_limits<float>::max();
|
||||
if (pelvis_correction_) {
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegRayInfo& ray = rays_info_[l];
|
||||
if (!ray.hit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this ankle is lower (in down direction) compared to the
|
||||
// previous one.
|
||||
const ozz::math::Float3 ankle_to_target =
|
||||
ankles_target_ws_[l] - ankles_initial_ws_[l];
|
||||
const float dot = Dot(ankle_to_target, kDown);
|
||||
if (dot > max_dot) {
|
||||
max_dot = dot;
|
||||
|
||||
// Compute offset using the maximum displacement that the legs should
|
||||
// have to touch ground.
|
||||
pelvis_offset_ = kDown * dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Applies two bone IK to the leg, and aim IK to the ankle
|
||||
bool UpdateFootIK() {
|
||||
// Pelvis offset needs to be considered when converting to medel space. So
|
||||
// we're using "offsetted" root transform.
|
||||
const ozz::math::Float4x4 root = GetOffsettedRootTransform();
|
||||
const ozz::math::Float4x4 inv_root = Invert(root);
|
||||
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
|
||||
// Perform IK
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegRayInfo& ray = rays_info_[l];
|
||||
if (!ray.hit) {
|
||||
continue;
|
||||
}
|
||||
const LegSetup& leg = legs_setup_[l];
|
||||
|
||||
// Updates leg joint chain so ankle reaches its targetted position.
|
||||
if (two_bone_ik_ &&
|
||||
!ApplyLegTwoBoneIK(leg, ankles_target_ws_[l], inv_root)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Updates leg joints model-space transforms.
|
||||
// Update will go from hip to ankle. Ankle's siblings might no be updated
|
||||
// as local-to-model will stop as soon as ankle joint is reached.
|
||||
ltm_job.from = leg.hip;
|
||||
ltm_job.to = leg.ankle;
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes ankle orientation so it's aligned to the floor normal.
|
||||
const ozz::math::Float3 aim_ik_target(ankles_target_ws_[l] +
|
||||
ray.hit_normal);
|
||||
if (aim_ik_ && !ApplyAnkleAimIK(leg, aim_ik_target, inv_root)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Updates model-space transformation now ankle local changes is done.
|
||||
// Ankle rotation has already been updated, but its siblings (or it's
|
||||
// parent siblings) might are not. So we local-to-model update must
|
||||
// be complete starting from hip.
|
||||
ltm_job.from = leg.hip;
|
||||
ltm_job.to = ozz::animation::Skeleton::kMaxJoints;
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function will compute two bone IK on the leg, updating hip and knee
|
||||
// rotations so that ankle can reach its targetted position.
|
||||
bool ApplyLegTwoBoneIK(const LegSetup& _leg,
|
||||
const ozz::math::Float3& _target_ws,
|
||||
const ozz::math::Float4x4& _inv_root) {
|
||||
// Target position and pole vectors must be in model space.
|
||||
const ozz::math::SimdFloat4 target_ms = TransformPoint(
|
||||
_inv_root, ozz::math::simd_float4::Load3PtrU(&_target_ws.x));
|
||||
const ozz::math::SimdFloat4 pole_vector_ms = models_[_leg.knee].cols[1];
|
||||
|
||||
// Builds two bone IK job.
|
||||
ozz::animation::IKTwoBoneJob ik_job;
|
||||
ik_job.target = target_ms;
|
||||
ik_job.pole_vector = pole_vector_ms;
|
||||
// Mid axis (knee) is constant (usualy), and arbitratry defined by
|
||||
// skeleton/rig setup.
|
||||
ik_job.mid_axis = kKneeAxis;
|
||||
ik_job.weight = weight_;
|
||||
ik_job.soften = soften_;
|
||||
ik_job.start_joint = &models_[_leg.hip];
|
||||
ik_job.mid_joint = &models_[_leg.knee];
|
||||
ik_job.end_joint = &models_[_leg.ankle];
|
||||
ozz::math::SimdQuaternion start_correction;
|
||||
ik_job.start_joint_correction = &start_correction;
|
||||
ozz::math::SimdQuaternion mid_correction;
|
||||
ik_job.mid_joint_correction = &mid_correction;
|
||||
if (!ik_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
// Apply IK quaternions to their respective local-space transforms.
|
||||
// Model-space transformations needs to be updated after a call to this
|
||||
// function.
|
||||
ozz::sample::MultiplySoATransformQuaternion(_leg.hip, start_correction,
|
||||
make_span(locals_));
|
||||
ozz::sample::MultiplySoATransformQuaternion(_leg.knee, mid_correction,
|
||||
make_span(locals_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function will compute aim IK on the ankle, updating its rotations so
|
||||
// it can be aligned with the floor.
|
||||
// The strategy is to align ankle up vector in the direction of the floor
|
||||
// normal. The forward direction of the foot is then driven by the pole
|
||||
// vector, which polls the foot (ankle forward vector) toward it's original
|
||||
// (animated) direction.
|
||||
bool ApplyAnkleAimIK(const LegSetup& _leg,
|
||||
const ozz::math::Float3& _target_ws,
|
||||
const ozz::math::Float4x4& _inv_root) {
|
||||
// Target position and pole vectors must be in model space.
|
||||
const ozz::math::SimdFloat4 target_ms = TransformPoint(
|
||||
_inv_root, ozz::math::simd_float4::Load3PtrU(&_target_ws.x));
|
||||
|
||||
ozz::animation::IKAimJob ik_job;
|
||||
// Forward and up vectors are constant (usualy), and arbitratry defined by
|
||||
// skeleton/rig setup.
|
||||
ik_job.forward = kAnkleForward;
|
||||
ik_job.up = kAnkleUp;
|
||||
|
||||
// Model space targetted direction (floor normal in this case).
|
||||
ik_job.target = target_ms;
|
||||
|
||||
// Uses constant ankle Y (skeleton/rig setup dependent) as pole vector. That
|
||||
// allows to maintain foot direction.
|
||||
ik_job.pole_vector = models_[_leg.ankle].cols[1];
|
||||
|
||||
ik_job.joint = &models_[_leg.ankle];
|
||||
ik_job.weight = weight_;
|
||||
ozz::math::SimdQuaternion correction;
|
||||
ik_job.joint_correction = &correction;
|
||||
if (!ik_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
// Apply IK quaternions to their respective local-space transforms.
|
||||
// Model-space transformations needs to be updated after a call to this
|
||||
// function.
|
||||
ozz::sample::MultiplySoATransformQuaternion(_leg.ankle, correction,
|
||||
make_span(locals_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
const float kAxeScale = .1f;
|
||||
const ozz::math::Float4x4 kAxesScale =
|
||||
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load1(kAxeScale));
|
||||
const ozz::math::Float4x4 identity = ozz::math::Float4x4::identity();
|
||||
const ozz::math::Float4x4 offsetted_root = GetOffsettedRootTransform();
|
||||
|
||||
bool success = true;
|
||||
|
||||
// Renders floor meshes.
|
||||
for (size_t i = 0; i < floors_.size(); ++i) {
|
||||
success &= _renderer->DrawMesh(floors_[i], identity);
|
||||
}
|
||||
|
||||
// Renders character.
|
||||
if (show_skin_) {
|
||||
// Builds skinning matrices.
|
||||
// The mesh might not use (aka be skinned by) all skeleton joints. We
|
||||
// use the joint remapping table (available from the mesh object) to
|
||||
// reorder model-space matrices and build skinning ones.
|
||||
for (size_t m = 0; m < meshes_.size(); ++m) {
|
||||
const ozz::sample::Mesh& mesh = meshes_[m];
|
||||
for (size_t i = 0; i < mesh.joint_remaps.size(); ++i) {
|
||||
skinning_matrices_[i] =
|
||||
models_[mesh.joint_remaps[i]] * mesh.inverse_bind_poses[i];
|
||||
}
|
||||
|
||||
success &= _renderer->DrawSkinnedMesh(
|
||||
mesh, make_span(skinning_matrices_), offsetted_root);
|
||||
}
|
||||
} else {
|
||||
// Renders skeleton only.
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
offsetted_root);
|
||||
}
|
||||
|
||||
// Showing joints
|
||||
if (show_joints_) {
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegSetup& leg = legs_setup_[l];
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const int joints[3] = {leg.hip, leg.knee, leg.ankle};
|
||||
const ozz::math::Float4x4& transform =
|
||||
offsetted_root * models_[joints[i]];
|
||||
success &= _renderer->DrawAxes(transform * kAxesScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shows raycast results
|
||||
if (show_raycast_) {
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegRayInfo& ray = rays_info_[l];
|
||||
if (ray.hit) {
|
||||
success &= _renderer->DrawSegment(ray.start, ray.hit_point,
|
||||
ozz::sample::kGreen, identity);
|
||||
success &= _renderer->DrawSegment(
|
||||
ray.hit_point, ray.hit_point + ray.hit_normal * .5f,
|
||||
ozz::sample::kRed, identity);
|
||||
} else {
|
||||
success &=
|
||||
_renderer->DrawSegment(ray.start, ray.start + ray.dir * 10.f,
|
||||
ozz::sample::kWhite, identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shows two bone ik ankle target
|
||||
if (show_ankle_target_) {
|
||||
for (size_t l = 0; l < kLegsCount; ++l) {
|
||||
const LegRayInfo& ray = rays_info_[l];
|
||||
if (ray.hit) {
|
||||
const ozz::math::Float4x4& transform =
|
||||
ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::Load3PtrU(&ankles_target_ws_[l].x));
|
||||
success &= _renderer->DrawAxes(transform * kAxesScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_root_) {
|
||||
const ozz::math::Float4x4 root = GetRootTransform();
|
||||
success &= _renderer->DrawAxes(root);
|
||||
}
|
||||
if (show_offsetted_root_) {
|
||||
success &= _renderer->DrawAxes(offsetted_root);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
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);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Finds left and right joints.
|
||||
if (!SetupLeg(skeleton_, kLeftJointNames, &legs_setup_[kLeft]) ||
|
||||
!SetupLeg(skeleton_, kRightJointNames, &legs_setup_[kRight])) {
|
||||
ozz::log::Err()
|
||||
<< "At least a joint wasn't found in the skeleton hierarchy."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading character mesh.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_mesh, &meshes_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The number of joints of the mesh needs to match skeleton.
|
||||
for (size_t m = 0; m < meshes_.size(); ++m) {
|
||||
const ozz::sample::Mesh& mesh = meshes_[m];
|
||||
if (num_joints < mesh.highest_joint_index()) {
|
||||
ozz::log::Err() << "The provided mesh doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
skinning_matrices_.resize(num_joints);
|
||||
|
||||
// Reading collision/rendering floor mesh.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_floor, &floors_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetupLeg(const ozz::animation::Skeleton& _skeleton,
|
||||
const char* _joint_names[3], LegSetup* _leg) {
|
||||
int found = 0;
|
||||
int joints[3] = {0, 0, 0};
|
||||
for (int i = 0; i < _skeleton.num_joints() && found != 3; i++) {
|
||||
const char* joint_name = _skeleton.joint_names()[i];
|
||||
if (std::strcmp(joint_name, _joint_names[found]) == 0) {
|
||||
joints[found] = i;
|
||||
++found;
|
||||
}
|
||||
}
|
||||
_leg->hip = joints[0];
|
||||
_leg->knee = joints[1];
|
||||
_leg->ankle = joints[2];
|
||||
return found == 3;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char txt[32];
|
||||
|
||||
// Main options
|
||||
{
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Sample options", &opened);
|
||||
if (opened) {
|
||||
_im_gui->DoCheckBox("Auto character height", &auto_character_height_);
|
||||
_im_gui->DoCheckBox("Pelvis correction", &pelvis_correction_);
|
||||
_im_gui->DoCheckBox("Two bone IK (legs)", &two_bone_ik_);
|
||||
_im_gui->DoCheckBox("Aim IK (ankles)", &aim_ik_);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Settings
|
||||
{
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "IK settings", &opened);
|
||||
if (opened) {
|
||||
sprintf(txt, "Foot height %.2g", foot_heigh_);
|
||||
_im_gui->DoSlider(txt, 0.f, .3f, &foot_heigh_);
|
||||
sprintf(txt, "Weight %.2g", weight_);
|
||||
_im_gui->DoSlider(txt, 0.f, 1.f, &weight_);
|
||||
sprintf(txt, "Soften %.2g", soften_);
|
||||
_im_gui->DoSlider(txt, 0.f, 1.f, &soften_, 1.f, two_bone_ik_);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Root
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Root transformation", &opened);
|
||||
if (opened) {
|
||||
bool moved = false;
|
||||
// Translation
|
||||
_im_gui->DoLabel("Translation");
|
||||
sprintf(txt, "x %.2g", root_translation_.x);
|
||||
moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.x);
|
||||
sprintf(txt, "y %.2g", root_translation_.y);
|
||||
moved |= _im_gui->DoSlider(txt, 0.f, 5.f, &root_translation_.y, 1.f,
|
||||
!auto_character_height_);
|
||||
sprintf(txt, "z %.2g", root_translation_.z);
|
||||
moved |= _im_gui->DoSlider(txt, -10.f, 10.f, &root_translation_.z);
|
||||
|
||||
// Rotation (in euler form)
|
||||
_im_gui->DoLabel("Rotation");
|
||||
sprintf(txt, "yaw %.3g", root_yaw_ * ozz::math::kRadianToDegree);
|
||||
moved |=
|
||||
_im_gui->DoSlider(txt, -ozz::math::kPi, ozz::math::kPi, &root_yaw_);
|
||||
|
||||
// Character position shouldn't be changed after the update. In this
|
||||
// case, because UI is updated after "game" update, we need to recompute
|
||||
// character offset and IK.
|
||||
if (moved && auto_character_height_) {
|
||||
OnUpdate(0.f, 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Options
|
||||
{
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Debug options", &opened);
|
||||
if (opened) {
|
||||
_im_gui->DoCheckBox("Show skin", &show_skin_);
|
||||
_im_gui->DoCheckBox("Show joints", &show_joints_);
|
||||
_im_gui->DoCheckBox("Show raycasts", &show_raycast_);
|
||||
_im_gui->DoCheckBox("Show ankle target", &show_ankle_target_);
|
||||
_im_gui->DoCheckBox("Show root", &show_root_);
|
||||
_im_gui->DoCheckBox("Show offsetted root", &show_offsetted_root_);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool GetCameraInitialSetup(ozz::math::Float3* _center,
|
||||
ozz::math::Float2* _angles,
|
||||
float* _distance) const {
|
||||
*_center = ozz::math::Float3(4.7f, 2.3f, -.13f);
|
||||
*_angles = ozz::math::Float2(-.14f, -2.1f);
|
||||
*_distance = 5.9f;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _box) const {
|
||||
*_box = ozz::math::Box();
|
||||
}
|
||||
|
||||
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::LoadX(root_yaw_));
|
||||
}
|
||||
|
||||
ozz::math::Float4x4 GetOffsettedRootTransform() const {
|
||||
if (!pelvis_correction_) {
|
||||
return GetRootTransform();
|
||||
}
|
||||
|
||||
const ozz::math::Float3 offsetted_translation =
|
||||
pelvis_offset_ + root_translation_;
|
||||
|
||||
return ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::Load3PtrU(&offsetted_translation.x)) *
|
||||
ozz::math::Float4x4::FromEuler(
|
||||
ozz::math::simd_float4::LoadX(root_yaw_));
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Buffer of skinning matrices, result of the joint multiplication of the
|
||||
// inverse bind pose with the model space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
ozz::vector<ozz::sample::Mesh> meshes_;
|
||||
|
||||
enum { kLeft, kRight };
|
||||
enum { kLegsCount = 2 };
|
||||
|
||||
LegSetup legs_setup_[kLegsCount];
|
||||
LegRayInfo rays_info_[kLegsCount];
|
||||
ozz::math::Float3 ankles_initial_ws_[kLegsCount];
|
||||
ozz::math::Float3 ankles_target_ws_[kLegsCount];
|
||||
|
||||
LegRayInfo capsule;
|
||||
|
||||
ozz::math::Float3 pelvis_offset_;
|
||||
|
||||
// The floor meshes used by the sample (collision and rendering).
|
||||
ozz::vector<ozz::sample::Mesh> floors_;
|
||||
|
||||
// Root transformation.
|
||||
ozz::math::Float3 root_translation_;
|
||||
float root_yaw_;
|
||||
|
||||
// Foot height setting
|
||||
float foot_heigh_;
|
||||
|
||||
float weight_;
|
||||
float soften_;
|
||||
|
||||
bool auto_character_height_;
|
||||
bool pelvis_correction_;
|
||||
bool two_bone_ik_;
|
||||
bool aim_ik_;
|
||||
|
||||
bool show_skin_;
|
||||
bool show_joints_;
|
||||
bool show_raycast_;
|
||||
bool show_ankle_target_;
|
||||
bool show_root_;
|
||||
bool show_offsetted_root_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Foot IK";
|
||||
return FootIKSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Framework library
|
||||
add_library(sample_framework STATIC
|
||||
application.h
|
||||
application.cc
|
||||
imgui.h
|
||||
image.h
|
||||
image.cc
|
||||
profile.h
|
||||
profile.cc
|
||||
renderer.h
|
||||
utils.h
|
||||
utils.cc
|
||||
mesh.h
|
||||
mesh.cc
|
||||
internal/camera.h
|
||||
internal/camera.cc
|
||||
internal/icosphere.h
|
||||
internal/immediate.h
|
||||
internal/immediate.cc
|
||||
internal/imgui_impl.h
|
||||
internal/imgui_impl.cc
|
||||
internal/renderer_impl.h
|
||||
internal/renderer_impl.cc
|
||||
internal/shader.h
|
||||
internal/shader.cc
|
||||
internal/shooter.h
|
||||
internal/shooter.cc)
|
||||
|
||||
# Samples requires OpenGL package.
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/extern/glfw glfw)
|
||||
|
||||
target_link_libraries(sample_framework
|
||||
glfw)
|
||||
endif()
|
||||
|
||||
target_link_libraries(sample_framework
|
||||
ozz_geometry
|
||||
ozz_animation_offline
|
||||
ozz_options)
|
||||
|
||||
if(TARGET BUILD_DATA_SAMPLE)
|
||||
add_dependencies(sample_framework BUILD_DATA_SAMPLE)
|
||||
endif()
|
||||
|
||||
set_target_properties(sample_framework
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
add_subdirectory(tools)
|
||||
@@ -0,0 +1,721 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "framework/application.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
#if EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
#include "framework/image.h"
|
||||
#include "framework/internal/camera.h"
|
||||
#include "framework/internal/imgui_impl.h"
|
||||
#include "framework/internal/renderer_impl.h"
|
||||
#include "framework/internal/shooter.h"
|
||||
#include "framework/profile.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
OZZ_OPTIONS_DECLARE_INT(
|
||||
max_idle_loops,
|
||||
"The maximum number of idle loops the sample application can perform."
|
||||
" Application automatically exit when this number of loops is reached."
|
||||
" A negative value disables this feature.",
|
||||
-1, false);
|
||||
|
||||
OZZ_OPTIONS_DECLARE_BOOL(render, "Enables sample redering.", true, false);
|
||||
|
||||
namespace {
|
||||
// Screen resolution presets.
|
||||
const ozz::sample::Resolution resolution_presets[] = {
|
||||
{640, 360}, {640, 480}, {800, 450}, {800, 600}, {1024, 576},
|
||||
{1024, 768}, {1280, 720}, {1280, 800}, {1280, 960}, {1280, 1024},
|
||||
{1400, 1050}, {1440, 900}, {1600, 900}, {1600, 1200}, {1680, 1050},
|
||||
{1920, 1080}, {1920, 1200}};
|
||||
const int kNumPresets = OZZ_ARRAY_SIZE(resolution_presets);
|
||||
} // namespace
|
||||
|
||||
// Check resolution argument is within 0 - kNumPresets
|
||||
static bool ResolutionCheck(const ozz::options::Option& _option,
|
||||
int /*_argc*/) {
|
||||
const ozz::options::IntOption& option =
|
||||
static_cast<const ozz::options::IntOption&>(_option);
|
||||
return option >= 0 && option < kNumPresets;
|
||||
}
|
||||
|
||||
OZZ_OPTIONS_DECLARE_INT_FN(resolution, "Resolution index (0 to 17).", 5, false,
|
||||
&ResolutionCheck);
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
Application* Application::application_ = nullptr;
|
||||
|
||||
Application::Application()
|
||||
: exit_(false),
|
||||
freeze_(false),
|
||||
fix_update_rate(false),
|
||||
fixed_update_rate(60.f),
|
||||
time_factor_(1.f),
|
||||
time_(0.f),
|
||||
last_idle_time_(0.),
|
||||
show_help_(false),
|
||||
show_grid_(true),
|
||||
show_axes_(true),
|
||||
capture_video_(false),
|
||||
capture_screenshot_(false),
|
||||
fps_(New<Record>(128)),
|
||||
update_time_(New<Record>(128)),
|
||||
render_time_(New<Record>(128)),
|
||||
resolution_(resolution_presets[0]) {
|
||||
#ifndef NDEBUG
|
||||
// Assert presets are correctly sorted.
|
||||
for (int i = 1; i < kNumPresets; ++i) {
|
||||
const Resolution& preset_m1 = resolution_presets[i - 1];
|
||||
const Resolution& preset = resolution_presets[i];
|
||||
assert(preset.width > preset_m1.width || preset.height > preset_m1.height);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
Application::~Application() {}
|
||||
|
||||
int Application::Run(int _argc, const char** _argv, const char* _version,
|
||||
const char* _title) {
|
||||
// Only one application at a time can be ran.
|
||||
if (application_) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
application_ = this;
|
||||
|
||||
// Starting application
|
||||
log::Out() << "Starting sample \"" << _title << "\" version \"" << _version
|
||||
<< "\"" << std::endl;
|
||||
log::Out() << "Ozz libraries were built with \""
|
||||
<< math::SimdImplementationName() << "\" SIMD math implementation."
|
||||
<< std::endl;
|
||||
|
||||
// Parse command line arguments.
|
||||
const char* usage =
|
||||
"Ozz animation sample. See README.md file for more details.";
|
||||
ozz::options::ParseResult result =
|
||||
ozz::options::ParseCommandLine(_argc, _argv, _version, usage);
|
||||
if (result != ozz::options::kSuccess) {
|
||||
exit_ = true;
|
||||
return result == ozz::options::kExitSuccess ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Fetch initial resolution.
|
||||
resolution_ = resolution_presets[OPTIONS_resolution];
|
||||
|
||||
#ifdef __APPLE__
|
||||
// On OSX, when run from Finder, working path is the root path. This does not
|
||||
// allow to load resources from relative path.
|
||||
// The workaround is to change the working directory to application directory.
|
||||
// The proper solution would probably be to use bundles and load data from
|
||||
// resource folder.
|
||||
chdir(ozz::options::ParsedExecutablePath().c_str());
|
||||
#endif // __APPLE__
|
||||
|
||||
// Initialize help.
|
||||
ParseReadme();
|
||||
|
||||
// Open an OpenGL window
|
||||
bool success = true;
|
||||
if (OPTIONS_render) {
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) {
|
||||
application_ = nullptr;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Setup GL context.
|
||||
const int gl_version_major = 2, gl_version_minor = 0;
|
||||
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, gl_version_major);
|
||||
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, gl_version_minor);
|
||||
glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
|
||||
#ifndef NDEBUG
|
||||
glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
|
||||
#endif // NDEBUG
|
||||
|
||||
// Initializes rendering before looping.
|
||||
if (!glfwOpenWindow(resolution_.width, resolution_.height, 8, 8, 8, 8, 32,
|
||||
0, GLFW_WINDOW)) {
|
||||
log::Err() << "Failed to open OpenGL window. Required OpenGL version is "
|
||||
<< gl_version_major << "." << gl_version_minor << "."
|
||||
<< std::endl;
|
||||
success = false;
|
||||
} else {
|
||||
log::Out() << "Successfully opened OpenGL window version \""
|
||||
<< glGetString(GL_VERSION) << "\"." << std::endl;
|
||||
|
||||
// Allocates and initializes camera
|
||||
camera_ = make_unique<internal::Camera>();
|
||||
math::Float3 camera_center;
|
||||
math::Float2 camera_angles;
|
||||
float distance;
|
||||
if (GetCameraInitialSetup(&camera_center, &camera_angles, &distance)) {
|
||||
camera_->Reset(camera_center, camera_angles, distance);
|
||||
}
|
||||
|
||||
// Allocates and initializes renderer.
|
||||
renderer_ = make_unique<internal::RendererImpl>(camera_.get());
|
||||
success = renderer_->Initialize();
|
||||
|
||||
if (success) {
|
||||
shooter_ = make_unique<internal::Shooter>();
|
||||
im_gui_ = make_unique<internal::ImGuiImpl>();
|
||||
|
||||
#ifndef EMSCRIPTEN // Better not rename web page.
|
||||
glfwSetWindowTitle(_title);
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
// Setup the window and installs callbacks.
|
||||
glfwSwapInterval(1); // Enables vertical sync by default.
|
||||
glfwSetWindowSizeCallback(&ResizeCbk);
|
||||
glfwSetWindowCloseCallback(&CloseCbk);
|
||||
|
||||
// Loop the sample.
|
||||
success = Loop();
|
||||
shooter_.reset();
|
||||
im_gui_.reset();
|
||||
}
|
||||
renderer_.reset();
|
||||
camera_.reset();
|
||||
}
|
||||
|
||||
// Closes window and terminates GLFW.
|
||||
glfwTerminate();
|
||||
|
||||
} else {
|
||||
// Loops without any rendering initialization.
|
||||
success = Loop();
|
||||
}
|
||||
|
||||
// Notifies that an error occurred.
|
||||
if (!success) {
|
||||
log::Err() << "An error occurred during sample execution." << std::endl;
|
||||
}
|
||||
|
||||
application_ = nullptr;
|
||||
|
||||
return success ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Helper function to detecte key pressed and released.
|
||||
template <int _Key>
|
||||
bool KeyPressed() {
|
||||
static int previous_key = glfwGetKey(_Key);
|
||||
const int key = glfwGetKey(_Key);
|
||||
const bool pressed = previous_key == GLFW_PRESS && key == GLFW_RELEASE;
|
||||
previous_key = key;
|
||||
return pressed;
|
||||
}
|
||||
|
||||
Application::LoopStatus Application::OneLoop(int _loops) {
|
||||
Profiler profile(fps_.get()); // Profiles frame.
|
||||
|
||||
// Tests for a manual exit request.
|
||||
if (exit_ || glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
|
||||
return kBreak;
|
||||
}
|
||||
|
||||
// Test for an exit request.
|
||||
if (OPTIONS_max_idle_loops > 0 && _loops > OPTIONS_max_idle_loops) {
|
||||
return kBreak;
|
||||
}
|
||||
|
||||
// Don't overload the cpu if the window is not active.
|
||||
#ifndef EMSCRIPTEN
|
||||
if (OPTIONS_render && !glfwGetWindowParam(GLFW_ACTIVE)) {
|
||||
glfwWaitEvents(); // Wait...
|
||||
|
||||
// Reset last update time in order to stop the time while the app isn't
|
||||
// active.
|
||||
last_idle_time_ = glfwGetTime();
|
||||
|
||||
return kContinue; // ...but don't do anything.
|
||||
}
|
||||
#else
|
||||
int width, height;
|
||||
if (emscripten_get_canvas_element_size(nullptr, &width, &height) !=
|
||||
EMSCRIPTEN_RESULT_SUCCESS) {
|
||||
return kBreakFailure;
|
||||
}
|
||||
if (width != resolution_.width || height != resolution_.height) {
|
||||
ResizeCbk(width, height);
|
||||
}
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
// Enable/disable help on F1 key.
|
||||
show_help_ = show_help_ ^ KeyPressed<GLFW_KEY_F1>();
|
||||
|
||||
// Capture screenshot or video.
|
||||
capture_screenshot_ = KeyPressed<'S'>();
|
||||
capture_video_ = capture_video_ ^ KeyPressed<'V'>();
|
||||
|
||||
// Do the main loop.
|
||||
if (!Idle(_loops == 0)) {
|
||||
return kBreakFailure;
|
||||
}
|
||||
|
||||
// Skips display if "no_render" option is enabled.
|
||||
if (OPTIONS_render) {
|
||||
if (!Display()) {
|
||||
return kBreakFailure;
|
||||
}
|
||||
}
|
||||
|
||||
return kContinue;
|
||||
}
|
||||
|
||||
void OneLoopCbk(void* _arg) {
|
||||
Application* app = reinterpret_cast<Application*>(_arg);
|
||||
static int loops = 0;
|
||||
app->OneLoop(loops++);
|
||||
}
|
||||
|
||||
bool Application::Loop() {
|
||||
// Initialize sample.
|
||||
bool success = OnInitialize();
|
||||
|
||||
// Emscripten requires to manage the main loop on their own, as browsers don't
|
||||
// like infinite blocking functions.
|
||||
#ifdef EMSCRIPTEN
|
||||
emscripten_set_main_loop_arg(OneLoopCbk, this, 0, 1);
|
||||
#else // EMSCRIPTEN
|
||||
// Loops.
|
||||
for (int loops = 0; success; ++loops) {
|
||||
const LoopStatus status = OneLoop(loops);
|
||||
success = status != kBreakFailure;
|
||||
if (status != kContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
// De-initialize sample, even in case of initialization failure.
|
||||
OnDestroy();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Application::Display() {
|
||||
assert(OPTIONS_render);
|
||||
|
||||
bool success = true;
|
||||
|
||||
{ // Profiles rendering excluding GUI.
|
||||
Profiler profile(render_time_.get());
|
||||
|
||||
GL(ClearDepth(1.f));
|
||||
GL(ClearColor(.4f, .42f, .38f, 1.f));
|
||||
GL(Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
|
||||
// Setup default states
|
||||
GL(Enable(GL_CULL_FACE));
|
||||
GL(CullFace(GL_BACK));
|
||||
GL(Enable(GL_DEPTH_TEST));
|
||||
GL(DepthMask(GL_TRUE));
|
||||
GL(DepthFunc(GL_LEQUAL));
|
||||
|
||||
// Bind 3D camera matrices.
|
||||
camera_->Bind3D();
|
||||
|
||||
// Forwards display event to the inheriting application.
|
||||
if (success) {
|
||||
success = OnDisplay(renderer_.get());
|
||||
}
|
||||
} // Ends profiling.
|
||||
|
||||
// Renders grid and axes at the end as they are transparent.
|
||||
if (show_grid_) {
|
||||
renderer_->DrawGrid(20, 1.f);
|
||||
}
|
||||
if (show_axes_) {
|
||||
renderer_->DrawAxes(ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
// Bind 2D camera matrices.
|
||||
camera_->Bind2D();
|
||||
|
||||
// Forwards gui event to the inheriting application.
|
||||
if (success) {
|
||||
success = Gui();
|
||||
}
|
||||
|
||||
// Capture back buffer.
|
||||
if (capture_screenshot_ || capture_video_) {
|
||||
shooter_->Capture(GL_BACK);
|
||||
capture_screenshot_ = false;
|
||||
}
|
||||
|
||||
// Swaps current window.
|
||||
glfwSwapBuffers();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Application::Idle(bool _first_frame) {
|
||||
// Early out if displaying help.
|
||||
if (show_help_) {
|
||||
last_idle_time_ = glfwGetTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compute elapsed time since last idle, and delta time.
|
||||
float delta;
|
||||
double time = glfwGetTime();
|
||||
if (_first_frame || // Don't take into account time spent initializing.
|
||||
time == 0.) { // Means glfw isn't initialized (rendering's disabled).
|
||||
delta = 1.f / 60.f;
|
||||
} else {
|
||||
delta = static_cast<float>(time - last_idle_time_);
|
||||
}
|
||||
last_idle_time_ = time;
|
||||
|
||||
// Update dt, can be scaled, fixed, freezed...
|
||||
float update_delta;
|
||||
if (freeze_) {
|
||||
update_delta = 0.f;
|
||||
} else {
|
||||
if (fix_update_rate) {
|
||||
update_delta = time_factor_ / fixed_update_rate;
|
||||
} else {
|
||||
update_delta = delta * time_factor_;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment current application time
|
||||
time_ += update_delta;
|
||||
|
||||
// Forwards update event to the inheriting application.
|
||||
bool update_result;
|
||||
{ // Profiles update scope.
|
||||
Profiler profile(update_time_.get());
|
||||
update_result = OnUpdate(update_delta, time_);
|
||||
}
|
||||
|
||||
// Updates screen shooter object.
|
||||
if (shooter_) {
|
||||
shooter_->Update();
|
||||
}
|
||||
|
||||
// Update camera model-view matrix.
|
||||
if (camera_) {
|
||||
math::Box scene_bounds;
|
||||
GetSceneBounds(&scene_bounds);
|
||||
|
||||
math::Float4x4 camera_transform;
|
||||
if (GetCameraOverride(&camera_transform)) {
|
||||
camera_->Update(camera_transform, scene_bounds, delta, _first_frame);
|
||||
} else {
|
||||
camera_->Update(scene_bounds, delta, _first_frame);
|
||||
}
|
||||
}
|
||||
|
||||
return update_result;
|
||||
}
|
||||
|
||||
bool Application::Gui() {
|
||||
bool success = true;
|
||||
const float kFormWidth = 200.f;
|
||||
const float kHelpMargin = 16.f;
|
||||
|
||||
// Finds gui area.
|
||||
const float kGuiMargin = 2.f;
|
||||
ozz::math::RectInt window_rect(0, 0, resolution_.width, resolution_.height);
|
||||
|
||||
// Fills ImGui's input structure.
|
||||
internal::ImGuiImpl::Inputs input;
|
||||
int mouse_y;
|
||||
glfwGetMousePos(&input.mouse_x, &mouse_y);
|
||||
input.mouse_y = window_rect.height - mouse_y;
|
||||
input.lmb_pressed = glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
|
||||
|
||||
// Starts frame
|
||||
im_gui_->BeginFrame(input, window_rect, renderer_.get());
|
||||
|
||||
// Downcast to public imgui.
|
||||
ImGui* im_gui = im_gui_.get();
|
||||
// Help gui.
|
||||
{
|
||||
math::RectFloat rect(kGuiMargin, kGuiMargin,
|
||||
window_rect.width - kGuiMargin * 2.f,
|
||||
window_rect.height - kGuiMargin * 2.f);
|
||||
// Doesn't constrain form is it's opened, so it covers all screen.
|
||||
ImGui::Form form(im_gui, "Show help", rect, &show_help_, !show_help_);
|
||||
if (show_help_) {
|
||||
im_gui->DoLabel(help_.c_str(), ImGui::kLeft, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Do framework gui.
|
||||
if (!show_help_ && success &&
|
||||
window_rect.width > (kGuiMargin + kFormWidth) * 2.f) {
|
||||
static bool open = true;
|
||||
math::RectFloat rect(kGuiMargin, kGuiMargin, kFormWidth,
|
||||
window_rect.height - kGuiMargin * 2.f - kHelpMargin);
|
||||
ImGui::Form form(im_gui, "Framework", rect, &open, true);
|
||||
if (open) {
|
||||
success = FrameworkGui();
|
||||
}
|
||||
}
|
||||
|
||||
// Do sample gui.
|
||||
if (!show_help_ && success && window_rect.width > kGuiMargin + kFormWidth) {
|
||||
static bool open = true;
|
||||
math::RectFloat rect(window_rect.width - kFormWidth - kGuiMargin,
|
||||
kGuiMargin, kFormWidth,
|
||||
window_rect.height - kGuiMargin * 2 - kHelpMargin);
|
||||
ImGui::Form form(im_gui, "Sample", rect, &open, true);
|
||||
if (open) {
|
||||
// Forwards event to the inherited application.
|
||||
success = OnGui(im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Ends frame
|
||||
im_gui_->EndFrame();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Application::FrameworkGui() {
|
||||
// Downcast to public imgui.
|
||||
ImGui* im_gui = im_gui_.get();
|
||||
{ // Render statistics
|
||||
static bool open = true;
|
||||
ImGui::OpenClose stat_oc(im_gui, "Statistics", &open);
|
||||
if (open) {
|
||||
char szLabel[64];
|
||||
{ // FPS
|
||||
Record::Statistics statistics = fps_->GetStatistics();
|
||||
std::sprintf(szLabel, "FPS: %.0f",
|
||||
statistics.mean == 0.f ? 0.f : 1000.f / statistics.mean);
|
||||
static bool fps_open = false;
|
||||
ImGui::OpenClose stats(im_gui, szLabel, &fps_open);
|
||||
if (fps_open) {
|
||||
std::sprintf(szLabel, "Frame: %.2f ms", statistics.mean);
|
||||
im_gui->DoGraph(szLabel, 0.f, statistics.max, statistics.latest,
|
||||
fps_->cursor(), fps_->record_begin(),
|
||||
fps_->record_end());
|
||||
}
|
||||
}
|
||||
{ // Update time
|
||||
Record::Statistics statistics = update_time_->GetStatistics();
|
||||
std::sprintf(szLabel, "Update: %.2f ms", statistics.mean);
|
||||
static bool update_open = true; // This is the most relevant for ozz.
|
||||
ImGui::OpenClose stats(im_gui, szLabel, &update_open);
|
||||
if (update_open) {
|
||||
im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest,
|
||||
update_time_->cursor(), update_time_->record_begin(),
|
||||
update_time_->record_end());
|
||||
}
|
||||
}
|
||||
{ // Render time
|
||||
Record::Statistics statistics = render_time_->GetStatistics();
|
||||
std::sprintf(szLabel, "Render: %.2f ms", statistics.mean);
|
||||
static bool render_open = false;
|
||||
ImGui::OpenClose stats(im_gui, szLabel, &render_open);
|
||||
if (render_open) {
|
||||
im_gui->DoGraph(nullptr, 0.f, statistics.max, statistics.latest,
|
||||
render_time_->cursor(), render_time_->record_begin(),
|
||||
render_time_->record_end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // Time control
|
||||
static bool open = false;
|
||||
ImGui::OpenClose stats(im_gui, "Time control", &open);
|
||||
if (open) {
|
||||
im_gui->DoButton("Freeze", true, &freeze_);
|
||||
im_gui->DoCheckBox("Fix update rate", &fix_update_rate, true);
|
||||
if (!fix_update_rate) {
|
||||
char sz_factor[64];
|
||||
std::sprintf(sz_factor, "Time factor: %.2f", time_factor_);
|
||||
im_gui->DoSlider(sz_factor, -5.f, 5.f, &time_factor_);
|
||||
if (im_gui->DoButton("Reset time factor", time_factor_ != 1.f)) {
|
||||
time_factor_ = 1.f;
|
||||
}
|
||||
} else {
|
||||
char sz_fixed_update_rate[64];
|
||||
std::sprintf(sz_fixed_update_rate, "Update rate: %.0f fps",
|
||||
fixed_update_rate);
|
||||
im_gui->DoSlider(sz_fixed_update_rate, 1.f, 200.f, &fixed_update_rate,
|
||||
.5f, true);
|
||||
if (im_gui->DoButton("Reset update rate", fixed_update_rate != 60.f)) {
|
||||
fixed_update_rate = 60.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // Rendering options
|
||||
static bool open = false;
|
||||
ImGui::OpenClose options(im_gui, "Options", &open);
|
||||
if (open) {
|
||||
// Multi-sampling.
|
||||
static bool fsaa_available = glfwGetWindowParam(GLFW_FSAA_SAMPLES) != 0;
|
||||
static bool fsaa_enabled = fsaa_available;
|
||||
if (im_gui->DoCheckBox("Anti-aliasing", &fsaa_enabled, fsaa_available)) {
|
||||
if (fsaa_enabled) {
|
||||
GL(Enable(GL_MULTISAMPLE));
|
||||
} else {
|
||||
GL(Disable(GL_MULTISAMPLE));
|
||||
}
|
||||
}
|
||||
// Vertical sync
|
||||
static bool vertical_sync_ = true; // On by default.
|
||||
if (im_gui->DoCheckBox("Vertical sync", &vertical_sync_, true)) {
|
||||
glfwSwapInterval(vertical_sync_ ? 1 : 0);
|
||||
}
|
||||
|
||||
im_gui->DoCheckBox("Show grid", &show_grid_, true);
|
||||
im_gui->DoCheckBox("Show axes", &show_axes_, true);
|
||||
}
|
||||
|
||||
// Searches for matching resolution settings.
|
||||
int preset_lookup = 0;
|
||||
for (; preset_lookup < kNumPresets - 1; ++preset_lookup) {
|
||||
const Resolution& preset = resolution_presets[preset_lookup];
|
||||
if (preset.width > resolution_.width) {
|
||||
break;
|
||||
} else if (preset.width == resolution_.width) {
|
||||
if (preset.height >= resolution_.height) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char szResolution[64];
|
||||
std::sprintf(szResolution, "Resolution: %dx%d", resolution_.width,
|
||||
resolution_.height);
|
||||
if (im_gui->DoSlider(szResolution, 0, kNumPresets - 1, &preset_lookup)) {
|
||||
// Resolution changed.
|
||||
resolution_ = resolution_presets[preset_lookup];
|
||||
glfwSetWindowSize(resolution_.width, resolution_.height);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Capture
|
||||
static bool open = false;
|
||||
ImGui::OpenClose controls(im_gui, "Capture", &open);
|
||||
if (open) {
|
||||
im_gui->DoButton("Capture video", true, &capture_video_);
|
||||
capture_screenshot_ |= im_gui->DoButton(
|
||||
"Capture screenshot", !capture_video_, &capture_screenshot_);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Controls
|
||||
static bool open = false;
|
||||
ImGui::OpenClose controls(im_gui, "Camera controls", &open);
|
||||
if (open) {
|
||||
camera_->OnGui(im_gui);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::GetCameraInitialSetup(math::Float3* _center,
|
||||
math::Float2* _angles,
|
||||
float* _distance) const {
|
||||
(void)_center;
|
||||
(void)_angles;
|
||||
(void)_distance;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default implementation doesn't override camera location.
|
||||
bool Application::GetCameraOverride(math::Float4x4* _transform) const {
|
||||
(void)_transform;
|
||||
assert(_transform);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::ResizeCbk(int _width, int _height) {
|
||||
// Stores new resolution settings.
|
||||
application_->resolution_.width = _width;
|
||||
application_->resolution_.height = _height;
|
||||
|
||||
// Uses the full viewport.
|
||||
GL(Viewport(0, 0, _width, _height));
|
||||
|
||||
// Forwards screen size to camera and shooter.
|
||||
application_->camera_->Resize(_width, _height);
|
||||
application_->shooter_->Resize(_width, _height);
|
||||
}
|
||||
|
||||
int Application::CloseCbk() {
|
||||
application_->exit_ = true;
|
||||
return GL_FALSE; // The window will be closed while exiting the main loop.
|
||||
}
|
||||
|
||||
void Application::ParseReadme() {
|
||||
const char* error_message = "Unable to find README.md help file.";
|
||||
|
||||
// Get README file, opens as binary to avoid conversions.
|
||||
ozz::io::File file("README.md", "rb");
|
||||
if (!file.opened()) {
|
||||
help_ = error_message;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate enough space to store the whole file.
|
||||
const size_t read_length = file.Size();
|
||||
ozz::memory::Allocator* allocator = ozz::memory::default_allocator();
|
||||
char* content = reinterpret_cast<char*>(allocator->Allocate(read_length, 4));
|
||||
|
||||
// Read the content
|
||||
if (file.Read(content, read_length) == read_length) {
|
||||
help_ = ozz::string(content, content + read_length);
|
||||
} else {
|
||||
help_ = error_message;
|
||||
}
|
||||
|
||||
// Deallocate temporary buffer;
|
||||
allocator->Deallocate(content);
|
||||
}
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,234 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_APPLICATION_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_APPLICATION_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include "ozz/base/containers/string.h"
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace math {
|
||||
struct Box;
|
||||
struct Float2;
|
||||
struct Float3;
|
||||
struct Float4x4;
|
||||
} // namespace math
|
||||
namespace sample {
|
||||
|
||||
class ImGui;
|
||||
class Renderer;
|
||||
class Record;
|
||||
|
||||
namespace internal {
|
||||
class ImGuiImpl;
|
||||
class RendererImpl;
|
||||
class Camera;
|
||||
class Shooter;
|
||||
} // namespace internal
|
||||
|
||||
// Screen resolution settings.
|
||||
struct Resolution {
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
class Application {
|
||||
public:
|
||||
// Creates a window and initialize GL context.
|
||||
// Any failure during initialization or loop execution will be silently
|
||||
// handled, until the call to ::Run that will return EXIT_FAILURE.
|
||||
Application();
|
||||
|
||||
// Destroys the application. Cleans up everything.
|
||||
virtual ~Application();
|
||||
|
||||
// Runs application main loop.
|
||||
// Caller must provide main function arguments, as well as application version
|
||||
// and usage strings.
|
||||
// Returns EXIT_SUCCESS if the application exits due to user request, or
|
||||
// EXIT_FAILURE if an error occurred during initialization or the main loop.
|
||||
// Only one application can be run at a time, otherwise EXIT_FAILURE is
|
||||
// returned.
|
||||
int Run(int _argc, const char** _argv, const char* _version,
|
||||
const char* _title);
|
||||
|
||||
private:
|
||||
// Provides initialization event to the inheriting application. Called while
|
||||
// the help screen is being displayed.
|
||||
// OnInitialize can return false which will in turn skip the display loop and
|
||||
// exit the application with EXIT_FAILURE. Note that OnDestroy is called in
|
||||
// any case.
|
||||
virtual bool OnInitialize() = 0;
|
||||
|
||||
// Provides de-initialization event to the inheriting application.
|
||||
// OnDestroy is called even if OnInitialize failed and returned an error.
|
||||
virtual void OnDestroy() = 0;
|
||||
|
||||
// Provides update event to the inheriting application.
|
||||
// _dt is the elapsed time (in seconds) since the last update.
|
||||
// _time is application time including scaling (aka accumulated _dt).
|
||||
// OnUpdate can return false which will in turn stop the loop and exit the
|
||||
// application with EXIT_FAILURE. Note that OnDestroy is called in any case.
|
||||
virtual bool OnUpdate(float _dt, float _time) = 0;
|
||||
|
||||
// Provides immediate mode gui display event to the inheriting application.
|
||||
// This function is called in between the OnDisplay and swap functions.
|
||||
// OnGui can return false which will in turn stop the loop and exit the
|
||||
// application with EXIT_FAILURE. Note that OnDestroy is called in any case.
|
||||
virtual bool OnGui(ImGui* _im_gui) = 0;
|
||||
|
||||
// Provides display event to the inheriting application.
|
||||
// This function is called in between the clear and swap functions.
|
||||
// OnDisplay can return false which will in turn stop the loop and exit the
|
||||
// application with EXIT_FAILURE. Note that OnDestroy is called in any case.
|
||||
virtual bool OnDisplay(Renderer* _renderer) = 0;
|
||||
|
||||
// Initial camera values. These will only be considered if function returns
|
||||
// true;
|
||||
virtual bool GetCameraInitialSetup(math::Float3* _center,
|
||||
math::Float2* _angles,
|
||||
float* _distance) const;
|
||||
|
||||
// Allows the inheriting application to override camera location.
|
||||
// Application should return true (false by default) if it wants to override
|
||||
// Camera location, and fills in this case _transform matrix.
|
||||
// This function is never called before a first OnUpdate.
|
||||
virtual bool GetCameraOverride(math::Float4x4* _transform) const;
|
||||
|
||||
// Requires the inheriting application to provide scene bounds. It is used by
|
||||
// the camera to frame all the scene.
|
||||
// This function is never called before a first OnUpdate.
|
||||
// If _bound is set to "invalid", then camera won't be updated.
|
||||
virtual void GetSceneBounds(math::Box* _bound) const = 0;
|
||||
|
||||
// Implements framework internal loop function.
|
||||
bool Loop();
|
||||
|
||||
// This callback has to forward loop call to OneLoop private function.
|
||||
friend void OneLoopCbk(void*);
|
||||
|
||||
// Implements framework internal one iteration loop function.
|
||||
enum LoopStatus {
|
||||
kContinue, // Can continue with next loop.
|
||||
kBreak, // Should stop looping (ex: exit).
|
||||
kBreakFailure, // // Should stop looping beacause something went wrong.
|
||||
};
|
||||
LoopStatus OneLoop(int _loops);
|
||||
|
||||
// Implements framework internal idle function.
|
||||
// Returns the value returned by OnIdle or from an internal issue.
|
||||
bool Idle(bool _first_frame);
|
||||
|
||||
// Implements framework internal display callback.
|
||||
// Returns the value returned by OnDisplay or from an internal issue.
|
||||
bool Display();
|
||||
|
||||
// Implements framework internal gui callback.
|
||||
// Returns the value returned by OnGui or from an internal error.
|
||||
bool Gui();
|
||||
|
||||
// Implements framework gui rendering.
|
||||
bool FrameworkGui();
|
||||
|
||||
// Implements framework glfw window reshape callback.
|
||||
static void ResizeCbk(int _width, int _height);
|
||||
|
||||
// Implements framework glfw window close callback.
|
||||
static int CloseCbk();
|
||||
|
||||
// Get README.md for content to display it in the help ui.
|
||||
void ParseReadme();
|
||||
|
||||
// Disallow copy and assignment.
|
||||
Application(const Application& _application);
|
||||
void operator=(const Application& _application);
|
||||
|
||||
// A pointer to the current, and only, running application.
|
||||
static Application* application_;
|
||||
|
||||
// Application exit request.
|
||||
bool exit_;
|
||||
|
||||
// Update time freeze state.
|
||||
bool freeze_;
|
||||
|
||||
// Fixes update rat to a fixed value, instead of real_time.
|
||||
bool fix_update_rate;
|
||||
|
||||
// Fixed update rate, only applies to application update dt, not the real fps.
|
||||
float fixed_update_rate;
|
||||
|
||||
// Update time scale factor.
|
||||
float time_factor_;
|
||||
|
||||
// Current application time, including scaling and freezes..
|
||||
float time_;
|
||||
|
||||
// Last time the idle function was called, in seconds.
|
||||
// This is a double value in order to maintain enough accuracy when the
|
||||
// application is running since a long time.
|
||||
double last_idle_time_;
|
||||
|
||||
// The camera object used by the application.
|
||||
unique_ptr<internal::Camera> camera_;
|
||||
|
||||
// The screen shooter object used by the application.
|
||||
unique_ptr<internal::Shooter> shooter_;
|
||||
|
||||
// Set to true to display help.
|
||||
bool show_help_;
|
||||
|
||||
// Grid display settings.
|
||||
bool show_grid_;
|
||||
bool show_axes_;
|
||||
|
||||
// Capture settings.
|
||||
bool capture_video_;
|
||||
bool capture_screenshot_;
|
||||
|
||||
// The renderer utility object used by the application.
|
||||
unique_ptr<internal::RendererImpl> renderer_;
|
||||
|
||||
// Immediate mode gui interface.
|
||||
unique_ptr<internal::ImGuiImpl> im_gui_;
|
||||
|
||||
// Timing records.
|
||||
unique_ptr<Record> fps_;
|
||||
unique_ptr<Record> update_time_;
|
||||
unique_ptr<Record> render_time_;
|
||||
|
||||
// Current screen resolution.
|
||||
Resolution resolution_;
|
||||
|
||||
// Help message.
|
||||
ozz::string help_;
|
||||
};
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_APPLICATION_H_
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/image.h"
|
||||
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace image {
|
||||
|
||||
bool HasAlpha(Format::Value _format) { return _format >= Format::kRGBA; }
|
||||
|
||||
int Stride(Format::Value _format) { return _format <= Format::kBGR ? 3 : 4; }
|
||||
|
||||
#define PUSH_PIXEL_RGB(_buffer, _size, _repetition, _pixel, _mapping) \
|
||||
_buffer[_size + 0] = 0x80 | ((_repetition - 1) & 0xff); \
|
||||
_buffer[_size + 1] = _pixel.c[_mapping[0]]; \
|
||||
_buffer[_size + 2] = _pixel.c[_mapping[1]]; \
|
||||
_buffer[_size + 3] = _pixel.c[_mapping[2]]; \
|
||||
_size += 4;
|
||||
|
||||
#define PUSH_PIXEL_RGBA(_buffer, _size, _repetition, _pixel, _mapping) \
|
||||
_buffer[_size + 0] = 0x80 | ((_repetition - 1) & 0xff); \
|
||||
_buffer[_size + 1] = _pixel.c[_mapping[0]]; \
|
||||
_buffer[_size + 2] = _pixel.c[_mapping[1]]; \
|
||||
_buffer[_size + 3] = _pixel.c[_mapping[2]]; \
|
||||
_buffer[_size + 4] = _pixel.c[_mapping[3]]; \
|
||||
_size += 5;
|
||||
|
||||
bool WriteTGA(const char* _filename, int _width, int _height,
|
||||
Format::Value _src_format, const uint8_t* _src_buffer,
|
||||
bool _write_alpha) {
|
||||
union Pixel {
|
||||
uint8_t c[4];
|
||||
uint32_t p;
|
||||
};
|
||||
|
||||
assert(_filename && _src_buffer);
|
||||
|
||||
ozz::log::LogV() << "Write image to TGA file \"" << _filename << "\"."
|
||||
<< std::endl;
|
||||
|
||||
// Opens output file.
|
||||
ozz::io::File file(_filename, "wb");
|
||||
if (!file.opened()) {
|
||||
ozz::log::Err() << "Failed to open file \"" << _filename
|
||||
<< "\" for writing." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Builds and writes tga header.
|
||||
const uint8_t header[] = {
|
||||
0, // ID length
|
||||
0, // Color map type
|
||||
10, // Image type (RLE true-color)
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, // Color map specification (no color map)
|
||||
0,
|
||||
0, // X-origin (2 bytes little-endian)
|
||||
0,
|
||||
0, // Y-origin (2 bytes little-endian)
|
||||
static_cast<uint8_t>(_width & 0xff), // Width (2 bytes little-endian)
|
||||
static_cast<uint8_t>((_width >> 8) & 0xff),
|
||||
static_cast<uint8_t>(_height & 0xff), // Height (2 bytes little-endian)
|
||||
static_cast<uint8_t>((_height >> 8) & 0xff),
|
||||
static_cast<uint8_t>(_write_alpha ? 32 : 24), // Pixel depth
|
||||
0}; // Image descriptor
|
||||
static_assert(sizeof(header) == 18, "Expects 18 bytes structure.");
|
||||
file.Write(header, sizeof(header));
|
||||
|
||||
// Early out if no pixel to write.
|
||||
if (!_width || !_height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writes pixels, with RLE compression.
|
||||
|
||||
// Prepares component mappings from src to TARGA format.
|
||||
const uint8_t mappings[4][4] = {
|
||||
{2, 1, 0, 0}, {0, 1, 2, 0}, {2, 1, 0, 3}, {0, 1, 2, 3}};
|
||||
const uint8_t* mapping = mappings[_src_format];
|
||||
|
||||
// Allocates enough space to store RLE packets for the worst case scenario.
|
||||
uint8_t* dest_buffer =
|
||||
reinterpret_cast<uint8_t*>(ozz::memory::default_allocator()->Allocate(
|
||||
(1 + (_write_alpha ? 4 : 3)) * _width * _height, 4));
|
||||
|
||||
size_t dest_size = 0;
|
||||
if (HasAlpha(_src_format)) {
|
||||
assert(Stride(_src_format) == 4);
|
||||
const int src_pitch = _width * 4;
|
||||
const int src_size = _height * src_pitch;
|
||||
if (_write_alpha) {
|
||||
for (int line = 0; line < src_size; line += src_pitch) {
|
||||
Pixel current = {{_src_buffer[line + 0], _src_buffer[line + 1],
|
||||
_src_buffer[line + 2], _src_buffer[line + 3]}};
|
||||
int count = 1;
|
||||
for (int p = line + 4; p < line + _width * 4; p += 4, count++) {
|
||||
const Pixel next = {{_src_buffer[p + 0], _src_buffer[p + 1],
|
||||
_src_buffer[p + 2], _src_buffer[p + 3]}};
|
||||
if (current.p != next.p || count == 128) {
|
||||
// Writes current packet.
|
||||
PUSH_PIXEL_RGBA(dest_buffer, dest_size, count, current, mapping);
|
||||
// Starts new RLE packet.
|
||||
current.p = next.p;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// Finishes the line.
|
||||
assert(count > 0 && count <= 128);
|
||||
PUSH_PIXEL_RGBA(dest_buffer, dest_size, count, current, mapping);
|
||||
}
|
||||
} else {
|
||||
for (int line = 0; line < src_size; line += src_pitch) {
|
||||
Pixel current = {{_src_buffer[line + 0], _src_buffer[line + 1],
|
||||
_src_buffer[line + 2], 0}};
|
||||
int count = 1;
|
||||
for (int p = line + 4; p < line + _width * 4; p += 4, count++) {
|
||||
const Pixel next = {
|
||||
{_src_buffer[p + 0], _src_buffer[p + 1], _src_buffer[p + 2], 0}};
|
||||
if (current.p != next.p || count == 128) {
|
||||
// Writes current packet.
|
||||
PUSH_PIXEL_RGB(dest_buffer, dest_size, count, current, mapping);
|
||||
// Starts new RLE packet.
|
||||
current.p = next.p;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// Finishes the line.
|
||||
assert(count > 0 && count <= 128);
|
||||
PUSH_PIXEL_RGB(dest_buffer, dest_size, count, current, mapping);
|
||||
}
|
||||
}
|
||||
} else { // Source has no alpha channel.
|
||||
assert(Stride(_src_format) == 3);
|
||||
const int src_pitch = _width * 3;
|
||||
const int src_size = _height * src_pitch;
|
||||
if (_write_alpha) {
|
||||
for (int line = 0; line < src_size; line += src_pitch) {
|
||||
Pixel current = {{_src_buffer[line + 0], _src_buffer[line + 1],
|
||||
_src_buffer[line + 2], 255}};
|
||||
int count = 1;
|
||||
for (int p = line + 3; p < line + _width * 3; p += 3, count++) {
|
||||
const Pixel next = {{_src_buffer[p + 0], _src_buffer[p + 1],
|
||||
_src_buffer[p + 2], 255}};
|
||||
if (current.p != next.p || count == 128) {
|
||||
// Writes current packet.
|
||||
PUSH_PIXEL_RGBA(dest_buffer, dest_size, count, current, mapping);
|
||||
// Starts new RLE packet.
|
||||
current.p = next.p;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// Finishes the line.
|
||||
assert(count > 0 && count <= 128);
|
||||
PUSH_PIXEL_RGBA(dest_buffer, dest_size, count, current, mapping);
|
||||
}
|
||||
} else {
|
||||
for (int line = 0; line < src_size; line += src_pitch) {
|
||||
Pixel current = {{_src_buffer[line + 0], _src_buffer[line + 1],
|
||||
_src_buffer[line + 2], 0}};
|
||||
int count = 1;
|
||||
for (int p = line + 3; p < line + _width * 3; p += 3, count++) {
|
||||
const Pixel next = {
|
||||
{_src_buffer[p + 0], _src_buffer[p + 1], _src_buffer[p + 2], 0}};
|
||||
if (current.p != next.p || count == 128) {
|
||||
// Writes current packet.
|
||||
PUSH_PIXEL_RGB(dest_buffer, dest_size, count, current, mapping);
|
||||
// Starts new RLE packet.
|
||||
current.p = next.p;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// Finishes the line.
|
||||
assert(count > 0 && count <= 128);
|
||||
PUSH_PIXEL_RGB(dest_buffer, dest_size, count, current, mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes all the RLE packets buffer at once.
|
||||
file.Write(dest_buffer, dest_size);
|
||||
|
||||
ozz::memory::default_allocator()->Deallocate(dest_buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
#undef PUSH_PIXEL_RGB
|
||||
#undef PUSH_PIXEL_RGBA
|
||||
} // namespace image
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,63 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_IMAGE_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_IMAGE_H_
|
||||
|
||||
// Implements image files read/write.
|
||||
|
||||
#include "ozz/base/platform.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace image {
|
||||
|
||||
// Pixel format definition.
|
||||
struct Format {
|
||||
enum Value {
|
||||
kRGB,
|
||||
kBGR,
|
||||
kRGBA,
|
||||
kBGRA,
|
||||
};
|
||||
};
|
||||
|
||||
// Tests if format specification contains alpha channel.
|
||||
bool HasAlpha(Format::Value _format);
|
||||
|
||||
// Get stride from format specification.
|
||||
int Stride(Format::Value _format);
|
||||
|
||||
// Writes as TARGA image to file _filename.
|
||||
bool WriteTGA(const char* _filename, int _width, int _height,
|
||||
Format::Value _src_format, const uint8_t* _src_buffer,
|
||||
bool _write_alpha);
|
||||
|
||||
} // namespace image
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_IMAGE_H_
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_IMGUI_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_IMGUI_H_
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace ozz {
|
||||
namespace math {
|
||||
struct RectFloat;
|
||||
}
|
||||
namespace sample {
|
||||
|
||||
// Interface for immediate mode graphical user interface rendering.
|
||||
class ImGui {
|
||||
public:
|
||||
// Declares a virtual destructor to allow proper destruction.
|
||||
virtual ~ImGui() {}
|
||||
|
||||
// Text justification types.
|
||||
enum Justification {
|
||||
kLeft,
|
||||
kCenter,
|
||||
kRight,
|
||||
};
|
||||
|
||||
// Begins a new form of size _rect.
|
||||
// This object uses the RAII mechanism to ensure begin/end symmetry.
|
||||
// A form is a root in the frame's container stack.
|
||||
// The _rect argument is relative to the parent's rect and is automatically
|
||||
// shrunk to fit inside parent's rect and to the size of its widgets.
|
||||
// Providing a non nullptr _title argument displays a title on top of the form.
|
||||
// Providing a non nullptr _open argument enables the open/close mechanism.
|
||||
class Form {
|
||||
public:
|
||||
Form(ImGui* _im_gui, const char* _title, const math::RectFloat& _rect,
|
||||
bool* _open, bool _constrain)
|
||||
: im_gui_(_im_gui) {
|
||||
im_gui_->BeginContainer(_title, &_rect, _open, _constrain);
|
||||
}
|
||||
~Form() { im_gui_->EndContainer(); }
|
||||
|
||||
private:
|
||||
Form(const Form&); // Forbids copy.
|
||||
void operator=(const Form&); // Forbids assignment.
|
||||
ImGui* im_gui_;
|
||||
};
|
||||
|
||||
// Begins a new open-close widget in the parent's rect, ie: a form or an other
|
||||
// open-close.
|
||||
// This object uses the RAII mechanism to ensure open/close symmetry.
|
||||
// Providing a non nullptr _title argument displays a title on top of the
|
||||
// open-close.
|
||||
// Providing a non nullptr _open argument enables the open/close mechanism.
|
||||
class OpenClose {
|
||||
public:
|
||||
OpenClose(ImGui* _im_gui, const char* _title, bool* _open)
|
||||
: im_gui_(_im_gui) {
|
||||
im_gui_->BeginContainer(_title, nullptr, _open, true);
|
||||
}
|
||||
~OpenClose() { im_gui_->EndContainer(); }
|
||||
|
||||
private:
|
||||
OpenClose(const OpenClose&); // Forbids copy.
|
||||
void operator=(const OpenClose&); // Forbids assignment.
|
||||
ImGui* im_gui_;
|
||||
};
|
||||
|
||||
// Adds a button to the current context and returns true if it was clicked.
|
||||
// If _enabled is false then interactions with the button are disabled, and
|
||||
// rendering is grayed out.
|
||||
// If _state is not nullptr, then it is used as an in-out parameter to set and
|
||||
// store button's state. The button can then behave like a check box, with
|
||||
// a button rendering style.
|
||||
// It allows for example to
|
||||
// Returns true is button was clicked.
|
||||
virtual bool DoButton(const char* _label, bool _enabled = true,
|
||||
bool* _state = nullptr) = 0;
|
||||
|
||||
// Adds a float slider to the current context and returns true if _value was
|
||||
// modified.
|
||||
// _value is the in-out parameter that stores slider value. It's clamped
|
||||
// between _min and _max bounds.
|
||||
// _pow is used to modify slider's scale. It can be used to give a higher
|
||||
// precision to low or high values according to _pow.
|
||||
// If _enabled is false then interactions with the slider are disabled, and
|
||||
// rendering is grayed out.
|
||||
// Returns true if _value _value has changed.
|
||||
virtual bool DoSlider(const char* _label, float _min, float _max,
|
||||
float* _value, float _pow = 1.f,
|
||||
bool _enabled = true) = 0;
|
||||
|
||||
// Adds an integer slider to the current context and returns true if _value
|
||||
// was modified.
|
||||
// _value is the in-out parameter that stores slider value. It's clamped
|
||||
// between _min and _max bounds.
|
||||
// _pow is used to modify slider's scale. It can be used to give a higher
|
||||
// precision to low or high values according to _pow.
|
||||
// If _enabled is false then interactions with the slider are disabled, and
|
||||
// rendering is grayed out.
|
||||
// Returns true if _value _value has changed.
|
||||
virtual bool DoSlider(const char* _label, int _min, int _max, int* _value,
|
||||
float _pow = 1.f, bool _enabled = true) = 0;
|
||||
|
||||
// Adds a check box to the current context and returns true if it has been
|
||||
// modified. Used to represent boolean value.
|
||||
// _state is the in-out parameter that stores check box state.
|
||||
// If _enabled is false then interactions with the slider are disabled, and
|
||||
// rendering is grayed out.
|
||||
// Returns true if _value _state has changed.
|
||||
virtual bool DoCheckBox(const char* _label, bool* _state,
|
||||
bool _enabled = true) = 0;
|
||||
|
||||
// Adds a radio button to the current context and returns true if it has been
|
||||
// modified. Used to represent a possible reference _ref value taken by the
|
||||
// current value _value.
|
||||
// Displays a "checked" radio button if _ref si equal to th selected _value.
|
||||
// Returns true if _value _value has changed.
|
||||
virtual bool DoRadioButton(int _ref, const char* _label, int* _value,
|
||||
bool _enabled = true) = 0;
|
||||
|
||||
// Adds a text label to the current context. Its height depends on the number
|
||||
// of lines.
|
||||
// _justification selects the text alignment in the current container.
|
||||
// if _single_line is true then _label text is cut at the end of the first
|
||||
// line.
|
||||
virtual void DoLabel(const char* _label, Justification _justification = kLeft,
|
||||
bool _single_line = true) = 0;
|
||||
|
||||
// Adds a graph widget to the current context.
|
||||
// Displays values from the right (newest value) to the left (latest).
|
||||
// The range of value is described by _value_begin, _value_end. _value_cursor
|
||||
// allows using a linear or circular buffer of values. Set _value_cursor to
|
||||
// _value_begin to use a linear buffer of range [_value_begin,_value_end[. Or
|
||||
// set _value_cursor of a circular buffer such that range [_value_cursor,
|
||||
// _value_end[ and [_value_begin,_value_cursor[ are used.
|
||||
// All values outside of _min and _max range are clamped.
|
||||
// If _label is not nullptr then a text is displayed on top of the graph.
|
||||
virtual void DoGraph(const char* _label, float _min, float _max, float _mean,
|
||||
const float* _value_cursor, const float* _value_begin,
|
||||
const float* _value_end) = 0;
|
||||
|
||||
private:
|
||||
// Begins a new container of size _rect.
|
||||
// Widgets (buttons, sliders...) can only be displayed in a container.
|
||||
// The rectangles height is the maximum height that the container can use. The
|
||||
// container automatically shrinks to fit the size of the widgets it contains.
|
||||
// Providing a non nullptr _title argument displays a title on top of the
|
||||
// container.
|
||||
// Providing a nullptr _rect argument means that the container will use all its
|
||||
// parent size.
|
||||
// Providing a non nullptr _open argument enables the open/close mechanism.
|
||||
virtual void BeginContainer(const char* _title, const math::RectFloat* _rect,
|
||||
bool* _open, bool _constrain) = 0;
|
||||
|
||||
// Ends the current container.
|
||||
virtual void EndContainer() = 0;
|
||||
};
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_IMGUI_H_
|
||||
@@ -0,0 +1,279 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "camera.h"
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/maths/math_constant.h"
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/platform.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
|
||||
#include "renderer_impl.h"
|
||||
|
||||
using ozz::math::Float2;
|
||||
using ozz::math::Float3;
|
||||
using ozz::math::Float4x4;
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
// Declares camera navigation constants.
|
||||
const float kDefaultDistance = 8.f;
|
||||
const Float3 kDefaultCenter = Float3(0.f, .5f, 0.f);
|
||||
const Float2 kDefaultAngle =
|
||||
Float2(-ozz::math::kPi * 1.f / 12.f, ozz::math::kPi * 1.f / 5.f);
|
||||
const float kAngleFactor = .01f;
|
||||
const float kDistanceFactor = .1f;
|
||||
const float kScrollFactor = .03f;
|
||||
const float kPanFactor = .05f;
|
||||
const float kKeyboardFactor = 100.f;
|
||||
const float kNear = .01f;
|
||||
const float kFar = 1000.f;
|
||||
const float kFovY = ozz::math::kPi / 3.f;
|
||||
const float kFrameAllZoomOut = 1.3f; // 30% bigger than the scene.
|
||||
|
||||
// Setups initial values.
|
||||
Camera::Camera()
|
||||
: projection_(Float4x4::identity()),
|
||||
projection_2d_(Float4x4::identity()),
|
||||
view_(Float4x4::identity()),
|
||||
view_proj_(Float4x4::identity()),
|
||||
angles_(kDefaultAngle),
|
||||
center_(kDefaultCenter),
|
||||
distance_(kDefaultDistance),
|
||||
mouse_last_x_(0),
|
||||
mouse_last_y_(0),
|
||||
mouse_last_wheel_(0),
|
||||
auto_framing_(true) {}
|
||||
|
||||
Camera::~Camera() {}
|
||||
|
||||
void Camera::Update(const math::Box& _box, float _delta_time,
|
||||
bool _first_frame) {
|
||||
// Frame the scene according to the provided box.
|
||||
if (_box.is_valid()) {
|
||||
if (auto_framing_ || _first_frame) {
|
||||
center_ = (_box.max + _box.min) * .5f;
|
||||
if (_first_frame) {
|
||||
const float radius = Length(_box.max - _box.min) * .5f;
|
||||
distance_ = radius * kFrameAllZoomOut / tanf(kFovY * .5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update manual controls.
|
||||
const Controls controls = UpdateControls(_delta_time);
|
||||
|
||||
// Disable autoframing according to inputs.
|
||||
auto_framing_ &=
|
||||
!controls.panning && !controls.zooming && !controls.zooming_wheel;
|
||||
}
|
||||
|
||||
void Camera::Update(const math::Float4x4& _transform, const math::Box& _box,
|
||||
float _delta_time, bool _first_frame) {
|
||||
// Extract distance and angles such that theu are coherent when switching out
|
||||
// of auto_framing_.
|
||||
if (_box.is_valid()) {
|
||||
if (auto_framing_ || _first_frame) {
|
||||
// Extract components from the view martrix.
|
||||
ozz::math::Float3 camera_dir;
|
||||
ozz::math::Store3PtrU(-ozz::math::Normalize3(_transform.cols[2]),
|
||||
&camera_dir.x);
|
||||
ozz::math::Float3 camera_pos;
|
||||
ozz::math::Store3PtrU(_transform.cols[3], &camera_pos.x);
|
||||
|
||||
// Arbitrary decides that distance (focus point) is from camera to scene
|
||||
// center.
|
||||
const ozz::math::Float3 box_center_ = (_box.max + _box.min) * .5f;
|
||||
distance_ = Length(box_center_ - camera_pos);
|
||||
center_ = camera_pos + camera_dir * distance_;
|
||||
angles_.x = asinf(camera_dir.y);
|
||||
angles_.y = atan2(-camera_dir.x, -camera_dir.z);
|
||||
}
|
||||
}
|
||||
|
||||
// Update manual controls.
|
||||
const Controls controls = UpdateControls(_delta_time);
|
||||
|
||||
// Disable autoframing according to inputs.
|
||||
auto_framing_ &= !controls.panning && !controls.rotating &&
|
||||
!controls.zooming && !controls.zooming_wheel;
|
||||
|
||||
if (auto_framing_) {
|
||||
view_ = Invert(_transform);
|
||||
}
|
||||
}
|
||||
|
||||
Camera::Controls Camera::UpdateControls(float _delta_time) {
|
||||
Controls controls;
|
||||
controls.zooming = false;
|
||||
controls.zooming_wheel = false;
|
||||
controls.rotating = false;
|
||||
controls.panning = false;
|
||||
|
||||
// Mouse wheel + SHIFT activates Zoom.
|
||||
if (glfwGetKey(GLFW_KEY_LSHIFT) == GLFW_PRESS) {
|
||||
const int w = glfwGetMouseWheel();
|
||||
const int dw = w - mouse_last_wheel_;
|
||||
mouse_last_wheel_ = w;
|
||||
if (dw != 0) {
|
||||
controls.zooming_wheel = true;
|
||||
distance_ *= 1.f + -dw * kScrollFactor;
|
||||
}
|
||||
} else {
|
||||
mouse_last_wheel_ = glfwGetMouseWheel();
|
||||
}
|
||||
|
||||
// Fetches current mouse position and compute its movement since last frame.
|
||||
int x, y;
|
||||
glfwGetMousePos(&x, &y);
|
||||
const int mdx = x - mouse_last_x_;
|
||||
const int mdy = y - mouse_last_y_;
|
||||
mouse_last_x_ = x;
|
||||
mouse_last_y_ = y;
|
||||
|
||||
// Finds keyboard relative dx and dy commmands.
|
||||
const int timed_factor =
|
||||
ozz::math::Max(1, static_cast<int>(kKeyboardFactor * _delta_time));
|
||||
const int kdx =
|
||||
timed_factor * (glfwGetKey(GLFW_KEY_LEFT) - glfwGetKey(GLFW_KEY_RIGHT));
|
||||
const int kdy =
|
||||
timed_factor * (glfwGetKey(GLFW_KEY_DOWN) - glfwGetKey(GLFW_KEY_UP));
|
||||
const bool keyboard_interact = kdx || kdy;
|
||||
|
||||
// Computes composed keyboard and mouse dx and dy.
|
||||
const int dx = mdx + kdx;
|
||||
const int dy = mdy + kdy;
|
||||
|
||||
// Mouse right button activates Zoom, Pan and Orbit modes.
|
||||
if (keyboard_interact ||
|
||||
glfwGetMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) {
|
||||
if (glfwGetKey(GLFW_KEY_LSHIFT) == GLFW_PRESS) { // Zoom mode.
|
||||
controls.zooming = true;
|
||||
|
||||
distance_ += dy * kDistanceFactor;
|
||||
} else if (glfwGetKey(GLFW_KEY_LALT) == GLFW_PRESS) { // Pan mode.
|
||||
controls.panning = true;
|
||||
|
||||
const float dx_pan = -dx * kPanFactor;
|
||||
const float dy_pan = -dy * kPanFactor;
|
||||
|
||||
// Moves along camera axes.
|
||||
math::Float4x4 transpose = Transpose(view_);
|
||||
math::Float3 right_transpose, up_transpose;
|
||||
math::Store3PtrU(transpose.cols[0], &right_transpose.x);
|
||||
math::Store3PtrU(transpose.cols[1], &up_transpose.x);
|
||||
center_ = center_ + right_transpose * dx_pan + up_transpose * dy_pan;
|
||||
} else { // Orbit mode.
|
||||
controls.rotating = true;
|
||||
|
||||
angles_.x = fmodf(angles_.x - dy * kAngleFactor, ozz::math::k2Pi);
|
||||
angles_.y = fmodf(angles_.y - dx * kAngleFactor, ozz::math::k2Pi);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the model view matrix components.
|
||||
const Float4x4 center = Float4x4::Translation(
|
||||
math::simd_float4::Load(center_.x, center_.y, center_.z, 1.f));
|
||||
const Float4x4 y_rotation = Float4x4::FromAxisAngle(
|
||||
math::simd_float4::y_axis(), math::simd_float4::Load1(angles_.y));
|
||||
const Float4x4 x_rotation = Float4x4::FromAxisAngle(
|
||||
math::simd_float4::x_axis(), math::simd_float4::Load1(angles_.x));
|
||||
const Float4x4 distance =
|
||||
Float4x4::Translation(math::simd_float4::Load(0.f, 0.f, distance_, 1.f));
|
||||
|
||||
// Concatenate view matrix components.
|
||||
view_ = Invert(center * y_rotation * x_rotation * distance);
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
void Camera::Reset(const math::Float3& _center, const math::Float2& _angles,
|
||||
float _distance) {
|
||||
center_ = _center;
|
||||
angles_ = _angles;
|
||||
distance_ = _distance;
|
||||
}
|
||||
|
||||
void Camera::OnGui(ImGui* _im_gui) {
|
||||
const char* controls_label =
|
||||
"-RMB: Rotate\n"
|
||||
"-Shift + Wheel: Zoom\n"
|
||||
"-Shift + RMB: Zoom\n"
|
||||
"-Alt + RMB: Pan\n";
|
||||
_im_gui->DoLabel(controls_label, ImGui::kLeft, false);
|
||||
|
||||
_im_gui->DoCheckBox("Automatic", &auto_framing_);
|
||||
}
|
||||
|
||||
void Camera::Bind3D() {
|
||||
// Updates internal vp matrix.
|
||||
view_proj_ = projection_ * view_;
|
||||
}
|
||||
|
||||
void Camera::Bind2D() {
|
||||
// Updates internal vp matrix. View matrix is identity.
|
||||
view_proj_ = projection_2d_;
|
||||
}
|
||||
|
||||
void Camera::Resize(int _width, int _height) {
|
||||
// Handle empty windows.
|
||||
if (_width <= 0 || _height <= 0) {
|
||||
projection_ = ozz::math::Float4x4::identity();
|
||||
projection_2d_ = ozz::math::Float4x4::identity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the 3D projection matrix.
|
||||
const float ratio = 1.f * _width / _height;
|
||||
const float h = tan(kFovY * .5f) * kNear;
|
||||
const float w = h * ratio;
|
||||
|
||||
projection_.cols[0] = math::simd_float4::Load(kNear / w, 0.f, 0.f, 0.f);
|
||||
projection_.cols[1] = math::simd_float4::Load(0.f, kNear / h, 0.f, 0.f);
|
||||
projection_.cols[2] =
|
||||
math::simd_float4::Load(0.f, 0.f, -(kFar + kNear) / (kFar - kNear), -1.f);
|
||||
projection_.cols[3] = math::simd_float4::Load(
|
||||
0.f, 0.f, -(2.f * kFar * kNear) / (kFar - kNear), 0.f);
|
||||
|
||||
// Computes the 2D projection matrix.
|
||||
projection_2d_.cols[0] = math::simd_float4::Load(2.f / _width, 0.f, 0.f, 0.f);
|
||||
projection_2d_.cols[1] =
|
||||
math::simd_float4::Load(0.f, 2.f / _height, 0.f, 0.f);
|
||||
projection_2d_.cols[2] = math::simd_float4::Load(0.f, 0.f, -2.0f, 0.f);
|
||||
projection_2d_.cols[3] = math::simd_float4::Load(-1.f, -1.f, 0.f, 1.f);
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,136 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_CAMERA_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_CAMERA_H_
|
||||
|
||||
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#error "This header is private, it cannot be included from public headers."
|
||||
#endif // OZZ_INCLUDE_PRIVATE_HEADER
|
||||
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace math {
|
||||
struct Box;
|
||||
}
|
||||
namespace sample {
|
||||
class ImGui;
|
||||
namespace internal {
|
||||
|
||||
// Framework internal implementation of an OpenGL/glfw camera system that can be
|
||||
// manipulated with the mouse and some shortcuts.
|
||||
class Camera {
|
||||
public:
|
||||
// Initializes camera to its default framing.
|
||||
Camera();
|
||||
|
||||
// Destructor.
|
||||
~Camera();
|
||||
|
||||
// Updates camera framing: mouse manipulation, timed transitions...
|
||||
// Returns actions that the user applied to the camera during the frame.
|
||||
void Update(const math::Box& _box, float _delta_time, bool _first_frame);
|
||||
|
||||
// Updates camera location, overriding user inputs.
|
||||
// Returns actions that the user applied to the camera during the frame.
|
||||
void Update(const math::Float4x4& _transform, const math::Box& _box,
|
||||
float _delta_time, bool _first_frame);
|
||||
|
||||
// Resets camera center, angles and distance.
|
||||
void Reset(const math::Float3& _center, const math::Float2& _angles,
|
||||
float _distance);
|
||||
|
||||
// Provides immediate mode gui display event.
|
||||
void OnGui(ImGui* _im_gui);
|
||||
|
||||
// Binds 3d projection and view matrices to the current matrix.
|
||||
void Bind3D();
|
||||
|
||||
// Binds 2d projection and view matrices to the current matrix.
|
||||
void Bind2D();
|
||||
|
||||
// Resize notification, used to rebuild projection matrix.
|
||||
void Resize(int _width, int _height);
|
||||
|
||||
// Get the current projection matrix.
|
||||
const math::Float4x4& projection() { return projection_; }
|
||||
|
||||
// Get the current model-view matrix.
|
||||
const math::Float4x4& view() { return view_; }
|
||||
|
||||
// Get the current model-view-projection matrix.
|
||||
const math::Float4x4& view_proj() { return view_proj_; }
|
||||
|
||||
// Set to true to automatically frame the camera on the whole scene.
|
||||
void set_auto_framing(bool _auto) { auto_framing_ = _auto; }
|
||||
// Get auto framing state.
|
||||
bool auto_framing() const { return auto_framing_; }
|
||||
|
||||
private:
|
||||
struct Controls {
|
||||
bool zooming;
|
||||
bool zooming_wheel;
|
||||
bool rotating;
|
||||
bool panning;
|
||||
};
|
||||
Controls UpdateControls(float _delta_time);
|
||||
|
||||
// The current projection matrix.
|
||||
math::Float4x4 projection_;
|
||||
|
||||
// The current projection matrix.
|
||||
math::Float4x4 projection_2d_;
|
||||
|
||||
// The current model-view matrix.
|
||||
math::Float4x4 view_;
|
||||
|
||||
// The current model-view-projection matrix.
|
||||
math::Float4x4 view_proj_;
|
||||
|
||||
// The angles in degree of the camera rotation around x and y axes.
|
||||
math::Float2 angles_;
|
||||
|
||||
// The center of the rotation.
|
||||
math::Float3 center_;
|
||||
|
||||
// The view distance, from the center of rotation.
|
||||
float distance_;
|
||||
|
||||
// The position of the mouse, the last time it has been seen.
|
||||
int mouse_last_x_;
|
||||
int mouse_last_y_;
|
||||
int mouse_last_wheel_;
|
||||
|
||||
// Set to true to automatically frame the camera on the whole scene.
|
||||
bool auto_framing_;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_CAMERA_H_
|
||||
@@ -0,0 +1,171 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_ICOSPHERE_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_ICOSPHERE_H_
|
||||
|
||||
#include "ozz/base/platform.h"
|
||||
|
||||
// Precomputed normalized icosphere with 2 levels of subdivisions.
|
||||
// Note that kVertices can also be used as normals.
|
||||
namespace icosphere {
|
||||
|
||||
static const float kVertices[] = {
|
||||
-0.682f, 0.717f, 0.148f, -0.588f, 0.688f, 0.425f, -0.443f, 0.864f,
|
||||
0.239f, -0.526f, 0.851f, 0.000f, -0.809f, 0.500f, 0.309f, -0.309f,
|
||||
0.809f, 0.500f, -0.717f, 0.148f, 0.682f, -0.688f, 0.425f, 0.588f,
|
||||
-0.864f, 0.239f, 0.443f, -0.851f, 0.000f, 0.526f, -0.500f, 0.309f,
|
||||
0.809f, -0.148f, 0.682f, 0.717f, -0.425f, 0.588f, 0.688f, -0.239f,
|
||||
0.443f, 0.864f, 0.000f, 0.526f, 0.851f, -0.162f, 0.951f, 0.263f,
|
||||
-0.295f, 0.955f, 0.000f, 0.000f, 1.000f, 0.000f, 0.148f, 0.682f,
|
||||
0.717f, 0.000f, 0.851f, 0.526f, 0.309f, 0.809f, 0.500f, 0.295f,
|
||||
0.955f, 0.000f, 0.162f, 0.951f, 0.263f, 0.443f, 0.864f, 0.239f,
|
||||
0.526f, 0.851f, 0.000f, -0.162f, 0.951f, -0.263f, -0.443f, 0.864f,
|
||||
-0.239f, -0.309f, 0.809f, -0.500f, 0.443f, 0.864f, -0.239f, 0.162f,
|
||||
0.951f, -0.263f, 0.309f, 0.809f, -0.500f, -0.148f, 0.682f, -0.717f,
|
||||
0.000f, 0.851f, -0.526f, 0.148f, 0.682f, -0.717f, 0.000f, 0.526f,
|
||||
-0.851f, -0.588f, 0.688f, -0.425f, -0.682f, 0.717f, -0.148f, -0.809f,
|
||||
0.500f, -0.309f, -0.239f, 0.443f, -0.864f, -0.425f, 0.588f, -0.688f,
|
||||
-0.500f, 0.309f, -0.809f, -0.864f, 0.239f, -0.443f, -0.688f, 0.425f,
|
||||
-0.588f, -0.717f, 0.148f, -0.682f, -0.851f, 0.000f, -0.526f, -0.851f,
|
||||
0.526f, 0.000f, -0.955f, 0.000f, -0.295f, -0.951f, 0.263f, -0.162f,
|
||||
-1.000f, 0.000f, 0.000f, -0.951f, 0.263f, 0.162f, -0.955f, 0.000f,
|
||||
0.295f, 0.588f, 0.688f, 0.425f, 0.682f, 0.717f, 0.148f, 0.809f,
|
||||
0.500f, 0.309f, 0.239f, 0.443f, 0.864f, 0.425f, 0.588f, 0.688f,
|
||||
0.500f, 0.309f, 0.809f, 0.864f, 0.239f, 0.443f, 0.688f, 0.425f,
|
||||
0.588f, 0.717f, 0.148f, 0.682f, 0.851f, 0.000f, 0.526f, -0.263f,
|
||||
0.162f, 0.951f, 0.000f, 0.295f, 0.955f, 0.000f, 0.000f, 1.000f,
|
||||
-0.717f, -0.148f, 0.682f, -0.526f, 0.000f, 0.851f, -0.500f, -0.309f,
|
||||
0.809f, 0.000f, -0.295f, 0.955f, -0.263f, -0.162f, 0.951f, -0.239f,
|
||||
-0.443f, 0.864f, 0.000f, -0.526f, 0.851f, -0.951f, -0.263f, 0.162f,
|
||||
-0.864f, -0.239f, 0.443f, -0.809f, -0.500f, 0.309f, -0.864f, -0.239f,
|
||||
-0.443f, -0.951f, -0.263f, -0.162f, -0.809f, -0.500f, -0.309f, -0.682f,
|
||||
-0.717f, 0.148f, -0.851f, -0.526f, 0.000f, -0.682f, -0.717f, -0.148f,
|
||||
-0.526f, -0.851f, 0.000f, -0.526f, 0.000f, -0.851f, -0.717f, -0.148f,
|
||||
-0.682f, -0.500f, -0.309f, -0.809f, 0.000f, 0.295f, -0.955f, -0.263f,
|
||||
0.162f, -0.951f, 0.000f, 0.000f, -1.000f, -0.239f, -0.443f, -0.864f,
|
||||
-0.263f, -0.162f, -0.951f, 0.000f, -0.295f, -0.955f, 0.000f, -0.526f,
|
||||
-0.851f, 0.425f, 0.588f, -0.688f, 0.239f, 0.443f, -0.864f, 0.500f,
|
||||
0.309f, -0.809f, 0.682f, 0.717f, -0.148f, 0.588f, 0.688f, -0.425f,
|
||||
0.809f, 0.500f, -0.309f, 0.717f, 0.148f, -0.682f, 0.688f, 0.425f,
|
||||
-0.588f, 0.864f, 0.239f, -0.443f, 0.851f, 0.000f, -0.526f, 0.682f,
|
||||
-0.717f, 0.148f, 0.588f, -0.688f, 0.425f, 0.443f, -0.864f, 0.239f,
|
||||
0.526f, -0.851f, 0.000f, 0.809f, -0.500f, 0.309f, 0.309f, -0.809f,
|
||||
0.500f, 0.717f, -0.148f, 0.682f, 0.688f, -0.425f, 0.588f, 0.864f,
|
||||
-0.239f, 0.443f, 0.500f, -0.309f, 0.809f, 0.148f, -0.682f, 0.717f,
|
||||
0.425f, -0.588f, 0.688f, 0.239f, -0.443f, 0.864f, 0.162f, -0.951f,
|
||||
0.263f, 0.295f, -0.955f, 0.000f, 0.000f, -1.000f, 0.000f, -0.148f,
|
||||
-0.682f, 0.717f, 0.000f, -0.851f, 0.526f, -0.309f, -0.809f, 0.500f,
|
||||
-0.295f, -0.955f, 0.000f, -0.162f, -0.951f, 0.263f, -0.443f, -0.864f,
|
||||
0.239f, 0.162f, -0.951f, -0.263f, 0.443f, -0.864f, -0.239f, 0.309f,
|
||||
-0.809f, -0.500f, -0.443f, -0.864f, -0.239f, -0.162f, -0.951f, -0.263f,
|
||||
-0.309f, -0.809f, -0.500f, 0.148f, -0.682f, -0.717f, 0.000f, -0.851f,
|
||||
-0.526f, -0.148f, -0.682f, -0.717f, 0.588f, -0.688f, -0.425f, 0.682f,
|
||||
-0.717f, -0.148f, 0.809f, -0.500f, -0.309f, 0.239f, -0.443f, -0.864f,
|
||||
0.425f, -0.588f, -0.688f, 0.500f, -0.309f, -0.809f, 0.864f, -0.239f,
|
||||
-0.443f, 0.688f, -0.425f, -0.588f, 0.717f, -0.148f, -0.682f, 0.851f,
|
||||
-0.526f, 0.000f, 0.955f, 0.000f, -0.295f, 0.951f, -0.263f, -0.162f,
|
||||
1.000f, 0.000f, 0.000f, 0.951f, -0.263f, 0.162f, 0.955f, 0.000f,
|
||||
0.295f, 0.263f, -0.162f, 0.951f, 0.526f, 0.000f, 0.851f, 0.263f,
|
||||
0.162f, 0.951f, -0.588f, -0.688f, 0.425f, -0.425f, -0.588f, 0.688f,
|
||||
-0.688f, -0.425f, 0.588f, -0.425f, -0.588f, -0.688f, -0.588f, -0.688f,
|
||||
-0.425f, -0.688f, -0.425f, -0.588f, 0.526f, 0.000f, -0.851f, 0.263f,
|
||||
-0.162f, -0.951f, 0.263f, 0.162f, -0.951f, 0.951f, 0.263f, 0.162f,
|
||||
0.951f, 0.263f, -0.162f, 0.851f, 0.526f, 0.000f};
|
||||
|
||||
static const int kNumVertices = OZZ_ARRAY_SIZE(kVertices);
|
||||
|
||||
static const uint16_t kIndices[] = {
|
||||
3, 0, 2, 4, 1, 0, 5, 2, 1, 0, 1, 2, 9, 6, 8,
|
||||
10, 7, 6, 4, 8, 7, 6, 7, 8, 14, 11, 13, 5, 12, 11,
|
||||
10, 13, 12, 11, 12, 13, 4, 7, 1, 10, 12, 7, 5, 1, 12,
|
||||
7, 12, 1, 3, 2, 16, 5, 15, 2, 17, 16, 15, 2, 15, 16,
|
||||
14, 18, 11, 20, 19, 18, 5, 11, 19, 18, 19, 11, 24, 21, 23,
|
||||
17, 22, 21, 20, 23, 22, 21, 22, 23, 5, 19, 15, 20, 22, 19,
|
||||
17, 15, 22, 19, 22, 15, 3, 16, 26, 17, 25, 16, 27, 26, 25,
|
||||
16, 25, 26, 24, 28, 21, 30, 29, 28, 17, 21, 29, 28, 29, 21,
|
||||
34, 31, 33, 27, 32, 31, 30, 33, 32, 31, 32, 33, 17, 29, 25,
|
||||
30, 32, 29, 27, 25, 32, 29, 32, 25, 3, 26, 36, 27, 35, 26,
|
||||
37, 36, 35, 26, 35, 36, 34, 38, 31, 40, 39, 38, 27, 31, 39,
|
||||
38, 39, 31, 44, 41, 43, 37, 42, 41, 40, 43, 42, 41, 42, 43,
|
||||
27, 39, 35, 40, 42, 39, 37, 35, 42, 39, 42, 35, 3, 36, 0,
|
||||
37, 45, 36, 4, 0, 45, 36, 45, 0, 44, 46, 41, 48, 47, 46,
|
||||
37, 41, 47, 46, 47, 41, 9, 8, 50, 4, 49, 8, 48, 50, 49,
|
||||
8, 49, 50, 37, 47, 45, 48, 49, 47, 4, 45, 49, 47, 49, 45,
|
||||
24, 23, 52, 20, 51, 23, 53, 52, 51, 23, 51, 52, 14, 54, 18,
|
||||
56, 55, 54, 20, 18, 55, 54, 55, 18, 60, 57, 59, 53, 58, 57,
|
||||
56, 59, 58, 57, 58, 59, 20, 55, 51, 56, 58, 55, 53, 51, 58,
|
||||
55, 58, 51, 14, 13, 62, 10, 61, 13, 63, 62, 61, 13, 61, 62,
|
||||
9, 64, 6, 66, 65, 64, 10, 6, 65, 64, 65, 6, 70, 67, 69,
|
||||
63, 68, 67, 66, 69, 68, 67, 68, 69, 10, 65, 61, 66, 68, 65,
|
||||
63, 61, 68, 65, 68, 61, 9, 50, 72, 48, 71, 50, 73, 72, 71,
|
||||
50, 71, 72, 44, 74, 46, 76, 75, 74, 48, 46, 75, 74, 75, 46,
|
||||
80, 77, 79, 73, 78, 77, 76, 79, 78, 77, 78, 79, 48, 75, 71,
|
||||
76, 78, 75, 73, 71, 78, 75, 78, 71, 44, 43, 82, 40, 81, 43,
|
||||
83, 82, 81, 43, 81, 82, 34, 84, 38, 86, 85, 84, 40, 38, 85,
|
||||
84, 85, 38, 90, 87, 89, 83, 88, 87, 86, 89, 88, 87, 88, 89,
|
||||
40, 85, 81, 86, 88, 85, 83, 81, 88, 85, 88, 81, 34, 33, 92,
|
||||
30, 91, 33, 93, 92, 91, 33, 91, 92, 24, 94, 28, 96, 95, 94,
|
||||
30, 28, 95, 94, 95, 28, 100, 97, 99, 93, 98, 97, 96, 99, 98,
|
||||
97, 98, 99, 30, 95, 91, 96, 98, 95, 93, 91, 98, 95, 98, 91,
|
||||
104, 101, 103, 105, 102, 101, 106, 103, 102, 101, 102, 103, 60, 107, 109,
|
||||
110, 108, 107, 105, 109, 108, 107, 108, 109, 70, 111, 113, 106, 112, 111,
|
||||
110, 113, 112, 111, 112, 113, 105, 108, 102, 110, 112, 108, 106, 102, 112,
|
||||
108, 112, 102, 104, 103, 115, 106, 114, 103, 116, 115, 114, 103, 114, 115,
|
||||
70, 117, 111, 119, 118, 117, 106, 111, 118, 117, 118, 111, 80, 120, 122,
|
||||
116, 121, 120, 119, 122, 121, 120, 121, 122, 106, 118, 114, 119, 121, 118,
|
||||
116, 114, 121, 118, 121, 114, 104, 115, 124, 116, 123, 115, 125, 124, 123,
|
||||
115, 123, 124, 80, 126, 120, 128, 127, 126, 116, 120, 127, 126, 127, 120,
|
||||
90, 129, 131, 125, 130, 129, 128, 131, 130, 129, 130, 131, 116, 127, 123,
|
||||
128, 130, 127, 125, 123, 130, 127, 130, 123, 104, 124, 133, 125, 132, 124,
|
||||
134, 133, 132, 124, 132, 133, 90, 135, 129, 137, 136, 135, 125, 129, 136,
|
||||
135, 136, 129, 100, 138, 140, 134, 139, 138, 137, 140, 139, 138, 139, 140,
|
||||
125, 136, 132, 137, 139, 136, 134, 132, 139, 136, 139, 132, 104, 133, 101,
|
||||
134, 141, 133, 105, 101, 141, 133, 141, 101, 100, 142, 138, 144, 143, 142,
|
||||
134, 138, 143, 142, 143, 138, 60, 109, 146, 105, 145, 109, 144, 146, 145,
|
||||
109, 145, 146, 134, 143, 141, 144, 145, 143, 105, 141, 145, 143, 145, 141,
|
||||
70, 113, 67, 110, 147, 113, 63, 67, 147, 113, 147, 67, 60, 59, 107,
|
||||
56, 148, 59, 110, 107, 148, 59, 148, 107, 14, 62, 54, 63, 149, 62,
|
||||
56, 54, 149, 62, 149, 54, 110, 148, 147, 56, 149, 148, 63, 147, 149,
|
||||
148, 149, 147, 80, 122, 77, 119, 150, 122, 73, 77, 150, 122, 150, 77,
|
||||
70, 69, 117, 66, 151, 69, 119, 117, 151, 69, 151, 117, 9, 72, 64,
|
||||
73, 152, 72, 66, 64, 152, 72, 152, 64, 119, 151, 150, 66, 152, 151,
|
||||
73, 150, 152, 151, 152, 150, 90, 131, 87, 128, 153, 131, 83, 87, 153,
|
||||
131, 153, 87, 80, 79, 126, 76, 154, 79, 128, 126, 154, 79, 154, 126,
|
||||
44, 82, 74, 83, 155, 82, 76, 74, 155, 82, 155, 74, 128, 154, 153,
|
||||
76, 155, 154, 83, 153, 155, 154, 155, 153, 100, 140, 97, 137, 156, 140,
|
||||
93, 97, 156, 140, 156, 97, 90, 89, 135, 86, 157, 89, 137, 135, 157,
|
||||
89, 157, 135, 34, 92, 84, 93, 158, 92, 86, 84, 158, 92, 158, 84,
|
||||
137, 157, 156, 86, 158, 157, 93, 156, 158, 157, 158, 156, 60, 146, 57,
|
||||
144, 159, 146, 53, 57, 159, 146, 159, 57, 100, 99, 142, 96, 160, 99,
|
||||
144, 142, 160, 99, 160, 142, 24, 52, 94, 53, 161, 52, 96, 94, 161,
|
||||
52, 161, 94, 144, 160, 159, 96, 161, 160, 53, 159, 161, 160, 161, 159};
|
||||
|
||||
static const int kNumIndices = OZZ_ARRAY_SIZE(kIndices);
|
||||
|
||||
} // namespace icosphere
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_ICOSPHERE_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,233 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMGUI_IMPL_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMGUI_IMPL_H_
|
||||
|
||||
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#error "This header is private, it cannot be included from public headers."
|
||||
#endif // OZZ_INCLUDE_PRIVATE_HEADER
|
||||
|
||||
// Implements immediate mode gui.
|
||||
// See imgui.h for details about function specifications.
|
||||
|
||||
#include "framework/imgui.h"
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/maths/rect.h"
|
||||
|
||||
#include "renderer_impl.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
class RendererImpl;
|
||||
|
||||
// Immediate mode gui implementation.
|
||||
class ImGuiImpl : public ImGui {
|
||||
public:
|
||||
ImGuiImpl();
|
||||
virtual ~ImGuiImpl();
|
||||
|
||||
// Input state.
|
||||
struct Inputs {
|
||||
// Default input values.
|
||||
Inputs() : mouse_x(0), mouse_y(0), lmb_pressed(false) {}
|
||||
|
||||
// Cursor x position. 0 indicates the screen left border.
|
||||
int mouse_x;
|
||||
// Cursor y position. 0 indicates the screen bottom border.
|
||||
int mouse_y;
|
||||
// Left mouse button state. true when the left mouse button is pressed.
|
||||
bool lmb_pressed;
|
||||
};
|
||||
|
||||
// Starts an imgui frame.
|
||||
// _inputs describes next frame inputs. It is internally copied.
|
||||
// _rect is the windows size.
|
||||
void BeginFrame(const Inputs& _inputs, const math::RectInt& _rect,
|
||||
RendererImpl* _renderer);
|
||||
|
||||
// Ends the current imgui frame.
|
||||
void EndFrame();
|
||||
|
||||
protected:
|
||||
// Starts ImGui interface implementation.
|
||||
// See imgui.h for virtual function specifications.
|
||||
|
||||
virtual void BeginContainer(const char* _title, const math::RectFloat* _rect,
|
||||
bool* _open, bool _constrain);
|
||||
|
||||
virtual void EndContainer();
|
||||
|
||||
virtual bool DoButton(const char* _label, bool _enabled, bool* _state);
|
||||
|
||||
virtual bool DoSlider(const char* _label, float _min, float _max,
|
||||
float* _value, float _pow, bool _enabled);
|
||||
|
||||
virtual bool DoSlider(const char* _label, int _min, int _max, int* _value,
|
||||
float _pow, bool _enabled);
|
||||
|
||||
virtual bool DoCheckBox(const char* _label, bool* _state, bool _enabled);
|
||||
|
||||
virtual bool DoRadioButton(int _ref, const char* _label, int* _value,
|
||||
bool _enabled);
|
||||
|
||||
virtual void DoLabel(const char* _label, Justification _justification,
|
||||
bool _single_line);
|
||||
|
||||
virtual void DoGraph(const char* _label, float _min, float _max, float _mean,
|
||||
const float* _value_cursor, const float* _value_begin,
|
||||
const float* _value_end);
|
||||
|
||||
private:
|
||||
// Computes the rect of a new widget to add.
|
||||
// Returns true if there is enough space for a new widget.
|
||||
bool AddWidget(float _height, math::RectFloat* _rect);
|
||||
|
||||
// Implements button logic.
|
||||
// Returns true if the button was clicked.
|
||||
bool ButtonLogic(const math::RectFloat& _rect, int _id, bool* _hot,
|
||||
bool* _active);
|
||||
|
||||
// Fills a rectangle with _rect coordinates. Draws rounded angles if _radius
|
||||
// is greater than 0.
|
||||
// If _texture id is not 0, then texture with id _texture is mapped using a
|
||||
// planar projection to the rect.
|
||||
void FillRect(const math::RectFloat& _rect, float _radius,
|
||||
const GLubyte _rgba[4]) const;
|
||||
void FillRect(const math::RectFloat& _rect, float _radius,
|
||||
const GLubyte _rgba[4],
|
||||
const ozz::math::Float4x4& _transform) const;
|
||||
|
||||
// Strokes a rectangle with _rect coordinates. Draws rounded angles if _radius
|
||||
// is greater than 0.
|
||||
void StrokeRect(const math::RectFloat& _rect, float _radius,
|
||||
const GLubyte _rgba[4]) const;
|
||||
void StrokeRect(const math::RectFloat& _rect, float _radius,
|
||||
const GLubyte _rgba[4],
|
||||
const ozz::math::Float4x4& _transform) const;
|
||||
|
||||
enum PrintLayout {
|
||||
kNorthWest,
|
||||
kNorth,
|
||||
kNorthEst,
|
||||
kWest,
|
||||
kMiddle,
|
||||
kEst,
|
||||
kSouthWest,
|
||||
kSouth,
|
||||
kSouthEst,
|
||||
};
|
||||
|
||||
// Print _text in _rect.
|
||||
float Print(const char* _text, const math::RectFloat& _rect,
|
||||
PrintLayout _layout, const GLubyte _rgba[4]) const;
|
||||
|
||||
// Initialize circle vertices.
|
||||
void InitializeCircle();
|
||||
|
||||
// Initializes and destroys the internal font rendering system.
|
||||
void InitalizeFont();
|
||||
void DestroyFont();
|
||||
|
||||
// ImGui state.
|
||||
|
||||
// Current frame inputs.
|
||||
Inputs inputs_;
|
||||
|
||||
// Internal states.
|
||||
// The hot item is the one that's below the mouse cursor.
|
||||
int hot_item_;
|
||||
|
||||
// The active item is the one being currently interacted with.
|
||||
int active_item_;
|
||||
|
||||
// The automatically generated widget identifier.
|
||||
int auto_gen_id_;
|
||||
|
||||
struct Container {
|
||||
// The current rect.
|
||||
math::RectFloat rect;
|
||||
|
||||
// The y offset of the top of the next widget in the current container.
|
||||
float offset_y;
|
||||
|
||||
// Constrains container height to the size used by controls in the
|
||||
// container.
|
||||
bool constrain;
|
||||
};
|
||||
|
||||
// Container stack.
|
||||
ozz::vector<Container> containers_;
|
||||
|
||||
// Round rendering.
|
||||
|
||||
// Defines the number of segments used by the precomputed circle.
|
||||
// Must be multiple of 4.
|
||||
static const int kCircleSegments = 8;
|
||||
|
||||
// Circle vertices coordinates (cosine/sinus)
|
||||
float circle_[kCircleSegments][2];
|
||||
|
||||
// Font rendering.
|
||||
|
||||
// Declares font's structure.
|
||||
struct Font {
|
||||
int texture_width; // width of the pow2 texture.
|
||||
int texture_height; // height of the pow2 texture.
|
||||
int image_width; // width of the image area in the texture.
|
||||
int image_height; // height of the image area in the texture.
|
||||
int glyph_height;
|
||||
int glyph_width;
|
||||
int glyph_count;
|
||||
unsigned char glyph_start; // ascii code of the first character.
|
||||
const unsigned char* pixels;
|
||||
size_t pixels_size;
|
||||
};
|
||||
|
||||
// Declares the font instance.
|
||||
static const Font font_;
|
||||
|
||||
// Pre-cooked glyphes uv and vertex coordinates.
|
||||
struct Glyph {
|
||||
float uv[4][2];
|
||||
float pos[4][2];
|
||||
};
|
||||
Glyph glyphes_[256];
|
||||
|
||||
// Glyph GL texture
|
||||
unsigned int glyph_texture_;
|
||||
|
||||
// Renderer, available between Begin/EndFrame
|
||||
RendererImpl* renderer_;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMGUI_IMPL_H_
|
||||
@@ -0,0 +1,116 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "immediate.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "camera.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
#include "renderer_impl.h"
|
||||
#include "shader.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
GlImmediateRenderer::GlImmediateRenderer(RendererImpl* _renderer)
|
||||
: vbo_(0), size_(0), renderer_(_renderer) {}
|
||||
|
||||
GlImmediateRenderer::~GlImmediateRenderer() {
|
||||
assert(size_ == 0 && "Immediate rendering still in use.");
|
||||
|
||||
if (vbo_) {
|
||||
GL(DeleteBuffers(1, &vbo_));
|
||||
vbo_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GlImmediateRenderer::Initialize() {
|
||||
GL(GenBuffers(1, &vbo_));
|
||||
|
||||
immediate_pc_shader = ImmediatePCShader::Build();
|
||||
if (!immediate_pc_shader) {
|
||||
return false;
|
||||
}
|
||||
immediate_ptc_shader = ImmediatePTCShader::Build();
|
||||
if (!immediate_ptc_shader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GlImmediateRenderer::Begin() {
|
||||
assert(size_ == 0 && "Immediate rendering already in use.");
|
||||
}
|
||||
|
||||
template <>
|
||||
void GlImmediateRenderer::End<VertexPC>(GLenum _mode,
|
||||
const ozz::math::Float4x4& _transform) {
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, vbo_));
|
||||
GL(BufferData(GL_ARRAY_BUFFER, size_, buffer_.data(), GL_STREAM_DRAW));
|
||||
|
||||
immediate_pc_shader->Bind(_transform, renderer_->camera()->view_proj(),
|
||||
sizeof(VertexPC), 0, sizeof(VertexPC), 12);
|
||||
|
||||
const int count = static_cast<int>(size_ / sizeof(VertexPC));
|
||||
GL(DrawArrays(_mode, 0, count));
|
||||
|
||||
immediate_pc_shader->Unbind();
|
||||
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
// Reset vertex count for the next call
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void GlImmediateRenderer::End<VertexPTC>(
|
||||
GLenum _mode, const ozz::math::Float4x4& _transform) {
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, vbo_));
|
||||
GL(BufferData(GL_ARRAY_BUFFER, size_, buffer_.data(), GL_STREAM_DRAW));
|
||||
|
||||
immediate_ptc_shader->Bind(_transform, renderer_->camera()->view_proj(),
|
||||
sizeof(VertexPTC), 0, sizeof(VertexPTC), 12,
|
||||
sizeof(VertexPTC), 20);
|
||||
|
||||
const int count = static_cast<int>(size_ / sizeof(VertexPTC));
|
||||
GL(DrawArrays(_mode, 0, count));
|
||||
|
||||
immediate_ptc_shader->Unbind();
|
||||
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
// Reset vertex count for the next call
|
||||
size_ = 0;
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,173 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMMEDIATE_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMMEDIATE_H_
|
||||
|
||||
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#error "This header is private, it cannot be included from public headers."
|
||||
#endif // OZZ_INCLUDE_PRIVATE_HEADER
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
#include "ozz/base/platform.h"
|
||||
#include "renderer_impl.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
class RendererImpl;
|
||||
class ImmediatePCShader;
|
||||
class ImmediatePNShader;
|
||||
class ImmediatePTCShader;
|
||||
|
||||
// Declares supported vertex formats.
|
||||
// Position + Color.
|
||||
struct VertexPC {
|
||||
float pos[3];
|
||||
GLubyte rgba[4];
|
||||
};
|
||||
|
||||
// Declares supported vertex formats.
|
||||
// Position + Normal.
|
||||
struct VertexPN {
|
||||
float pos[3];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
// Position + Texture coordinate + Color.
|
||||
struct VertexPTC {
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
GLubyte rgba[4];
|
||||
};
|
||||
|
||||
// Declares Immediate mode types.
|
||||
template <typename _Ty>
|
||||
class GlImmediate;
|
||||
typedef GlImmediate<VertexPC> GlImmediatePC;
|
||||
typedef GlImmediate<VertexPN> GlImmediatePN;
|
||||
typedef GlImmediate<VertexPTC> GlImmediatePTC;
|
||||
|
||||
// GL immedialte mode renderer.
|
||||
// Should be used with a GlImmediate object to push vertices to the renderer.
|
||||
class GlImmediateRenderer {
|
||||
public:
|
||||
GlImmediateRenderer(RendererImpl* _renderer);
|
||||
~GlImmediateRenderer();
|
||||
|
||||
// Initializes immediate mode renderer. Can fail.
|
||||
bool Initialize();
|
||||
|
||||
private:
|
||||
// GlImmediate is used to work with the renderer.
|
||||
template <typename _Ty>
|
||||
friend class GlImmediate;
|
||||
|
||||
// Begin stacking vertices.
|
||||
void Begin();
|
||||
|
||||
// End stacking vertices. Call GL rendering.
|
||||
template <typename _Ty>
|
||||
void End(GLenum _mode, const ozz::math::Float4x4& _transform);
|
||||
|
||||
// Push a new vertex to the buffer.
|
||||
template <typename _Ty>
|
||||
OZZ_INLINE void PushVertex(const _Ty& _vertex) {
|
||||
// Resize buffer if needed.
|
||||
const size_t new_size = size_ + sizeof(_Ty);
|
||||
if (new_size > buffer_.size()) {
|
||||
buffer_.resize(new_size);
|
||||
}
|
||||
|
||||
// Copy this last vertex.
|
||||
std::memcpy(buffer_.data() + size_, &_vertex, sizeof(_Ty));
|
||||
size_ = new_size;
|
||||
}
|
||||
|
||||
// The vertex object used by the renderer.
|
||||
GLuint vbo_;
|
||||
|
||||
// Buffer of vertices.
|
||||
ozz::vector<char> buffer_;
|
||||
|
||||
// Number of vertices.
|
||||
size_t size_;
|
||||
|
||||
// Immediate mode shaders;
|
||||
ozz::unique_ptr<ImmediatePCShader> immediate_pc_shader;
|
||||
ozz::unique_ptr<ImmediatePTCShader> immediate_ptc_shader;
|
||||
|
||||
// The renderer object.
|
||||
RendererImpl* renderer_;
|
||||
};
|
||||
|
||||
// RAII object that allows to push vertices to the imrender stack.
|
||||
template <typename _Ty>
|
||||
class GlImmediate {
|
||||
public:
|
||||
// Immediate object vertex format.
|
||||
typedef _Ty Vertex;
|
||||
|
||||
// Start a new immediate stack.
|
||||
GlImmediate(GlImmediateRenderer* _renderer, GLenum _mode,
|
||||
const ozz::math::Float4x4& _transform)
|
||||
: transform_(_transform), renderer_(_renderer), mode_(_mode) {
|
||||
renderer_->Begin();
|
||||
}
|
||||
|
||||
// End immediate vertex stacking, and renders all vertices.
|
||||
~GlImmediate() { renderer_->End<_Ty>(mode_, transform_); }
|
||||
|
||||
// Pushes a new vertex to the stack.
|
||||
OZZ_INLINE void PushVertex(const _Ty& _vertex) {
|
||||
renderer_->PushVertex(_vertex);
|
||||
}
|
||||
|
||||
private:
|
||||
// Non copyable.
|
||||
GlImmediate(const GlImmediate&);
|
||||
void operator=(const GlImmediate&);
|
||||
|
||||
// Transformation matrix.
|
||||
const ozz::math::Float4x4 transform_;
|
||||
|
||||
// Shared renderer.
|
||||
GlImmediateRenderer* renderer_;
|
||||
|
||||
// Draw array mode GL_POINTS, GL_LINE_STRIP, ...
|
||||
GLenum mode_;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_IMMEDIATE_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,332 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_RENDERER_IMPL_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_RENDERER_IMPL_H_
|
||||
|
||||
#ifndef OZZ_INCLUDE_PRIVATE_HEADER
|
||||
#error "This header is private, it cannot be included from public headers."
|
||||
#endif // OZZ_INCLUDE_PRIVATE_HEADER
|
||||
|
||||
// GL and GL ext requires that ptrdif_t is defined on APPLE platforms.
|
||||
#include <cstddef>
|
||||
|
||||
// Don't allow gl.h to automatically include glext.h
|
||||
#define GL_GLEXT_LEGACY
|
||||
|
||||
// Including glfw includes gl.h
|
||||
#include "GL/glfw.h"
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
// include features as core functions.
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#else // EMSCRIPTEN
|
||||
|
||||
// Detects already defined GL_VERSION and deduces required extensions.
|
||||
#ifndef GL_VERSION_1_5
|
||||
#define OZZ_GL_VERSION_1_5_EXT
|
||||
#endif // GL_VERSION_1_5
|
||||
#ifndef GL_VERSION_2_0
|
||||
#define OZZ_GL_VERSION_2_0_EXT
|
||||
#endif // GL_VERSION_2_0
|
||||
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
// Include features as extentions
|
||||
#include "GL/glext.h"
|
||||
|
||||
#include "framework/renderer.h"
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
|
||||
// Provides helper macro to test for glGetError on a gl call.
|
||||
#ifndef NDEBUG
|
||||
#define GL(_f) \
|
||||
do { \
|
||||
gl##_f; \
|
||||
GLenum error = glGetError(); \
|
||||
assert(error == GL_NO_ERROR); \
|
||||
\
|
||||
} while (void(0), 0)
|
||||
#else // NDEBUG
|
||||
#define GL(_f) gl##_f
|
||||
#endif // NDEBUG
|
||||
|
||||
// Convenient macro definition for specifying buffer offsets.
|
||||
#define GL_PTR_OFFSET(i) reinterpret_cast<void*>(static_cast<intptr_t>(i))
|
||||
|
||||
namespace ozz {
|
||||
namespace animation {
|
||||
class Skeleton;
|
||||
}
|
||||
namespace math {
|
||||
struct Float4x4;
|
||||
}
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
class Camera;
|
||||
class Shader;
|
||||
class SkeletonShader;
|
||||
class AmbientShader;
|
||||
class AmbientTexturedShader;
|
||||
class AmbientShaderInstanced;
|
||||
class GlImmediateRenderer;
|
||||
|
||||
// Implements Renderer interface.
|
||||
class RendererImpl : public Renderer {
|
||||
public:
|
||||
RendererImpl(Camera* _camera);
|
||||
virtual ~RendererImpl();
|
||||
|
||||
// See Renderer for all the details about the API.
|
||||
virtual bool Initialize();
|
||||
|
||||
virtual bool DrawAxes(const ozz::math::Float4x4& _transform);
|
||||
|
||||
virtual bool DrawGrid(int _cell_count, float _cell_size);
|
||||
|
||||
virtual bool DrawSkeleton(const animation::Skeleton& _skeleton,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints);
|
||||
|
||||
virtual bool DrawPosture(const animation::Skeleton& _skeleton,
|
||||
ozz::span<const ozz::math::Float4x4> _matrices,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints);
|
||||
|
||||
virtual bool DrawBoxIm(const ozz::math::Box& _box,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _colors[2]);
|
||||
|
||||
virtual bool DrawBoxShaded(const ozz::math::Box& _box,
|
||||
ozz::span<const ozz::math::Float4x4> _transforms,
|
||||
Color _color);
|
||||
|
||||
virtual bool DrawSphereIm(float _radius,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _color);
|
||||
|
||||
virtual bool DrawSphereShaded(
|
||||
float _radius, ozz::span<const ozz::math::Float4x4> _transforms,
|
||||
Color _color);
|
||||
|
||||
virtual bool DrawSkinnedMesh(const Mesh& _mesh,
|
||||
const span<math::Float4x4> _skinning_matrices,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Options& _options = Options());
|
||||
|
||||
virtual bool DrawMesh(const Mesh& _mesh,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Options& _options = Options());
|
||||
|
||||
virtual bool DrawSegment(const math::Float3& _begin, const math::Float3& _end,
|
||||
Color _color, const ozz::math::Float4x4& _transform);
|
||||
|
||||
virtual bool DrawVectors(ozz::span<const float> _positions,
|
||||
size_t _positions_stride,
|
||||
ozz::span<const float> _directions,
|
||||
size_t _directions_stride, int _num_vectors,
|
||||
float _vector_length, Color _color,
|
||||
const ozz::math::Float4x4& _transform);
|
||||
|
||||
virtual bool DrawBinormals(
|
||||
ozz::span<const float> _positions, size_t _positions_stride,
|
||||
ozz::span<const float> _normals, size_t _normals_stride,
|
||||
ozz::span<const float> _tangents, size_t _tangents_stride,
|
||||
ozz::span<const float> _handenesses, size_t _handenesses_stride,
|
||||
int _num_vectors, float _vector_length, Color _color,
|
||||
const ozz::math::Float4x4& _transform);
|
||||
|
||||
// Get GL immediate renderer implementation;
|
||||
GlImmediateRenderer* immediate_renderer() const { return immediate_.get(); }
|
||||
|
||||
// Get application camera that provides rendering matrices.
|
||||
Camera* camera() const { return camera_; }
|
||||
|
||||
private:
|
||||
// Defines the internal structure used to define a model.
|
||||
struct Model {
|
||||
Model();
|
||||
~Model();
|
||||
|
||||
GLuint vbo;
|
||||
GLenum mode;
|
||||
GLsizei count;
|
||||
ozz::unique_ptr<SkeletonShader> shader;
|
||||
};
|
||||
|
||||
// Detects and initializes all OpenGL extension.
|
||||
// Return true if all mandatory extensions were found.
|
||||
bool InitOpenGLExtensions();
|
||||
|
||||
// Initializes posture rendering.
|
||||
// Return true if initialization succeeded.
|
||||
bool InitPostureRendering();
|
||||
|
||||
// Initializes the checkered texture.
|
||||
// Return true if initialization succeeded.
|
||||
bool InitCheckeredTexture();
|
||||
|
||||
// Draw posture internal non-instanced rendering fall back implementation.
|
||||
void DrawPosture_Impl(const ozz::math::Float4x4& _transform,
|
||||
const float* _uniforms, int _instance_count,
|
||||
bool _draw_joints);
|
||||
|
||||
// Draw posture internal instanced rendering implementation.
|
||||
void DrawPosture_InstancedImpl(const ozz::math::Float4x4& _transform,
|
||||
const float* _uniforms, int _instance_count,
|
||||
bool _draw_joints);
|
||||
|
||||
// Array of matrices used to store model space matrices during DrawSkeleton
|
||||
// execution.
|
||||
ozz::vector<ozz::math::Float4x4> prealloc_models_;
|
||||
|
||||
// Application camera that provides rendering matrices.
|
||||
Camera* camera_;
|
||||
|
||||
// Bone and joint model objects.
|
||||
Model models_[2];
|
||||
|
||||
// Dynamic vbo used for arrays.
|
||||
GLuint dynamic_array_bo_;
|
||||
|
||||
// Dynamic vbo used for indices.
|
||||
GLuint dynamic_index_bo_;
|
||||
|
||||
// Volatile memory buffer that can be used within function scope.
|
||||
// Minimum alignment is 16 bytes.
|
||||
class ScratchBuffer {
|
||||
public:
|
||||
ScratchBuffer();
|
||||
~ScratchBuffer();
|
||||
|
||||
// Resizes the buffer to the new size and return the memory address.
|
||||
void* Resize(size_t _size);
|
||||
|
||||
private:
|
||||
void* buffer_;
|
||||
size_t size_;
|
||||
};
|
||||
ScratchBuffer scratch_buffer_;
|
||||
|
||||
// Immediate renderer implementation.
|
||||
ozz::unique_ptr<GlImmediateRenderer> immediate_;
|
||||
|
||||
// Ambient rendering shader.
|
||||
ozz::unique_ptr<AmbientShader> ambient_shader;
|
||||
ozz::unique_ptr<AmbientTexturedShader> ambient_textured_shader;
|
||||
ozz::unique_ptr<AmbientShaderInstanced> ambient_shader_instanced;
|
||||
|
||||
// Checkered texture
|
||||
unsigned int checkered_texture_;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
|
||||
// OpenGL 1.5 buffer object management functions, mandatory.
|
||||
#ifdef OZZ_GL_VERSION_1_5_EXT
|
||||
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
||||
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||
extern PFNGLGENBUFFERSPROC glGenBuffers;
|
||||
extern PFNGLISBUFFERPROC glIsBuffer;
|
||||
extern PFNGLBUFFERDATAPROC glBufferData;
|
||||
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||
extern PFNGLGETBUFFERSUBDATAPROC glGetBufferSubData;
|
||||
extern PFNGLMAPBUFFERPROC glMapBuffer;
|
||||
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||
extern PFNGLGETBUFFERPARAMETERIVPROC glGetBufferParameteriv;
|
||||
extern PFNGLGETBUFFERPOINTERVPROC glGetBufferPointerv;
|
||||
#endif // OZZ_GL_VERSION_1_5_EXT
|
||||
|
||||
// OpenGL 2.0 shader management functions, mandatory.
|
||||
#ifdef OZZ_GL_VERSION_2_0_EXT
|
||||
extern PFNGLATTACHSHADERPROC glAttachShader;
|
||||
extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
|
||||
extern PFNGLCOMPILESHADERPROC glCompileShader;
|
||||
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||
extern PFNGLCREATESHADERPROC glCreateShader;
|
||||
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||
extern PFNGLDELETESHADERPROC glDeleteShader;
|
||||
extern PFNGLDETACHSHADERPROC glDetachShader;
|
||||
extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
|
||||
extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
|
||||
extern PFNGLGETACTIVEATTRIBPROC glGetActiveAttrib;
|
||||
extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform;
|
||||
extern PFNGLGETATTACHEDSHADERSPROC glGetAttachedShaders;
|
||||
extern PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;
|
||||
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
extern PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||
extern PFNGLGETSHADERSOURCEPROC glGetShaderSource;
|
||||
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
extern PFNGLGETUNIFORMFVPROC glGetUniformfv;
|
||||
extern PFNGLGETUNIFORMIVPROC glGetUniformiv;
|
||||
extern PFNGLGETVERTEXATTRIBDVPROC glGetVertexAttribdv;
|
||||
extern PFNGLGETVERTEXATTRIBFVPROC glGetVertexAttribfv;
|
||||
extern PFNGLGETVERTEXATTRIBIVPROC glGetVertexAttribiv;
|
||||
extern PFNGLGETVERTEXATTRIBPOINTERVPROC glGetVertexAttribPointerv;
|
||||
extern PFNGLISPROGRAMPROC glIsProgram;
|
||||
extern PFNGLISSHADERPROC glIsShader;
|
||||
extern PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||
extern PFNGLSHADERSOURCEPROC glShaderSource;
|
||||
extern PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
extern PFNGLUNIFORM1FPROC glUniform1f;
|
||||
extern PFNGLUNIFORM2FPROC glUniform2f;
|
||||
extern PFNGLUNIFORM3FPROC glUniform3f;
|
||||
extern PFNGLUNIFORM4FPROC glUniform4f;
|
||||
extern PFNGLUNIFORM1IPROC glUniform1i;
|
||||
extern PFNGLUNIFORM2IPROC glUniform2i;
|
||||
extern PFNGLUNIFORM3IPROC glUniform3i;
|
||||
extern PFNGLUNIFORM4IPROC glUniform4i;
|
||||
extern PFNGLUNIFORM1FVPROC glUniform1fv;
|
||||
extern PFNGLUNIFORM2FVPROC glUniform2fv;
|
||||
extern PFNGLUNIFORM3FVPROC glUniform3fv;
|
||||
extern PFNGLUNIFORM4FVPROC glUniform4fv;
|
||||
extern PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv;
|
||||
extern PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv;
|
||||
extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
|
||||
extern PFNGLVALIDATEPROGRAMPROC glValidateProgram;
|
||||
extern PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f;
|
||||
extern PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv;
|
||||
extern PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f;
|
||||
extern PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv;
|
||||
extern PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f;
|
||||
extern PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv;
|
||||
extern PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f;
|
||||
extern PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv;
|
||||
extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
|
||||
#endif // OZZ_GL_VERSION_2_0_EXT
|
||||
|
||||
// OpenGL ARB_instanced_arrays extension, optional.
|
||||
extern bool GL_ARB_instanced_arrays_supported;
|
||||
extern PFNGLVERTEXATTRIBDIVISORARBPROC glVertexAttribDivisor_;
|
||||
extern PFNGLDRAWARRAYSINSTANCEDARBPROC glDrawArraysInstanced_;
|
||||
extern PFNGLDRAWELEMENTSINSTANCEDARBPROC glDrawElementsInstanced_;
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_RENDERER_IMPL_H_
|
||||
@@ -0,0 +1,766 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "shader.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
// WebGL requires to specify floating point precision
|
||||
static const char* kPlatformSpecivicVSHeader = "precision mediump float;\n";
|
||||
static const char* kPlatformSpecivicFSHeader = "precision mediump float;\n";
|
||||
#else // EMSCRIPTEN
|
||||
static const char* kPlatformSpecivicVSHeader = "";
|
||||
static const char* kPlatformSpecivicFSHeader = "";
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
Shader::Shader() : program_(0), vertex_(0), fragment_(0) {}
|
||||
|
||||
Shader::~Shader() {
|
||||
if (vertex_) {
|
||||
GL(DetachShader(program_, vertex_));
|
||||
GL(DeleteShader(vertex_));
|
||||
}
|
||||
if (fragment_) {
|
||||
GL(DetachShader(program_, fragment_));
|
||||
GL(DeleteShader(fragment_));
|
||||
}
|
||||
if (program_) {
|
||||
GL(DeleteProgram(program_));
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
GLuint CompileShader(GLenum _type, int _count, const char** _src) {
|
||||
GLuint shader = glCreateShader(_type);
|
||||
GL(ShaderSource(shader, _count, _src, nullptr));
|
||||
GL(CompileShader(shader));
|
||||
|
||||
int infolog_length = 0;
|
||||
GL(GetShaderiv(shader, GL_INFO_LOG_LENGTH, &infolog_length));
|
||||
if (infolog_length > 1) {
|
||||
char* info_log = reinterpret_cast<char*>(
|
||||
memory::default_allocator()->Allocate(infolog_length, alignof(char)));
|
||||
int chars_written = 0;
|
||||
glGetShaderInfoLog(shader, infolog_length, &chars_written, info_log);
|
||||
log::Err() << info_log << std::endl;
|
||||
memory::default_allocator()->Deallocate(info_log);
|
||||
}
|
||||
|
||||
int status;
|
||||
GL(GetShaderiv(shader, GL_COMPILE_STATUS, &status));
|
||||
if (status) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
GL(DeleteShader(shader));
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool Shader::BuildFromSource(int _vertex_count, const char** _vertex,
|
||||
int _fragment_count, const char** _fragment) {
|
||||
// Tries to compile shaders.
|
||||
GLuint vertex_shader = 0;
|
||||
if (_vertex) {
|
||||
vertex_shader = CompileShader(GL_VERTEX_SHADER, _vertex_count, _vertex);
|
||||
if (!vertex_shader) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
GLuint fragment_shader = 0;
|
||||
if (_fragment) {
|
||||
fragment_shader =
|
||||
CompileShader(GL_FRAGMENT_SHADER, _fragment_count, _fragment);
|
||||
if (!fragment_shader) {
|
||||
if (vertex_shader) {
|
||||
GL(DeleteShader(vertex_shader));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Shaders are compiled, builds program.
|
||||
program_ = glCreateProgram();
|
||||
vertex_ = vertex_shader;
|
||||
fragment_ = fragment_shader;
|
||||
GL(AttachShader(program_, vertex_shader));
|
||||
GL(AttachShader(program_, fragment_shader));
|
||||
GL(LinkProgram(program_));
|
||||
|
||||
int infolog_length = 0;
|
||||
GL(GetProgramiv(program_, GL_INFO_LOG_LENGTH, &infolog_length));
|
||||
if (infolog_length > 1) {
|
||||
char* info_log = reinterpret_cast<char*>(
|
||||
memory::default_allocator()->Allocate(infolog_length, alignof(char)));
|
||||
int chars_written = 0;
|
||||
glGetProgramInfoLog(program_, infolog_length, &chars_written, info_log);
|
||||
log::Err() << info_log << std::endl;
|
||||
memory::default_allocator()->Deallocate(info_log);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Shader::BindUniform(const char* _semantic) {
|
||||
if (!program_) {
|
||||
return false;
|
||||
}
|
||||
GLint location = glGetUniformLocation(program_, _semantic);
|
||||
if (glGetError() != GL_NO_ERROR || location == -1) { // _semantic not found.
|
||||
return false;
|
||||
}
|
||||
uniforms_.push_back(location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Shader::FindAttrib(const char* _semantic) {
|
||||
if (!program_) {
|
||||
return false;
|
||||
}
|
||||
GLint location = glGetAttribLocation(program_, _semantic);
|
||||
if (glGetError() != GL_NO_ERROR || location == -1) { // _semantic not found.
|
||||
return false;
|
||||
}
|
||||
attribs_.push_back(location);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shader::UnbindAttribs() {
|
||||
for (size_t i = 0; i < attribs_.size(); ++i) {
|
||||
GL(DisableVertexAttribArray(attribs_[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void Shader::Unbind() {
|
||||
UnbindAttribs();
|
||||
GL(UseProgram(0));
|
||||
}
|
||||
|
||||
ozz::unique_ptr<ImmediatePCShader> ImmediatePCShader::Build() {
|
||||
bool success = true;
|
||||
|
||||
const char* kSimplePCVS =
|
||||
"uniform mat4 u_mvp;\n"
|
||||
"attribute vec3 a_position;\n"
|
||||
"attribute vec4 a_color;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" vec4 vertex = vec4(a_position.xyz, 1.);\n"
|
||||
" gl_Position = u_mvp * vertex;\n"
|
||||
" v_vertex_color = a_color;\n"
|
||||
"}\n";
|
||||
const char* kSimplePCPS =
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" gl_FragColor = v_vertex_color;\n"
|
||||
"}\n";
|
||||
|
||||
const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePCVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePCPS};
|
||||
|
||||
ozz::unique_ptr<ImmediatePCShader> shader = make_unique<ImmediatePCShader>();
|
||||
success &=
|
||||
shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
// Binds default attributes
|
||||
success &= shader->FindAttrib("a_position");
|
||||
success &= shader->FindAttrib("a_color");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void ImmediatePCShader::Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset,
|
||||
GLsizei _color_stride, GLsizei _color_offset) {
|
||||
GL(UseProgram(program()));
|
||||
|
||||
const GLint position_attrib = attrib(0);
|
||||
GL(EnableVertexAttribArray(position_attrib));
|
||||
GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
|
||||
GL_PTR_OFFSET(_pos_offset)));
|
||||
|
||||
const GLint color_attrib = attrib(1);
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
|
||||
// Binds mvp uniform
|
||||
const GLint mvp_uniform = uniform(0);
|
||||
const ozz::math::Float4x4 mvp = _view_proj * _model;
|
||||
float values[16];
|
||||
math::StorePtrU(mvp.cols[0], values + 0);
|
||||
math::StorePtrU(mvp.cols[1], values + 4);
|
||||
math::StorePtrU(mvp.cols[2], values + 8);
|
||||
math::StorePtrU(mvp.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
|
||||
}
|
||||
|
||||
ozz::unique_ptr<ImmediatePTCShader> ImmediatePTCShader::Build() {
|
||||
bool success = true;
|
||||
|
||||
const char* kSimplePCVS =
|
||||
"uniform mat4 u_mvp;\n"
|
||||
"attribute vec3 a_position;\n"
|
||||
"attribute vec2 a_tex_coord;\n"
|
||||
"attribute vec4 a_color;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"varying vec2 v_texture_coord;\n"
|
||||
"void main() {\n"
|
||||
" vec4 vertex = vec4(a_position.xyz, 1.);\n"
|
||||
" gl_Position = u_mvp * vertex;\n"
|
||||
" v_vertex_color = a_color;\n"
|
||||
" v_texture_coord = a_tex_coord;\n"
|
||||
"}\n";
|
||||
const char* kSimplePCPS =
|
||||
"uniform sampler2D u_texture;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"varying vec2 v_texture_coord;\n"
|
||||
"void main() {\n"
|
||||
" vec4 tex_color = texture2D(u_texture, v_texture_coord);\n"
|
||||
" gl_FragColor = v_vertex_color * tex_color;\n"
|
||||
" if(gl_FragColor.a < .01) discard;\n" // Implements alpha testing.
|
||||
"}\n";
|
||||
|
||||
const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePCVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePCPS};
|
||||
|
||||
ozz::unique_ptr<ImmediatePTCShader> shader =
|
||||
make_unique<ImmediatePTCShader>();
|
||||
success &=
|
||||
shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
// Binds default attributes
|
||||
success &= shader->FindAttrib("a_position");
|
||||
success &= shader->FindAttrib("a_tex_coord");
|
||||
success &= shader->FindAttrib("a_color");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
success &= shader->BindUniform("u_texture");
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void ImmediatePTCShader::Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset,
|
||||
GLsizei _tex_stride, GLsizei _tex_offset,
|
||||
GLsizei _color_stride, GLsizei _color_offset) {
|
||||
GL(UseProgram(program()));
|
||||
|
||||
const GLint position_attrib = attrib(0);
|
||||
GL(EnableVertexAttribArray(position_attrib));
|
||||
GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
|
||||
GL_PTR_OFFSET(_pos_offset)));
|
||||
|
||||
const GLint tex_attrib = attrib(1);
|
||||
GL(EnableVertexAttribArray(tex_attrib));
|
||||
GL(VertexAttribPointer(tex_attrib, 2, GL_FLOAT, GL_FALSE, _tex_stride,
|
||||
GL_PTR_OFFSET(_tex_offset)));
|
||||
|
||||
const GLint color_attrib = attrib(2);
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
|
||||
// Binds mvp uniform
|
||||
const GLint mvp_uniform = uniform(0);
|
||||
const ozz::math::Float4x4 mvp = _view_proj * _model;
|
||||
float values[16];
|
||||
math::StorePtrU(mvp.cols[0], values + 0);
|
||||
math::StorePtrU(mvp.cols[1], values + 4);
|
||||
math::StorePtrU(mvp.cols[2], values + 8);
|
||||
math::StorePtrU(mvp.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
|
||||
|
||||
// Binds texture
|
||||
const GLint texture = uniform(1);
|
||||
GL(Uniform1i(texture, 0));
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char* kPassUv =
|
||||
"attribute vec2 a_uv;\n"
|
||||
"varying vec2 v_vertex_uv;\n"
|
||||
"void PassUv() {\n"
|
||||
" v_vertex_uv = a_uv;\n"
|
||||
"}\n";
|
||||
const char* kPassNoUv =
|
||||
"void PassUv() {\n"
|
||||
"}\n";
|
||||
const char* kShaderUberVS =
|
||||
"uniform mat4 u_mvp;\n"
|
||||
"attribute vec3 a_position;\n"
|
||||
"attribute vec3 a_normal;\n"
|
||||
"attribute vec4 a_color;\n"
|
||||
"varying vec3 v_world_normal;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" mat4 world_matrix = GetWorldMatrix();\n"
|
||||
" vec4 vertex = vec4(a_position.xyz, 1.);\n"
|
||||
" gl_Position = u_mvp * world_matrix * vertex;\n"
|
||||
" mat3 cross_matrix = mat3(\n"
|
||||
" cross(world_matrix[1].xyz, world_matrix[2].xyz),\n"
|
||||
" cross(world_matrix[2].xyz, world_matrix[0].xyz),\n"
|
||||
" cross(world_matrix[0].xyz, world_matrix[1].xyz));\n"
|
||||
" float invdet = 1.0 / dot(cross_matrix[2], world_matrix[2].xyz);\n"
|
||||
" mat3 normal_matrix = cross_matrix * invdet;\n"
|
||||
" v_world_normal = normal_matrix * a_normal;\n"
|
||||
" v_vertex_color = a_color;\n"
|
||||
" PassUv();\n"
|
||||
"}\n";
|
||||
const char* kShaderAmbientFct =
|
||||
"vec4 GetAmbient(vec3 _world_normal) {\n"
|
||||
" vec3 normal = normalize(_world_normal);\n"
|
||||
" vec3 alpha = (normal + 1.) * .5;\n"
|
||||
" vec2 bt = mix(vec2(.3, .7), vec2(.4, .8), alpha.xz);\n"
|
||||
" vec3 ambient = mix(vec3(bt.x, .3, bt.x), vec3(bt.y, .8, bt.y), "
|
||||
"alpha.y);\n"
|
||||
" return vec4(ambient, 1.);\n"
|
||||
"}\n";
|
||||
const char* kShaderAmbientFS =
|
||||
"varying vec3 v_world_normal;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" vec4 ambient = GetAmbient(v_world_normal);\n"
|
||||
" gl_FragColor = ambient *\n"
|
||||
" v_vertex_color;\n"
|
||||
"}\n";
|
||||
const char* kShaderAmbientTexturedFS =
|
||||
"uniform sampler2D u_texture;\n"
|
||||
"varying vec3 v_world_normal;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"varying vec2 v_vertex_uv;\n"
|
||||
"void main() {\n"
|
||||
" vec4 ambient = GetAmbient(v_world_normal);\n"
|
||||
" gl_FragColor = ambient *\n"
|
||||
" v_vertex_color *\n"
|
||||
" texture2D(u_texture, v_vertex_uv);\n"
|
||||
"}\n";
|
||||
} // namespace
|
||||
|
||||
void SkeletonShader::Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj, GLsizei _pos_stride,
|
||||
GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset) {
|
||||
GL(UseProgram(program()));
|
||||
|
||||
const GLint position_attrib = attrib(0);
|
||||
GL(EnableVertexAttribArray(position_attrib));
|
||||
GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
|
||||
GL_PTR_OFFSET(_pos_offset)));
|
||||
|
||||
const GLint normal_attrib = attrib(1);
|
||||
GL(EnableVertexAttribArray(normal_attrib));
|
||||
GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_FALSE, _normal_stride,
|
||||
GL_PTR_OFFSET(_normal_offset)));
|
||||
|
||||
const GLint color_attrib = attrib(2);
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
|
||||
// Binds mvp uniform
|
||||
const GLint mvp_uniform = uniform(0);
|
||||
const ozz::math::Float4x4 mvp = _view_proj * _model;
|
||||
float values[16];
|
||||
math::StorePtrU(mvp.cols[0], values + 0);
|
||||
math::StorePtrU(mvp.cols[1], values + 4);
|
||||
math::StorePtrU(mvp.cols[2], values + 8);
|
||||
math::StorePtrU(mvp.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
|
||||
}
|
||||
|
||||
ozz::unique_ptr<JointShader> JointShader::Build() {
|
||||
bool success = true;
|
||||
|
||||
const char* vs_joint_to_world_matrix =
|
||||
"mat4 GetWorldMatrix() {\n"
|
||||
" // Rebuilds joint matrix.\n"
|
||||
" mat4 joint_matrix;\n"
|
||||
" joint_matrix[0] = vec4(normalize(joint[0].xyz), 0.);\n"
|
||||
" joint_matrix[1] = vec4(normalize(joint[1].xyz), 0.);\n"
|
||||
" joint_matrix[2] = vec4(normalize(joint[2].xyz), 0.);\n"
|
||||
" joint_matrix[3] = vec4(joint[3].xyz, 1.);\n"
|
||||
|
||||
" // Rebuilds bone properties.\n"
|
||||
" vec3 bone_dir = vec3(joint[0].w, joint[1].w, joint[2].w);\n"
|
||||
" float bone_len = length(bone_dir);\n"
|
||||
|
||||
" // Setup rendering world matrix.\n"
|
||||
" mat4 world_matrix;\n"
|
||||
" world_matrix[0] = joint_matrix[0] * bone_len;\n"
|
||||
" world_matrix[1] = joint_matrix[1] * bone_len;\n"
|
||||
" world_matrix[2] = joint_matrix[2] * bone_len;\n"
|
||||
" world_matrix[3] = joint_matrix[3];\n"
|
||||
" return world_matrix;\n"
|
||||
"}\n";
|
||||
const char* vs[] = {kPlatformSpecivicVSHeader, kPassNoUv,
|
||||
GL_ARB_instanced_arrays_supported
|
||||
? "attribute mat4 joint;\n"
|
||||
: "uniform mat4 joint;\n",
|
||||
vs_joint_to_world_matrix, kShaderUberVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
|
||||
kShaderAmbientFS};
|
||||
|
||||
ozz::unique_ptr<JointShader> shader = make_unique<JointShader>();
|
||||
success &=
|
||||
shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
// Binds default attributes
|
||||
success &= shader->FindAttrib("a_position");
|
||||
success &= shader->FindAttrib("a_normal");
|
||||
success &= shader->FindAttrib("a_color");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
|
||||
if (GL_ARB_instanced_arrays_supported) {
|
||||
success &= shader->FindAttrib("joint");
|
||||
} else {
|
||||
success &= shader->BindUniform("joint");
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
ozz::unique_ptr<BoneShader>
|
||||
BoneShader::Build() { // Builds a world matrix from joint uniforms,
|
||||
// sticking bone model between
|
||||
bool success = true;
|
||||
|
||||
// parent and child joints.
|
||||
const char* vs_joint_to_world_matrix =
|
||||
"mat4 GetWorldMatrix() {\n"
|
||||
" // Rebuilds bone properties.\n"
|
||||
" // Bone length is set to zero to disable leaf rendering.\n"
|
||||
" float is_bone = joint[3].w;\n"
|
||||
" vec3 bone_dir = vec3(joint[0].w, joint[1].w, joint[2].w) * is_bone;\n"
|
||||
" float bone_len = length(bone_dir);\n"
|
||||
|
||||
" // Setup rendering world matrix.\n"
|
||||
" float dot1 = dot(joint[2].xyz, bone_dir);\n"
|
||||
" float dot2 = dot(joint[0].xyz, bone_dir);\n"
|
||||
" vec3 binormal = abs(dot1) < abs(dot2) ? joint[2].xyz : joint[0].xyz;\n"
|
||||
|
||||
" mat4 world_matrix;\n"
|
||||
" world_matrix[0] = vec4(bone_dir, 0.);\n"
|
||||
" world_matrix[1] = \n"
|
||||
" vec4(bone_len * normalize(cross(binormal, bone_dir)), 0.);\n"
|
||||
" world_matrix[2] =\n"
|
||||
" vec4(bone_len * normalize(cross(bone_dir, world_matrix[1].xyz)), "
|
||||
"0.);\n"
|
||||
" world_matrix[3] = vec4(joint[3].xyz, 1.);\n"
|
||||
" return world_matrix;\n"
|
||||
"}\n";
|
||||
const char* vs[] = {kPlatformSpecivicVSHeader, kPassNoUv,
|
||||
GL_ARB_instanced_arrays_supported
|
||||
? "attribute mat4 joint;\n"
|
||||
: "uniform mat4 joint;\n",
|
||||
vs_joint_to_world_matrix, kShaderUberVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
|
||||
kShaderAmbientFS};
|
||||
|
||||
ozz::unique_ptr<BoneShader> shader = make_unique<BoneShader>();
|
||||
success &=
|
||||
shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
// Binds default attributes
|
||||
success &= shader->FindAttrib("a_position");
|
||||
success &= shader->FindAttrib("a_normal");
|
||||
success &= shader->FindAttrib("a_color");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
|
||||
if (GL_ARB_instanced_arrays_supported) {
|
||||
success &= shader->FindAttrib("joint");
|
||||
} else {
|
||||
success &= shader->BindUniform("joint");
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
ozz::unique_ptr<AmbientShader> AmbientShader::Build() {
|
||||
const char* vs[] = {
|
||||
kPlatformSpecivicVSHeader, kPassNoUv,
|
||||
"uniform mat4 u_mw;\n mat4 GetWorldMatrix() {return u_mw;}\n",
|
||||
kShaderUberVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
|
||||
kShaderAmbientFS};
|
||||
|
||||
ozz::unique_ptr<AmbientShader> shader = make_unique<AmbientShader>();
|
||||
bool success =
|
||||
shader->InternalBuild(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
bool AmbientShader::InternalBuild(int _vertex_count, const char** _vertex,
|
||||
int _fragment_count, const char** _fragment) {
|
||||
bool success = true;
|
||||
|
||||
success &=
|
||||
BuildFromSource(_vertex_count, _vertex, _fragment_count, _fragment);
|
||||
|
||||
// Binds default attributes
|
||||
success &= FindAttrib("a_position");
|
||||
success &= FindAttrib("a_normal");
|
||||
success &= FindAttrib("a_color");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= BindUniform("u_mw");
|
||||
success &= BindUniform("u_mvp");
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void AmbientShader::Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj, GLsizei _pos_stride,
|
||||
GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset) {
|
||||
GL(UseProgram(program()));
|
||||
|
||||
const GLint position_attrib = attrib(0);
|
||||
GL(EnableVertexAttribArray(position_attrib));
|
||||
GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
|
||||
GL_PTR_OFFSET(_pos_offset)));
|
||||
|
||||
const GLint normal_attrib = attrib(1);
|
||||
GL(EnableVertexAttribArray(normal_attrib));
|
||||
GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_TRUE, _normal_stride,
|
||||
GL_PTR_OFFSET(_normal_offset)));
|
||||
|
||||
const GLint color_attrib = attrib(2);
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
|
||||
// Binds mw uniform
|
||||
float values[16];
|
||||
const GLint mw_uniform = uniform(0);
|
||||
math::StorePtrU(_model.cols[0], values + 0);
|
||||
math::StorePtrU(_model.cols[1], values + 4);
|
||||
math::StorePtrU(_model.cols[2], values + 8);
|
||||
math::StorePtrU(_model.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mw_uniform, 1, false, values));
|
||||
|
||||
// Binds mvp uniform
|
||||
const GLint mvp_uniform = uniform(1);
|
||||
math::StorePtrU(_view_proj.cols[0], values + 0);
|
||||
math::StorePtrU(_view_proj.cols[1], values + 4);
|
||||
math::StorePtrU(_view_proj.cols[2], values + 8);
|
||||
math::StorePtrU(_view_proj.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
|
||||
}
|
||||
|
||||
ozz::unique_ptr<AmbientShaderInstanced> AmbientShaderInstanced::Build() {
|
||||
bool success = true;
|
||||
|
||||
const char* vs[] = {
|
||||
kPlatformSpecivicVSHeader, kPassNoUv,
|
||||
"attribute mat4 a_mw;\n mat4 GetWorldMatrix() {return a_mw;}\n",
|
||||
kShaderUberVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
|
||||
kShaderAmbientFS};
|
||||
|
||||
ozz::unique_ptr<AmbientShaderInstanced> shader =
|
||||
make_unique<AmbientShaderInstanced>();
|
||||
success &=
|
||||
shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
// Binds default attributes
|
||||
success &= shader->FindAttrib("a_position");
|
||||
success &= shader->FindAttrib("a_normal");
|
||||
success &= shader->FindAttrib("a_color");
|
||||
success &= shader->FindAttrib("a_mw");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void AmbientShaderInstanced::Bind(GLsizei _models_offset,
|
||||
const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset,
|
||||
GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset) {
|
||||
GL(UseProgram(program()));
|
||||
|
||||
const GLint position_attrib = attrib(0);
|
||||
GL(EnableVertexAttribArray(position_attrib));
|
||||
GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
|
||||
GL_PTR_OFFSET(_pos_offset)));
|
||||
|
||||
const GLint normal_attrib = attrib(1);
|
||||
GL(EnableVertexAttribArray(normal_attrib));
|
||||
GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_TRUE, _normal_stride,
|
||||
GL_PTR_OFFSET(_normal_offset)));
|
||||
|
||||
const GLint color_attrib = attrib(2);
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
if (_color_stride == 0) {
|
||||
GL(VertexAttribDivisor_(color_attrib,
|
||||
std::numeric_limits<unsigned int>::max()));
|
||||
}
|
||||
|
||||
// Binds mw uniform
|
||||
const GLint models_attrib = attrib(3);
|
||||
GL(EnableVertexAttribArray(models_attrib + 0));
|
||||
GL(EnableVertexAttribArray(models_attrib + 1));
|
||||
GL(EnableVertexAttribArray(models_attrib + 2));
|
||||
GL(EnableVertexAttribArray(models_attrib + 3));
|
||||
GL(VertexAttribDivisor_(models_attrib + 0, 1));
|
||||
GL(VertexAttribDivisor_(models_attrib + 1, 1));
|
||||
GL(VertexAttribDivisor_(models_attrib + 2, 1));
|
||||
GL(VertexAttribDivisor_(models_attrib + 3, 1));
|
||||
GL(VertexAttribPointer(models_attrib + 0, 4, GL_FLOAT, GL_FALSE,
|
||||
sizeof(math::Float4x4),
|
||||
GL_PTR_OFFSET(0 + _models_offset)));
|
||||
GL(VertexAttribPointer(models_attrib + 1, 4, GL_FLOAT, GL_FALSE,
|
||||
sizeof(math::Float4x4),
|
||||
GL_PTR_OFFSET(16 + _models_offset)));
|
||||
GL(VertexAttribPointer(models_attrib + 2, 4, GL_FLOAT, GL_FALSE,
|
||||
sizeof(math::Float4x4),
|
||||
GL_PTR_OFFSET(32 + _models_offset)));
|
||||
GL(VertexAttribPointer(models_attrib + 3, 4, GL_FLOAT, GL_FALSE,
|
||||
sizeof(math::Float4x4),
|
||||
GL_PTR_OFFSET(48 + _models_offset)));
|
||||
|
||||
// Binds mvp uniform
|
||||
const GLint mvp_uniform = uniform(0);
|
||||
float values[16];
|
||||
math::StorePtrU(_view_proj.cols[0], values + 0);
|
||||
math::StorePtrU(_view_proj.cols[1], values + 4);
|
||||
math::StorePtrU(_view_proj.cols[2], values + 8);
|
||||
math::StorePtrU(_view_proj.cols[3], values + 12);
|
||||
GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
|
||||
}
|
||||
|
||||
void AmbientShaderInstanced::Unbind() {
|
||||
const GLint color_attrib = attrib(2);
|
||||
GL(VertexAttribDivisor_(color_attrib, 0));
|
||||
|
||||
const GLint models_attrib = attrib(3);
|
||||
GL(DisableVertexAttribArray(models_attrib + 0));
|
||||
GL(DisableVertexAttribArray(models_attrib + 1));
|
||||
GL(DisableVertexAttribArray(models_attrib + 2));
|
||||
GL(DisableVertexAttribArray(models_attrib + 3));
|
||||
GL(VertexAttribDivisor_(models_attrib + 0, 0));
|
||||
GL(VertexAttribDivisor_(models_attrib + 1, 0));
|
||||
GL(VertexAttribDivisor_(models_attrib + 2, 0));
|
||||
GL(VertexAttribDivisor_(models_attrib + 3, 0));
|
||||
Shader::Unbind();
|
||||
}
|
||||
|
||||
ozz::unique_ptr<AmbientTexturedShader> AmbientTexturedShader::Build() {
|
||||
const char* vs[] = {
|
||||
kPlatformSpecivicVSHeader, kPassUv,
|
||||
"uniform mat4 u_mw;\n mat4 GetWorldMatrix() {return u_mw;}\n",
|
||||
kShaderUberVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
|
||||
kShaderAmbientTexturedFS};
|
||||
|
||||
ozz::unique_ptr<AmbientTexturedShader> shader =
|
||||
make_unique<AmbientTexturedShader>();
|
||||
bool success =
|
||||
shader->InternalBuild(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);
|
||||
|
||||
success &= shader->FindAttrib("a_uv");
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void AmbientTexturedShader::Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset,
|
||||
GLsizei _normal_stride, GLsizei _normal_offset,
|
||||
GLsizei _color_stride, GLsizei _color_offset,
|
||||
GLsizei _uv_stride, GLsizei _uv_offset) {
|
||||
AmbientShader::Bind(_model, _view_proj, _pos_stride, _pos_offset,
|
||||
_normal_stride, _normal_offset, _color_stride,
|
||||
_color_offset);
|
||||
|
||||
const GLint uv_attrib = attrib(3);
|
||||
GL(EnableVertexAttribArray(uv_attrib));
|
||||
GL(VertexAttribPointer(uv_attrib, 2, GL_FLOAT, GL_FALSE, _uv_stride,
|
||||
GL_PTR_OFFSET(_uv_offset)));
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,249 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHADER_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHADER_H_
|
||||
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
#include "renderer_impl.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace math {
|
||||
struct Float4x4;
|
||||
}
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
// Declares a shader program.
|
||||
class Shader {
|
||||
public:
|
||||
// Construct a fixed function pipeline shader. Use Shader::Build to specify
|
||||
// shader sources.
|
||||
Shader();
|
||||
|
||||
// Destruct a shader.
|
||||
virtual ~Shader();
|
||||
|
||||
// Returns the shader program that can be bound to the OpenGL context.
|
||||
GLuint program() const { return program_; }
|
||||
|
||||
// Request an uniform location and pushes it to the uniform stack.
|
||||
// The uniform location is then accessible thought uniform().
|
||||
bool BindUniform(const char* _semantic);
|
||||
|
||||
// Get an uniform location from the stack at index _index.
|
||||
GLint uniform(int _index) const { return uniforms_[_index]; }
|
||||
|
||||
// Request an attribute location and pushes it to the uniform stack.
|
||||
// The varying location is then accessible thought attrib().
|
||||
bool FindAttrib(const char* _semantic);
|
||||
|
||||
// Get an varying location from the stack at index _index.
|
||||
GLint attrib(int _index) const { return attribs_[_index]; }
|
||||
|
||||
// Unblind shader.
|
||||
virtual void Unbind();
|
||||
|
||||
protected:
|
||||
// Constructs a shader from _vertex and _fragment glsl sources.
|
||||
// Mutliple source files can be specified using the *count argument.
|
||||
bool BuildFromSource(int _vertex_count, const char** _vertex,
|
||||
int _fragment_count, const char** _fragment);
|
||||
|
||||
private:
|
||||
// Unbind all attribs from GL.
|
||||
void UnbindAttribs();
|
||||
|
||||
// Shader program
|
||||
GLuint program_;
|
||||
|
||||
// Vertex and fragment shaders
|
||||
GLuint vertex_;
|
||||
GLuint fragment_;
|
||||
|
||||
// Uniform locations, in the order they were requested.
|
||||
ozz::vector<GLint> uniforms_;
|
||||
|
||||
// Varying locations, in the order they were requested.
|
||||
ozz::vector<GLint> attribs_;
|
||||
};
|
||||
|
||||
class ImmediatePCShader : public Shader {
|
||||
public:
|
||||
ImmediatePCShader() {}
|
||||
virtual ~ImmediatePCShader() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<ImmediatePCShader> Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset);
|
||||
};
|
||||
|
||||
class ImmediatePTCShader : public Shader {
|
||||
public:
|
||||
ImmediatePTCShader() {}
|
||||
virtual ~ImmediatePTCShader() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<ImmediatePTCShader> Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _tex_stride,
|
||||
GLsizei _tex_offset, GLsizei _color_stride, GLsizei _color_offset);
|
||||
};
|
||||
|
||||
class SkeletonShader : public Shader {
|
||||
public:
|
||||
SkeletonShader() {}
|
||||
virtual ~SkeletonShader() {}
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset);
|
||||
|
||||
// Get an attribute location for the join, in cased of instanced rendering.
|
||||
GLint joint_instanced_attrib() const { return attrib(3); }
|
||||
|
||||
// Get an uniform location for the join, in cased of non-instanced rendering.
|
||||
GLint joint_uniform() const { return uniform(1); }
|
||||
};
|
||||
|
||||
class JointShader : public SkeletonShader {
|
||||
public:
|
||||
JointShader() {}
|
||||
virtual ~JointShader() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<JointShader> Build();
|
||||
};
|
||||
|
||||
class BoneShader : public SkeletonShader {
|
||||
public:
|
||||
BoneShader() {}
|
||||
virtual ~BoneShader() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<BoneShader> Build();
|
||||
};
|
||||
|
||||
class AmbientShader : public Shader {
|
||||
public:
|
||||
AmbientShader() {}
|
||||
virtual ~AmbientShader() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<AmbientShader> Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset);
|
||||
|
||||
protected:
|
||||
bool InternalBuild(int _vertex_count, const char** _vertex,
|
||||
int _fragment_count, const char** _fragment);
|
||||
};
|
||||
|
||||
class AmbientShaderInstanced : public Shader {
|
||||
public:
|
||||
AmbientShaderInstanced() {}
|
||||
virtual ~AmbientShaderInstanced() {}
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<AmbientShaderInstanced> Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(GLsizei _models_offset, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset);
|
||||
|
||||
virtual void Unbind();
|
||||
};
|
||||
|
||||
class AmbientTexturedShader : public AmbientShader {
|
||||
public:
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static ozz::unique_ptr<AmbientTexturedShader> Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _normal_stride,
|
||||
GLsizei _normal_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset, GLsizei _uv_stride, GLsizei _uv_offset);
|
||||
};
|
||||
/*
|
||||
class AmbientTexturedShaderInstanced : public AmbientShaderInstanced {
|
||||
public:
|
||||
|
||||
// Constructs the shader.
|
||||
// Returns nullptr if shader compilation failed or a valid Shader pointer on
|
||||
// success. The shader must then be deleted using default allocator Delete
|
||||
// function.
|
||||
static AmbientTexturedShaderInstanced* Build();
|
||||
|
||||
// Binds the shader.
|
||||
void Bind(GLsizei _models_offset,
|
||||
const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset,
|
||||
GLsizei _normal_stride, GLsizei _normal_offset,
|
||||
GLsizei _color_stride, GLsizei _color_offset,
|
||||
GLsizei _uv_stride, GLsizei _uv_offset);
|
||||
};
|
||||
*/
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHADER_H_
|
||||
@@ -0,0 +1,220 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "shooter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
#include "renderer_impl.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
Shooter::Shooter()
|
||||
: gl_shot_format_(GL_RGBA), // Default fail safe format and types.
|
||||
image_format_(image::Format::kRGBA),
|
||||
shot_number_(0) {
|
||||
// Test required extension (optional for the framework).
|
||||
// If GL_VERSION_1_5 is defined (aka OZZ_GL_VERSION_1_5_EXT not defined), then
|
||||
// these functions are part of the library (don't need extensions).
|
||||
supported_ = true;
|
||||
#ifdef OZZ_GL_VERSION_1_5_EXT
|
||||
supported_ &= glMapBuffer != nullptr && glUnmapBuffer != nullptr;
|
||||
#endif // OZZ_GL_VERSION_1_5_EXT
|
||||
|
||||
// Initializes shots
|
||||
GLuint pbos[kNumShots];
|
||||
GL(GenBuffers(kNumShots, pbos));
|
||||
for (int i = 0; i < kNumShots; ++i) {
|
||||
Shot& shot = shots_[i];
|
||||
shot.pbo = pbos[i];
|
||||
}
|
||||
|
||||
// OpenGL ES2 compatibility extension allows to query for implementation best
|
||||
// format and type.
|
||||
if (glfwExtensionSupported("GL_ARB_ES2_compatibility") != 0) {
|
||||
GLint format;
|
||||
GL(GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &format));
|
||||
GLint type;
|
||||
GL(GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &type));
|
||||
|
||||
// Only support GL_UNSIGNED_BYTE.
|
||||
if (type == GL_UNSIGNED_BYTE) {
|
||||
switch (format) {
|
||||
case GL_RGBA:
|
||||
gl_shot_format_ = format;
|
||||
image_format_ = image::Format::kRGBA;
|
||||
break;
|
||||
case GL_BGRA:
|
||||
gl_shot_format_ = format;
|
||||
image_format_ = image::Format::kBGRA;
|
||||
break;
|
||||
case GL_RGB:
|
||||
gl_shot_format_ = format;
|
||||
image_format_ = image::Format::kRGB;
|
||||
break;
|
||||
case GL_BGR:
|
||||
gl_shot_format_ = format;
|
||||
image_format_ = image::Format::kBGR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default fail safe format and types.
|
||||
gl_shot_format_ = GL_RGBA;
|
||||
image_format_ = image::Format::kRGBA;
|
||||
}
|
||||
}
|
||||
|
||||
Shooter::~Shooter() {
|
||||
// Process all remaining shots.
|
||||
ProcessAll();
|
||||
|
||||
// Clean shot pbos.
|
||||
for (int i = 0; i < kNumShots; ++i) {
|
||||
Shot& shot = shots_[i];
|
||||
GL(DeleteBuffers(1, &shot.pbo));
|
||||
|
||||
assert(shot.cooldown == 0); // Must have been processed.
|
||||
}
|
||||
}
|
||||
|
||||
void Shooter::Resize(int _width, int _height) {
|
||||
// Early out if not supported.
|
||||
if (!supported_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process all remaining shots.
|
||||
ProcessAll();
|
||||
|
||||
// Resizes all pbos.
|
||||
#ifndef EMSCRIPTEN
|
||||
for (int i = 0; i < kNumShots; ++i) {
|
||||
Shot& shot = shots_[i];
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, shot.pbo));
|
||||
GL(BufferData(GL_PIXEL_PACK_BUFFER, _width * _height * 4, 0,
|
||||
GL_STREAM_READ));
|
||||
shot.width = _width;
|
||||
shot.height = _height;
|
||||
assert(shot.cooldown == 0); // Must have been processed.
|
||||
}
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, 0));
|
||||
#endif // EMSCRIPTEN
|
||||
}
|
||||
|
||||
bool Shooter::Update() { return Process(); }
|
||||
|
||||
bool Shooter::Process() {
|
||||
// Early out if not supported.
|
||||
if (!supported_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Early out if process stack is empty.
|
||||
for (int i = 0; i < kNumShots; ++i) {
|
||||
Shot& shot = shots_[i];
|
||||
|
||||
// Early out for already processed, or empty shots.
|
||||
if (shot.cooldown == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip shots that didn't reached the cooldown.
|
||||
if (--shot.cooldown != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Processes this shot.
|
||||
#ifdef EMSCRIPTEN
|
||||
(void)shot_number_;
|
||||
#else // EMSCRIPTEN
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, shot.pbo));
|
||||
const void* pixels = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
if (pixels) {
|
||||
char name[16];
|
||||
sprintf(name, "%06d.tga", shot_number_++);
|
||||
|
||||
ozz::sample::image::WriteTGA(name, shot.width, shot.height, image_format_,
|
||||
reinterpret_cast<const uint8_t*>(pixels),
|
||||
false);
|
||||
GL(UnmapBuffer(GL_PIXEL_PACK_BUFFER));
|
||||
}
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, 0));
|
||||
#endif // EMSCRIPTEN
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Shooter::ProcessAll() {
|
||||
// Reset cooldown to 1 for all "unprocessed" shots, so they will be
|
||||
// "processed".
|
||||
for (int i = 0; i < kNumShots; ++i) {
|
||||
Shot& shot = shots_[i];
|
||||
shot.cooldown = shot.cooldown > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
return Process();
|
||||
}
|
||||
|
||||
bool Shooter::Capture(int _buffer) {
|
||||
assert(_buffer == GL_FRONT || _buffer == GL_BACK);
|
||||
|
||||
// Early out if not supported.
|
||||
if (!supported_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finds the shot to use for this capture.
|
||||
Shot* shot;
|
||||
for (shot = shots_; shot < shots_ + kNumShots && shot->cooldown != 0;
|
||||
++shot) {
|
||||
}
|
||||
assert(shot != shots_ + kNumShots);
|
||||
|
||||
// Initializes cooldown.
|
||||
shot->cooldown = kInitialCountDown;
|
||||
|
||||
#ifndef EMSCRIPTEN
|
||||
// Copy pixels to shot's pbo.
|
||||
GL(ReadBuffer(_buffer));
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, shot->pbo));
|
||||
GL(PixelStorei(GL_PACK_ALIGNMENT, 4));
|
||||
GL(ReadPixels(0, 0, shot->width, shot->height, gl_shot_format_,
|
||||
GL_UNSIGNED_BYTE, 0));
|
||||
GL(BindBuffer(GL_PIXEL_PACK_BUFFER, 0));
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
@@ -0,0 +1,95 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHOOTER_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHOOTER_H_
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
|
||||
#include "framework/image.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
namespace internal {
|
||||
|
||||
// Implements GL screen shot and video shooter
|
||||
class Shooter {
|
||||
public:
|
||||
Shooter();
|
||||
virtual ~Shooter();
|
||||
|
||||
// Resize notification, used to resize memory buffers.
|
||||
void Resize(int _width, int _height);
|
||||
|
||||
// Updates shooter (output captured buffers to memory).
|
||||
bool Update();
|
||||
|
||||
// Captures current (GL_FRONT or GL_BACK) _buffer to memory.
|
||||
bool Capture(int _buffer);
|
||||
|
||||
private:
|
||||
// Updates all cooldowns and process terminated shots. Returns false if it
|
||||
// fails, true on success or empty stack.
|
||||
bool Process();
|
||||
|
||||
// Processes all shots from the stack. Returns false if it fails, true on
|
||||
// success or empty stack.
|
||||
bool ProcessAll();
|
||||
|
||||
// Defines shot buffer (pbo) and data.
|
||||
struct Shot {
|
||||
unsigned int pbo;
|
||||
int width;
|
||||
int height;
|
||||
int cooldown; // Shot is processed when cooldown falls to 0.
|
||||
Shot() : pbo(0), width(0), height(0), cooldown(0) {}
|
||||
};
|
||||
|
||||
// Array of pre-allocated shots, used to allow asynchronous dma transfers of
|
||||
// pbos.
|
||||
enum {
|
||||
kInitialCountDown = 2, // Allows to delay pbo mapping 2 rendering frames.
|
||||
kNumShots = kInitialCountDown,
|
||||
};
|
||||
Shot shots_[kNumShots];
|
||||
|
||||
// Format of pixels to use to glReadPixels calls.
|
||||
int gl_shot_format_;
|
||||
|
||||
// Image format that matches GL format.
|
||||
image::Format::Value image_format_;
|
||||
|
||||
// Shot number, used to name images.
|
||||
int shot_number_;
|
||||
|
||||
// Is the shooter functionality supported.
|
||||
bool supported_;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_INTERNAL_SHOOTER_H_
|
||||
@@ -0,0 +1,95 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 "mesh.h"
|
||||
|
||||
#include "ozz/base/containers/vector_archive.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
|
||||
#include "ozz/base/io/archive.h"
|
||||
|
||||
#include "ozz/base/maths/math_archive.h"
|
||||
#include "ozz/base/maths/simd_math_archive.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace io {
|
||||
|
||||
void Extern<sample::Mesh::Part>::Save(OArchive& _archive,
|
||||
const sample::Mesh::Part* _parts,
|
||||
size_t _count) {
|
||||
for (size_t i = 0; i < _count; ++i) {
|
||||
const sample::Mesh::Part& part = _parts[i];
|
||||
_archive << part.positions;
|
||||
_archive << part.normals;
|
||||
_archive << part.tangents;
|
||||
_archive << part.uvs;
|
||||
_archive << part.colors;
|
||||
_archive << part.joint_indices;
|
||||
_archive << part.joint_weights;
|
||||
}
|
||||
}
|
||||
|
||||
void Extern<sample::Mesh::Part>::Load(IArchive& _archive,
|
||||
sample::Mesh::Part* _parts, size_t _count,
|
||||
uint32_t _version) {
|
||||
(void)_version;
|
||||
for (size_t i = 0; i < _count; ++i) {
|
||||
sample::Mesh::Part& part = _parts[i];
|
||||
_archive >> part.positions;
|
||||
_archive >> part.normals;
|
||||
_archive >> part.tangents;
|
||||
_archive >> part.uvs;
|
||||
_archive >> part.colors;
|
||||
_archive >> part.joint_indices;
|
||||
_archive >> part.joint_weights;
|
||||
}
|
||||
}
|
||||
|
||||
void Extern<sample::Mesh>::Save(OArchive& _archive, const sample::Mesh* _meshes,
|
||||
size_t _count) {
|
||||
for (size_t i = 0; i < _count; ++i) {
|
||||
const sample::Mesh& mesh = _meshes[i];
|
||||
_archive << mesh.parts;
|
||||
_archive << mesh.triangle_indices;
|
||||
_archive << mesh.joint_remaps;
|
||||
_archive << mesh.inverse_bind_poses;
|
||||
}
|
||||
}
|
||||
|
||||
void Extern<sample::Mesh>::Load(IArchive& _archive, sample::Mesh* _meshes,
|
||||
size_t _count, uint32_t _version) {
|
||||
(void)_version;
|
||||
for (size_t i = 0; i < _count; ++i) {
|
||||
sample::Mesh& mesh = _meshes[i];
|
||||
_archive >> mesh.parts;
|
||||
_archive >> mesh.triangle_indices;
|
||||
_archive >> mesh.joint_remaps;
|
||||
_archive >> mesh.inverse_bind_poses;
|
||||
}
|
||||
}
|
||||
} // namespace io
|
||||
} // namespace ozz
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_MESH_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_MESH_H_
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/io/archive_traits.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
#include "ozz/base/platform.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
|
||||
// Defines a mesh with skinning information (joint indices and weights).
|
||||
// The mesh is subdivided into parts that group vertices according to their
|
||||
// number of influencing joints. Triangle indices are shared across mesh parts.
|
||||
struct Mesh {
|
||||
// Number of triangle indices for the mesh.
|
||||
int triangle_index_count() const {
|
||||
return static_cast<int>(triangle_indices.size());
|
||||
}
|
||||
|
||||
// Number of vertices for all mesh parts.
|
||||
int vertex_count() const {
|
||||
int vertex_count = 0;
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
vertex_count += parts[i].vertex_count();
|
||||
}
|
||||
return vertex_count;
|
||||
}
|
||||
|
||||
// Maximum number of joints influences for all mesh parts.
|
||||
int max_influences_count() const {
|
||||
int max_influences_count = 0;
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
const int influences_count = parts[i].influences_count();
|
||||
max_influences_count = influences_count > max_influences_count
|
||||
? influences_count
|
||||
: max_influences_count;
|
||||
}
|
||||
return max_influences_count;
|
||||
}
|
||||
|
||||
// Test if the mesh has skinning informations.
|
||||
bool skinned() const {
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
if (parts[i].influences_count() != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the number of joints used to skin the mesh.
|
||||
int num_joints() const { return static_cast<int>(inverse_bind_poses.size()); }
|
||||
|
||||
// Returns the highest joint number used in the skeleton.
|
||||
int highest_joint_index() const {
|
||||
// Takes advantage that joint_remaps is sorted.
|
||||
return joint_remaps.size() != 0 ? static_cast<int>(joint_remaps.back()) : 0;
|
||||
}
|
||||
|
||||
// Defines a portion of the mesh. A mesh is subdivided in sets of vertices
|
||||
// with the same number of joint influences.
|
||||
struct Part {
|
||||
int vertex_count() const { return static_cast<int>(positions.size()) / 3; }
|
||||
|
||||
int influences_count() const {
|
||||
const int _vertex_count = vertex_count();
|
||||
if (_vertex_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(joint_indices.size()) / _vertex_count;
|
||||
}
|
||||
|
||||
typedef ozz::vector<float> Positions;
|
||||
Positions positions;
|
||||
enum { kPositionsCpnts = 3 }; // x, y, z components
|
||||
|
||||
typedef ozz::vector<float> Normals;
|
||||
Normals normals;
|
||||
enum { kNormalsCpnts = 3 }; // x, y, z components
|
||||
|
||||
typedef ozz::vector<float> Tangents;
|
||||
Tangents tangents;
|
||||
enum { kTangentsCpnts = 4 }; // x, y, z, right or left handed.
|
||||
|
||||
typedef ozz::vector<float> UVs;
|
||||
UVs uvs; // u, v components
|
||||
enum { kUVsCpnts = 2 };
|
||||
|
||||
typedef ozz::vector<uint8_t> Colors;
|
||||
Colors colors;
|
||||
enum { kColorsCpnts = 4 }; // r, g, b, a components
|
||||
|
||||
typedef ozz::vector<uint16_t> JointIndices;
|
||||
JointIndices joint_indices; // Stride equals influences_count
|
||||
|
||||
typedef ozz::vector<float> JointWeights;
|
||||
JointWeights joint_weights; // Stride equals influences_count - 1
|
||||
};
|
||||
typedef ozz::vector<Part> Parts;
|
||||
Parts parts;
|
||||
|
||||
// Triangles indices. Indices are shared across all parts.
|
||||
typedef ozz::vector<uint16_t> TriangleIndices;
|
||||
TriangleIndices triangle_indices;
|
||||
|
||||
// Joints remapping indices. As a skin might be influenced by a part of the
|
||||
// skeleton only, joint indices and inverse bind pose matrices are reordered
|
||||
// to contain only used ones. Note that this array is sorted.
|
||||
typedef ozz::vector<uint16_t> JointRemaps;
|
||||
JointRemaps joint_remaps;
|
||||
|
||||
// Inverse bind-pose matrices. These are only available for skinned meshes.
|
||||
typedef ozz::vector<ozz::math::Float4x4> InversBindPoses;
|
||||
InversBindPoses inverse_bind_poses;
|
||||
};
|
||||
} // namespace sample
|
||||
|
||||
namespace io {
|
||||
|
||||
OZZ_IO_TYPE_TAG("ozz-sample-Mesh-Part", sample::Mesh::Part)
|
||||
OZZ_IO_TYPE_VERSION(1, sample::Mesh::Part)
|
||||
|
||||
template <>
|
||||
struct Extern<sample::Mesh::Part> {
|
||||
static void Save(OArchive& _archive, const sample::Mesh::Part* _parts,
|
||||
size_t _count);
|
||||
static void Load(IArchive& _archive, sample::Mesh::Part* _parts,
|
||||
size_t _count, uint32_t _version);
|
||||
};
|
||||
|
||||
OZZ_IO_TYPE_TAG("ozz-sample-Mesh", sample::Mesh)
|
||||
OZZ_IO_TYPE_VERSION(1, sample::Mesh)
|
||||
|
||||
template <>
|
||||
struct Extern<sample::Mesh> {
|
||||
static void Save(OArchive& _archive, const sample::Mesh* _meshes,
|
||||
size_t _count);
|
||||
static void Load(IArchive& _archive, sample::Mesh* _meshes, size_t _count,
|
||||
uint32_t _version);
|
||||
};
|
||||
} // namespace io
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_MESH_H_
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
||||
|
||||
#include "framework/profile.h"
|
||||
#include "framework/internal/renderer_impl.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
|
||||
Profiler::Profiler(Record* _record)
|
||||
: begin_(static_cast<float>(glfwGetTime())), record_(_record) {}
|
||||
|
||||
Profiler::~Profiler() {
|
||||
if (record_) {
|
||||
float end = static_cast<float>(glfwGetTime());
|
||||
record_->Push((end - begin_) * 1000.f);
|
||||
}
|
||||
}
|
||||
|
||||
Record::Record(int _max_records)
|
||||
: max_records_(_max_records < 1 ? 1 : _max_records),
|
||||
records_end_(
|
||||
reinterpret_cast<float*>(memory::default_allocator()->Allocate(
|
||||
_max_records * sizeof(float), alignof(float))) +
|
||||
max_records_),
|
||||
records_begin_(records_end_),
|
||||
cursor_(records_end_) {}
|
||||
|
||||
Record::~Record() {
|
||||
memory::default_allocator()->Deallocate(records_end_ - max_records_);
|
||||
}
|
||||
|
||||
void Record::Push(float _value) {
|
||||
if (records_begin_ + max_records_ == records_end_) {
|
||||
if (cursor_ == records_begin_) { // Looping...
|
||||
cursor_ = records_begin_ + max_records_;
|
||||
}
|
||||
} else {
|
||||
// The buffer is not full yet.
|
||||
records_begin_--;
|
||||
}
|
||||
--cursor_;
|
||||
*cursor_ = _value;
|
||||
}
|
||||
|
||||
Record::Statistics Record::GetStatistics() {
|
||||
Statistics statistics = {FLT_MAX, -FLT_MAX, 0.f, 0.f};
|
||||
if (records_begin_ == records_end_) { // No record.
|
||||
return statistics;
|
||||
}
|
||||
|
||||
// Computes statistics.
|
||||
float sum = 0.f;
|
||||
|
||||
const float* current = cursor_;
|
||||
const float* end = records_end_;
|
||||
while (current < end) {
|
||||
if (*current < statistics.min) {
|
||||
statistics.min = *current;
|
||||
}
|
||||
if (*current > statistics.max) {
|
||||
statistics.max = *current;
|
||||
}
|
||||
sum += *current;
|
||||
++current;
|
||||
|
||||
if (current == records_end_) { // Looping...
|
||||
end = cursor_;
|
||||
current = records_begin_;
|
||||
}
|
||||
}
|
||||
|
||||
// Stores outputs.
|
||||
/*
|
||||
int exponent;
|
||||
std::frexp(_f, &exponent);
|
||||
float upper = pow(2.f, exponent);
|
||||
statistics.max = func(statistics.max);*/
|
||||
|
||||
statistics.latest = *cursor_;
|
||||
statistics.mean = sum / (records_end_ - records_begin_);
|
||||
|
||||
return statistics;
|
||||
}
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_PROFILE_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_PROFILE_H_
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
// Records up to a maximum number of float values. Once the maximum number is
|
||||
// reached, it keeps the most recent ones and reject the oldest.
|
||||
class Record {
|
||||
public:
|
||||
// Constructs and sets the maximum number of record-able values.
|
||||
// The minimum record-able number of values is 1.
|
||||
explicit Record(int _max_records);
|
||||
|
||||
// Deallocate records.
|
||||
~Record();
|
||||
|
||||
// Adds _value to the records, while rejecting the oldest one if the maximum
|
||||
// number is reached.
|
||||
void Push(float _value);
|
||||
|
||||
// Returns the cursor to the newest value in the circular buffer.
|
||||
// cursor() == record_begin() == record_end() if record is empty.
|
||||
// Recorded values can be accessed sequentially from the newest to the oldest
|
||||
// from [cursor():record_end()[ and then [record_begin():cursor()[
|
||||
const float* cursor() const { return cursor_; }
|
||||
|
||||
// Returns the beginning of the recorded values.
|
||||
const float* record_begin() const { return records_begin_; }
|
||||
|
||||
// Returns the end of the recorded values.
|
||||
const float* record_end() const { return records_end_; }
|
||||
|
||||
// Statistics returned by GetStatistics function.
|
||||
struct Statistics {
|
||||
// Minimum value of the recorded range.
|
||||
float min;
|
||||
// Maximum value of the recorded range.
|
||||
float max;
|
||||
// Mean value of the recorded range.
|
||||
float mean;
|
||||
// Latest value of the recorded range.
|
||||
float latest;
|
||||
};
|
||||
|
||||
// Builds statistics of the current record state.
|
||||
Statistics GetStatistics();
|
||||
|
||||
private:
|
||||
// Disables assignment and copy.
|
||||
Record(const Record& _record);
|
||||
void operator=(const Record& _record);
|
||||
|
||||
// The maximum number of recorded entries.
|
||||
int max_records_;
|
||||
|
||||
// Circular buffer of recorded valued, limited to max_records_ entries.
|
||||
// records_begin_ is set to records_end_ when record is empty.
|
||||
// records_begin_ then moves down to allocation begin. Therefore
|
||||
// deallocation should be done using records_end_.
|
||||
float* const records_end_;
|
||||
float* records_begin_;
|
||||
|
||||
// Cursor in the circular buffer. Points to the latest pushed value.
|
||||
float* cursor_;
|
||||
};
|
||||
|
||||
// Measures the time spent between the constructor and the destructor (as a
|
||||
// RAII object) and pushes the result to a Record.
|
||||
class Profiler {
|
||||
public:
|
||||
// Starts measurement.
|
||||
explicit Profiler(Record* _record);
|
||||
|
||||
// Ends measurement and pushes the result to the record.
|
||||
~Profiler();
|
||||
|
||||
private:
|
||||
// Disables assignment and copy.
|
||||
Profiler(const Profiler& _profiler);
|
||||
void operator=(const Profiler& _profiler);
|
||||
|
||||
// The time at which profiling began.
|
||||
float begin_;
|
||||
|
||||
// Profiling result is pushed in the record_ object.
|
||||
Record* record_;
|
||||
};
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_PROFILE_H_
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_RENDERER_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_RENDERER_H_
|
||||
|
||||
#include "ozz/base/platform.h"
|
||||
#include "ozz/base/span.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace animation {
|
||||
class Skeleton;
|
||||
}
|
||||
namespace math {
|
||||
struct Float4x4;
|
||||
struct Float3;
|
||||
struct Box;
|
||||
} // namespace math
|
||||
namespace sample {
|
||||
|
||||
// Sample framework mesh type.
|
||||
struct Mesh;
|
||||
|
||||
// Defines render Color structure.
|
||||
struct Color {
|
||||
unsigned char r, g, b, a;
|
||||
};
|
||||
|
||||
// Color constants.
|
||||
static const Color kRed = {0xff, 0, 0, 0xff};
|
||||
static const Color kGreen = {0, 0xff, 0, 0xff};
|
||||
static const Color kBlue = {0, 0, 0xff, 0xff};
|
||||
static const Color kWhite = {0xff, 0xff, 0xff, 0xff};
|
||||
static const Color kYellow = {0xff, 0xff, 0, 0xff};
|
||||
static const Color kMagenta = {0xff, 0, 0xff, 0xff};
|
||||
static const Color kCyan = {0, 0xff, 0xff, 0xff};
|
||||
static const Color kGrey = {0x80, 0x80, 0x80, 0xff};
|
||||
static const Color kBlack = {0x80, 0x80, 0x80, 0xff};
|
||||
|
||||
// Defines renderer abstract interface.
|
||||
class Renderer {
|
||||
public:
|
||||
// Declares a virtual destructor to allow proper destruction.
|
||||
virtual ~Renderer() {}
|
||||
|
||||
// Initializes he renderer.
|
||||
// Return true on success.
|
||||
virtual bool Initialize() = 0;
|
||||
|
||||
// Renders coordinate system axes: X in red, Y in green and W in blue.
|
||||
// Axes size is given by _scale argument.
|
||||
virtual bool DrawAxes(const ozz::math::Float4x4& _transform) = 0;
|
||||
|
||||
// Renders a square grid of _cell_count cells width, where each square cell
|
||||
// has a size of _cell_size.
|
||||
virtual bool DrawGrid(int _cell_count, float _cell_size) = 0;
|
||||
|
||||
// Renders a skeleton in its bind pose posture.
|
||||
virtual bool DrawSkeleton(const animation::Skeleton& _skeleton,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints = true) = 0;
|
||||
|
||||
// Renders a skeleton at the specified _position in the posture given by model
|
||||
// space _matrices.
|
||||
// Returns true on success, or false if _matrices range does not match with
|
||||
// the _skeleton.
|
||||
virtual bool DrawPosture(const animation::Skeleton& _skeleton,
|
||||
ozz::span<const ozz::math::Float4x4> _matrices,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints = true) = 0;
|
||||
|
||||
// Renders a box at a specified location.
|
||||
// The 2 slots of _colors array respectively defines color of the filled
|
||||
// faces and color of the box outlines.
|
||||
virtual bool DrawBoxIm(const ozz::math::Box& _box,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _colors[2]) = 0;
|
||||
|
||||
// Renders shaded boxes at specified locations.
|
||||
virtual bool DrawBoxShaded(const ozz::math::Box& _box,
|
||||
ozz::span<const ozz::math::Float4x4> _transforms,
|
||||
Color _color) = 0;
|
||||
|
||||
// Renders a sphere at a specified location.
|
||||
virtual bool DrawSphereIm(float _radius,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _color) = 0;
|
||||
|
||||
// Renders shaded spheres at specified locations.
|
||||
virtual bool DrawSphereShaded(
|
||||
float _radius, ozz::span<const ozz::math::Float4x4> _transforms,
|
||||
Color _color) = 0;
|
||||
|
||||
struct Options {
|
||||
bool texture; // Show texture (default checkered texture).
|
||||
bool normals; // Show normals.
|
||||
bool tangents; // Show tangents.
|
||||
bool binormals; // Show binormals, computed from the normal and tangent.
|
||||
bool colors; // Show vertex colors.
|
||||
bool wireframe; // Show vertex colors.
|
||||
bool skip_skinning; // Show texture (default checkered texture).
|
||||
|
||||
Options()
|
||||
: texture(false),
|
||||
normals(false),
|
||||
tangents(false),
|
||||
binormals(false),
|
||||
colors(false),
|
||||
wireframe(false),
|
||||
skip_skinning(false) {}
|
||||
|
||||
Options(bool _texture, bool _normals, bool _tangents, bool _binormals,
|
||||
bool _colors, bool _wireframe, bool _skip_skinning)
|
||||
: texture(_texture),
|
||||
normals(_normals),
|
||||
tangents(_tangents),
|
||||
binormals(_binormals),
|
||||
colors(_colors),
|
||||
wireframe(_wireframe),
|
||||
skip_skinning(_skip_skinning) {}
|
||||
};
|
||||
|
||||
// Renders a skinned mesh at a specified location.
|
||||
virtual bool DrawSkinnedMesh(const Mesh& _mesh,
|
||||
const span<math::Float4x4> _skinning_matrices,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Options& _options = Options()) = 0;
|
||||
|
||||
// Renders a mesh at a specified location.
|
||||
virtual bool DrawMesh(const Mesh& _mesh,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Options& _options = Options()) = 0;
|
||||
|
||||
// Renders a segment from begin to end.
|
||||
virtual bool DrawSegment(const math::Float3& _begin, const math::Float3& _end,
|
||||
Color _color,
|
||||
const ozz::math::Float4x4& _transform) = 0;
|
||||
|
||||
// Renders vectors, defined by their starting point and a direction.
|
||||
virtual bool DrawVectors(ozz::span<const float> _positions,
|
||||
size_t _positions_stride,
|
||||
ozz::span<const float> _directions,
|
||||
size_t _directions_stride, int _num_vectors,
|
||||
float _vector_length, Color _color,
|
||||
const ozz::math::Float4x4& _transform) = 0;
|
||||
|
||||
// Compute binormals from normals and tangents, before displaying them.
|
||||
virtual bool DrawBinormals(
|
||||
ozz::span<const float> _positions, size_t _positions_stride,
|
||||
ozz::span<const float> _normals, size_t _normals_stride,
|
||||
ozz::span<const float> _tangents, size_t _tangents_stride,
|
||||
ozz::span<const float> _handenesses, size_t _handenesses_stride,
|
||||
int _num_vectors, float _vector_length, Color _color,
|
||||
const ozz::math::Float4x4& _transform) = 0;
|
||||
};
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_RENDERER_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
# Adds fbx2mesh utility target.
|
||||
if(ozz_build_fbx)
|
||||
|
||||
# share meshes with thte sample framework
|
||||
add_executable(sample_fbx2mesh
|
||||
fbx2mesh.cc
|
||||
${PROJECT_SOURCE_DIR}/samples/framework/mesh.cc
|
||||
${PROJECT_SOURCE_DIR}/samples/framework/mesh.h)
|
||||
target_link_libraries(sample_fbx2mesh
|
||||
ozz_animation_fbx
|
||||
ozz_options)
|
||||
set_target_properties(sample_fbx2mesh
|
||||
PROPERTIES FOLDER "samples/tools")
|
||||
|
||||
install(TARGETS sample_fbx2mesh DESTINATION bin/samples/tools)
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
$<$<AND:$<BOOL:${ozz_build_data}>,$<BOOL:${ozz_build_fbx}>>:sample_fbx2mesh>
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/fbx/pab/arnaud.fbx"
|
||||
"${ozz_media_directory}/fbx/sketchfab/ruby.fbx"
|
||||
"${ozz_media_directory}/collada/floor.dae"
|
||||
OUTPUT "${ozz_media_directory}/bin/arnaud_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/arnaud_mesh_4.ozz"
|
||||
OUTPUT "${ozz_media_directory}/bin/ruby_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/floor.ozz"
|
||||
COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/fbx/pab/arnaud.fbx" "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz" "--mesh=${ozz_media_directory}/bin/arnaud_mesh.ozz"
|
||||
COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/fbx/pab/arnaud.fbx" "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz" "--mesh=${ozz_media_directory}/bin/arnaud_mesh_4.ozz" --nosplit --max_influences=4
|
||||
COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/fbx/sketchfab/ruby.fbx" "--skeleton=${ozz_media_directory}/bin/ruby_skeleton.ozz" "--mesh=${ozz_media_directory}/bin/ruby_mesh.ozz" --max_influences=4
|
||||
COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/collada/floor.dae" "--mesh=${ozz_media_directory}/bin/floor.ozz" "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
VERBATIM)
|
||||
|
||||
# Creates a target to build sample data
|
||||
add_custom_target(BUILD_DATA_SAMPLE ALL DEPENDS
|
||||
"${ozz_media_directory}/bin/arnaud_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/arnaud_mesh_4.ozz"
|
||||
"${ozz_media_directory}/bin/ruby_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/floor.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_test(NAME sample_fbx2mesh COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/fbx/pab/skeleton.fbx" "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz" "--mesh=${ozz_temp_directory}/mesh.ozz")
|
||||
|
||||
add_test(NAME sample_fbx2mesh_invalid_file COMMAND sample_fbx2mesh "--file=${ozz_temp_directory}/dont_exist.fbx" "--skeleton=${ozz_media_directory}/bin/pab_skeleton.ozz" "--mesh=${ozz_temp_directory}/should_not_exist.ozz")
|
||||
set_tests_properties(sample_fbx2mesh_invalid_file PROPERTIES WILL_FAIL true)
|
||||
|
||||
add_test(NAME sample_fbx2mesh_invalid_skeleton COMMAND sample_fbx2mesh "--file=${ozz_media_directory}/fbx/pab/skeleton.fbx" "--skeleton=${ozz_media_directory}/bin/pab_walk.ozz" "--mesh=${ozz_temp_directory}/should_not_exist.ozz")
|
||||
set_tests_properties(sample_fbx2mesh_invalid_skeleton PROPERTIES WILL_FAIL true)
|
||||
|
||||
# Ensures nothing was outputted.
|
||||
add_test(NAME sample_fbx2mesh_output COMMAND ${CMAKE_COMMAND} -E copy "${ozz_temp_directory}/should_not_exist.ozz" "${ozz_temp_directory}/should_not_exist_too.ozz")
|
||||
set_tests_properties(sample_fbx2mesh_output PROPERTIES WILL_FAIL true)
|
||||
set_tests_properties(sample_fbx2mesh_output PROPERTIES
|
||||
DEPENDS "sample_fbx2mesh_invalid_file
|
||||
sample_fbx2mesh_invalid_skeleton")
|
||||
|
||||
endif()
|
||||
File diff suppressed because it is too large
Load Diff
+544
@@ -0,0 +1,544 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 {
|
||||
// Moller–Trumbore 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
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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. //
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_UTILS_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_UTILS_H_
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/platform.h"
|
||||
#include "ozz/base/span.h"
|
||||
|
||||
namespace ozz {
|
||||
// Forward declarations.
|
||||
namespace math {
|
||||
struct Box;
|
||||
struct Float3;
|
||||
struct Float4x4;
|
||||
struct SimdQuaternion;
|
||||
struct SoaTransform;
|
||||
} // namespace math
|
||||
namespace animation {
|
||||
class Animation;
|
||||
class Skeleton;
|
||||
class FloatTrack;
|
||||
class Float2Track;
|
||||
class Float3Track;
|
||||
class Float4Track;
|
||||
class QuaternionTrack;
|
||||
namespace offline {
|
||||
struct RawAnimation;
|
||||
struct RawSkeleton;
|
||||
} // namespace offline
|
||||
} // namespace animation
|
||||
namespace sample {
|
||||
class ImGui;
|
||||
struct Mesh;
|
||||
|
||||
// Utility class that helps with controlling animation playback time. Time is
|
||||
// computed every update according to the dt given by the caller, playback speed
|
||||
// and "play" state.
|
||||
// Internally time is stored as a ratio in unit interval [0,1], as expected by
|
||||
// ozz runtime animation jobs.
|
||||
// OnGui function allows to tweak controller parameters through the application
|
||||
// Gui.
|
||||
class PlaybackController {
|
||||
public:
|
||||
// Constructor.
|
||||
PlaybackController();
|
||||
|
||||
// Sets animation current time.
|
||||
void set_time_ratio(float _time);
|
||||
|
||||
// Gets animation current time.
|
||||
float time_ratio() const;
|
||||
|
||||
// Gets animation time ratio of last update. Useful when the range between
|
||||
// previous and current frame needs to pe processed.
|
||||
float previous_time_ratio() const;
|
||||
|
||||
// Sets playback speed.
|
||||
void set_playback_speed(float _speed) { playback_speed_ = _speed; }
|
||||
|
||||
// Gets playback speed.
|
||||
float playback_speed() const { return playback_speed_; }
|
||||
|
||||
// Sets loop modes. If true, animation time is always clamped between 0 and 1.
|
||||
void set_loop(bool _loop) { loop_ = _loop; }
|
||||
|
||||
// Gets loop mode.
|
||||
bool loop() const { return loop_; }
|
||||
|
||||
// Updates animation time if in "play" state, according to playback speed and
|
||||
// given frame time _dt.
|
||||
// Returns true if animation has looped during update
|
||||
void Update(const animation::Animation& _animation, float _dt);
|
||||
|
||||
// Resets all parameters to their default value.
|
||||
void Reset();
|
||||
|
||||
// Do controller Gui.
|
||||
// Returns true if animation time has been changed.
|
||||
bool OnGui(const animation::Animation& _animation, ImGui* _im_gui,
|
||||
bool _enabled = true, bool _allow_set_time = true);
|
||||
|
||||
private:
|
||||
// Current animation time ratio, in the unit interval [0,1], where 0 is the
|
||||
// beginning of the animation, 1 is the end.
|
||||
float time_ratio_;
|
||||
|
||||
// Time ratio of the previous update.
|
||||
float previous_time_ratio_;
|
||||
|
||||
// Playback speed, can be negative in order to play the animation backward.
|
||||
float playback_speed_;
|
||||
|
||||
// Animation play mode state: play/pause.
|
||||
bool play_;
|
||||
|
||||
// Animation loop mode.
|
||||
bool loop_;
|
||||
};
|
||||
|
||||
// Computes the bounding box of _skeleton. This is the box that encloses all
|
||||
// skeleton's joints in model space.
|
||||
// _bound must be a valid math::Box instance.
|
||||
void ComputeSkeletonBounds(const animation::Skeleton& _skeleton,
|
||||
math::Box* _bound);
|
||||
|
||||
// Computes the bounding box of posture defines be _matrices range.
|
||||
// _bound must be a valid math::Box instance.
|
||||
void ComputePostureBounds(ozz::span<const ozz::math::Float4x4> _matrices,
|
||||
math::Box* _bound);
|
||||
|
||||
// Allows to edit translation/rotation/scale of a skeleton pose.
|
||||
// This object should be used for a single skeleton, because it stores
|
||||
// open/close states from a frame to the next.
|
||||
class RawSkeletonEditor {
|
||||
public:
|
||||
// Returns true if skeleton was modified.
|
||||
bool OnGui(animation::offline::RawSkeleton* _skeleton, ImGui* _im_gui);
|
||||
|
||||
private:
|
||||
// Imgui Open/Close states for each skeleton joint.
|
||||
ozz::vector<bool> open_close_states;
|
||||
};
|
||||
|
||||
// Multiplies a single quaternion at a specific index in a SoA transform range.
|
||||
void MultiplySoATransformQuaternion(
|
||||
int _index, const ozz::math::SimdQuaternion& _quat,
|
||||
const ozz::span<ozz::math::SoaTransform>& _transforms);
|
||||
|
||||
// Loads a skeleton from an ozz archive file named _filename.
|
||||
// This function will fail and return false if the file cannot be opened or if
|
||||
// it is not a valid ozz skeleton archive. A valid skeleton archive can be
|
||||
// produced with ozz tools (fbx2ozz) or using ozz skeleton serialization API.
|
||||
// _filename and _skeleton must be non-nullptr.
|
||||
bool LoadSkeleton(const char* _filename, ozz::animation::Skeleton* _skeleton);
|
||||
|
||||
// Loads an animation from an ozz archive file named _filename.
|
||||
// This function will fail and return false if the file cannot be opened or if
|
||||
// it is not a valid ozz animation archive. A valid animation archive can be
|
||||
// produced with ozz tools (fbx2ozz) or using ozz animation serialization API.
|
||||
// _filename and _animation must be non-nullptr.
|
||||
bool LoadAnimation(const char* _filename,
|
||||
ozz::animation::Animation* _animation);
|
||||
|
||||
// Loads a float track from an ozz archive file named _filename.
|
||||
// This function will fail and return false if the file cannot be opened or if
|
||||
// it is not a valid ozz float track archive. A valid float track archive can be
|
||||
// produced with ozz tools (fbx2ozz) or using ozz serialization API.
|
||||
// _filename and _track must be non-nullptr.
|
||||
bool LoadTrack(const char* _filename, ozz::animation::FloatTrack* _track);
|
||||
bool LoadTrack(const char* _filename, ozz::animation::Float2Track* _track);
|
||||
bool LoadTrack(const char* _filename, ozz::animation::Float3Track* _track);
|
||||
bool LoadTrack(const char* _filename, ozz::animation::Float4Track* _track);
|
||||
bool LoadTrack(const char* _filename, ozz::animation::QuaternionTrack* _track);
|
||||
|
||||
// Loads a sample::Mesh from an ozz archive file named _filename.
|
||||
// This function will fail and return false if the file cannot be opened or if
|
||||
// it is not a valid ozz mesh archive. A valid mesh archive can be
|
||||
// serialization API.
|
||||
// _filename and _mesh must be non-nullptr.
|
||||
bool LoadMesh(const char* _filename, ozz::sample::Mesh* _mesh);
|
||||
|
||||
// Loads n sample::Mesh from an ozz archive file named _filename.
|
||||
// This function will fail and return false if the file cannot be opened or if
|
||||
// it is not a valid ozz mesh archive. A valid mesh archive can be
|
||||
// produced with ozz tools (fbx2skin) or using ozz animation serialization API.
|
||||
// _filename and _mesh must be non-nullptr.
|
||||
bool LoadMeshes(const char* _filename, ozz::vector<ozz::sample::Mesh>* _meshes);
|
||||
|
||||
// Intersect _mesh with the half-line extending from _ray_origin indefinitely in
|
||||
// _ray_direction only. Returns true if there was an intersection. Fills
|
||||
// intersection point and normal if provided, with the closest intersecting
|
||||
// triangle from _ray_origin. Only supports non-skinned, single part meshes.
|
||||
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);
|
||||
|
||||
// Intersect _meshes with the half-line extending from _ray_origin indefinitely
|
||||
// in _ray_direction only. See RayIntersectsMesh.
|
||||
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);
|
||||
} // namespace sample
|
||||
} // namespace ozz
|
||||
#endif // OZZ_SAMPLES_FRAMEWORK_UTILS_H_
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
$<$<BOOL:${ozz_build_fbx}>:BUILD_DATA_SAMPLE>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_crossarms.ozz"
|
||||
"${ozz_media_directory}/bin/arnaud_mesh.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crossarms.ozz" "./media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/arnaud_mesh.ozz" "./media/mesh.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_look_at
|
||||
sample_look_at.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz")
|
||||
target_link_libraries(sample_look_at
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_look_at
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_look_at
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_look_at.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_look_at.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_look_at.wasm
|
||||
DESTINATION bin/samples/look_at)
|
||||
else()
|
||||
install(TARGETS sample_look_at DESTINATION bin/samples/look_at)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/look_at)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/look_at)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_look_at COMMAND sample_look_at "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_look_at_path1 COMMAND sample_look_at "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--mesh=media/mesh.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_look_at_invalid_skeleton_path COMMAND sample_look_at "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_look_at_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_look_at_invalid_animation_path COMMAND sample_look_at "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_look_at_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_look_at_invalid_mesh_path COMMAND sample_look_at "--mesh=media/bad_mesh.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_look_at_invalid_mesh_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,36 @@
|
||||
# Ozz-animation sample: Look-at
|
||||
|
||||
## Description
|
||||
|
||||
Procedural look-at objective is to orientate character head in a direction (at a target position), without using any animation data. This comes as a correction on (some) joint(s) rotation, on top of current animation. The character could still use animation as a base look-at depending on target position, and use the procedural correction for the final/precise adjustment.
|
||||
|
||||
## Concept
|
||||
|
||||
The sample relies on ozz::animation::IKAimJob which allows a single joint to aim at a targeted position. It extends the basic IKAimJob usage, by iteratively applying IK to head's ancestor joints, allowing to spread the rotation over more joints for a more realistic result. ozz::animation::IKAimJob::offset member (offset position from the joint in local-space) is a key component to achieve this result. It's used at first to tune eyes position compared to the head joint. During later iterations (for head ancestors), offset becomes even more relevant as the distance to the joint increases.
|
||||
|
||||
This same concept can be applied to aim a weapon at a precise target.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample allows to move target position or change its animation settings. If also exposes important setup aspects of the IK chain:
|
||||
- Chain length: Number of joints (from the predefined hierarchy) that are corrected by IK.
|
||||
- Joint weight: Weight given to IK for each joint of the chain (except for the last joint which is 1). This directly affect correction spreading.
|
||||
- Chain weight: Weight given to the whole look-at process, allowing bending in and out.
|
||||
Debug settings are exposed to help understanding:
|
||||
- Displaying character forward vector is interesting to confirm algorithm precision.
|
||||
- Displaying joints part of the look-at chain helps understand how weight and correction are spread.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. At initialization time:
|
||||
1. Loads skeleton, animation and mesh. See "playback" sample for more details.
|
||||
2. Locates head joint and some of its ancestors, searching them by name.
|
||||
2. At run time:
|
||||
1. Updates base animation and skeleton joints model-space matrices. See "playback" sample for more details.
|
||||
2. Iteratively updates from the first joint (closer to the head) to the last (the further ancestor, closer to the pelvis). For the first joint, aim IK is applied with the global forward and offset, so the forward vector aligns in direction of the target.
|
||||
3. If a weight lower that 1 is provided to the first joint, then it will not fully align to the target. In this case further joint will need to be updated:
|
||||
1. Rotates forward and offset position based on the result of the previous joint IK.
|
||||
2. Brings forward and offset back in next joint local-space.
|
||||
3. Applies IKAimJob.
|
||||
4. Aim is iteratively applied up to the last selected joint of the hierarchy. A weight of 1 is given to the last joint so we can guarantee target is reached.
|
||||
5. Updates model-space transforms. Note that they don't need to be updated between each pass, as joints are ordered from child to parent.
|
||||
@@ -0,0 +1,533 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/ik_aim_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_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/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/mesh.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Mesh archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(mesh,
|
||||
"Path to the skinned mesh (ozz archive format).",
|
||||
"media/mesh.ozz", false)
|
||||
|
||||
// Defines IK chain joint names.
|
||||
// Joints must be from the same hierarchy (all ancestors of the first joint
|
||||
// listed) and ordered from child to parent.
|
||||
const char* kJointNames[] = {"Head", "Spine3", "Spine2", "Spine1"};
|
||||
const size_t kMaxChainLength = OZZ_ARRAY_SIZE(kJointNames);
|
||||
|
||||
// Forward vector in head local-space.
|
||||
const ozz::math::SimdFloat4 kHeadForward = ozz::math::simd_float4::y_axis();
|
||||
|
||||
// Defines Up vectors for each joint. This is skeleton/rig dependant.
|
||||
const ozz::math::SimdFloat4 kJointUpVectors[] = {
|
||||
ozz::math::simd_float4::x_axis(), ozz::math::simd_float4::x_axis(),
|
||||
ozz::math::simd_float4::x_axis(), ozz::math::simd_float4::x_axis()};
|
||||
static_assert(OZZ_ARRAY_SIZE(kJointUpVectors) == kMaxChainLength,
|
||||
"Array size mismatch.");
|
||||
|
||||
class LookAtSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
LookAtSampleApplication()
|
||||
: target_offset_(.2f, 1.5f, -.3f),
|
||||
target_extent_(1.f),
|
||||
eyes_offset_(.07f, .1f, 0.f),
|
||||
enable_ik_(true),
|
||||
chain_length_(kMaxChainLength),
|
||||
joint_weight_(.5f),
|
||||
chain_weight_(1.f),
|
||||
show_skin_(true),
|
||||
show_joints_(false),
|
||||
show_target_(true),
|
||||
show_eyes_offset_(false),
|
||||
show_forward_(false) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float _time) {
|
||||
// Animates target position.
|
||||
MoveTarget(_time);
|
||||
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local-space to model-space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
// Early out if IK is disabled.
|
||||
if (!enable_ik_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IK aim job setup.
|
||||
ozz::animation::IKAimJob ik_job;
|
||||
|
||||
// Pole vector and target position are constant for the whole algorithm, in
|
||||
// model-space.
|
||||
ik_job.pole_vector = ozz::math::simd_float4::y_axis();
|
||||
ik_job.target = ozz::math::simd_float4::Load3PtrU(&target_.x);
|
||||
|
||||
// The same quaternion will be used each time the job is run.
|
||||
ozz::math::SimdQuaternion correction;
|
||||
ik_job.joint_correction = &correction;
|
||||
|
||||
// The algorithm iteratively updates from the first joint (closer to the
|
||||
// head) to the last (the further ancestor, closer to the pelvis). Joints
|
||||
// order is already validated. For the first joint, aim IK is applied with
|
||||
// the global forward and offset, so the forward vector aligns in direction
|
||||
// of the target. If a weight lower that 1 is provided to the first joint,
|
||||
// then it will not fully align to the target. In this case further joint
|
||||
// will need to be updated. For the remaining joints, forward vector and
|
||||
// offset position are computed in each joint local-space, before IK is
|
||||
// applied:
|
||||
// 1. Rotates forward and offset position based on the result of the
|
||||
// previous joint IK.
|
||||
// 2. Brings forward and offset back in joint local-space.
|
||||
// Aim is iteratively applied up to the last selected joint of the
|
||||
// hierarchy. A weight of 1 is given to the last joint so we can guarantee
|
||||
// target is reached. Note that model-space transform of each joint doesn't
|
||||
// need to be updated between each pass, as joints are ordered from child to
|
||||
// parent.
|
||||
int previous_joint = ozz::animation::Skeleton::kNoParent;
|
||||
for (int i = 0, joint = joints_chain_[0]; i < chain_length_;
|
||||
++i, previous_joint = joint, joint = joints_chain_[i]) {
|
||||
// Setups the model-space matrix of the joint being processed by IK.
|
||||
ik_job.joint = &models_[joint];
|
||||
|
||||
// Setups joint local-space up vector.
|
||||
ik_job.up = kJointUpVectors[i];
|
||||
|
||||
// Setups weights of IK job.
|
||||
// the last joint being processed needs a full weight (1.f) to ensure
|
||||
// target is reached.
|
||||
const bool last = i == chain_length_ - 1;
|
||||
ik_job.weight = chain_weight_ * (last ? 1.f : joint_weight_);
|
||||
|
||||
// Setup offset and forward vector for the current joint being processed.
|
||||
if (i == 0) {
|
||||
// First joint, uses global forward and offset.
|
||||
ik_job.offset = ozz::math::simd_float4::Load3PtrU(&eyes_offset_.x);
|
||||
ik_job.forward = kHeadForward;
|
||||
} else {
|
||||
// Applies previous correction to "forward" and "offset", before
|
||||
// bringing them to model-space (_ms).
|
||||
const ozz::math::SimdFloat4 corrected_forward_ms =
|
||||
TransformVector(models_[previous_joint],
|
||||
TransformVector(correction, ik_job.forward));
|
||||
const ozz::math::SimdFloat4 corrected_offset_ms =
|
||||
TransformPoint(models_[previous_joint],
|
||||
TransformVector(correction, ik_job.offset));
|
||||
|
||||
// Brings "forward" and "offset" to joint local-space
|
||||
const ozz::math::Float4x4 inv_joint = Invert(models_[joint]);
|
||||
ik_job.forward = TransformVector(inv_joint, corrected_forward_ms);
|
||||
ik_job.offset = TransformPoint(inv_joint, corrected_offset_ms);
|
||||
}
|
||||
|
||||
// Runs IK aim job.
|
||||
if (!ik_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply IK quaternion to its respective local-space transforms.
|
||||
ozz::sample::MultiplySoATransformQuaternion(joint, correction,
|
||||
make_span(locals_));
|
||||
}
|
||||
|
||||
// Skeleton model-space matrices need to be updated again. This re-uses the
|
||||
// already setup job, but limits the update to childs of the last joint (the
|
||||
// parent-iest of the chain).
|
||||
ltm_job.from = previous_joint;
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sample arbitrary target animation implementation.
|
||||
bool MoveTarget(float _time) {
|
||||
const ozz::math::Float3 animated_target(std::sin(_time * .5f),
|
||||
std::cos(_time * .25f),
|
||||
std::cos(_time) * .5f + .5f);
|
||||
target_ = target_offset_ + animated_target * target_extent_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
const ozz::math::Float4x4 identity = ozz::math::Float4x4::identity();
|
||||
const float kAxeScale = .1f;
|
||||
const ozz::math::Float4x4 kAxesScale =
|
||||
ozz::math::Float4x4::Scaling(ozz::math::simd_float4::Load1(kAxeScale));
|
||||
|
||||
// Renders character.
|
||||
if (show_skin_) {
|
||||
// Builds skinning matrices.
|
||||
// The mesh might not use (aka be skinned by) all skeleton joints. We
|
||||
// use the joint remapping table (available from the mesh object) to
|
||||
// reorder model-space matrices and build skinning ones.
|
||||
for (size_t m = 0; m < meshes_.size(); ++m) {
|
||||
const ozz::sample::Mesh& mesh = meshes_[m];
|
||||
for (size_t i = 0; i < mesh.joint_remaps.size(); ++i) {
|
||||
skinning_matrices_[i] =
|
||||
models_[mesh.joint_remaps[i]] * mesh.inverse_bind_poses[i];
|
||||
}
|
||||
|
||||
success &= _renderer->DrawSkinnedMesh(
|
||||
mesh, make_span(skinning_matrices_), identity);
|
||||
}
|
||||
} else {
|
||||
// Renders skeleton only.
|
||||
success &=
|
||||
_renderer->DrawPosture(skeleton_, make_span(models_), identity);
|
||||
}
|
||||
|
||||
// Showing joints
|
||||
if (show_joints_) {
|
||||
const float kSphereRadius = .02f;
|
||||
for (int i = 0; i < chain_length_; ++i) {
|
||||
const ozz::math::Float4x4& transform = models_[joints_chain_[i]];
|
||||
success &= _renderer->DrawAxes(transform * kAxesScale);
|
||||
success &= _renderer->DrawSphereIm(kSphereRadius, transform,
|
||||
ozz::sample::kWhite);
|
||||
}
|
||||
}
|
||||
|
||||
// Showing target, as a box or axes depending on show_forward_ option.
|
||||
if (show_target_) {
|
||||
const ozz::math::Float4x4 target = ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::Load3PtrU(&target_.x));
|
||||
if (show_forward_) {
|
||||
success &= _renderer->DrawAxes(target * kAxesScale);
|
||||
} else {
|
||||
success &= _renderer->DrawSphereIm(.02f, target, ozz::sample::kGreen);
|
||||
}
|
||||
}
|
||||
|
||||
if (show_eyes_offset_ || show_forward_) {
|
||||
const int head = joints_chain_[0];
|
||||
const ozz::math::Float4x4 offset =
|
||||
models_[head] *
|
||||
ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::Load3PtrU(&eyes_offset_.x));
|
||||
if (show_eyes_offset_) {
|
||||
success &= _renderer->DrawAxes(offset * kAxesScale);
|
||||
}
|
||||
if (show_forward_) {
|
||||
ozz::math::Float3 begin;
|
||||
ozz::math::Store3PtrU(offset.cols[3], &begin.x);
|
||||
ozz::math::Float3 forward;
|
||||
ozz::math::Store3PtrU(kHeadForward, &forward.x);
|
||||
ozz::sample::Color color = {0xff, 0xff, 0xff, 0xff};
|
||||
success &= _renderer->DrawSegment(ozz::math::Float3::zero(),
|
||||
forward * 10.f, color, offset);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for each joint in the chain.
|
||||
int found = 0;
|
||||
for (int i = 0; i < skeleton_.num_joints() && found != kMaxChainLength;
|
||||
++i) {
|
||||
const char* joint_name = skeleton_.joint_names()[i];
|
||||
if (std::strcmp(joint_name, kJointNames[found]) == 0) {
|
||||
joints_chain_[found] = i;
|
||||
|
||||
// Restart search
|
||||
++found;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if all joints weren't found.
|
||||
if (found != kMaxChainLength) {
|
||||
ozz::log::Err()
|
||||
<< "At least a joint wasn't found in the skeleton hierarchy."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validates joints are order from child to parent of the same hierarchy.
|
||||
if (!ValidateJointsOrder(skeleton_, joints_chain_)) {
|
||||
ozz::log::Err() << "Joints aren't properly ordered, they must be from "
|
||||
"the same hierarchy (all ancestors of the first joint "
|
||||
"listed) and ordered from child to parent."
|
||||
<< std::endl;
|
||||
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);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading character mesh.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_mesh, &meshes_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The number of joints of the mesh needs to match skeleton.
|
||||
for (size_t m = 0; m < meshes_.size(); ++m) {
|
||||
const ozz::sample::Mesh& mesh = meshes_[m];
|
||||
if (num_joints < mesh.highest_joint_index()) {
|
||||
ozz::log::Err() << "The provided mesh doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
skinning_matrices_.resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Traverses the hierarchy from the first joint to the root, to check if
|
||||
// joints are all ancestors (same branch), and ordered.
|
||||
bool ValidateJointsOrder(const ozz::animation::Skeleton& _skeleton,
|
||||
ozz::span<const int> _joints) {
|
||||
const size_t count = _joints.size();
|
||||
if (count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t i = 1;
|
||||
for (int joint = _joints[0], parent = _skeleton.joint_parents()[joint];
|
||||
i != count && joint != ozz::animation::Skeleton::kNoParent;
|
||||
joint = parent, parent = _skeleton.joint_parents()[joint]) {
|
||||
if (parent == _joints[i]) {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return count == i;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char txt[64];
|
||||
|
||||
_im_gui->DoCheckBox("Enable ik", &enable_ik_);
|
||||
sprintf(txt, "IK chain length: %d", chain_length_);
|
||||
_im_gui->DoSlider(txt, 0, kMaxChainLength, &chain_length_);
|
||||
sprintf(txt, "Joint weight %.2g", joint_weight_);
|
||||
_im_gui->DoSlider(txt, 0.f, 1.f, &joint_weight_);
|
||||
sprintf(txt, "Chain weight %.2g", chain_weight_);
|
||||
_im_gui->DoSlider(txt, 0.f, 1.f, &chain_weight_);
|
||||
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Target position
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Target offset", &opened);
|
||||
if (opened) {
|
||||
const float kTargetRange = 3.f;
|
||||
|
||||
_im_gui->DoLabel("Animated extent");
|
||||
sprintf(txt, "%.2g", target_extent_);
|
||||
_im_gui->DoSlider(txt, 0.f, kTargetRange, &target_extent_);
|
||||
|
||||
sprintf(txt, "x %.2g", target_offset_.x);
|
||||
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.x);
|
||||
sprintf(txt, "y %.2g", target_offset_.y);
|
||||
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.y);
|
||||
sprintf(txt, "z %.2g", target_offset_.z);
|
||||
_im_gui->DoSlider(txt, -kTargetRange, kTargetRange, &target_offset_.z);
|
||||
}
|
||||
}
|
||||
|
||||
{ // Offset position
|
||||
static bool opened = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Eyes offset", &opened);
|
||||
if (opened) {
|
||||
const float kOffsetRange = .5f;
|
||||
sprintf(txt, "x %.2g", eyes_offset_.x);
|
||||
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.x);
|
||||
sprintf(txt, "y %.2g", eyes_offset_.y);
|
||||
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.y);
|
||||
sprintf(txt, "z %.2g", eyes_offset_.z);
|
||||
_im_gui->DoSlider(txt, -kOffsetRange, kOffsetRange, &eyes_offset_.z);
|
||||
}
|
||||
}
|
||||
|
||||
// Options
|
||||
{
|
||||
_im_gui->DoCheckBox("Show skin", &show_skin_);
|
||||
_im_gui->DoCheckBox("Show joints", &show_joints_);
|
||||
_im_gui->DoCheckBox("Show target", &show_target_);
|
||||
_im_gui->DoCheckBox("Show eyes offset", &show_eyes_offset_);
|
||||
_im_gui->DoCheckBox("Show forward", &show_forward_);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
const ozz::math::Float3 radius(target_extent_ * .8f);
|
||||
_bound->min = target_offset_ - radius;
|
||||
_bound->max = target_offset_ + radius;
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Buffer of skinning matrices, result of the joint multiplication of the
|
||||
// inverse bind pose with the model-space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
ozz::vector<ozz::sample::Mesh> meshes_;
|
||||
|
||||
// Indices of the joints that are IKed for look-at purpose.
|
||||
// Joints must be from the same hierarchy (all ancestors of the first joint
|
||||
// listed) and ordered from child to parent.
|
||||
int joints_chain_[kMaxChainLength];
|
||||
|
||||
// Sample settings
|
||||
|
||||
// Target position management.
|
||||
ozz::math::Float3 target_offset_;
|
||||
float target_extent_;
|
||||
ozz::math::Float3 target_;
|
||||
|
||||
// Offset of the look at position in (head) joint local-space.
|
||||
ozz::math::Float3 eyes_offset_;
|
||||
|
||||
// IK settings
|
||||
|
||||
// Enable IK look at.
|
||||
bool enable_ik_;
|
||||
|
||||
// Set length of the chain that is IKed, between 0 and kMaxChainLength.
|
||||
int chain_length_;
|
||||
|
||||
// Weight given to every joint of the chain. If any joint has a weight of 1,
|
||||
// no other following joint will contribute (as the target will be reached).
|
||||
float joint_weight_;
|
||||
|
||||
// Overall weight given to the IK on the full chain. This allows blending in
|
||||
// and out of IK.
|
||||
float chain_weight_;
|
||||
|
||||
// Options
|
||||
bool show_skin_;
|
||||
bool show_joints_;
|
||||
bool show_target_;
|
||||
bool show_eyes_offset_;
|
||||
bool show_forward_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Look at";
|
||||
return LookAtSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
add_custom_command(
|
||||
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/README.md .
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_millipede
|
||||
sample_millipede.cc
|
||||
${CMAKE_CURRENT_BINARY_DIR}/README.md)
|
||||
|
||||
target_link_libraries(sample_millipede
|
||||
ozz_animation_offline
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_millipede
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set_target_properties(sample_millipede
|
||||
PROPERTIES LINK_FLAGS "--embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_millipede.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_millipede.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_millipede.wasm
|
||||
DESTINATION bin/samples/millipede)
|
||||
else()
|
||||
install(TARGETS sample_millipede DESTINATION bin/samples/millipede)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/millipede)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_millipede COMMAND sample_millipede "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
@@ -0,0 +1,28 @@
|
||||
# Ozz-animation sample: Millipede, offline libraries usage.
|
||||
|
||||
## Description
|
||||
|
||||
Demonstrates usage of ozz offline data structures and utilities.
|
||||
|
||||
## Concept
|
||||
|
||||
Procedurally creates an offline skeleton and animation, whose are then converted to runtime structures for sampling and rendering. The procedural skeleton aims to look like a millipede, made of slices of a leg pair and a vertebra. It makes it easy to tweak the number of joints by adding/removing slices. Both the skeleton and the animation are rebuilt from scratch when the number of joints is changed.
|
||||
This sample does not intend to demonstrate how to do procedural runtime animations, which would rather require to work directly on local-transforms (the output of animation sampling stage, input of the blending stage).
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample provides the GUI to tweak the number of joints, from 7 (1 slice) up to the maximum number of joints supported by ozz (currently 1023).
|
||||
Some other playback parameters can be tuned:
|
||||
- Play/pause animation.
|
||||
- Fix animation time.
|
||||
- Set playback speed, which can be negative to go backward.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Create the skeleton object:
|
||||
1. Instantiates a RawSkeleton and fills the hierarchy with as many slices (a vertebra and two legs) as needed to reach the requested number of joints. The RawSkeleton object is an offline suitable format, meaning it can be easily programmatically modified: Add/remove/rename joints...
|
||||
2. Convert the RawSkeleton (aka offline) object to a runtime ozz::animation::offline::Skeleton, using ozz::animation::offline::SkeletonBuilder. This offline utility does the conversion to the runtime format, which can then be serialized or used by the runtime API.
|
||||
2. Create the animation object:
|
||||
1. Instantiates a ozz::animation::offline::RawAnimation object and fills it with keyframes for every joint of the skeleton, simulating walk cycles. The RawAnimation is an offline suitable format, meaning it is easily programmatically modified: Add/remove keys, add/remove tracks, change duration...
|
||||
2. Convert the offline animation to a runtime format, using ozz::animation::offline::AnimationBuilder. This utility takes as input a RawAnimation and outputs a runtime ozz::animation::Animation. The runtime format is the one used for sampling. In opposition with the offline one, it cannot be edited/modified. It is optimized for runtime usage in term of memory layout (cache coherence for sampling) and footprint (compression scheme).
|
||||
3. The remaining code of the sample is to allocate runtime buffers and samples animation every frame. See playback sample for more details.
|
||||
@@ -0,0 +1,440 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/raw_skeleton.h"
|
||||
#include "ozz/animation/offline/skeleton_builder.h"
|
||||
|
||||
#include "ozz/base/maths/quaternion.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
using ozz::animation::offline::RawAnimation;
|
||||
using ozz::animation::offline::RawSkeleton;
|
||||
using ozz::math::Float3;
|
||||
using ozz::math::Float4;
|
||||
using ozz::math::Float4x4;
|
||||
using ozz::math::Quaternion;
|
||||
using ozz::math::SoaTransform;
|
||||
|
||||
// A millipede slice is 2 legs and a spine.
|
||||
// Each slice is made of 7 joints, organized as follows.
|
||||
// * root
|
||||
// |
|
||||
// spine spine
|
||||
// | | |
|
||||
// left_up right_up left_down - left_u - . - right_u - right_down
|
||||
// | | | |
|
||||
// left_down right_down left_foot * root right_foot
|
||||
// | |
|
||||
// left_foot right_foot
|
||||
|
||||
// The following constants are used to define the millipede skeleton and
|
||||
// animation.
|
||||
// Skeleton constants.
|
||||
const Float3 kTransUp = Float3(0.f, 0.f, 0.f);
|
||||
const Float3 kTransDown = Float3(0.f, 0.f, 1.f);
|
||||
const Float3 kTransFoot = Float3(1.f, 0.f, 0.f);
|
||||
|
||||
const Quaternion kRotLeftUp =
|
||||
Quaternion::FromAxisAngle(Float3::y_axis(), -ozz::math::kPi_2);
|
||||
const Quaternion kRotLeftDown =
|
||||
Quaternion::FromAxisAngle(Float3::x_axis(), ozz::math::kPi_2) *
|
||||
Quaternion::FromAxisAngle(Float3::y_axis(), -ozz::math::kPi_2);
|
||||
const Quaternion kRotRightUp =
|
||||
Quaternion::FromAxisAngle(Float3::y_axis(), ozz::math::kPi_2);
|
||||
const Quaternion kRotRightDown =
|
||||
Quaternion::FromAxisAngle(Float3::x_axis(), ozz::math::kPi_2) *
|
||||
Quaternion::FromAxisAngle(Float3::y_axis(), -ozz::math::kPi_2);
|
||||
|
||||
// Animation constants.
|
||||
const float kDuration = 6.f;
|
||||
const float kSpinLength = .5f;
|
||||
const float kWalkCycleLength = 2.f;
|
||||
const int kWalkCycleCount = 4;
|
||||
const float kSpinLoop = 2 * kWalkCycleCount * kWalkCycleLength / kSpinLength;
|
||||
|
||||
const RawAnimation::TranslationKey kPrecomputedKeys[] = {
|
||||
{0.f * kDuration, Float3(.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.125f * kDuration, Float3(-.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.145f * kDuration, Float3(-.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.23f * kDuration, Float3(.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.25f * kDuration, Float3(.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.375f * kDuration, Float3(-.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.395f * kDuration, Float3(-.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.48f * kDuration, Float3(.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.5f * kDuration, Float3(.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.625f * kDuration, Float3(-.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.645f * kDuration, Float3(-.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.73f * kDuration, Float3(.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.75f * kDuration, Float3(.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.875f * kDuration, Float3(-.25f * kWalkCycleLength, 0.f, 0.f)},
|
||||
{.895f * kDuration, Float3(-.17f * kWalkCycleLength, .3f, 0.f)},
|
||||
{.98f * kDuration, Float3(.17f * kWalkCycleLength, .3f, 0.f)}};
|
||||
const int kPrecomputedKeyCount = OZZ_ARRAY_SIZE(kPrecomputedKeys);
|
||||
|
||||
class MillipedeSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
MillipedeSampleApplication() : slice_count_(26) {}
|
||||
|
||||
protected:
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time
|
||||
controller_.Update(*animation_, _dt);
|
||||
|
||||
// Samples animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = animation_.get();
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = skeleton_.get();
|
||||
ltm_job.input = make_span(locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
return ltm_job.Run();
|
||||
}
|
||||
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
// Renders the animated posture.
|
||||
return _renderer->DrawPosture(*skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() { return Build(); }
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Rebuilds all if the number of joints has changed.
|
||||
int joints = skeleton_->num_joints();
|
||||
char label[64];
|
||||
std::sprintf(label, "Joints count: %d", joints);
|
||||
|
||||
// Uses an exponential scale in the slider to maintain enough precision in
|
||||
// the lowest values.
|
||||
if (_im_gui->DoSlider(label, 8, ozz::animation::Skeleton::kMaxJoints,
|
||||
&joints, .3f, true)) {
|
||||
const int new_slice_count = (joints - 1) / 7;
|
||||
// Slider use floats, we need to check if it has really changed.
|
||||
if (new_slice_count != slice_count_) {
|
||||
slice_count_ = new_slice_count;
|
||||
if (!Build()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updates controller Gui.
|
||||
controller_.OnGui(*animation_, _im_gui);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Procedurally builds millipede skeleton and walk animation
|
||||
bool Build() {
|
||||
// Initializes the root. The root pointer will change from a spine to the
|
||||
// next for each slice.
|
||||
RawSkeleton raw_skeleton;
|
||||
CreateSkeleton(&raw_skeleton);
|
||||
const int num_joints = raw_skeleton.num_joints();
|
||||
|
||||
// Build the run time skeleton.
|
||||
ozz::animation::offline::SkeletonBuilder skeleton_builder;
|
||||
skeleton_ = skeleton_builder(raw_skeleton);
|
||||
if (!skeleton_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build a walk animation.
|
||||
RawAnimation raw_animation;
|
||||
CreateAnimation(&raw_animation);
|
||||
|
||||
// Build the run time animation from the raw animation.
|
||||
ozz::animation::offline::AnimationBuilder animation_builder;
|
||||
animation_ = animation_builder(raw_animation);
|
||||
if (!animation_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
const int num_soa_joints = skeleton_->num_soa_joints();
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches new animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateSkeleton(ozz::animation::offline::RawSkeleton* _skeleton) {
|
||||
_skeleton->roots.resize(1);
|
||||
RawSkeleton::Joint* root = &_skeleton->roots[0];
|
||||
root->name = "root";
|
||||
root->transform.translation = Float3(0.f, 1.f, -slice_count_ * kSpinLength);
|
||||
root->transform.rotation = Quaternion::identity();
|
||||
root->transform.scale = Float3::one();
|
||||
|
||||
char buf[16];
|
||||
for (int i = 0; i < slice_count_; ++i) {
|
||||
// Format joint number.
|
||||
std::sprintf(buf, "%d", i);
|
||||
|
||||
root->children.resize(3);
|
||||
|
||||
// Left leg.
|
||||
RawSkeleton::Joint& lu = root->children[0];
|
||||
lu.name = "lu";
|
||||
lu.name += buf;
|
||||
lu.transform.translation = kTransUp;
|
||||
lu.transform.rotation = kRotLeftUp;
|
||||
lu.transform.scale = Float3::one();
|
||||
|
||||
lu.children.resize(1);
|
||||
RawSkeleton::Joint& ld = lu.children[0];
|
||||
ld.name = "ld";
|
||||
ld.name += buf;
|
||||
ld.transform.translation = kTransDown;
|
||||
ld.transform.rotation = kRotLeftDown;
|
||||
ld.transform.scale = Float3::one();
|
||||
|
||||
ld.children.resize(1);
|
||||
RawSkeleton::Joint& lf = ld.children[0];
|
||||
lf.name = "lf";
|
||||
lf.name += buf;
|
||||
lf.transform.translation = Float3::x_axis();
|
||||
lf.transform.rotation = Quaternion::identity();
|
||||
lf.transform.scale = Float3::one();
|
||||
|
||||
// Right leg.
|
||||
RawSkeleton::Joint& ru = root->children[1];
|
||||
ru.name = "ru";
|
||||
ru.name += buf;
|
||||
ru.transform.translation = kTransUp;
|
||||
ru.transform.rotation = kRotRightUp;
|
||||
ru.transform.scale = Float3::one();
|
||||
|
||||
ru.children.resize(1);
|
||||
RawSkeleton::Joint& rd = ru.children[0];
|
||||
rd.name = "rd";
|
||||
rd.name += buf;
|
||||
rd.transform.translation = kTransDown;
|
||||
rd.transform.rotation = kRotRightDown;
|
||||
rd.transform.scale = Float3::one();
|
||||
|
||||
rd.children.resize(1);
|
||||
RawSkeleton::Joint& rf = rd.children[0];
|
||||
rf.name = "rf";
|
||||
rf.name += buf;
|
||||
rf.transform.translation = Float3::x_axis();
|
||||
rf.transform.rotation = Quaternion::identity();
|
||||
rf.transform.scale = Float3::one();
|
||||
|
||||
// Spine.
|
||||
RawSkeleton::Joint& sp = root->children[2];
|
||||
sp.name = "sp";
|
||||
sp.name += buf;
|
||||
sp.transform.translation = Float3(0.f, 0.f, kSpinLength);
|
||||
sp.transform.rotation = Quaternion::identity();
|
||||
sp.transform.scale = Float3::one();
|
||||
|
||||
root = &sp;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAnimation(ozz::animation::offline::RawAnimation* _animation) {
|
||||
_animation->duration = kDuration;
|
||||
_animation->tracks.resize(skeleton_->num_joints());
|
||||
|
||||
for (int i = 0; i < _animation->num_tracks(); ++i) {
|
||||
RawAnimation::JointTrack& track = _animation->tracks[i];
|
||||
const char* joint_name = skeleton_->joint_names()[i];
|
||||
|
||||
if (strstr(joint_name, "ld") || strstr(joint_name, "rd")) {
|
||||
bool left = joint_name[0] == 'l'; // First letter of "ld".
|
||||
|
||||
// Copy original keys while taking into consideration the spine number
|
||||
// as a phase.
|
||||
const int spine_number = std::atoi(joint_name + 2);
|
||||
const float offset =
|
||||
kDuration * (slice_count_ - spine_number) / kSpinLoop;
|
||||
const float phase = std::fmod(offset, kDuration);
|
||||
|
||||
// Loop to find animation start.
|
||||
int i_offset = 0;
|
||||
while (i_offset < kPrecomputedKeyCount &&
|
||||
kPrecomputedKeys[i_offset].time < phase) {
|
||||
i_offset++;
|
||||
}
|
||||
|
||||
// Push key with their corrected time.
|
||||
track.translations.reserve(kPrecomputedKeyCount);
|
||||
for (int j = i_offset; j < i_offset + kPrecomputedKeyCount; ++j) {
|
||||
const RawAnimation::TranslationKey& rkey =
|
||||
kPrecomputedKeys[j % kPrecomputedKeyCount];
|
||||
float new_time = rkey.time - phase;
|
||||
if (new_time < 0.f) {
|
||||
new_time = kDuration - phase + rkey.time;
|
||||
}
|
||||
|
||||
if (left) {
|
||||
const RawAnimation::TranslationKey tkey = {new_time,
|
||||
kTransDown + rkey.value};
|
||||
track.translations.push_back(tkey);
|
||||
} else {
|
||||
const RawAnimation::TranslationKey tkey = {
|
||||
new_time,
|
||||
Float3(kTransDown.x - rkey.value.x, kTransDown.y + rkey.value.y,
|
||||
kTransDown.z + rkey.value.z)};
|
||||
track.translations.push_back(tkey);
|
||||
}
|
||||
}
|
||||
|
||||
// Pushes rotation key-frame.
|
||||
if (left) {
|
||||
const RawAnimation::RotationKey rkey = {0.f, kRotLeftDown};
|
||||
track.rotations.push_back(rkey);
|
||||
} else {
|
||||
const RawAnimation::RotationKey rkey = {0.f, kRotRightDown};
|
||||
track.rotations.push_back(rkey);
|
||||
}
|
||||
} else if (strstr(joint_name, "lu")) {
|
||||
const RawAnimation::TranslationKey tkey = {0.f, kTransUp};
|
||||
track.translations.push_back(tkey);
|
||||
|
||||
const RawAnimation::RotationKey rkey = {0.f, kRotLeftUp};
|
||||
track.rotations.push_back(rkey);
|
||||
|
||||
} else if (strstr(joint_name, "ru")) {
|
||||
const RawAnimation::TranslationKey tkey0 = {0.f, kTransUp};
|
||||
track.translations.push_back(tkey0);
|
||||
|
||||
const RawAnimation::RotationKey rkey0 = {0.f, kRotRightUp};
|
||||
track.rotations.push_back(rkey0);
|
||||
} else if (strstr(joint_name, "lf")) {
|
||||
const RawAnimation::TranslationKey tkey = {0.f, kTransFoot};
|
||||
track.translations.push_back(tkey);
|
||||
} else if (strstr(joint_name, "rf")) {
|
||||
const RawAnimation::TranslationKey tkey0 = {0.f, kTransFoot};
|
||||
track.translations.push_back(tkey0);
|
||||
} else if (strstr(joint_name, "sp")) {
|
||||
const RawAnimation::TranslationKey skey = {
|
||||
0.f, Float3(0.f, 0.f, kSpinLength)};
|
||||
track.translations.push_back(skey);
|
||||
|
||||
const RawAnimation::RotationKey rkey = {
|
||||
0.f, ozz::math::Quaternion::identity()};
|
||||
track.rotations.push_back(rkey);
|
||||
} else if (strstr(joint_name, "root")) {
|
||||
const RawAnimation::TranslationKey tkey0 = {
|
||||
0.f, Float3(0.f, 1.f, -slice_count_ * kSpinLength)};
|
||||
track.translations.push_back(tkey0);
|
||||
const RawAnimation::TranslationKey tkey1 = {
|
||||
kDuration,
|
||||
Float3(0.f, 1.f,
|
||||
kWalkCycleCount * kWalkCycleLength + tkey0.value.z)};
|
||||
track.translations.push_back(tkey1);
|
||||
}
|
||||
|
||||
// Make sure begin and end keys are looping.
|
||||
if (track.translations.front().time != 0.f) {
|
||||
const RawAnimation::TranslationKey& front = track.translations.front();
|
||||
const RawAnimation::TranslationKey& back = track.translations.back();
|
||||
const float lerp_time =
|
||||
front.time / (front.time + kDuration - back.time);
|
||||
const RawAnimation::TranslationKey tkey = {
|
||||
0.f, Lerp(front.value, back.value, lerp_time)};
|
||||
track.translations.insert(track.translations.begin(), tkey);
|
||||
}
|
||||
if (track.translations.back().time != kDuration) {
|
||||
const RawAnimation::TranslationKey& front = track.translations.front();
|
||||
const RawAnimation::TranslationKey& back = track.translations.back();
|
||||
const float lerp_time =
|
||||
(kDuration - back.time) / (front.time + kDuration - back.time);
|
||||
const RawAnimation::TranslationKey tkey = {
|
||||
kDuration, Lerp(back.value, front.value, lerp_time)};
|
||||
track.translations.push_back(tkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Millipede skeleton number of slices. 7 joints per slice.
|
||||
int slice_count_;
|
||||
|
||||
// The millipede skeleton.
|
||||
ozz::unique_ptr<ozz::animation::Skeleton> skeleton_;
|
||||
|
||||
// The millipede procedural walk animation.
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_;
|
||||
|
||||
// Sampling cache, as used by SamplingJob.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
// These are shared between sampling output and local-to-model input.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
// Buffer of model matrices (local-to-model output).
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: RawAnimation/RawSkeleton building";
|
||||
return MillipedeSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
if(EMSCRIPTEN)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Sample requires thread libraries
|
||||
find_package(Threads)
|
||||
if (NOT Threads_FOUND)
|
||||
message("Multithread sample discarded because threading libraries aren't available.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_walk.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_multithread
|
||||
sample_multithread.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
|
||||
target_link_libraries(sample_multithread
|
||||
sample_framework
|
||||
${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
set_target_properties(sample_multithread
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set_target_properties(sample_multithread PROPERTIES COMPILE_FLAGS "-s USE_PTHREADS=1")
|
||||
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_multithread
|
||||
PROPERTIES LINK_FLAGS "-s TOTAL_MEMORY=100663296 -s USE_PTHREADS=1 --embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_multithread.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_multithread.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_multithread.wasm
|
||||
DESTINATION bin/samples/multithread)
|
||||
else()
|
||||
install(TARGETS sample_multithread DESTINATION bin/samples/multithread)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/multithread)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/multithread)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_multithread COMMAND sample_multithread "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_multithread_path COMMAND sample_multithread "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_multithread_invalid_skeleton_path COMMAND sample_multithread "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_multithread_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_multithread_invalid_animation_path1 COMMAND sample_multithread "--animation1=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_multithread_invalid_animation_path1 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_multithread_invalid_animation_path2 COMMAND sample_multithread "--animation2=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_multithread_invalid_animation_path2 PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Ozz-animation sample: Parallelized animation update using c++11 std::async
|
||||
|
||||
## Description
|
||||
|
||||
The sample takes advantage of ozz jobs thread-safety to distribute sampling and local-to-model jobs across multiple threads. It uses c++11 std::async API to implement a parallel-for loop over all computation tasks.
|
||||
User can tweak the number of characters and the maximum number of characters per task. Animation control is automatically handled by the sample for all characters.
|
||||
|
||||
## Concept
|
||||
|
||||
All ozz jobs are thread-safe: ozz::animation::SamplingJob, ozz::animation::BlendingJob, ozz::animation::LocalToModelJob... This is an effect of the data-driven architecture, which makes a clear distinction between data and processes (aka jobs). Jobs' execution can thus be distributed to multiple threads safely, as long as the data provided as inputs and outputs do not create any race conditions.
|
||||
As a proof of concept, this sample uses a naive strategy: All characters' update (execution of their sampling and local-to-model stages, as demonstrated in playback sample) are distributed using a parallel-for loop, every frame. During initialization, every character is allocated all the data required for their own update, eliminating any dependency and race condition risk.
|
||||
|
||||
The parallel-for recursively splits the range of characters to process into subranges, to the point such that subrange is small enough (less than a predefined number of characters). The sample also implement a small trick in order to get the number of threads (from crt thread pool) that were used during the parallel-for execution.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample allows to switch multi-threading on/off and set the maximum number of characters that can be updated per task.
|
||||
The number of characters can also be set from the GUI.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. This sample extends "playback" sample, and uses the same procedure to load skeleton and animation objects.
|
||||
2. For each character, allocates runtime buffers (local-space transforms of type ozz::math::SoaTransform, model-space matrices of type ozz::math::Float4x4) with the number of elements required for the skeleton, and a sampling cache (ozz::animation::SamplingCache). Only the skeleton and the animation are shared amongst all characters, as they are read only objects, not modified during jobs execution.
|
||||
3. Update function uses a parallel-for loop to split up characters' update loop amongst std::async tasks (sampling and local-to-model jobs execution), allowing all characters' update to be executed in concurrent batches.
|
||||
@@ -0,0 +1,370 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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 <atomic>
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#if EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/threading.h>
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
"Path to the skeleton (ozz archive format).",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the first animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Interval between each character.
|
||||
const float kInterval = 2.f;
|
||||
|
||||
// Width and depth of characters repartition.
|
||||
const int kWidth = 16;
|
||||
const int kDepth = 16;
|
||||
|
||||
// The maximum number of characters.
|
||||
const int kMaxCharacters = 4096;
|
||||
|
||||
// The minimum number of characters per task.
|
||||
const int kMinGrainSize = 32;
|
||||
|
||||
// Checks if platform has threading support.
|
||||
bool HasThreadingSupport() {
|
||||
#ifdef EMSCRIPTEN
|
||||
return emscripten_has_threading_support();
|
||||
#else // EMSCRIPTEN
|
||||
return true;
|
||||
#endif // EMSCRIPTEN
|
||||
}
|
||||
|
||||
class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
MultithreadSampleApplication()
|
||||
: characters_(kMaxCharacters),
|
||||
num_characters_(kMaxCharacters / 4),
|
||||
has_threading_support_(HasThreadingSupport()),
|
||||
enable_theading_(has_threading_support_),
|
||||
grain_size_(128) {
|
||||
if (has_threading_support_) {
|
||||
ozz::log::Out() << "Platform has threading support." << std::endl;
|
||||
} else {
|
||||
ozz::log::Out() << "Platform doesn't have threading support, "
|
||||
<< "multithreading is disabled." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Nested Character struct forward declaration.
|
||||
struct Character;
|
||||
|
||||
static bool UpdateCharacter(const ozz::animation::Animation& _animation,
|
||||
const ozz::animation::Skeleton& _skeleton,
|
||||
float _dt, Character* _character) {
|
||||
// Samples animation.
|
||||
_character->controller.Update(_animation, _dt);
|
||||
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &_animation;
|
||||
sampling_job.cache = &_character->cache;
|
||||
sampling_job.ratio = _character->controller.time_ratio();
|
||||
sampling_job.output = make_span(_character->locals);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &_skeleton;
|
||||
ltm_job.input = make_span(_character->locals);
|
||||
ltm_job.output = make_span(_character->models);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Data structure used to pass const arguments to parallel tasks.
|
||||
struct ParallelArgs {
|
||||
const ozz::animation::Animation* animation;
|
||||
const ozz::animation::Skeleton* skeleton;
|
||||
float dt;
|
||||
int grain_size; // Maximum number of characters that can be processed by a
|
||||
// task.
|
||||
};
|
||||
|
||||
// Data used to monitor and analyze threading.
|
||||
// Every task will push its thread id to an array. We can then process it to
|
||||
// find how many threads were used.
|
||||
struct ParallelMonitor {
|
||||
ParallelMonitor() {
|
||||
// Finds the maximum possible number of tasks considering kMinGrain grain
|
||||
// size for kMaxCharacters characters.
|
||||
int max_tasks = 1;
|
||||
for (int processed = kMinGrainSize; processed < kMaxCharacters;
|
||||
processed *= 2, max_tasks *= 2) {
|
||||
}
|
||||
thread_ids_.resize(max_tasks);
|
||||
num_async_tasks.store(0);
|
||||
}
|
||||
ozz::vector<std::thread::id> thread_ids_;
|
||||
std::atomic_uint num_async_tasks;
|
||||
};
|
||||
|
||||
static bool ParallelUpdate(const ParallelArgs& _args, Character* _characters,
|
||||
int _num, ParallelMonitor* _monitor) {
|
||||
bool success = true;
|
||||
if (_num <= _args.grain_size) {
|
||||
// Stores this thread identifier to a new task slot.
|
||||
_monitor->thread_ids_[_monitor->num_async_tasks++] =
|
||||
std::this_thread::get_id();
|
||||
|
||||
for (int i = 0; i < _num; ++i) {
|
||||
success &= UpdateCharacter(*_args.animation, *_args.skeleton, _args.dt,
|
||||
&_characters[i]);
|
||||
}
|
||||
} else {
|
||||
// Run half the job on an async task, possibly another thread.
|
||||
const int half = _num / 2;
|
||||
auto handle = std::async(std::launch::async, ParallelUpdate, _args,
|
||||
_characters + half, _num - half, _monitor);
|
||||
|
||||
// The other half is processed by this thread.
|
||||
success &= ParallelUpdate(_args, _characters, half, _monitor);
|
||||
|
||||
// Get result of the async task or process it if it's not started yet.
|
||||
success &= handle.get();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Updates current animation time.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
bool success = true;
|
||||
if (enable_theading_) {
|
||||
// Initialize task counter. It's only used to monitor threading behavior.
|
||||
monitor_.num_async_tasks.store(0);
|
||||
|
||||
const ParallelArgs args = {&animation_, &skeleton_, _dt, grain_size_};
|
||||
success = ParallelUpdate(args, array_begin(characters_), num_characters_,
|
||||
&monitor_);
|
||||
} else {
|
||||
for (int i = 0; i < num_characters_; ++i) {
|
||||
success &= UpdateCharacter(animation_, skeleton_, _dt,
|
||||
array_begin(characters_) + i);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Renders all skeletons.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
for (int c = 0; success && c < num_characters_; ++c) {
|
||||
const ozz::math::Float4 position(
|
||||
((c % kWidth) - kWidth / 2) * kInterval,
|
||||
((c / kWidth) / kDepth) * kInterval,
|
||||
(((c / kWidth) % kDepth) - kDepth / 2) * kInterval, 1.f);
|
||||
const ozz::math::Float4x4 transform = ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::LoadPtrU(&position.x));
|
||||
success &= _renderer->DrawPosture(
|
||||
skeleton_, make_span(characters_[c].models), transform, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animations.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate a default number of characters.
|
||||
AllocateCharaters();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AllocateCharaters() {
|
||||
// Reallocate all characters.
|
||||
for (size_t c = 0; c < kMaxCharacters; ++c) {
|
||||
Character& character = characters_[c];
|
||||
|
||||
// Initializes each controller start time to a different value.
|
||||
character.controller.set_time_ratio(animation_.duration() * kWidth * c /
|
||||
kMaxCharacters);
|
||||
|
||||
character.locals.resize(skeleton_.num_soa_joints());
|
||||
character.models.resize(skeleton_.num_joints());
|
||||
character.cache.Resize(animation_.num_tracks());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes number of characters.
|
||||
{
|
||||
static bool oc_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Sample control", &oc_open);
|
||||
if (oc_open) {
|
||||
char label[64];
|
||||
std::sprintf(label, "Number of entities: %d", num_characters_);
|
||||
_im_gui->DoSlider(label, 1, kMaxCharacters, &num_characters_, .7f);
|
||||
const int num_joints = num_characters_ * skeleton_.num_joints();
|
||||
std::sprintf(label, "Number of joints: %d", num_joints);
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
// Exposes multi-threading parameters.
|
||||
{
|
||||
static bool oc_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Threading control", &oc_open);
|
||||
if (oc_open) {
|
||||
_im_gui->DoCheckBox("Enables threading", &enable_theading_,
|
||||
has_threading_support_);
|
||||
if (enable_theading_) {
|
||||
char label[64];
|
||||
std::sprintf(label, "Grain size: %d", grain_size_);
|
||||
_im_gui->DoSlider(label, kMinGrainSize, kMaxCharacters, &grain_size_,
|
||||
.2f);
|
||||
|
||||
// Finds number of threads, which is the number of unique thread ids
|
||||
// found.
|
||||
std::sort(monitor_.thread_ids_.begin(),
|
||||
monitor_.thread_ids_.begin() + monitor_.num_async_tasks);
|
||||
auto end = std::unique(
|
||||
monitor_.thread_ids_.begin(),
|
||||
monitor_.thread_ids_.begin() + monitor_.num_async_tasks);
|
||||
const int num_threads =
|
||||
static_cast<int>(end - monitor_.thread_ids_.begin());
|
||||
std::sprintf(label, "Thread/task count: %d/%d", num_threads,
|
||||
monitor_.num_async_tasks.load());
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
_bound->min.x = -(kWidth / 2) * kInterval;
|
||||
_bound->max.x =
|
||||
_bound->min.x + ozz::math::Min(num_characters_, kWidth) * kInterval;
|
||||
_bound->min.y = 0.f;
|
||||
_bound->max.y = ((num_characters_ / kWidth / kDepth) + 1) * kInterval;
|
||||
_bound->min.z = -(kDepth / 2) * kInterval;
|
||||
_bound->max.z =
|
||||
_bound->min.z +
|
||||
ozz::math::Min(num_characters_ / kWidth, kDepth) * kInterval;
|
||||
}
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Character structure contains all the data required to sample and blend a
|
||||
// character.
|
||||
struct Character {
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
|
||||
// Buffer of model space matrices. These are computed by the local-to-model
|
||||
// job after the blending stage.
|
||||
ozz::vector<ozz::math::Float4x4> models;
|
||||
};
|
||||
|
||||
// Array of characters of the sample.
|
||||
ozz::vector<Character> characters_;
|
||||
|
||||
// Number of used characters.
|
||||
int num_characters_;
|
||||
|
||||
// Does the current plateform actually has threading support.
|
||||
bool has_threading_support_;
|
||||
|
||||
// Enable or disable threading.
|
||||
bool enable_theading_;
|
||||
|
||||
// Define the number of characters that a task can handle.
|
||||
int grain_size_;
|
||||
|
||||
// Data used to monitor and analyze threading.
|
||||
ParallelMonitor monitor_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Multi-threading";
|
||||
return MultithreadSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_atlas_raw.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_raw.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_atlas_raw.ozz" "./media/animation_raw.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_optimize
|
||||
sample_optimize.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_raw.ozz")
|
||||
|
||||
target_link_libraries(sample_optimize
|
||||
ozz_animation_offline
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_optimize
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten.
|
||||
set_target_properties(sample_optimize
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_optimize.wasm
|
||||
DESTINATION bin/samples/optimize)
|
||||
else()
|
||||
install(TARGETS sample_optimize DESTINATION bin/samples/optimize)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/optimize)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/optimize)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_optimize_default COMMAND sample_optimize "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_optimize_path1 COMMAND sample_optimize "--skeleton=media/skeleton.ozz" "--animation=media/animation_raw.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_optimize_invalid_skeleton_path COMMAND sample_optimize "--skeleton=media/unexisting.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_optimize_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_optimize_invalid_animation_path COMMAND sample_optimize "--animation=media/unexisting.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_optimize_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,37 @@
|
||||
# Ozz-animation sample: Animation keyframe optimization
|
||||
|
||||
## Description
|
||||
|
||||
This sample removes redundant keyframes (within a tolerance) from an animation in order to optimize its memory footprint and runtime update cost. It can display optimized and non-optimized version of the animation, as well as the difference between the two (aka the error generated by the optimization).
|
||||
|
||||
## Concept
|
||||
|
||||
This sample loads a runtime skeleton and a raw animation from ozz files. The raw animation (which is usually reserved for offline purpose) was exported using fbx2ozz tool with optimizations disabled. The raw animation object is converted to a run-time object using `ozz::animation::offline::AnimationBuilder` utility.
|
||||
Prior to the conversion, animation is optimized by removing redundant keyframes (within a tolerance). It uses `ozz::animation::offline::AnimationOptimizer` utility that takes as input a raw animation and outputs a new optimized one.
|
||||
The sample displays the source raw animation median and maximum "error" generated by the optimization stage. Actually it also includes the "error" generated by the quantization of the translation, rotation and scale values performed during runtime animation object creation.
|
||||
|
||||
## Sample usage
|
||||
|
||||
This samples allows to tweak animation key-frame optimization stage using 2 parameters:
|
||||
- tolerance: The maximum error that an optimization is allowed to generate on a whole joint hierarchy.
|
||||
- distance: The distance (from the joint) at which error is measured (if bigger that joint hierarchy). This allows to emulate effect on skinning or a long object (like a sword) attached to a joint (hand).
|
||||
The sample also exposes parameters to override the tolerance for one of the joint, as supported by `ozz::animation::offline::AnimationOptimizer`. This is useful for example when a joint a the skeleton has a contact point which requires more precision than other part.
|
||||
Optimization result can be checked within the sample:
|
||||
- Optimization quality: 3 rendering mode are provided to check optimization quality:
|
||||
- "Raw animation": Renders the source raw animation. No tolerance optimization or compression is performed.
|
||||
- "Run time" (default): Result of the optimization and compression passes, using sample tolerances.
|
||||
- "Absolute error": Renders the difference between the optimized and the source animation. This is especially useful for debugging as it makes it easy to detect compression artifacts.
|
||||
- Optimization quantity: Run-time animation size is displayed in the UI.
|
||||
|
||||
Some other parameters can be tuned from sample UI to affect animation playback.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Import the runtime skeleton and the unoptimized raw animation objects. These are imported from files as described in the "how to load an object from a file" section. Note that the animation is still in an offline format at this stage.
|
||||
2. Optimizes animation by removing interpolable keyframes using `ozz::animation::offline::AnimationOptimizer` utility. It processes the source raw animation and outputs a new one. Optimization tolerance values like translation (meter), rotation (radian) and scale (percentage) are exposed to sample UI.
|
||||
3. Convert the offline animation to the runtime format, using `ozz::animation::offline::AnimationBuilder`. This utility takes as input the optimized RawAnimation and outputs the runtime `ozz::animation::Animation`. The runtime format is the one used for sampling. In opposition with the offline one, it cannot be edited/modified. It is optimized for runtime usage in term of memory layout (cache coherence for sampling) and footprint (compression scheme).
|
||||
4. Local-space transformations (in SoA format as required by the runtime pipeline) are computed from the runtime and the source raw animations according the selected rendering mode:
|
||||
- "Raw animation": Every track of the source raw animation is traversed in order to extract each relevant keyframes. Keyframes are interpolated to match the exact expected time. This implementation isn't part of the API but done by the sample, as raw animations aren't intended to be used this way in runtime. Even though it works, performance are quite low compared with sampling a runtime animation. On the other end the result is the closest to the input file, kind of lossless.
|
||||
- "Run time" (default): Samples optimized runtime animation as usual.
|
||||
- "Absolute error": Samples both Raw and runtime animations as described above. Resulting local transformed are subtracted in order to compute the error generated by the runtime animation compression and optimization.
|
||||
5. The remaining code of the sample allocates runtime buffers and samples animation every frame. See playback sample for more details.
|
||||
@@ -0,0 +1,578 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/animation_optimizer.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/raw_animation_utils.h"
|
||||
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/profile.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// Skeleton and animation file can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton, "Path to the runtime skeleton file.",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation, "Path to the raw animation file.",
|
||||
"media/animation_raw.ozz", false)
|
||||
|
||||
namespace {
|
||||
|
||||
// Loads a raw animation from a file.
|
||||
bool LoadAnimation(const char* _filename,
|
||||
ozz::animation::offline::RawAnimation* _animation) {
|
||||
assert(_filename && _animation);
|
||||
ozz::log::Out() << "Loading raw 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::offline::RawAnimation>()) {
|
||||
ozz::log::Err() << "Failed to load raw animation instance from file "
|
||||
<< _filename << "." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once the tag is validated, reading cannot fail.
|
||||
archive >> *_animation;
|
||||
|
||||
// Ensure animation is valid.
|
||||
return _animation->Validate();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
OptimizeSampleApplication()
|
||||
: selected_display_(eRuntimeAnimation),
|
||||
optimize_(true),
|
||||
joint_setting_enable_(true),
|
||||
joint_(0),
|
||||
error_record_med_(64),
|
||||
error_record_max_(64),
|
||||
joint_error_record_(64) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(*animation_rt_, _dt);
|
||||
|
||||
// Prepares sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
|
||||
// Samples optimized animation (_according to the display mode).
|
||||
sampling_job.animation = animation_rt_.get();
|
||||
sampling_job.output = make_span(locals_rt_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Also samples non-optimized animation, from the raw animation.
|
||||
if (!SampleRawAnimation(raw_animation_,
|
||||
controller_.time_ratio() * raw_animation_.duration,
|
||||
make_span(locals_raw_))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes difference between the optimized and non-optimized animations
|
||||
// in local space, and rebinds it to the bind pose.
|
||||
{
|
||||
const ozz::span<const ozz::math::SoaTransform>& bind_poses =
|
||||
skeleton_.joint_bind_poses();
|
||||
const ozz::math::SoaTransform* bind_pose = bind_poses.begin();
|
||||
const ozz::math::SoaTransform* locals_raw = locals_raw_.data();
|
||||
const ozz::math::SoaTransform* locals_rt = locals_rt_.data();
|
||||
ozz::math::SoaTransform* locals_diff = locals_diff_.data();
|
||||
for (; bind_pose < bind_poses.end();
|
||||
++locals_raw, ++locals_rt, ++locals_diff, ++bind_pose) {
|
||||
assert(locals_raw < array_end(locals_raw_) &&
|
||||
locals_rt < array_end(locals_rt_) &&
|
||||
locals_diff < array_end(locals_diff_) &&
|
||||
bind_pose < bind_poses.end());
|
||||
|
||||
// Computes difference.
|
||||
const ozz::math::SoaTransform diff = {
|
||||
locals_rt->translation - locals_raw->translation,
|
||||
locals_rt->rotation * Conjugate(locals_raw->rotation),
|
||||
locals_rt->scale / locals_raw->scale};
|
||||
|
||||
// Rebinds to the bind pose in the diff buffer.
|
||||
locals_diff->translation = bind_pose->translation + diff.translation;
|
||||
locals_diff->rotation = bind_pose->rotation * diff.rotation;
|
||||
locals_diff->scale = bind_pose->scale * diff.scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
|
||||
// Optimized samples.
|
||||
ltm_job.input = make_span(locals_rt_);
|
||||
ltm_job.output = make_span(models_rt_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-optimized samples (from the raw animation).
|
||||
ltm_job.input = make_span(locals_raw_);
|
||||
ltm_job.output = make_span(models_raw_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Difference between optimized and non-optimized samples.
|
||||
ltm_job.input = make_span(locals_diff_);
|
||||
ltm_job.output = make_span(models_diff_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes the absolute error, aka the difference between the raw and
|
||||
// runtime model space transformation.
|
||||
const size_t num_joints = models_rt_.size();
|
||||
float errors_sq[ozz::animation::Skeleton::kMaxJoints];
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
// Computes error based on the translation difference.
|
||||
errors_sq[i] = ozz::math::GetX(ozz::math::Length3Sqr(
|
||||
models_rt_[i].cols[3] - models_raw_[i].cols[3]));
|
||||
}
|
||||
|
||||
std::sort(errors_sq, errors_sq + models_rt_.size());
|
||||
error_record_med_.Push(std::sqrt(errors_sq[num_joints / 2]) * 1000.f);
|
||||
error_record_max_.Push(std::sqrt(errors_sq[num_joints - 1]) * 1000.f);
|
||||
joint_error_record_.Push(std::sqrt(errors_sq[joint_]) * 1000.f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SampleRawAnimation(
|
||||
const ozz::animation::offline::RawAnimation& _animation, float _time,
|
||||
ozz::span<ozz::math::SoaTransform> _locals) {
|
||||
// Ensure output is big enough.
|
||||
if (_locals.size() * 4 < _animation.tracks.size() &&
|
||||
locals_raw_aos_.size() * 4 < _animation.tracks.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sample raw animation and converts AoS transforms to SoA transform array.
|
||||
assert(_animation.Validate() && "Animation should be valid.");
|
||||
bool success = true;
|
||||
for (int i = 0; success && i < _animation.num_tracks(); i += 4) {
|
||||
ozz::math::SimdFloat4 translations[4];
|
||||
ozz::math::SimdFloat4 rotations[4];
|
||||
ozz::math::SimdFloat4 scales[4];
|
||||
|
||||
// Works on 4 consecutive tracks, or what remains to be processed if it's
|
||||
// lower than 4.
|
||||
const int jmax = ozz::math::Min(_animation.num_tracks() - i, 4);
|
||||
for (int j = 0; success && j < jmax; ++j) {
|
||||
// Samples raw animation.
|
||||
ozz::math::Transform transform;
|
||||
success &= SampleTrack(_animation.tracks[i + j], _time, &transform);
|
||||
|
||||
// Convert transform to AoS SimdFloat4 values.
|
||||
translations[j] =
|
||||
ozz::math::simd_float4::Load3PtrU(&transform.translation.x);
|
||||
rotations[j] = ozz::math::simd_float4::LoadPtrU(&transform.rotation.x);
|
||||
scales[j] = ozz::math::simd_float4::Load3PtrU(&transform.scale.x);
|
||||
}
|
||||
// Fills remaining transforms.
|
||||
for (int j = jmax; j < 4; ++j) {
|
||||
translations[j] = ozz::math::simd_float4::zero();
|
||||
rotations[j] = ozz::math::simd_float4::w_axis();
|
||||
scales[j] = ozz::math::simd_float4::one();
|
||||
}
|
||||
// Stores AoS keyframes to the SoA output.
|
||||
ozz::math::SoaTransform& output = _locals[i / 4];
|
||||
ozz::math::Transpose4x3(translations, &output.translation.x);
|
||||
ozz::math::Transpose4x4(rotations, &output.rotation.x);
|
||||
ozz::math::Transpose4x3(scales, &output.scale.x);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Selects model space matrices according to the display mode.
|
||||
ozz::span<const ozz::math::Float4x4> models() const {
|
||||
switch (selected_display_) {
|
||||
case eRuntimeAnimation:
|
||||
return make_span(models_rt_);
|
||||
case eRawAnimation:
|
||||
return make_span(models_raw_);
|
||||
case eAbsoluteError:
|
||||
return make_span(models_diff_);
|
||||
default: {
|
||||
assert(false && "Invalid display mode");
|
||||
return make_span(models_rt_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
const ozz::span<const ozz::math::Float4x4> transforms = models();
|
||||
|
||||
// Renders posture.
|
||||
success &= _renderer->DrawPosture(skeleton_, transforms,
|
||||
ozz::math::Float4x4::identity());
|
||||
|
||||
if (joint_setting_enable_) {
|
||||
// Renders an axes with targetted joint transform.
|
||||
const ozz::math::Float4x4 axes_scale = ozz::math::Float4x4::Scaling(
|
||||
ozz::math::simd_float4::Load1(joint_setting_.distance));
|
||||
success &= _renderer->DrawAxes(transforms[joint_] * axes_scale);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Imports offline skeleton from a binary file.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Imports offline animation from a binary file.
|
||||
// Invalid animations are rejected by the load function.
|
||||
if (!LoadAnimation(OPTIONS_animation, &raw_animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
|
||||
// Finds the joint where the object should be attached.
|
||||
for (int i = 0; i < num_joints; i++) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "L Finger2Nub")) {
|
||||
joint_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the runtime animation from the raw one.
|
||||
if (!BuildAnimations()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
locals_rt_.resize(num_soa_joints);
|
||||
models_rt_.resize(num_joints);
|
||||
locals_raw_aos_.resize(num_joints);
|
||||
locals_raw_.resize(num_soa_joints);
|
||||
models_raw_.resize(num_joints);
|
||||
locals_diff_.resize(num_soa_joints);
|
||||
models_diff_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char label[64];
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose occ(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(*animation_rt_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes optimizer's tolerances.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Optimization tolerances",
|
||||
&open);
|
||||
if (open) {
|
||||
bool rebuild = false;
|
||||
|
||||
rebuild |= _im_gui->DoCheckBox("Enable optimizations", &optimize_);
|
||||
|
||||
std::sprintf(label, "Tolerance: %0.2f mm", setting_.tolerance * 1000);
|
||||
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &setting_.tolerance, .5f,
|
||||
optimize_);
|
||||
|
||||
std::sprintf(label, "Distance: %0.2f mm", setting_.distance * 1000);
|
||||
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &setting_.distance, .5f,
|
||||
optimize_);
|
||||
|
||||
rebuild |= _im_gui->DoCheckBox("Enable joint setting",
|
||||
&joint_setting_enable_, optimize_);
|
||||
|
||||
std::sprintf(label, "%s (%d)", skeleton_.joint_names()[joint_], joint_);
|
||||
rebuild |=
|
||||
_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1, &joint_,
|
||||
1.f, joint_setting_enable_ && optimize_);
|
||||
|
||||
std::sprintf(label, "Tolerance: %0.2f mm",
|
||||
joint_setting_.tolerance * 1000);
|
||||
rebuild |= _im_gui->DoSlider(label, 0.f, .1f, &joint_setting_.tolerance,
|
||||
.5f, joint_setting_enable_ && optimize_);
|
||||
|
||||
std::sprintf(label, "Distance: %0.2f mm",
|
||||
joint_setting_.distance * 1000);
|
||||
rebuild |= _im_gui->DoSlider(label, 0.f, 1.f, &joint_setting_.distance,
|
||||
.5f, joint_setting_enable_ && optimize_);
|
||||
|
||||
if (rebuild) {
|
||||
// Invalidates the cache in case the new animation has the same
|
||||
// address as the previous one. Other cases (like changing animation)
|
||||
// are automatic handled by the cache. See SamplingCache::Invalidate
|
||||
// for more details.
|
||||
cache_.Invalidate();
|
||||
|
||||
// Rebuilds a new runtime animation.
|
||||
if (!BuildAnimations()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose ocb(_im_gui, "Memory size", &open);
|
||||
if (open) {
|
||||
std::sprintf(label, "Original: %dKB",
|
||||
static_cast<int>(raw_animation_.size() >> 10));
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
std::sprintf(label, "Optimized: %dKB (%.1f:1)",
|
||||
static_cast<int>(raw_optimized_animation_.size() >> 10),
|
||||
static_cast<float>(raw_animation_.size()) /
|
||||
raw_optimized_animation_.size());
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
std::sprintf(
|
||||
label, "Compressed: %dKB (%.1f:1)",
|
||||
static_cast<int>(animation_rt_->size() >> 10),
|
||||
static_cast<float>(raw_animation_.size()) / animation_rt_->size());
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Selects display mode.
|
||||
static bool open_mode = true;
|
||||
ozz::sample::ImGui::OpenClose mode(_im_gui, "Display mode", &open_mode);
|
||||
if (open_mode) {
|
||||
_im_gui->DoRadioButton(eRuntimeAnimation, "Runtime animation",
|
||||
&selected_display_);
|
||||
_im_gui->DoRadioButton(eRawAnimation, "Raw animation",
|
||||
&selected_display_);
|
||||
_im_gui->DoRadioButton(eAbsoluteError, "Absolute error",
|
||||
&selected_display_);
|
||||
}
|
||||
|
||||
// Show absolute error.
|
||||
{
|
||||
char szLabel[64];
|
||||
static bool error_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc_stats(_im_gui, "Absolute error",
|
||||
&error_open);
|
||||
if (error_open) {
|
||||
{
|
||||
std::sprintf(szLabel, "Median error: %.2fmm",
|
||||
*error_record_med_.cursor());
|
||||
const ozz::sample::Record::Statistics error_stats =
|
||||
error_record_med_.GetStatistics();
|
||||
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
|
||||
error_record_med_.cursor(),
|
||||
error_record_med_.record_begin(),
|
||||
error_record_med_.record_end());
|
||||
}
|
||||
{
|
||||
std::sprintf(szLabel, "Maximum error: %.2fmm",
|
||||
*error_record_max_.cursor());
|
||||
const ozz::sample::Record::Statistics error_stats =
|
||||
error_record_max_.GetStatistics();
|
||||
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
|
||||
error_record_max_.cursor(),
|
||||
error_record_max_.record_begin(),
|
||||
error_record_max_.record_end());
|
||||
}
|
||||
{
|
||||
std::sprintf(szLabel, "Joint %d error: %.2fmm", joint_,
|
||||
*joint_error_record_.cursor());
|
||||
const ozz::sample::Record::Statistics error_stats =
|
||||
joint_error_record_.GetStatistics();
|
||||
_im_gui->DoGraph(szLabel, 0.f, error_stats.max, error_stats.latest,
|
||||
joint_error_record_.cursor(),
|
||||
joint_error_record_.record_begin(),
|
||||
joint_error_record_.record_end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
bool BuildAnimations() {
|
||||
// Instantiate an animation builder.
|
||||
ozz::animation::offline::AnimationBuilder animation_builder;
|
||||
|
||||
// Builds the optimized animation.
|
||||
if (optimize_) {
|
||||
ozz::animation::offline::AnimationOptimizer optimizer;
|
||||
|
||||
// Setup global optimization settings.
|
||||
optimizer.setting = setting_;
|
||||
|
||||
// Setup joint specific optimization settings.
|
||||
if (joint_setting_enable_) {
|
||||
optimizer.joints_setting_override[joint_] = joint_setting_;
|
||||
} else {
|
||||
optimizer.joints_setting_override.clear();
|
||||
}
|
||||
|
||||
if (!optimizer(raw_animation_, skeleton_, &raw_optimized_animation_)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Builds runtime animation from the brute one.
|
||||
raw_optimized_animation_ = raw_animation_;
|
||||
}
|
||||
|
||||
// Builds runtime animation from the optimized one.
|
||||
animation_rt_ = animation_builder(raw_optimized_animation_);
|
||||
|
||||
// Check if building runtime animation was successful.
|
||||
if (!animation_rt_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(models(), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Selects which animation is displayed.
|
||||
enum DisplayMode {
|
||||
eRuntimeAnimation,
|
||||
eRawAnimation,
|
||||
eAbsoluteError,
|
||||
};
|
||||
int selected_display_;
|
||||
|
||||
// Select whether optimization should be performed.
|
||||
bool optimize_;
|
||||
|
||||
// Imported non-optimized animation.
|
||||
ozz::animation::offline::RawAnimation raw_animation_;
|
||||
|
||||
// Optimized raw animation.
|
||||
ozz::animation::offline::RawAnimation raw_optimized_animation_;
|
||||
|
||||
// Optimizer global settings.
|
||||
ozz::animation::offline::AnimationOptimizer::Setting setting_;
|
||||
|
||||
// Optimizer joint specific settings.
|
||||
bool joint_setting_enable_;
|
||||
int joint_;
|
||||
ozz::animation::offline::AnimationOptimizer::Setting joint_setting_;
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Sampling cache, shared across optimized and non-optimized animations. This
|
||||
// is not optimal, but it's not an issue either.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// Runtime optimized animation.
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_rt_;
|
||||
|
||||
// Buffer of local and model space transformations as sampled from the
|
||||
// rutime (optimized and compressed) animation.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_rt_;
|
||||
ozz::vector<ozz::math::Float4x4> models_rt_;
|
||||
|
||||
// Buffer of local and model space transformations as sampled from the
|
||||
// non-optimized (raw) animation.
|
||||
// Sampling the raw animation results in AoS data, meaning we have to
|
||||
// allocate AoS data and do the SoA conversion by hand.
|
||||
ozz::vector<ozz::math::Transform> locals_raw_aos_;
|
||||
ozz::vector<ozz::math::SoaTransform> locals_raw_;
|
||||
ozz::vector<ozz::math::Float4x4> models_raw_;
|
||||
|
||||
// Buffer of local and model space transformations storing samples from the
|
||||
// difference between optimized and non-optimized animations.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_diff_;
|
||||
ozz::vector<ozz::math::Float4x4> models_diff_;
|
||||
|
||||
// Record of accuracy errors produced by animation compression and
|
||||
// optimization.
|
||||
ozz::sample::Record error_record_med_;
|
||||
ozz::sample::Record error_record_max_;
|
||||
ozz::sample::Record joint_error_record_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Animation keyframe optimization";
|
||||
return OptimizeSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_walk.ozz"
|
||||
"${ozz_media_directory}/bin/pab_crossarms.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_partial.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_walk.ozz" "./media/animation_base.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crossarms.ozz" "./media/animation_partial.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_partial_blend
|
||||
sample_partial_blend.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_base.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_partial.ozz")
|
||||
target_link_libraries(sample_partial_blend
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_partial_blend
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_partial_blend
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_partial_blend.wasm
|
||||
DESTINATION bin/samples/partial_blend)
|
||||
else()
|
||||
install(TARGETS sample_partial_blend DESTINATION bin/samples/partial_blend)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/partial_blend)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/partial_blend)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_partial_blend COMMAND sample_partial_blend "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_partial_blend_path COMMAND sample_partial_blend "--skeleton=media/skeleton.ozz" "--lower_body_animation=media/animation_base.ozz" "--upper_body_animation=media/animation_partial.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_partial_blend_invalid_skeleton_path COMMAND sample_partial_blend "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_partial_blend_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_partial_blend_invalid_animation_path1 COMMAND sample_partial_blend "--lower_body_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_partial_blend_invalid_animation_path1 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_partial_blend_invalid_animation_path2 COMMAND sample_partial_blend "--upper_body_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_partial_blend_invalid_animation_path2 PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,29 @@
|
||||
# Ozz-animation sample: Partial animations blending
|
||||
|
||||
## Description
|
||||
|
||||
Uses partial animation blending technique to animate the lower and upper part of the skeleton with different animations.
|
||||
|
||||
## Concept
|
||||
|
||||
Partial animation blending uses the same job (aka ozz::animation::BlendingJob) as the full blending technique (see "blend" sample for more details). Partial blending uses a coefficient per joint to weight the animation influence, for every joint. This per-joint weight is modulated with layer's weight to compute the final influence of every joint. This set of weight coefficients are provided to the BlendingJob::Layer as an array of SoA floating point values, arranged in the same order as skeleton joints.
|
||||
The sample uses ozz::animation::IterateJointsDF helper function to iterate all children of the upper body "root" joint, and set up per-joint weight masks.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample proposes two modes of interaction:
|
||||
- Automatic: With this mode the GUI proposes to automatically control all blending parameters from a single "upper body weight" slider. The selected coefficient is used to set up the per-joint weights of the upper body layer, while lower body weights are set to one minus upper body weights.
|
||||
- Manuel: Allows to setup all blending parameters independently.
|
||||
The GUI also proposes to select the "root" joint of the upper body hierarchy, which is affected by the partially animated layer.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. This sample extends "blend" sample, and uses the same procedure to load skeleton and animations objects.
|
||||
2. Samples each animation at their own speed, they do not need to be synchronized.
|
||||
3. Prepares per-joint weight masks for the two lower and upper body layers. If interaction mode is set to automatic, then lower and upper body weight coefficients are computed from the single "Upper body weight" parameter. In this case upper body coefficient equals one minus lower body coefficient. If interaction mode is set to manual, then all parameters are set manually.
|
||||
4. Uses ozz::animation::IterateJointsDF helper function to iterate all children of the upper body root joint, and set up per-joint weight masks as follows (note that weight coefficients are stored as SoA floats):
|
||||
- Upper body weight mask: Affects upper body weight coefficient to all the joints that are part of the upper body, all others are set to zero.
|
||||
- Lower body weight mask: Affects lower body weight coefficient to all the joints that are part of the lower body (ie: all the ones that are not part of the upper body), all others are set to one.
|
||||
5. Sets ozz::animation::BlendingJob object with the two layers for the lower and upper body. Per-joint weight masks are provided as an input to each layer. All other arguments (bind-pose, input local-space transforms, weights, output) are the same as those used by the full skeleton hierarchy blending. See "blend" sample for more details.
|
||||
6. Converts local-space transformations, outputted from the blending stage, to model-space matrices using ozz::animation::LocalToModelJob. It also takes as input the skeleton (to know about joint's hierarchy). Output is model-space matrices array.
|
||||
7. Model-space matrices array can then be used for rendering (to skin a mesh) or updating the scene graph.
|
||||
@@ -0,0 +1,405 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/animation.h"
|
||||
#include "ozz/animation/runtime/blending_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/animation/runtime/skeleton_utils.h"
|
||||
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
#include "ozz/base/containers/vector.h"
|
||||
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
"Path to the skeleton (ozz archive format).",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
// Lower body animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(
|
||||
lower_body_animation,
|
||||
"Path to the lower body animation(ozz archive format).",
|
||||
"media/animation_base.ozz", false)
|
||||
|
||||
// Upper body animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(
|
||||
upper_body_animation,
|
||||
"Path to the upper body animation (ozz archive format).",
|
||||
"media/animation_partial.ozz", false)
|
||||
|
||||
class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
PartialBlendSampleApplication()
|
||||
: upper_body_root_(0),
|
||||
threshold_(ozz::animation::BlendingJob().threshold) {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates and samples both animations to their respective local space
|
||||
// transform buffers.
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
// Updates animations time.
|
||||
sampler.controller.Update(sampler.animation, _dt);
|
||||
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &sampler.animation;
|
||||
sampling_job.cache = &sampler.cache;
|
||||
sampling_job.ratio = sampler.controller.time_ratio();
|
||||
sampling_job.output = make_span(sampler.locals);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Blends animations.
|
||||
// Blends the local spaces transforms computed by sampling all animations
|
||||
// (1st stage just above), and outputs the result to the local space
|
||||
// transform buffer blended_locals_
|
||||
|
||||
// Prepares blending layers.
|
||||
ozz::animation::BlendingJob::Layer layers[kNumLayers];
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
layers[i].transform = make_span(samplers_[i].locals);
|
||||
layers[i].weight = samplers_[i].weight_setting;
|
||||
|
||||
// Set per-joint weights for the partially blended layer.
|
||||
layers[i].joint_weights = make_span(samplers_[i].joint_weights);
|
||||
}
|
||||
|
||||
// Setups blending job.
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = threshold_;
|
||||
blend_job.layers = layers;
|
||||
blend_job.bind_pose = skeleton_.joint_bind_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
if (!blend_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
// Gets the output of the blending stage, and converts it to model space.
|
||||
|
||||
// Setup local-to-model conversion job.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(blended_locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
|
||||
// Run ltm job.
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
return _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
|
||||
// Reading animations.
|
||||
const char* filenames[] = {OPTIONS_lower_body_animation,
|
||||
OPTIONS_upper_body_animation};
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
|
||||
if (!ozz::sample::LoadAnimation(filenames[i], &sampler.animation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates sampler runtime buffers.
|
||||
sampler.locals.resize(num_soa_joints);
|
||||
|
||||
// Allocates per-joint weights used for the partial animation. Note that
|
||||
// this is a Soa structure.
|
||||
sampler.joint_weights.resize(num_soa_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
sampler.cache.Resize(num_joints);
|
||||
}
|
||||
|
||||
// Default weight settings.
|
||||
Sampler& lower_body_sampler = samplers_[kLowerBody];
|
||||
lower_body_sampler.weight_setting = 1.f;
|
||||
lower_body_sampler.joint_weight_setting = 0.f;
|
||||
|
||||
Sampler& upper_body_sampler = samplers_[kUpperBody];
|
||||
upper_body_sampler.weight_setting = 1.f;
|
||||
upper_body_sampler.joint_weight_setting = 1.f;
|
||||
|
||||
// Allocates local space runtime buffers of blended data.
|
||||
blended_locals_.resize(num_soa_joints);
|
||||
|
||||
// Allocates model space runtime buffers of blended data.
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Finds the "Spine1" joint in the joint hierarchy.
|
||||
for (int i = 0; i < num_joints; ++i) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "Spine1")) {
|
||||
upper_body_root_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetupPerJointWeights();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper functor used to set weights while traversing joints hierarchy.
|
||||
struct WeightSetupIterator {
|
||||
WeightSetupIterator(ozz::vector<ozz::math::SimdFloat4>* _weights,
|
||||
float _weight_setting)
|
||||
: weights(_weights), weight_setting(_weight_setting) {}
|
||||
void operator()(int _joint, int) {
|
||||
ozz::math::SimdFloat4& soa_weight = weights->at(_joint / 4);
|
||||
soa_weight = ozz::math::SetI(
|
||||
soa_weight, ozz::math::simd_float4::Load1(weight_setting),
|
||||
_joint % 4);
|
||||
}
|
||||
ozz::vector<ozz::math::SimdFloat4>* weights;
|
||||
float weight_setting;
|
||||
};
|
||||
|
||||
void SetupPerJointWeights() {
|
||||
// Setup partial animation mask. This mask is defined by a weight_setting
|
||||
// assigned to each joint of the hierarchy. Joint to disable are set to a
|
||||
// weight_setting of 0.f, and enabled joints are set to 1.f.
|
||||
// Per-joint weights of lower and upper body layers have opposed values
|
||||
// (weight_setting and 1 - weight_setting) in order for a layer to select
|
||||
// joints that are rejected by the other layer.
|
||||
Sampler& lower_body_sampler = samplers_[kLowerBody];
|
||||
Sampler& upper_body_sampler = samplers_[kUpperBody];
|
||||
|
||||
// Disables all joints: set all weights to 0.
|
||||
for (int i = 0; i < skeleton_.num_soa_joints(); ++i) {
|
||||
lower_body_sampler.joint_weights[i] = ozz::math::simd_float4::one();
|
||||
upper_body_sampler.joint_weights[i] = ozz::math::simd_float4::zero();
|
||||
}
|
||||
|
||||
// Sets the weight_setting of all the joints children of the lower and upper
|
||||
// body weights. Note that they are stored in SoA format.
|
||||
WeightSetupIterator lower_it(&lower_body_sampler.joint_weights,
|
||||
lower_body_sampler.joint_weight_setting);
|
||||
ozz::animation::IterateJointsDF(skeleton_, lower_it, upper_body_root_);
|
||||
|
||||
WeightSetupIterator upper_it(&upper_body_sampler.joint_weights,
|
||||
upper_body_sampler.joint_weight_setting);
|
||||
ozz::animation::IterateJointsDF(skeleton_, upper_it, upper_body_root_);
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes blending parameters.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Blending parameters", &open);
|
||||
if (open) {
|
||||
char label[64];
|
||||
|
||||
static bool automatic = true;
|
||||
_im_gui->DoCheckBox("Use automatic blending settings", &automatic);
|
||||
|
||||
static float coeff = 1.f; // All power to the partial animation.
|
||||
std::sprintf(label, "Upper body weight: %.2f", coeff);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &coeff, 1.f, automatic);
|
||||
|
||||
Sampler& lower_body_sampler = samplers_[kLowerBody];
|
||||
Sampler& upper_body_sampler = samplers_[kUpperBody];
|
||||
|
||||
if (automatic) {
|
||||
// Blending values are forced when "automatic" mode is selected.
|
||||
lower_body_sampler.weight_setting = 1.f;
|
||||
lower_body_sampler.joint_weight_setting = 1.f - coeff;
|
||||
upper_body_sampler.weight_setting = 1.f;
|
||||
upper_body_sampler.joint_weight_setting = coeff;
|
||||
}
|
||||
|
||||
_im_gui->DoLabel("Manual settings:");
|
||||
_im_gui->DoLabel("Lower body layer:");
|
||||
std::sprintf(label, "Layer weight: %.2f",
|
||||
lower_body_sampler.weight_setting);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &lower_body_sampler.weight_setting,
|
||||
1.f, !automatic);
|
||||
std::sprintf(label, "Joints weight: %.2f",
|
||||
lower_body_sampler.joint_weight_setting);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f,
|
||||
&lower_body_sampler.joint_weight_setting, 1.f,
|
||||
!automatic);
|
||||
_im_gui->DoLabel("Upper body layer:");
|
||||
std::sprintf(label, "Layer weight: %.2f",
|
||||
upper_body_sampler.weight_setting);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &upper_body_sampler.weight_setting,
|
||||
1.f, !automatic);
|
||||
std::sprintf(label, "Joints weight: %.2f",
|
||||
upper_body_sampler.joint_weight_setting);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f,
|
||||
&upper_body_sampler.joint_weight_setting, 1.f,
|
||||
!automatic);
|
||||
_im_gui->DoLabel("Global settings:");
|
||||
std::sprintf(label, "Threshold: %.2f", threshold_);
|
||||
_im_gui->DoSlider(label, .01f, 1.f, &threshold_);
|
||||
|
||||
SetupPerJointWeights();
|
||||
}
|
||||
}
|
||||
// Exposes selection of the root of the partial blending hierarchy.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Root", &open);
|
||||
if (open && skeleton_.num_joints() != 0) {
|
||||
_im_gui->DoLabel("Root of the upper body hierarchy:",
|
||||
ozz::sample::ImGui::kLeft, false);
|
||||
char label[64];
|
||||
std::sprintf(label, "%s (%d)",
|
||||
skeleton_.joint_names()[upper_body_root_],
|
||||
upper_body_root_);
|
||||
if (_im_gui->DoSlider(label, 0, skeleton_.num_joints() - 1,
|
||||
&upper_body_root_)) {
|
||||
SetupPerJointWeights();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exposes animations runtime playback controls.
|
||||
{
|
||||
static bool oc_open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &oc_open);
|
||||
if (oc_open) {
|
||||
static bool open[kNumLayers] = {true, true};
|
||||
const char* oc_names[kNumLayers] = {"Lower body animation",
|
||||
"Upper body animation"};
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
Sampler& sampler = samplers_[i];
|
||||
ozz::sample::ImGui::OpenClose loc(_im_gui, oc_names[i], nullptr);
|
||||
if (open[i]) {
|
||||
sampler.controller.OnGui(sampler.animation, _im_gui);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// The number of layers to blend.
|
||||
enum {
|
||||
kLowerBody = 0,
|
||||
kUpperBody = 1,
|
||||
kNumLayers = 2,
|
||||
};
|
||||
|
||||
// Sampler structure contains all the data required to sample a single
|
||||
// animation.
|
||||
struct Sampler {
|
||||
// Constructor, default initialization.
|
||||
Sampler() : weight_setting(1.f), joint_weight_setting(1.f) {}
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
|
||||
// Blending weight_setting for the layer.
|
||||
float weight_setting;
|
||||
|
||||
// Blending weight_setting setting of the joints of this layer that are
|
||||
// affected
|
||||
// by the masking.
|
||||
float joint_weight_setting;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
|
||||
// Per-joint weights used to define the partial animation mask. Allows to
|
||||
// select which joints are considered during blending, and their individual
|
||||
// weight_setting.
|
||||
ozz::vector<ozz::math::SimdFloat4> joint_weights;
|
||||
} samplers_[kNumLayers]; // kNumLayers animations to blend.
|
||||
|
||||
// Index of the joint at the base of the upper body hierarchy.
|
||||
int upper_body_root_;
|
||||
|
||||
// Blending job bind pose threshold.
|
||||
float threshold_;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> blended_locals_;
|
||||
|
||||
// Buffer of model space matrices. These are computed by the local-to-model
|
||||
// job after the blending stage.
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Partial animations blending";
|
||||
return PartialBlendSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/pab_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/pab_crossarms.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_crossarms.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_playback
|
||||
sample_playback.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
target_link_libraries(sample_playback
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_playback
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_playback
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_playback.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_playback.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_playback.wasm
|
||||
DESTINATION bin/samples/playback)
|
||||
else()
|
||||
install(TARGETS sample_playback DESTINATION bin/samples/playback)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/playback)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/playback)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_playback COMMAND sample_playback "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_playback_path1 COMMAND sample_playback "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_playback_invalid_skeleton_path COMMAND sample_playback "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_playback_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_playback_invalid_animation_path COMMAND sample_playback "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_playback_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Ozz-animation sample: Animation playback
|
||||
|
||||
## Description
|
||||
|
||||
Loads a skeleton and an animation from ozz binary archives. Then playbacks animation and renders the corresponding animated skeleton.
|
||||
|
||||
## Concept
|
||||
|
||||
This sample loads ozz binary archive file (to read an animation and a skeleton). Ozz binary files can be produced with ozz command line tool fbx2ozz, or with ozz serializer (ozz::io::OArchive) from your own application/converter.
|
||||
At every frame the animation is sampled with ozz::animation::SamplingJob. Sampled local-space output is then converted to model-space matrices for rendering using ozz::animation::LocalToModelJob.
|
||||
|
||||
## Sample usage
|
||||
|
||||
Some parameters can be tuned from sample UI:
|
||||
- Play/pause animation.
|
||||
- Fix animation time.
|
||||
- Set playback speed, which can be negative to go backward.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animation and skeleton.
|
||||
1. Open a ozz::io::OArchive object with a valid ozz::io::Stream as argument. The stream can be a ozz::io::File, or your custom io read capable object that inherits from ozz::io::Stream.
|
||||
2. Check that the stream stores the expected object using ozz::io::OArchive::TestTag() function. Object type is specified as a template argument.
|
||||
3. De-serialize the object with >> operator.
|
||||
2. Allocates runtime buffers (local-space transforms of type ozz::math::SoaTransform, model-space matrices of type ozz::math::Float4x4) with the number of elements required for the skeleton. Note that local-space transform are Soa objects, meaning that 1 ozz::math::SoaTransform can store multiple (4) joints.
|
||||
3. Allocates sampling cache (ozz::animation::SamplingCache) with the number of joints required for the animation. This cache is used to store sampling local data as well as optimizing key-frame lookup while reading animation forward.
|
||||
4. Sample animation to get local-space transformations using ozz::animation::SamplingJob. This job takes as input the animation, the cache and a time at which the animation should be sampled. Output is the local-space transformation array.
|
||||
5. Convert local-space transformations to model-space matrices using ozz::animation::LocalToModelJob. It takes as input the skeleton (to know about joint's hierarchy) and local-space transforms. Output is model-space matrices array.
|
||||
6. Model-space matrices array can then be used for rendering (to skin a mesh) or updating the scene graph.
|
||||
@@ -0,0 +1,161 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
class PlaybackSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
PlaybackSampleApplication() {}
|
||||
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
return _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skeleton and animation needs to match.
|
||||
if (skeleton_.num_joints() != animation_.num_tracks()) {
|
||||
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);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title =
|
||||
"Ozz-animation sample: Binary animation/skeleton playback";
|
||||
return PlaybackSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
$<$<BOOL:${ozz_build_fbx}>:BUILD_DATA_SAMPLE>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/ruby_mesh.ozz"
|
||||
"${ozz_media_directory}/bin/ruby_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/ruby_animation.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/ruby_mesh.ozz" "./media/mesh.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/ruby_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/ruby_animation.ozz" "./media/animation.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_skinning
|
||||
sample_skinning.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
target_link_libraries(sample_skinning
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_skinning
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_skinning
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_skinning.wasm
|
||||
DESTINATION bin/samples/skinning)
|
||||
else()
|
||||
install(TARGETS sample_skinning DESTINATION bin/samples/skinning)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/skinning)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/skinning)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_skinning COMMAND sample_skinning "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_skinning_path COMMAND sample_skinning "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--mesh=media/mesh.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_skinning_invalid_skeleton_path COMMAND sample_skinning "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_skinning_invalid_animation_path COMMAND sample_skinning "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_skinning_invalid_mesh_path COMMAND sample_skinning "--mesh=media/bad_mesh.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_skinning_invalid_mesh_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Ozz-animation sample: Skinning
|
||||
|
||||
## Description
|
||||
|
||||
This sample adds skinning to the playback sample. It loads a skeleton, an animation and skinned meshes from ozz binary archives. It playbacks animation every frame and uses model-space matrices to build skinning matrices and render a skinned mesh.
|
||||
|
||||
## Concept
|
||||
|
||||
This sample is based on playback sample for the most part, reading and sampling an animation. In addition it loads an ozz binary mesh file which was generated with sample_fbx2mesh tool.
|
||||
At every frame, once the animation is sampled and model-space matrices computed, skinning matrices are built. Skinning matrices are the composition (multiplication) of model-space and mesh inverse bind pose matrices. This step can be seen as allowing to transform vertices into joint's local-space, so they can be transformed by this joint.
|
||||
|
||||
## Sample usage
|
||||
|
||||
Animation playback parameters can be tuned from sample UI:
|
||||
- Play/pause animation.
|
||||
- Fix animation time.
|
||||
- Set playback speed, which can be negative to go backward.
|
||||
|
||||
Rendering options are also exposed:
|
||||
- Enabling skeleton display.
|
||||
- Enabling mesh display.
|
||||
- Enabling skinning stage.
|
||||
- Display normals, tangent and binormals.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Load animation and skeleton, sample animation to get local-space transformations, and finally convert local-space transformations to model-space matrices. See Playback sample for more details about these steps.
|
||||
2. Load meshes from ozz archive. There can be multiple meshes as import utility (aka fbx2mesh) maintains dcc file meshes split.
|
||||
3. Computes and allocates skinning matrices. Number of skinning matrices might be less from the number of joints, as a mesh might be skinned by a subset of all skeleton joints only. Mesh::joint_remaps is used to know how to order skinning matrices, hence is size defines their number.
|
||||
4. Skinning matrices array is updated before rendering each mesh. A skinning matrix is the multiplication of the model-space and the mesh inverse bind pose matrix for a joint. Mesh::joint_remaps is used to index skeleton joints, so they match with the mesh.
|
||||
5. Skinning is performed by ozz::geometry::SkinningJob. Please check sample framework DrawSkinnedMesh function for more details about how to setup that job.
|
||||
@@ -0,0 +1,287 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/mesh.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.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)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Mesh archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(mesh,
|
||||
"Path to the skinned mesh (ozz archive format).",
|
||||
"media/mesh.ozz", false)
|
||||
|
||||
class SkinningSampleApplication : public ozz::sample::Application {
|
||||
protected:
|
||||
// Updates current animation time and skeleton pose.
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
if (draw_skeleton_) {
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
if (draw_mesh_) {
|
||||
// Builds skinning matrices, based on the output of the animation stage.
|
||||
// The mesh might not use (aka be skinned by) all skeleton joints. We use
|
||||
// the joint remapping table (available from the mesh object) to reorder
|
||||
// model-space matrices and build skinning ones.
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
for (size_t i = 0; i < mesh.joint_remaps.size(); ++i) {
|
||||
skinning_matrices_[i] =
|
||||
models_[mesh.joint_remaps[i]] * mesh.inverse_bind_poses[i];
|
||||
}
|
||||
|
||||
// Renders skin.
|
||||
success &= _renderer->DrawSkinnedMesh(
|
||||
mesh, make_span(skinning_matrices_),
|
||||
ozz::math::Float4x4::identity(), render_options_);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skeleton and animation needs to match.
|
||||
if (skeleton_.num_joints() != animation_.num_tracks()) {
|
||||
ozz::log::Err() << "The provided animation doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
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);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Reading skinned meshes.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_mesh, &meshes_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computes the number of skinning matrices required to skin all meshes.
|
||||
// A mesh is skinned by only a subset of joints, so the number of skinning
|
||||
// matrices might be less that the number of skeleton joints.
|
||||
// Mesh::joint_remaps is used to know how to order skinning matrices. So the
|
||||
// number of matrices required is the size of joint_remaps.
|
||||
size_t num_skinning_matrices = 0;
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
num_skinning_matrices =
|
||||
ozz::math::Max(num_skinning_matrices, mesh.joint_remaps.size());
|
||||
}
|
||||
|
||||
// Allocates skinning matrices.
|
||||
skinning_matrices_.resize(num_skinning_matrices);
|
||||
|
||||
// Check the skeleton matches with the mesh, especially that the mesh
|
||||
// doesn't expect more joints than the skeleton has.
|
||||
for (const ozz::sample::Mesh& mesh : meshes_) {
|
||||
if (num_joints < mesh.highest_joint_index()) {
|
||||
ozz::log::Err() << "The provided mesh doesn't match skeleton "
|
||||
"(joint count mismatch)."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes model informations.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Model statisitics", &open);
|
||||
if (open) {
|
||||
char label[255];
|
||||
sprintf(label, "%d animated joints", skeleton_.num_joints());
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int influences = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
influences = ozz::math::Max(influences, mesh.max_influences_count());
|
||||
}
|
||||
sprintf(label, "%d influences (max)", influences);
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int vertices = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
vertices += mesh.vertex_count();
|
||||
}
|
||||
sprintf(label, "%.1fK vertices", vertices / 1000.f);
|
||||
_im_gui->DoLabel(label);
|
||||
|
||||
int indices = 0;
|
||||
for (const auto& mesh : meshes_) {
|
||||
indices += mesh.triangle_index_count();
|
||||
}
|
||||
sprintf(label, "%.1fK triangles", indices / 3000.f);
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
controller_.OnGui(animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
// Expose mesh rendering options
|
||||
{
|
||||
// Rendering options.
|
||||
static bool oc_open = false;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Rendering options", &oc_open);
|
||||
if (oc_open) {
|
||||
_im_gui->DoCheckBox("Draw skeleton", &draw_skeleton_);
|
||||
_im_gui->DoCheckBox("Draw mesh", &draw_mesh_);
|
||||
|
||||
_im_gui->DoCheckBox("Show texture", &render_options_.texture);
|
||||
_im_gui->DoCheckBox("Show normals", &render_options_.normals);
|
||||
_im_gui->DoCheckBox("Show tangents", &render_options_.tangents);
|
||||
_im_gui->DoCheckBox("Show binormals", &render_options_.binormals);
|
||||
_im_gui->DoCheckBox("Show colors", &render_options_.colors);
|
||||
_im_gui->DoCheckBox("Wireframe", &render_options_.wireframe);
|
||||
_im_gui->DoCheckBox("Skip skinning", &render_options_.skip_skinning);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputeSkeletonBounds(skeleton_, _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Buffer of skinning matrices, result of the joint multiplication of the
|
||||
// inverse bind pose with the model space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
ozz::vector<ozz::sample::Mesh> meshes_;
|
||||
|
||||
// Redering options.
|
||||
bool draw_skeleton_ = false;
|
||||
bool draw_mesh_ = true;
|
||||
|
||||
// Mesh rendering options.
|
||||
ozz::sample::Renderer::Options render_options_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Skinning";
|
||||
return SkinningSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/robot_skeleton.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/robot_skeleton.ozz" "./media/skeleton.ozz")
|
||||
|
||||
add_executable(sample_two_bone_ik
|
||||
sample_two_bone_ik.cc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz")
|
||||
target_link_libraries(sample_two_bone_ik
|
||||
sample_framework
|
||||
ozz_animation_offline
|
||||
ozz_animation)
|
||||
|
||||
set_target_properties(sample_two_bone_ik
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_two_bone_ik
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_two_bone_ik.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_two_bone_ik.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_two_bone_ik.wasm
|
||||
DESTINATION bin/samples/two_bone_ik)
|
||||
else()
|
||||
install(TARGETS sample_two_bone_ik DESTINATION bin/samples/two_bone_ik)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/two_bone_ik)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/two_bone_ik)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_two_bone_ik COMMAND sample_two_bone_ik "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_two_bone_ik_path COMMAND sample_two_bone_ik "--skeleton=media/skeleton.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_two_bone_ik_invalid_skeleton_path COMMAND sample_two_bone_ik "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_two_bone_ik_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
@@ -0,0 +1,33 @@
|
||||
# Ozz-animation sample: Two bone IK
|
||||
|
||||
## Description
|
||||
|
||||
Performs two bone IK on robot arm skeleton.
|
||||
|
||||
## Concept
|
||||
|
||||
This sample uses ozz::animation::IKTwoBoneJob to compute two bone IK on a skeleton. Inverse kinematic allows a chain of joints to reach a target position. The job computes the transformations (rotations only) that needs to be applied to the first two joints of the chain such that the third joint reaches the provided target position (if possible). The three joints don't need to be consecutive though, they just require to be from the same chain.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample exposes IKTwoBoneJob parameters:
|
||||
|
||||
- The pole vector which defines where the direction the middle joint should point to, allowing to control IK chain orientation.
|
||||
- Twist_angle which rotates IK chain around the vector define by start-to-target vector.
|
||||
- Soften ratio, allowing the chain to gradually fall behind the target position. This prevents the joint chain from snapping into the final position, softening the final degrees before the joint chain becomes flat.
|
||||
- Weight given to the IK correction. This allows to blend / interpolate from zero to full IK.
|
||||
float weight;
|
||||
|
||||
Target position is animated by the sample, but can also be tweaked manually, as well as skeleton root transformation. Fix initial transform option select whether ik job is run from the skeleton bind pose or the last frame transforms. This allows to use IK job's weighting parameter, as well as stressing it with a wide range of input.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. At initialization time:
|
||||
1. Loads skeleton. See "playback" sample for more details.
|
||||
2. Locates the three joint indices from the skeleton, searching them by name.
|
||||
2. At run time:
|
||||
1. Updates skeleton joints model-space matrices from local-space transforms, just like it would be done at the end of animation sampling and blending stages.
|
||||
2. Target and pole vectors are converted to skeleton model space.
|
||||
3. Setup IKTwoBoneJob with sample parameters, providing the three joints model-space matrices.
|
||||
4. Once run, the job outputs two quaternions for the two first joints of the chain. The samples multiplies them to their respective local-space rotations.
|
||||
5. Model-space matrices must be updated again. Only the children of the first joint of the chain need to be updated again.
|
||||
@@ -0,0 +1,436 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
add_custom_command(
|
||||
DEPENDS $<$<BOOL:${ozz_build_fbx}>:BUILD_DATA>
|
||||
"${CMAKE_CURRENT_LIST_DIR}/README.md"
|
||||
"${ozz_media_directory}/bin/robot_skeleton.ozz"
|
||||
"${ozz_media_directory}/bin/robot_animation.ozz"
|
||||
"${ozz_media_directory}/bin/robot_track_grasp.ozz"
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/track.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory media
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/README.md" .
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/robot_skeleton.ozz" "./media/skeleton.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/robot_animation.ozz" "./media/animation.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/robot_track_grasp.ozz" "./media/track.ozz")
|
||||
|
||||
add_executable(sample_user_channel
|
||||
sample_user_channel.cc
|
||||
config.json
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/README.md"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/skeleton.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/track.ozz")
|
||||
target_link_libraries(sample_user_channel
|
||||
sample_framework)
|
||||
|
||||
set_target_properties(sample_user_channel
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# Resource files are embedded to the output file with emscripten
|
||||
set_target_properties(sample_user_channel
|
||||
PROPERTIES LINK_FLAGS "--embed-file media --embed-file README.md --memory-init-file 0")
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_user_channel.html
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_user_channel.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sample_user_channel.wasm
|
||||
DESTINATION bin/samples/user_channel)
|
||||
else()
|
||||
install(TARGETS sample_user_channel DESTINATION bin/samples/user_channel)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/media DESTINATION bin/samples/user_channel)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/README.md DESTINATION bin/samples/user_channel)
|
||||
endif(EMSCRIPTEN)
|
||||
|
||||
add_test(NAME sample_user_channel COMMAND sample_user_channel "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_user_channel_path1 COMMAND sample_user_channel "--skeleton=media/skeleton.ozz" "--animation=media/animation.ozz" "--max_idle_loops=${ozz_sample_testing_loops}" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
add_test(NAME sample_user_channel_invalid_skeleton_path COMMAND sample_user_channel "--skeleton=media/bad_skeleton.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_user_channel_invalid_skeleton_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_user_channel_invalid_animation_path COMMAND sample_user_channel "--animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_user_channel_invalid_animation_path PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_user_channel_invalid_track_path COMMAND sample_user_channel "--track=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_user_channel_invalid_track_path PROPERTIES WILL_FAIL true)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Ozz-animation sample: Animation user channels
|
||||
|
||||
## Description
|
||||
|
||||
The samples uses a user channel track to drive attachment state of a box manipulated by a robot's arm. The track was edited in a DCC tool as a custom property, and imported along side the animation using fbx2ozz.
|
||||
|
||||
## Concept
|
||||
|
||||
User-channels are the way to animate data that aren't joint transformations: float, bool... In this sample the user-data is the boolean attachment state of the box with the robot's finger. The sample attaches the box (see attachment sample) to the robot's finger when the boolean becomes true, and detaches it when it's getting false.
|
||||
The user-channel track was edited in a DCC tool as a custom property and saved in the fbx file alongside the animation. fbx2ozz is used to extract this track using "animations[].tracks[].properties[]" parameter of fbx2ozz json configuration. See [config.json](config.json).
|
||||
|
||||
The sample demonstrates two ways to use the user-channel track, based on ozz API:
|
||||
1. Sampling: Using ozz::animation::TrackSamplingJob, the samples queries the track value each keyframe. It will get true or false depending on if the box should be attached or detached for current frame time. If the box is detached, its position remains unchanged. If it is attached, the sample computes box position as transformed by the finger joint. The relative transformation of the box to the finger is stored when attachment state switches from off to on. Because the frame time isn't exactly the time when the state has changed, a transformation error is accumulated.
|
||||
2. Edge triggering: Using ozz::animation::TrackTriggeringJob, the samples iterates all state changes that happened since the last update. This allows to compute the box relative transformation for each state change, at the exact state change time. This prevents accumulating transformation error. It even supports jumping to any point in time, because all state changes (attach / detach) are be processed.
|
||||
|
||||
## Sample usage
|
||||
|
||||
The sample exposes usual animation playback settings. It also exposes options to choose to from sampling or edge triggering technique (default option).
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Loading of data is based on animation playback sample, including the user-channel track.
|
||||
2. Box attachment and transformation is based on attachment sample.
|
||||
3. When attachment state switches from off to on, the relative transformation of the box to the finger joint is computed (inverse wolrd-space joint matrix * box world-space matrix).
|
||||
4. The sample uses 2 techniques to detect attachment state changes:
|
||||
1. Sampling, based on ozz::animation::TrackSamplingJob. User-channel track is sampled at the same time t as the animation, which gives attachment state for the current frame. State changes are thus detected depending on application frame rate.
|
||||
2. Edge triggering, based on ozz::animation::TrackTriggeringJob. TrackTriggeringJob computes rising and falling edges for a given period of time. The sample thus queries all edges from the previous frame time to the current one. The exact edge time (aka when the track crosses threshold value) is used to compute finger joint transformation, instead of current frame time (as with sampling case). This make the algorithm frame rate independent.
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"skeleton":
|
||||
{
|
||||
"filename":"robot_skeleton.ozz",
|
||||
"import":
|
||||
{
|
||||
"enable":true // Skeleton needs to be imported/created from the source file.
|
||||
}
|
||||
},
|
||||
"animations":
|
||||
[
|
||||
{
|
||||
"filename":"robot_animation.ozz",
|
||||
"tracks":
|
||||
[
|
||||
{
|
||||
"properties": // User-channel property track must be imported.
|
||||
[
|
||||
{
|
||||
"type" : "float1", // Type of the property.
|
||||
"joint_name":"thumb2", // Get the property from this node
|
||||
"property_name":"grasp", // Name of the custom property
|
||||
"filename":"robot_track_grasp.ozz" // Output filename
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
//----------------------------------------------------------------------------//
|
||||
// //
|
||||
// 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/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/animation/runtime/track.h"
|
||||
#include "ozz/animation/runtime/track_sampling_job.h"
|
||||
#include "ozz/animation/runtime/track_triggering_job.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
// Some scene constants.
|
||||
const ozz::math::Box kBox(ozz::math::Float3(-.01f, -.1f, -.05f),
|
||||
ozz::math::Float3(.01f, .1f, .05f));
|
||||
|
||||
const ozz::math::SimdFloat4 kBoxInitialPosition =
|
||||
ozz::math::simd_float4::Load(0.f, .1f, .3f, 0.f);
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
"Path to the skeleton (ozz archive format).",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
// Animation archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
"Path to the animation (ozz archive format).",
|
||||
"media/animation.ozz", false)
|
||||
|
||||
// Track archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(track, "Path to the track (ozz archive format).",
|
||||
"media/track.ozz", false)
|
||||
|
||||
class UserChannelSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
UserChannelSampleApplication()
|
||||
: method_(kTriggering), // Triggering is the most robust method.
|
||||
attached_(false),
|
||||
attach_joint_(0) {
|
||||
ResetState();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Resets everything to it's initial state.
|
||||
void ResetState() {
|
||||
controller_.set_time_ratio(0.f);
|
||||
attached_ = false;
|
||||
box_local_transform_ = ozz::math::Float4x4::identity();
|
||||
box_world_transform_ =
|
||||
ozz::math::Float4x4::Translation(kBoxInitialPosition);
|
||||
}
|
||||
|
||||
virtual bool OnUpdate(float _dt, float) {
|
||||
// Updates current animation time.
|
||||
controller_.Update(animation_, _dt);
|
||||
|
||||
// Update attachment state depending on the selected method, aka sample or
|
||||
// triggering.
|
||||
if (method_ == kSampling) {
|
||||
if (!Update_SamplingMethod()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!Update_TriggeringMethod()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Updates box transform based on attachment state.
|
||||
if (attached_) {
|
||||
box_world_transform_ = models_[attach_joint_] * box_local_transform_;
|
||||
} else {
|
||||
// Lets the box where it is.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Update_SamplingMethod() {
|
||||
// Updates animation and computes new joints position at current frame time.
|
||||
if (!Update_Joints(controller_.time_ratio())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Samples the track in order to know if the box should be attached to the
|
||||
// skeleton joint (hand).
|
||||
ozz::animation::FloatTrackSamplingJob job;
|
||||
|
||||
// Tracks have a unit length duration. They are thus sampled with a ratio
|
||||
// (rather than a time), which is computed based on the duration of the
|
||||
// animation they refer to.
|
||||
job.ratio = controller_.time_ratio();
|
||||
job.track = &track_;
|
||||
float attached;
|
||||
job.result = &attached;
|
||||
if (!job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool previously_attached = attached_;
|
||||
attached_ = attached != 0.f;
|
||||
|
||||
// If box is being attached, then computes it's relative position with the
|
||||
// attachment joint.
|
||||
if (attached_ && !previously_attached) {
|
||||
box_local_transform_ =
|
||||
Invert(models_[attach_joint_]) * box_world_transform_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Update_TriggeringMethod() {
|
||||
// Walks through the track to find edges, aka when the box should be
|
||||
// attached or detached.
|
||||
ozz::animation::TrackTriggeringJob job;
|
||||
|
||||
// Tracks have a unit length duration. They are thus sampled with a ratio
|
||||
// (rather than a time), which is computed based on the duration of the
|
||||
// animation they refer to.
|
||||
// Its important to use exact "previous time" here, because if we recompute
|
||||
// it, we might end up using a time range that is not exactly the previous
|
||||
// one, leading to missed or redundant edges.
|
||||
// Previous_time can be higher that current time, in case of a loop. It's
|
||||
// not a problem. Edges will be triggered backward (rewinding track in
|
||||
// time), so the "attachment" state remains valid. It's not the shortest or
|
||||
// optimum path though.
|
||||
job.from = controller_.previous_time_ratio();
|
||||
job.to = controller_.time_ratio();
|
||||
job.track = &track_;
|
||||
job.threshold = 0.f; // Considered attached as soon as the value is
|
||||
// greater than 0, aka different from 0.
|
||||
ozz::animation::TrackTriggeringJob::Iterator iterator;
|
||||
job.iterator = &iterator;
|
||||
if (!job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iteratively evaluates all edges.
|
||||
// Edges are lazily evaluated on iterator increments.
|
||||
for (const ozz::animation::TrackTriggeringJob::Iterator end = job.end();
|
||||
iterator != end; ++iterator) {
|
||||
const ozz::animation::TrackTriggeringJob::Edge& edge = *iterator;
|
||||
|
||||
// Updates attachment state.
|
||||
// Triggering job ensures rising and falling edges symmetry, this can be
|
||||
// asserted.
|
||||
assert(attached_ != edge.rising);
|
||||
attached_ = edge.rising;
|
||||
|
||||
// Knowing exact edge ratio, joint position can be re-sampled in order
|
||||
// to get attachment joint position at the precise attachment time. This
|
||||
// makes the algorithm frame rate independent.
|
||||
// Sampling is cached so this intermediate updates don't have a big
|
||||
// performance impact.
|
||||
if (!Update_Joints(edge.ratio)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (edge.rising) {
|
||||
// Box is being attached on rising edges.
|
||||
// Find the relative transform of the box to the attachment joint at the
|
||||
// exact time of the rising edge.
|
||||
box_local_transform_ =
|
||||
Invert(models_[attach_joint_]) * box_world_transform_;
|
||||
} else {
|
||||
// Box is being detached on falling edges.
|
||||
// Compute box position when at the exact time it is released.
|
||||
box_world_transform_ = models_[attach_joint_] * box_local_transform_;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally updates animation and computes joints position at current frame
|
||||
// time.
|
||||
return Update_Joints(controller_.time_ratio());
|
||||
}
|
||||
|
||||
bool Update_Joints(float _ratio) {
|
||||
// Samples animation at r = _ratio.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.ratio = _ratio;
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts from local space to model space matrices.
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
// Draw box at the position computed during update.
|
||||
success &= _renderer->DrawBoxShaded(
|
||||
kBox, ozz::span<ozz::math::Float4x4>(box_world_transform_),
|
||||
ozz::sample::kGrey);
|
||||
|
||||
// Draws a sphere at hand position, which shows "attached" flag status.
|
||||
const ozz::sample::Color colors[] = {{0, 0xff, 0, 0xff},
|
||||
{0xff, 0, 0, 0xff}};
|
||||
_renderer->DrawSphereIm(.01f, models_[attach_joint_], colors[attached_]);
|
||||
|
||||
// Draws the animated skeleton.
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finds the hand joint where the box should be attached.
|
||||
// If not found, let it be 0.
|
||||
for (int i = 0; i < skeleton_.num_joints(); i++) {
|
||||
if (std::strstr(skeleton_.joint_names()[i], "thumb2")) {
|
||||
attach_joint_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
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);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
|
||||
// Reading track.
|
||||
if (!ozz::sample::LoadTrack(OPTIONS_track, &track_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
// Exposes sample specific parameters.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Track access method", &open);
|
||||
int method = static_cast<int>(method_);
|
||||
bool changed = _im_gui->DoRadioButton(kSampling, "Sampling", &method);
|
||||
changed |= _im_gui->DoRadioButton(kTriggering, "Triggering", &method);
|
||||
if (changed) {
|
||||
method_ = static_cast<Method>(method);
|
||||
ResetState();
|
||||
}
|
||||
}
|
||||
// Exposes animation runtime playback controls.
|
||||
{
|
||||
static bool open = true;
|
||||
ozz::sample::ImGui::OpenClose oc(_im_gui, "Animation control", &open);
|
||||
if (open) {
|
||||
if (controller_.OnGui(animation_, _im_gui, true)) {
|
||||
// Triggering method can adapt to "big" time changes (jumps), because
|
||||
// all the events will be triggered and processed for the whole time
|
||||
// period.
|
||||
// No point doing that for sampling method.
|
||||
if (method_ == kTriggering) {
|
||||
if (!Update_TriggeringMethod()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
}
|
||||
|
||||
private:
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime skeleton.
|
||||
ozz::animation::Skeleton skeleton_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Runtime float track.
|
||||
// Stores whether the box should be attached to the hand.
|
||||
ozz::animation::FloatTrack track_;
|
||||
|
||||
// Track reading method.
|
||||
enum Method {
|
||||
kSampling, // Will use TrackSamplingJob
|
||||
kTriggering // Will use TrackTriggeringJob
|
||||
} method_;
|
||||
|
||||
// Stores whether the box is currently attached. This flag is computed
|
||||
// during update. This is only used for debug display purpose.
|
||||
bool attached_;
|
||||
|
||||
// Index of the joint where the box must be attached.
|
||||
int attach_joint_;
|
||||
|
||||
// Box current transformation.
|
||||
ozz::math::Float4x4 box_world_transform_;
|
||||
|
||||
// Box transformation relative to the attached bone.
|
||||
ozz::math::Float4x4 box_local_transform_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: User channels";
|
||||
return UserChannelSampleApplication().Run(_argc, _argv, "1.0", title);
|
||||
}
|
||||
Reference in New Issue
Block a user