animation system - play rate node; ik

This commit is contained in:
Mikulas Florek 2023-08-29 18:49:38 +02:00
parent 3952f184c0
commit 9208bbdea4
13 changed files with 565 additions and 205 deletions

Binary file not shown.

View file

@ -56,11 +56,6 @@ struct AnimationModuleImpl final : AnimationModule {
Flags flags = Flags::NONE;
anim::RuntimeContext* ctx = nullptr;
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 {
}
}
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
{
i32 getAnimatorInputIndex(EntityRef entity, const char* name) const override {
const Animator& animator = m_animators[m_animator_map[entity]];
for (anim::Controller::Input& input : animator.resource->m_inputs) {
if (input.name == name) return i32(&input - animator.resource->m_inputs.begin());
@ -151,7 +135,7 @@ struct AnimationModuleImpl final : AnimationModule {
if (!iter.isValid()) return;
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;
}
else {
@ -513,11 +497,21 @@ 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::Value::FLOAT) return;
if (animator.resource->m_inputs[input_idx].type != anim::Value::NUMBER) return;
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 {
Animator& animator = m_animators[m_animator_map[entity]];
if (!animator.ctx) return;
@ -533,7 +527,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::Value::FLOAT);
ASSERT(animator.resource->m_inputs[input_idx].type == anim::Value::NUMBER);
return animator.ctx->inputs[input_idx].f;
}
@ -548,6 +542,16 @@ struct AnimationModuleImpl final : AnimationModule {
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
{
auto iter = m_animator_map.find(entity);
@ -626,12 +630,6 @@ struct AnimationModuleImpl final : AnimationModule {
model->getRelativePose(*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);
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)
{
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, bool))&AnimationModule::setAnimatorInput>("setBoolInput", "AnimationModule::setAnimatorInput")
.LUMIX_FUNC_EX(AnimationModule::getAnimatorInputIndex, "getInputIndex")
.LUMIX_FUNC_EX(AnimationModule::setAnimatorIK, "setIK")
.LUMIX_PROP(AnimatorSource, "Source").resourceAttribute(anim::Controller::TYPE)
.LUMIX_PROP(AnimatorDefaultSet, "Default set")
.LUMIX_PROP(AnimatorUseRootMotion, "Use root motion")

View file

@ -35,8 +35,10 @@ struct AnimationModule : IModule {
virtual void updateAnimator(EntityRef entity, float time_delta) = 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, Vec3 value) = 0;
virtual void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) = 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 struct LocalRigidTransform getAnimatorRootMotion(EntityRef entity) = 0;
virtual void setAnimatorUseRootMotion(EntityRef entity, bool value) = 0;
@ -49,7 +51,6 @@ struct AnimationModule : IModule {
virtual u32 getAnimatorDefaultSet(EntityRef entity) = 0;
virtual anim::Controller* getAnimatorController(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 OutputMemoryStream& beginBlendstackUpdate(EntityRef entity) = 0;
virtual void endBlendstackUpdate(EntityRef entity) = 0;

View file

@ -18,7 +18,6 @@ Controller::Controller(const Path& path, ResourceManager& resource_manager, IAll
, m_allocator(allocator)
, m_animation_entries(allocator)
, m_inputs(allocator)
, m_ik(allocator)
, m_bone_masks(allocator)
{}
@ -94,11 +93,6 @@ void Controller::serialize(OutputMemoryStream& stream) {
stream.write(entry.set);
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());
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;
}
u32 ik_count = stream.read<u32>();
m_ik.reserve(ik_count);
for (u32 i = 0; i < ik_count; ++i) {
IK& ik = m_ik.emplace(m_allocator);
stream.read(ik.max_iterations);
stream.readArray(&ik.bones);
}
NodeType type;
stream.read(type);
m_root = (PoseNode*)Node::create(type, *this);
@ -162,6 +148,113 @@ static void getPose(const anim::RuntimeContext& ctx, Time time, float weight, u3
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) {
InputMemoryStream bs(ctx.blendstack);
@ -170,6 +263,14 @@ void evalBlendStack(const anim::RuntimeContext& ctx, Pose& pose) {
bs.read(instr);
switch (instr) {
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: {
u32 slot = bs.read<u32>();
float weight = bs.read<float>();

View file

@ -15,21 +15,25 @@ namespace anim {
struct Value {
enum Type : u32 {
FLOAT,
BOOL
NUMBER,
BOOL,
VEC3
};
Type type;
union {
float f;
bool b;
Vec3 v3;
};
Value() : f(0), type(FLOAT) {}
Value(float f) : f(f), type(FLOAT) {}
Value() : f(0), type(NUMBER) {}
Value(float f) : f(f), type(NUMBER) {}
Value(bool b) : b(b), type(BOOL) {}
float toFloat() const { ASSERT(type == FLOAT); return f; }
i32 toI32() const { ASSERT(type == FLOAT); return i32(f + 0.5f); }
Value(Vec3 v3) : v3(v3), type(VEC3) {}
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; }
Vec3 toVec3() const { ASSERT(type == VEC3); return v3; }
};
struct RuntimeContext {
@ -53,6 +57,7 @@ struct RuntimeContext {
enum BlendStackInstructions : u8 {
END,
SAMPLE,
IK
};
enum class ControllerVersion : u32 {
@ -88,18 +93,11 @@ struct Controller final : Resource {
StaticString<32> name;
};
struct IK {
IK(IAllocator& allocator) : bones(allocator) {}
u32 max_iterations = 5;
Array<BoneNameHash> bones;
};
IAllocator& m_allocator;
struct PoseNode* m_root = nullptr;
Array<AnimationEntry> m_animation_entries;
Array<BoneMask> m_bone_masks;
Array<Input> m_inputs;
Array<IK> m_ik;
u32 m_animation_slots_count = 0;
private:

View file

@ -209,6 +209,8 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
.visitType(anim::NodeType::SUB, "Subtract");
visitor.endCategory();
}
visitor.visitType(anim::NodeType::IK, "Inverse kinematics", 0);
if (visitor.beginCategory("Inputs")) {
struct : INodeTypeVisitor::INodeCreator {
@ -228,6 +230,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
visitor
.visitType(anim::NodeType::LAYERS, "Layers", 'L')
.visitType(anim::NodeType::PLAYRATE, "Play rate", 0)
.visitType(anim::NodeType::SELECT, "Select", 'S')
.visitType(anim::NodeType::SWITCH, "Switch", 'W')
.visitType(anim::NodeType::TREE, "Tree", 'T');
@ -238,6 +241,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
, NodeEditor(allocator)
, m_allocator(allocator)
, m_to_fix_skeleton(allocator)
, m_visualize_position_inputs(allocator)
, m_recording(app, allocator)
, m_app(app)
, m_plugin(plugin)
@ -503,6 +507,21 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
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) {
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);
switch (instr) {
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: {
u32 slot = blob.read<u32>();
float weight = blob.read<float>();
@ -617,6 +646,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
void debuggerUI(World& world, EntityRef entity) {
processControllerMapping(world, entity);
processPositionVisualizations(world, entity);
const ComponentType animator_type = reflection::getComponentType("animator");
AnimationModule* module = (AnimationModule*)world.getModule(animator_type);
@ -630,7 +660,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
ImGui::PushID(&input);
const u32 idx = u32(&input - ctrl->m_inputs.begin());
switch (input.type) {
case anim::Value::FLOAT: {
case anim::Value::NUMBER: {
float val = module->getAnimatorFloatInput(entity, idx);
ImGuiEx::Label(input.name);
@ -662,6 +692,23 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
}
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();
}
@ -995,7 +1042,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
saveUndo(ImGui::InputText("##name", input.name.data, sizeof(input.name.data)));
ImGui::NextColumn();
ImGui::SetNextItemWidth(-1);
if (ImGui::Combo("##type", (int*)&input.type, "number\0bool")) {
if (ImGui::Combo("##type", (int*)&input.type, "number\0bool\0vector 3\0")) {
saveUndo(true);
}
ImGui::NextColumn();
@ -1124,7 +1171,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
if (m_current_node) {
for (Node* n : m_current_node->m_nodes) {
if (n->m_selected) {
n->propertiesGUI();
n->propertiesGUI(*m_skeleton);
any_selected = true;
break;
}
@ -1177,11 +1224,6 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
i32 axis_y = -1;
};
struct IKDebug {
bool enabled = false;
Vec3 target = Vec3(0);
};
WorldViewer m_viewer;
IAllocator& m_allocator;
Array<Path> m_to_fix_skeleton;
@ -1195,7 +1237,7 @@ struct ControllerEditorImpl : ControllerEditor, AssetBrowser::IPlugin, AssetComp
float m_playback_speed = 1.f;
bool m_show_skeleton = true;
ControllerDebugMapping m_controller_debug_mapping;
IKDebug m_ik_debug[4];
Array<u32> m_visualize_position_inputs;
TextFilter m_node_filter;
u32 m_node_filter_selection = 0;
struct Record {

View file

@ -108,6 +108,9 @@ anim::Node* Blend2DNode::compile(anim::Controller& controller) {
ValueNode* x = castToValueNode(getInput(0));
ValueNode* y = castToValueNode(getInput(1));
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_y_value = (anim::ValueNode*)y->compile(controller);
if (!node->m_x_value) return nullptr;
@ -116,7 +119,7 @@ anim::Node* Blend2DNode::compile(anim::Controller& controller) {
return node.detach();
}
bool Blend2DNode::propertiesGUI() {
bool Blend2DNode::propertiesGUI(Model& skeleton) {
ImGuiEx::Label("Name");
bool res = inputString("##name", &m_name);
@ -345,7 +348,7 @@ Blend1DNode::Blend1DNode(Node* parent, Controller& controller, IAllocator& alloc
, m_name("blend1d", allocator)
{}
bool Blend1DNode::propertiesGUI() {
bool Blend1DNode::propertiesGUI(Model& skeleton) {
ImGuiEx::Label("Name");
bool res = inputString("##name", &m_name);
@ -395,6 +398,8 @@ anim::Node* Blend1DNode::compile(anim::Controller& controller) {
m_children.copyTo(node->m_children);
ValueNode* val = castToValueNode(getInput(0));
if (!val) return nullptr;
if (val->getReturnType() != anim::Value::NUMBER) return nullptr;
node->m_value = (anim::ValueNode*)val->compile(controller);
if (!node->m_value) return nullptr;
@ -424,7 +429,7 @@ anim::Node* AnimationNode::compile(anim::Controller& controller) {
return node;
}
bool AnimationNode::propertiesGUI() {
bool AnimationNode::propertiesGUI(Model& skeleton) {
ImGuiEx::Label("Slot");
static i32 selected = -1;
bool res = editSlot(m_controller, "##slot", &m_slot);
@ -524,7 +529,7 @@ bool InputNode::onGUI() {
return false;
}
bool InputNode::propertiesGUI() {
bool InputNode::propertiesGUI(Model& skeleton) {
return editInput("Input", &m_input_index, m_controller);
}
@ -535,6 +540,11 @@ anim::Node* InputNode::compile(anim::Controller& controller) {
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)
: 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)
: ValueNode(parent, controller, allocator)
, 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)
: PoseNode(parent, controller, allocator)
{}
@ -671,7 +821,7 @@ anim::Node* TreeNode::compile(anim::Controller& controller) {
return m_nodes[0]->compile(controller);
}
bool TreeNode::propertiesGUI() {
bool TreeNode::propertiesGUI(Model& skeleton) {
ImGuiEx::Label("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); }
bool SelectNode::propertiesGUI() {
bool SelectNode::propertiesGUI(Model& skeleton) {
float node_blend_length = m_blend_length.seconds();
ImGuiEx::Label("Blend length");
if (ImGui::DragFloat("##bl", &node_blend_length)) {
@ -706,7 +856,7 @@ bool SelectNode::propertiesGUI() {
bool SelectNode::onGUI() {
ImGuiEx::NodeTitle("Select");
outputSlot();
inputSlot(); ImGui::TextUnformatted("Value");
inputSlot(ImGuiEx::PinShape::SQUARE); ImGui::TextUnformatted("Value");
bool changed = false;
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) {
if (m_options_count == 0) return nullptr;
ValueNode* value_node = castToValueNode(getInput(0));
// TODO make sure value_node returns i32
if (!value_node) return nullptr;
if (value_node->getReturnType() != anim::Value::NUMBER) return nullptr;
UniquePtr<anim::SelectNode> node = UniquePtr<anim::SelectNode>::create(controller.m_allocator, controller.m_allocator);
node->m_blend_length = m_blend_length;
@ -781,7 +931,7 @@ void SelectNode::serialize(OutputMemoryStream& stream) const {
bool SwitchNode::onGUI() {
ImGuiEx::NodeTitle("Switch");
outputSlot();
outputSlot(ImGuiEx::PinShape::SQUARE);
inputSlot(); ImGui::TextUnformatted("Condition");
inputSlot(); ImGui::TextUnformatted("True");
@ -796,8 +946,8 @@ SwitchNode::SwitchNode(Node* parent, Controller& controller, IAllocator& allocat
anim::Node* SwitchNode::compile(anim::Controller& controller) {
ValueNode* value_node = castToValueNode(getInput(0));
// TODO make sure value_node returns bool
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);
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::OUTPUT: return LUMIX_NEW(allocator, OutputNode)(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::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);
@ -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::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::IK: return LUMIX_NEW(allocator, IKNode)(parent, controller, allocator);
case anim::NodeType::NONE: ASSERT(false); return nullptr;
}
ASSERT(false);

View file

@ -40,7 +40,7 @@ struct Node : NodeEditorNode {
virtual void serialize(OutputMemoryStream& stream) const;
virtual void deserialize(InputMemoryStream& stream, Controller& ctrl, u32 version);
virtual bool onGUI() { return false; }
virtual bool propertiesGUI() { return false; }
virtual bool propertiesGUI(Model& skeleton) { return false; }
virtual anim::Node* compile(anim::Controller& controller) = 0;
virtual bool isValueNode() const { return false; }
virtual bool isPoseNode() const { return false; }
@ -72,6 +72,7 @@ struct PoseNode : Node {
struct ValueNode : Node {
ValueNode(Node* parent, Controller& controller, IAllocator& allocator) : Node(parent, controller, allocator) {}
bool isValueNode() const override { return true; }
virtual anim::Value::Type getReturnType() = 0;
};
struct ConstNode : ValueNode {
@ -83,6 +84,7 @@ struct ConstNode : ValueNode {
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
anim::Node* compile(anim::Controller& controller) override;
anim::Value::Type getReturnType() override { return m_value.type; }
bool onGUI() override;
anim::Value m_value;
@ -97,12 +99,43 @@ struct InputNode : ValueNode {
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
anim::Node* compile(anim::Controller& controller) override;
anim::Value::Type getReturnType() override;
bool onGUI() override;
bool propertiesGUI() override;
bool propertiesGUI(Model& skeleton) override;
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 {
OutputNode(Node* parent, Controller& controller, IAllocator& allocator);
@ -123,7 +156,7 @@ struct TreeNode final : PoseNode {
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return false; }
bool onGUI() override;
bool propertiesGUI() override;
bool propertiesGUI(Model& skeleton) override;
anim::Node* compile(anim::Controller& controller) override;
void serialize(OutputMemoryStream& stream) const override;
@ -139,7 +172,7 @@ struct SelectNode final : PoseNode {
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
bool propertiesGUI(Model& skeleton) override;
void serialize(OutputMemoryStream& stream) const 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 hasOutputPins() const override { return true; }
bool onGUI() override;
anim::Node* compile(anim::Controller& controller) override;
anim::Value::Type getReturnType() override;
const anim::NodeType m_type;
};
@ -184,7 +217,7 @@ struct AnimationNode final : PoseNode {
bool hasInputPins() const override { return false; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
bool propertiesGUI(Model& skeleton) override;
anim::Node* compile(anim::Controller& controller) override;
void serialize(OutputMemoryStream& stream) const override;
@ -202,7 +235,7 @@ struct Blend2DNode final : PoseNode {
bool hasInputPins() const override { return true; }
bool hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() override;
bool propertiesGUI(Model& skeleton) override;
void serialize(OutputMemoryStream& stream) const 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 hasOutputPins() const override { return true; }
bool onGUI() override;
bool propertiesGUI() 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;

View file

@ -46,7 +46,7 @@ RuntimeContext::RuntimeContext(Controller& controller, IAllocator& allocator)
}
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;
}
@ -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::SELECT: return LUMIX_NEW(controller.m_allocator, SelectNode)(controller.m_allocator);
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::ANIMATION: return LUMIX_NEW(controller.m_allocator, AnimationNode);
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::ADD: return LUMIX_NEW(controller.m_allocator, MathNode<NodeType::ADD>);
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::NONE:
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); }
SwitchNode::~SwitchNode() {
LUMIX_DELETE(m_allocator, m_value);
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); }
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 {
stream.write(m_input_index);
}
@ -647,4 +701,46 @@ void Blend1DNode::skip(RuntimeContext& ctx) const {
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

View file

@ -41,7 +41,9 @@ enum class NodeType : u32 {
SUB,
CONSTANT,
AND,
OR
OR,
PLAYRATE,
IK
};
struct Node {
@ -128,6 +130,23 @@ struct MathNode final : ValueNode {
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 {
Blend1DNode(IAllocator& allocator);
~Blend1DNode();
@ -229,6 +248,26 @@ struct SwitchNode final : PoseNode {
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 {
NodeType type() const override { return anim::NodeType::ANIMATION; }
void serialize(OutputMemoryStream& stream) const override;

View file

@ -364,6 +364,7 @@ void FBXImporter::gatherAnimations()
const ofbx::AnimationLayer* anim_layer = anim.fbx->getLayer(0);
if (!anim_layer || !anim_layer->getCurveNode(0)) {
m_animations.pop();
continue;
}
bool data_found = false;

View file

@ -42,6 +42,7 @@ struct ModelMeta {
WRITE_BOOL(split, false);
WRITE_BOOL(import_vertex_colors, false);
WRITE_BOOL(vertex_color_is_ao, false);
WRITE_BOOL(ignore_animations, false);
WRITE_VALUE(anim_translation_error, 1.f);
WRITE_VALUE(anim_rotation_error, 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, "create_impostor", &create_impostor);
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, "lod_count", &lod_count);
@ -167,6 +169,7 @@ struct ModelMeta {
bool force_skin = false;
bool import_vertex_colors = false;
bool vertex_color_is_ao = false;
bool ignore_animations = false;
u8 autolod_mask = 0;
u32 lod_count = 1;
float anim_rotation_error = 1.f;

View file

@ -1957,6 +1957,8 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
saveUndo(ImGui::Checkbox("##frcskn", &m_meta.force_skin));
ImGuiEx::Label("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");
saveUndo(ImGui::Checkbox("##creimp", &m_meta.create_impostor));
if (m_meta.create_impostor) {
@ -2476,17 +2478,19 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
compiler.addResource(physics_geom, tmp);
}
if (meta.clips.empty()) {
const Array<FBXImporter::ImportAnimation>& animations = importer.getAnimations();
for (const FBXImporter::ImportAnimation& anim : animations) {
Path tmp(anim.name, ".ani:", path);
compiler.addResource(ResourceType("animation"), tmp);
if (!meta.ignore_animations) {
if (meta.clips.empty()) {
const Array<FBXImporter::ImportAnimation>& animations = importer.getAnimations();
for (const FBXImporter::ImportAnimation& anim : animations) {
Path tmp(anim.name, ".ani:", path);
compiler.addResource(ResourceType("animation"), tmp);
}
}
}
else {
for (const FBXImporter::ImportConfig::Clip& clip : meta.clips) {
Path tmp(clip.name, ".ani:", Path(path));
compiler.addResource(ResourceType("animation"), tmp);
else {
for (const FBXImporter::ImportConfig::Clip& clip : meta.clips) {
Path tmp(clip.name, ".ani:", Path(path));
compiler.addResource(ResourceType("animation"), tmp);
}
}
}
@ -2540,7 +2544,9 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
cfg.origin = FBXImporter::ImportConfig::Origin::SOURCE;
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.writeAnimations(filepath, cfg) || any_written;
if (!meta.ignore_animations) {
any_written = m_fbx_importer.writeAnimations(filepath, cfg) || any_written;
}
any_written = m_fbx_importer.writePhysics(filepath, cfg) || any_written;
return any_written;
}