animation system - play rate node; ik
This commit is contained in:
parent
3952f184c0
commit
9208bbdea4
13 changed files with 565 additions and 205 deletions
Binary file not shown.
|
@ -56,11 +56,6 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
Flags flags = Flags::NONE;
|
Flags flags = Flags::NONE;
|
||||||
anim::RuntimeContext* ctx = nullptr;
|
anim::RuntimeContext* ctx = nullptr;
|
||||||
LocalRigidTransform root_motion = {{0, 0, 0}, {0, 0, 0, 1}};
|
LocalRigidTransform root_motion = {{0, 0, 0}, {0, 0, 0, 1}};
|
||||||
|
|
||||||
struct IK {
|
|
||||||
float weight = 0;
|
|
||||||
Vec3 target;
|
|
||||||
} inverse_kinematics[4];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,18 +121,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i32 getAnimatorInputIndex(EntityRef entity, const char* name) const override {
|
||||||
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()];
|
|
||||||
Animator::IK& ik = animator.inverse_kinematics[index];
|
|
||||||
ik.weight = clamp(weight, 0.f, 1.f);
|
|
||||||
ik.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
i32 getAnimatorInputIndex(EntityRef entity, const char* name) const override
|
|
||||||
{
|
|
||||||
const Animator& animator = m_animators[m_animator_map[entity]];
|
const Animator& animator = m_animators[m_animator_map[entity]];
|
||||||
for (anim::Controller::Input& input : animator.resource->m_inputs) {
|
for (anim::Controller::Input& input : animator.resource->m_inputs) {
|
||||||
if (input.name == name) return i32(&input - animator.resource->m_inputs.begin());
|
if (input.name == name) return i32(&input - animator.resource->m_inputs.begin());
|
||||||
|
@ -151,7 +135,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
if (!iter.isValid()) return;
|
if (!iter.isValid()) return;
|
||||||
|
|
||||||
Animator& animator = m_animators[iter.value()];
|
Animator& animator = m_animators[iter.value()];
|
||||||
if (animator.resource->m_inputs[input_idx].type == anim::Value::FLOAT) {
|
if (animator.resource->m_inputs[input_idx].type == anim::Value::NUMBER) {
|
||||||
animator.ctx->inputs[input_idx].f = value;
|
animator.ctx->inputs[input_idx].f = value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -513,11 +497,21 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
if (!animator.ctx) return;
|
if (!animator.ctx) return;
|
||||||
|
|
||||||
if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
|
if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
|
||||||
if (animator.resource->m_inputs[input_idx].type != anim::Value::FLOAT) return;
|
if (animator.resource->m_inputs[input_idx].type != anim::Value::NUMBER) return;
|
||||||
|
|
||||||
animator.ctx->inputs[input_idx].f = value;
|
animator.ctx->inputs[input_idx].f = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAnimatorInput(EntityRef entity, u32 input_idx, Vec3 value) override {
|
||||||
|
Animator& animator = m_animators[m_animator_map[entity]];
|
||||||
|
if (!animator.ctx) return;
|
||||||
|
|
||||||
|
if (input_idx >= (u32)animator.resource->m_inputs.size()) return;
|
||||||
|
if (animator.resource->m_inputs[input_idx].type != anim::Value::VEC3) return;
|
||||||
|
|
||||||
|
animator.ctx->inputs[input_idx].v3 = value;
|
||||||
|
}
|
||||||
|
|
||||||
void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) override {
|
void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) override {
|
||||||
Animator& animator = m_animators[m_animator_map[entity]];
|
Animator& animator = m_animators[m_animator_map[entity]];
|
||||||
if (!animator.ctx) return;
|
if (!animator.ctx) return;
|
||||||
|
@ -533,7 +527,7 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
if (!animator.ctx) return 0;
|
if (!animator.ctx) return 0;
|
||||||
|
|
||||||
ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
|
ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
|
||||||
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::FLOAT);
|
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::NUMBER);
|
||||||
|
|
||||||
return animator.ctx->inputs[input_idx].f;
|
return animator.ctx->inputs[input_idx].f;
|
||||||
}
|
}
|
||||||
|
@ -548,6 +542,16 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
return animator.ctx->inputs[input_idx].b;
|
return animator.ctx->inputs[input_idx].b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vec3 getAnimatorVec3Input(EntityRef entity, u32 input_idx) override {
|
||||||
|
Animator& animator = m_animators[m_animator_map[entity]];
|
||||||
|
if (!animator.ctx) return Vec3(0);
|
||||||
|
|
||||||
|
ASSERT(input_idx < (u32)animator.resource->m_inputs.size());
|
||||||
|
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::VEC3);
|
||||||
|
|
||||||
|
return animator.ctx->inputs[input_idx].v3;
|
||||||
|
}
|
||||||
|
|
||||||
LocalRigidTransform getAnimatorRootMotion(EntityRef entity) override
|
LocalRigidTransform getAnimatorRootMotion(EntityRef entity) override
|
||||||
{
|
{
|
||||||
auto iter = m_animator_map.find(entity);
|
auto iter = m_animator_map.find(entity);
|
||||||
|
@ -626,12 +630,6 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
model->getRelativePose(*pose);
|
model->getRelativePose(*pose);
|
||||||
evalBlendStack(*animator.ctx, *pose);
|
evalBlendStack(*animator.ctx, *pose);
|
||||||
|
|
||||||
for (Animator::IK& ik : animator.inverse_kinematics) {
|
|
||||||
if (ik.weight == 0) break;
|
|
||||||
const u32 idx = u32(&ik - animator.inverse_kinematics);
|
|
||||||
updateIK(animator.resource->m_ik[idx], ik, *pose, *model);
|
|
||||||
}
|
|
||||||
|
|
||||||
pose->computeAbsolute(*model);
|
pose->computeAbsolute(*model);
|
||||||
|
|
||||||
m_render_module->unlockPose(entity, true);
|
m_render_module->unlockPose(entity, true);
|
||||||
|
@ -644,115 +642,6 @@ struct AnimationModuleImpl final : AnimationModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static LocalRigidTransform getAbsolutePosition(const Pose& pose, const Model& model, int bone_index)
|
|
||||||
{
|
|
||||||
const Model::Bone& bone = model.getBone(bone_index);
|
|
||||||
LocalRigidTransform bone_transform{pose.positions[bone_index], pose.rotations[bone_index]};
|
|
||||||
if (bone.parent_idx < 0)
|
|
||||||
{
|
|
||||||
return bone_transform;
|
|
||||||
}
|
|
||||||
return getAbsolutePosition(pose, model, bone.parent_idx) * bone_transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void updateIK(anim::Controller::IK& res_ik, Animator::IK& ik, Pose& pose, Model& model)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
|
|
||||||
indices[i] = iter.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert from bone space to object space
|
|
||||||
const Model::Bone& first_bone = model.getBone(indices[0]);
|
|
||||||
LocalRigidTransform roots_parent;
|
|
||||||
if (first_bone.parent_idx >= 0) {
|
|
||||||
roots_parent = getAbsolutePosition(pose, model, first_bone.parent_idx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
roots_parent.pos = Vec3::ZERO;
|
|
||||||
roots_parent.rot = Quat::IDENTITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalRigidTransform parent_tr = roots_parent;
|
|
||||||
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;
|
|
||||||
if (i > 0) {
|
|
||||||
len[i - 1] = length(transforms[i].pos - transforms[i - 1].pos);
|
|
||||||
len_sum += len[i - 1];
|
|
||||||
}
|
|
||||||
parent_tr = transforms[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec3 target = ik.target;
|
|
||||||
Vec3 to_target = target - transforms[0].pos;
|
|
||||||
if (len_sum * len_sum < squaredLength(to_target)) {
|
|
||||||
to_target = normalize(to_target);
|
|
||||||
target = transforms[0].pos + to_target * len_sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u32 iteration = 0; iteration < res_ik.max_iterations; ++iteration) {
|
|
||||||
transforms[bones_count - 1].pos = target;
|
|
||||||
|
|
||||||
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 (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 (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;
|
|
||||||
|
|
||||||
Quat rel_rot = Quat::vec3ToVec3(old_d, new_d);
|
|
||||||
transforms[i].rot = rel_rot * transforms[i].rot;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert from object space to bone space
|
|
||||||
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 (i32 i = bones_count - 2; i > 0; --i) {
|
|
||||||
ik_out[i].rot = transforms[i].rot;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ik_out[0].rot = transforms[0].rot;
|
|
||||||
}
|
|
||||||
ik_out[0].pos = pose.positions[indices[0]];
|
|
||||||
|
|
||||||
const float w = ik.weight;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void applyPropertyAnimator(EntityRef entity, PropertyAnimator& animator)
|
void applyPropertyAnimator(EntityRef entity, PropertyAnimator& animator)
|
||||||
{
|
{
|
||||||
const PropertyAnimation* animation = animator.animation;
|
const PropertyAnimation* animation = animator.animation;
|
||||||
|
@ -908,7 +797,6 @@ void AnimationModule::reflect(Engine& engine) {
|
||||||
.function<(void (AnimationModule::*)(EntityRef, u32, float))&AnimationModule::setAnimatorInput>("setFloatInput", "AnimationModule::setAnimatorInput")
|
.function<(void (AnimationModule::*)(EntityRef, u32, float))&AnimationModule::setAnimatorInput>("setFloatInput", "AnimationModule::setAnimatorInput")
|
||||||
.function<(void (AnimationModule::*)(EntityRef, u32, bool))&AnimationModule::setAnimatorInput>("setBoolInput", "AnimationModule::setAnimatorInput")
|
.function<(void (AnimationModule::*)(EntityRef, u32, bool))&AnimationModule::setAnimatorInput>("setBoolInput", "AnimationModule::setAnimatorInput")
|
||||||
.LUMIX_FUNC_EX(AnimationModule::getAnimatorInputIndex, "getInputIndex")
|
.LUMIX_FUNC_EX(AnimationModule::getAnimatorInputIndex, "getInputIndex")
|
||||||
.LUMIX_FUNC_EX(AnimationModule::setAnimatorIK, "setIK")
|
|
||||||
.LUMIX_PROP(AnimatorSource, "Source").resourceAttribute(anim::Controller::TYPE)
|
.LUMIX_PROP(AnimatorSource, "Source").resourceAttribute(anim::Controller::TYPE)
|
||||||
.LUMIX_PROP(AnimatorDefaultSet, "Default set")
|
.LUMIX_PROP(AnimatorDefaultSet, "Default set")
|
||||||
.LUMIX_PROP(AnimatorUseRootMotion, "Use root motion")
|
.LUMIX_PROP(AnimatorUseRootMotion, "Use root motion")
|
||||||
|
|
|
@ -35,8 +35,10 @@ struct AnimationModule : IModule {
|
||||||
virtual void updateAnimator(EntityRef entity, float time_delta) = 0;
|
virtual void updateAnimator(EntityRef entity, float time_delta) = 0;
|
||||||
virtual Animable& getAnimable(EntityRef entity) = 0;
|
virtual Animable& getAnimable(EntityRef entity) = 0;
|
||||||
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, float value) = 0;
|
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, float value) = 0;
|
||||||
|
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, Vec3 value) = 0;
|
||||||
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) = 0;
|
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) = 0;
|
||||||
virtual float getAnimatorFloatInput(EntityRef entity, u32 input_idx) = 0;
|
virtual float getAnimatorFloatInput(EntityRef entity, u32 input_idx) = 0;
|
||||||
|
virtual Vec3 getAnimatorVec3Input(EntityRef entity, u32 input_idx) = 0;
|
||||||
virtual bool getAnimatorBoolInput(EntityRef entity, u32 input_idx) = 0;
|
virtual bool getAnimatorBoolInput(EntityRef entity, u32 input_idx) = 0;
|
||||||
virtual struct LocalRigidTransform getAnimatorRootMotion(EntityRef entity) = 0;
|
virtual struct LocalRigidTransform getAnimatorRootMotion(EntityRef entity) = 0;
|
||||||
virtual void setAnimatorUseRootMotion(EntityRef entity, bool value) = 0;
|
virtual void setAnimatorUseRootMotion(EntityRef entity, bool value) = 0;
|
||||||
|
@ -49,7 +51,6 @@ struct AnimationModule : IModule {
|
||||||
virtual u32 getAnimatorDefaultSet(EntityRef entity) = 0;
|
virtual u32 getAnimatorDefaultSet(EntityRef entity) = 0;
|
||||||
virtual anim::Controller* getAnimatorController(EntityRef entity) = 0;
|
virtual anim::Controller* getAnimatorController(EntityRef entity) = 0;
|
||||||
virtual anim::RuntimeContext* getAnimatorRuntimeContext(EntityRef entity) = 0;
|
virtual anim::RuntimeContext* getAnimatorRuntimeContext(EntityRef entity) = 0;
|
||||||
virtual void setAnimatorIK(EntityRef entity, u32 index, float weight, const struct Vec3& target) = 0;
|
|
||||||
virtual float getAnimationLength(int animation_idx) = 0;
|
virtual float getAnimationLength(int animation_idx) = 0;
|
||||||
virtual OutputMemoryStream& beginBlendstackUpdate(EntityRef entity) = 0;
|
virtual OutputMemoryStream& beginBlendstackUpdate(EntityRef entity) = 0;
|
||||||
virtual void endBlendstackUpdate(EntityRef entity) = 0;
|
virtual void endBlendstackUpdate(EntityRef entity) = 0;
|
||||||
|
|
|
@ -18,7 +18,6 @@ Controller::Controller(const Path& path, ResourceManager& resource_manager, IAll
|
||||||
, m_allocator(allocator)
|
, m_allocator(allocator)
|
||||||
, m_animation_entries(allocator)
|
, m_animation_entries(allocator)
|
||||||
, m_inputs(allocator)
|
, m_inputs(allocator)
|
||||||
, m_ik(allocator)
|
|
||||||
, m_bone_masks(allocator)
|
, m_bone_masks(allocator)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -94,11 +93,6 @@ void Controller::serialize(OutputMemoryStream& stream) {
|
||||||
stream.write(entry.set);
|
stream.write(entry.set);
|
||||||
stream.writeString(entry.animation ? entry.animation->getPath() : Path());
|
stream.writeString(entry.animation ? entry.animation->getPath() : Path());
|
||||||
}
|
}
|
||||||
stream.write(m_ik.size());
|
|
||||||
for (const IK& ik : m_ik) {
|
|
||||||
stream.write(ik.max_iterations);
|
|
||||||
stream.writeArray(ik.bones);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.write(m_root->type());
|
stream.write(m_root->type());
|
||||||
m_root->serialize(stream);
|
m_root->serialize(stream);
|
||||||
|
@ -130,14 +124,6 @@ bool Controller::deserialize(InputMemoryStream& stream) {
|
||||||
entry.animation = path[0] ? m_resource_manager.getOwner().load<Animation>(Path(path)) : nullptr;
|
entry.animation = path[0] ? m_resource_manager.getOwner().load<Animation>(Path(path)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
NodeType type;
|
||||||
stream.read(type);
|
stream.read(type);
|
||||||
m_root = (PoseNode*)Node::create(type, *this);
|
m_root = (PoseNode*)Node::create(type, *this);
|
||||||
|
@ -162,6 +148,113 @@ static void getPose(const anim::RuntimeContext& ctx, Time time, float weight, u3
|
||||||
anim->getRelativePose(sample_ctx);
|
anim->getRelativePose(sample_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LocalRigidTransform getAbsolutePosition(const Pose& pose, const Model& model, int bone_index)
|
||||||
|
{
|
||||||
|
const Model::Bone& bone = model.getBone(bone_index);
|
||||||
|
LocalRigidTransform bone_transform{pose.positions[bone_index], pose.rotations[bone_index]};
|
||||||
|
if (bone.parent_idx < 0)
|
||||||
|
{
|
||||||
|
return bone_transform;
|
||||||
|
}
|
||||||
|
return getAbsolutePosition(pose, model, bone.parent_idx) * bone_transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
void evalIK(float alpha, Vec3 target, u32 leaf_bone, u32 bones_count, Model& model, Pose& pose) {
|
||||||
|
if (alpha < 0.001f) return;
|
||||||
|
|
||||||
|
// TODO user defined
|
||||||
|
const u32 max_iterations = 5;
|
||||||
|
enum { MAX_BONES_COUNT = 32 };
|
||||||
|
ASSERT(bones_count <= MAX_BONES_COUNT);
|
||||||
|
|
||||||
|
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;
|
||||||
|
indices[bones_count - 1] = leaf_bone;
|
||||||
|
for (u32 i = 1; i < bones_count; ++i) {
|
||||||
|
indices[bones_count - 1 - i] = model.getBoneParent(indices[bones_count - i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert from bone space to object space
|
||||||
|
const Model::Bone& first_bone = model.getBone(indices[0]);
|
||||||
|
LocalRigidTransform roots_parent;
|
||||||
|
if (first_bone.parent_idx >= 0) {
|
||||||
|
roots_parent = getAbsolutePosition(pose, model, first_bone.parent_idx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
roots_parent.pos = Vec3::ZERO;
|
||||||
|
roots_parent.rot = Quat::IDENTITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalRigidTransform parent_tr = roots_parent;
|
||||||
|
for (u32 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;
|
||||||
|
if (i > 0) {
|
||||||
|
len[i - 1] = length(transforms[i].pos - transforms[i - 1].pos);
|
||||||
|
len_sum += len[i - 1];
|
||||||
|
}
|
||||||
|
parent_tr = transforms[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 to_target = target - transforms[0].pos;
|
||||||
|
if (len_sum * len_sum < squaredLength(to_target)) {
|
||||||
|
to_target = normalize(to_target);
|
||||||
|
target = transforms[0].pos + to_target * len_sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 iteration = 0; iteration < max_iterations; ++iteration) {
|
||||||
|
transforms[bones_count - 1].pos = target;
|
||||||
|
|
||||||
|
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 (u32 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 (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;
|
||||||
|
|
||||||
|
Quat rel_rot = Quat::vec3ToVec3(old_d, new_d);
|
||||||
|
transforms[i].rot = rel_rot * transforms[i].rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert from object space to bone space
|
||||||
|
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 (i32 i = bones_count - 2; i > 0; --i) {
|
||||||
|
ik_out[i].rot = transforms[i].rot;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ik_out[0].rot = transforms[0].rot;
|
||||||
|
}
|
||||||
|
ik_out[0].pos = pose.positions[indices[0]];
|
||||||
|
|
||||||
|
for (u32 i = 0; i < bones_count; ++i) {
|
||||||
|
const u32 idx = indices[i];
|
||||||
|
pose.positions[idx] = lerp(pose.positions[idx], ik_out[i].pos, alpha);
|
||||||
|
pose.rotations[idx] = nlerp(pose.rotations[idx], ik_out[i].rot, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose) {
|
void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose) {
|
||||||
InputMemoryStream bs(ctx.blendstack);
|
InputMemoryStream bs(ctx.blendstack);
|
||||||
|
|
||||||
|
@ -170,6 +263,14 @@ void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose) {
|
||||||
bs.read(instr);
|
bs.read(instr);
|
||||||
switch (instr) {
|
switch (instr) {
|
||||||
case anim::BlendStackInstructions::END: return;
|
case anim::BlendStackInstructions::END: return;
|
||||||
|
case anim::BlendStackInstructions::IK: {
|
||||||
|
float alpha = bs.read<float>();
|
||||||
|
Vec3 pos = bs.read<Vec3>();
|
||||||
|
u32 leaf_bone = bs.read<u32>();
|
||||||
|
u32 bone_count = bs.read<u32>();
|
||||||
|
evalIK(alpha * ctx.weight, pos, leaf_bone, bone_count, *ctx.model, pose);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case anim::BlendStackInstructions::SAMPLE: {
|
case anim::BlendStackInstructions::SAMPLE: {
|
||||||
u32 slot = bs.read<u32>();
|
u32 slot = bs.read<u32>();
|
||||||
float weight = bs.read<float>();
|
float weight = bs.read<float>();
|
||||||
|
|
|
@ -15,21 +15,25 @@ namespace anim {
|
||||||
|
|
||||||
struct Value {
|
struct Value {
|
||||||
enum Type : u32 {
|
enum Type : u32 {
|
||||||
FLOAT,
|
NUMBER,
|
||||||
BOOL
|
BOOL,
|
||||||
|
VEC3
|
||||||
};
|
};
|
||||||
Type type;
|
Type type;
|
||||||
union {
|
union {
|
||||||
float f;
|
float f;
|
||||||
bool b;
|
bool b;
|
||||||
|
Vec3 v3;
|
||||||
};
|
};
|
||||||
|
|
||||||
Value() : f(0), type(FLOAT) {}
|
Value() : f(0), type(NUMBER) {}
|
||||||
Value(float f) : f(f), type(FLOAT) {}
|
Value(float f) : f(f), type(NUMBER) {}
|
||||||
Value(bool b) : b(b), type(BOOL) {}
|
Value(bool b) : b(b), type(BOOL) {}
|
||||||
float toFloat() const { ASSERT(type == FLOAT); return f; }
|
Value(Vec3 v3) : v3(v3), type(VEC3) {}
|
||||||
i32 toI32() const { ASSERT(type == FLOAT); return i32(f + 0.5f); }
|
float toFloat() const { ASSERT(type == NUMBER); return f; }
|
||||||
|
i32 toI32() const { ASSERT(type == NUMBER); return i32(f + 0.5f); }
|
||||||
bool toBool() const { ASSERT(type == BOOL); return b; }
|
bool toBool() const { ASSERT(type == BOOL); return b; }
|
||||||
|
Vec3 toVec3() const { ASSERT(type == VEC3); return v3; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RuntimeContext {
|
struct RuntimeContext {
|
||||||
|
@ -53,6 +57,7 @@ struct RuntimeContext {
|
||||||
enum BlendStackInstructions : u8 {
|
enum BlendStackInstructions : u8 {
|
||||||
END,
|
END,
|
||||||
SAMPLE,
|
SAMPLE,
|
||||||
|
IK
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ControllerVersion : u32 {
|
enum class ControllerVersion : u32 {
|
||||||
|
@ -88,18 +93,11 @@ struct Controller final : Resource {
|
||||||
StaticString<32> name;
|
StaticString<32> name;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IK {
|
|
||||||
IK(IAllocator& allocator) : bones(allocator) {}
|
|
||||||
u32 max_iterations = 5;
|
|
||||||
Array<BoneNameHash> bones;
|
|
||||||
};
|
|
||||||
|
|
||||||
IAllocator& m_allocator;
|
IAllocator& m_allocator;
|
||||||
struct PoseNode* m_root = nullptr;
|
struct PoseNode* m_root = nullptr;
|
||||||
Array<AnimationEntry> m_animation_entries;
|
Array<AnimationEntry> m_animation_entries;
|
||||||
Array<BoneMask> m_bone_masks;
|
Array<BoneMask> m_bone_masks;
|
||||||
Array<Input> m_inputs;
|
Array<Input> m_inputs;
|
||||||
Array<IK> m_ik;
|
|
||||||
u32 m_animation_slots_count = 0;
|
u32 m_animation_slots_count = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -210,6 +210,8 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
visitor.endCategory();
|
visitor.endCategory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitor.visitType(anim::NodeType::IK, "Inverse kinematics", 0);
|
||||||
|
|
||||||
if (visitor.beginCategory("Inputs")) {
|
if (visitor.beginCategory("Inputs")) {
|
||||||
struct : INodeTypeVisitor::INodeCreator {
|
struct : INodeTypeVisitor::INodeCreator {
|
||||||
Node* create(EditorWindow& editor) const override {
|
Node* create(EditorWindow& editor) const override {
|
||||||
|
@ -228,6 +230,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
|
|
||||||
visitor
|
visitor
|
||||||
.visitType(anim::NodeType::LAYERS, "Layers", 'L')
|
.visitType(anim::NodeType::LAYERS, "Layers", 'L')
|
||||||
|
.visitType(anim::NodeType::PLAYRATE, "Play rate", 0)
|
||||||
.visitType(anim::NodeType::SELECT, "Select", 'S')
|
.visitType(anim::NodeType::SELECT, "Select", 'S')
|
||||||
.visitType(anim::NodeType::SWITCH, "Switch", 'W')
|
.visitType(anim::NodeType::SWITCH, "Switch", 'W')
|
||||||
.visitType(anim::NodeType::TREE, "Tree", 'T');
|
.visitType(anim::NodeType::TREE, "Tree", 'T');
|
||||||
|
@ -238,6 +241,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
, NodeEditor(allocator)
|
, NodeEditor(allocator)
|
||||||
, m_allocator(allocator)
|
, m_allocator(allocator)
|
||||||
, m_to_fix_skeleton(allocator)
|
, m_to_fix_skeleton(allocator)
|
||||||
|
, m_visualize_position_inputs(allocator)
|
||||||
, m_recording(app, allocator)
|
, m_recording(app, allocator)
|
||||||
, m_app(app)
|
, m_app(app)
|
||||||
, m_plugin(plugin)
|
, m_plugin(plugin)
|
||||||
|
@ -503,6 +507,21 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
recordButton(world, entity);
|
recordButton(world, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void processPositionVisualizations(World& world, EntityRef& entity) {
|
||||||
|
if (m_visualize_position_inputs.empty()) return;
|
||||||
|
|
||||||
|
Transform entity_transform = world.getTransform(entity);
|
||||||
|
const ComponentType animator_type = reflection::getComponentType("animator");
|
||||||
|
AnimationModule* anim_module = (AnimationModule*)world.getModule(animator_type);
|
||||||
|
RenderModule* render_module = (RenderModule*)world.getModule("renderer");
|
||||||
|
|
||||||
|
for (u32 input_idx : m_visualize_position_inputs) {
|
||||||
|
const Vec3 val = anim_module->getAnimatorVec3Input(entity, input_idx);
|
||||||
|
DVec3 p = entity_transform.transform(val);
|
||||||
|
render_module->addDebugCubeSolid(p - DVec3(0.05f), p + DVec3(0.05f), Color::BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void processControllerMapping(World& world, EntityRef entity) {
|
void processControllerMapping(World& world, EntityRef entity) {
|
||||||
if (m_controller_debug_mapping.axis_x < 0 && m_controller_debug_mapping.axis_y < 0) return;
|
if (m_controller_debug_mapping.axis_x < 0 && m_controller_debug_mapping.axis_y < 0) return;
|
||||||
|
|
||||||
|
@ -568,6 +587,16 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
blob.read(instr);
|
blob.read(instr);
|
||||||
switch (instr) {
|
switch (instr) {
|
||||||
case anim::BlendStackInstructions::END: return;
|
case anim::BlendStackInstructions::END: return;
|
||||||
|
case anim::BlendStackInstructions::IK: {
|
||||||
|
ImGui::TextUnformatted("IK");
|
||||||
|
float alpha = blob.read<float>();
|
||||||
|
ImGui::Text("Alpha: %f", alpha);
|
||||||
|
Vec3 pos = blob.read<Vec3>();
|
||||||
|
ImGui::Text("Position: %f %f %F", pos.x, pos.y, pos.z);
|
||||||
|
blob.read<u32>(); // leaf
|
||||||
|
blob.read<u32>(); // bone count
|
||||||
|
break;
|
||||||
|
}
|
||||||
case anim::BlendStackInstructions::SAMPLE: {
|
case anim::BlendStackInstructions::SAMPLE: {
|
||||||
u32 slot = blob.read<u32>();
|
u32 slot = blob.read<u32>();
|
||||||
float weight = blob.read<float>();
|
float weight = blob.read<float>();
|
||||||
|
@ -617,6 +646,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
|
|
||||||
void debuggerUI(World& world, EntityRef entity) {
|
void debuggerUI(World& world, EntityRef entity) {
|
||||||
processControllerMapping(world, entity);
|
processControllerMapping(world, entity);
|
||||||
|
processPositionVisualizations(world, entity);
|
||||||
|
|
||||||
const ComponentType animator_type = reflection::getComponentType("animator");
|
const ComponentType animator_type = reflection::getComponentType("animator");
|
||||||
AnimationModule* module = (AnimationModule*)world.getModule(animator_type);
|
AnimationModule* module = (AnimationModule*)world.getModule(animator_type);
|
||||||
|
@ -630,7 +660,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
ImGui::PushID(&input);
|
ImGui::PushID(&input);
|
||||||
const u32 idx = u32(&input - ctrl->m_inputs.begin());
|
const u32 idx = u32(&input - ctrl->m_inputs.begin());
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case anim::Value::FLOAT: {
|
case anim::Value::NUMBER: {
|
||||||
float val = module->getAnimatorFloatInput(entity, idx);
|
float val = module->getAnimatorFloatInput(entity, idx);
|
||||||
|
|
||||||
ImGuiEx::Label(input.name);
|
ImGuiEx::Label(input.name);
|
||||||
|
@ -662,6 +692,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case anim::Value::VEC3: {
|
||||||
|
Vec3 val = module->getAnimatorVec3Input(entity, idx);
|
||||||
|
ImGuiEx::Label(input.name);
|
||||||
|
if (ImGui::DragFloat3("##i", &val.x)) {
|
||||||
|
module->setAnimatorInput(entity, idx, val);
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupContextItem("##i")) {
|
||||||
|
if (m_visualize_position_inputs.indexOf(idx) >= 0) {
|
||||||
|
if (ImGui::Selectable("Disable visualization")) m_visualize_position_inputs.eraseItem(idx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ImGui::Selectable("Visualize")) m_visualize_position_inputs.push(idx);
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
@ -995,7 +1042,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data)));
|
saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data)));
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::SetNextItemWidth(-1);
|
ImGui::SetNextItemWidth(-1);
|
||||||
if (ImGui::Combo("##type", (int*)&input.type, "number\0bool")) {
|
if (ImGui::Combo("##type", (int*)&input.type, "number\0bool\0vector 3\0")) {
|
||||||
saveUndo(true);
|
saveUndo(true);
|
||||||
}
|
}
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
|
@ -1124,7 +1171,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
if (m_current_node) {
|
if (m_current_node) {
|
||||||
for (Node* n : m_current_node->m_nodes) {
|
for (Node* n : m_current_node->m_nodes) {
|
||||||
if (n->m_selected) {
|
if (n->m_selected) {
|
||||||
n->propertiesGUI();
|
n->propertiesGUI(*m_skeleton);
|
||||||
any_selected = true;
|
any_selected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1177,11 +1224,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
i32 axis_y = -1;
|
i32 axis_y = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IKDebug {
|
|
||||||
bool enabled = false;
|
|
||||||
Vec3 target = Vec3(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
WorldViewer m_viewer;
|
WorldViewer m_viewer;
|
||||||
IAllocator& m_allocator;
|
IAllocator& m_allocator;
|
||||||
Array<Path> m_to_fix_skeleton;
|
Array<Path> m_to_fix_skeleton;
|
||||||
|
@ -1195,7 +1237,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
|
||||||
float m_playback_speed = 1.f;
|
float m_playback_speed = 1.f;
|
||||||
bool m_show_skeleton = true;
|
bool m_show_skeleton = true;
|
||||||
ControllerDebugMapping m_controller_debug_mapping;
|
ControllerDebugMapping m_controller_debug_mapping;
|
||||||
IKDebug m_ik_debug[4];
|
Array<u32> m_visualize_position_inputs;
|
||||||
TextFilter m_node_filter;
|
TextFilter m_node_filter;
|
||||||
u32 m_node_filter_selection = 0;
|
u32 m_node_filter_selection = 0;
|
||||||
struct Record {
|
struct Record {
|
||||||
|
|
|
@ -108,6 +108,9 @@ anim::Node* Blend2DNode::compile(anim::Controller& controller) {
|
||||||
ValueNode* x = castToValueNode(getInput(0));
|
ValueNode* x = castToValueNode(getInput(0));
|
||||||
ValueNode* y = castToValueNode(getInput(1));
|
ValueNode* y = castToValueNode(getInput(1));
|
||||||
if (!x || !y) return nullptr;
|
if (!x || !y) return nullptr;
|
||||||
|
if (x->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
if (y->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
|
||||||
node->m_x_value = (anim::ValueNode*)x->compile(controller);
|
node->m_x_value = (anim::ValueNode*)x->compile(controller);
|
||||||
node->m_y_value = (anim::ValueNode*)y->compile(controller);
|
node->m_y_value = (anim::ValueNode*)y->compile(controller);
|
||||||
if (!node->m_x_value) return nullptr;
|
if (!node->m_x_value) return nullptr;
|
||||||
|
@ -116,7 +119,7 @@ anim::Node* Blend2DNode::compile(anim::Controller& controller) {
|
||||||
return node.detach();
|
return node.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Blend2DNode::propertiesGUI() {
|
bool Blend2DNode::propertiesGUI(Model& skeleton) {
|
||||||
ImGuiEx::Label("Name");
|
ImGuiEx::Label("Name");
|
||||||
bool res = inputString("##name", &m_name);
|
bool res = inputString("##name", &m_name);
|
||||||
|
|
||||||
|
@ -345,7 +348,7 @@ Blend1DNode::Blend1DNode(Node* parent, Controller& controller, IAllocator& alloc
|
||||||
, m_name("blend1d", allocator)
|
, m_name("blend1d", allocator)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool Blend1DNode::propertiesGUI() {
|
bool Blend1DNode::propertiesGUI(Model& skeleton) {
|
||||||
ImGuiEx::Label("Name");
|
ImGuiEx::Label("Name");
|
||||||
bool res = inputString("##name", &m_name);
|
bool res = inputString("##name", &m_name);
|
||||||
|
|
||||||
|
@ -395,6 +398,8 @@ anim::Node* Blend1DNode::compile(anim::Controller& controller) {
|
||||||
m_children.copyTo(node->m_children);
|
m_children.copyTo(node->m_children);
|
||||||
ValueNode* val = castToValueNode(getInput(0));
|
ValueNode* val = castToValueNode(getInput(0));
|
||||||
if (!val) return nullptr;
|
if (!val) return nullptr;
|
||||||
|
if (val->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
|
||||||
node->m_value = (anim::ValueNode*)val->compile(controller);
|
node->m_value = (anim::ValueNode*)val->compile(controller);
|
||||||
if (!node->m_value) return nullptr;
|
if (!node->m_value) return nullptr;
|
||||||
|
|
||||||
|
@ -424,7 +429,7 @@ anim::Node* AnimationNode::compile(anim::Controller& controller) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnimationNode::propertiesGUI() {
|
bool AnimationNode::propertiesGUI(Model& skeleton) {
|
||||||
ImGuiEx::Label("Slot");
|
ImGuiEx::Label("Slot");
|
||||||
static i32 selected = -1;
|
static i32 selected = -1;
|
||||||
bool res = editSlot(m_controller, "##slot", &m_slot);
|
bool res = editSlot(m_controller, "##slot", &m_slot);
|
||||||
|
@ -524,7 +529,7 @@ bool InputNode::onGUI() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputNode::propertiesGUI() {
|
bool InputNode::propertiesGUI(Model& skeleton) {
|
||||||
return editInput("Input", &m_input_index, m_controller);
|
return editInput("Input", &m_input_index, m_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,6 +540,11 @@ anim::Node* InputNode::compile(anim::Controller& controller) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anim::Value::Type InputNode::getReturnType() {
|
||||||
|
if (m_input_index >= (u32)m_controller.m_inputs.size()) return anim::Value::NUMBER;
|
||||||
|
return m_controller.m_inputs[m_input_index].type;
|
||||||
|
}
|
||||||
|
|
||||||
InputNode::InputNode(Node* parent, Controller& controller, IAllocator& allocator)
|
InputNode::InputNode(Node* parent, Controller& controller, IAllocator& allocator)
|
||||||
: ValueNode(parent, controller, allocator)
|
: ValueNode(parent, controller, allocator)
|
||||||
{}
|
{}
|
||||||
|
@ -634,11 +644,151 @@ anim::Node* MathNode::compile(anim::Controller& controller) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anim::Value::Type MathNode::getReturnType() {
|
||||||
|
ValueNode* input0 = castToValueNode(getInput(0));
|
||||||
|
if (!input0) return anim::Value::NUMBER;
|
||||||
|
return input0->getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
MathNode::MathNode(Node* parent, Controller& controller, anim::NodeType type, IAllocator& allocator)
|
MathNode::MathNode(Node* parent, Controller& controller, anim::NodeType type, IAllocator& allocator)
|
||||||
: ValueNode(parent, controller, allocator)
|
: ValueNode(parent, controller, allocator)
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
IKNode::IKNode(Node* parent, Controller& controller, IAllocator& allocator)
|
||||||
|
: PoseNode(parent, controller, allocator)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool IKNode::onGUI() {
|
||||||
|
outputSlot();
|
||||||
|
inputSlot(ImGuiEx::PinShape::SQUARE);
|
||||||
|
ImGui::TextUnformatted("Alpha");
|
||||||
|
inputSlot(ImGuiEx::PinShape::SQUARE);
|
||||||
|
ImGui::TextUnformatted("Effector position");
|
||||||
|
inputSlot();
|
||||||
|
ImGui::TextUnformatted("Input");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim::Node* IKNode::compile(anim::Controller& controller) {
|
||||||
|
if (m_bones_count == 0) return nullptr;
|
||||||
|
|
||||||
|
UniquePtr<anim::IKNode> node = UniquePtr<anim::IKNode>::create(controller.m_allocator, controller.m_allocator);
|
||||||
|
node->m_bones_count = m_bones_count;
|
||||||
|
node->m_leaf_bone = m_leaf_bone;
|
||||||
|
|
||||||
|
ValueNode* alpha = castToValueNode(getInput(0));
|
||||||
|
if (!alpha) return nullptr;
|
||||||
|
if (alpha->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
node->m_alpha = (anim::ValueNode*)alpha->compile(controller);
|
||||||
|
if (!node->m_alpha) return nullptr;
|
||||||
|
|
||||||
|
ValueNode* effector = castToValueNode(getInput(1));
|
||||||
|
if (!effector) return nullptr;
|
||||||
|
if (effector->getReturnType() != anim::Value::VEC3) return nullptr;
|
||||||
|
node->m_effector_position = (anim::ValueNode*)effector->compile(controller);
|
||||||
|
if (!node->m_effector_position) return nullptr;
|
||||||
|
|
||||||
|
PoseNode* input = castToPoseNode(getInput(2));
|
||||||
|
if (!input) return nullptr;
|
||||||
|
node->m_input = (anim::PoseNode*)input->compile(controller);
|
||||||
|
if (!node->m_input) return nullptr;
|
||||||
|
|
||||||
|
return node.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::serialize(OutputMemoryStream& stream) const {
|
||||||
|
Node::serialize(stream);
|
||||||
|
stream.write(m_leaf_bone);
|
||||||
|
stream.write(m_bones_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
|
||||||
|
Node::deserialize(stream, ctrl, version);
|
||||||
|
stream.read(m_leaf_bone);
|
||||||
|
stream.read(m_bones_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKNode::propertiesGUI(Model& skeleton) {
|
||||||
|
ImGuiEx::Label("Leaf");
|
||||||
|
bool changed = false;
|
||||||
|
if (ImGui::BeginCombo("##leaf", skeleton.getBoneName(m_leaf_bone))) {
|
||||||
|
for (u32 j = 0, cj = skeleton.getBoneCount(); j < cj; ++j) {
|
||||||
|
const char* bone_name = skeleton.getBoneName(j);
|
||||||
|
if (ImGui::Selectable(bone_name)) {
|
||||||
|
m_leaf_bone = j;
|
||||||
|
m_bones_count = 1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 iter = skeleton.getBoneParent(m_leaf_bone);
|
||||||
|
for (u32 i = 0; i < m_bones_count - 1; ++i) {
|
||||||
|
if (iter == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ImGuiEx::TextUnformatted(skeleton.getBoneName(iter));
|
||||||
|
iter = skeleton.getBoneParent(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iter >= 0) {
|
||||||
|
i32 parent = skeleton.getBoneParent(iter);
|
||||||
|
if (parent >= 0) {
|
||||||
|
const char* bone_name = skeleton.getBoneName(parent);
|
||||||
|
const StaticString<64> add_label("Add ", bone_name);
|
||||||
|
if (ImGui::Button(add_label)) {
|
||||||
|
++m_bones_count;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bones_count > 1) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Pop")) {
|
||||||
|
--m_bones_count;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayRateNode::PlayRateNode(Node* parent, Controller& controller, IAllocator& allocator)
|
||||||
|
: PoseNode(parent, controller, allocator)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool PlayRateNode::onGUI() {
|
||||||
|
outputSlot();
|
||||||
|
inputSlot(ImGuiEx::PinShape::SQUARE);
|
||||||
|
ImGuiEx::TextUnformatted("Play rate multiplier");
|
||||||
|
inputSlot();
|
||||||
|
ImGuiEx::TextUnformatted("Input");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim::Node* PlayRateNode::compile(anim::Controller& controller) {
|
||||||
|
UniquePtr<anim::PlayRateNode> node = UniquePtr<anim::PlayRateNode>::create(controller.m_allocator, controller.m_allocator);
|
||||||
|
|
||||||
|
ValueNode* value = castToValueNode(getInput(0));
|
||||||
|
if (!value) return nullptr;
|
||||||
|
if (value->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
node->m_value = (anim::ValueNode*)value->compile(controller);
|
||||||
|
if (!node->m_value) return nullptr;
|
||||||
|
|
||||||
|
Node* pose = getInput(1);
|
||||||
|
if (!pose) return nullptr;
|
||||||
|
if (!pose->isPoseNode()) return nullptr;
|
||||||
|
node->m_node = (anim::PoseNode*)pose->compile(controller);
|
||||||
|
if (!node->m_node) return nullptr;
|
||||||
|
|
||||||
|
return node.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayRateNode::serialize(OutputMemoryStream& stream) const { Node::serialize(stream); }
|
||||||
|
void PlayRateNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) { Node::deserialize(stream, ctrl, version); }
|
||||||
|
|
||||||
OutputNode::OutputNode(Node* parent, Controller& controller, IAllocator& allocator)
|
OutputNode::OutputNode(Node* parent, Controller& controller, IAllocator& allocator)
|
||||||
: PoseNode(parent, controller, allocator)
|
: PoseNode(parent, controller, allocator)
|
||||||
{}
|
{}
|
||||||
|
@ -671,7 +821,7 @@ anim::Node* TreeNode::compile(anim::Controller& controller) {
|
||||||
return m_nodes[0]->compile(controller);
|
return m_nodes[0]->compile(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TreeNode::propertiesGUI() {
|
bool TreeNode::propertiesGUI(Model& skeleton) {
|
||||||
ImGuiEx::Label("Name");
|
ImGuiEx::Label("Name");
|
||||||
return inputString("##name", &m_name);
|
return inputString("##name", &m_name);
|
||||||
}
|
}
|
||||||
|
@ -693,7 +843,7 @@ void TreeNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 vers
|
||||||
|
|
||||||
void TreeNode::serialize(OutputMemoryStream& stream) const { Node::serialize(stream); stream.write(m_name); }
|
void TreeNode::serialize(OutputMemoryStream& stream) const { Node::serialize(stream); stream.write(m_name); }
|
||||||
|
|
||||||
bool SelectNode::propertiesGUI() {
|
bool SelectNode::propertiesGUI(Model& skeleton) {
|
||||||
float node_blend_length = m_blend_length.seconds();
|
float node_blend_length = m_blend_length.seconds();
|
||||||
ImGuiEx::Label("Blend length");
|
ImGuiEx::Label("Blend length");
|
||||||
if (ImGui::DragFloat("##bl", &node_blend_length)) {
|
if (ImGui::DragFloat("##bl", &node_blend_length)) {
|
||||||
|
@ -706,7 +856,7 @@ bool SelectNode::propertiesGUI() {
|
||||||
bool SelectNode::onGUI() {
|
bool SelectNode::onGUI() {
|
||||||
ImGuiEx::NodeTitle("Select");
|
ImGuiEx::NodeTitle("Select");
|
||||||
outputSlot();
|
outputSlot();
|
||||||
inputSlot(); ImGui::TextUnformatted("Value");
|
inputSlot(ImGuiEx::PinShape::SQUARE); ImGui::TextUnformatted("Value");
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
for (u32 i = 0; i < m_options_count; ++i) {
|
for (u32 i = 0; i < m_options_count; ++i) {
|
||||||
|
@ -747,8 +897,8 @@ SelectNode::SelectNode(Node* parent, Controller& controller, IAllocator& allocat
|
||||||
anim::Node* SelectNode::compile(anim::Controller& controller) {
|
anim::Node* SelectNode::compile(anim::Controller& controller) {
|
||||||
if (m_options_count == 0) return nullptr;
|
if (m_options_count == 0) return nullptr;
|
||||||
ValueNode* value_node = castToValueNode(getInput(0));
|
ValueNode* value_node = castToValueNode(getInput(0));
|
||||||
// TODO make sure value_node returns i32
|
|
||||||
if (!value_node) return nullptr;
|
if (!value_node) return nullptr;
|
||||||
|
if (value_node->getReturnType() != anim::Value::NUMBER) return nullptr;
|
||||||
|
|
||||||
UniquePtr<anim::SelectNode> node = UniquePtr<anim::SelectNode>::create(controller.m_allocator, controller.m_allocator);
|
UniquePtr<anim::SelectNode> node = UniquePtr<anim::SelectNode>::create(controller.m_allocator, controller.m_allocator);
|
||||||
node->m_blend_length = m_blend_length;
|
node->m_blend_length = m_blend_length;
|
||||||
|
@ -781,7 +931,7 @@ void SelectNode::serialize(OutputMemoryStream& stream) const {
|
||||||
|
|
||||||
bool SwitchNode::onGUI() {
|
bool SwitchNode::onGUI() {
|
||||||
ImGuiEx::NodeTitle("Switch");
|
ImGuiEx::NodeTitle("Switch");
|
||||||
outputSlot();
|
outputSlot(ImGuiEx::PinShape::SQUARE);
|
||||||
inputSlot(); ImGui::TextUnformatted("Condition");
|
inputSlot(); ImGui::TextUnformatted("Condition");
|
||||||
|
|
||||||
inputSlot(); ImGui::TextUnformatted("True");
|
inputSlot(); ImGui::TextUnformatted("True");
|
||||||
|
@ -796,8 +946,8 @@ SwitchNode::SwitchNode(Node* parent, Controller& controller, IAllocator& allocat
|
||||||
|
|
||||||
anim::Node* SwitchNode::compile(anim::Controller& controller) {
|
anim::Node* SwitchNode::compile(anim::Controller& controller) {
|
||||||
ValueNode* value_node = castToValueNode(getInput(0));
|
ValueNode* value_node = castToValueNode(getInput(0));
|
||||||
// TODO make sure value_node returns bool
|
|
||||||
if (!value_node) return nullptr;
|
if (!value_node) return nullptr;
|
||||||
|
if (value_node->getReturnType() != anim::Value::BOOL) return nullptr;
|
||||||
|
|
||||||
UniquePtr<anim::SwitchNode> node = UniquePtr<anim::SwitchNode>::create(controller.m_allocator, controller.m_allocator);
|
UniquePtr<anim::SwitchNode> node = UniquePtr<anim::SwitchNode>::create(controller.m_allocator, controller.m_allocator);
|
||||||
node->m_blend_length = m_blend_length;
|
node->m_blend_length = m_blend_length;
|
||||||
|
@ -915,6 +1065,7 @@ Node* Node::create(Node* parent, Type type, Controller& controller, IAllocator&
|
||||||
case anim::NodeType::TREE: return LUMIX_NEW(allocator, TreeNode)(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::OUTPUT: return LUMIX_NEW(allocator, OutputNode)(parent, controller, allocator);
|
||||||
case anim::NodeType::INPUT: return LUMIX_NEW(allocator, InputNode)(parent, controller, allocator);
|
case anim::NodeType::INPUT: return LUMIX_NEW(allocator, InputNode)(parent, controller, allocator);
|
||||||
|
case anim::NodeType::PLAYRATE: return LUMIX_NEW(allocator, PlayRateNode)(parent, controller, allocator);
|
||||||
case anim::NodeType::CONSTANT: return LUMIX_NEW(allocator, ConstNode)(parent, controller, allocator);
|
case anim::NodeType::CONSTANT: return LUMIX_NEW(allocator, ConstNode)(parent, controller, allocator);
|
||||||
case anim::NodeType::SWITCH: return LUMIX_NEW(allocator, SwitchNode)(parent, controller, allocator);
|
case anim::NodeType::SWITCH: return LUMIX_NEW(allocator, SwitchNode)(parent, controller, allocator);
|
||||||
case anim::NodeType::CMP_EQ: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::CMP_EQ, allocator);
|
case anim::NodeType::CMP_EQ: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::CMP_EQ, allocator);
|
||||||
|
@ -929,6 +1080,7 @@ Node* Node::create(Node* parent, Type type, Controller& controller, IAllocator&
|
||||||
case anim::NodeType::DIV: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::DIV, allocator);
|
case anim::NodeType::DIV: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::DIV, allocator);
|
||||||
case anim::NodeType::MUL: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::MUL, allocator);
|
case anim::NodeType::MUL: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::MUL, allocator);
|
||||||
case anim::NodeType::SUB: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::SUB, allocator);
|
case anim::NodeType::SUB: return LUMIX_NEW(allocator, MathNode)(parent, controller, anim::NodeType::SUB, allocator);
|
||||||
|
case anim::NodeType::IK: return LUMIX_NEW(allocator, IKNode)(parent, controller, allocator);
|
||||||
case anim::NodeType::NONE: ASSERT(false); return nullptr;
|
case anim::NodeType::NONE: ASSERT(false); return nullptr;
|
||||||
}
|
}
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
|
|
|
@ -40,7 +40,7 @@ struct Node : NodeEditorNode {
|
||||||
virtual void serialize(OutputMemoryStream& stream) const;
|
virtual void serialize(OutputMemoryStream& stream) const;
|
||||||
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version);
|
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version);
|
||||||
virtual bool onGUI() { return false; }
|
virtual bool onGUI() { return false; }
|
||||||
virtual bool propertiesGUI() { return false; }
|
virtual bool propertiesGUI(Model& skeleton) { return false; }
|
||||||
virtual anim::Node* compile(anim::Controller& controller) = 0;
|
virtual anim::Node* compile(anim::Controller& controller) = 0;
|
||||||
virtual bool isValueNode() const { return false; }
|
virtual bool isValueNode() const { return false; }
|
||||||
virtual bool isPoseNode() const { return false; }
|
virtual bool isPoseNode() const { return false; }
|
||||||
|
@ -72,6 +72,7 @@ struct PoseNode : Node {
|
||||||
struct ValueNode : Node {
|
struct ValueNode : Node {
|
||||||
ValueNode(Node* parent, Controller& controller, IAllocator& allocator) : Node(parent, controller, allocator) {}
|
ValueNode(Node* parent, Controller& controller, IAllocator& allocator) : Node(parent, controller, allocator) {}
|
||||||
bool isValueNode() const override { return true; }
|
bool isValueNode() const override { return true; }
|
||||||
|
virtual anim::Value::Type getReturnType() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ConstNode : ValueNode {
|
struct ConstNode : ValueNode {
|
||||||
|
@ -83,6 +84,7 @@ struct ConstNode : ValueNode {
|
||||||
bool hasInputPins() const override { return false; }
|
bool hasInputPins() const override { return false; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
anim::Value::Type getReturnType() override { return m_value.type; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
|
|
||||||
anim::Value m_value;
|
anim::Value m_value;
|
||||||
|
@ -97,12 +99,43 @@ struct InputNode : ValueNode {
|
||||||
bool hasInputPins() const override { return false; }
|
bool hasInputPins() const override { return false; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
anim::Value::Type getReturnType() override;
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
|
|
||||||
u32 m_input_index = 0;
|
u32 m_input_index = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PlayRateNode final : PoseNode {
|
||||||
|
PlayRateNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||||
|
|
||||||
|
Type type() const override { return anim::NodeType::PLAYRATE; }
|
||||||
|
bool hasInputPins() const override { return true; }
|
||||||
|
bool hasOutputPins() const override { return true; }
|
||||||
|
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 IKNode final : PoseNode {
|
||||||
|
IKNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||||
|
|
||||||
|
Type type() const override { return anim::NodeType::IK; }
|
||||||
|
bool hasInputPins() const override { return true; }
|
||||||
|
bool hasOutputPins() const override { return true; }
|
||||||
|
bool onGUI() override;
|
||||||
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
|
|
||||||
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
|
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
||||||
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
|
||||||
|
i32 m_leaf_bone = 0;
|
||||||
|
u32 m_bones_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct OutputNode final : PoseNode {
|
struct OutputNode final : PoseNode {
|
||||||
OutputNode(Node* parent, Controller& controller, IAllocator& allocator);
|
OutputNode(Node* parent, Controller& controller, IAllocator& allocator);
|
||||||
|
|
||||||
|
@ -123,7 +156,7 @@ struct TreeNode final : PoseNode {
|
||||||
bool hasInputPins() const override { return false; }
|
bool hasInputPins() const override { return false; }
|
||||||
bool hasOutputPins() const override { return false; }
|
bool hasOutputPins() const override { return false; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
|
@ -139,7 +172,7 @@ struct SelectNode final : PoseNode {
|
||||||
bool hasInputPins() const override { return true; }
|
bool hasInputPins() const override { return true; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
|
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
||||||
|
@ -156,8 +189,8 @@ struct MathNode final : ValueNode {
|
||||||
bool hasInputPins() const override { return true; }
|
bool hasInputPins() const override { return true; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
|
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
anim::Value::Type getReturnType() override;
|
||||||
|
|
||||||
const anim::NodeType m_type;
|
const anim::NodeType m_type;
|
||||||
};
|
};
|
||||||
|
@ -184,7 +217,7 @@ struct AnimationNode final : PoseNode {
|
||||||
bool hasInputPins() const override { return false; }
|
bool hasInputPins() const override { return false; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
|
@ -202,7 +235,7 @@ struct Blend2DNode final : PoseNode {
|
||||||
bool hasInputPins() const override { return true; }
|
bool hasInputPins() const override { return true; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
|
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
||||||
|
@ -225,7 +258,7 @@ struct Blend1DNode final : PoseNode {
|
||||||
bool hasInputPins() const override { return true; }
|
bool hasInputPins() const override { return true; }
|
||||||
bool hasOutputPins() const override { return true; }
|
bool hasOutputPins() const override { return true; }
|
||||||
bool onGUI() override;
|
bool onGUI() override;
|
||||||
bool propertiesGUI() override;
|
bool propertiesGUI(Model& skeleton) override;
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) override;
|
||||||
anim::Node* compile(anim::Controller& controller) override;
|
anim::Node* compile(anim::Controller& controller) override;
|
||||||
|
|
|
@ -46,7 +46,7 @@ RuntimeContext::RuntimeContext(Controller& controller, IAllocator& allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeContext::setInput(u32 input_idx, float value) {
|
void RuntimeContext::setInput(u32 input_idx, float value) {
|
||||||
ASSERT(controller.m_inputs[input_idx].type == Value::FLOAT);
|
ASSERT(controller.m_inputs[input_idx].type == Value::NUMBER);
|
||||||
inputs[input_idx].f = value;
|
inputs[input_idx].f = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ Node* Node::create(NodeType type, Controller& controller) {
|
||||||
case NodeType::BLEND2D: return LUMIX_NEW(controller.m_allocator, Blend2DNode)(controller.m_allocator);
|
case NodeType::BLEND2D: return LUMIX_NEW(controller.m_allocator, Blend2DNode)(controller.m_allocator);
|
||||||
case NodeType::SELECT: return LUMIX_NEW(controller.m_allocator, SelectNode)(controller.m_allocator);
|
case NodeType::SELECT: return LUMIX_NEW(controller.m_allocator, SelectNode)(controller.m_allocator);
|
||||||
case NodeType::INPUT: return LUMIX_NEW(controller.m_allocator, InputNode);
|
case NodeType::INPUT: return LUMIX_NEW(controller.m_allocator, InputNode);
|
||||||
|
case NodeType::PLAYRATE: return LUMIX_NEW(controller.m_allocator, PlayRateNode)(controller.m_allocator);
|
||||||
case NodeType::CONSTANT: return LUMIX_NEW(controller.m_allocator, ConstNode);
|
case NodeType::CONSTANT: return LUMIX_NEW(controller.m_allocator, ConstNode);
|
||||||
case NodeType::ANIMATION: return LUMIX_NEW(controller.m_allocator, AnimationNode);
|
case NodeType::ANIMATION: return LUMIX_NEW(controller.m_allocator, AnimationNode);
|
||||||
case NodeType::SWITCH: return LUMIX_NEW(controller.m_allocator, SwitchNode)(controller.m_allocator);
|
case NodeType::SWITCH: return LUMIX_NEW(controller.m_allocator, SwitchNode)(controller.m_allocator);
|
||||||
|
@ -77,6 +78,7 @@ Node* Node::create(NodeType type, Controller& controller) {
|
||||||
case NodeType::DIV: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::DIV>);
|
case NodeType::DIV: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::DIV>);
|
||||||
case NodeType::ADD: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::ADD>);
|
case NodeType::ADD: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::ADD>);
|
||||||
case NodeType::SUB: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::SUB>);
|
case NodeType::SUB: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::SUB>);
|
||||||
|
case NodeType::IK: return LUMIX_NEW(controller.m_allocator, IKNode)(controller.m_allocator);
|
||||||
case NodeType::OUTPUT:
|
case NodeType::OUTPUT:
|
||||||
case NodeType::NONE:
|
case NodeType::NONE:
|
||||||
case NodeType::TREE: return nullptr; // editor only node
|
case NodeType::TREE: return nullptr; // editor only node
|
||||||
|
@ -241,7 +243,6 @@ Time SelectNode::length(const RuntimeContext& ctx) const { return Time::fromSeco
|
||||||
|
|
||||||
Time SelectNode::time(const RuntimeContext& ctx) const { return Time(0); }
|
Time SelectNode::time(const RuntimeContext& ctx) const { return Time(0); }
|
||||||
|
|
||||||
|
|
||||||
SwitchNode::~SwitchNode() {
|
SwitchNode::~SwitchNode() {
|
||||||
LUMIX_DELETE(m_allocator, m_value);
|
LUMIX_DELETE(m_allocator, m_value);
|
||||||
LUMIX_DELETE(m_allocator, m_true_node);
|
LUMIX_DELETE(m_allocator, m_true_node);
|
||||||
|
@ -336,6 +337,59 @@ Time SwitchNode::length(const RuntimeContext& ctx) const { return Time::fromSeco
|
||||||
|
|
||||||
Time SwitchNode::time(const RuntimeContext& ctx) const { return Time(0); }
|
Time SwitchNode::time(const RuntimeContext& ctx) const { return Time(0); }
|
||||||
|
|
||||||
|
|
||||||
|
IKNode::~IKNode() {
|
||||||
|
LUMIX_DELETE(m_allocator, m_alpha);
|
||||||
|
LUMIX_DELETE(m_allocator, m_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
IKNode::IKNode(IAllocator& allocator)
|
||||||
|
: m_allocator(allocator)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void IKNode::update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const {
|
||||||
|
m_input->update(ctx, root_motion);
|
||||||
|
float alpha = m_alpha->eval(ctx).toFloat();
|
||||||
|
if (alpha > 0) {
|
||||||
|
alpha = minimum(1.f, alpha);
|
||||||
|
Vec3 effector_position = m_effector_position->eval(ctx).toVec3();
|
||||||
|
ctx.blendstack.write(BlendStackInstructions::IK);
|
||||||
|
ctx.blendstack.write(alpha);
|
||||||
|
ctx.blendstack.write(effector_position);
|
||||||
|
ctx.blendstack.write(m_leaf_bone);
|
||||||
|
ctx.blendstack.write(m_bones_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::enter(RuntimeContext& ctx) {
|
||||||
|
m_input->enter(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::skip(RuntimeContext& ctx) const {
|
||||||
|
m_input->skip(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::serialize(OutputMemoryStream& stream) const {
|
||||||
|
stream.write(m_bones_count);
|
||||||
|
stream.write(m_leaf_bone);
|
||||||
|
serializeNode(stream, *m_alpha);
|
||||||
|
serializeNode(stream, *m_effector_position);
|
||||||
|
serializeNode(stream, *m_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
|
||||||
|
stream.read(m_bones_count);
|
||||||
|
stream.read(m_leaf_bone);
|
||||||
|
m_alpha = (ValueNode*)deserializeNode(stream, ctrl, version);
|
||||||
|
m_effector_position = (ValueNode*)deserializeNode(stream, ctrl, version);
|
||||||
|
m_input = (PoseNode*)deserializeNode(stream, ctrl, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
Time IKNode::length(const RuntimeContext& ctx) const { return m_input->length(ctx); }
|
||||||
|
|
||||||
|
Time IKNode::time(const RuntimeContext& ctx) const { return m_input->time(ctx); }
|
||||||
|
|
||||||
|
|
||||||
void InputNode::serialize(OutputMemoryStream& stream) const {
|
void InputNode::serialize(OutputMemoryStream& stream) const {
|
||||||
stream.write(m_input_index);
|
stream.write(m_input_index);
|
||||||
}
|
}
|
||||||
|
@ -647,4 +701,46 @@ void Blend1DNode::skip(RuntimeContext& ctx) const {
|
||||||
ctx.input_runtime.skip(sizeof(float));
|
ctx.input_runtime.skip(sizeof(float));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayRateNode::~PlayRateNode() {
|
||||||
|
LUMIX_DELETE(m_allocator, m_node);
|
||||||
|
LUMIX_DELETE(m_allocator, m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayRateNode::PlayRateNode(IAllocator& allocator)
|
||||||
|
: m_allocator(allocator)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void PlayRateNode::serialize(OutputMemoryStream& stream) const {
|
||||||
|
serializeNode(stream, *m_value);
|
||||||
|
serializeNode(stream, *m_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayRateNode::deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version) {
|
||||||
|
m_value = (ValueNode*)deserializeNode(stream, ctrl, version);
|
||||||
|
m_node = (PoseNode*)deserializeNode(stream, ctrl, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayRateNode::update(RuntimeContext& ctx, LocalRigidTransform& root_motion) const {
|
||||||
|
float td = ctx.time_delta.seconds();
|
||||||
|
ctx.time_delta = ctx.time_delta * maximum(0.f, m_value->eval(ctx).toFloat());
|
||||||
|
m_node->update(ctx, root_motion);
|
||||||
|
ctx.time_delta = Time::fromSeconds(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
Time PlayRateNode::length(const RuntimeContext& ctx) const {
|
||||||
|
return m_node->length(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Time PlayRateNode::time(const RuntimeContext& ctx) const {
|
||||||
|
return m_node->time(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayRateNode::enter(RuntimeContext& ctx) {
|
||||||
|
m_node->enter(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayRateNode::skip(RuntimeContext& ctx) const {
|
||||||
|
m_node->skip(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Lumix::anim
|
} // namespace Lumix::anim
|
|
@ -41,7 +41,9 @@ enum class NodeType : u32 {
|
||||||
SUB,
|
SUB,
|
||||||
CONSTANT,
|
CONSTANT,
|
||||||
AND,
|
AND,
|
||||||
OR
|
OR,
|
||||||
|
PLAYRATE,
|
||||||
|
IK
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Node {
|
struct Node {
|
||||||
|
@ -128,6 +130,23 @@ struct MathNode final : ValueNode {
|
||||||
ValueNode* m_input1 = nullptr;
|
ValueNode* m_input1 = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PlayRateNode final : PoseNode {
|
||||||
|
PlayRateNode(IAllocator& allocator);
|
||||||
|
~PlayRateNode();
|
||||||
|
NodeType type() const override { return anim::NodeType::PLAYRATE; }
|
||||||
|
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;
|
||||||
|
|
||||||
|
IAllocator& m_allocator;
|
||||||
|
ValueNode* m_value = nullptr;
|
||||||
|
PoseNode* m_node = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
struct Blend1DNode final : PoseNode {
|
struct Blend1DNode final : PoseNode {
|
||||||
Blend1DNode(IAllocator& allocator);
|
Blend1DNode(IAllocator& allocator);
|
||||||
~Blend1DNode();
|
~Blend1DNode();
|
||||||
|
@ -229,6 +248,26 @@ struct SwitchNode final : PoseNode {
|
||||||
Time m_blend_length;
|
Time m_blend_length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct IKNode final : PoseNode {
|
||||||
|
IKNode(IAllocator& allocator);
|
||||||
|
~IKNode();
|
||||||
|
NodeType type() const override { return anim::NodeType::IK; }
|
||||||
|
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;
|
||||||
|
|
||||||
|
IAllocator& m_allocator;
|
||||||
|
ValueNode* m_alpha = nullptr;
|
||||||
|
ValueNode* m_effector_position = nullptr;
|
||||||
|
PoseNode* m_input = nullptr;
|
||||||
|
u32 m_leaf_bone;
|
||||||
|
u32 m_bones_count;
|
||||||
|
};
|
||||||
|
|
||||||
struct AnimationNode final : PoseNode {
|
struct AnimationNode final : PoseNode {
|
||||||
NodeType type() const override { return anim::NodeType::ANIMATION; }
|
NodeType type() const override { return anim::NodeType::ANIMATION; }
|
||||||
void serialize(OutputMemoryStream& stream) const override;
|
void serialize(OutputMemoryStream& stream) const override;
|
||||||
|
|
|
@ -364,6 +364,7 @@ void FBXImporter::gatherAnimations()
|
||||||
const ofbx::AnimationLayer* anim_layer = anim.fbx->getLayer(0);
|
const ofbx::AnimationLayer* anim_layer = anim.fbx->getLayer(0);
|
||||||
if (!anim_layer || !anim_layer->getCurveNode(0)) {
|
if (!anim_layer || !anim_layer->getCurveNode(0)) {
|
||||||
m_animations.pop();
|
m_animations.pop();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool data_found = false;
|
bool data_found = false;
|
||||||
|
|
|
@ -42,6 +42,7 @@ struct ModelMeta {
|
||||||
WRITE_BOOL(split, false);
|
WRITE_BOOL(split, false);
|
||||||
WRITE_BOOL(import_vertex_colors, false);
|
WRITE_BOOL(import_vertex_colors, false);
|
||||||
WRITE_BOOL(vertex_color_is_ao, false);
|
WRITE_BOOL(vertex_color_is_ao, false);
|
||||||
|
WRITE_BOOL(ignore_animations, false);
|
||||||
WRITE_VALUE(anim_translation_error, 1.f);
|
WRITE_VALUE(anim_translation_error, 1.f);
|
||||||
WRITE_VALUE(anim_rotation_error, 1.f);
|
WRITE_VALUE(anim_rotation_error, 1.f);
|
||||||
WRITE_VALUE(scale, 1.f);
|
WRITE_VALUE(scale, 1.f);
|
||||||
|
@ -105,6 +106,7 @@ struct ModelMeta {
|
||||||
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "bake_vertex_ao", &bake_vertex_ao);
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "bake_vertex_ao", &bake_vertex_ao);
|
||||||
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "create_impostor", &create_impostor);
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "create_impostor", &create_impostor);
|
||||||
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "import_vertex_colors", &import_vertex_colors);
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "import_vertex_colors", &import_vertex_colors);
|
||||||
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "ignore_animations", &ignore_animations);
|
||||||
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "vertex_color_is_ao", &vertex_color_is_ao);
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "vertex_color_is_ao", &vertex_color_is_ao);
|
||||||
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "lod_count", &lod_count);
|
LuaWrapper::getOptionalField(L, LUA_GLOBALSINDEX, "lod_count", &lod_count);
|
||||||
|
|
||||||
|
@ -167,6 +169,7 @@ struct ModelMeta {
|
||||||
bool force_skin = false;
|
bool force_skin = false;
|
||||||
bool import_vertex_colors = false;
|
bool import_vertex_colors = false;
|
||||||
bool vertex_color_is_ao = false;
|
bool vertex_color_is_ao = false;
|
||||||
|
bool ignore_animations = false;
|
||||||
u8 autolod_mask = 0;
|
u8 autolod_mask = 0;
|
||||||
u32 lod_count = 1;
|
u32 lod_count = 1;
|
||||||
float anim_rotation_error = 1.f;
|
float anim_rotation_error = 1.f;
|
||||||
|
|
|
@ -1957,6 +1957,8 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
|
||||||
saveUndo(ImGui::Checkbox("##frcskn", &m_meta.force_skin));
|
saveUndo(ImGui::Checkbox("##frcskn", &m_meta.force_skin));
|
||||||
ImGuiEx::Label("Split");
|
ImGuiEx::Label("Split");
|
||||||
saveUndo(ImGui::Checkbox("##split", &m_meta.split));
|
saveUndo(ImGui::Checkbox("##split", &m_meta.split));
|
||||||
|
ImGuiEx::Label("Ignore animations");
|
||||||
|
saveUndo(ImGui::Checkbox("##ignoreanim", &m_meta.ignore_animations));
|
||||||
ImGuiEx::Label("Create impostor mesh");
|
ImGuiEx::Label("Create impostor mesh");
|
||||||
saveUndo(ImGui::Checkbox("##creimp", &m_meta.create_impostor));
|
saveUndo(ImGui::Checkbox("##creimp", &m_meta.create_impostor));
|
||||||
if (m_meta.create_impostor) {
|
if (m_meta.create_impostor) {
|
||||||
|
@ -2476,6 +2478,7 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
|
||||||
compiler.addResource(physics_geom, tmp);
|
compiler.addResource(physics_geom, tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!meta.ignore_animations) {
|
||||||
if (meta.clips.empty()) {
|
if (meta.clips.empty()) {
|
||||||
const Array<FBXImporter::ImportAnimation>& animations = importer.getAnimations();
|
const Array<FBXImporter::ImportAnimation>& animations = importer.getAnimations();
|
||||||
for (const FBXImporter::ImportAnimation& anim : animations) {
|
for (const FBXImporter::ImportAnimation& anim : animations) {
|
||||||
|
@ -2489,6 +2492,7 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
|
||||||
compiler.addResource(ResourceType("animation"), tmp);
|
compiler.addResource(ResourceType("animation"), tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}, &m_subres_signal, 2);
|
}, &m_subres_signal, 2);
|
||||||
}
|
}
|
||||||
|
@ -2540,7 +2544,9 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
|
||||||
cfg.origin = FBXImporter::ImportConfig::Origin::SOURCE;
|
cfg.origin = FBXImporter::ImportConfig::Origin::SOURCE;
|
||||||
any_written = m_fbx_importer.writeModel(src, cfg) || any_written;
|
any_written = m_fbx_importer.writeModel(src, cfg) || any_written;
|
||||||
any_written = m_fbx_importer.writeMaterials(filepath, cfg) || any_written;
|
any_written = m_fbx_importer.writeMaterials(filepath, cfg) || any_written;
|
||||||
|
if (!meta.ignore_animations) {
|
||||||
any_written = m_fbx_importer.writeAnimations(filepath, cfg) || any_written;
|
any_written = m_fbx_importer.writeAnimations(filepath, cfg) || any_written;
|
||||||
|
}
|
||||||
any_written = m_fbx_importer.writePhysics(filepath, cfg) || any_written;
|
any_written = m_fbx_importer.writePhysics(filepath, cfg) || any_written;
|
||||||
return any_written;
|
return any_written;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue