animation system wip

This commit is contained in:
Mikulas Florek 2023-08-24 19:50:56 +02:00
parent d0923683cc
commit 3393efebdd
12 changed files with 1825 additions and 1504 deletions

View file

@ -30,6 +30,7 @@ struct Time {
Time operator-(const Time& rhs) const { return Time{value - rhs.value}; } Time operator-(const Time& rhs) const { return Time{value - rhs.value}; }
void operator+=(const Time& rhs) { value += rhs.value; } void operator+=(const Time& rhs) { value += rhs.value; }
bool operator<(const Time& rhs) const { return value < rhs.value; } bool operator<(const Time& rhs) const { return value < rhs.value; }
bool operator>(const Time& rhs) const { return value > rhs.value; }
bool operator<=(const Time& rhs) const { return value <= rhs.value; } bool operator<=(const Time& rhs) const { return value <= rhs.value; }
bool operator>=(const Time& rhs) const { return value >= rhs.value; } bool operator>=(const Time& rhs) const { return value >= rhs.value; }
Time operator%(const Time& rhs) const { return Time{value % rhs.value}; } Time operator%(const Time& rhs) const { return Time{value % rhs.value}; }
@ -43,9 +44,9 @@ private:
struct BoneMask struct BoneMask
{ {
BoneMask(IAllocator& allocator) : bones(allocator) {} BoneMask(IAllocator& allocator) : bones(allocator), name(allocator) {}
BoneMask(BoneMask&& rhs) = default; BoneMask(BoneMask&& rhs) = default;
StaticString<32> name; String name;
HashMap<BoneNameHash, u8> bones; HashMap<BoneNameHash, u8> bones;
}; };

View file

@ -28,8 +28,7 @@ struct Animation;
struct Engine; struct Engine;
struct World; struct World;
enum class AnimationModuleVersion enum class AnimationModuleVersion {
{
USE_ROOT_MOTION, USE_ROOT_MOTION,
LATEST LATEST
@ -130,13 +129,6 @@ struct AnimationModuleImpl final : AnimationModule {
} }
const OutputMemoryStream* getEventStream(EntityRef entity) const override {
auto iter = m_animator_map.find(entity);
const Animator& animator = m_animators[iter.value()];
return animator.ctx ? &animator.ctx->events : nullptr;
}
void setAnimatorIK(EntityRef entity, u32 index, float weight, const Vec3& target) override { void setAnimatorIK(EntityRef entity, u32 index, float weight, const Vec3& target) override {
auto iter = m_animator_map.find(entity); auto iter = m_animator_map.find(entity);
Animator& animator = m_animators[iter.value()]; Animator& animator = m_animators[iter.value()];
@ -161,7 +153,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!iter.isValid()) return; if (!iter.isValid()) return;
Animator& animator = m_animators[iter.value()]; Animator& animator = m_animators[iter.value()];
if (animator.resource->m_inputs[input_idx].type == anim::Controller::Input::FLOAT) { if (animator.resource->m_inputs[input_idx].type == anim::Value::FLOAT) {
animator.ctx->inputs[input_idx].f = value; animator.ctx->inputs[input_idx].f = value;
} }
else { else {
@ -175,8 +167,8 @@ struct AnimationModuleImpl final : AnimationModule {
if (!iter.isValid()) return; if (!iter.isValid()) return;
Animator& animator = m_animators[iter.value()]; Animator& animator = m_animators[iter.value()];
if (animator.resource->m_inputs[input_idx].type == anim::Controller::Input::I32) { if (animator.resource->m_inputs[input_idx].type == anim::Value::I32) {
animator.ctx->inputs[input_idx].i32 = value; animator.ctx->inputs[input_idx].s32 = value;
} }
else { else {
logWarning("Trying to set i32 to ", animator.resource->m_inputs[input_idx].name); logWarning("Trying to set i32 to ", animator.resource->m_inputs[input_idx].name);
@ -189,7 +181,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!iter.isValid()) return; if (!iter.isValid()) return;
Animator& animator = m_animators[iter.value()]; Animator& animator = m_animators[iter.value()];
if (animator.resource->m_inputs[input_idx].type == anim::Controller::Input::BOOL) { if (animator.resource->m_inputs[input_idx].type == anim::Value::BOOL) {
animator.ctx->inputs[input_idx].b = value; animator.ctx->inputs[input_idx].b = value;
} }
else { else {
@ -524,7 +516,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return; if (!animator.ctx) return;
if (input_idx >= (u32)animator.resource->m_inputs.size()) return; if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
if (animator.resource->m_inputs[input_idx].type != anim::Controller::Input::FLOAT) return; if (animator.resource->m_inputs[input_idx].type != anim::Value::FLOAT) return;
animator.ctx->inputs[input_idx].f = value; animator.ctx->inputs[input_idx].f = value;
} }
@ -534,7 +526,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return; if (!animator.ctx) return;
if (input_idx >= (u32)animator.resource->m_inputs.size()) return; if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
if (animator.resource->m_inputs[input_idx].type != anim::Controller::Input::BOOL) return; if (animator.resource->m_inputs[input_idx].type != anim::Value::BOOL) return;
animator.ctx->inputs[input_idx].b = value; animator.ctx->inputs[input_idx].b = value;
} }
@ -544,7 +536,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return 0; if (!animator.ctx) return 0;
ASSERT(input_idx < (u32)animator.resource->m_inputs.size()); ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Controller::Input::FLOAT); ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::FLOAT);
return animator.ctx->inputs[input_idx].f; return animator.ctx->inputs[input_idx].f;
} }
@ -554,7 +546,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return 0; if (!animator.ctx) return 0;
ASSERT(input_idx < (u32)animator.resource->m_inputs.size()); ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Controller::Input::BOOL); ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::BOOL);
return animator.ctx->inputs[input_idx].b; return animator.ctx->inputs[input_idx].b;
} }
@ -564,9 +556,9 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return 0; if (!animator.ctx) return 0;
ASSERT(input_idx < (u32)animator.resource->m_inputs.size()); ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Controller::Input::I32); ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::I32);
return animator.ctx->inputs[input_idx].i32; return animator.ctx->inputs[input_idx].s32;
} }
@ -575,9 +567,9 @@ struct AnimationModuleImpl final : AnimationModule {
if (!animator.ctx) return; if (!animator.ctx) return;
if (input_idx >= (u32)animator.resource->m_inputs.size()) return; if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
if (animator.resource->m_inputs[input_idx].type != anim::Controller::Input::I32) return; if (animator.resource->m_inputs[input_idx].type != anim::Value::I32) return;
animator.ctx->inputs[input_idx].i32 = value; animator.ctx->inputs[input_idx].s32 = value;
} }
LocalRigidTransform getAnimatorRootMotion(EntityRef entity) override LocalRigidTransform getAnimatorRootMotion(EntityRef entity) override
@ -631,11 +623,11 @@ struct AnimationModuleImpl final : AnimationModule {
animator.ctx->model = model; animator.ctx->model = model;
animator.ctx->time_delta = Time::fromSeconds(time_delta); animator.ctx->time_delta = Time::fromSeconds(time_delta);
animator.ctx->root_bone_hash = animator.resource->m_root_motion_bone.empty() ? BoneNameHash() : BoneNameHash(animator.resource->m_root_motion_bone); animator.ctx->root_bone_hash = animator.resource->m_root_motion_bone;
animator.resource->update(*animator.ctx, animator.root_motion); animator.resource->update(*animator.ctx, animator.root_motion);
model->getRelativePose(*pose); model->getRelativePose(*pose);
animator.resource->getPose(*animator.ctx, *pose); evalBlendStack(*animator.ctx, *pose);
for (Animator::IK& ik : animator.inverse_kinematics) { for (Animator::IK& ik : animator.inverse_kinematics) {
if (ik.weight == 0) break; if (ik.weight == 0) break;
@ -668,12 +660,15 @@ struct AnimationModuleImpl final : AnimationModule {
static void updateIK(anim::Controller::IK& res_ik, Animator::IK& ik, Pose& pose, Model& model) static void updateIK(anim::Controller::IK& res_ik, Animator::IK& ik, Pose& pose, Model& model)
{ {
u32 indices[anim::Controller::IK::MAX_BONES_COUNT]; enum { MAX_BONES_COUNT = 32 };
LocalRigidTransform transforms[anim::Controller::IK::MAX_BONES_COUNT]; u32 indices[MAX_BONES_COUNT];
Vec3 old_pos[anim::Controller::IK::MAX_BONES_COUNT]; LocalRigidTransform transforms[MAX_BONES_COUNT];
float len[anim::Controller::IK::MAX_BONES_COUNT - 1]; Vec3 old_pos[MAX_BONES_COUNT];
float len[MAX_BONES_COUNT - 1];
float len_sum = 0; float len_sum = 0;
for (int i = 0; i < res_ik.bones_count; ++i) { const i32 bones_count = res_ik.bones.size();
ASSERT(bones_count <= MAX_BONES_COUNT);
for (i32 i = 0; i < bones_count; ++i) {
auto iter = model.getBoneIndex(res_ik.bones[i]); auto iter = model.getBoneIndex(res_ik.bones[i]);
if (!iter.isValid()) return; if (!iter.isValid()) return;
@ -692,7 +687,7 @@ struct AnimationModuleImpl final : AnimationModule {
} }
LocalRigidTransform parent_tr = roots_parent; LocalRigidTransform parent_tr = roots_parent;
for (int i = 0; i < res_ik.bones_count; ++i) { for (i32 i = 0; i < bones_count; ++i) {
LocalRigidTransform tr{pose.positions[indices[i]], pose.rotations[indices[i]]}; LocalRigidTransform tr{pose.positions[indices[i]], pose.rotations[indices[i]]};
transforms[i] = parent_tr * tr; transforms[i] = parent_tr * tr;
old_pos[i] = transforms[i].pos; old_pos[i] = transforms[i].pos;
@ -710,22 +705,22 @@ struct AnimationModuleImpl final : AnimationModule {
target = transforms[0].pos + to_target * len_sum; target = transforms[0].pos + to_target * len_sum;
} }
for (int iteration = 0; iteration < res_ik.max_iterations; ++iteration) { for (u32 iteration = 0; iteration < res_ik.max_iterations; ++iteration) {
transforms[res_ik.bones_count - 1].pos = target; transforms[bones_count - 1].pos = target;
for (int i = res_ik.bones_count - 1; i > 1; --i) { for (i32 i = bones_count - 1; i > 1; --i) {
Vec3 dir = normalize((transforms[i - 1].pos - transforms[i].pos)); Vec3 dir = normalize((transforms[i - 1].pos - transforms[i].pos));
transforms[i - 1].pos = transforms[i].pos + dir * len[i - 1]; transforms[i - 1].pos = transforms[i].pos + dir * len[i - 1];
} }
for (int i = 1; i < res_ik.bones_count; ++i) { for (i32 i = 1; i < bones_count; ++i) {
Vec3 dir = normalize((transforms[i].pos - transforms[i - 1].pos)); Vec3 dir = normalize((transforms[i].pos - transforms[i - 1].pos));
transforms[i].pos = transforms[i - 1].pos + dir * len[i - 1]; transforms[i].pos = transforms[i - 1].pos + dir * len[i - 1];
} }
} }
// compute rotations from new positions // compute rotations from new positions
for (int i = res_ik.bones_count - 2; i >= 0; --i) { for (i32 i = bones_count - 2; i >= 0; --i) {
Vec3 old_d = old_pos[i + 1] - old_pos[i]; Vec3 old_d = old_pos[i + 1] - old_pos[i];
Vec3 new_d = transforms[i + 1].pos - transforms[i].pos; Vec3 new_d = transforms[i + 1].pos - transforms[i].pos;
@ -734,15 +729,15 @@ struct AnimationModuleImpl final : AnimationModule {
} }
// convert from object space to bone space // convert from object space to bone space
LocalRigidTransform ik_out[anim::Controller::IK::MAX_BONES_COUNT]; LocalRigidTransform ik_out[MAX_BONES_COUNT];
for (int i = res_ik.bones_count - 1; i > 0; --i) { for (i32 i = bones_count - 1; i > 0; --i) {
transforms[i] = transforms[i - 1].inverted() * transforms[i]; transforms[i] = transforms[i - 1].inverted() * transforms[i];
ik_out[i].pos = transforms[i].pos; ik_out[i].pos = transforms[i].pos;
} }
for (int i = res_ik.bones_count - 2; i > 0; --i) { for (i32 i = bones_count - 2; i > 0; --i) {
ik_out[i].rot = transforms[i].rot; ik_out[i].rot = transforms[i].rot;
} }
ik_out[res_ik.bones_count - 1].rot = pose.rotations[indices[res_ik.bones_count - 1]]; ik_out[bones_count - 1].rot = pose.rotations[indices[bones_count - 1]];
if (first_bone.parent_idx >= 0) { if (first_bone.parent_idx >= 0) {
ik_out[0].rot = roots_parent.rot.conjugated() * transforms[0].rot; ik_out[0].rot = roots_parent.rot.conjugated() * transforms[0].rot;
@ -753,7 +748,7 @@ struct AnimationModuleImpl final : AnimationModule {
ik_out[0].pos = pose.positions[indices[0]]; ik_out[0].pos = pose.positions[indices[0]];
const float w = ik.weight; const float w = ik.weight;
for (u32 i = 0; i < res_ik.bones_count; ++i) { for (i32 i = 0; i < bones_count; ++i) {
const u32 idx = indices[i]; const u32 idx = indices[i];
pose.positions[idx] = lerp(pose.positions[idx], ik_out[i].pos, w); pose.positions[idx] = lerp(pose.positions[idx], ik_out[i].pos, w);
pose.rotations[idx] = nlerp(pose.rotations[idx], ik_out[i].rot, w); pose.rotations[idx] = nlerp(pose.rotations[idx], ik_out[i].rot, w);

View file

@ -22,7 +22,6 @@ struct AnimationModule : IModule {
static UniquePtr<AnimationModule> create(Engine& engine, ISystem& system, World& world, struct IAllocator& allocator); static UniquePtr<AnimationModule> create(Engine& engine, ISystem& system, World& world, struct IAllocator& allocator);
static void reflect(Engine& engine); static void reflect(Engine& engine);
virtual const struct OutputMemoryStream* getEventStream(EntityRef e) const = 0;
virtual struct Path getPropertyAnimation(EntityRef entity) = 0; virtual struct Path getPropertyAnimation(EntityRef entity) = 0;
virtual void setPropertyAnimation(EntityRef entity, const Path& path) = 0; virtual void setPropertyAnimation(EntityRef entity, const Path& path) = 0;
virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0; virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0;

View file

@ -4,6 +4,7 @@
#include "engine/hash.h" #include "engine/hash.h"
#include "engine/log.h" #include "engine/log.h"
#include "engine/resource_manager.h" #include "engine/resource_manager.h"
#include "engine/stack_array.h"
#include "renderer/model.h" #include "renderer/model.h"
#include "renderer/pose.h" #include "renderer/pose.h"
@ -15,30 +16,22 @@ const ResourceType Controller::TYPE = ResourceType("anim_controller");
Controller::Controller(const Path& path, ResourceManager& resource_manager, IAllocator& allocator) Controller::Controller(const Path& path, ResourceManager& resource_manager, IAllocator& allocator)
: Resource(path, resource_manager, allocator) : Resource(path, resource_manager, allocator)
, m_allocator(allocator) , m_allocator(allocator)
, m_animation_slots(allocator)
, m_animation_entries(allocator) , m_animation_entries(allocator)
, m_inputs(allocator) , m_inputs(allocator)
, m_ik(allocator)
, m_bone_masks(allocator) , m_bone_masks(allocator)
{ {}
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator);
m_root->m_name = "Root";
}
Controller::~Controller() { Controller::~Controller() {
LUMIX_DELETE(m_allocator, m_root); LUMIX_DELETE(m_allocator, m_root);
ASSERT(isEmpty()); ASSERT(isEmpty());
} }
void Controller::clear() {
unload();
}
void Controller::unload() { void Controller::unload() {
for (const AnimationEntry& entry : m_animation_entries) { for (const AnimationEntry& entry : m_animation_entries) {
if (entry.animation) entry.animation->decRefCount(); if (entry.animation) entry.animation->decRefCount();
} }
m_animation_entries.clear(); m_animation_entries.clear();
m_animation_slots.clear();
m_bone_masks.clear(); m_bone_masks.clear();
m_inputs.clear(); m_inputs.clear();
LUMIX_DELETE(m_allocator, m_root); LUMIX_DELETE(m_allocator, m_root);
@ -55,11 +48,13 @@ void Controller::destroyRuntime(RuntimeContext& ctx) {
} }
RuntimeContext* Controller::createRuntime(u32 anim_set) { RuntimeContext* Controller::createRuntime(u32 anim_set) {
if (!m_compiled) return nullptr;
RuntimeContext* ctx = LUMIX_NEW(m_allocator, RuntimeContext)(*this, m_allocator); RuntimeContext* ctx = LUMIX_NEW(m_allocator, RuntimeContext)(*this, m_allocator);
ctx->inputs.resize(m_inputs.size()); ctx->inputs.resize(m_inputs.size());
memset(ctx->inputs.begin(), 0, ctx->inputs.byte_size()); memset(ctx->inputs.begin(), 0, ctx->inputs.byte_size());
ctx->animations.resize(m_animation_slots.size()); for (u32 i = 0; i < (u32)m_inputs.size(); ++i) {
ctx->inputs[i].type = m_inputs[i].type;
}
ctx->animations.resize(m_animation_slots_count);
memset(ctx->animations.begin(), 0, ctx->animations.byte_size()); memset(ctx->animations.begin(), 0, ctx->animations.byte_size());
for (AnimationEntry& anim : m_animation_entries) { for (AnimationEntry& anim : m_animation_entries) {
if (anim.set == anim_set) { if (anim.set == anim_set) {
@ -71,27 +66,18 @@ RuntimeContext* Controller::createRuntime(u32 anim_set) {
} }
void Controller::update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const { void Controller::update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const {
if (!m_compiled) return;
ASSERT(&ctx.controller == this); ASSERT(&ctx.controller == this);
// TODO better allocation strategy // TODO better allocation strategy
const Span<u8> mem = ctx.data.releaseOwnership(); const Span<u8> mem = ctx.data.releaseOwnership();
ctx.data.reserve(mem.length()); ctx.data.reserve(mem.length());
ctx.events.clear(); ctx.blendstack.clear();
ctx.input_runtime.set(mem.begin(), mem.length()); ctx.input_runtime.set(mem.begin(), mem.length());
if (m_root) m_root->update(ctx, root_motion); if (m_root) m_root->update(ctx, root_motion);
m_allocator.deallocate(mem.begin()); m_allocator.deallocate(mem.begin());
} }
void Controller::getPose(RuntimeContext& ctx, Pose& pose) {
if (!m_compiled) return;
ASSERT(&ctx.controller == this);
ctx.input_runtime.set(ctx.data.data(), ctx.data.size());
if (m_root) m_root->getPose(ctx, 1.f, pose, 0xffFFffFF);
}
struct Header { struct Header {
u32 magic = MAGIC; u32 magic = MAGIC;
ControllerVersion version = ControllerVersion::LATEST; ControllerVersion version = ControllerVersion::LATEST;
@ -101,22 +87,22 @@ struct Header {
void Controller::serialize(OutputMemoryStream& stream) { void Controller::serialize(OutputMemoryStream& stream) {
Header header; Header header;
stream.write(header); stream.write(header);
stream.write(m_flags);
stream.write(m_id_generator);
stream.write(m_root_motion_bone); stream.write(m_root_motion_bone);
stream.writeArray(m_inputs); stream.writeArray(m_inputs);
stream.write((u32)m_animation_slots.size()); stream.write(m_animation_slots_count);
for (const String& slot : m_animation_slots) {
stream.writeString(slot);
}
stream.write((u32)m_animation_entries.size()); stream.write((u32)m_animation_entries.size());
for (const AnimationEntry& entry : m_animation_entries) { for (const AnimationEntry& entry : m_animation_entries) {
stream.write(entry.slot); stream.write(entry.slot);
stream.write(entry.set); stream.write(entry.set);
stream.writeString(entry.animation ? entry.animation->getPath() : Path()); stream.writeString(entry.animation ? entry.animation->getPath() : Path());
} }
stream.write(m_ik); stream.write(m_ik.size());
stream.write(m_ik_count); for (const IK& ik : m_ik) {
stream.write(ik.max_iterations);
stream.writeArray(ik.bones);
}
stream.write(m_root->type());
m_root->serialize(stream); m_root->serialize(stream);
} }
@ -133,22 +119,9 @@ bool Controller::deserialize(InputMemoryStream& stream) {
return false; return false;
} }
if (header.version <= ControllerVersion::FIRST_SUPPORTED) {
logError("Version of animation controller ", getPath(), " is too old and not supported");
return false;
}
stream.read(m_flags);
stream.read(m_id_generator);
stream.read(m_root_motion_bone); stream.read(m_root_motion_bone);
stream.readArray(&m_inputs); stream.readArray(&m_inputs);
const u32 slots_count = stream.read<u32>(); stream.read(m_animation_slots_count);
m_animation_slots.reserve(slots_count);
for (u32 i = 0; i < slots_count; ++i) {
String& slot = m_animation_slots.emplace(m_allocator);
const char* tmp = stream.readString();
slot = tmp;
}
const u32 entries_count = stream.read<u32>(); const u32 entries_count = stream.read<u32>();
m_animation_entries.reserve(entries_count); m_animation_entries.reserve(entries_count);
@ -160,20 +133,57 @@ bool Controller::deserialize(InputMemoryStream& stream) {
entry.animation = path[0] ? m_resource_manager.getOwner().load<Animation>(Path(path)) : nullptr; entry.animation = path[0] ? m_resource_manager.getOwner().load<Animation>(Path(path)) : nullptr;
} }
stream.read(m_ik); u32 ik_count = stream.read<u32>();
stream.read(m_ik_count); m_ik.reserve(ik_count);
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator); for (u32 i = 0; i < ik_count; ++i) {
m_root->m_name = "Root"; IK& ik = m_ik.emplace(m_allocator);
stream.read(ik.max_iterations);
stream.readArray(&ik.bones);
}
NodeType type;
stream.read(type);
m_root = (PoseNode*)Node::create(type, *this);
m_root->deserialize(stream, *this, (u32)header.version); m_root->deserialize(stream, *this, (u32)header.version);
if (m_root->compile()) {
m_compiled = true;
}
else {
logError("Failed to compile ", m_path);
m_compiled = false;
}
return true; return true;
} }
static void getPose(const anim::RuntimeContext& ctx, Time time, float weight, u32 slot, Pose& pose, u32 mask_idx, bool looped) {
Animation* anim = ctx.animations[slot];
ASSERT(anim);
ASSERT(ctx.model->isReady());
ASSERT(anim->isReady());
const Time anim_time = looped ? time % anim->getLength() : minimum(time, anim->getLength());
Animation::SampleContext sample_ctx;
sample_ctx.pose = &pose;
sample_ctx.time = anim_time;
sample_ctx.model = ctx.model;
sample_ctx.weight = weight;
sample_ctx.mask = mask_idx < (u32)ctx.controller.m_bone_masks.size() ? &ctx.controller.m_bone_masks[mask_idx] : nullptr;
anim->setRootMotionBone(ctx.root_bone_hash);
anim->getRelativePose(sample_ctx);
}
void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose) {
InputMemoryStream bs(ctx.blendstack);
for (;;) {
anim::BlendStackInstructions instr;
bs.read(instr);
switch (instr) {
case anim::BlendStackInstructions::END: return;
case anim::BlendStackInstructions::SAMPLE: {
u32 slot = bs.read<u32>();
float weight = bs.read<float>();
Time time = bs.read<Time>();
bool looped = bs.read<bool>();
getPose(ctx, time, weight, slot, pose, 0, looped);
break;
}
}
}
}
} // ns Lumix::Anim } // ns Lumix::Anim

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "animation.h"
#include "engine/flag_set.h" #include "engine/flag_set.h"
#include "engine/hash.h" #include "engine/hash.h"
#include "engine/resource.h" #include "engine/resource.h"
@ -13,15 +14,59 @@ struct Pose;
namespace anim { namespace anim {
struct Node; struct Value {
struct RuntimeContext; enum Type : u32 {
FLOAT,
I32,
BOOL
};
Type type;
union {
float f;
bool b;
i32 s32;
};
Value() : f(0), type(FLOAT) {}
Value(float f) : f(f), type(FLOAT) {}
Value(bool b) : b(b), type(BOOL) {}
Value(i32 i) : s32(i), type(I32) {}
float toFloat() const { ASSERT(type == FLOAT); return f; }
i32 toI32() const { ASSERT(type == I32); return s32; }
};
struct RuntimeContext {
RuntimeContext(struct Controller& controller, IAllocator& allocator);
void setInput(u32 input_idx, float value);
void setInput(u32 input_idx, bool value);
Controller& controller;
Array<Value> inputs;
Array<Animation*> animations;
OutputMemoryStream data;
OutputMemoryStream blendstack;
float weight = 1;
BoneNameHash root_bone_hash;
Time time_delta;
Model* model = nullptr;
InputMemoryStream input_runtime;
};
enum BlendStackInstructions : u8 {
END,
SAMPLE,
};
enum class ControllerVersion : u32 { enum class ControllerVersion : u32 {
FIRST_SUPPORTED = 4, FIRST,
LATEST LATEST
}; };
void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose);
struct Controller final : Resource { struct Controller final : Resource {
Controller(const Path& path, ResourceManager& resource_manager, IAllocator& allocator); Controller(const Path& path, ResourceManager& resource_manager, IAllocator& allocator);
~Controller(); ~Controller();
@ -32,8 +77,6 @@ struct Controller final : Resource {
RuntimeContext* createRuntime(u32 anim_set); RuntimeContext* createRuntime(u32 anim_set);
void destroyRuntime(RuntimeContext& ctx); void destroyRuntime(RuntimeContext& ctx);
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const; void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const;
void getPose(RuntimeContext& ctx, struct Pose& pose);
void clear();
ResourceType getType() const override { return TYPE; } ResourceType getType() const override { return TYPE; }
static const ResourceType TYPE; static const ResourceType TYPE;
@ -41,40 +84,28 @@ struct Controller final : Resource {
struct AnimationEntry { struct AnimationEntry {
u32 set; u32 set;
u32 slot; u32 slot;
Animation* animation; Animation* animation = nullptr;
}; };
struct Input { struct Input {
enum Type { Value::Type type;
FLOAT,
BOOL,
I32
};
Type type;
StaticString<32> name; StaticString<32> name;
}; };
struct IK {
IK(IAllocator& allocator) : bones(allocator) {}
u32 max_iterations = 5;
Array<BoneNameHash> bones;
};
IAllocator& m_allocator; IAllocator& m_allocator;
struct TreeNode* m_root = nullptr; struct PoseNode* m_root = nullptr;
Array<AnimationEntry> m_animation_entries; Array<AnimationEntry> m_animation_entries;
Array<String> m_animation_slots;
Array<BoneMask> m_bone_masks; Array<BoneMask> m_bone_masks;
Array<Input> m_inputs; Array<Input> m_inputs;
u32 m_id_generator = 0; Array<IK> m_ik;
enum class Flags : u32 { u32 m_animation_slots_count = 0;
UNUSED_FLAG = 1 << 0 BoneNameHash m_root_motion_bone;
};
FlagSet<Flags, u32> m_flags;
struct IK {
enum { MAX_BONES_COUNT = 8 };
u16 max_iterations = 5;
u16 bones_count = 4;
BoneNameHash bones[MAX_BONES_COUNT];
} m_ik[4];
u32 m_ik_count = 0;
StaticString<64> m_root_motion_bone;
bool m_compiled = false;
private: private:
void unload() override; void unload() override;

View file

@ -642,7 +642,7 @@ struct StudioAppPlugin : StudioApp::IPlugin {
m_app.getPropertyGrid().addPlugin(m_animable_plugin); m_app.getPropertyGrid().addPlugin(m_animable_plugin);
m_anim_editor = anim::ControllerEditor::create(m_app); m_anim_editor = anim_editor::ControllerEditor::create(m_app);
} }
bool showGizmo(WorldView&, ComponentUID) override { return false; } bool showGizmo(WorldView&, ComponentUID) override { return false; }
@ -662,7 +662,7 @@ struct StudioAppPlugin : StudioApp::IPlugin {
AnimablePropertyGridPlugin m_animable_plugin; AnimablePropertyGridPlugin m_animable_plugin;
AnimationAssetBrowserPlugin m_animation_plugin; AnimationAssetBrowserPlugin m_animation_plugin;
PropertyAnimationPlugin m_prop_anim_plugin; PropertyAnimationPlugin m_prop_anim_plugin;
UniquePtr<anim::ControllerEditor> m_anim_editor; UniquePtr<anim_editor::ControllerEditor> m_anim_editor;
}; };

View file

@ -1,7 +1,9 @@
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include "animation/animation_module.h" #include "animation/animation_module.h"
#include "animation/controller.h"
#include "controller_editor.h" #include "controller_editor.h"
#include "editor_nodes.h"
#include "editor/asset_browser.h" #include "editor/asset_browser.h"
#include "editor/asset_compiler.h" #include "editor/asset_compiler.h"
#include "editor/editor_asset.h" #include "editor/editor_asset.h"
@ -18,12 +20,133 @@
#include "renderer/editor/world_viewer.h" #include "renderer/editor/world_viewer.h"
#include "renderer/model.h" #include "renderer/model.h"
#include "renderer/render_module.h" #include "renderer/render_module.h"
#include "../animation.h"
#include "../controller.h"
#include "../nodes.h"
namespace Lumix::anim { namespace Lumix::anim_editor {
Controller::Controller(const Path& path, IAllocator& allocator)
: m_allocator(allocator)
, m_animation_slots(allocator)
, m_animation_entries(allocator)
, m_inputs(allocator)
, m_ik(allocator)
, m_bone_masks(allocator)
, m_path(path)
{
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator);
m_root->m_name = "Root";
}
Controller::~Controller() { LUMIX_DELETE(m_allocator, m_root); }
void Controller::clear() {
m_animation_entries.clear();
m_animation_slots.clear();
m_bone_masks.clear();
m_inputs.clear();
LUMIX_DELETE(m_allocator, m_root);
m_root = nullptr;
}
struct Header {
u32 magic = MAGIC;
ControllerVersion version = ControllerVersion::LATEST;
static constexpr u32 MAGIC = '_LAC';
};
void Controller::serialize(OutputMemoryStream& stream) {
Header header;
stream.write(header);
stream.write(m_id_generator);
stream.write(m_root_motion_bone);
stream.writeArray(m_inputs);
stream.write((u32)m_animation_slots.size());
for (const String& slot : m_animation_slots) {
stream.writeString(slot);
}
stream.write((u32)m_animation_entries.size());
for (const AnimationEntry& entry : m_animation_entries) {
stream.write(entry.slot);
stream.write(entry.set);
stream.writeString(entry.animation);
}
stream.write(m_ik.size());
for (const IK& ik : m_ik) {
stream.write(ik.max_iterations);
stream.writeArray(ik.bones);
}
m_root->serialize(stream);
}
bool Controller::compile(StudioApp& app, OutputMemoryStream& blob) {
ResourceManager* rm = app.getEngine().getResourceManager().get(anim::Controller::TYPE);
anim::Controller controller(m_path, *rm, m_allocator);
controller.m_animation_slots_count = m_animation_slots.size();
controller.m_inputs.resize(m_inputs.size());
for (u32 i = 0; i < (u32)m_inputs.size(); ++i) {
controller.m_inputs[i].name = m_inputs[i].name;
controller.m_inputs[i].type = m_inputs[i].type;
}
controller.m_animation_entries.resize(m_animation_entries.size());
for (u32 i = 0; i < (u32)m_animation_entries.size(); ++i) {
controller.m_animation_entries[i].set = m_animation_entries[i].set;
controller.m_animation_entries[i].slot = m_animation_entries[i].slot;
const Path& path = m_animation_entries[i].animation;
controller.m_animation_entries[i].animation = path.isEmpty() ? nullptr : rm->getOwner().load<Animation>(path);
}
controller.m_root = (anim::PoseNode*)m_root->compile(controller);
if (!controller.m_root) return false;
controller.serialize(blob);
return true;
}
bool Controller::deserialize(InputMemoryStream& stream) {
Header header;
stream.read(header);
if (header.magic != Header::MAGIC) return false;
if (header.version > ControllerVersion::LATEST) return false;
if (header.version <= ControllerVersion::FIRST_SUPPORTED) return false;
stream.read(m_id_generator);
stream.read(m_root_motion_bone);
stream.readArray(&m_inputs);
const u32 slots_count = stream.read<u32>();
m_animation_slots.reserve(slots_count);
for (u32 i = 0; i < slots_count; ++i) {
String& slot = m_animation_slots.emplace(m_allocator);
slot = stream.readString();
}
const u32 entries_count = stream.read<u32>();
m_animation_entries.reserve(entries_count);
for (u32 i = 0; i < entries_count; ++i) {
AnimationEntry& entry = m_animation_entries.emplace();
stream.read(entry.slot);
stream.read(entry.set);
entry.animation = stream.readString();
}
u32 ik_count = stream.read<u32>();
m_ik.reserve(ik_count);
for (u32 i = 0; i < ik_count; ++i) {
IK& ik = m_ik.emplace(m_allocator);
stream.read(ik.max_iterations);
stream.readArray(&ik.bones);
}
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator);
m_root->m_name = "Root";
m_root->deserialize(stream, *this, (u32)header.version);
OutputMemoryStream blob(m_allocator);
return true;
}
struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetCompiler::IPlugin { struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetCompiler::IPlugin {
struct EditorWindow : AssetEditorWindow, NodeEditor { struct EditorWindow : AssetEditorWindow, NodeEditor {
@ -36,12 +159,12 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
virtual void endCategory() {} virtual void endCategory() {}
virtual INodeTypeVisitor& visitType(const char* label, const INodeCreator& creator, char shortcut = 0) = 0; virtual INodeTypeVisitor& visitType(const char* label, const INodeCreator& creator, char shortcut = 0) = 0;
INodeTypeVisitor& visitType(Node::Type type, const char* label, char shortcut = 0) { INodeTypeVisitor& visitType(anim::NodeType type, const char* label, char shortcut = 0) {
struct : INodeCreator { struct : INodeCreator {
Node* create(EditorWindow& editor) const override { Node* create(EditorWindow& editor) const override {
return Node::create(editor.m_current_node, type, editor.m_controller, editor.m_controller.m_allocator); return Node::create(editor.m_current_node, type, editor.m_controller, editor.m_controller.m_allocator);
} }
Node::Type type; anim::NodeType type;
} creator; } creator;
creator.type = type; creator.type = type;
return visitType(label, creator, shortcut); return visitType(label, creator, shortcut);
@ -52,7 +175,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
if (visitor.beginCategory("Animations")) { if (visitor.beginCategory("Animations")) {
struct : INodeTypeVisitor::INodeCreator { struct : INodeTypeVisitor::INodeCreator {
Node* create(EditorWindow& editor) const override { Node* create(EditorWindow& editor) const override {
Node* n = Node::create(editor.m_current_node, Node::ANIMATION, editor.m_controller, editor.m_controller.m_allocator); Node* n = Node::create(editor.m_current_node, anim::NodeType::ANIMATION, editor.m_controller, editor.m_controller.m_allocator);
((AnimationNode*)n)->m_slot = slot; ((AnimationNode*)n)->m_slot = slot;
return n; return n;
} }
@ -65,11 +188,29 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
visitor.endCategory(); visitor.endCategory();
} }
visitor visitor
.visitType(Node::BLEND1D, "Blend 1D", '1') .visitType(anim::NodeType::BLEND1D, "Blend 1D", '1')
.visitType(Node::BLEND2D, "Blend 2D", '2') .visitType(anim::NodeType::BLEND2D, "Blend 2D", '2');
.visitType(Node::LAYERS, "Layers", 'L')
.visitType(Node::SELECT, "Select", 'S') if (visitor.beginCategory("Inputs")) {
.visitType(Node::TREE, "Tree", 'T'); struct : INodeTypeVisitor::INodeCreator {
Node* create(EditorWindow& editor) const override {
Node* n = Node::create(editor.m_current_node, anim::NodeType::INPUT, editor.m_controller, editor.m_controller.m_allocator);
((InputNode*)n)->m_input_index = input_index;
return n;
}
u32 input_index;
} creator;
for (Controller::Input& input : m_controller.m_inputs) {
creator.input_index = u32(&input - m_controller.m_inputs.begin());
visitor.visitType(input.name, creator);
}
visitor.endCategory();
}
visitor
.visitType(anim::NodeType::LAYERS, "Layers", 'L')
.visitType(anim::NodeType::SELECT, "Select", 'S')
.visitType(anim::NodeType::TREE, "Tree", 'T');
} }
EditorWindow(const Path& path, ControllerEditorImpl& plugin, StudioApp& app, IAllocator& allocator) EditorWindow(const Path& path, ControllerEditorImpl& plugin, StudioApp& app, IAllocator& allocator)
@ -78,14 +219,12 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
, m_allocator(allocator) , m_allocator(allocator)
, m_app(app) , m_app(app)
, m_plugin(plugin) , m_plugin(plugin)
, m_copy_buffer(allocator) , m_controller(path, allocator)
, m_controller(path, *app.getEngine().getResourceManager().get(Controller::TYPE), allocator)
, m_viewer(app) , m_viewer(app)
{ {
FileSystem& fs = m_app.getEngine().getFileSystem(); FileSystem& fs = m_app.getEngine().getFileSystem();
OutputMemoryStream data(m_allocator); OutputMemoryStream data(m_allocator);
if (fs.getContentSync(Path(path), data)) { if (fs.getContentSync(Path(path), data)) {
ResourceManager* res_manager = m_app.getEngine().getResourceManager().get(Controller::TYPE);
InputMemoryStream str(data); InputMemoryStream str(data);
if (m_controller.deserialize(str)) { if (m_controller.deserialize(str)) {
m_current_node = m_controller.m_root; m_current_node = m_controller.m_root;
@ -214,12 +353,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
} }
void previewUI() { void previewUI() {
if (!ImGui::BeginTable("tab", 2, ImGuiTableFlags_Resizable)) return;
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 250);
ImGui::TableNextRow();
ImGui::TableNextColumn();
debuggerUI(*m_viewer.m_world, *m_viewer.m_mesh); debuggerUI(*m_viewer.m_world, *m_viewer.m_mesh);
ImGui::Separator(); ImGui::Separator();
@ -229,7 +362,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
ImGuiEx::Label("Preview model"); ImGuiEx::Label("Preview model");
if (m_app.getAssetBrowser().resourceInput("model", model_path, Model::TYPE)) { if (m_app.getAssetBrowser().resourceInput("model", model_path, Model::TYPE)) {
render_module->setModelInstancePath(*m_viewer.m_mesh, model_path); render_module->setModelInstancePath(*m_viewer.m_mesh, model_path);
anim_module->setAnimatorSource(*m_viewer.m_mesh, m_controller.getPath()); anim_module->setAnimatorSource(*m_viewer.m_mesh, m_controller.m_path);
} }
Model* model = render_module->getModelInstanceModel(*m_viewer.m_mesh); Model* model = render_module->getModelInstanceModel(*m_viewer.m_mesh);
if (model && model->isReady()) { if (model && model->isReady()) {
@ -255,15 +388,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
ImGui::Checkbox("##fm", &m_viewer.m_follow_mesh); ImGui::Checkbox("##fm", &m_viewer.m_follow_mesh);
ImGuiEx::Label("Playback speed"); ImGuiEx::Label("Playback speed");
ImGui::DragFloat("##spd", &m_playback_speed, 0.1f, 0, FLT_MAX); ImGui::DragFloat("##spd", &m_playback_speed, 0.1f, 0, FLT_MAX);
if (ImGui::Button("Apply")) {
anim::Controller* ctrl = anim_module->getAnimatorController(*m_viewer.m_mesh);
OutputMemoryStream blob(m_allocator);
m_controller.serialize(blob);
InputMemoryStream tmp(blob);
ctrl->clear();
ctrl->deserialize(tmp);
}
ImGui::SameLine();
if (ImGui::Button("Reset")) { if (ImGui::Button("Reset")) {
m_viewer.m_world->setTransform(*m_viewer.m_mesh, DVec3(0), Quat::IDENTITY, Vec3(1)); m_viewer.m_world->setTransform(*m_viewer.m_mesh, DVec3(0), Quat::IDENTITY, Vec3(1));
} }
@ -274,10 +398,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
else { else {
anim_module->updateAnimator(*m_viewer.m_mesh, m_app.getEngine().getLastTimeDelta() * m_playback_speed); anim_module->updateAnimator(*m_viewer.m_mesh, m_app.getEngine().getLastTimeDelta() * m_playback_speed);
} }
ImGui::TableNextColumn();
m_viewer.gui();
ImGui::EndTable();
} }
void debuggerUI() { void debuggerUI() {
@ -328,17 +448,17 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
const ComponentType animator_type = reflection::getComponentType("animator"); const ComponentType animator_type = reflection::getComponentType("animator");
AnimationModule* module = (AnimationModule*)world.getModule(animator_type); AnimationModule* module = (AnimationModule*)world.getModule(animator_type);
Controller* ctrl = module->getAnimatorController(entity); anim::Controller* ctrl = module->getAnimatorController(entity);
if (!ctrl) { if (!ctrl) {
ImGui::TextUnformatted("Selected entity does not have resource assigned in animator component"); ImGui::TextUnformatted("Selected entity does not have resource assigned in animator component");
return; return;
} }
for (const Controller::Input& input : ctrl->m_inputs) { for (const anim::Controller::Input& input : ctrl->m_inputs) {
ImGui::PushID(&input); ImGui::PushID(&input);
const u32 idx = u32(&input - ctrl->m_inputs.begin()); const u32 idx = u32(&input - ctrl->m_inputs.begin());
switch (input.type) { switch (input.type) {
case Controller::Input::Type::FLOAT: { case anim::Value::FLOAT: {
float val = module->getAnimatorFloatInput(entity, idx); float val = module->getAnimatorFloatInput(entity, idx);
ImGuiEx::Label(input.name); ImGuiEx::Label(input.name);
@ -362,7 +482,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
} }
break; break;
} }
case Controller::Input::BOOL: { case anim::Value::BOOL: {
bool val = module->getAnimatorBoolInput(entity, idx); bool val = module->getAnimatorBoolInput(entity, idx);
ImGuiEx::Label(input.name); ImGuiEx::Label(input.name);
if (ImGui::Checkbox("##i", &val)) { if (ImGui::Checkbox("##i", &val)) {
@ -370,7 +490,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
} }
break; break;
} }
case Controller::Input::I32: { case anim::Value::I32: {
i32 val = module->getAnimatorI32Input(entity, idx); i32 val = module->getAnimatorI32Input(entity, idx);
ImGuiEx::Label(input.name); ImGuiEx::Label(input.name);
if (ImGui::DragInt("##i", (int*)&val, 1, 0, 0x7ffFFff)) { if (ImGui::DragInt("##i", (int*)&val, 1, 0, 0x7ffFFff)) {
@ -382,21 +502,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
ImGui::PopID(); ImGui::PopID();
} }
if (m_controller.m_ik_count > 0) { #if 0
for (u32 i = 0; i < m_controller.m_ik_count; ++i) { for (Controller::IK& ik : m_controller.m_ik) {
if (ImGui::TreeNode((const void*)(uintptr)i, "IK chain %d", i)) { const u32 i = u32(&ik - m_controller.m_ik.begin());
ImGui::Checkbox("Enabled", &m_ik_debug[i].enabled); if (ImGui::TreeNode(&ik, "IK chain %d", i)) {
if (m_ik_debug[i].enabled) ImGui::DragFloat3("Target", &m_ik_debug[i].target.x); ImGui::Checkbox("Enabled", &m_ik_debug[i].enabled);
ImGui::TreePop(); if (m_ik_debug[i].enabled) ImGui::DragFloat3("Target", &m_ik_debug[i].target.x);
} ImGui::TreePop();
module->setAnimatorIK(entity, i, m_ik_debug[i].enabled ? 1.f : 0.f, m_ik_debug[i].target); }
if (m_ik_debug[i].enabled) { module->setAnimatorIK(entity, i, m_ik_debug[i].enabled ? 1.f : 0.f, m_ik_debug[i].target);
auto* render_module = (RenderModule*)world.getModule("renderer"); if (m_ik_debug[i].enabled) {
Transform tr = world.getTransform(entity); auto* render_module = (RenderModule*)world.getModule("renderer");
render_module->addDebugCross(tr.transform(m_ik_debug[i].target), 0.25f, Color::RED); Transform tr = world.getTransform(entity);
} render_module->addDebugCross(tr.transform(m_ik_debug[i].target), 0.25f, Color::RED);
} }
} }
#endif
} }
void deleteSelectedNodes() { void deleteSelectedNodes() {
@ -438,42 +560,36 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
m_model = m_app.getEngine().getResourceManager().load<Model>(model_path); m_model = m_app.getEngine().getResourceManager().load<Model>(model_path);
} }
if(m_model) { if(m_model) {
for (u32 i = 0; i < m_controller.m_ik_count; ++i) { for (u32 i = 0; i < (u32)m_controller.m_ik.size(); ++i) {
ImGui::PushID(i); ImGui::PushID(i);
if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) { if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) {
if (i < m_controller.m_ik_count - 1) { m_controller.m_ik.swapAndPop(i);
memmove(&m_controller.m_ik[i]
, &m_controller.m_ik[i + 1]
, sizeof(m_controller.m_ik[i + 1]) * (m_controller.m_ik_count - 1 - i)
);
}
--m_controller.m_ik_count;
ImGui::PopID(); ImGui::PopID();
continue; continue;
} }
ImGui::PopID(); ImGui::PopID();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::TreeNode((const void*)(uintptr)i, "Chain %d", i)) { Controller::IK& ik = m_controller.m_ik[i];
Controller::IK& ik = m_controller.m_ik[i]; if (ImGui::TreeNode(&ik, "Chain %d", i)) {
ASSERT(ik.bones_count > 0); ASSERT(!ik.bones.empty());
const u32 bones_count = m_model->getBoneCount(); const u32 bones_count = m_model->getBoneCount();
auto leaf_iter = m_model->getBoneIndex(ik.bones[ik.bones_count - 1]); auto leaf_iter = m_model->getBoneIndex(ik.bones.back());
ImGuiEx::Label("Leaf"); ImGuiEx::Label("Leaf");
if (ImGui::BeginCombo("##leaf", leaf_iter.isValid() ? m_model->getBone(leaf_iter.value()).name.c_str() : "N/A")) { if (ImGui::BeginCombo("##leaf", leaf_iter.isValid() ? m_model->getBone(leaf_iter.value()).name.c_str() : "N/A")) {
bool selected = false; bool selected = false;
for (u32 j = 0; j < bones_count; ++j) { for (u32 j = 0; j < bones_count; ++j) {
const char* bone_name = m_model->getBone(j).name.c_str(); const char* bone_name = m_model->getBone(j).name.c_str();
if (ImGui::Selectable(bone_name)) { if (ImGui::Selectable(bone_name)) {
ik.bones_count = 1; ik.bones.clear();
ik.bones[0] = BoneNameHash(bone_name); ik.bones.push(BoneNameHash(bone_name));
selected = true; selected = true;
} }
} }
ImGui::EndCombo(); ImGui::EndCombo();
saveUndo(selected); saveUndo(selected);
} }
for (u32 j = ik.bones_count - 2; j != 0xffFFffFF; --j) { for (i32 j = ik.bones.size() - 2; j >= 0; --j) {
auto iter = m_model->getBoneIndex(ik.bones[j]); auto iter = m_model->getBoneIndex(ik.bones[j]);
if (iter.isValid()) { if (iter.isValid()) {
ImGuiEx::TextUnformatted(m_model->getBone(iter.value()).name); ImGuiEx::TextUnformatted(m_model->getBone(iter.value()).name);
@ -485,31 +601,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
auto iter = m_model->getBoneIndex(ik.bones[0]); auto iter = m_model->getBoneIndex(ik.bones[0]);
if (iter.isValid()) { if (iter.isValid()) {
if (ik.bones_count < lengthOf(ik.bones)) { const int parent_idx = m_model->getBone(iter.value()).parent_idx;
const int parent_idx = m_model->getBone(iter.value()).parent_idx; if (parent_idx >= 0) {
if (parent_idx >= 0) { const char* bone_name = m_model->getBone(parent_idx).name.c_str();
const char* bone_name = m_model->getBone(parent_idx).name.c_str(); const StaticString<64> add_label("Add ", bone_name);
const StaticString<64> add_label("Add ", bone_name); if (ImGui::Button(add_label)) {
if (ImGui::Button(add_label)) { ik.bones.insert(0, BoneNameHash(bone_name));
memmove(&ik.bones[1], &ik.bones[0], sizeof(ik.bones[0]) * ik.bones_count); saveUndo(true);
ik.bones[0] = BoneNameHash(bone_name);
++ik.bones_count;
saveUndo(true);
}
} }
} }
else {
ImGui::Text("IK is full");
}
} }
else { else {
ImGui::Text("Unknown bone."); ImGui::Text("Unknown bone.");
} }
if (ik.bones_count > 1) { if (ik.bones.size() > 1) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Pop")) { if (ImGui::Button("Pop")) {
memmove(&ik.bones[0], &ik.bones[1], sizeof(ik.bones[0]) * ik.bones_count - 1); ik.bones.erase(0);
--ik.bones_count;
saveUndo(true); saveUndo(true);
} }
} }
@ -518,10 +626,9 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
} }
} }
if (m_controller.m_ik_count < (u32)lengthOf(m_controller.m_ik) && ImGui::Button(ICON_FA_PLUS_CIRCLE)) { if (ImGui::Button(ICON_FA_PLUS_CIRCLE)) {
m_controller.m_ik[m_controller.m_ik_count].bones_count = 1; Controller::IK& ik = m_controller.m_ik.emplace(m_allocator);
m_controller.m_ik[m_controller.m_ik_count].bones[0] = BoneNameHash(); ik.bones.push(BoneNameHash());
++m_controller.m_ik_count;
saveUndo(true); saveUndo(true);
} }
} }
@ -563,10 +670,10 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
ImGui::PopID(); ImGui::PopID();
ImGui::SameLine(); ImGui::SameLine();
if (!ImGui::TreeNodeEx(&mask, 0, "%s", mask.name.data)) continue; if (!ImGui::TreeNodeEx(&mask, 0, "%s", mask.name.c_str())) continue;
ImGuiEx::Label("Name"); ImGuiEx::Label("Name");
saveUndo(ImGui::InputText("##name", mask.name.data, sizeof(mask.name.data))); saveUndo(inputString("##name", &mask.name));
for (u32 i = 0, c = m_model->getBoneCount(); i < c; ++i) { for (u32 i = 0, c = m_model->getBoneCount(); i < c; ++i) {
const char* bone_name = m_model->getBone(i).name.c_str(); const char* bone_name = m_model->getBone(i).name.c_str();
const BoneNameHash bone_name_hash(bone_name); const BoneNameHash bone_name_hash(bone_name);
@ -629,7 +736,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
if (Path::hasExtension(subres, "ani")) { if (Path::hasExtension(subres, "ani")) {
Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace(); Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace();
ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager(); ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager();
entry.animation = res_manager.load<Animation>(Path(path)); entry.animation = path;
entry.set = 0; entry.set = 0;
entry.slot = m_controller.m_animation_slots.size(); entry.slot = m_controller.m_animation_slots.size();
@ -645,7 +752,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
if (Path::hasExtension(subres, "ani")) { if (Path::hasExtension(subres, "ani")) {
Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace(); Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace();
ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager(); ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager();
entry.animation = res_manager.load<Animation>(Path(path)); entry.animation = path;
entry.set = 0; entry.set = 0;
entry.slot = m_controller.m_animation_slots.size(); entry.slot = m_controller.m_animation_slots.size();
@ -690,14 +797,10 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(-1); ImGui::SetNextItemWidth(-1);
saveUndo(inputSlot(m_controller, "##slot", &entry.slot)); saveUndo(editSlot(m_controller, "##slot", &entry.slot));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::PushItemWidth(-1); ImGui::PushItemWidth(-1);
Path path = entry.animation ? entry.animation->getPath() : Path(); if (m_app.getAssetBrowser().resourceInput("anim", entry.animation, Animation::TYPE)) {
if (m_app.getAssetBrowser().resourceInput("anim", path, Animation::TYPE)) {
if (entry.animation) entry.animation->decRefCount();
ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager();
entry.animation = res_manager.load<Animation>(path);
saveUndo(true); saveUndo(true);
} }
ImGui::PopItemWidth(); ImGui::PopItemWidth();
@ -750,7 +853,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data))); saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data)));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::SetNextItemWidth(-1); ImGui::SetNextItemWidth(-1);
if (ImGui::Combo("##type", (int*)&input.type, "float\0u32\0bool")) { if (ImGui::Combo("##type", (int*)&input.type, "float\0i32\0bool")) {
saveUndo(true); saveUndo(true);
} }
ImGui::NextColumn(); ImGui::NextColumn();
@ -773,7 +876,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
const char* name = "N/A"; const char* name = "N/A";
switch (node->type()) { switch (node->type()) {
case Node::TREE: name = ((TreeNode*)node)->m_name.c_str(); break; case anim::NodeType::TREE: name = ((TreeNode*)node)->m_name.c_str(); break;
default: ASSERT(false); default: ASSERT(false);
}; };
if (m_current_node == node) { if (m_current_node == node) {
@ -797,54 +900,46 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
if (ImGui::BeginTabBar("ctb")) { if (ImGui::BeginTabBar("ctb")) {
if (ImGui::BeginTabItem("Tree")) { if (ImGui::BeginTabItem("Tree")) {
ImGui::Columns(2); if (ImGui::BeginTable("tt", 3, ImGuiTableFlags_Resizable)) {
breadcrumbs(m_current_node); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 250);
if (m_current_node) nodeEditorGUI(m_current_node->m_nodes, m_current_node->m_links); ImGui::TableNextRow();
ImGui::NextColumn(); ImGui::TableNextColumn();
if (m_current_node) { bool any_selected = false;
for (Node* n : m_current_node->m_nodes) { if (m_current_node) {
if (n->m_selected) { for (Node* n : m_current_node->m_nodes) {
n->propertiesGUI(); if (n->m_selected) {
break; n->propertiesGUI();
any_selected = true;
break;
}
} }
} }
if (ImGui::CollapsingHeader("Controller")) {
ImGuiEx::Label("Root motion bone");
saveUndo(ImGui::InputText("##rmb", m_controller.m_root_motion_bone.data, sizeof(m_controller.m_root_motion_bone.data)));
}
if (ImGui::CollapsingHeader("Inputs")) inputsGUI();
if (ImGui::CollapsingHeader("Slots")) slotsGUI();
if (ImGui::CollapsingHeader("Sets")) setsGUI();
if (ImGui::CollapsingHeader("Bone masks")) boneMasksGUI();
if (ImGui::CollapsingHeader("IK")) IKGUI();
ImGui::TableNextColumn();
breadcrumbs(m_current_node);
if (m_current_node) nodeEditorGUI(m_current_node->m_nodes, m_current_node->m_links);
ImGui::TableNextColumn();
if (ImGui::CollapsingHeader("Preview", ImGuiTreeNodeFlags_DefaultOpen)) previewUI();
m_viewer.gui();
ImGui::EndTable();
} }
ImGui::Columns();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Controller")) {
ImGuiEx::Label("Root motion bone");
saveUndo(ImGui::InputText("##rmb", m_controller.m_root_motion_bone.data, sizeof(m_controller.m_root_motion_bone.data)));
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Inputs")) {
inputsGUI();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Bone masks")) {
boneMasksGUI();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("IK")) {
IKGUI();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Slots")) {
slotsGUI();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Sets")) {
setsGUI();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Debugger")) { if (ImGui::BeginTabItem("Debugger")) {
debuggerUI(); debuggerUI();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Preview")) {
previewUI();
ImGui::EndTabItem();
}
ImGui::EndTabBar(); ImGui::EndTabBar();
} }
@ -853,12 +948,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
const Path& getPath() override { return m_path; } const Path& getPath() override { return m_path; }
const char* getName() const override { return "Animation Editor"; } const char* getName() const override { return "Animation Editor"; }
struct CopyBuffer {
CopyBuffer(IAllocator& allocator) : data(allocator) {}
OutputMemoryStream data;
Node::Type node_type;
} m_copy_buffer;
struct ControllerDebugMapping { struct ControllerDebugMapping {
i32 axis_x = -1; i32 axis_x = -1;
i32 axis_y = -1; i32 axis_y = -1;
@ -903,7 +992,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
void createResource(OutputMemoryStream& blob) override { void createResource(OutputMemoryStream& blob) override {
ResourceManager* rm = m_app.getEngine().getResourceManager().get(anim::Controller::TYPE); ResourceManager* rm = m_app.getEngine().getResourceManager().get(anim::Controller::TYPE);
anim::Controller controller(Path("new controller"), *rm, m_app.getAllocator()); anim_editor::Controller controller(Path("new controller"), m_app.getAllocator());
controller.serialize(blob); controller.serialize(blob);
} }
@ -912,9 +1001,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
m_app.getAssetBrowser().addWindow(win.move()); m_app.getAssetBrowser().addWindow(win.move());
} }
bool compile(const Path& src) override {
FileSystem& fs = m_app.getEngine().getFileSystem();
OutputMemoryStream src_data(m_app.getAllocator());
if (!fs.getContentSync(src, src_data)) return false;
InputMemoryStream input(src_data);
OutputMemoryStream output(m_app.getAllocator());
Controller ctrl(src, m_allocator);
if (!ctrl.deserialize(input)) return false;
if (!ctrl.compile(m_app, output)) return false;
return m_app.getAssetCompiler().writeCompiledResource(src, Span(output.data(), (i32)output.size()));
}
bool canCreateResource() const override { return true; } bool canCreateResource() const override { return true; }
const char* getDefaultExtension() const override { return "act"; } const char* getDefaultExtension() const override { return "act"; }
bool compile(const Path& src) override { return m_app.getAssetCompiler().copyCompile(src); }
const char* getLabel() const override { return "Animation Controller"; } const char* getLabel() const override { return "Animation Controller"; }
TagAllocator m_allocator; TagAllocator m_allocator;

View file

@ -6,7 +6,54 @@ namespace Lumix {
template <typename T> struct UniquePtr; template <typename T> struct UniquePtr;
namespace anim { namespace anim_editor {
struct Node;
enum class ControllerVersion : u32 {
FIRST_SUPPORTED = 4,
LATEST
};
struct Controller final {
Controller(const Path& path, IAllocator& allocator);
~Controller();
void serialize(OutputMemoryStream& stream);
bool deserialize(InputMemoryStream& stream);
void clear();
bool compile(StudioApp& app, OutputMemoryStream& blob);
struct AnimationEntry {
u32 set;
u32 slot;
Path animation;
};
struct Input {
anim::Value::Type type;
StaticString<32> name;
};
struct IK {
IK(IAllocator& allocator) : bones(allocator) {}
u32 max_iterations = 5;
Array<BoneNameHash> bones;
};
Path m_path;
IAllocator& m_allocator;
struct TreeNode* m_root = nullptr;
Array<AnimationEntry> m_animation_entries;
Array<String> m_animation_slots;
Array<BoneMask> m_bone_masks;
Array<Input> m_inputs;
u32 m_id_generator = 0;
Array<IK> m_ik;
StaticString<64> m_root_motion_bone;
bool m_compiled = false;
};
struct ControllerEditor { struct ControllerEditor {
static UniquePtr<ControllerEditor> create(StudioApp& app); static UniquePtr<ControllerEditor> create(StudioApp& app);

View file

@ -0,0 +1,765 @@
#include "animation/animation.h"
#include "animation/controller.h"
#include "engine/log.h"
#include "editor_nodes.h"
#include "renderer/model.h"
#include "renderer/pose.h"
#include "engine/crt.h"
#include "engine/stack_array.h"
namespace Lumix::anim_editor {
static constexpr u32 OUTPUT_FLAG = 1 << 31;
bool editInput(const char* label, u32* input_index, const Controller& controller) {
ASSERT(input_index);
bool changed = false;
ImGuiEx::Label(label);
if (controller.m_inputs.empty()) {
ImGui::Text("No inputs");
return false;
}
const Controller::Input& current_input = controller.m_inputs[*input_index];
if (ImGui::BeginCombo(StaticString<64>("##input", label), current_input.name)) {
for (const Controller::Input& input : controller.m_inputs) {
if (ImGui::Selectable(input.name)) {
changed = true;
*input_index = u32(&input - controller.m_inputs.begin());
}
}
ImGui::EndCombo();
}
return changed;
}
bool editSlot(const Controller& controller, const char* str_id, u32* slot) {
bool changed = false;
const char* preview = *slot < (u32)controller.m_animation_slots.size() ? controller.m_animation_slots[*slot].c_str() : "N/A";
if (ImGui::BeginCombo(str_id, preview, 0)) {
static char filter[64] = "";
ImGuiEx::filter("Filter", filter, sizeof(filter), -1, ImGui::IsWindowAppearing());
bool selected = false;
for (u32 i = 0, c = controller.m_animation_slots.size(); i < c; ++i) {
const char* name = controller.m_animation_slots[i].c_str();
if ((!filter[0] || findInsensitive(name, filter)) && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::Selectable(name))) {
*slot = i;
changed = true;
filter[0] = '\0';
ImGui::CloseCurrentPopup();
break;
}
}
ImGui::EndCombo();
}
return changed;
}
static PoseNode* castToPoseNode(Node* n) {
if (!n->isPoseNode()) return nullptr;
return (PoseNode*)n;
}
static ValueNode* castToValueNode(Node* n) {
if (!n) return nullptr;
if (!n->isValueNode()) return nullptr;
return (ValueNode*)n;
}
Blend2DNode::Blend2DNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
, m_children(allocator)
, m_triangles(allocator)
, m_name("blend2d", allocator)
{}
bool Blend2DNode::onGUI() {
ImGuiEx::NodeTitle(m_name.c_str());
inputSlot();
outputSlot();
ImGui::TextUnformatted("X input");
inputSlot();
ImGui::TextUnformatted("Y input");
return false;
}
anim::Node* Blend2DNode::compile(anim::Controller& controller) {
if (m_triangles.empty()) return nullptr;
UniquePtr<anim::Blend2DNode> node = UniquePtr<anim::Blend2DNode>::create(controller.m_allocator, controller.m_allocator);
m_children.copyTo(node->m_children);
m_triangles.copyTo(node->m_triangles);
ValueNode* x = castToValueNode(getInput(0));
ValueNode* y = castToValueNode(getInput(1));
if (!x || !y) return nullptr;
node->m_x_value = (anim::ValueNode*)x->compile(controller);
node->m_y_value = (anim::ValueNode*)y->compile(controller);
if (!node->m_x_value) return nullptr;
if (!node->m_y_value) return nullptr;
return node.detach();
}
bool Blend2DNode::propertiesGUI() {
ImGuiEx::Label("Name");
bool res = inputString("##name", &m_name);
if (ImGui::BeginTable("b2dt", 3, ImGuiTableFlags_Resizable)) {
for (Blend2DNode::Child& child : m_children) {
ImGui::PushID(&child);
ImGui::TableNextRow(ImGuiTableFlags_RowBg);
if (m_hovered_blend2d_child == i32(&child - m_children.begin())) {
ImU32 row_bg_color = ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TabHovered]);
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
}
else {
ImU32 row_bg_color = ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TableRowBg]);
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
}
ImGui::TableNextColumn();
if (ImGuiEx::IconButton(ICON_FA_TIMES_CIRCLE, "Remove")) {
m_children.erase(u32(&child - m_children.begin()));
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::PopID();
res = true;
continue;
}
ImGui::SameLine();
ImGui::SetNextItemWidth(-1);
res = ImGui::DragFloat("##xval", &child.value.x) || res;
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1);
res = ImGui::DragFloat("##yval", &child.value.y) || res;
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1);
res = editSlot(m_controller, "##anim", &child.slot) || res;
ImGui::PopID();
}
ImGui::EndTable();
}
if (ImGuiEx::IconButton(ICON_FA_PLUS_CIRCLE, "Add")) {
m_children.emplace();
if(m_children.size() > 1) {
m_children.back().value = m_children[m_children.size() - 2].value;
}
res = true;
}
if (!m_triangles.empty()) {
float w = maximum(ImGui::GetContentRegionAvail().x, 100.f);
ImGui::InvisibleButton("tmp", ImVec2(w, w));
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 p = ImGui::GetItemRectMin() + ImVec2(4, 4);
ImVec2 s = ImGui::GetItemRectSize() - ImVec2(8, 8);
Vec2 min(FLT_MAX), max(-FLT_MAX);
for (const Blend2DNode::Child& c : m_children) {
min = minimum(min, c.value);
max = maximum(max, c.value);
}
swap(min.y, max.y);
Vec2 inv_range = Vec2(s) / (max - min);
const ImGuiStyle& style = ImGui::GetStyle();
ImU32 lines_color = ImGui::GetColorU32(style.Colors[ImGuiCol_PlotLines]);
ImU32 hovered_color = ImGui::GetColorU32(style.Colors[ImGuiCol_PlotLinesHovered]);
ImU32 fill_color = ImGui::GetColorU32(style.Colors[ImGuiCol_FrameBgActive]);
ImU32 bg_color = ImGui::GetColorU32(style.Colors[ImGuiCol_FrameBg]);
dl->AddRectFilled(p, p + s, bg_color);
for (const Blend2DNode::Triangle& t : m_triangles) {
dl->AddTriangleFilled(p + (m_children[t.a].value - min) * inv_range
, p + (m_children[t.c].value - min) * inv_range
, p + (m_children[t.b].value - min) * inv_range
, fill_color);
}
auto old_flags = dl->Flags;
dl->Flags = dl->Flags & ~ImDrawListFlags_AntiAliasedLines;
for (const Blend2DNode::Triangle& t : m_triangles) {
dl->AddTriangle(p + (m_children[t.a].value - min) * inv_range
, p + (m_children[t.c].value - min) * inv_range
, p + (m_children[t.b].value - min) * inv_range
, lines_color);
}
i32 hovered = -1;
for (const Blend2DNode::Child& ch : m_children) {
ImVec2 p0 = p + (ch.value - min) * inv_range - ImVec2(4, 4);
ImVec2 p1 = p0 + ImVec2(8, 8);
if (ImGui::IsMouseHoveringRect(p0, p1)) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted(m_controller.m_animation_slots[ch.slot].c_str());
ImGui::Text("X = %f", ch.value.x);
ImGui::Text("Y = %f", ch.value.y);
ImGui::EndTooltip();
hovered = i32(&ch - m_children.begin());
}
dl->AddRect(p0, p1, hovered_color);
}
else {
dl->AddRect(p0, p1, lines_color);
}
}
m_hovered_blend2d_child = hovered;
dl->Flags = old_flags;
}
return res;
}
static Vec2 computeCircumcircleCenter(Vec2 a, Vec2 b, Vec2 c) {
Vec2 dab = b - a;
Vec2 dac = c - a;
Vec2 o = (dac * squaredLength(dab) - dab * squaredLength(dac)).ortho() / ((dab.x * dac.y - dab.y * dac.x) * 2.f);
return o + a;
}
// delaunay triangulation
void Blend2DNode::dataChanged(IAllocator& allocator) {
m_triangles.clear();
if (m_children.size() < 3) return;
struct Edge {
u32 a, b;
bool valid = true;
bool operator ==(const Edge& rhs) {
return a == rhs.a && b == rhs.b || a == rhs.b && b == rhs.a;
}
};
StackArray<Edge, 8> edges(allocator);
auto pushTriangle = [&](u32 a, u32 b, u32 c){
Triangle& t = m_triangles.emplace();
t.a = a;
t.b = b;
t.c = c;
t.circumcircle_center = computeCircumcircleCenter(m_children[a].value, m_children[b].value, m_children[c].value);
};
Vec2 min = Vec2(FLT_MAX);
Vec2 max = Vec2(-FLT_MAX);
for (const Child& i : m_children) {
min = minimum(min, i.value);
max = maximum(max, i.value);
}
{
// bounding triangle
Vec2 d = max - min;
float dmax = maximum(d.x, d.y);
Vec2 mid = (max + min) * 0.5f;
m_children.emplace().value = Vec2(mid.x - 20 * dmax, mid.y - dmax);
m_children.emplace().value = Vec2(mid.x, mid.y + 20 * dmax);
m_children.emplace().value = Vec2(mid.x + 20 * dmax, mid.y - dmax);
pushTriangle(m_children.size() - 1, m_children.size() - 2, 0);
pushTriangle(m_children.size() - 2, m_children.size() - 3, 0);
pushTriangle(m_children.size() - 3, m_children.size() - 1, 0);
}
for (u32 ch = 1, c = m_children.size() - 3; ch < c; ++ch) {
Vec2 p = m_children[ch].value;
edges.clear();
for (i32 ti = m_triangles.size() - 1; ti >= 0; --ti) {
const Triangle& t = m_triangles[ti];
Vec2 center = t.circumcircle_center;
if (squaredLength(p - center) > squaredLength(m_children[t.a].value - center)) continue;
edges.push({t.a, t.b});
edges.push({t.b, t.c});
edges.push({t.c, t.a});
m_triangles.swapAndPop(ti);
}
for (i32 i = edges.size() - 1; i > 0; --i) {
for (i32 j = i - 1; j >= 0; --j) {
if (edges[i] == edges[j]) {
edges[i].valid = false;
edges[j].valid = false;
}
}
}
edges.eraseItems([](const Edge& e){ return !e.valid; });
for (Edge& e : edges) {
pushTriangle(e.a, e.b, ch);
}
}
// pop bounding triangle's vertices and remove related triangles
m_children.pop();
m_children.pop();
m_children.pop();
m_triangles.eraseItems([&](const Triangle& t){
const u32 s = (u32)m_children.size();
return t.a >= s || t.b >= s || t.c >= s;
});
}
void Blend2DNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write(m_name);
stream.writeArray(m_children);
}
void Blend2DNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
stream.read(m_name);
stream.readArray(&m_children);
dataChanged(ctrl.m_allocator);
}
Blend1DNode::Blend1DNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
, m_children(allocator)
, m_name("blend1d", allocator)
{}
bool Blend1DNode::propertiesGUI() {
ImGuiEx::Label("Name");
bool res = inputString("##name", &m_name);
if (ImGui::BeginTable("tab", 2, ImGuiTableFlags_Resizable)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Value");
ImGui::TableNextColumn();
ImGui::Text("Slot");
for (Blend1DNode::Child& child : m_children) {
ImGui::TableNextColumn();
ImGui::PushID(&child);
ImGui::SetNextItemWidth(-1);
res = ImGui::InputFloat("##val", &child.value) || res;
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1);
res = editSlot(m_controller, "##anim", &child.slot) || res;
ImGui::PopID();
}
ImGui::EndTable();
}
if (ImGui::Button(ICON_FA_PLUS_CIRCLE)) {
m_children.emplace();
if(m_children.size() > 1) {
m_children.back().value = m_children[m_children.size() - 2].value;
}
res = true;
}
return res;
}
bool Blend1DNode::onGUI() {
inputSlot(ImGuiEx::PinShape::SQUARE);
outputSlot();
ImGuiEx::TextUnformatted(m_name.c_str());
return false;
}
anim::Node* Blend1DNode::compile(anim::Controller& controller) {
UniquePtr<anim::Blend1DNode> node = UniquePtr<anim::Blend1DNode>::create(controller.m_allocator, controller.m_allocator);
m_children.copyTo(node->m_children);
ValueNode* val = castToValueNode(getInput(0));
if (!val) return nullptr;
node->m_value = (anim::ValueNode*)val->compile(controller);
if (!node->m_value) return nullptr;
return node.detach();
}
void Blend1DNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write(m_name);
stream.write((u32)m_children.size());
stream.write(m_children.begin(), m_children.byte_size());
}
void Blend1DNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
stream.read(m_name);
u32 count;
stream.read(count);
m_children.resize(count);
stream.read(m_children.begin(), m_children.byte_size());
}
anim::Node* AnimationNode::compile(anim::Controller& controller) {
anim::AnimationNode* node = LUMIX_NEW(controller.m_allocator, anim::AnimationNode);
node->m_flags = m_flags;
node->m_slot = m_slot;
return node;
}
bool AnimationNode::propertiesGUI() {
ImGuiEx::Label("Slot");
bool res = editSlot(m_controller, "##slot", &m_slot);
ImGuiEx::Label("Looping");
bool loop = m_flags && Flags::LOOPED;
if (ImGui::Checkbox("##loop", &loop)) {
if (loop) m_flags = m_flags | Flags::LOOPED;
else m_flags = m_flags & ~Flags::LOOPED;
res = true;
}
return res;
}
bool AnimationNode::onGUI() {
outputSlot();
if (m_slot < (u32)m_controller.m_animation_slots.size()) {
ImGui::TextUnformatted(ICON_FA_PLAY);
ImGui::SameLine();
ImGui::TextUnformatted(m_controller.m_animation_slots[m_slot].c_str());
}
else {
ImGui::TextUnformatted(ICON_FA_PLAY " Animation");
}
return false;
}
AnimationNode::AnimationNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
{}
void AnimationNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write(m_slot);
stream.write(m_flags);
}
void AnimationNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
stream.read(m_slot);
stream.read(m_flags);
}
LayersNode::Layer::Layer(IAllocator& allocator)
: name(allocator)
{
}
LayersNode::LayersNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
, m_layers(allocator)
, m_allocator(allocator)
{
}
LayersNode::~LayersNode() {
for (Layer& l : m_layers) {
LUMIX_DELETE(m_allocator, l.node);
}
}
void LayersNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write((u32)m_layers.size());
for (const Layer& layer : m_layers) {
stream.writeString(layer.name);
stream.write(layer.mask);
stream.write(layer.node->type());
layer.node->serialize(stream);
}
}
anim::Node* LayersNode::compile(anim::Controller& controller) {
ASSERT(false);
return false;
}
void LayersNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
u32 c;
stream.read(c);
for (u32 i = 0; i < c; ++i) {
Layer& layer = m_layers.emplace(m_allocator);
layer.name = stream.readString();
stream.read(layer.mask);
anim::NodeType type;
stream.read(type);
layer.node = (PoseNode*)Node::create(this, type, m_controller, m_allocator);
layer.node->deserialize(stream, ctrl, version);
}
}
bool InputNode::onGUI() {
outputSlot(ImGuiEx::PinShape::SQUARE);
ImGui::TextUnformatted(ICON_FA_SIGN_IN_ALT);
ImGui::SameLine();
ImGui::TextUnformatted(m_controller.m_inputs[m_input_index].name);
return false;
}
bool InputNode::propertiesGUI() {
return editInput("Input", &m_input_index, m_controller);
}
anim::Node* InputNode::compile(anim::Controller& controller) {
if (m_input_index >= (u32)m_controller.m_inputs.size()) return nullptr;
anim::InputNode* node = LUMIX_NEW(controller.m_allocator, anim::InputNode);
node->m_input_index = m_input_index;
return node;
}
InputNode::InputNode(Node* parent, Controller& controller, IAllocator& allocator)
: ValueNode(parent, controller, allocator)
{}
void InputNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write(m_input_index);
}
void InputNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
stream.read(m_input_index);
}
OutputNode::OutputNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
{}
bool OutputNode::onGUI() {
inputSlot();
ImGuiEx::TextUnformatted(ICON_FA_SIGN_OUT_ALT " Output");
return false;
}
anim::Node* OutputNode::compile(anim::Controller& controller) {
Node* n = getInput(0);
if (!n) return false;
if (!n->isPoseNode()) return false;
return n->compile(controller);
}
void OutputNode::serialize(OutputMemoryStream& stream) const { Node::serialize(stream); }
void OutputNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) { Node::deserialize(stream, ctrl, version); }
TreeNode::TreeNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
, m_name("new tree", allocator)
{
LUMIX_NEW(m_allocator, OutputNode)(this, controller, m_allocator);
}
anim::Node* TreeNode::compile(anim::Controller& controller) {
if (m_nodes.empty() || m_nodes[0]->type() != anim::NodeType::OUTPUT) return false;
return m_nodes[0]->compile(controller);
}
bool TreeNode::propertiesGUI() {
ImGuiEx::Label("Name");
return inputString("##name", &m_name);
}
bool TreeNode::onGUI() {
outputSlot();
ImGui::TextUnformatted(ICON_FA_TREE);
ImGui::SameLine();
ImGui::TextUnformatted(m_name.c_str());
return false;
}
void TreeNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
LUMIX_DELETE(m_allocator, m_nodes[0]);
m_nodes.clear();
Node::deserialize(stream, ctrl, version);
stream.read(m_name);
}
void TreeNode::serialize(OutputMemoryStream& stream) const { Node::serialize(stream); stream.write(m_name); }
bool SelectNode::propertiesGUI() {
float node_blend_length = m_blend_length.seconds();
ImGuiEx::Label("Blend length");
if (ImGui::DragFloat("##bl", &node_blend_length)) {
m_blend_length = Time::fromSeconds(node_blend_length);
return true;
}
return false;
}
bool SelectNode::onGUI() {
ImGuiEx::NodeTitle("Select");
outputSlot();
inputSlot(); ImGui::TextUnformatted("Value");
bool changed = false;
for (u32 i = 0; i < m_options_count; ++i) {
inputSlot();
ImGui::PushID(i);
if (ImGuiEx::IconButton(ICON_FA_TIMES_CIRCLE, "Remove")) {
--m_options_count;
for (i32 link_idx = m_parent->m_links.size() - 1; link_idx >= 0; --link_idx) {
NodeEditorLink& link = m_parent->m_links[link_idx];
if (link.getToNode() != m_id) continue;
if (link.getToPin() == i + 1) {
m_parent->m_links.erase(link_idx);
}
else if (link.getToPin() > i + 1) {
link.to = link.getToNode() | (link.getToPin() << 16);
}
}
changed = true;
}
ImGui::PopID();
ImGui::SameLine();
ImGui::Text("Option %d", i);
}
inputSlot(); ImGui::TextUnformatted("New option");
if (getInput(1 + m_options_count)) {
++m_options_count;
changed = true;
}
return changed;
}
SelectNode::SelectNode(Node* parent, Controller& controller, IAllocator& allocator)
: PoseNode(parent, controller, allocator)
{}
anim::Node* SelectNode::compile(anim::Controller& controller) {
if (m_options_count == 0) return nullptr;
ValueNode* value_node = castToValueNode(getInput(0));
// TODO make sure value_node returns i32
if (!value_node) return nullptr;
UniquePtr<anim::SelectNode> node = UniquePtr<anim::SelectNode>::create(controller.m_allocator, controller.m_allocator);
node->m_blend_length = m_blend_length;
node->m_value = (anim::ValueNode*)value_node->compile(controller);
if (!node->m_value) return nullptr;
node->m_children.resize(m_options_count);
for (u32 i = 0; i < m_options_count; ++i) {
PoseNode* n = castToPoseNode(getInput(i + 1));
if (!n) return nullptr;
node->m_children[i] = (anim::PoseNode*)n->compile(controller);
if (!node->m_children[i]) return nullptr;
}
return node.detach();
}
void SelectNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
Node::deserialize(stream, ctrl, version);
stream.read(m_blend_length);
stream.read(m_options_count);
}
void SelectNode::serialize(OutputMemoryStream& stream) const {
Node::serialize(stream);
stream.write(m_blend_length);
stream.write(m_options_count);
}
void Node::serialize(OutputMemoryStream& stream) const {
stream.write(m_id);
stream.write(m_pos);
stream.writeArray(m_links);
stream.write(m_nodes.size());
for (Node* node : m_nodes) {
stream.write(node->type());
node->serialize(stream);
}
}
void Node::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
stream.read(m_id);
stream.read(m_pos);
stream.readArray(&m_links);
u32 count;
stream.read(count);
m_nodes.reserve(count);
for (u32 i = 0; i < count; ++i) {
anim::NodeType type;
stream.read(type);
Node* child = Node::create(this, type, m_controller, m_allocator);
child->deserialize(stream, ctrl, version);
}
}
void Node::inputSlot(ImGuiEx::PinShape shape) {
ImGuiEx::Pin(m_id | (u32(m_input_counter) << 16), true, shape);
++m_input_counter;
}
Node* Node::getInput(u32 idx) const {
if (!m_parent) return nullptr;
for (const NodeEditorLink& link : m_parent->m_links) {
if (link.getToNode() == m_id && link.getToPin() == idx) {
for (Node* n : m_parent->m_nodes) {
if (link.getFromNode() == n->m_id) return n;
}
ASSERT(false);
}
}
return nullptr;
}
void Node::outputSlot(ImGuiEx::PinShape shape) {
ImGuiEx::Pin(m_id | (u32(m_output_counter) << 16) | OUTPUT_FLAG, false, shape);
++m_output_counter;
}
bool Node::nodeGUI() {
m_input_counter = 0;
m_output_counter = 0;
const ImVec2 old_pos = m_pos;
ImGuiEx::BeginNode(m_id, m_pos, &m_selected);
bool res = onGUI();
if (m_error.length() > 0) {
ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0xff, 0, 0, 0xff));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 4);
}
else if (!m_reachable) {
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_TableBorderLight));
}
ImGuiEx::EndNode();
if (m_error.length() > 0) {
ImDrawList* dl = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetItemRectMax() - ImGui::GetStyle().FramePadding;
dl->AddText(p, IM_COL32(0xff, 0, 0, 0xff), ICON_FA_EXCLAMATION_TRIANGLE);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", m_error.c_str());
}
else if (!m_reachable) {
ImGui::PopStyleColor();
}
return res;
}
Node* Node::create(Node* parent, Type type, Controller& controller, IAllocator& allocator) {
switch (type) {
case anim::NodeType::ANIMATION: return LUMIX_NEW(allocator, AnimationNode)(parent, controller, allocator);
case anim::NodeType::BLEND1D: return LUMIX_NEW(allocator, Blend1DNode)(parent, controller, allocator);
case anim::NodeType::BLEND2D: return LUMIX_NEW(allocator, Blend2DNode)(parent, controller, allocator);
case anim::NodeType::LAYERS: return LUMIX_NEW(allocator, LayersNode)(parent, controller, allocator);
case anim::NodeType::SELECT: return LUMIX_NEW(allocator, SelectNode)(parent, controller, allocator);
case anim::NodeType::TREE: return LUMIX_NEW(allocator, TreeNode)(parent, controller, allocator);
case anim::NodeType::OUTPUT: return LUMIX_NEW(allocator, OutputNode)(parent, controller, allocator);
case anim::NodeType::INPUT: return LUMIX_NEW(allocator, InputNode)(parent, controller, allocator);
case anim::NodeType::NONE: ASSERT(false); return nullptr;
}
ASSERT(false);
return nullptr;
}
} // namespace Lumix::anim

View file

@ -0,0 +1,220 @@
#pragma once
#include "animation/animation.h"
#include "animation/nodes.h"
#include "controller_editor.h"
#include "engine/array.h"
#include "engine/math.h"
#include "engine/stream.h"
#include "engine/string.h"
#include "editor/utils.h"
namespace Lumix {
struct Model;
struct Pose;
namespace anim_editor {
bool editSlot(const Controller& controller, const char* str_id, u32* slot);
using RuntimeContext = anim::RuntimeContext;
using Type = anim::NodeType;
struct Node : NodeEditorNode {
Node(Node* parent, Controller& controller, IAllocator& allocator)
: m_parent(parent)
, m_error(allocator)
, m_nodes(allocator)
, m_links(allocator)
, m_controller(controller)
, m_allocator(allocator)
{
m_id = ++m_controller.m_id_generator;
if (parent) parent->m_nodes.push(this);
}
virtual ~Node() {}
virtual Type type() const = 0;
virtual void serialize(OutputMemoryStream& stream) const;
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version);
virtual bool onGUI() { return false; }
virtual bool propertiesGUI() { return false; }
virtual anim::Node* compile(anim::Controller& controller) = 0;
virtual bool isValueNode() const { return false; }
virtual bool isPoseNode() const { return false; }
bool nodeGUI() override;
void inputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE);
void outputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE);
Node* getInput(u32 idx) const;
static Node* create(Node* parent, Type type, Controller& controller, IAllocator& allocator);
IAllocator& m_allocator;
Node* m_parent;
u8 m_input_counter;
u8 m_output_counter;
bool m_selected = false;
bool m_reachable = false;
String m_error;
Array<NodeEditorLink> m_links;
Array<Node*> m_nodes;
Controller& m_controller;
};
struct PoseNode : Node {
PoseNode(Node* parent, Controller& controller, IAllocator& allocator) : Node(parent, controller, allocator) {}
bool isPoseNode() const override { return true; }
};
struct ValueNode : Node {
ValueNode(Node* parent, Controller& controller, IAllocator& allocator) : Node(parent, controller, allocator) {}
bool isValueNode() const override { return true; }
};
struct InputNode : ValueNode {
InputNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::INPUT; }
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
anim::Node* compile(anim::Controller& controller) override;
bool onGUI() override;
bool propertiesGUI() override;
u32 m_input_index = 0;
};
struct OutputNode final : PoseNode {
OutputNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::OUTPUT; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return false; }
bool onGUI() override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
anim::Node* compile(anim::Controller& controller) override;
};
struct TreeNode final : PoseNode {
TreeNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::TREE; }
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return false; }
bool onGUI() override;
bool propertiesGUI() override;
anim::Node* compile(anim::Controller& controller) override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
String m_name;
};
struct SelectNode final : PoseNode {
SelectNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::SELECT; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
anim::Node* compile(anim::Controller& controller) override;
u32 m_options_count = 2;
Time m_blend_length = Time::fromSeconds(0.3f);
};
struct AnimationNode final : PoseNode {
AnimationNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::ANIMATION; }
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
anim::Node* compile(anim::Controller& controller) override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
using Flags = anim::AnimationNode::Flags;
u32 m_slot = 0;
u32 m_flags = Flags::LOOPED;
};
struct Blend2DNode final : PoseNode {
Blend2DNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::BLEND2D; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
anim::Node* compile(anim::Controller& controller) override;
void dataChanged(IAllocator& tmp_allocator);
using Child = anim::Blend2DNode::Child;
using Triangle = anim::Blend2DNode::Triangle;
String m_name;
Array<Child> m_children;
Array<Triangle> m_triangles;
i32 m_hovered_blend2d_child = -1;
};
struct Blend1DNode final : PoseNode {
Blend1DNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return anim::NodeType::BLEND1D; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
anim::Node* compile(anim::Controller& controller) override;
using Child = anim::Blend1DNode::Child;
String m_name;
Array<Child> m_children;
};
struct LayersNode final : PoseNode {
LayersNode(Node* parent, Controller& controller, IAllocator& allocator);
~LayersNode();
Type type() const override { return anim::NodeType::LAYERS; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
anim::Node* compile(anim::Controller& controller) override;
struct Layer {
Layer(IAllocator& allocator);
PoseNode* node = nullptr;
u32 mask = 0;
String name;
};
IAllocator& m_allocator;
Array<Layer> m_layers;
};
} // namespace anim
} // namespace Lumix

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,6 @@
#include "engine/math.h" #include "engine/math.h"
#include "engine/stream.h" #include "engine/stream.h"
#include "engine/string.h" #include "engine/string.h"
#include "editor/utils.h"
namespace Lumix { namespace Lumix {
@ -16,214 +15,81 @@ struct Pose;
namespace anim { namespace anim {
struct Controller; struct Controller;
struct GroupNode;
bool inputSlot(const Controller& controller, const char* str_id, u32* slot); enum class NodeType : u32 {
bool editInput(const char* label, u32* input_index, const Controller& controller); ANIMATION,
BLEND1D,
LAYERS,
struct RuntimeContext { NONE,
RuntimeContext(Controller& controller, IAllocator& allocator); SELECT,
BLEND2D,
void setInput(u32 input_idx, float value); TREE,
void setInput(u32 input_idx, bool value); OUTPUT,
INPUT
union InputValue {
float f;
i32 i32;
bool b;
};
Controller& controller;
Array<InputValue> inputs;
Array<Animation*> animations;
OutputMemoryStream data;
OutputMemoryStream events;
BoneNameHash root_bone_hash;
Time time_delta;
Model* model = nullptr;
InputMemoryStream input_runtime;
}; };
struct Node : NodeEditorNode { struct Node {
enum Type : u32 { static Node* create(NodeType type, Controller& controller);
ANIMATION,
BLEND1D,
LAYERS,
NONE,
SELECT,
BLEND2D,
TREE,
OUTPUT
};
Node(Node* parent, Controller& controller, IAllocator& allocator)
: m_parent(parent)
, m_error(allocator)
, m_nodes(allocator)
, m_links(allocator)
, m_controller(controller)
, m_allocator(allocator)
{
m_id = ++m_controller.m_id_generator;
if (parent) parent->m_nodes.push(this);
}
virtual ~Node() {} virtual ~Node() {}
virtual Type type() const = 0; virtual NodeType type() const = 0;
virtual void serialize(OutputMemoryStream& stream) const = 0;
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) = 0;
};
struct PoseNode : Node {
virtual void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const = 0; virtual void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const = 0;
virtual void enter(RuntimeContext& ctx) = 0; virtual void enter(RuntimeContext& ctx) = 0;
virtual void skip(RuntimeContext& ctx) const = 0; virtual void skip(RuntimeContext& ctx) const = 0;
virtual void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const = 0;
virtual void serialize(OutputMemoryStream& stream) const;
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version);
virtual Time length(const RuntimeContext& ctx) const = 0; virtual Time length(const RuntimeContext& ctx) const = 0;
virtual Time time(const RuntimeContext& ctx) const = 0; virtual Time time(const RuntimeContext& ctx) const = 0;
virtual bool onGUI() { return false; }
virtual bool propertiesGUI() { return false; }
virtual bool compile() { return true; }
bool nodeGUI() override;
void inputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE);
void outputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE);
Node* getInput(u32 idx) const;
static Node* create(Node* parent, Type type, Controller& controller, IAllocator& allocator);
IAllocator& m_allocator;
Node* m_parent;
u8 m_input_counter;
u8 m_output_counter;
bool m_selected = false;
bool m_reachable = false;
String m_error;
Array<NodeEditorLink> m_links;
Array<Node*> m_nodes;
Controller& m_controller;
}; };
struct OutputNode final : Node { struct ValueNode : Node {
OutputNode(Node* parent, Controller& controller, IAllocator& allocator); virtual Value eval(const RuntimeContext& ctx) const = 0;
};
Type type() const override { return OUTPUT; } struct InputNode : ValueNode {
bool hasInputPins() const override { return true; } NodeType type() const override { return anim::NodeType::INPUT; }
bool hasOutputPins() const override { return false; } void serialize(OutputMemoryStream& stream) const override;
bool onGUI() override; void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Value eval(const RuntimeContext& ctx) const override;
u32 m_input_index;
};
struct Blend1DNode final : PoseNode {
Blend1DNode(IAllocator& allocator);
NodeType type() const override { return anim::NodeType::BLEND1D; }
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override; void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override; void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override; void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override;
bool compile() override;
Node* input = nullptr;
};
struct TreeNode final : Node {
TreeNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return TREE; }
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return false; }
bool onGUI() override;
bool propertiesGUI() override;
bool compile() override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override; Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override; Time time(const RuntimeContext& ctx) const override;
String m_name; struct Child {
}; float value = 0;
u32 slot = 0;
struct SelectNode final : Node {
struct RuntimeData {
u32 from;
u32 to;
Time t;
}; };
SelectNode(Node* parent, Controller& controller, IAllocator& allocator); Array<Child> m_children;
ValueNode* m_value;
Type type() const override { return SELECT; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
bool compile() override;
Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override;
u32 getChildIndex(float input_val) const;
Array<float> m_max_values;
Array<Node*> m_inputs;
u32 m_input_index = 0;
Time m_blend_length = Time::fromSeconds(0.3f);
}; };
struct AnimationNode final : Node {
AnimationNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return ANIMATION; }
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
bool compile() override;
struct Blend2DNode final : PoseNode {
Blend2DNode(IAllocator& allocator);
NodeType type() const override { return anim::NodeType::BLEND2D; }
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override; void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override; void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override; void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override; Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override; Time time(const RuntimeContext& ctx) const override;
enum Flags : u32 {
LOOPED = 1 << 0
};
u32 m_slot = 0;
u32 m_flags = LOOPED;
};
struct Blend2DNode final : Node {
Blend2DNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return BLEND2D; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override;
void dataChanged(IAllocator& tmp_allocator);
struct Child { struct Child {
Vec2 value = Vec2(0); Vec2 value = Vec2(0);
u32 slot = 0; u32 slot = 0;
@ -234,68 +100,52 @@ struct Blend2DNode final : Node {
Vec2 circumcircle_center; Vec2 circumcircle_center;
}; };
String m_name;
Array<Child> m_children;
Array<Triangle> m_triangles; Array<Triangle> m_triangles;
u32 m_x_input_index = 0;
u32 m_y_input_index = 0;
i32 m_hovered_blend2d_child = -1;
};
struct Blend1DNode final : Node {
Blend1DNode(Node* parent, Controller& controller, IAllocator& allocator);
Type type() const override { return BLEND1D; }
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override;
struct Child {
float value = 0;
u32 slot = 0;
};
String m_name;
Array<Child> m_children; Array<Child> m_children;
u32 m_input_index = 0; ValueNode* m_x_value;
ValueNode* m_y_value;
}; };
struct LayersNode final : Node { struct SelectNode final : PoseNode {
LayersNode(Node* parent, Controller& controller, IAllocator& allocator); struct RuntimeData {
~LayersNode(); u32 from;
Type type() const override { return LAYERS; } u32 to;
bool hasInputPins() const override { return true; } Time t;
bool hasOutputPins() const override { return true; } };
SelectNode(IAllocator& allocator);
NodeType type() const override { return anim::NodeType::SELECT; }
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override; void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override; void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override; void skip(RuntimeContext& ctx) const override;
void getPose(RuntimeContext& ctx, float weight, Pose& pose, u32 mask) const override;
void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
Time length(const RuntimeContext& ctx) const override; Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override; Time time(const RuntimeContext& ctx) const override;
struct Layer { Array<PoseNode*> m_children;
Layer(IAllocator& allocator); ValueNode* m_value;
Time m_blend_length;
};
Node* node = nullptr; struct AnimationNode final : PoseNode {
u32 mask = 0; NodeType type() const override { return anim::NodeType::ANIMATION; }
String name; void serialize(OutputMemoryStream& stream) const override;
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const override;
void enter(RuntimeContext& ctx) override;
void skip(RuntimeContext& ctx) const override;
Time length(const RuntimeContext& ctx) const override;
Time time(const RuntimeContext& ctx) const override;
enum Flags : u32 {
LOOPED = 1 << 0
}; };
IAllocator& m_allocator; u32 m_slot = 0;
Array<Layer> m_layers; u32 m_flags = LOOPED;
}; };
} // namespace anim } // namespace anim
} // namespace Lumix } // namespace Lumix