diff --git a/editor/includes/viewport/cameracontroller.h b/editor/includes/viewport/cameracontroller.h index 3506bea53..a846d1bcb 100644 --- a/editor/includes/viewport/cameracontroller.h +++ b/editor/includes/viewport/cameracontroller.h @@ -61,7 +61,7 @@ class EDITOR_EXPORT CameraController : public QObject { void blockRotations(bool flag) { m_blockRotation = flag; } bool isPickingBlocked() const { return m_blockPicking; } - void blockPicking(bool flag) { m_blockPicking = flag; } + void blockPicking(bool flag) { m_blockPicking = flag; m_blockPickingBack = flag; } bool isPickingOverlaped() const { return m_overlapPicking; } void overlapPicking(bool flag) { m_overlapPicking = flag; } diff --git a/editor/src/viewport/cameracontroller.cpp b/editor/src/viewport/cameracontroller.cpp index d5484e249..403684d7e 100644 --- a/editor/src/viewport/cameracontroller.cpp +++ b/editor/src/viewport/cameracontroller.cpp @@ -134,7 +134,6 @@ void CameraController::update() { if(m_mouseSaved != p) { m_cameraInMove = true; - m_blockPickingBack = m_blockPicking; m_blockPicking = true; } m_mouseSaved = p; diff --git a/engine/includes/components/skinnedspriterender.h b/engine/includes/components/skinnedspriterender.h new file mode 100644 index 000000000..d44d84da4 --- /dev/null +++ b/engine/includes/components/skinnedspriterender.h @@ -0,0 +1,45 @@ +#ifndef SKINNEDSPRITERENDER_H +#define SKINNEDSPRITERENDER_H + +#include + +#include + +class ENGINE_EXPORT SkinnedSpriteRender : public SpriteRender { + A_OBJECT(SkinnedSpriteRender, SpriteRender, Components/2D) + + A_PROPERTIES( + A_PROPERTYEX(Armature *, armature, SkinnedSpriteRender::armature, SkinnedSpriteRender::setArmature, "editor=Component"), + A_PROPERTY(Vector3, boundsCenter, SkinnedSpriteRender::boundsCenter, SkinnedSpriteRender::setBoundsCenter), + A_PROPERTY(Vector3, boundsExtent, SkinnedSpriteRender::boundsExtent, SkinnedSpriteRender::setBoundsExtent) + ) + A_NOMETHODS() + A_NOENUMS() + +public: + SkinnedSpriteRender(); + + Vector3 boundsCenter() const; + void setBoundsCenter(const Vector3 ¢er); + + Vector3 boundsExtent() const; + void setBoundsExtent(const Vector3 &extent); + + Armature *armature() const; + void setArmature(Armature *armature); + +private: + void setMaterialsList(const std::list &materials) override; + + AABBox localBound() override; + + void onReferenceDestroyed() override; + +private: + AABBox m_bounds; + + Armature *m_armature; + +}; + +#endif // SKINNEDSPRITERENDER_H diff --git a/engine/includes/components/spriterender.h b/engine/includes/components/spriterender.h index b4519318b..8024fdd6b 100644 --- a/engine/includes/components/spriterender.h +++ b/engine/includes/components/spriterender.h @@ -65,7 +65,7 @@ class ENGINE_EXPORT SpriteRender : public Renderable { MaterialInstance *materialInstance(int index) override; -private: +protected: Mesh *meshToDraw() override; void setMaterialsList(const std::list &materials) override; diff --git a/engine/src/components/actor.cpp b/engine/src/components/actor.cpp index dd8aaad99..8e2aec71d 100644 --- a/engine/src/components/actor.cpp +++ b/engine/src/components/actor.cpp @@ -37,6 +37,17 @@ static Component *componentInChildHelper(const TString &type, Object *parent) { return nullptr; } +static void componentsInChildHelper(const TString &type, Object *parent, std::list &result) { + PROFILE_FUNCTION(); + for(auto it : parent->getChildren()) { + const MetaObject *meta = it->metaObject(); + if(meta->canCastTo(type.data())) { + result.push_back(static_cast(it)); + } + componentsInChildHelper(type, it, result); + } +} + /*! \class Actor \brief Base class for all entities in Thunder Engine. @@ -272,10 +283,7 @@ std::list Actor::componentsInChild(const TString &type) const { PROFILE_FUNCTION(); std::list result; for(auto it : getChildren()) { - Component *component = componentInChildHelper(type, it); - if(component) { - result.push_back(component); - } + componentsInChildHelper(type, it, result); } return result; } diff --git a/engine/src/components/animator.cpp b/engine/src/components/animator.cpp index e2a60eda6..0abbc47d8 100644 --- a/engine/src/components/animator.cpp +++ b/engine/src/components/animator.cpp @@ -9,11 +9,12 @@ #include "timer.h" #include -enum TransformFlags { +enum class TransformFlags { Position = 1, Rotation, Scale, - Quat + Quat, + Object }; /*! @@ -58,10 +59,14 @@ void Animator::process(float dt) { for(auto &it : m_bindProperties) { TargetProperty &target = it.second; - switch(target.defaultValue.type()) { - case MetaType::QUATERNION: sampleQuaternion(dt, target); break; - case MetaType::STRING: sampleString(dt, target); break; - default: sampleVector4(dt, target); break; + if(static_cast(target.flag) == TransformFlags::Object) { + sampleString(dt, target); + } else { + switch(target.defaultValue.type()) { + case MetaType::QUATERNION: sampleQuaternion(dt, target); break; + case MetaType::STRING: sampleString(dt, target); break; + default: sampleVector4(dt, target); break; + } } } @@ -109,7 +114,7 @@ void Animator::sampleVector4(float dt, TargetProperty &target) { case MetaType::FLOAT: target.property.write(target.object, vec4.x); break; case MetaType::VECTOR2: target.property.write(target.object, Vector2(vec4)); break; case MetaType::VECTOR3: { - switch(target.flag) { + switch(static_cast(target.flag)) { case TransformFlags::Position: static_cast(target.object)->setPosition(vec4); break; case TransformFlags::Rotation: static_cast(target.object)->setRotation(vec4); break; case TransformFlags::Scale: static_cast(target.object)->setScale(vec4); break; @@ -154,7 +159,7 @@ void Animator::sampleQuaternion(float dt, TargetProperty &target) { } if(updated) { - if(target.flag == TransformFlags::Quat) { + if(static_cast(target.flag) == TransformFlags::Quat) { static_cast(target.object)->setQuaternion(quat); } else { target.property.write(target.object, quat); @@ -195,7 +200,15 @@ void Animator::sampleString(float dt, TargetProperty &target) { } if(updated) { - target.property.write(target.object, str); + if(static_cast(target.flag) == TransformFlags::Object) { + Resource *resource = nullptr; + if(!str.isEmpty()) { + resource = Engine::loadResourceAsync(str); + } + target.property.write(target.object, Variant(target.defaultValue.userType(), &resource)); + } else { + target.property.write(target.object, str); + } } } @@ -353,7 +366,12 @@ Animator::TargetProperty *Animator::bindTrack(const AnimationTrack &track) { if(t) { auto transformIt = tranformFlags.find(track.property()); if(transformIt != tranformFlags.end()) { - data.flag = transformIt->second; + data.flag = static_cast(transformIt->second); + } + } else { + int flags = MetaType::table(data.defaultValue.userType())->flags; + if(flags & MetaType::BASE_OBJECT) { + data.flag = static_cast(TransformFlags::Object); } } diff --git a/engine/src/components/skinnedspriterender.cpp b/engine/src/components/skinnedspriterender.cpp new file mode 100644 index 000000000..ff907f4d8 --- /dev/null +++ b/engine/src/components/skinnedspriterender.cpp @@ -0,0 +1,109 @@ +#include "components/skinnedspriterender.h" + +#include "resources/material.h" + +/*! + \class SkinnedSpriteRender + \brief Draws an animated skeletal sprite for the 2D graphics. + \inmodule Components + + The SkinnedSpriteRender component allows you to display 2D Skeletal Sprite to use in both 2D and 3D scenes. +*/ + +SkinnedSpriteRender::SkinnedSpriteRender() : + m_armature(nullptr) { + + m_bounds.radius = 0.0f; + m_surfaceType = Material::Skinned; +} +/*! + \internal +*/ +AABBox SkinnedSpriteRender::localBound() { + return m_bounds; +} +/*! + Returns the center of the local bounding box. +*/ +Vector3 SkinnedSpriteRender::boundsCenter() const { + return m_bounds.center; +} +/*! + Sets the \a center of the local bounding box. +*/ +void SkinnedSpriteRender::setBoundsCenter(const Vector3 ¢er) { + m_bounds.center = center; +} +/*! + Returns the extent of the local bounding box. +*/ +Vector3 SkinnedSpriteRender::boundsExtent() const { + return m_bounds.extent; +} +/*! + Sets the \a extent of the local bounding box. +*/ +void SkinnedSpriteRender::setBoundsExtent(const Vector3 &extent) { + m_bounds.extent = extent; +} +/*! + Returns a Armature component for the attached skeleton. +*/ +Armature *SkinnedSpriteRender::armature() const { + return m_armature; +} +/*! + Attaches an \a armature skeleton. +*/ +void SkinnedSpriteRender::setArmature(Armature *armature) { + if(m_armature != armature) { + if(m_armature) { + disconnect(m_armature, _SIGNAL(destroyed()), this, _SLOT(onReferenceDestroyed())); + + for(auto it : m_materials) { + m_armature->removeInstance(it); + } + } + + m_armature = armature; + if(m_armature) { + connect(m_armature, _SIGNAL(destroyed()), this, _SLOT(onReferenceDestroyed())); + + for(auto it : m_materials) { + m_armature->addInstance(it); + } + + m_armature->update(); + } + } +} +/*! + \internal +*/ +void SkinnedSpriteRender::setMaterialsList(const std::list &materials) { + if(m_armature) { + for(auto it : m_materials) { + m_armature->removeInstance(it); + } + } + + SpriteRender::setMaterialsList(materials); + + for(auto it : m_materials) { + if(it) { + if(m_armature) { + m_armature->addInstance(it); + } + } + } +} +/*! + \internal +*/ +void SkinnedSpriteRender::onReferenceDestroyed() { + SpriteRender::onReferenceDestroyed(); + + if(sender() == m_armature) { + m_armature = nullptr; + } +} diff --git a/engine/src/components/spriterender.cpp b/engine/src/components/spriterender.cpp index ab291131a..af4b714b9 100644 --- a/engine/src/components/spriterender.cpp +++ b/engine/src/components/spriterender.cpp @@ -35,7 +35,7 @@ SpriteRender::SpriteRender() : m_size(1.0f), m_sprite(nullptr), m_texture(nullptr), - m_mesh(PipelineContext::defaultPlane()), + m_mesh(nullptr), m_customMesh(Engine::objectCreate()), m_customMeshInstance(nullptr), m_drawMode(Simple), @@ -68,7 +68,6 @@ Mesh *SpriteRender::meshToDraw() { if(m_drawMode == Sprite::Simple) { m_size = mesh->bound().extent * 2.0f; } - } else { m_mesh = PipelineContext::defaultPlane(); } @@ -76,7 +75,7 @@ Mesh *SpriteRender::meshToDraw() { m_useCustom = true; } } else { - m_mesh = PipelineContext::defaultPlane(); + m_mesh = m_texture ? PipelineContext::defaultPlane() : nullptr; } m_dirtyMesh = false; @@ -286,10 +285,7 @@ Mesh *SpriteRender::ensureMeshInstance() { m_customMeshInstance = Engine::objectCreate(); m_customMeshInstance->setIndices(m_customMesh->indices()); m_customMeshInstance->setVertices(m_customMesh->vertices()); - m_customMeshInstance->setNormals(m_customMesh->normals()); - m_customMeshInstance->setTangents(m_customMesh->tangents()); m_customMeshInstance->setUv0(m_customMesh->uv0()); - m_customMeshInstance->setUv1(m_customMesh->uv1()); m_customMeshInstance->setColors(m_customMesh->colors()); m_customMeshInstance->setBones(m_customMesh->bones()); m_customMeshInstance->setWeights(m_customMesh->weights()); diff --git a/engine/src/systems/rendersystem.cpp b/engine/src/systems/rendersystem.cpp index f509678f6..9cc10f1f2 100644 --- a/engine/src/systems/rendersystem.cpp +++ b/engine/src/systems/rendersystem.cpp @@ -4,6 +4,7 @@ #include "components/meshrender.h" #include "components/textrender.h" #include "components/spriterender.h" +#include "components/skinnedspriterender.h" #include "components/effectrender.h" #include "components/directlight.h" #include "components/pointlight.h" @@ -44,6 +45,7 @@ RenderSystem::RenderSystem() : MeshRender::registerClassFactory(this); TextRender::registerClassFactory(this); SpriteRender::registerClassFactory(this); + SkinnedSpriteRender::registerClassFactory(this); SkinnedMeshRender::registerClassFactory(this); BaseLight::registerClassFactory(this); @@ -86,6 +88,7 @@ RenderSystem::~RenderSystem() { MeshRender::unregisterClassFactory(this); TextRender::unregisterClassFactory(this); SpriteRender::unregisterClassFactory(this); + SkinnedSpriteRender::unregisterClassFactory(this); SkinnedMeshRender::unregisterClassFactory(this); BaseLight::unregisterClassFactory(this); diff --git a/modules/editor/spineimporter/converter/animationimporter.cpp b/modules/editor/spineimporter/converter/animationimporter.cpp index a8f225cfa..d4d1ba892 100644 --- a/modules/editor/spineimporter/converter/animationimporter.cpp +++ b/modules/editor/spineimporter/converter/animationimporter.cpp @@ -20,6 +20,32 @@ enum TransformMode { Scale }; +void appendKeyFrame(AnimationCurve &curve, float position, const Vector3 &value) { + AnimationCurve::KeyFrame frame; + frame.m_type = AnimationCurve::KeyFrame::Linear; + frame.m_position = position; + frame.m_value = { value.x, value.y, value.z }; + curve.m_keys.push_back(frame); +} + +void appendKeyFrame(AnimationCurve &curve, float position, const Vector4 &value) { + AnimationCurve::KeyFrame frame; + frame.m_type = AnimationCurve::KeyFrame::Linear; + frame.m_position = position; + frame.m_value = { value.x, value.y, value.z, value.w }; + curve.m_keys.push_back(frame); +} + +bool needSetupPoseKey(const VariantList &keys) { + if(keys.empty()) { + return true; + } + + const VariantMap firstKey = keys.front().value(); + auto it = firstKey.find(gTime); + return it == firstKey.end() || it->second.toFloat() > 0.0f; +} + void importBoneTransform(TransformMode mode, AnimationClip &clip, const TString &path, const Vector3 &value, const Variant &data, SpineConverterSettings *settings) { AnimationTrack track; track.setPath(path); @@ -34,19 +60,20 @@ void importBoneTransform(TransformMode mode, AnimationClip &clip, const TString AnimationCurve &curve = track.curve(); - AnimationCurve::KeyFrame frame; - frame.m_type = AnimationCurve::KeyFrame::Linear; - frame.m_position = 0.0f; - frame.m_value = { value.x, value.y, value.z }; + const VariantList keys = data.value(); + if(needSetupPoseKey(keys)) { + appendKeyFrame(curve, 0.0f, value); + } float customScale = settings->customScale(); - for(auto &key : data.value()) { + for(auto &key : keys) { VariantMap fields = key.value(); + float position = 0.0f; auto it = fields.find(gTime); if(it != fields.end()) { - frame.m_position = it->second.toFloat(); + position = it->second.toFloat(); } Vector3 v = value; @@ -65,8 +92,7 @@ void importBoneTransform(TransformMode mode, AnimationClip &clip, const TString it = fields.find(gX); if(it != fields.end()) { if(mode == TransformMode::Translate) { - v.x = value.x + it->second.toFloat(); - v.x *= customScale; + v.x = value.x + it->second.toFloat() * customScale; } else { v.x = value.x * it->second.toFloat(); } @@ -75,17 +101,14 @@ void importBoneTransform(TransformMode mode, AnimationClip &clip, const TString it = fields.find(gY); if(it != fields.end()) { if(mode == TransformMode::Translate) { - v.y = value.y + it->second.toFloat(); - v.y *= customScale; + v.y = value.y + it->second.toFloat() * customScale; } else { v.y = value.y * it->second.toFloat(); } } } - frame.m_value = { v.x, v.y, v.z }; - - curve.m_keys.push_back(frame); + appendKeyFrame(curve, position, v); } clip.addAnimationTrack(track); @@ -93,22 +116,23 @@ void importBoneTransform(TransformMode mode, AnimationClip &clip, const TString void importBoneTimeline(const VariantMap &bones, AnimationClip &clip, SpineConverterSettings *settings) { for(auto &bone : bones) { - Actor *boneActor = settings->m_boneStructure[bone.first]; - - Transform *t = boneActor->transform(); + Transform *t = nullptr; + for(auto it : settings->m_bones) { + if(it->name() == bone.first) { + t = it->transform(); + break; + } + } - TString path = SpineConverter::pathTo(settings->m_root, t); + TString path = SpineConverter::pathTo(settings->m_bones.front(), t); for(auto &type : bone.second.value()) { if(type.first == gRotate) { importBoneTransform(TransformMode::Rotate, clip, path, t->rotation(), type.second, settings); - } else if(type.first == gTranslate) { importBoneTransform(TransformMode::Translate, clip, path, t->position(), type.second, settings); - } else if(type.first == gScale) { importBoneTransform(TransformMode::Scale, clip, path, t->scale(), type.second, settings); - } else if(type.first == "shear") { // Unimplemented } @@ -120,20 +144,38 @@ void importSlotTimeline(const VariantMap &slotes, AnimationClip &clip, SpineConv for(auto &slotIt : slotes) { Slot &slot = settings->m_slots[slotIt.first]; - TString path = SpineConverter::pathTo(settings->m_root, slot.render); + TString path = SpineConverter::pathTo(settings->m_bones.front(), slot.render); for(auto &type : slotIt.second.value()) { if(type.first == gAttachment) { AnimationTrack track; track.setPath(path); - track.setProperty("item"); + track.setProperty("sprite"); AnimationTrack::Frames &frames = track.frames(); - frames.push_back({ Engine::reference(slot.render->sprite()), 0.0f }); + const VariantList keys = type.second.value(); + float maxPosition = 0.0f; + for(auto &key : keys) { + VariantMap fields = key.value(); + + auto it = fields.find(gTime); + if(it != fields.end()) { + maxPosition = MAX(maxPosition, it->second.toFloat()); + } + } + + if(needSetupPoseKey(keys)) { + if(slot.item.isEmpty()) { + frames.push_back({ TString(), 0.0f }); + } else { + ResourceSystem::ResourceInfo resSprite = settings->subItem(slot.item); + frames.push_back({ resSprite.uuid, 0.0f }); + } + } - for(auto &key : type.second.value()) { + for(auto &key : keys) { VariantMap fields = key.value(); float position = 0.0f; @@ -142,14 +184,28 @@ void importSlotTimeline(const VariantMap &slotes, AnimationClip &clip, SpineConv position = it->second.toFloat(); } - TString ref = Engine::reference(slot.render->sprite()); - + TString attachmentName; it = fields.find(gName); if(it != fields.end()) { - ref = it->second.toString(); + attachmentName = it->second.toString(); + } + + if(attachmentName.isEmpty()) { + attachmentName = slot.item; } - frames.push_back({ ref, position }); + if(attachmentName.isEmpty()) { + frames.push_back({ TString(), position }); + } else { + ResourceSystem::ResourceInfo resSprite = settings->subItem(attachmentName); + frames.push_back({ resSprite.uuid, position }); + } + } + + if(maxPosition > 0.0f) { + for(auto &frame : frames) { + frame.m_position /= maxPosition; + } } clip.addAnimationTrack(track); @@ -161,34 +217,28 @@ void importSlotTimeline(const VariantMap &slotes, AnimationClip &clip, SpineConv AnimationCurve &curve = track.curve(); - Vector4 value(slot.render->color()); - - AnimationCurve::KeyFrame frame; - frame.m_type = AnimationCurve::KeyFrame::Linear; - frame.m_position = 0.0f; - frame.m_value = { value.x, value.y, value.z, value.w }; - - curve.m_keys.push_back(frame); + const VariantList keys = type.second.value(); + if(needSetupPoseKey(keys)) { + appendKeyFrame(curve, 0.0f, slot.render->color()); + } - for(auto &key : type.second.value()) { + for(auto &key : keys) { VariantMap fields = key.value(); - frame.m_position = 0.0f; + float position = 0.0f; auto it = fields.find(gTime); if(it != fields.end()) { - frame.m_position = it->second.toFloat(); + position = it->second.toFloat(); } - value = slot.render->color(); + Vector4 value = slot.render->color(); it = fields.find(gColor); if(it != fields.end()) { value = SpineConverter::toColor(it->second.toString()); } - frame.m_value = { value.x, value.y, value.z, value.w }; - - curve.m_keys.push_back(frame); + appendKeyFrame(curve, position, value); } clip.addAnimationTrack(track); @@ -199,6 +249,12 @@ void importSlotTimeline(const VariantMap &slotes, AnimationClip &clip, SpineConv void importDrawOrderTimeline(const VariantList &keys, AnimationClip &clip, SpineConverterSettings *settings) { std::map tracks; + bool needInitialState = false; + if(!keys.empty()) { + VariantMap firstKey = keys.front().value(); + auto firstTime = firstKey.find(gTime); + needInitialState = firstTime == firstKey.end() || firstTime->second.toFloat() > 0.0f; + } for(auto &key : keys) { VariantMap keyFields = key.value(); @@ -214,15 +270,17 @@ void importDrawOrderTimeline(const VariantList &keys, AnimationClip &clip, Spine if(trackIt == tracks.end()) { AnimationTrack track; track.setProperty("layer"); - track.setPath(SpineConverter::pathTo(settings->m_root, slot.render)); + track.setPath(SpineConverter::pathTo(settings->m_bones.front(), slot.render)); - AnimationCurve::KeyFrame frame; - frame.m_type = AnimationCurve::KeyFrame::Constant; - frame.m_value = { static_cast(slot.render->layer()) }; - frame.m_position = 0.0f; + if(needInitialState) { + AnimationCurve::KeyFrame frame; + frame.m_type = AnimationCurve::KeyFrame::Constant; + frame.m_value = { static_cast(slot.render->layer()) }; + frame.m_position = 0.0f; - AnimationCurve &curve = track.curve(); - curve.m_keys.push_back(frame); + AnimationCurve &curve = track.curve(); + curve.m_keys.push_back(frame); + } tracks[slotName] = track; } @@ -248,6 +306,133 @@ void importDrawOrderTimeline(const VariantList &keys, AnimationClip &clip, Spine } } +void importAttachmentsTimeline(const VariantMap &attachments, AnimationClip &clip, const TString &clipName, SpineConverterSettings *settings) { + for(auto &attachmentIt : attachments) { + for(auto &slotIt : attachmentIt.second.value()) { + TString slotName = slotIt.first; + Slot &slot = settings->m_slots[slotName]; + + for(auto &itemIt : slotIt.second.value()) { + TString spriteName = itemIt.first; + Item &item = settings->m_atlasItems[spriteName]; + + for(auto &type : itemIt.second.value()) { + if(type.first == "deform") { + AnimationTrack track; + track.setProperty(TString("blendShape:") + clipName); + track.setPath(SpineConverter::pathTo(settings->m_bones.front(), slot.render)); + + AnimationCurve &curve = track.curve(); + + // Process blend shape deformations from animation + const VariantList deformKeys = type.second.value(); + if(!deformKeys.empty()) { + for(auto &key : deformKeys) { + VariantMap fields = key.value(); + + float position = 0.0f; + auto it = fields.find(gTime); + if(it != fields.end()) { + position = it->second.toFloat(); + } + + // Extract vertex deltas + std::vector indices; + Vector3Vector frameVertices; + + uint32_t offset = 0; + auto offsetIt = fields.find("offset"); + if(offsetIt != fields.end()) { + offset = offsetIt->second.toInt(); + } + + auto verticesIt = fields.find("vertices"); + if(verticesIt != fields.end()) { + VariantList vertices = verticesIt->second.toList(); + auto vertexIt = vertices.begin(); + uint32_t index = 0; + while(vertexIt != vertices.end()) { + Vector3 delta; + delta.x = vertexIt->toFloat(); + vertexIt++; + if(vertexIt != vertices.end()) { + delta.y = vertexIt->toFloat(); + vertexIt++; + } + + delta *= settings->customScale(); + + if(delta.x != 0.0f || delta.y != 0.0f) { + indices.push_back(offset + index); + frameVertices.push_back(delta); + } + index++; + } + + // Add blend shape frame to mesh if we have data + if(!indices.empty()) { + Sprite *sprite = item.sprite; + if(sprite) { + Mesh *mesh = sprite->mesh(); + if(mesh) { + AnimationCurve::KeyFrame frame; + frame.m_type = AnimationCurve::KeyFrame::Linear; + frame.m_value = { static_cast(position) }; + frame.m_position = position; + curve.m_keys.push_back(frame); + + mesh->addBlendShapeFrame(clipName, position, indices, frameVertices); + } + } + } + } + } + } + + if(!curve.m_keys.empty()) { + float lastPosition = curve.m_keys.back().m_position; + + for(auto &it : curve.m_keys) { + it.m_position /= lastPosition; + it.m_value[0] /= lastPosition; + } + + track.setDuration(lastPosition * 1000.0f); + + clip.addAnimationTrack(track); + } + } + + Sprite *sprite = item.sprite; + if(sprite) { + Mesh *mesh = sprite->mesh(); + if(mesh) { + for(auto &shape : mesh->blendShapes()) { + if(shape.name == clipName && !shape.frames.empty()) { + float lastWeight = shape.frames.back().weight; + if(lastWeight != 0.0f) { + for(auto &it : shape.frames) { + it.weight /= lastWeight; + } + } + } + } + + TString spriteUuid(Engine::reference(sprite)); + + Url dst(settings->absoluteDestination()); + AssetConverter::ReturnCode result = settings->saveBinary(Engine::toVariant(sprite), dst.absoluteDir() + "/" + spriteUuid); + if(result != AssetConverter::Success) { + aError() << "Unable to update sprite"; + } + } + } + } + } + } + } +} + void SpineConverter::importAnimations(const VariantMap &animations, SpineConverterSettings *settings) { for(auto &animation : animations) { ResourceSystem::ResourceInfo info = settings->subItem(animation.first, MetaType::name()); @@ -265,6 +450,8 @@ void SpineConverter::importAnimations(const VariantMap &animations, SpineConvert importSlotTimeline(timeline.second.value(), *clip, settings); } else if(timeline.first.toLower() == gDrawOrder) { importDrawOrderTimeline(timeline.second.value(), *clip, settings); + } else if(timeline.first == gAttachments) { + importAttachmentsTimeline(timeline.second.value(), *clip, animation.first, settings); } } @@ -274,6 +461,11 @@ void SpineConverter::importAnimations(const VariantMap &animations, SpineConvert if(!curve.m_keys.empty()) { duration = MAX(curve.m_keys.back().m_position, duration); } + + for(auto &frame : it.frames()) { + duration = MAX(frame.m_position, duration); + } + it.setDuration(duration * 1000.0f); if(duration > 0.0f) { // Normalize diff --git a/modules/editor/spineimporter/converter/spineconverter.cpp b/modules/editor/spineimporter/converter/spineconverter.cpp index bd7e99a20..e45cb534d 100644 --- a/modules/editor/spineimporter/converter/spineconverter.cpp +++ b/modules/editor/spineimporter/converter/spineconverter.cpp @@ -7,11 +7,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -29,7 +31,7 @@ enum class SkinTypes { }; SpineConverterSettings::SpineConverterSettings() : - m_root(nullptr), + m_rootBone(nullptr), m_scale(1.0f) { setVersion(FORMAT_VERSION); } @@ -69,7 +71,7 @@ AssetConverter::ReturnCode SpineConverter::convertFile(AssetConverterSettings *s // Bones section auto it = sections.find(gBones); if(it != sections.end()) { - spineSettings->m_root = importBones(it->second.value(), spineSettings); + spineSettings->m_rootBone = importBones(it->second.value(), spineSettings); } // Slots section @@ -91,20 +93,19 @@ AssetConverter::ReturnCode SpineConverter::convertFile(AssetConverterSettings *s } /// \todo Constraints section - /// \todo Events section - if(spineSettings->m_root) { - TString name(spineSettings->m_root->name()); - spineSettings->m_root->setName(settings->destination()); - stabilizeUUID(spineSettings->m_root); - spineSettings->m_root->setName(name); + if(spineSettings->m_rootBone) { + TString name(spineSettings->m_rootBone->name()); + spineSettings->m_rootBone->setName(settings->destination()); + stabilizeUUID(spineSettings->m_rootBone); + spineSettings->m_rootBone->setName(name); Prefab *prefab = Engine::loadResource(settings->destination()); if(prefab == nullptr) { prefab = Engine::objectCreate(settings->destination()); } - prefab->setActor(spineSettings->m_root); + prefab->setActor(spineSettings->m_rootBone); uint32_t uuid = settings->info().id; if(uuid == 0) { @@ -126,8 +127,6 @@ AssetConverter::ReturnCode SpineConverter::convertFile(AssetConverterSettings *s Actor *SpineConverter::importBones(const VariantList &bones, SpineConverterSettings *settings) { Actor *result = nullptr; - float scale = settings->customScale(); - for(auto &bone : bones) { VariantMap fields = bone.value(); @@ -136,35 +135,33 @@ Actor *SpineConverter::importBones(const VariantList &bones, SpineConverterSetti TString name = fields[gName].toString(); if(!parentField.isEmpty()) { - auto it = settings->m_boneStructure.find(parentField); - if(it != settings->m_boneStructure.end()) { - parent = it->second; + for(auto it : settings->m_bones) { + if(it->name() == parentField) { + parent = it; + break; + } } } Actor *actor = Engine::objectCreate(name, parent); actor->addComponent(gTransform); - settings->m_boneStructure[name] = actor; + Transform *t = actor->transform(); if(parentField.isEmpty()) { result = actor; } - Transform *t = actor->transform(); - - float customScale = settings->customScale(); - Vector3 pos; auto it = fields.find(gX); if(it != fields.end()) { - pos.x = it->second.toFloat() * scale; + pos.x = it->second.toFloat(); } it = fields.find(gY); if(it != fields.end()) { - pos.y = it->second.toFloat() * scale; + pos.y = it->second.toFloat(); } - t->setPosition(pos * customScale); + t->setPosition(pos * settings->customScale()); Vector3 rot; it = fields.find(gRotation); @@ -183,6 +180,8 @@ Actor *SpineConverter::importBones(const VariantList &bones, SpineConverterSetti scl.y = it->second.toFloat(); } t->setScale(scl); + + settings->m_bones.push_back(actor); } return result; @@ -218,7 +217,7 @@ void SpineConverter::importSlots(const VariantList &list, SpineConverterSettings currentSlot.item = it->second.toString(); } - /// \todo: blend + /// \todo: color blending settings->m_slots[key] = currentSlot; @@ -232,8 +231,6 @@ void SpineConverter::importSkins(const VariantList &list, SpineConverterSettings auto skinIt = skinFields.find(gAttachments); if(skinIt != skinFields.end()) { - TString skinName(skinFields[gName].toString()); - importAtlas(settings); Url dst(settings->absoluteDestination()); @@ -241,10 +238,15 @@ void SpineConverter::importSkins(const VariantList &list, SpineConverterSettings for(auto &slotIt : skinIt->second.value()) { Slot &slot = settings->m_slots[slotIt.first]; - Actor *bone = settings->m_boneStructure[slot.bone]; + Actor *bone = nullptr; + for(auto boneIt : settings->m_bones) { + if(boneIt->name() == slot.bone) { + bone = boneIt; + break; + } + } - Actor *slotActor = Engine::composeActor(slotIt.first, bone); - slot.render = slotActor->getComponent(); + Actor *slotActor = Engine::composeActor(slotIt.first, bone); for(auto &attachmentIt : slotIt.second.value()) { TString attachmentName = attachmentIt.first; @@ -285,13 +287,15 @@ void SpineConverter::importSkins(const VariantList &list, SpineConverterSettings sprite->setMode(Sprite::Complex); sprite->setTexture(Engine::loadResource(settings->m_texture)); Mesh *mesh = sprite->mesh(); + mesh->clearBlendShapes(); + Pose *pose = nullptr; switch(type) { case SkinTypes::Region: { importRegion(attachmentFields, attachmentName, slotActor->transform(), mesh, settings); } break; case SkinTypes::Mesh: { - importMesh(attachmentFields, attachmentName, mesh, settings); + pose = importMesh(attachmentFields, attachmentName, slotActor->transform(), mesh, settings); } break; default: break; } @@ -307,11 +311,35 @@ void SpineConverter::importSkins(const VariantList &list, SpineConverterSettings settings->setSubItem(attachmentName, resSprite); } + settings->m_atlasItems[attachmentName].sprite = sprite; + + if(slot.render == nullptr) { + if(!mesh->weights().empty()) { + SkinnedSpriteRender *skinned = static_cast(slotActor->addComponent("SkinnedSpriteRender")); + if(pose) { + Actor *parentActor = dynamic_cast(slotActor->parent()); + if(parentActor) { + Armature *armature = static_cast(parentActor->addComponent("Armature")); + armature->setBindPose(pose); + skinned->setArmature(armature); + } + } + + slot.render = skinned; + } else { + slot.render = static_cast(slotActor->addComponent("SpriteRender")); + } + slot.render->setMaterial(Engine::loadResource(".embedded/DefaultSprite.shader")); + } + if(slot.render && slot.render->sprite() == nullptr) { - slot.render->setSprite(sprite); - slot.render->setLayer(slot.layer); - if(!slot.color.isEmpty()) { - slot.render->setColor(toColor(slot.color)); + bool isSetupPoseAttachment = !slot.item.isEmpty() && (attachmentIt.first == slot.item || attachmentName == slot.item); + if(isSetupPoseAttachment) { + slot.render->setSprite(sprite); + slot.render->setLayer(slot.layer); + if(!slot.color.isEmpty()) { + slot.render->setColor(toColor(slot.color)); + } } } } @@ -337,7 +365,6 @@ void SpineConverter::importAtlas(SpineConverterSettings *settings) { }; TString itemName; - Vector4 bounds; uint32_t currentState = State::FileName; for(auto &it : data.split('\n')) { @@ -389,44 +416,42 @@ void SpineConverter::importAtlas(SpineConverterSettings *settings) { } } -namespace { - void applyAttachmentTransform(const VariantMap &fields, Vector3 &vertex, float customScale) { - Vector3 pos; - auto it = fields.find(gX); - if(it != fields.end()) { - pos.x = it->second.toString().toFloat(); - } - it = fields.find(gY); - if(it != fields.end()) { - pos.y = it->second.toString().toFloat(); - } +void applyAttachmentTransform(const VariantMap &fields, Vector3 &vertex, float customScale) { + Vector3 pos; + auto it = fields.find(gX); + if(it != fields.end()) { + pos.x = it->second.toString().toFloat(); + } + it = fields.find(gY); + if(it != fields.end()) { + pos.y = it->second.toString().toFloat(); + } - Vector3 rot; - it = fields.find(gRotation); - if(it != fields.end()) { - rot.z = it->second.toString().toFloat(); - } + Vector3 rot; + it = fields.find(gRotation); + if(it != fields.end()) { + rot.z = it->second.toString().toFloat(); + } - Vector3 scl(1.0f); - it = fields.find(gScaleX); - if(it != fields.end()) { - scl.x = it->second.toString().toFloat(); - } - it = fields.find(gScaleY); - if(it != fields.end()) { - scl.y = it->second.toString().toFloat(); - } + Vector3 scl(1.0f); + it = fields.find(gScaleX); + if(it != fields.end()) { + scl.x = it->second.toString().toFloat(); + } + it = fields.find(gScaleY); + if(it != fields.end()) { + scl.y = it->second.toString().toFloat(); + } - Matrix3 matrix; - matrix.rotate(Vector3(0.0f, 0.0f, 1.0f), rot.z); + Matrix3 matrix; + matrix.rotate(Vector3(0.0f, 0.0f, 1.0f), rot.z); - Vector3 transformed(vertex.x * scl.x, vertex.y * scl.y, vertex.z); - transformed = matrix * transformed; - transformed += pos; - transformed *= customScale; + Vector3 transformed(vertex.x * scl.x, vertex.y * scl.y, vertex.z); + transformed = matrix * transformed; + transformed += pos; + transformed *= customScale; - vertex = transformed; - } + vertex = transformed; } void SpineConverter::importRegion(const VariantMap &fields, const TString &itemName, Transform *transform, Mesh *mesh, SpineConverterSettings *settings) { @@ -516,8 +541,8 @@ void SpineConverter::importRegion(const VariantMap &fields, const TString &itemN mesh->setUv0(uvs); } -void SpineConverter::importMesh(const VariantMap &fields, const TString &itemName, Mesh *mesh, SpineConverterSettings *settings) { - Item item = settings->m_atlasItems[itemName]; +Pose *SpineConverter::importMesh(const VariantMap &fields, const TString &itemName, Transform *transform, Mesh *mesh, SpineConverterSettings *settings) { + const Item &item = settings->m_atlasItems[itemName]; uint32_t uvCount = 0; @@ -575,51 +600,117 @@ void SpineConverter::importMesh(const VariantMap &fields, const TString &itemNam mesh->setUv0(uvs); } + it = fields.find(gTriangles); + if(it != fields.end()) { + IndexVector &indices = mesh->indices(); + + VariantList list = it->second.toList(); + indices.resize(list.size()); + uint32_t indexCount = 0; + for(auto &index : list) { + indices[indexCount] = index.toInt(); + indexCount++; + } + } + it = fields.find(gVertices); if(it != fields.end()) { uint32_t vertexCount = 0; Vector3Vector &vertices = mesh->vertices(); - vertices.resize(uvCount); + float customScale = settings->customScale(); + VariantList list = it->second.toList(); - if(list.size() > uvCount * 2) { // weighted mesh + bool isWeighted = (list.size() > uvCount * 2); + + if(isWeighted) { Vector4Vector &bones = mesh->bones(); Vector4Vector &weights = mesh->weights(); bones.resize(uvCount); weights.resize(uvCount); + Actor *rootActor = transform->actor(); + + TString poseName(rootActor->name() + "Pose"); + + ResourceSystem::ResourceInfo info = settings->subItem(poseName, MetaType::name()); + Pose *pose = Engine::loadResource(info.uuid); + if(pose == nullptr) { + pose = Engine::objectCreate(info.uuid); + } + pose->clear(); + + std::map boneIndexMap; + int localIndex = 0; + + Matrix4 rootInverse = transform->worldTransform().inverse(); + auto vertexIt = list.begin(); while(vertexIt != list.end()) { int32_t wCount = vertexIt->toInt(); ++vertexIt; - Vector3 vertex; - for(int32_t w = 0; w < wCount; w++) { - int bone = vertexIt->toInt(); + int boneIndex = vertexIt->toInt(); ++vertexIt; - float posX = vertexIt->toFloat(); + float bindX = vertexIt->toFloat(); ++vertexIt; - float posY = vertexIt->toFloat(); + float bindY = vertexIt->toFloat(); ++vertexIt; float weight = vertexIt->toFloat(); ++vertexIt; if(w < 4) { - bones[vertexCount][w] = bone; - weights[vertexCount][w] = weight; + Actor *bone = settings->m_bones[boneIndex]; + if(bone) { + Transform *boneTransform = bone->transform(); + + int currentIndex = 0; + auto boneIt = boneIndexMap.find(boneIndex); + if(boneIt == boneIndexMap.end()) { + boneIndexMap[boneIndex] = localIndex; + currentIndex = localIndex; + localIndex++; + + Bone b; + b.setName(bone->name()); + b.setPosition(boneTransform->position()); + b.setRotation(boneTransform->rotation()); + b.setScale(boneTransform->scale()); + + pose->addBone(b); + } else { + currentIndex = boneIt->second; + } + + bones[vertexCount][w] = currentIndex; + weights[vertexCount][w] = weight; - vertex += Vector3(posX, posY, 0.0f) * weight; + if(w == 0) { + Vector3 localPos(bindX * customScale, bindY * customScale, 0.0f); + Vector3 worldPos(boneTransform->worldTransform() * localPos); + + vertices[vertexCount] = Vector3(rootInverse * worldPos); + } + } } } - vertices[vertexCount] = vertex; - vertexCount++; } + + Url dst(settings->absoluteDestination()); + + AssetConverter::ReturnCode result = settings->saveBinary(Engine::toVariant(pose), dst.absoluteDir() + "/" + info.uuid); + if(result == AssetConverter::Success) { + info.id = pose->uuid(); + settings->setSubItem(poseName, info); + } + + return pose; } else { for(auto &vertex : list) { uint32_t index = vertexCount / 2; @@ -627,75 +718,35 @@ void SpineConverter::importMesh(const VariantMap &fields, const TString &itemNam vertexCount++; } - } - float customScale = settings->customScale(); - for(auto &vertex : vertices) { - applyAttachmentTransform(fields, vertex, customScale); + for(auto &vertex : vertices) { + applyAttachmentTransform(fields, vertex, customScale); + } } } - auto deformIt = fields.find("deform"); - if(deformIt != fields.end()) { - VariantList deformList = deformIt->second.toList(); - if(!deformList.empty()) { - for(auto &deform : deformList) { - VariantMap deformData = deform.toMap(); - TString shapeName = deformData["name"].toString(); - - auto verticesIt = deformData.find("vertices"); - if(verticesIt != deformData.end()) { - std::vector indices; - Vector3Vector frameVertices; - - uint32_t offset = deformData["offset"].toInt(); - - VariantList vertices = verticesIt->second.toList(); - auto vertexIt = vertices.begin(); - uint32_t index = 0; - while(vertexIt != vertices.end()) { - Vector3 delta; - delta.x = vertexIt->toFloat(); - vertexIt++; - delta.y = vertexIt->toFloat(); - vertexIt++; - - if(delta.x != 0.0f || delta.y != 0.0f) { - indices.push_back(offset + index); - frameVertices.push_back(delta); - } - index++; - } + return nullptr; +} - if(!indices.empty()) { - mesh->addBlendShapeFrame(shapeName, 1, indices, frameVertices); - } - } - } - } +Vector4 SpineConverter::toColor(const TString &color) { + TString hex = color; + if(hex.startsWith("#")) { + hex = hex.mid(1, hex.size() - 1); + } else if(hex.startsWith("0x") || hex.startsWith("0X")) { + hex = hex.mid(2, hex.size() - 2); } - it = fields.find(gTriangles); - if(it != fields.end()) { - IndexVector &indices = mesh->indices(); - - VariantList list = it->second.toList(); - indices.reserve(list.size()); - for(auto &index : list) { - indices.push_back(index.toInt()); - } + if(hex.size() == 6) { + hex += "ff"; } -} -Vector4 SpineConverter::toColor(const TString &color) { - uint32_t rgba = stoul(color.toStdString(), nullptr, 16); - uint8_t rgb[4]; - rgb[0] = rgba; - rgb[1] = rgba >> 8; - rgb[2] = rgba >> 16; - rgb[3] = rgba >> 24; - - return Vector4((float)rgb[3] / 255.0f, (float)rgb[2] / 255.0f, (float)rgb[1] / 255.0f, (float)rgb[0] / 255.0f); + uint32_t rgba = stoul(hex.toStdString(), nullptr, 16); + uint8_t r = (rgba >> 24) & 0xFF; + uint8_t g = (rgba >> 16) & 0xFF; + uint8_t b = (rgba >> 8) & 0xFF; + uint8_t a = rgba & 0xFF; + + return Vector4((float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)a / 255.0f); } TString SpineConverter::pathTo(Object *root, Object *dst) { @@ -711,7 +762,8 @@ TString SpineConverter::pathTo(Object *root, Object *dst) { } void SpineConverter::stabilizeUUID(Object *object) { - Engine::replaceUUID(object, Mathf::hashString(pathTo(nullptr, object))); + TString path = pathTo(nullptr, object); + Engine::replaceUUID(object, Mathf::hashString(path)); for(auto it : object->getChildren()) { stabilizeUUID(it); diff --git a/modules/editor/spineimporter/converter/spineconverter.h b/modules/editor/spineimporter/converter/spineconverter.h index 54678dd67..c446a48cd 100644 --- a/modules/editor/spineimporter/converter/spineconverter.h +++ b/modules/editor/spineimporter/converter/spineconverter.h @@ -3,10 +3,12 @@ #include +class Pose; class Mesh; class Sprite; class Transform; class SpriteRender; +class SkinnedSpriteRender; namespace { const char *gX("x"); @@ -43,18 +45,20 @@ namespace { const char *gValue("value"); const char *gTransform("Transform"); -}; +} struct Item { Vector4 bounds; - float rotate; + float rotate = 0.0f; + + Sprite *sprite = nullptr; }; struct Slot { TString color; TString bone; TString item; - uint32_t layer; + uint32_t layer = 0; SpriteRender *render = nullptr; }; @@ -76,7 +80,7 @@ class SpineConverterSettings : public AssetConverterSettings { StringList typeNames() const override; public: - std::map m_boneStructure; + std::vector m_bones; std::map m_slots; @@ -86,7 +90,7 @@ class SpineConverterSettings : public AssetConverterSettings { Vector2 m_atlasSize; - Actor *m_root; + Actor *m_rootBone; private: float m_scale; @@ -116,7 +120,7 @@ class SpineConverter : public AssetConverter { void importAtlas(SpineConverterSettings *settings); void importRegion(const VariantMap &fields, const TString &itemName, Transform *transform, Mesh *mesh, SpineConverterSettings *settings); - void importMesh(const VariantMap &fields, const TString &itemName, Mesh *mesh, SpineConverterSettings *settings); + Pose *importMesh(const VariantMap &fields, const TString &itemName, Transform *transform, Mesh *mesh, SpineConverterSettings *settings); void stabilizeUUID(Object *object); diff --git a/modules/editor/timeline/editor/timelineedit.cpp b/modules/editor/timeline/editor/timelineedit.cpp index f4b601c2f..ebc7324d9 100644 --- a/modules/editor/timeline/editor/timelineedit.cpp +++ b/modules/editor/timeline/editor/timelineedit.cpp @@ -158,8 +158,8 @@ void TimelineEdit::setPosition(uint32_t position) { } } - if(m_armature) { - m_armature->update(); + for(auto it : m_armatures) { + it->update(); } ui->widget->setPosition(position); @@ -190,10 +190,12 @@ void TimelineEdit::setAnimator(Animator *animator) { saveClip(); m_animator = animator; - m_armature = nullptr; + m_armatures.clear(); if(m_animator) { - m_armature = m_animator->actor()->getComponentInChild(); + for(auto it : m_animator->actor()->componentsInChild("Armature")) { + m_armatures.push_back(static_cast(it)); + } } ui->toolBar->setVisible(m_animator != nullptr); diff --git a/modules/editor/timeline/editor/timelineedit.h b/modules/editor/timeline/editor/timelineedit.h index 8939b9921..3ffdb1989 100644 --- a/modules/editor/timeline/editor/timelineedit.h +++ b/modules/editor/timeline/editor/timelineedit.h @@ -94,14 +94,14 @@ private slots: private: std::map m_clips; + std::list m_armatures; + TString m_currentClip; Ui::TimelineEdit *ui = nullptr; Animator *m_animator = nullptr; - Armature *m_armature = nullptr; - TimelineController *m_model = nullptr; AssetEditor *m_editor = nullptr;