| #include "rive/bones/skin.hpp" | |
| #include "rive/bones/bone.hpp" | |
| #include "rive/bones/skinnable.hpp" | |
| #include "rive/bones/tendon.hpp" | |
| #include "rive/shapes/vertex.hpp" | |
| #include "rive/shapes/path_vertex.hpp" | |
| #include "rive/constraints/constraint.hpp" | |
| #include <algorithm> | |
| using namespace rive; | |
| Skin::~Skin() { delete[] m_BoneTransforms; } | |
| StatusCode Skin::onAddedDirty(CoreContext* context) | |
| { | |
| StatusCode code = Super::onAddedDirty(context); | |
| if (code != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| m_WorldTransform[0] = xx(); | |
| m_WorldTransform[1] = xy(); | |
| m_WorldTransform[2] = yx(); | |
| m_WorldTransform[3] = yy(); | |
| m_WorldTransform[4] = tx(); | |
| m_WorldTransform[5] = ty(); | |
| m_Skinnable = Skinnable::from(parent()); | |
| if (m_Skinnable == nullptr) | |
| { | |
| return StatusCode::MissingObject; | |
| } | |
| #ifndef WITH_RIVE_EDITOR | |
| // Runtime-only; editor build registers via editorParentChanged. | |
| m_Skinnable->skin(this); | |
| #endif | |
| return StatusCode::Ok; | |
| } | |
| void Skin::update(ComponentDirt value) | |
| { | |
| int bidx = 6; | |
| for (auto tendon : m_Tendons) | |
| { | |
| #ifdef WITH_RIVE_EDITOR | |
| // Edit-time: a partially-hydrated coop batch can leave a | |
| // Tendon with a null `m_Bone` (its Bone hasn't been delivered | |
| // yet, or was deleted). Without this guard `update` reads the | |
| // null bone's world transform and crashes mid-frame. Emit the | |
| // identity transform so the skin can still render in its | |
| // bind pose until the bone reappears. | |
| if (tendon->bone() == nullptr) | |
| { | |
| const auto& inv = tendon->inverseBind(); | |
| m_BoneTransforms[bidx++] = inv[0]; | |
| m_BoneTransforms[bidx++] = inv[1]; | |
| m_BoneTransforms[bidx++] = inv[2]; | |
| m_BoneTransforms[bidx++] = inv[3]; | |
| m_BoneTransforms[bidx++] = inv[4]; | |
| m_BoneTransforms[bidx++] = inv[5]; | |
| continue; | |
| } | |
| #endif | |
| auto world = tendon->bone()->worldTransform() * tendon->inverseBind(); | |
| m_BoneTransforms[bidx++] = world[0]; | |
| m_BoneTransforms[bidx++] = world[1]; | |
| m_BoneTransforms[bidx++] = world[2]; | |
| m_BoneTransforms[bidx++] = world[3]; | |
| m_BoneTransforms[bidx++] = world[4]; | |
| m_BoneTransforms[bidx++] = world[5]; | |
| } | |
| } | |
| void Skin::buildDependencies() | |
| { | |
| // depend on bones from tendons | |
| for (auto tendon : m_Tendons) | |
| { | |
| auto bone = tendon->bone(); | |
| #ifdef WITH_RIVE_EDITOR | |
| // See `Skin::update` — coop can leave an unresolved Tendon | |
| // with a null bone. `editor_native/finalizeBatch` calls | |
| // `Tendon::resolveBone` on every tendon before us, so this | |
| // is only hit for tendons whose bone genuinely isn't in the | |
| // file yet. Skip; the dependency edge rewires when the bone | |
| // arrives in a later batch and we re-run finalizeBatch. | |
| if (bone == nullptr) | |
| { | |
| continue; | |
| } | |
| #endif | |
| bone->addDependent(this); | |
| for (auto constraint : bone->peerConstraints()) | |
| { | |
| constraint->parent()->addDependent(this); | |
| } | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| // Editor re-runs buildDependencies after every batch (Pass-B-prep | |
| // wipes the dependents list, then the typed per-class method | |
| // rewires it). For Skin that means a second allocation of the | |
| // bone-transform buffer — `assert` below would catch the runtime- | |
| // only invariant. Free the prior buffer so the editor case stays | |
| // leak-free while keeping the runtime guard intact. | |
| if (m_BoneTransforms != nullptr) | |
| { | |
| delete[] m_BoneTransforms; | |
| m_BoneTransforms = nullptr; | |
| } | |
| #else | |
| // Make sure no-one is calling this twice. | |
| assert(m_BoneTransforms == nullptr); | |
| #endif | |
| // We can now init the bone buffer. | |
| auto size = (m_Tendons.size() + 1) * 6; | |
| m_BoneTransforms = new float[size]; | |
| m_BoneTransforms[0] = 1; | |
| m_BoneTransforms[1] = 0; | |
| m_BoneTransforms[2] = 0; | |
| m_BoneTransforms[3] = 1; | |
| m_BoneTransforms[4] = 0; | |
| m_BoneTransforms[5] = 0; | |
| } | |
| void Skin::deform(Span<Vertex*> vertices) | |
| { | |
| for (auto vertex : vertices) | |
| { | |
| vertex->deform(m_WorldTransform, m_BoneTransforms); | |
| } | |
| } | |
| void Skin::addTendon(Tendon* tendon) { m_Tendons.push_back(tendon); } | |
| #ifdef WITH_RIVE_EDITOR | |
| void Skin::sortTendonsForEditor() | |
| { | |
| auto byChildOrder = [](const Tendon* a, const Tendon* b) { | |
| return a->childOrder().compareTo(b->childOrder()) < 0; | |
| }; | |
| if (std::is_sorted(m_Tendons.begin(), m_Tendons.end(), byChildOrder)) | |
| { | |
| return; | |
| } | |
| std::sort(m_Tendons.begin(), m_Tendons.end(), byChildOrder); | |
| // Weights index m_BoneTransforms by tendon position, so a reorder has to | |
| // rebuild the buffer and re-deform the skinnable. | |
| addDirt(ComponentDirt::WorldTransform); | |
| } | |
| #endif | |
| void Skin::onDirty(ComponentDirt dirt) | |
| { | |
| if (m_Skinnable != nullptr) | |
| { | |
| m_Skinnable->markSkinDirty(); | |
| } | |
| } |