animation system wip
This commit is contained in:
parent
d0923683cc
commit
3393efebdd
12 changed files with 1825 additions and 1504 deletions
|
@ -30,6 +30,7 @@ struct Time {
|
|||
Time operator-(const Time& rhs) const { return Time{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; }
|
||||
Time operator%(const Time& rhs) const { return Time{value % rhs.value}; }
|
||||
|
@ -43,9 +44,9 @@ private:
|
|||
|
||||
struct BoneMask
|
||||
{
|
||||
BoneMask(IAllocator& allocator) : bones(allocator) {}
|
||||
BoneMask(IAllocator& allocator) : bones(allocator), name(allocator) {}
|
||||
BoneMask(BoneMask&& rhs) = default;
|
||||
StaticString<32> name;
|
||||
String name;
|
||||
HashMap<BoneNameHash, u8> bones;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,8 +28,7 @@ struct Animation;
|
|||
struct Engine;
|
||||
struct World;
|
||||
|
||||
enum class AnimationModuleVersion
|
||||
{
|
||||
enum class AnimationModuleVersion {
|
||||
USE_ROOT_MOTION,
|
||||
|
||||
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 {
|
||||
auto iter = m_animator_map.find(entity);
|
||||
Animator& animator = m_animators[iter.value()];
|
||||
|
@ -161,7 +153,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!iter.isValid()) return;
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
|
@ -175,8 +167,8 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!iter.isValid()) return;
|
||||
|
||||
Animator& animator = m_animators[iter.value()];
|
||||
if (animator.resource->m_inputs[input_idx].type == anim::Controller::Input::I32) {
|
||||
animator.ctx->inputs[input_idx].i32 = value;
|
||||
if (animator.resource->m_inputs[input_idx].type == anim::Value::I32) {
|
||||
animator.ctx->inputs[input_idx].s32 = value;
|
||||
}
|
||||
else {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
|
@ -524,7 +516,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!animator.ctx) 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;
|
||||
}
|
||||
|
@ -534,7 +526,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!animator.ctx) 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;
|
||||
}
|
||||
|
@ -544,7 +536,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!animator.ctx) return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -554,7 +546,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!animator.ctx) return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -564,9 +556,9 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
if (!animator.ctx) return 0;
|
||||
|
||||
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 (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
|
||||
|
@ -631,11 +623,11 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
|
||||
animator.ctx->model = model;
|
||||
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);
|
||||
|
||||
model->getRelativePose(*pose);
|
||||
animator.resource->getPose(*animator.ctx, *pose);
|
||||
evalBlendStack(*animator.ctx, *pose);
|
||||
|
||||
for (Animator::IK& ik : animator.inverse_kinematics) {
|
||||
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)
|
||||
{
|
||||
u32 indices[anim::Controller::IK::MAX_BONES_COUNT];
|
||||
LocalRigidTransform transforms[anim::Controller::IK::MAX_BONES_COUNT];
|
||||
Vec3 old_pos[anim::Controller::IK::MAX_BONES_COUNT];
|
||||
float len[anim::Controller::IK::MAX_BONES_COUNT - 1];
|
||||
enum { MAX_BONES_COUNT = 32 };
|
||||
u32 indices[MAX_BONES_COUNT];
|
||||
LocalRigidTransform transforms[MAX_BONES_COUNT];
|
||||
Vec3 old_pos[MAX_BONES_COUNT];
|
||||
float len[MAX_BONES_COUNT - 1];
|
||||
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]);
|
||||
if (!iter.isValid()) return;
|
||||
|
||||
|
@ -692,7 +687,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
}
|
||||
|
||||
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]]};
|
||||
transforms[i] = parent_tr * tr;
|
||||
old_pos[i] = transforms[i].pos;
|
||||
|
@ -710,22 +705,22 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
target = transforms[0].pos + to_target * len_sum;
|
||||
}
|
||||
|
||||
for (int iteration = 0; iteration < res_ik.max_iterations; ++iteration) {
|
||||
transforms[res_ik.bones_count - 1].pos = target;
|
||||
for (u32 iteration = 0; iteration < res_ik.max_iterations; ++iteration) {
|
||||
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));
|
||||
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));
|
||||
transforms[i].pos = transforms[i - 1].pos + dir * len[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// 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 new_d = transforms[i + 1].pos - transforms[i].pos;
|
||||
|
||||
|
@ -734,15 +729,15 @@ struct AnimationModuleImpl final : AnimationModule {
|
|||
}
|
||||
|
||||
// convert from object space to bone space
|
||||
LocalRigidTransform ik_out[anim::Controller::IK::MAX_BONES_COUNT];
|
||||
for (int i = res_ik.bones_count - 1; i > 0; --i) {
|
||||
LocalRigidTransform ik_out[MAX_BONES_COUNT];
|
||||
for (i32 i = bones_count - 1; i > 0; --i) {
|
||||
transforms[i] = transforms[i - 1].inverted() * transforms[i];
|
||||
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[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) {
|
||||
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]];
|
||||
|
||||
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];
|
||||
pose.positions[idx] = lerp(pose.positions[idx], ik_out[i].pos, w);
|
||||
pose.rotations[idx] = nlerp(pose.rotations[idx], ik_out[i].rot, w);
|
||||
|
|
|
@ -22,7 +22,6 @@ struct AnimationModule : IModule {
|
|||
static UniquePtr<AnimationModule> create(Engine& engine, ISystem& system, World& world, struct IAllocator& allocator);
|
||||
static void reflect(Engine& engine);
|
||||
|
||||
virtual const struct OutputMemoryStream* getEventStream(EntityRef e) const = 0;
|
||||
virtual struct Path getPropertyAnimation(EntityRef entity) = 0;
|
||||
virtual void setPropertyAnimation(EntityRef entity, const Path& path) = 0;
|
||||
virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "engine/hash.h"
|
||||
#include "engine/log.h"
|
||||
#include "engine/resource_manager.h"
|
||||
#include "engine/stack_array.h"
|
||||
#include "renderer/model.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)
|
||||
: Resource(path, resource_manager, allocator)
|
||||
, m_allocator(allocator)
|
||||
, m_animation_slots(allocator)
|
||||
, m_animation_entries(allocator)
|
||||
, m_inputs(allocator)
|
||||
, m_ik(allocator)
|
||||
, m_bone_masks(allocator)
|
||||
{
|
||||
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator);
|
||||
m_root->m_name = "Root";
|
||||
}
|
||||
{}
|
||||
|
||||
Controller::~Controller() {
|
||||
LUMIX_DELETE(m_allocator, m_root);
|
||||
ASSERT(isEmpty());
|
||||
}
|
||||
|
||||
void Controller::clear() {
|
||||
unload();
|
||||
}
|
||||
|
||||
void Controller::unload() {
|
||||
for (const AnimationEntry& entry : m_animation_entries) {
|
||||
if (entry.animation) entry.animation->decRefCount();
|
||||
}
|
||||
m_animation_entries.clear();
|
||||
m_animation_slots.clear();
|
||||
m_bone_masks.clear();
|
||||
m_inputs.clear();
|
||||
LUMIX_DELETE(m_allocator, m_root);
|
||||
|
@ -55,11 +48,13 @@ void Controller::destroyRuntime(RuntimeContext& ctx) {
|
|||
}
|
||||
|
||||
RuntimeContext* Controller::createRuntime(u32 anim_set) {
|
||||
if (!m_compiled) return nullptr;
|
||||
RuntimeContext* ctx = LUMIX_NEW(m_allocator, RuntimeContext)(*this, m_allocator);
|
||||
ctx->inputs.resize(m_inputs.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());
|
||||
for (AnimationEntry& anim : m_animation_entries) {
|
||||
if (anim.set == anim_set) {
|
||||
|
@ -71,27 +66,18 @@ RuntimeContext* Controller::createRuntime(u32 anim_set) {
|
|||
}
|
||||
|
||||
void Controller::update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const {
|
||||
if (!m_compiled) return;
|
||||
ASSERT(&ctx.controller == this);
|
||||
// TODO better allocation strategy
|
||||
const Span<u8> mem = ctx.data.releaseOwnership();
|
||||
ctx.data.reserve(mem.length());
|
||||
ctx.events.clear();
|
||||
ctx.blendstack.clear();
|
||||
ctx.input_runtime.set(mem.begin(), mem.length());
|
||||
if (m_root) m_root->update(ctx, root_motion);
|
||||
|
||||
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 {
|
||||
|
||||
u32 magic = MAGIC;
|
||||
ControllerVersion version = ControllerVersion::LATEST;
|
||||
|
||||
|
@ -101,22 +87,22 @@ struct Header {
|
|||
void Controller::serialize(OutputMemoryStream& stream) {
|
||||
Header header;
|
||||
stream.write(header);
|
||||
stream.write(m_flags);
|
||||
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(m_animation_slots_count);
|
||||
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 ? entry.animation->getPath() : Path());
|
||||
}
|
||||
stream.write(m_ik);
|
||||
stream.write(m_ik_count);
|
||||
stream.write(m_ik.size());
|
||||
for (const IK& ik : m_ik) {
|
||||
stream.write(ik.max_iterations);
|
||||
stream.writeArray(ik.bones);
|
||||
}
|
||||
|
||||
stream.write(m_root->type());
|
||||
m_root->serialize(stream);
|
||||
}
|
||||
|
||||
|
@ -133,22 +119,9 @@ bool Controller::deserialize(InputMemoryStream& stream) {
|
|||
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.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);
|
||||
const char* tmp = stream.readString();
|
||||
slot = tmp;
|
||||
}
|
||||
stream.read(m_animation_slots_count);
|
||||
|
||||
const u32 entries_count = stream.read<u32>();
|
||||
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;
|
||||
}
|
||||
|
||||
stream.read(m_ik);
|
||||
stream.read(m_ik_count);
|
||||
m_root = LUMIX_NEW(m_allocator, TreeNode)(nullptr, *this, m_allocator);
|
||||
m_root->m_name = "Root";
|
||||
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);
|
||||
}
|
||||
|
||||
NodeType type;
|
||||
stream.read(type);
|
||||
m_root = (PoseNode*)Node::create(type, *this);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "animation.h"
|
||||
#include "engine/flag_set.h"
|
||||
#include "engine/hash.h"
|
||||
#include "engine/resource.h"
|
||||
|
@ -13,15 +14,59 @@ struct Pose;
|
|||
|
||||
namespace anim {
|
||||
|
||||
struct Node;
|
||||
struct RuntimeContext;
|
||||
struct Value {
|
||||
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 {
|
||||
FIRST_SUPPORTED = 4,
|
||||
FIRST,
|
||||
|
||||
LATEST
|
||||
};
|
||||
|
||||
void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose);
|
||||
|
||||
struct Controller final : Resource {
|
||||
Controller(const Path& path, ResourceManager& resource_manager, IAllocator& allocator);
|
||||
~Controller();
|
||||
|
@ -32,8 +77,6 @@ struct Controller final : Resource {
|
|||
RuntimeContext* createRuntime(u32 anim_set);
|
||||
void destroyRuntime(RuntimeContext& ctx);
|
||||
void update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const;
|
||||
void getPose(RuntimeContext& ctx, struct Pose& pose);
|
||||
void clear();
|
||||
|
||||
ResourceType getType() const override { return TYPE; }
|
||||
static const ResourceType TYPE;
|
||||
|
@ -41,40 +84,28 @@ struct Controller final : Resource {
|
|||
struct AnimationEntry {
|
||||
u32 set;
|
||||
u32 slot;
|
||||
Animation* animation;
|
||||
Animation* animation = nullptr;
|
||||
};
|
||||
|
||||
struct Input {
|
||||
enum Type {
|
||||
FLOAT,
|
||||
BOOL,
|
||||
I32
|
||||
};
|
||||
|
||||
Type type;
|
||||
Value::Type type;
|
||||
StaticString<32> name;
|
||||
};
|
||||
|
||||
struct IK {
|
||||
IK(IAllocator& allocator) : bones(allocator) {}
|
||||
u32 max_iterations = 5;
|
||||
Array<BoneNameHash> bones;
|
||||
};
|
||||
|
||||
IAllocator& m_allocator;
|
||||
struct TreeNode* m_root = nullptr;
|
||||
struct PoseNode* 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;
|
||||
enum class Flags : u32 {
|
||||
UNUSED_FLAG = 1 << 0
|
||||
};
|
||||
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;
|
||||
Array<IK> m_ik;
|
||||
u32 m_animation_slots_count = 0;
|
||||
BoneNameHash m_root_motion_bone;
|
||||
|
||||
private:
|
||||
void unload() override;
|
||||
|
|
|
@ -642,7 +642,7 @@ struct StudioAppPlugin : StudioApp::IPlugin {
|
|||
|
||||
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; }
|
||||
|
@ -662,7 +662,7 @@ struct StudioAppPlugin : StudioApp::IPlugin {
|
|||
AnimablePropertyGridPlugin m_animable_plugin;
|
||||
AnimationAssetBrowserPlugin m_animation_plugin;
|
||||
PropertyAnimationPlugin m_prop_anim_plugin;
|
||||
UniquePtr<anim::ControllerEditor> m_anim_editor;
|
||||
UniquePtr<anim_editor::ControllerEditor> m_anim_editor;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#include <imgui/imgui.h>
|
||||
|
||||
#include "animation/animation_module.h"
|
||||
#include "animation/controller.h"
|
||||
#include "controller_editor.h"
|
||||
#include "editor_nodes.h"
|
||||
#include "editor/asset_browser.h"
|
||||
#include "editor/asset_compiler.h"
|
||||
#include "editor/editor_asset.h"
|
||||
|
@ -18,12 +20,133 @@
|
|||
#include "renderer/editor/world_viewer.h"
|
||||
#include "renderer/model.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 EditorWindow : AssetEditorWindow, NodeEditor {
|
||||
|
@ -36,12 +159,12 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
virtual void endCategory() {}
|
||||
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 {
|
||||
Node* create(EditorWindow& editor) const override {
|
||||
return Node::create(editor.m_current_node, type, editor.m_controller, editor.m_controller.m_allocator);
|
||||
}
|
||||
Node::Type type;
|
||||
anim::NodeType type;
|
||||
} creator;
|
||||
creator.type = type;
|
||||
return visitType(label, creator, shortcut);
|
||||
|
@ -52,7 +175,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
if (visitor.beginCategory("Animations")) {
|
||||
struct : INodeTypeVisitor::INodeCreator {
|
||||
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;
|
||||
return n;
|
||||
}
|
||||
|
@ -65,11 +188,29 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
visitor.endCategory();
|
||||
}
|
||||
visitor
|
||||
.visitType(Node::BLEND1D, "Blend 1D", '1')
|
||||
.visitType(Node::BLEND2D, "Blend 2D", '2')
|
||||
.visitType(Node::LAYERS, "Layers", 'L')
|
||||
.visitType(Node::SELECT, "Select", 'S')
|
||||
.visitType(Node::TREE, "Tree", 'T');
|
||||
.visitType(anim::NodeType::BLEND1D, "Blend 1D", '1')
|
||||
.visitType(anim::NodeType::BLEND2D, "Blend 2D", '2');
|
||||
|
||||
if (visitor.beginCategory("Inputs")) {
|
||||
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)
|
||||
|
@ -78,14 +219,12 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
, m_allocator(allocator)
|
||||
, m_app(app)
|
||||
, m_plugin(plugin)
|
||||
, m_copy_buffer(allocator)
|
||||
, m_controller(path, *app.getEngine().getResourceManager().get(Controller::TYPE), allocator)
|
||||
, m_controller(path, allocator)
|
||||
, m_viewer(app)
|
||||
{
|
||||
FileSystem& fs = m_app.getEngine().getFileSystem();
|
||||
OutputMemoryStream data(m_allocator);
|
||||
if (fs.getContentSync(Path(path), data)) {
|
||||
ResourceManager* res_manager = m_app.getEngine().getResourceManager().get(Controller::TYPE);
|
||||
InputMemoryStream str(data);
|
||||
if (m_controller.deserialize(str)) {
|
||||
m_current_node = m_controller.m_root;
|
||||
|
@ -214,12 +353,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
ImGui::Separator();
|
||||
|
@ -229,7 +362,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
ImGuiEx::Label("Preview model");
|
||||
if (m_app.getAssetBrowser().resourceInput("model", model_path, Model::TYPE)) {
|
||||
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);
|
||||
if (model && model->isReady()) {
|
||||
|
@ -255,15 +388,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
ImGui::Checkbox("##fm", &m_viewer.m_follow_mesh);
|
||||
ImGuiEx::Label("Playback speed");
|
||||
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")) {
|
||||
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 {
|
||||
anim_module->updateAnimator(*m_viewer.m_mesh, m_app.getEngine().getLastTimeDelta() * m_playback_speed);
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
m_viewer.gui();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
void debuggerUI() {
|
||||
|
@ -328,17 +448,17 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
|
||||
const ComponentType animator_type = reflection::getComponentType("animator");
|
||||
AnimationModule* module = (AnimationModule*)world.getModule(animator_type);
|
||||
Controller* ctrl = module->getAnimatorController(entity);
|
||||
anim::Controller* ctrl = module->getAnimatorController(entity);
|
||||
if (!ctrl) {
|
||||
ImGui::TextUnformatted("Selected entity does not have resource assigned in animator component");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Controller::Input& input : ctrl->m_inputs) {
|
||||
for (const anim::Controller::Input& input : ctrl->m_inputs) {
|
||||
ImGui::PushID(&input);
|
||||
const u32 idx = u32(&input - ctrl->m_inputs.begin());
|
||||
switch (input.type) {
|
||||
case Controller::Input::Type::FLOAT: {
|
||||
case anim::Value::FLOAT: {
|
||||
float val = module->getAnimatorFloatInput(entity, idx);
|
||||
|
||||
ImGuiEx::Label(input.name);
|
||||
|
@ -362,7 +482,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Controller::Input::BOOL: {
|
||||
case anim::Value::BOOL: {
|
||||
bool val = module->getAnimatorBoolInput(entity, idx);
|
||||
ImGuiEx::Label(input.name);
|
||||
if (ImGui::Checkbox("##i", &val)) {
|
||||
|
@ -370,7 +490,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Controller::Input::I32: {
|
||||
case anim::Value::I32: {
|
||||
i32 val = module->getAnimatorI32Input(entity, idx);
|
||||
ImGuiEx::Label(input.name);
|
||||
if (ImGui::DragInt("##i", (int*)&val, 1, 0, 0x7ffFFff)) {
|
||||
|
@ -382,21 +502,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (m_controller.m_ik_count > 0) {
|
||||
for (u32 i = 0; i < m_controller.m_ik_count; ++i) {
|
||||
if (ImGui::TreeNode((const void*)(uintptr)i, "IK chain %d", i)) {
|
||||
ImGui::Checkbox("Enabled", &m_ik_debug[i].enabled);
|
||||
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) {
|
||||
auto* render_module = (RenderModule*)world.getModule("renderer");
|
||||
Transform tr = world.getTransform(entity);
|
||||
render_module->addDebugCross(tr.transform(m_ik_debug[i].target), 0.25f, Color::RED);
|
||||
}
|
||||
#if 0
|
||||
for (Controller::IK& ik : m_controller.m_ik) {
|
||||
const u32 i = u32(&ik - m_controller.m_ik.begin());
|
||||
if (ImGui::TreeNode(&ik, "IK chain %d", i)) {
|
||||
ImGui::Checkbox("Enabled", &m_ik_debug[i].enabled);
|
||||
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) {
|
||||
auto* render_module = (RenderModule*)world.getModule("renderer");
|
||||
Transform tr = world.getTransform(entity);
|
||||
render_module->addDebugCross(tr.transform(m_ik_debug[i].target), 0.25f, Color::RED);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void deleteSelectedNodes() {
|
||||
|
@ -438,42 +560,36 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
m_model = m_app.getEngine().getResourceManager().load<Model>(model_path);
|
||||
}
|
||||
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);
|
||||
if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) {
|
||||
if (i < m_controller.m_ik_count - 1) {
|
||||
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;
|
||||
m_controller.m_ik.swapAndPop(i);
|
||||
ImGui::PopID();
|
||||
continue;
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::TreeNode((const void*)(uintptr)i, "Chain %d", i)) {
|
||||
Controller::IK& ik = m_controller.m_ik[i];
|
||||
ASSERT(ik.bones_count > 0);
|
||||
Controller::IK& ik = m_controller.m_ik[i];
|
||||
if (ImGui::TreeNode(&ik, "Chain %d", i)) {
|
||||
ASSERT(!ik.bones.empty());
|
||||
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");
|
||||
if (ImGui::BeginCombo("##leaf", leaf_iter.isValid() ? m_model->getBone(leaf_iter.value()).name.c_str() : "N/A")) {
|
||||
bool selected = false;
|
||||
for (u32 j = 0; j < bones_count; ++j) {
|
||||
const char* bone_name = m_model->getBone(j).name.c_str();
|
||||
if (ImGui::Selectable(bone_name)) {
|
||||
ik.bones_count = 1;
|
||||
ik.bones[0] = BoneNameHash(bone_name);
|
||||
ik.bones.clear();
|
||||
ik.bones.push(BoneNameHash(bone_name));
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
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]);
|
||||
if (iter.isValid()) {
|
||||
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]);
|
||||
if (iter.isValid()) {
|
||||
if (ik.bones_count < lengthOf(ik.bones)) {
|
||||
const int parent_idx = m_model->getBone(iter.value()).parent_idx;
|
||||
if (parent_idx >= 0) {
|
||||
const char* bone_name = m_model->getBone(parent_idx).name.c_str();
|
||||
const StaticString<64> add_label("Add ", bone_name);
|
||||
if (ImGui::Button(add_label)) {
|
||||
memmove(&ik.bones[1], &ik.bones[0], sizeof(ik.bones[0]) * ik.bones_count);
|
||||
ik.bones[0] = BoneNameHash(bone_name);
|
||||
++ik.bones_count;
|
||||
saveUndo(true);
|
||||
}
|
||||
const int parent_idx = m_model->getBone(iter.value()).parent_idx;
|
||||
if (parent_idx >= 0) {
|
||||
const char* bone_name = m_model->getBone(parent_idx).name.c_str();
|
||||
const StaticString<64> add_label("Add ", bone_name);
|
||||
if (ImGui::Button(add_label)) {
|
||||
ik.bones.insert(0, BoneNameHash(bone_name));
|
||||
saveUndo(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::Text("IK is full");
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::Text("Unknown bone.");
|
||||
}
|
||||
if (ik.bones_count > 1) {
|
||||
if (ik.bones.size() > 1) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Pop")) {
|
||||
memmove(&ik.bones[0], &ik.bones[1], sizeof(ik.bones[0]) * ik.bones_count - 1);
|
||||
--ik.bones_count;
|
||||
ik.bones.erase(0);
|
||||
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)) {
|
||||
m_controller.m_ik[m_controller.m_ik_count].bones_count = 1;
|
||||
m_controller.m_ik[m_controller.m_ik_count].bones[0] = BoneNameHash();
|
||||
++m_controller.m_ik_count;
|
||||
if (ImGui::Button(ICON_FA_PLUS_CIRCLE)) {
|
||||
Controller::IK& ik = m_controller.m_ik.emplace(m_allocator);
|
||||
ik.bones.push(BoneNameHash());
|
||||
saveUndo(true);
|
||||
}
|
||||
}
|
||||
|
@ -563,10 +670,10 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
ImGui::PopID();
|
||||
|
||||
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");
|
||||
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) {
|
||||
const char* bone_name = m_model->getBone(i).name.c_str();
|
||||
const BoneNameHash bone_name_hash(bone_name);
|
||||
|
@ -629,7 +736,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
if (Path::hasExtension(subres, "ani")) {
|
||||
Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace();
|
||||
ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager();
|
||||
entry.animation = res_manager.load<Animation>(Path(path));
|
||||
entry.animation = path;
|
||||
entry.set = 0;
|
||||
entry.slot = m_controller.m_animation_slots.size();
|
||||
|
||||
|
@ -645,7 +752,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
if (Path::hasExtension(subres, "ani")) {
|
||||
Controller::AnimationEntry& entry = m_controller.m_animation_entries.emplace();
|
||||
ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager();
|
||||
entry.animation = res_manager.load<Animation>(Path(path));
|
||||
entry.animation = path;
|
||||
entry.set = 0;
|
||||
entry.slot = m_controller.m_animation_slots.size();
|
||||
|
||||
|
@ -690,14 +797,10 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
saveUndo(inputSlot(m_controller, "##slot", &entry.slot));
|
||||
saveUndo(editSlot(m_controller, "##slot", &entry.slot));
|
||||
ImGui::NextColumn();
|
||||
ImGui::PushItemWidth(-1);
|
||||
Path path = entry.animation ? entry.animation->getPath() : Path();
|
||||
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);
|
||||
if (m_app.getAssetBrowser().resourceInput("anim", entry.animation, Animation::TYPE)) {
|
||||
saveUndo(true);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
@ -750,7 +853,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data)));
|
||||
ImGui::NextColumn();
|
||||
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);
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
@ -773,7 +876,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
|
||||
const char* name = "N/A";
|
||||
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);
|
||||
};
|
||||
if (m_current_node == node) {
|
||||
|
@ -797,54 +900,46 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
|
||||
if (ImGui::BeginTabBar("ctb")) {
|
||||
if (ImGui::BeginTabItem("Tree")) {
|
||||
ImGui::Columns(2);
|
||||
breadcrumbs(m_current_node);
|
||||
if (m_current_node) nodeEditorGUI(m_current_node->m_nodes, m_current_node->m_links);
|
||||
ImGui::NextColumn();
|
||||
if (m_current_node) {
|
||||
for (Node* n : m_current_node->m_nodes) {
|
||||
if (n->m_selected) {
|
||||
n->propertiesGUI();
|
||||
break;
|
||||
if (ImGui::BeginTable("tt", 3, ImGuiTableFlags_Resizable)) {
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 250);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
bool any_selected = false;
|
||||
if (m_current_node) {
|
||||
for (Node* n : m_current_node->m_nodes) {
|
||||
if (n->m_selected) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Debugger")) {
|
||||
debuggerUI();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Preview")) {
|
||||
previewUI();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
|
@ -853,12 +948,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
const Path& getPath() override { return m_path; }
|
||||
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 {
|
||||
i32 axis_x = -1;
|
||||
i32 axis_y = -1;
|
||||
|
@ -903,7 +992,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
|
||||
void createResource(OutputMemoryStream& blob) override {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -912,9 +1001,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
|||
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; }
|
||||
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"; }
|
||||
|
||||
TagAllocator m_allocator;
|
||||
|
|
|
@ -6,7 +6,54 @@ namespace Lumix {
|
|||
|
||||
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 {
|
||||
static UniquePtr<ControllerEditor> create(StudioApp& app);
|
||||
|
|
765
src/animation/editor/editor_nodes.cpp
Normal file
765
src/animation/editor/editor_nodes.cpp
Normal 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
|
220
src/animation/editor/editor_nodes.h
Normal file
220
src/animation/editor/editor_nodes.h
Normal 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
|
@ -5,7 +5,6 @@
|
|||
#include "engine/math.h"
|
||||
#include "engine/stream.h"
|
||||
#include "engine/string.h"
|
||||
#include "editor/utils.h"
|
||||
|
||||
|
||||
namespace Lumix {
|
||||
|
@ -16,214 +15,81 @@ struct Pose;
|
|||
namespace anim {
|
||||
|
||||
struct Controller;
|
||||
struct GroupNode;
|
||||
|
||||
bool inputSlot(const Controller& controller, const char* str_id, u32* slot);
|
||||
bool editInput(const char* label, u32* input_index, const Controller& controller);
|
||||
|
||||
|
||||
struct RuntimeContext {
|
||||
RuntimeContext(Controller& controller, IAllocator& allocator);
|
||||
|
||||
void setInput(u32 input_idx, float value);
|
||||
void setInput(u32 input_idx, bool value);
|
||||
|
||||
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;
|
||||
enum class NodeType : u32 {
|
||||
ANIMATION,
|
||||
BLEND1D,
|
||||
LAYERS,
|
||||
NONE,
|
||||
SELECT,
|
||||
BLEND2D,
|
||||
TREE,
|
||||
OUTPUT,
|
||||
INPUT
|
||||
};
|
||||
|
||||
struct Node : NodeEditorNode {
|
||||
enum Type : u32 {
|
||||
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);
|
||||
}
|
||||
struct Node {
|
||||
static Node* create(NodeType type, Controller& controller);
|
||||
|
||||
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 enter(RuntimeContext& ctx) = 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 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 {
|
||||
OutputNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||
struct ValueNode : Node {
|
||||
virtual Value eval(const RuntimeContext& ctx) const = 0;
|
||||
};
|
||||
|
||||
Type type() const override { return OUTPUT; }
|
||||
bool hasInputPins() const override { return true; }
|
||||
bool hasOutputPins() const override { return false; }
|
||||
bool onGUI() override;
|
||||
|
||||
struct InputNode : ValueNode {
|
||||
NodeType type() const override { return anim::NodeType::INPUT; }
|
||||
void serialize(OutputMemoryStream& stream) const 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 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;
|
||||
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 time(const RuntimeContext& ctx) const override;
|
||||
|
||||
String m_name;
|
||||
};
|
||||
|
||||
struct SelectNode final : Node {
|
||||
struct RuntimeData {
|
||||
u32 from;
|
||||
u32 to;
|
||||
Time t;
|
||||
struct Child {
|
||||
float value = 0;
|
||||
u32 slot = 0;
|
||||
};
|
||||
|
||||
SelectNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||
|
||||
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);
|
||||
Array<Child> m_children;
|
||||
ValueNode* m_value;
|
||||
};
|
||||
|
||||
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 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;
|
||||
|
||||
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 {
|
||||
Vec2 value = Vec2(0);
|
||||
u32 slot = 0;
|
||||
|
@ -234,68 +100,52 @@ struct Blend2DNode final : Node {
|
|||
Vec2 circumcircle_center;
|
||||
};
|
||||
|
||||
String m_name;
|
||||
Array<Child> m_children;
|
||||
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;
|
||||
u32 m_input_index = 0;
|
||||
ValueNode* m_x_value;
|
||||
ValueNode* m_y_value;
|
||||
};
|
||||
|
||||
struct LayersNode final : Node {
|
||||
LayersNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||
~LayersNode();
|
||||
Type type() const override { return LAYERS; }
|
||||
bool hasInputPins() const override { return true; }
|
||||
bool hasOutputPins() const override { return true; }
|
||||
|
||||
struct SelectNode final : PoseNode {
|
||||
struct RuntimeData {
|
||||
u32 from;
|
||||
u32 to;
|
||||
Time t;
|
||||
};
|
||||
|
||||
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 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 Layer {
|
||||
Layer(IAllocator& allocator);
|
||||
Array<PoseNode*> m_children;
|
||||
ValueNode* m_value;
|
||||
Time m_blend_length;
|
||||
};
|
||||
|
||||
Node* node = nullptr;
|
||||
u32 mask = 0;
|
||||
String name;
|
||||
struct AnimationNode final : PoseNode {
|
||||
NodeType type() const override { return anim::NodeType::ANIMATION; }
|
||||
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;
|
||||
Array<Layer> m_layers;
|
||||
u32 m_slot = 0;
|
||||
u32 m_flags = LOOPED;
|
||||
};
|
||||
|
||||
|
||||
} // namespace anim
|
||||
} // namespace Lumix
|
||||
|
|
Loading…
Reference in a new issue