diff --git a/src/animation/animation.h b/src/animation/animation.h index 828b1e796..2766e3978 100644 --- a/src/animation/animation.h +++ b/src/animation/animation.h @@ -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 bones; }; diff --git a/src/animation/animation_module.cpp b/src/animation/animation_module.cpp index cbfd15d3b..238056de0 100644 --- a/src/animation/animation_module.cpp +++ b/src/animation/animation_module.cpp @@ -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); diff --git a/src/animation/animation_module.h b/src/animation/animation_module.h index 120a611a1..f85249ae4 100644 --- a/src/animation/animation_module.h +++ b/src/animation/animation_module.h @@ -22,7 +22,6 @@ struct AnimationModule : IModule { static UniquePtr 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; diff --git a/src/animation/controller.cpp b/src/animation/controller.cpp index 592dd6399..5c47796cd 100644 --- a/src/animation/controller.cpp +++ b/src/animation/controller.cpp @@ -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 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(); - 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(); m_animation_entries.reserve(entries_count); @@ -160,20 +133,57 @@ bool Controller::deserialize(InputMemoryStream& stream) { entry.animation = path[0] ? m_resource_manager.getOwner().load(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(); + 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(); + float weight = bs.read(); + Time time = bs.read