Updated ozz-animation to version 0.14.1 @35b2efd4
This commit is contained in:
+13
-7
@@ -5,16 +5,19 @@ add_custom_command(
|
||||
"${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"
|
||||
"${ozz_media_directory}/bin/pab_curl_additive.ozz"
|
||||
"${ozz_media_directory}/bin/pab_splay_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"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_curl_additive.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_splay_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"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_curl_additive.ozz" "./media/animation_curl_additive.ozz"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${ozz_media_directory}/bin/pab_splay_additive.ozz" "./media/animation_splay_additive.ozz"
|
||||
VERBATIM)
|
||||
|
||||
add_executable(sample_additive
|
||||
@@ -22,10 +25,11 @@ add_executable(sample_additive
|
||||
"${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")
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_curl_additive.ozz"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_splay_additive.ozz")
|
||||
target_link_libraries(sample_additive
|
||||
sample_framework)
|
||||
|
||||
target_copy_shared_libraries(sample_additive)
|
||||
set_target_properties(sample_additive
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
@@ -46,10 +50,12 @@ else()
|
||||
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_path COMMAND sample_additive "--skeleton=media/skeleton.ozz" "--animation=media/animation_base.ozz" "--splay_animation=media/animation_splay_additive.ozz" "--curl_animation=media/animation_curl_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>)
|
||||
add_test(NAME sample_additive_invalid_animation_path2 COMMAND sample_additive "--curl_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_additive_invalid_animation_path2 PROPERTIES WILL_FAIL true)
|
||||
add_test(NAME sample_additive_invalid_animation_path3 COMMAND sample_additive "--splay_animation=media/bad_animation.ozz" $<$<BOOL:${ozz_run_tests_headless}>:--norender>)
|
||||
set_tests_properties(sample_additive_invalid_animation_path3 PROPERTIES WILL_FAIL true)
|
||||
+10
-17
@@ -2,32 +2,25 @@
|
||||
|
||||
## 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.
|
||||
Additive blending is a key concept in run-time animation. Superimposing a movement on top of a playing animation allows to add variety while lessening animation count and management complexity. In this sample, 2 *splay* and *curl* hand animations are a added to a *walk* cycle. A weight is associated to each additive layers independently, allowing to control fingers.
|
||||
|
||||
## 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 different from normal blending because it does not interpolate from a pose to an other. Instead it combines poses, meaning the two animations can be seen at the same time. In this example the walk cycle is never altered. Fingers curl and splay poses coming from the additive animation are simply added (visually speaking) to the base animation. For the purpose of this sample, only the first frames of the 2 additive animations are used, aka a fixed pose for each (curl and splay poses).
|
||||
|
||||
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.
|
||||
Additive blending is performed by ozz::animation::BlendingJob. 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.
|
||||
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 and gltf2ozz support additive configuration option to export delta animations from a source file.
|
||||
|
||||
## 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.
|
||||
The sample proposes to tune base and additive (curl & splay) layers weights. Additive layers weight are exposed via 2d slider to make it easier to control together.
|
||||
|
||||
## 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.
|
||||
1. Loads base, additive (curl and splay) animations, and their skeleton. See "playback" sample for more details.
|
||||
2. At initialization time, the first frame of curl and splay animations are sampled in order to extract their respective local-space transformations. Sampling an additive animation is not different from a standard one. The 2 animations are deleted as only the extracted pose will be used further.
|
||||
3. At runtime, base animation is sampled to get skeleton base local-space transformations.
|
||||
3. Setups ozz::animation::BlendingJob object. BlendingJob takes as input two arrays of BlendingJob::Layer, one for standard blending (base animation sampling output), the other for additive blending (the 2 curl and splay poses). Each layer is setup with its weights and the local-space transforms.
|
||||
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.
|
||||
5. Model-space matrices array is then used for rendering the animated skeleton pose (or the skinned mesh in a real world example).
|
||||
|
||||
+167
-200
@@ -39,6 +39,7 @@
|
||||
#include "ozz/animation/runtime/skeleton_utils.h"
|
||||
#include "ozz/base/containers/vector.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"
|
||||
@@ -56,68 +57,65 @@ OZZ_OPTIONS_DECLARE_STRING(animation,
|
||||
|
||||
// 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)
|
||||
splay_animation,
|
||||
"Path to the additive splay animation (ozz archive format).",
|
||||
"media/animation_splay_additive.ozz", false)
|
||||
OZZ_OPTIONS_DECLARE_STRING(
|
||||
curl_animation, "Path to the additive curl animation (ozz archive format).",
|
||||
"media/animation_curl_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) {}
|
||||
: base_weight_(0.f),
|
||||
additive_weigths_{.3f, .9f},
|
||||
auto_animate_weights_(true) {}
|
||||
|
||||
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;
|
||||
}
|
||||
// For the sample purpose, animates additive weights automatically so the
|
||||
// hand moves.
|
||||
if (auto_animate_weights_) {
|
||||
AnimateWeights(_dt);
|
||||
}
|
||||
|
||||
// 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_
|
||||
// Updates base animation time for main animation.
|
||||
controller_.Update(base_animation_, _dt);
|
||||
|
||||
// Prepares standard blending layers.
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &base_animation_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setups blending job layers.
|
||||
|
||||
// Main animation is used as-is.
|
||||
ozz::animation::BlendingJob::Layer layers[1];
|
||||
layers[0].transform = make_span(samplers_[kMainAnimation].locals);
|
||||
layers[0].weight = samplers_[kMainAnimation].weight_setting;
|
||||
layers[0].transform = make_span(locals_);
|
||||
layers[0].weight = base_weight_;
|
||||
layers[0].joint_weights = make_span(base_joint_weights_);
|
||||
|
||||
// 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_);
|
||||
// The two additive layers (curl and splay) are blended on top of the main
|
||||
// layer.
|
||||
ozz::animation::BlendingJob::Layer additive_layers[kNumLayers];
|
||||
for (size_t i = 0; i < kNumLayers; ++i) {
|
||||
additive_layers[i].transform = make_span(additive_locals_[i]);
|
||||
additive_layers[i].weight = additive_weigths_[i];
|
||||
}
|
||||
|
||||
// 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.rest_pose = skeleton_.joint_rest_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
@@ -141,12 +139,34 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimateWeights(float _dt) {
|
||||
static float t = 0.f;
|
||||
t += _dt;
|
||||
additive_weigths_[0] = .5f + std::cos(t * 1.7f) * .5f;
|
||||
additive_weigths_[1] = .5f + std::cos(t * 2.5f) * .5f;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
bool SetJointWeights(const char* _name, float _weight) {
|
||||
const auto set_joint = [this, _weight](int _joint, int) {
|
||||
ozz::math::SimdFloat4& soa_weight = base_joint_weights_[_joint / 4];
|
||||
soa_weight = ozz::math::SetI(
|
||||
soa_weight, ozz::math::simd_float4::Load1(_weight), _joint % 4);
|
||||
};
|
||||
|
||||
const int joint = FindJoint(skeleton_, _name);
|
||||
if (joint >= 0) {
|
||||
ozz::animation::IterateJointsDF(skeleton_, set_joint, joint);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Reading skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
@@ -155,81 +175,70 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
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);
|
||||
// Reads base animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &base_animation_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default weight settings.
|
||||
samplers_[kMainAnimation].weight_setting = 1.f;
|
||||
if (num_joints != base_animation_.num_tracks()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
upper_body_joint_weight_setting_ = 1.f;
|
||||
samplers_[kAdditiveAnimation].weight_setting = 1.f;
|
||||
// Allocates sampling context.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Allocates local space runtime buffers of blended data.
|
||||
blended_locals_.resize(num_soa_joints);
|
||||
// Allocates local space runtime buffers for base animation.
|
||||
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);
|
||||
// Storage for blending stage output.
|
||||
blended_locals_.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;
|
||||
// Allocates and sets base animation mask weights to one.
|
||||
base_joint_weights_.resize(num_soa_joints, ozz::math::simd_float4::one());
|
||||
SetJointWeights("Lefthand", 0.f);
|
||||
SetJointWeights("RightHand", 0.f);
|
||||
|
||||
// Reads and extract additive animations pose.
|
||||
const char* filenames[] = {OPTIONS_splay_animation, OPTIONS_curl_animation};
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
// Reads animation on the stack as it won't need to be maintained in
|
||||
// memory. Only the pose is needed.
|
||||
ozz::animation::Animation animation;
|
||||
if (!ozz::sample::LoadAnimation(filenames[i], &animation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_joints != animation.num_tracks()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates additive poses, aka buffers of Soa tranforms.
|
||||
additive_locals_[i].resize(num_soa_joints);
|
||||
|
||||
// Samples the first frame pose.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = 0.f; // Only needs the first frame pose
|
||||
sampling_job.output = make_span(additive_locals_[i]);
|
||||
|
||||
// Samples animation.
|
||||
if (!sampling_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invalidates context which will be re-used for another animation.
|
||||
// This is usually not needed, animation address on the stack is the same
|
||||
// each loop, hence creating an issue as animation content is changing.
|
||||
context_.Invalidate();
|
||||
}
|
||||
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) {
|
||||
@@ -241,69 +250,31 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
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);
|
||||
std::sprintf(label, "Layer weight: %.2f", base_weight_);
|
||||
_im_gui->DoSlider(label, 0.f, 1.f, &base_weight_, 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);
|
||||
_im_gui->DoCheckBox("Animates weights", &auto_animate_weights_);
|
||||
ozz::array<float, OZZ_ARRAY_SIZE(additive_weigths_)> weights;
|
||||
std::memcpy(weights.data(), additive_weigths_,
|
||||
sizeof(additive_weigths_));
|
||||
|
||||
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();
|
||||
std::sprintf(label, "Weights\nCurl: %.2f\nSplay: %.2f",
|
||||
additive_weigths_[kCurl], additive_weigths_[kSplay]);
|
||||
if (_im_gui->DoSlider2D(label, {{0.f, 0.f}}, {{1.f, 1.f}}, &weights)) {
|
||||
auto_animate_weights_ = false; // User interacted.
|
||||
std::memcpy(additive_weigths_, weights.data(),
|
||||
sizeof(additive_weigths_));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exposes animations runtime playback controls.
|
||||
|
||||
// Exposes base animation 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);
|
||||
}
|
||||
}
|
||||
controller_.OnGui(base_animation_, _im_gui);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,62 +282,55 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
}
|
||||
|
||||
virtual void GetSceneBounds(ozz::math::Box* _bound) const {
|
||||
ozz::sample::ComputePostureBounds(make_span(models_), _bound);
|
||||
// Finds the "hand" joint in the joint hierarchy.
|
||||
const int hand = FindJoint(skeleton_, "Lefthand");
|
||||
|
||||
// Creates a bounding volume around the hand.
|
||||
if (hand != -1) {
|
||||
const ozz::math::Float4x4& hand_matrix = models_[hand];
|
||||
ozz::math::Float3 hand_position;
|
||||
ozz::math::Store3PtrU(hand_matrix.cols[3], &hand_position.x);
|
||||
const ozz::math::Float3 extent(.15f);
|
||||
_bound->min = hand_position - extent;
|
||||
_bound->max = hand_position + extent;
|
||||
} else {
|
||||
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,
|
||||
};
|
||||
// The number of additive layers to blend.
|
||||
enum { kSplay, kCurl, kNumLayers };
|
||||
|
||||
// Sampler structure contains all the data required to sample a single
|
||||
// animation.
|
||||
struct Sampler {
|
||||
// Constructor, default initialization.
|
||||
Sampler() : weight_setting(1.f) {}
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation base_animation_;
|
||||
|
||||
// Playback animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
// Per-joint weights used to define the base animation mask. Allows to remove
|
||||
// hands from base animations.
|
||||
ozz::vector<ozz::math::SimdFloat4> base_joint_weights_;
|
||||
|
||||
// Blending weight_setting for the layer.
|
||||
float weight_setting;
|
||||
// Main animation controller. This is a utility class that helps with
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller_;
|
||||
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
// Buffer of local transforms as sampled from main animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
// Blending weight of the base animation layer.
|
||||
float base_weight_;
|
||||
|
||||
} samplers_[kNumLayers]; // kNumLayers animations to blend.
|
||||
// Poses of local transforms as sampled from curl and splay animations.
|
||||
// They are sampled during initialization, as a single pose is used.
|
||||
ozz::vector<ozz::math::SoaTransform> additive_locals_[kNumLayers];
|
||||
|
||||
// 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_;
|
||||
// Blending weight of the additive animation layer.
|
||||
float additive_weigths_[kNumLayers];
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> blended_locals_;
|
||||
@@ -374,7 +338,10 @@ class AdditiveBlendSampleApplication : public ozz::sample::Application {
|
||||
// Buffer of model space matrices. These are computed by the local-to-model
|
||||
// job after the blending stage.
|
||||
ozz::vector<ozz::math::Float4x4> models_;
|
||||
};
|
||||
|
||||
// Automatically animates additive weights.
|
||||
bool auto_animate_weights_;
|
||||
};
|
||||
|
||||
int main(int _argc, const char** _argv) {
|
||||
const char* title = "Ozz-animation sample: Additive animations blending";
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ add_executable(sample_attach
|
||||
|
||||
target_link_libraries(sample_attach
|
||||
sample_framework)
|
||||
|
||||
target_copy_shared_libraries(sample_attach)
|
||||
set_target_properties(sample_attach
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
|
||||
+19
-24
@@ -25,24 +25,21 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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"
|
||||
#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/skeleton_utils.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"
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
@@ -67,7 +64,7 @@ class AttachSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -133,15 +130,13 @@ class AttachSampleApplication : public ozz::sample::Application {
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.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;
|
||||
}
|
||||
attachment_ = FindJoint(skeleton_, "LeftHandMiddle1");
|
||||
if (attachment_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -198,8 +193,8 @@ class AttachSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
@@ -23,6 +23,7 @@ add_executable(sample_baked
|
||||
|
||||
target_link_libraries(sample_baked
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_baked)
|
||||
|
||||
set_target_properties(sample_baked
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+15
-19
@@ -25,24 +25,20 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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"
|
||||
#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"
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
@@ -67,7 +63,7 @@ class BakedSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -103,8 +99,8 @@ class BakedSampleApplication : public ozz::sample::Application {
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Look for a "camera" joint.
|
||||
for (int i = 0; i < num_joints; i++) {
|
||||
@@ -167,8 +163,8 @@ class BakedSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
@@ -31,6 +31,7 @@ add_executable(sample_blend
|
||||
|
||||
target_link_libraries(sample_blend
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_blend)
|
||||
|
||||
set_target_properties(sample_blend
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -23,6 +23,6 @@ There are two ways to use the sample:
|
||||
|
||||
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.
|
||||
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 rest-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.
|
||||
+11
-15
@@ -25,26 +25,22 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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/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).",
|
||||
@@ -97,7 +93,7 @@ class BlendSampleApplication : public ozz::sample::Application {
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &sampler.animation;
|
||||
sampling_job.cache = &sampler.cache;
|
||||
sampling_job.context = &sampler.context;
|
||||
sampling_job.ratio = sampler.controller.time_ratio();
|
||||
sampling_job.output = make_span(sampler.locals);
|
||||
|
||||
@@ -123,7 +119,7 @@ class BlendSampleApplication : public ozz::sample::Application {
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = threshold_;
|
||||
blend_job.layers = layers;
|
||||
blend_job.bind_pose = skeleton_.joint_bind_poses();
|
||||
blend_job.rest_pose = skeleton_.joint_rest_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
@@ -216,8 +212,8 @@ class BlendSampleApplication : public ozz::sample::Application {
|
||||
// Allocates sampler runtime buffers.
|
||||
sampler.locals.resize(num_soa_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
sampler.cache.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
sampler.context.Resize(num_joints);
|
||||
}
|
||||
|
||||
// Allocates local space runtime buffers of blended data.
|
||||
@@ -319,14 +315,14 @@ class BlendSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context;
|
||||
|
||||
// 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.
|
||||
// Blending job rest pose threshold.
|
||||
float threshold_;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
|
||||
BIN
Binary file not shown.
@@ -29,6 +29,7 @@ add_executable(sample_foot_ik
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/floor.ozz")
|
||||
target_link_libraries(sample_foot_ik
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_foot_ik)
|
||||
|
||||
set_target_properties(sample_foot_ik
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+17
-23
@@ -25,34 +25,28 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#include <limits>
|
||||
|
||||
#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/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).",
|
||||
@@ -107,7 +101,7 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
FootIKSampleApplication()
|
||||
: pelvis_offset_(0.f, 0.f, 0.f),
|
||||
root_translation_(2.17f, 2.f, -2.06f),
|
||||
root_yaw_(2.f),
|
||||
root_yaw_(-2.f),
|
||||
foot_heigh_(.12f),
|
||||
weight_(1.f),
|
||||
soften_(1.f),
|
||||
@@ -186,7 +180,7 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -481,8 +475,8 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
}
|
||||
} else {
|
||||
// Renders skeleton only.
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
offsetted_root);
|
||||
success &=
|
||||
_renderer->DrawPosture(skeleton_, make_span(models_), offsetted_root);
|
||||
}
|
||||
|
||||
// Showing joints
|
||||
@@ -557,8 +551,8 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Finds left and right joints.
|
||||
if (!SetupLeg(skeleton_, kLeftJointNames, &legs_setup_[kLeft]) ||
|
||||
@@ -597,7 +591,7 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
bool SetupLeg(const ozz::animation::Skeleton& _skeleton,
|
||||
const char* _joint_names[3], LegSetup* _leg) {
|
||||
int found = 0;
|
||||
int joints[3] = {0, 0, 0};
|
||||
int joints[3] = {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) {
|
||||
@@ -742,8 +736,8 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
@@ -752,7 +746,7 @@ class FootIKSampleApplication : public ozz::sample::Application {
|
||||
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.
|
||||
// inverse rest pose with the model space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
|
||||
+2
-5
@@ -29,9 +29,7 @@ add_library(sample_framework STATIC
|
||||
# Samples requires OpenGL package.
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/extern/glfw glfw)
|
||||
|
||||
target_link_libraries(sample_framework
|
||||
glfw)
|
||||
target_link_libraries(sample_framework glfw)
|
||||
endif()
|
||||
|
||||
target_link_libraries(sample_framework
|
||||
@@ -43,7 +41,6 @@ if(TARGET BUILD_DATA_SAMPLE)
|
||||
add_dependencies(sample_framework BUILD_DATA_SAMPLE)
|
||||
endif()
|
||||
|
||||
set_target_properties(sample_framework
|
||||
PROPERTIES FOLDER "samples")
|
||||
set_target_properties(sample_framework PROPERTIES FOLDER "samples")
|
||||
|
||||
add_subdirectory(tools)
|
||||
|
||||
@@ -476,6 +476,12 @@ bool Application::Gui() {
|
||||
|
||||
// Downcast to public imgui.
|
||||
ImGui* im_gui = im_gui_.get();
|
||||
|
||||
// Do floating gui.
|
||||
if (!show_help_) {
|
||||
success = OnFloatingGui(im_gui);
|
||||
}
|
||||
|
||||
// Help gui.
|
||||
{
|
||||
math::RectFloat rect(kGuiMargin, kGuiMargin,
|
||||
@@ -658,6 +664,31 @@ bool Application::FrameworkGui() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::OnInitialize() { return true; }
|
||||
|
||||
void Application::OnDestroy() {}
|
||||
|
||||
bool Application::OnUpdate(float _dt, float _time) {
|
||||
(void)_dt;
|
||||
(void)_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::OnGui(ImGui* _im_gui) {
|
||||
(void)_im_gui;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::OnFloatingGui(ImGui* _im_gui) {
|
||||
(void)_im_gui;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::OnDisplay(Renderer* _renderer) {
|
||||
(void)_renderer;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::GetCameraInitialSetup(math::Float3* _center,
|
||||
math::Float2* _angles,
|
||||
float* _distance) const {
|
||||
@@ -674,6 +705,23 @@ bool Application::GetCameraOverride(math::Float4x4* _transform) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::GetSceneBounds(math::Box* _bound) const { (void)_bound; }
|
||||
|
||||
math::Float2 Application::WorldToScreen(const math::Float3& _world) const {
|
||||
const math::SimdFloat4 ndc =
|
||||
(camera_->projection() * camera_->view()) *
|
||||
math::simd_float4::Load(_world.x, _world.y, _world.z, 1.f);
|
||||
|
||||
const math::SimdFloat4 resolution = math::simd_float4::FromInt(
|
||||
math::simd_int4::Load(resolution_.width, resolution_.height, 0, 0));
|
||||
const ozz::math::SimdFloat4 screen =
|
||||
resolution * ((ndc / math::SplatW(ndc)) + math::simd_float4::one()) /
|
||||
math::simd_float4::Load1(2.f);
|
||||
math::Float2 ret;
|
||||
math::Store2PtrU(screen, &ret.x);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Application::ResizeCbk(int _width, int _height) {
|
||||
// Stores new resolution settings.
|
||||
application_->resolution_.width = _width;
|
||||
|
||||
+18
-6
@@ -29,6 +29,7 @@
|
||||
#define OZZ_SAMPLES_FRAMEWORK_APPLICATION_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "ozz/base/containers/string.h"
|
||||
#include "ozz/base/memory/unique_ptr.h"
|
||||
|
||||
@@ -78,36 +79,47 @@ class Application {
|
||||
int Run(int _argc, const char** _argv, const char* _version,
|
||||
const char* _title);
|
||||
|
||||
protected:
|
||||
// Allows application to convert from world space to screen coordinates.
|
||||
math::Float2 WorldToScreen(const math::Float3& _world) const;
|
||||
|
||||
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;
|
||||
virtual bool OnInitialize();
|
||||
|
||||
// Provides de-initialization event to the inheriting application.
|
||||
// OnDestroy is called even if OnInitialize failed and returned an error.
|
||||
virtual void OnDestroy() = 0;
|
||||
virtual void OnDestroy();
|
||||
|
||||
// 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;
|
||||
virtual bool OnUpdate(float _dt, float _time);
|
||||
|
||||
// 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;
|
||||
virtual bool OnGui(ImGui* _im_gui);
|
||||
|
||||
// Provides immediate mode floating gui display event to the inheriting
|
||||
// application. Floating gui allows to render a Gui anywere one screen. User
|
||||
// must provide the form. OnFloatingGui 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 OnFloatingGui(ImGui* _im_gui);
|
||||
|
||||
// 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;
|
||||
virtual bool OnDisplay(Renderer* _renderer);
|
||||
|
||||
// Initial camera values. These will only be considered if function returns
|
||||
// true;
|
||||
@@ -125,7 +137,7 @@ class Application {
|
||||
// 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;
|
||||
virtual void GetSceneBounds(math::Box* _bound) const;
|
||||
|
||||
// Implements framework internal loop function.
|
||||
bool Loop();
|
||||
|
||||
+14
-6
@@ -28,12 +28,14 @@
|
||||
#ifndef OZZ_SAMPLES_FRAMEWORK_IMGUI_H_
|
||||
#define OZZ_SAMPLES_FRAMEWORK_IMGUI_H_
|
||||
|
||||
#include <ozz/base/containers/array.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace ozz {
|
||||
namespace math {
|
||||
struct RectFloat;
|
||||
}
|
||||
} // namespace math
|
||||
namespace sample {
|
||||
|
||||
// Interface for immediate mode graphical user interface rendering.
|
||||
@@ -54,8 +56,9 @@ class ImGui {
|
||||
// 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.
|
||||
// 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,
|
||||
@@ -115,6 +118,11 @@ class ImGui {
|
||||
float* _value, float _pow = 1.f,
|
||||
bool _enabled = true) = 0;
|
||||
|
||||
virtual bool DoSlider2D(const char* _label, ozz::array<float, 2> _min,
|
||||
ozz::array<float, 2> _max,
|
||||
ozz::array<float, 2>* _value,
|
||||
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
|
||||
@@ -172,9 +180,9 @@ class ImGui {
|
||||
// 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.
|
||||
// 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;
|
||||
|
||||
|
||||
+144
-23
@@ -34,11 +34,11 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "immediate.h"
|
||||
#include "ozz/base/maths/math_constant.h"
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
|
||||
#include "immediate.h"
|
||||
#include "renderer_impl.h"
|
||||
|
||||
namespace ozz {
|
||||
@@ -211,6 +211,11 @@ bool ImGuiImpl::AddWidget(float _height, math::RectFloat* _rect) {
|
||||
// Get current container.
|
||||
Container& container = containers_.back();
|
||||
|
||||
// Make it a square if height is negative.
|
||||
if (_height < 0) {
|
||||
_height = container.rect.width - kWidgetMarginX * 2.f;
|
||||
}
|
||||
|
||||
// Early out if outside of the container.
|
||||
// But don't modify current container's state.
|
||||
if (container.offset_y < kWidgetMarginY + _height) {
|
||||
@@ -740,6 +745,33 @@ void ImGuiImpl::DoGraph(const char* _label, float _min, float _max, float _mean,
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
float SliderControl(float _value, float _min, float _max, float _pow,
|
||||
int _mouse, int _mouse_max, bool _enabled, bool _active,
|
||||
float* _cursor) {
|
||||
const float pow_min = powf(_min, _pow);
|
||||
const float pow_max = powf(_max, _pow);
|
||||
const float clamped_value = ozz::math::Clamp(_min, _value, _max);
|
||||
float pow_value = powf(clamped_value, _pow);
|
||||
|
||||
// Finds cursor position and rect.
|
||||
*_cursor = floorf((_mouse_max * (pow_value - pow_min)) / (pow_max - pow_min));
|
||||
|
||||
if (_enabled) {
|
||||
if (_active) {
|
||||
_mouse = ozz::math::Clamp(0, _mouse, _mouse_max);
|
||||
pow_value = (_mouse * (pow_max - pow_min)) / _mouse_max + pow_min;
|
||||
return ozz::math::Clamp(_min, powf(pow_value, 1.f / _pow), _max);
|
||||
} else {
|
||||
// Clamping is only applied if the widget is enabled.
|
||||
return clamped_value;
|
||||
}
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ImGuiImpl::DoSlider(const char* _label, float _min, float _max,
|
||||
float* _value, float _pow, bool _enabled) {
|
||||
math::RectFloat rect;
|
||||
@@ -751,7 +783,6 @@ bool ImGuiImpl::DoSlider(const char* _label, float _min, float _max,
|
||||
|
||||
// Calculate mouse cursor's relative y offset.
|
||||
const float initial_value = *_value;
|
||||
const float clamped_value = ozz::math::Clamp(_min, initial_value, _max);
|
||||
|
||||
// Check for hotness.
|
||||
bool hot = false, active = false;
|
||||
@@ -795,24 +826,100 @@ bool ImGuiImpl::DoSlider(const char* _label, float _min, float _max,
|
||||
}
|
||||
|
||||
// Update widget value.
|
||||
const float pow_min = powf(_min, _pow);
|
||||
const float pow_max = powf(_max, _pow);
|
||||
float pow_value = powf(clamped_value, _pow);
|
||||
float cursor;
|
||||
*_value =
|
||||
SliderControl(initial_value, _min, _max, _pow,
|
||||
inputs_.mouse_x - static_cast<int>(rect.left),
|
||||
static_cast<int>(rect.width), _enabled, active, &cursor);
|
||||
|
||||
// Renders slider's rail.
|
||||
const math::RectFloat rail_rect(rect.left, rect.bottom, rect.width,
|
||||
rect.height);
|
||||
FillRect(rail_rect, kSliderRoundRectRadius, background_color);
|
||||
StrokeRect(rail_rect, kSliderRoundRectRadius, border_color);
|
||||
|
||||
const math::RectFloat cursor_rect(
|
||||
rect.left + cursor - kWidgetCursorWidth / 2.f, rect.bottom - 1.f,
|
||||
kWidgetCursorWidth, rect.height + 2.f);
|
||||
FillRect(cursor_rect, kSliderRoundRectRadius, slider_color);
|
||||
StrokeRect(cursor_rect, kSliderRoundRectRadius, slider_border_color);
|
||||
|
||||
const math::RectFloat text_rect(
|
||||
rail_rect.left + kSliderRoundRectRadius, rail_rect.bottom,
|
||||
rail_rect.width - kSliderRoundRectRadius * 2.f, rail_rect.height);
|
||||
Print(_label, text_rect, kMiddle, text_color);
|
||||
|
||||
// Returns true if the value has changed or if it was clamped in _min / _max
|
||||
// bounds.
|
||||
return initial_value != *_value;
|
||||
}
|
||||
|
||||
bool ImGuiImpl::DoSlider2D(const char* _label, ozz::array<float, 2> _min,
|
||||
ozz::array<float, 2> _max,
|
||||
ozz::array<float, 2>* _value, bool _enabled) {
|
||||
math::RectFloat rect;
|
||||
if (!AddWidget(-1.f, &rect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto_gen_id_++;
|
||||
|
||||
// Calculate mouse cursor's relative y offset.
|
||||
const ozz::array<float, 2> initial_value = *_value;
|
||||
|
||||
// Check for hotness.
|
||||
bool hot = false, active = false;
|
||||
if (_enabled) {
|
||||
// Includes the cursor size in the pick region.
|
||||
math::RectFloat pick_rect = rect;
|
||||
pick_rect.left -= kWidgetHeight / 2.f;
|
||||
pick_rect.width += kWidgetHeight;
|
||||
pick_rect.bottom -= kWidgetHeight / 2.f;
|
||||
pick_rect.height += kWidgetHeight;
|
||||
ButtonLogic(pick_rect, auto_gen_id_, &hot, &active);
|
||||
|
||||
// A slider is active on lmb pressed, not released. It's different to the
|
||||
// usual button behavior.
|
||||
active &= inputs_.lmb_pressed;
|
||||
}
|
||||
|
||||
// Render the scrollbar
|
||||
const GLubyte* background_color = kSliderBackgroundColor;
|
||||
const GLubyte* border_color = kWidgetBorderColor;
|
||||
const GLubyte* slider_color = kSliderCursorColor;
|
||||
const GLubyte* slider_border_color = kWidgetBorderColor;
|
||||
const GLubyte* text_color = kWidgetTextColor;
|
||||
|
||||
if (!_enabled) {
|
||||
background_color = kWidgetDisabledBackgroundColor;
|
||||
border_color = kWidgetDisabledBorderColor;
|
||||
slider_color = kSliderDisabledCursorColor;
|
||||
slider_border_color = kWidgetDisabledBorderColor;
|
||||
text_color = kWidgetDisabledTextColor;
|
||||
} else if (hot) {
|
||||
if (active) {
|
||||
int mousepos = inputs_.mouse_x - static_cast<int>(rect.left);
|
||||
if (mousepos < 0) {
|
||||
mousepos = 0;
|
||||
}
|
||||
if (mousepos > rect.width) {
|
||||
mousepos = static_cast<int>(rect.width);
|
||||
}
|
||||
pow_value = (mousepos * (pow_max - pow_min)) / rect.width + pow_min;
|
||||
*_value = ozz::math::Clamp(_min, powf(pow_value, 1.f / _pow), _max);
|
||||
// Button is both 'hot' and 'active'.
|
||||
slider_color = kWidgetActiveBackgroundColor;
|
||||
slider_border_color = kWidgetActiveBorderColor;
|
||||
} else {
|
||||
// Clamping is only applied if the widget is enabled.
|
||||
*_value = clamped_value;
|
||||
// Button is merely 'hot'.
|
||||
slider_color = kSliderCursorHotColor;
|
||||
slider_border_color = kWidgetHotBorderColor;
|
||||
}
|
||||
} else {
|
||||
// button is not hot, but it may be active. Use default colors.
|
||||
}
|
||||
|
||||
// Update widget value.
|
||||
ozz::array<float, 2> cursor;
|
||||
const int mouse[2] = {inputs_.mouse_x - static_cast<int>(rect.left),
|
||||
inputs_.mouse_y - static_cast<int>(rect.bottom)};
|
||||
const int mouse_max[2] = {static_cast<int>(rect.width),
|
||||
static_cast<int>(rect.height)};
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
_value->at(i) =
|
||||
SliderControl(initial_value[i], _min[i], _max[i], 1.f, mouse[i],
|
||||
mouse_max[i], _enabled, active, &cursor[i]);
|
||||
}
|
||||
|
||||
// Renders slider's rail.
|
||||
@@ -822,14 +929,24 @@ bool ImGuiImpl::DoSlider(const char* _label, float _min, float _max,
|
||||
StrokeRect(rail_rect, kSliderRoundRectRadius, border_color);
|
||||
|
||||
// Finds cursor position and rect.
|
||||
const float cursor =
|
||||
floorf((rect.width * (pow_value - pow_min)) / (pow_max - pow_min));
|
||||
const math::RectFloat cursor_rect(
|
||||
rect.left + cursor - kWidgetCursorWidth / 2.f, rect.bottom - 1.f,
|
||||
kWidgetCursorWidth, rect.height + 2.f);
|
||||
rect.left + cursor[0] - kWidgetHeight / 2.f,
|
||||
rect.bottom + cursor[1] - kWidgetHeight / 2.f, kWidgetHeight,
|
||||
kWidgetHeight);
|
||||
|
||||
// Cursor cross
|
||||
const math::RectFloat cross_hrect(rect.left, rect.bottom + cursor[1],
|
||||
rect.width, 1);
|
||||
StrokeRect(cross_hrect, 0.f, slider_color);
|
||||
const math::RectFloat cross_vrect(rect.left + cursor[0], rect.bottom, 1,
|
||||
rect.height);
|
||||
StrokeRect(cross_vrect, 0.f, slider_color);
|
||||
|
||||
// Draw slider
|
||||
FillRect(cursor_rect, kSliderRoundRectRadius, slider_color);
|
||||
StrokeRect(cursor_rect, kSliderRoundRectRadius, slider_border_color);
|
||||
|
||||
// Text
|
||||
const math::RectFloat text_rect(
|
||||
rail_rect.left + kSliderRoundRectRadius, rail_rect.bottom,
|
||||
rail_rect.width - kSliderRoundRectRadius * 2.f, rail_rect.height);
|
||||
@@ -1361,7 +1478,9 @@ float ImGuiImpl::Print(const char* _text, const math::RectFloat& _rect,
|
||||
ly = _rect.bottom + (line_count - 1) * (font_.glyph_height + interlign);
|
||||
break;
|
||||
}
|
||||
default: { break; }
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GL(BindTexture(GL_TEXTURE_2D, glyph_texture_));
|
||||
@@ -1385,7 +1504,9 @@ float ImGuiImpl::Print(const char* _text, const math::RectFloat& _rect,
|
||||
lx = _rect.right() - (line_char_count * font_.glyph_width);
|
||||
break;
|
||||
}
|
||||
default: { break; }
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through all characters of the current line, and renders them using
|
||||
|
||||
@@ -36,10 +36,8 @@
|
||||
// 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 {
|
||||
@@ -90,6 +88,10 @@ class ImGuiImpl : public ImGui {
|
||||
virtual bool DoSlider(const char* _label, float _min, float _max,
|
||||
float* _value, float _pow, bool _enabled);
|
||||
|
||||
virtual bool DoSlider2D(const char* _label, ozz::array<float, 2> _min,
|
||||
ozz::array<float, 2> _max,
|
||||
ozz::array<float, 2>* _value, bool _enabled);
|
||||
|
||||
virtual bool DoSlider(const char* _label, int _min, int _max, int* _value,
|
||||
float _pow, bool _enabled);
|
||||
|
||||
|
||||
+214
-91
@@ -132,6 +132,12 @@ bool RendererImpl::Initialize() {
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate instanced ambient rendering shader.
|
||||
points_shader = PointsShader::Build();
|
||||
if (!points_shader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -239,7 +245,7 @@ bool RendererImpl::DrawGrid(int _cell_count, float _cell_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Computes the model space bind pose and renders it.
|
||||
// Computes the model space rest pose and renders it.
|
||||
bool RendererImpl::DrawSkeleton(const ozz::animation::Skeleton& _skeleton,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints) {
|
||||
@@ -253,9 +259,9 @@ bool RendererImpl::DrawSkeleton(const ozz::animation::Skeleton& _skeleton,
|
||||
// Reallocate matrix array if necessary.
|
||||
prealloc_models_.resize(num_joints);
|
||||
|
||||
// Compute model space bind pose.
|
||||
// Compute model space rest pose.
|
||||
ozz::animation::LocalToModelJob job;
|
||||
job.input = _skeleton.joint_bind_poses();
|
||||
job.input = _skeleton.joint_rest_poses();
|
||||
job.output = make_span(prealloc_models_);
|
||||
job.skeleton = &_skeleton;
|
||||
if (!job.Run()) {
|
||||
@@ -460,10 +466,7 @@ int DrawPosture_FillUniforms(const ozz::animation::Skeleton& _skeleton,
|
||||
// Copy parent joint's raw matrix, to render a bone between the parent
|
||||
// and current matrix.
|
||||
float* uniform = _uniforms + instances * 16;
|
||||
math::StorePtr(parent.cols[0], uniform + 0);
|
||||
math::StorePtr(parent.cols[1], uniform + 4);
|
||||
math::StorePtr(parent.cols[2], uniform + 8);
|
||||
math::StorePtr(parent.cols[3], uniform + 12);
|
||||
std::memcpy(uniform, parent.cols, 16 * sizeof(float));
|
||||
|
||||
// Set bone direction (bone_dir). The shader expects to find it at index
|
||||
// [3,7,11] of the matrix.
|
||||
@@ -483,11 +486,7 @@ int DrawPosture_FillUniforms(const ozz::animation::Skeleton& _skeleton,
|
||||
// Only the joint is rendered for leaves, the bone model isn't.
|
||||
if (IsLeaf(_skeleton, i)) {
|
||||
// Copy current joint's raw matrix.
|
||||
uniform = _uniforms + instances * 16;
|
||||
math::StorePtr(current.cols[0], uniform + 0);
|
||||
math::StorePtr(current.cols[1], uniform + 4);
|
||||
math::StorePtr(current.cols[2], uniform + 8);
|
||||
math::StorePtr(current.cols[3], uniform + 12);
|
||||
std::memcpy(uniform, current.cols, 16 * sizeof(float));
|
||||
|
||||
// Re-use bone_dir to fix the size of the leaf (same as previous bone).
|
||||
// The shader expects to find it at index [3,7,11] of the matrix.
|
||||
@@ -621,6 +620,84 @@ bool RendererImpl::DrawPosture(const ozz::animation::Skeleton& _skeleton,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererImpl::DrawPoints(const ozz::span<const float>& _positions,
|
||||
const ozz::span<const float>& _sizes,
|
||||
const ozz::span<const Color>& _colors,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _round, bool _screen_space) {
|
||||
// Early out if no instance to render.
|
||||
if (_positions.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sizes and colors must be of size 1 or equal to _positions' size.
|
||||
if (_sizes.size() != 1 && _sizes.size() != _positions.size() / 3) {
|
||||
return false;
|
||||
}
|
||||
if (_colors.size() != 1 && _colors.size() != _positions.size() / 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const GLsizei positions_size = static_cast<GLsizei>(_positions.size_bytes());
|
||||
const GLsizei colors_size =
|
||||
static_cast<GLsizei>(_colors.size() == 1 ? 0 : _colors.size_bytes());
|
||||
const GLsizei sizes_size =
|
||||
static_cast<GLsizei>(_sizes.size() == 1 ? 0 : _sizes.size_bytes());
|
||||
const GLsizei buffer_size = positions_size + colors_size + sizes_size;
|
||||
const GLsizei positions_offset = 0;
|
||||
const GLsizei colors_offset = positions_offset + positions_size;
|
||||
const GLsizei sizes_offset = colors_offset + colors_size;
|
||||
|
||||
// Reallocate vertex buffer.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_));
|
||||
GL(BufferData(GL_ARRAY_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW));
|
||||
|
||||
GL(BufferSubData(GL_ARRAY_BUFFER, positions_offset, positions_size,
|
||||
_positions.data()));
|
||||
GL(BufferSubData(GL_ARRAY_BUFFER, colors_offset, colors_size,
|
||||
_colors.data()));
|
||||
GL(BufferSubData(GL_ARRAY_BUFFER, sizes_offset, sizes_size, _sizes.data()));
|
||||
|
||||
// Square or round sprites. Beware msaa makes sprites round if GL_POINT_SPRITE
|
||||
// isn't enabled
|
||||
if (_round) {
|
||||
GL(Enable(GL_POINT_SMOOTH));
|
||||
GL(Disable(GL_POINT_SPRITE));
|
||||
} else {
|
||||
GL(Disable(GL_POINT_SMOOTH));
|
||||
GL(Enable(GL_POINT_SPRITE));
|
||||
}
|
||||
|
||||
// Size is managed in vertex shader side.
|
||||
GL(Enable(GL_PROGRAM_POINT_SIZE));
|
||||
|
||||
const PointsShader::GenericAttrib attrib =
|
||||
points_shader->Bind(_transform, camera()->view_proj(), 12,
|
||||
positions_offset, colors_size ? 4 : 0, colors_offset,
|
||||
sizes_size ? 4 : 0, sizes_offset, _screen_space);
|
||||
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
// Apply remaining general attributes
|
||||
if (_colors.size() <= 1) {
|
||||
const Color color = _colors.empty() ? kWhite : _colors[0];
|
||||
GL(VertexAttrib4f(attrib.color, color.r / 255.f, color.g / 255.f,
|
||||
color.b / 255.f, color.a / 255.f));
|
||||
}
|
||||
if (_sizes.size() <= 1) {
|
||||
const float size = _sizes.empty() ? 1.f : _sizes[0];
|
||||
GL(VertexAttrib1f(attrib.size, size));
|
||||
}
|
||||
|
||||
// Draws the mesh.
|
||||
GL(DrawArrays(GL_POINTS, 0, static_cast<GLsizei>(_positions.size() / 3)));
|
||||
|
||||
// Unbinds.
|
||||
points_shader->Unbind();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererImpl::DrawBoxIm(const ozz::math::Box& _box,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _colors[2]) {
|
||||
@@ -1218,49 +1295,70 @@ bool RendererImpl::DrawMesh(const Mesh& _mesh,
|
||||
vertex_offset += part_vertex_count;
|
||||
}
|
||||
|
||||
// Binds shader with this array buffer, depending on rendering options.
|
||||
Shader* shader = nullptr;
|
||||
if (_options.texture) {
|
||||
ambient_textured_shader->Bind(_transform, camera()->view_proj(),
|
||||
positions_stride, positions_offset,
|
||||
normals_stride, normals_offset, colors_stride,
|
||||
colors_offset, uvs_stride, uvs_offset);
|
||||
shader = ambient_textured_shader.get();
|
||||
if (_options.triangles) {
|
||||
// Binds shader with this array buffer, depending on rendering options.
|
||||
Shader* shader = nullptr;
|
||||
if (_options.texture) {
|
||||
ambient_textured_shader->Bind(
|
||||
_transform, camera()->view_proj(), positions_stride, positions_offset,
|
||||
normals_stride, normals_offset, colors_stride, colors_offset,
|
||||
uvs_stride, uvs_offset);
|
||||
shader = ambient_textured_shader.get();
|
||||
|
||||
// Binds default texture
|
||||
GL(BindTexture(GL_TEXTURE_2D, checkered_texture_));
|
||||
} else {
|
||||
ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride,
|
||||
positions_offset, normals_stride, normals_offset,
|
||||
colors_stride, colors_offset);
|
||||
shader = ambient_shader.get();
|
||||
// Binds default texture
|
||||
GL(BindTexture(GL_TEXTURE_2D, checkered_texture_));
|
||||
} else {
|
||||
ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride,
|
||||
positions_offset, normals_stride, normals_offset,
|
||||
colors_stride, colors_offset);
|
||||
shader = ambient_shader.get();
|
||||
}
|
||||
|
||||
// Maps the index dynamic buffer and update it.
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_));
|
||||
const Mesh::TriangleIndices& indices = _mesh.triangle_indices;
|
||||
GL(BufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
indices.size() * sizeof(Mesh::TriangleIndices::value_type),
|
||||
array_begin(indices), GL_STREAM_DRAW));
|
||||
|
||||
// Draws the mesh.
|
||||
static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2,
|
||||
"Expects 2 bytes indices.");
|
||||
GL(DrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()),
|
||||
GL_UNSIGNED_SHORT, 0));
|
||||
|
||||
// Unbinds.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
GL(BindTexture(GL_TEXTURE_2D, 0));
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
shader->Unbind();
|
||||
}
|
||||
|
||||
// Maps the index dynamic buffer and update it.
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_));
|
||||
const Mesh::TriangleIndices& indices = _mesh.triangle_indices;
|
||||
GL(BufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
indices.size() * sizeof(Mesh::TriangleIndices::value_type),
|
||||
array_begin(indices), GL_STREAM_DRAW));
|
||||
|
||||
// Draws the mesh.
|
||||
static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2,
|
||||
"Expects 2 bytes indices.");
|
||||
GL(DrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()),
|
||||
GL_UNSIGNED_SHORT, 0));
|
||||
|
||||
// Unbinds.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
GL(BindTexture(GL_TEXTURE_2D, 0));
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
shader->Unbind();
|
||||
|
||||
if (_options.wireframe) {
|
||||
#ifndef EMSCRIPTEN
|
||||
GL(PolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||
#endif // EMSCRIPTEN
|
||||
}
|
||||
|
||||
// Renders debug vertices.
|
||||
if (_options.vertices) {
|
||||
for (size_t i = 0; i < _mesh.parts.size(); ++i) {
|
||||
const Mesh::Part& part = _mesh.parts[i];
|
||||
ozz::sample::Color color = ozz::sample::kWhite;
|
||||
span<const ozz::sample::Color> colors;
|
||||
if (_options.colors && part.colors.size() == part.positions.size() / 3) {
|
||||
colors = {
|
||||
reinterpret_cast<const ozz::sample::Color*>(part.colors.data()),
|
||||
part.positions.size() / 3};
|
||||
} else {
|
||||
colors = {&color, 1};
|
||||
}
|
||||
const float size = 2.f;
|
||||
DrawPoints({part.positions.data(), part.positions.size()}, {&size, 1},
|
||||
{&color, 1}, _transform, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Renders debug normals.
|
||||
if (_options.normals) {
|
||||
for (size_t i = 0; i < _mesh.parts.size(); ++i) {
|
||||
@@ -1313,7 +1411,7 @@ bool RendererImpl::DrawSkinnedMesh(
|
||||
const Mesh& _mesh, const span<math::Float4x4> _skinning_matrices,
|
||||
const ozz::math::Float4x4& _transform, const Options& _options) {
|
||||
// Forward to DrawMesh function is skinning is disabled.
|
||||
if (_options.skip_skinning) {
|
||||
if (_options.skip_skinning || !_mesh.skinned()) {
|
||||
return DrawMesh(_mesh, _transform, _options);
|
||||
}
|
||||
|
||||
@@ -1327,13 +1425,16 @@ bool RendererImpl::DrawSkinnedMesh(
|
||||
|
||||
// Positions and normals are interleaved to improve caching while executing
|
||||
// skinning job.
|
||||
|
||||
const GLsizei positions_offset = 0;
|
||||
const GLsizei normals_offset = sizeof(float) * 3;
|
||||
const GLsizei tangents_offset = sizeof(float) * 6;
|
||||
const GLsizei positions_stride = sizeof(float) * 9;
|
||||
const GLsizei normals_stride = positions_stride;
|
||||
const GLsizei tangents_stride = positions_stride;
|
||||
const GLsizei skinned_data_size = vertex_count * positions_stride;
|
||||
const GLsizei positions_stride = sizeof(float) * 3;
|
||||
const GLsizei normals_offset = vertex_count * positions_stride;
|
||||
const GLsizei normals_stride = sizeof(float) * 3;
|
||||
const GLsizei tangents_offset =
|
||||
normals_offset + vertex_count * normals_stride;
|
||||
const GLsizei tangents_stride = sizeof(float) * 3;
|
||||
const GLsizei skinned_data_size =
|
||||
tangents_offset + vertex_count * tangents_stride;
|
||||
|
||||
// Colors and uvs are contiguous. They aren't transformed, so they can be
|
||||
// directly copied from source mesh which is non-interleaved as-well.
|
||||
@@ -1542,54 +1643,76 @@ bool RendererImpl::DrawSkinnedMesh(
|
||||
processed_vertex_count += part_vertex_count;
|
||||
}
|
||||
|
||||
// Updates dynamic vertex buffer with skinned data.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_));
|
||||
GL(BufferData(GL_ARRAY_BUFFER, vbo_size, nullptr, GL_STREAM_DRAW));
|
||||
GL(BufferSubData(GL_ARRAY_BUFFER, 0, vbo_size, vbo_map));
|
||||
if (_options.triangles) {
|
||||
// Updates dynamic vertex buffer with skinned data.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_));
|
||||
GL(BufferData(GL_ARRAY_BUFFER, vbo_size, nullptr, GL_STREAM_DRAW));
|
||||
GL(BufferSubData(GL_ARRAY_BUFFER, 0, vbo_size, vbo_map));
|
||||
|
||||
// Binds shader with this array buffer, depending on rendering options.
|
||||
Shader* shader = nullptr;
|
||||
if (_options.texture) {
|
||||
ambient_textured_shader->Bind(_transform, camera()->view_proj(),
|
||||
positions_stride, positions_offset,
|
||||
normals_stride, normals_offset, colors_stride,
|
||||
colors_offset, uvs_stride, uvs_offset);
|
||||
shader = ambient_textured_shader.get();
|
||||
// Binds shader with this array buffer, depending on rendering options.
|
||||
Shader* shader = nullptr;
|
||||
if (_options.texture) {
|
||||
ambient_textured_shader->Bind(
|
||||
_transform, camera()->view_proj(), positions_stride, positions_offset,
|
||||
normals_stride, normals_offset, colors_stride, colors_offset,
|
||||
uvs_stride, uvs_offset);
|
||||
shader = ambient_textured_shader.get();
|
||||
|
||||
// Binds default texture
|
||||
GL(BindTexture(GL_TEXTURE_2D, checkered_texture_));
|
||||
} else {
|
||||
ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride,
|
||||
positions_offset, normals_stride, normals_offset,
|
||||
colors_stride, colors_offset);
|
||||
shader = ambient_shader.get();
|
||||
// Binds default texture
|
||||
GL(BindTexture(GL_TEXTURE_2D, checkered_texture_));
|
||||
} else {
|
||||
ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride,
|
||||
positions_offset, normals_stride, normals_offset,
|
||||
colors_stride, colors_offset);
|
||||
shader = ambient_shader.get();
|
||||
}
|
||||
|
||||
// Maps the index dynamic buffer and update it.
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_));
|
||||
const Mesh::TriangleIndices& indices = _mesh.triangle_indices;
|
||||
GL(BufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
indices.size() * sizeof(Mesh::TriangleIndices::value_type),
|
||||
array_begin(indices), GL_STREAM_DRAW));
|
||||
|
||||
// Draws the mesh.
|
||||
static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2,
|
||||
"Expects 2 bytes indices.");
|
||||
GL(DrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()),
|
||||
GL_UNSIGNED_SHORT, 0));
|
||||
|
||||
// Unbinds.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
GL(BindTexture(GL_TEXTURE_2D, 0));
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
shader->Unbind();
|
||||
}
|
||||
|
||||
// Maps the index dynamic buffer and update it.
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_));
|
||||
const Mesh::TriangleIndices& indices = _mesh.triangle_indices;
|
||||
GL(BufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
indices.size() * sizeof(Mesh::TriangleIndices::value_type),
|
||||
array_begin(indices), GL_STREAM_DRAW));
|
||||
|
||||
// Draws the mesh.
|
||||
static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2,
|
||||
"Expects 2 bytes indices.");
|
||||
GL(DrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()),
|
||||
GL_UNSIGNED_SHORT, 0));
|
||||
|
||||
// Unbinds.
|
||||
GL(BindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
GL(BindTexture(GL_TEXTURE_2D, 0));
|
||||
GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
shader->Unbind();
|
||||
|
||||
if (_options.wireframe) {
|
||||
#ifndef EMSCRIPTEN
|
||||
GL(PolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||
#endif // EMSCRIPTEN
|
||||
}
|
||||
|
||||
// Renders debug vertices.
|
||||
if (_options.vertices) {
|
||||
ozz::sample::Color color = ozz::sample::kWhite;
|
||||
span<const ozz::sample::Color> colors;
|
||||
if (_options.colors) {
|
||||
colors = {reinterpret_cast<const ozz::sample::Color*>(
|
||||
ozz::PointerStride(vbo_map, colors_offset)),
|
||||
static_cast<size_t>(vertex_count)};
|
||||
} else {
|
||||
colors = {&color, 1};
|
||||
}
|
||||
|
||||
const span<const float> vertices{
|
||||
reinterpret_cast<const float*>(
|
||||
ozz::PointerStride(vbo_map, positions_offset)),
|
||||
static_cast<size_t>(vertex_count * 3)};
|
||||
const float size = 2.f;
|
||||
DrawPoints(vertices, {&size, 1}, colors, _transform, true, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
// 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"
|
||||
@@ -91,6 +90,7 @@ namespace sample {
|
||||
namespace internal {
|
||||
class Camera;
|
||||
class Shader;
|
||||
class PointsShader;
|
||||
class SkeletonShader;
|
||||
class AmbientShader;
|
||||
class AmbientTexturedShader;
|
||||
@@ -119,6 +119,12 @@ class RendererImpl : public Renderer {
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints);
|
||||
|
||||
virtual bool DrawPoints(
|
||||
const ozz::span<const float>& _positions,
|
||||
const ozz::span<const float>& _sizes,
|
||||
const ozz::span<const Color>& _colors,
|
||||
const ozz::math::Float4x4& _transform, bool _round, bool _screen_space);
|
||||
|
||||
virtual bool DrawBoxIm(const ozz::math::Box& _box,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
const Color _colors[2]);
|
||||
@@ -241,6 +247,7 @@ class RendererImpl : public Renderer {
|
||||
ozz::unique_ptr<AmbientShader> ambient_shader;
|
||||
ozz::unique_ptr<AmbientTexturedShader> ambient_textured_shader;
|
||||
ozz::unique_ptr<AmbientShaderInstanced> ambient_shader_instanced;
|
||||
ozz::unique_ptr<PointsShader> points_shader;
|
||||
|
||||
// Checkered texture
|
||||
unsigned int checkered_texture_;
|
||||
|
||||
@@ -326,6 +326,91 @@ void ImmediatePTCShader::Bind(const math::Float4x4& _model,
|
||||
GL(Uniform1i(texture, 0));
|
||||
}
|
||||
|
||||
ozz::unique_ptr<PointsShader> PointsShader::Build() {
|
||||
bool success = true;
|
||||
|
||||
const char* kSimplePointsVS =
|
||||
"uniform mat4 u_mvp;\n"
|
||||
"attribute vec3 a_position;\n"
|
||||
"attribute vec4 a_color;\n"
|
||||
"attribute float a_size;\n"
|
||||
"attribute float a_screen_space;\n"
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" vec4 vertex = vec4(a_position.xyz, 1.);\n"
|
||||
" gl_Position = u_mvp * vertex;\n"
|
||||
" gl_PointSize = a_screen_space == 0. ? a_size / gl_Position.w : a_size;\n"
|
||||
" v_vertex_color = a_color;\n"
|
||||
"}\n";
|
||||
const char* kSimplePointsPS =
|
||||
"varying vec4 v_vertex_color;\n"
|
||||
"void main() {\n"
|
||||
" gl_FragColor = v_vertex_color;\n"
|
||||
"}\n";
|
||||
|
||||
const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePointsVS};
|
||||
const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePointsPS};
|
||||
|
||||
ozz::unique_ptr<PointsShader> shader = make_unique<PointsShader>();
|
||||
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");
|
||||
success &= shader->FindAttrib("a_size");
|
||||
success &= shader->FindAttrib("a_screen_space");
|
||||
|
||||
// Binds default uniforms
|
||||
success &= shader->BindUniform("u_mvp");
|
||||
|
||||
if (!success) {
|
||||
shader.reset();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
PointsShader::GenericAttrib PointsShader::Bind(
|
||||
const math::Float4x4& _model, const math::Float4x4& _view_proj,
|
||||
GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset, GLsizei _size_stride, GLsizei _size_offset,
|
||||
bool _screen_space) {
|
||||
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);
|
||||
if (_color_stride) {
|
||||
GL(EnableVertexAttribArray(color_attrib));
|
||||
GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
_color_stride, GL_PTR_OFFSET(_color_offset)));
|
||||
}
|
||||
const GLint size_attrib = attrib(2);
|
||||
if (_size_stride) {
|
||||
GL(EnableVertexAttribArray(size_attrib));
|
||||
GL(VertexAttribPointer(size_attrib, 1, GL_FLOAT, GL_TRUE, _size_stride,
|
||||
GL_PTR_OFFSET(_size_offset)));
|
||||
}
|
||||
const GLint screen_space_attrib = attrib(3);
|
||||
GL(VertexAttrib1f(screen_space_attrib, 1.f * _screen_space));
|
||||
|
||||
// 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));
|
||||
|
||||
return {_color_stride ? -1 : color_attrib, _size_stride ? -1 : size_attrib};
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char* kPassUv =
|
||||
"attribute vec2 a_uv;\n"
|
||||
|
||||
@@ -126,6 +126,29 @@ class ImmediatePTCShader : public Shader {
|
||||
GLsizei _tex_offset, GLsizei _color_stride, GLsizei _color_offset);
|
||||
};
|
||||
|
||||
class PointsShader : public Shader {
|
||||
public:
|
||||
PointsShader() {}
|
||||
virtual ~PointsShader() {}
|
||||
|
||||
// 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<PointsShader> Build();
|
||||
|
||||
// Binds the shader.
|
||||
struct GenericAttrib {
|
||||
GLint color;
|
||||
GLint size;
|
||||
};
|
||||
GenericAttrib Bind(const math::Float4x4& _model,
|
||||
const math::Float4x4& _view_proj, GLsizei _pos_stride,
|
||||
GLsizei _pos_offset, GLsizei _color_stride,
|
||||
GLsizei _color_offset, GLsizei _size_stride,
|
||||
GLsizei _size_offset, bool _screen_space);
|
||||
};
|
||||
|
||||
class SkeletonShader : public Shader {
|
||||
public:
|
||||
SkeletonShader() {}
|
||||
|
||||
+1
-6
@@ -69,12 +69,7 @@ struct Mesh {
|
||||
|
||||
// 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;
|
||||
return !inverse_bind_poses.empty();
|
||||
}
|
||||
|
||||
// Returns the number of joints used to skin the mesh.
|
||||
|
||||
+24
-6
@@ -79,7 +79,7 @@ class Renderer {
|
||||
// has a size of _cell_size.
|
||||
virtual bool DrawGrid(int _cell_count, float _cell_size) = 0;
|
||||
|
||||
// Renders a skeleton in its bind pose posture.
|
||||
// Renders a skeleton in its rest pose posture.
|
||||
virtual bool DrawSkeleton(const animation::Skeleton& _skeleton,
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints = true) = 0;
|
||||
@@ -93,6 +93,17 @@ class Renderer {
|
||||
const ozz::math::Float4x4& _transform,
|
||||
bool _draw_joints = true) = 0;
|
||||
|
||||
// Renders points.
|
||||
// _sizes and _colors must be either of ize 1 or equal to _positions' size.
|
||||
// If _screen_space is true, then points size is fixed in screen-space,
|
||||
// otherwise it changes with screen depth.
|
||||
virtual bool DrawPoints(
|
||||
const ozz::span<const float>& _positions,
|
||||
const ozz::span<const float>& _sizes,
|
||||
const ozz::span<const Color>& _colors,
|
||||
const ozz::math::Float4x4& _transform, bool _round = true,
|
||||
bool _screen_space = false) = 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.
|
||||
@@ -116,16 +127,20 @@ class Renderer {
|
||||
Color _color) = 0;
|
||||
|
||||
struct Options {
|
||||
bool triangles; // Show triangles mesh.
|
||||
bool texture; // Show texture (default checkered texture).
|
||||
bool vertices; // Show vertices as points.
|
||||
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 wireframe; // Show vertex colors.
|
||||
bool skip_skinning; // Show texture (default checkered texture).
|
||||
|
||||
Options()
|
||||
: texture(false),
|
||||
: triangles(true),
|
||||
texture(false),
|
||||
vertices(false),
|
||||
normals(false),
|
||||
tangents(false),
|
||||
binormals(false),
|
||||
@@ -133,9 +148,12 @@ class Renderer {
|
||||
wireframe(false),
|
||||
skip_skinning(false) {}
|
||||
|
||||
Options(bool _texture, bool _normals, bool _tangents, bool _binormals,
|
||||
bool _colors, bool _wireframe, bool _skip_skinning)
|
||||
: texture(_texture),
|
||||
Options(bool _triangles, bool _texture, bool _vertices, bool _normals,
|
||||
bool _tangents, bool _binormals, bool _colors, bool _wireframe,
|
||||
bool _skip_skinning)
|
||||
: triangles(_triangles),
|
||||
texture(_texture),
|
||||
vertices(_vertices),
|
||||
normals(_normals),
|
||||
tangents(_tangents),
|
||||
binormals(_binormals),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Adds fbx2mesh utility target.
|
||||
if(ozz_build_fbx)
|
||||
|
||||
# share meshes with thte sample framework
|
||||
# share meshes with the sample framework
|
||||
add_executable(sample_fbx2mesh
|
||||
fbx2mesh.cc
|
||||
${PROJECT_SOURCE_DIR}/samples/framework/mesh.cc
|
||||
@@ -9,6 +9,7 @@ if(ozz_build_fbx)
|
||||
target_link_libraries(sample_fbx2mesh
|
||||
ozz_animation_fbx
|
||||
ozz_options)
|
||||
target_copy_shared_libraries(sample_fbx2mesh)
|
||||
set_target_properties(sample_fbx2mesh
|
||||
PROPERTIES FOLDER "samples/tools")
|
||||
|
||||
|
||||
+19
-20
@@ -25,12 +25,12 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "framework/mesh.h"
|
||||
|
||||
#include "ozz/animation/offline/fbx/fbx.h"
|
||||
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
#include "ozz/base/containers/map.h"
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
@@ -39,14 +39,8 @@
|
||||
#include "ozz/base/maths/math_ex.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "fbxsdk/utils/fbxgeometryconverter.h"
|
||||
|
||||
// Declares command line options.
|
||||
OZZ_OPTIONS_DECLARE_STRING(file, "Specifies input file.", "", true)
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
@@ -504,19 +498,18 @@ bool BuildSkin(FbxMesh* _fbx_mesh,
|
||||
const int* ctrl_point_indices = cluster->GetControlPointIndices();
|
||||
const double* ctrl_point_weights = cluster->GetControlPointWeights();
|
||||
for (int cpi = 0; cpi < ctrl_point_index_count; ++cpi) {
|
||||
const SkinMapping mapping = {joint,
|
||||
static_cast<float>(ctrl_point_weights[cpi])};
|
||||
if (mapping.weight <= 0.f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ctrl_point = ctrl_point_indices[cpi];
|
||||
assert(ctrl_point < static_cast<int>(_remap.size()));
|
||||
// It happens that weight is 0. In this case give the vertex a very small
|
||||
// weight so normalization succeeds.
|
||||
const float ctrl_point_weight =
|
||||
static_cast<float>(ctrl_point_weights[cpi]);
|
||||
const SkinMapping mapping = {
|
||||
joint, ctrl_point_weight == 0.f ? 1e-9f : ctrl_point_weight};
|
||||
|
||||
// remap.size() can be 0, skinned control point might not be used by any
|
||||
// polygon of the mesh. Sometimes, the mesh can have less points than at
|
||||
// the time of the skinning because a smooth operator was active when
|
||||
// skinning but has been deactivated during export.
|
||||
const int ctrl_point = ctrl_point_indices[cpi];
|
||||
const ControlPointRemap& remap = _remap[ctrl_point];
|
||||
for (size_t v = 0; v < remap.size(); ++v) {
|
||||
vertex_skin_mappings[remap[v]].push_back(mapping);
|
||||
@@ -560,6 +553,11 @@ bool BuildSkin(FbxMesh* _fbx_mesh,
|
||||
|
||||
// Stores joint's indices and weights.
|
||||
size_t influence_count = inv.size();
|
||||
if (influence_count == 0) {
|
||||
vertex_skin_mappings[i].push_back({0,1.f});
|
||||
influence_count = 1;
|
||||
}
|
||||
|
||||
if (influence_count > 0) {
|
||||
size_t j = 0;
|
||||
for (; j < influence_count; ++j) {
|
||||
@@ -579,11 +577,12 @@ bool BuildSkin(FbxMesh* _fbx_mesh,
|
||||
}
|
||||
|
||||
if (vertex_isnt_influenced) {
|
||||
ozz::log::Err() << "At least one vertex isn't influenced by any joints."
|
||||
<< std::endl;
|
||||
ozz::log::LogV() << "At least one vertex isn't influenced by any joints. "
|
||||
"It's been reassigned to root joint."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return !vertex_isnt_influenced;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Limits the number of joints influencing a vertex.
|
||||
|
||||
+11
-17
@@ -30,28 +30,22 @@
|
||||
#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 "framework/imgui.h"
|
||||
#include "framework/mesh.h"
|
||||
#include "ozz/animation/offline/raw_skeleton.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"
|
||||
#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/geometry/runtime/skinning_job.h"
|
||||
|
||||
namespace ozz {
|
||||
namespace sample {
|
||||
@@ -224,9 +218,9 @@ void ComputeSkeletonBounds(const animation::Skeleton& _skeleton,
|
||||
// Allocate matrix array, out of memory is handled by the LocalToModelJob.
|
||||
ozz::vector<ozz::math::Float4x4> models(num_joints);
|
||||
|
||||
// Compute model space bind pose.
|
||||
// Compute model space rest pose.
|
||||
ozz::animation::LocalToModelJob job;
|
||||
job.input = _skeleton.joint_bind_poses();
|
||||
job.input = _skeleton.joint_rest_poses();
|
||||
job.output = make_span(models);
|
||||
job.skeleton = &_skeleton;
|
||||
if (job.Run()) {
|
||||
|
||||
@@ -25,6 +25,7 @@ add_executable(sample_look_at
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/mesh.ozz")
|
||||
target_link_libraries(sample_look_at
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_look_at)
|
||||
|
||||
set_target_properties(sample_look_at
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+18
-22
@@ -25,27 +25,23 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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"
|
||||
#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"
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
@@ -106,7 +102,7 @@ class LookAtSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -339,8 +335,8 @@ class LookAtSampleApplication : public ozz::sample::Application {
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Reading animation.
|
||||
if (!ozz::sample::LoadAnimation(OPTIONS_animation, &animation_)) {
|
||||
@@ -472,8 +468,8 @@ class LookAtSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
@@ -482,7 +478,7 @@ class LookAtSampleApplication : public ozz::sample::Application {
|
||||
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.
|
||||
// inverse rest pose with the model-space matrix.
|
||||
ozz::vector<ozz::math::Float4x4> skinning_matrices_;
|
||||
|
||||
// The mesh used by the sample.
|
||||
|
||||
+1
-1
@@ -7,10 +7,10 @@ add_custom_command(
|
||||
add_executable(sample_millipede
|
||||
sample_millipede.cc
|
||||
${CMAKE_CURRENT_BINARY_DIR}/README.md)
|
||||
|
||||
target_link_libraries(sample_millipede
|
||||
ozz_animation_offline
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_millipede)
|
||||
|
||||
set_target_properties(sample_millipede
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -24,5 +24,5 @@ Some other playback parameters can be tuned:
|
||||
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).
|
||||
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 (context 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.
|
||||
|
||||
+17
-20
@@ -30,25 +30,22 @@
|
||||
#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"
|
||||
#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/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/maths/quaternion.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
using ozz::animation::offline::RawAnimation;
|
||||
using ozz::animation::offline::RawSkeleton;
|
||||
@@ -126,7 +123,7 @@ class MillipedeSampleApplication : public ozz::sample::Application {
|
||||
// Samples animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = animation_.get();
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -208,8 +205,8 @@ class MillipedeSampleApplication : public ozz::sample::Application {
|
||||
locals_.resize(num_soa_joints);
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches new animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches new animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -423,8 +420,8 @@ class MillipedeSampleApplication : public ozz::sample::Application {
|
||||
// The millipede procedural walk animation.
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_;
|
||||
|
||||
// Sampling cache, as used by SamplingJob.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context, as used by SamplingJob.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
// These are shared between sampling output and local-to-model input.
|
||||
|
||||
@@ -28,10 +28,10 @@ add_executable(sample_multithread
|
||||
"${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})
|
||||
target_copy_shared_libraries(sample_multithread)
|
||||
|
||||
set_target_properties(sample_multithread
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -20,5 +20,5 @@ 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.
|
||||
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 context (ozz::animation::SamplingJob::Context). 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.
|
||||
@@ -30,28 +30,23 @@
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
|
||||
#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/containers/vector.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/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>
|
||||
@@ -118,7 +113,7 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &_animation;
|
||||
sampling_job.cache = &_character->cache;
|
||||
sampling_job.context = &_character->context;
|
||||
sampling_job.ratio = _character->controller.time_ratio();
|
||||
sampling_job.output = make_span(_character->locals);
|
||||
|
||||
@@ -150,7 +145,8 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
// 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 {
|
||||
class ParallelMonitor {
|
||||
public:
|
||||
ParallelMonitor() {
|
||||
// Finds the maximum possible number of tasks considering kMinGrain grain
|
||||
// size for kMaxCharacters characters.
|
||||
@@ -159,19 +155,39 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
processed *= 2, max_tasks *= 2) {
|
||||
}
|
||||
thread_ids_.resize(max_tasks);
|
||||
num_async_tasks.store(0);
|
||||
num_async_tasks_.store(0);
|
||||
}
|
||||
ozz::vector<std::thread::id> thread_ids_;
|
||||
std::atomic_uint num_async_tasks;
|
||||
|
||||
void Reset() { num_async_tasks_.store(0); }
|
||||
|
||||
// Stores this thread identifier to a new task slot.
|
||||
void PushTask() {
|
||||
thread_ids_[num_async_tasks_++] = hasher_(std::this_thread::get_id());
|
||||
}
|
||||
|
||||
// Finds number of threads, which is the number of unique thread ids
|
||||
// found.
|
||||
int ThreadCount() {
|
||||
std::sort(thread_ids_.begin(), thread_ids_.begin() + num_async_tasks_);
|
||||
auto end = std::unique(thread_ids_.begin(),
|
||||
thread_ids_.begin() + num_async_tasks_);
|
||||
|
||||
return static_cast<int>(end - thread_ids_.begin());
|
||||
}
|
||||
|
||||
int TaskCount() const { return num_async_tasks_; }
|
||||
|
||||
private:
|
||||
std::hash<std::thread::id> hasher_;
|
||||
ozz::vector<size_t> 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();
|
||||
_monitor->PushTask();
|
||||
|
||||
for (int i = 0; i < _num; ++i) {
|
||||
success &= UpdateCharacter(*_args.animation, *_args.skeleton, _args.dt,
|
||||
@@ -197,7 +213,7 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
bool success = true;
|
||||
if (enable_theading_) {
|
||||
// Initialize task counter. It's only used to monitor threading behavior.
|
||||
monitor_.num_async_tasks.store(0);
|
||||
monitor_.Reset();
|
||||
|
||||
const ParallelArgs args = {&animation_, &skeleton_, _dt, grain_size_};
|
||||
success = ParallelUpdate(args, array_begin(characters_), num_characters_,
|
||||
@@ -256,7 +272,7 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
|
||||
character.locals.resize(skeleton_.num_soa_joints());
|
||||
character.models.resize(skeleton_.num_joints());
|
||||
character.cache.Resize(animation_.num_tracks());
|
||||
character.context.Resize(animation_.num_tracks());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -290,18 +306,9 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
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());
|
||||
const int num_threads = monitor_.ThreadCount();
|
||||
std::sprintf(label, "Thread/task count: %d/%d", num_threads,
|
||||
monitor_.num_async_tasks.load());
|
||||
monitor_.TaskCount());
|
||||
_im_gui->DoLabel(label);
|
||||
}
|
||||
}
|
||||
@@ -334,8 +341,8 @@ class MultithreadSampleApplication : public ozz::sample::Application {
|
||||
// controlling animation playback time.
|
||||
ozz::sample::PlaybackController controller;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
|
||||
+1
-1
@@ -17,10 +17,10 @@ add_executable(sample_optimize
|
||||
"${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)
|
||||
target_copy_shared_libraries(sample_optimize)
|
||||
|
||||
set_target_properties(sample_optimize
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ Some other parameters can be tuned from sample UI to affect animation playback.
|
||||
|
||||
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).
|
||||
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 (context 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.
|
||||
|
||||
+39
-45
@@ -25,36 +25,30 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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 <algorithm>
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/profile.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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/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/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/base/memory/unique_ptr.h"
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
// Skeleton and animation file can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton, "Path to the runtime skeleton file.",
|
||||
@@ -111,7 +105,7 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
|
||||
// Prepares sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
|
||||
// Samples optimized animation (_according to the display mode).
|
||||
@@ -129,20 +123,20 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
}
|
||||
|
||||
// Computes difference between the optimized and non-optimized animations
|
||||
// in local space, and rebinds it to the bind pose.
|
||||
// in local space, and rebinds it to the rest 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::span<const ozz::math::SoaTransform>& rest_poses =
|
||||
skeleton_.joint_rest_poses();
|
||||
const ozz::math::SoaTransform* rest_pose = rest_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) {
|
||||
for (; rest_pose < rest_poses.end();
|
||||
++locals_raw, ++locals_rt, ++locals_diff, ++rest_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());
|
||||
rest_pose < rest_poses.end());
|
||||
|
||||
// Computes difference.
|
||||
const ozz::math::SoaTransform diff = {
|
||||
@@ -150,10 +144,10 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
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;
|
||||
// Rebinds to the rest pose in the diff buffer.
|
||||
locals_diff->translation = rest_pose->translation + diff.translation;
|
||||
locals_diff->rotation = rest_pose->rotation * diff.rotation;
|
||||
locals_diff->scale = rest_pose->scale * diff.scale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,8 +314,8 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
locals_diff_.resize(num_soa_joints);
|
||||
models_diff_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -374,11 +368,11 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
.5f, joint_setting_enable_ && optimize_);
|
||||
|
||||
if (rebuild) {
|
||||
// Invalidates the cache in case the new animation has the same
|
||||
// Invalidates the context 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();
|
||||
// are automatic handled by the context. See
|
||||
// SamplingJob::Context::Invalidate for more details.
|
||||
context_.Invalidate();
|
||||
|
||||
// Rebuilds a new runtime animation.
|
||||
if (!BuildAnimations()) {
|
||||
@@ -540,9 +534,9 @@ class OptimizeSampleApplication : public ozz::sample::Application {
|
||||
// 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_;
|
||||
// Sampling context, shared across optimized and non-optimized animations.
|
||||
// This is not optimal, but it's not an issue either.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Runtime optimized animation.
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_rt_;
|
||||
|
||||
@@ -23,6 +23,7 @@ add_executable(sample_partial_blend
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation_partial.ozz")
|
||||
target_link_libraries(sample_partial_blend
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_partial_blend)
|
||||
|
||||
set_target_properties(sample_partial_blend
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -24,6 +24,6 @@ The GUI also proposes to select the "root" joint of the upper body hierarchy, wh
|
||||
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.
|
||||
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 (rest-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.
|
||||
@@ -25,30 +25,25 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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/log.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"
|
||||
|
||||
#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).",
|
||||
@@ -86,7 +81,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
// Setup sampling job.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &sampler.animation;
|
||||
sampling_job.cache = &sampler.cache;
|
||||
sampling_job.context = &sampler.context;
|
||||
sampling_job.ratio = sampler.controller.time_ratio();
|
||||
sampling_job.output = make_span(sampler.locals);
|
||||
|
||||
@@ -115,7 +110,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
ozz::animation::BlendingJob blend_job;
|
||||
blend_job.threshold = threshold_;
|
||||
blend_job.layers = layers;
|
||||
blend_job.bind_pose = skeleton_.joint_bind_poses();
|
||||
blend_job.rest_pose = skeleton_.joint_rest_poses();
|
||||
blend_job.output = make_span(blended_locals_);
|
||||
|
||||
// Blends.
|
||||
@@ -171,8 +166,8 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
// this is a Soa structure.
|
||||
sampler.joint_weights.resize(num_soa_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
sampler.cache.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
sampler.context.Resize(num_joints);
|
||||
}
|
||||
|
||||
// Default weight settings.
|
||||
@@ -191,11 +186,9 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
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;
|
||||
}
|
||||
upper_body_root_ = FindJoint(skeleton_, "Spine1");
|
||||
if (upper_body_root_ < 0) {
|
||||
return false;
|
||||
}
|
||||
SetupPerJointWeights();
|
||||
|
||||
@@ -373,8 +366,8 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals;
|
||||
@@ -388,7 +381,7 @@ class PartialBlendSampleApplication : public ozz::sample::Application {
|
||||
// Index of the joint at the base of the upper body hierarchy.
|
||||
int upper_body_root_;
|
||||
|
||||
// Blending job bind pose threshold.
|
||||
// Blending job rest pose threshold.
|
||||
float threshold_;
|
||||
|
||||
// Buffer of local transforms which stores the blending result.
|
||||
|
||||
@@ -20,6 +20,7 @@ add_executable(sample_playback
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
target_link_libraries(sample_playback
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_playback)
|
||||
|
||||
set_target_properties(sample_playback
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ Some parameters can be tuned from sample UI:
|
||||
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.
|
||||
3. Allocates sampling context (ozz::animation::SamplingJob::Context) with the number of joints required for the animation. This context 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 context 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.
|
||||
@@ -62,7 +62,7 @@ class PlaybackSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -109,8 +109,8 @@ class PlaybackSampleApplication : public ozz::sample::Application {
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -144,8 +144,8 @@ class PlaybackSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
@@ -25,6 +25,7 @@ add_executable(sample_skinning
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/animation.ozz")
|
||||
target_link_libraries(sample_skinning
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_skinning)
|
||||
|
||||
set_target_properties(sample_skinning
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+31
-24
@@ -68,7 +68,7 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
// Samples optimized animation at t = animation_time_.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = controller_.time_ratio();
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -90,17 +90,18 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
// Samples animation, transforms to model space and renders.
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
const ozz::math::Float4x4 transform = ozz::math::Float4x4::identity();
|
||||
|
||||
if (draw_skeleton_) {
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_),
|
||||
ozz::math::Float4x4::identity());
|
||||
success &=
|
||||
_renderer->DrawPosture(skeleton_, make_span(models_), transform);
|
||||
}
|
||||
|
||||
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.
|
||||
// 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] =
|
||||
@@ -109,8 +110,7 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
|
||||
// Renders skin.
|
||||
success &= _renderer->DrawSkinnedMesh(
|
||||
mesh, make_span(skinning_matrices_),
|
||||
ozz::math::Float4x4::identity(), render_options_);
|
||||
mesh, make_span(skinning_matrices_), transform, render_options_);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
@@ -141,8 +141,8 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Reading skinned meshes.
|
||||
if (!ozz::sample::LoadMeshes(OPTIONS_mesh, &meshes_)) {
|
||||
@@ -152,8 +152,8 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
// 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.
|
||||
// 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 =
|
||||
@@ -224,19 +224,26 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
// 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) {
|
||||
static bool ocd_open = true;
|
||||
ozz::sample::ImGui::OpenClose ocd(_im_gui, "Display options", &ocd_open);
|
||||
if (ocd_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);
|
||||
static bool ocr_open = false;
|
||||
ozz::sample::ImGui::OpenClose ocr(_im_gui, "Rendering options",
|
||||
&ocr_open);
|
||||
if (ocr_open) {
|
||||
_im_gui->DoCheckBox("Show triangles", &render_options_.triangles);
|
||||
_im_gui->DoCheckBox("Show texture", &render_options_.texture);
|
||||
_im_gui->DoCheckBox("Show vertices", &render_options_.vertices);
|
||||
_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;
|
||||
@@ -257,8 +264,8 @@ class SkinningSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
@@ -17,6 +17,7 @@ target_link_libraries(sample_two_bone_ik
|
||||
sample_framework
|
||||
ozz_animation_offline
|
||||
ozz_animation)
|
||||
target_copy_shared_libraries(sample_two_bone_ik)
|
||||
|
||||
set_target_properties(sample_two_bone_ik
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ The sample exposes IKTwoBoneJob parameters:
|
||||
- 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.
|
||||
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 rest 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
|
||||
|
||||
|
||||
+404
-409
@@ -25,412 +25,407 @@
|
||||
// //
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
#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);
|
||||
}
|
||||
#include <algorithm>
|
||||
|
||||
#include "framework/application.h"
|
||||
#include "framework/imgui.h"
|
||||
#include "framework/renderer.h"
|
||||
#include "framework/utils.h"
|
||||
#include "ozz/animation/runtime/ik_two_bone_job.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/box.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include "ozz/base/maths/simd_quaternion.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/memory/allocator.h"
|
||||
#include "ozz/options/options.h"
|
||||
|
||||
// Skeleton archive can be specified as an option.
|
||||
OZZ_OPTIONS_DECLARE_STRING(skeleton,
|
||||
"Path to the skeleton (ozz archive format).",
|
||||
"media/skeleton.ozz", false)
|
||||
|
||||
class TwoBoneIKSampleApplication : public ozz::sample::Application {
|
||||
public:
|
||||
TwoBoneIKSampleApplication()
|
||||
: start_joint_(-1),
|
||||
mid_joint_(-1),
|
||||
end_joint_(-1),
|
||||
pole_vector(0.f, 1.f, 0.f),
|
||||
weight_(1.f),
|
||||
soften_(.97f),
|
||||
twist_angle_(0.f),
|
||||
reached_(false),
|
||||
fix_initial_transform_(true),
|
||||
two_bone_ik_(true),
|
||||
show_target_(true),
|
||||
show_joints_(false),
|
||||
show_pole_vector_(false),
|
||||
root_translation_(0.f, 0.f, 0.f),
|
||||
root_euler_(0.f, 0.f, 0.f),
|
||||
root_scale_(1.f),
|
||||
target_extent_(.5f),
|
||||
target_offset_(0.f, .2f, .1f),
|
||||
target_(0.f, 0.f, 0.f) {}
|
||||
|
||||
protected:
|
||||
bool ApplyTwoBoneIK() {
|
||||
// Target and pole should be in model-space, so they must be converted from
|
||||
// world-space using character inverse root matrix.
|
||||
// IK jobs must support non invertible matrices (like 0 scale matrices).
|
||||
ozz::math::SimdInt4 invertible;
|
||||
const ozz::math::Float4x4 invert_root =
|
||||
Invert(GetRootTransform(), &invertible);
|
||||
|
||||
const ozz::math::SimdFloat4 target_ms = TransformPoint(
|
||||
invert_root, ozz::math::simd_float4::Load3PtrU(&target_.x));
|
||||
const ozz::math::SimdFloat4 pole_vector_ms = TransformVector(
|
||||
invert_root, ozz::math::simd_float4::Load3PtrU(&pole_vector.x));
|
||||
|
||||
// Setup IK job.
|
||||
ozz::animation::IKTwoBoneJob ik_job;
|
||||
ik_job.target = target_ms;
|
||||
ik_job.pole_vector = pole_vector_ms;
|
||||
ik_job.mid_axis = ozz::math::simd_float4::z_axis(); // Middle joint
|
||||
// rotation axis is
|
||||
// fixed, and depends
|
||||
// on skeleton rig.
|
||||
ik_job.weight = weight_;
|
||||
ik_job.soften = soften_;
|
||||
ik_job.twist_angle = twist_angle_;
|
||||
|
||||
// Provides start, middle and end joints model space matrices.
|
||||
ik_job.start_joint = &models_[start_joint_];
|
||||
ik_job.mid_joint = &models_[mid_joint_];
|
||||
ik_job.end_joint = &models_[end_joint_];
|
||||
|
||||
// Setup output pointers.
|
||||
ozz::math::SimdQuaternion start_correction;
|
||||
ik_job.start_joint_correction = &start_correction;
|
||||
ozz::math::SimdQuaternion mid_correction;
|
||||
ik_job.mid_joint_correction = &mid_correction;
|
||||
ik_job.reached = &reached_;
|
||||
|
||||
if (!ik_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply IK quaternions to their respective local-space transforms.
|
||||
ozz::sample::MultiplySoATransformQuaternion(start_joint_, start_correction,
|
||||
make_span(locals_));
|
||||
ozz::sample::MultiplySoATransformQuaternion(mid_joint_, mid_correction,
|
||||
make_span(locals_));
|
||||
|
||||
// Updates model-space matrices now IK has been applied to local transforms.
|
||||
// All the ancestors of the start of the IK chain must be computed.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
ltm_job.from =
|
||||
start_joint_; // Local transforms haven't changed before start_joint_.
|
||||
ltm_job.to = ozz::animation::Skeleton::kMaxJoints;
|
||||
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnUpdate(float, float _time) {
|
||||
// Updates sample target position.
|
||||
if (!MoveTarget(_time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset locals to skeleton rest pose if option is true.
|
||||
// This allows to always start IK from a fix position (required to test
|
||||
// weighting), or do IK from the latest computed pose
|
||||
if (fix_initial_transform_) {
|
||||
for (size_t i = 0; i < locals_.size(); ++i) {
|
||||
locals_[i] = skeleton_.joint_rest_poses()[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Updates model-space matrices from current local-space setup.
|
||||
// Model-space matrices needs to be updated up to the end joint. Any joint
|
||||
// after that will need to be recomputed after IK indeed.
|
||||
ozz::animation::LocalToModelJob ltm_job;
|
||||
ltm_job.skeleton = &skeleton_;
|
||||
ltm_job.input = make_span(locals_);
|
||||
ltm_job.output = make_span(models_);
|
||||
if (!ltm_job.Run()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup and run IK job.
|
||||
if (two_bone_ik_ && !ApplyTwoBoneIK()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool OnDisplay(ozz::sample::Renderer* _renderer) {
|
||||
bool success = true;
|
||||
|
||||
// Get skeleton root transform.
|
||||
const ozz::math::Float4x4 root = GetRootTransform();
|
||||
|
||||
if (show_target_ && two_bone_ik_) {
|
||||
// Displays target
|
||||
const ozz::sample::Color colors[2][2] = {
|
||||
{ozz::sample::kRed, ozz::sample::kBlack},
|
||||
{ozz::sample::kGreen, ozz::sample::kBlack}};
|
||||
|
||||
const float kBoxHalfSize = .006f;
|
||||
const ozz::math::Box box(ozz::math::Float3(-kBoxHalfSize),
|
||||
ozz::math::Float3(kBoxHalfSize));
|
||||
success &= _renderer->DrawBoxIm(
|
||||
box,
|
||||
ozz::math::Float4x4::Translation(
|
||||
ozz::math::simd_float4::Load3PtrU(&target_.x)),
|
||||
colors[reached_]);
|
||||
}
|
||||
|
||||
// Displays pole vector
|
||||
if (show_pole_vector_) {
|
||||
ozz::math::Float3 begin;
|
||||
ozz::math::Store3PtrU(TransformPoint(root, models_[mid_joint_].cols[3]),
|
||||
&begin.x);
|
||||
success &= _renderer->DrawSegment(begin, begin + pole_vector,
|
||||
ozz::sample::kWhite,
|
||||
ozz::math::Float4x4::identity());
|
||||
}
|
||||
|
||||
// Showing joints
|
||||
if (show_joints_) {
|
||||
const float kAxeScale = .1f;
|
||||
const float kSphereRadius = .009f;
|
||||
const ozz::math::Float4x4 kAxesScale = ozz::math::Float4x4::Scaling(
|
||||
ozz::math::simd_float4::Load1(kAxeScale));
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const int joints[3] = {start_joint_, mid_joint_, end_joint_};
|
||||
const ozz::math::Float4x4& transform = root * models_[joints[i]];
|
||||
success &= _renderer->DrawAxes(transform * kAxesScale);
|
||||
success &= _renderer->DrawSphereIm(kSphereRadius, transform,
|
||||
ozz::sample::kWhite);
|
||||
}
|
||||
}
|
||||
|
||||
// Draws the animated skeleton posture.
|
||||
success &= _renderer->DrawPosture(skeleton_, make_span(models_), root);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool OnInitialize() {
|
||||
// Loads skeleton.
|
||||
if (!ozz::sample::LoadSkeleton(OPTIONS_skeleton, &skeleton_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates runtime buffers.
|
||||
const int num_soa_joints = skeleton_.num_soa_joints();
|
||||
locals_.resize(num_soa_joints);
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Find the 3 joints in skeleton hierarchy.
|
||||
start_joint_ = mid_joint_ = end_joint_ = -1;
|
||||
for (int i = 0; i < skeleton_.num_joints(); i++) {
|
||||
const char* joint_name = skeleton_.joint_names()[i];
|
||||
if (std::strcmp(joint_name, "shoulder") == 0) {
|
||||
start_joint_ = i;
|
||||
} else if (std::strcmp(joint_name, "forearm") == 0) {
|
||||
mid_joint_ = i;
|
||||
} else if (std::strcmp(joint_name, "wrist") == 0) {
|
||||
end_joint_ = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Fails if a joint is missing.
|
||||
if (start_joint_ < 0 || mid_joint_ < 0 || end_joint_ < 0) {
|
||||
ozz::log::Err() << "Failed to find required joints." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize locals from skeleton rest pose
|
||||
for (size_t i = 0; i < locals_.size(); ++i) {
|
||||
locals_[i] = skeleton_.joint_rest_poses()[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
virtual bool OnGui(ozz::sample::ImGui* _im_gui) {
|
||||
char 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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ add_executable(sample_user_channel
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/media/track.ozz")
|
||||
target_link_libraries(sample_user_channel
|
||||
sample_framework)
|
||||
target_copy_shared_libraries(sample_user_channel)
|
||||
|
||||
set_target_properties(sample_user_channel
|
||||
PROPERTIES FOLDER "samples")
|
||||
|
||||
@@ -212,7 +212,7 @@ class UserChannelSampleApplication : public ozz::sample::Application {
|
||||
// Samples animation at r = _ratio.
|
||||
ozz::animation::SamplingJob sampling_job;
|
||||
sampling_job.animation = &animation_;
|
||||
sampling_job.cache = &cache_;
|
||||
sampling_job.context = &context_;
|
||||
sampling_job.ratio = _ratio;
|
||||
sampling_job.output = make_span(locals_);
|
||||
if (!sampling_job.Run()) {
|
||||
@@ -277,8 +277,8 @@ class UserChannelSampleApplication : public ozz::sample::Application {
|
||||
const int num_joints = skeleton_.num_joints();
|
||||
models_.resize(num_joints);
|
||||
|
||||
// Allocates a cache that matches animation requirements.
|
||||
cache_.Resize(num_joints);
|
||||
// Allocates a context that matches animation requirements.
|
||||
context_.Resize(num_joints);
|
||||
|
||||
// Reading track.
|
||||
if (!ozz::sample::LoadTrack(OPTIONS_track, &track_)) {
|
||||
@@ -339,8 +339,8 @@ class UserChannelSampleApplication : public ozz::sample::Application {
|
||||
// Runtime animation.
|
||||
ozz::animation::Animation animation_;
|
||||
|
||||
// Sampling cache.
|
||||
ozz::animation::SamplingCache cache_;
|
||||
// Sampling context.
|
||||
ozz::animation::SamplingJob::Context context_;
|
||||
|
||||
// Buffer of local transforms as sampled from animation_.
|
||||
ozz::vector<ozz::math::SoaTransform> locals_;
|
||||
|
||||
Reference in New Issue
Block a user