#include "RuntimeApi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DTS_SPINE_LINE #error DTS_SPINE_LINE must be defined before including LegacyRuntimeAdapter.inl #endif namespace { struct TextureRecord { std::string path; }; class MetadataTextureLoader final : public spine::TextureLoader { public: void load(spine::AtlasPage& page, const spine::String& path) override { #if defined(DTS_SPINE_41) page.texture = new TextureRecord{path.buffer()}; #else page.setRendererObject(new TextureRecord{path.buffer()}); #endif } void unload(void* texture) override { delete static_cast(texture); } }; struct RenderBatch { std::vector vertices; std::vector indices; std::string texturePath; std::uint32_t blendMode = 0; }; struct RuntimeContext { MetadataTextureLoader textureLoader; std::unique_ptr atlas; std::unique_ptr skeletonData; std::unique_ptr skeleton; std::unique_ptr animationStateData; std::unique_ptr animationState; spine::SkeletonClipping clipper; std::vector animationNames; std::vector animationDurations; std::vector skinNames; std::vector renderBatches; std::string error; }; RuntimeContext* context(DtsRuntimeHandle handle) { return static_cast(handle); } std::string lowercaseExtension(const std::string& path) { const auto dot = path.find_last_of('.'); auto extension = dot == std::string::npos ? std::string{} : path.substr(dot); std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) { return static_cast(std::tolower(value)); }); return extension; } void collectMetadata(RuntimeContext& value) { value.animationNames.clear(); value.animationDurations.clear(); auto& animations = value.skeletonData->getAnimations(); for (std::size_t index = 0; index < animations.size(); ++index) { value.animationNames.emplace_back(animations[index]->getName().buffer()); value.animationDurations.push_back(animations[index]->getDuration()); } value.skinNames.clear(); auto& skins = value.skeletonData->getSkins(); for (std::size_t index = 0; index < skins.size(); ++index) value.skinNames.emplace_back(skins[index]->getName().buffer()); } spine::AtlasRegion* regionFor(spine::RegionAttachment& attachment) { #if defined(DTS_SPINE_41) return static_cast(attachment.getRegion()); #else return static_cast(attachment.getRendererObject()); #endif } spine::AtlasRegion* regionFor(spine::MeshAttachment& attachment) { #if defined(DTS_SPINE_41) return static_cast(attachment.getRegion()); #else return static_cast(attachment.getRendererObject()); #endif } std::uint32_t packedColor(spine::Skeleton& skeleton, spine::Slot& slot, spine::Color& attachment) { const auto component = [](float value) { return static_cast(std::clamp(value, 0.0F, 1.0F) * 255.0F); }; const auto& skeletonColor = skeleton.getColor(); const auto& slotColor = slot.getColor(); const auto red = component(skeletonColor.r * slotColor.r * attachment.r); const auto green = component(skeletonColor.g * slotColor.g * attachment.g); const auto blue = component(skeletonColor.b * slotColor.b * attachment.b); const auto alpha = component(skeletonColor.a * slotColor.a * attachment.a); return (alpha << 24) | (red << 16) | (green << 8) | blue; } void appendBatch(RuntimeContext& value, spine::Slot& slot, spine::Color& color, spine::AtlasRegion* region, spine::Vector& positions, spine::Vector& uvs, spine::Vector& indices) { if (!region || !region->page) return; #if defined(DTS_SPINE_41) auto* texture = static_cast(region->page->texture); #else auto* texture = static_cast(region->page->getRendererObject()); #endif if (!texture) return; auto* sourcePositions = &positions; auto* sourceUvs = &uvs; auto* sourceIndices = &indices; if (value.clipper.isClipping()) { value.clipper.clipTriangles(positions, indices, uvs, 2); sourcePositions = &value.clipper.getClippedVertices(); sourceUvs = &value.clipper.getClippedUVs(); sourceIndices = &value.clipper.getClippedTriangles(); } RenderBatch batch; const auto vertexCount = sourcePositions->size() / 2; batch.vertices.reserve(vertexCount); const auto packed = packedColor(*value.skeleton, slot, color); for (std::size_t index = 0; index < vertexCount; ++index) batch.vertices.push_back({(*sourcePositions)[index * 2], -(*sourcePositions)[index * 2 + 1], (*sourceUvs)[index * 2], (*sourceUvs)[index * 2 + 1], packed}); batch.indices.assign(sourceIndices->buffer(), sourceIndices->buffer() + sourceIndices->size()); batch.texturePath = texture->path; batch.blendMode = static_cast(slot.getData().getBlendMode()); value.renderBatches.push_back(std::move(batch)); } void collectDrawCommands(RuntimeContext& value) { value.renderBatches.clear(); spine::Vector positions; spine::Vector quadIndices; quadIndices.add(0); quadIndices.add(1); quadIndices.add(2); quadIndices.add(2); quadIndices.add(3); quadIndices.add(0); auto& drawOrder = value.skeleton->getDrawOrder(); for (std::size_t index = 0; index < drawOrder.size(); ++index) { auto& slot = *drawOrder[index]; auto* attachment = slot.getAttachment(); if (!attachment) { value.clipper.clipEnd(slot); continue; } if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) { auto& region = *static_cast(attachment); positions.setSize(8, 0); #if defined(DTS_SPINE_41) region.computeWorldVertices(slot, positions, 0, 2); #else region.computeWorldVertices(slot.getBone(), positions, 0, 2); #endif appendBatch(value, slot, region.getColor(), regionFor(region), positions, region.getUVs(), quadIndices); } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) { auto& mesh = *static_cast(attachment); positions.setSize(mesh.getWorldVerticesLength(), 0); mesh.computeWorldVertices(slot, 0, mesh.getWorldVerticesLength(), positions.buffer(), 0, 2); appendBatch(value, slot, mesh.getColor(), regionFor(mesh), positions, mesh.getUVs(), mesh.getTriangles()); } else if (attachment->getRTTI().isExactly(spine::ClippingAttachment::rtti)) { value.clipper.clipStart(slot, static_cast(attachment)); } value.clipper.clipEnd(slot); } value.clipper.clipEnd(); } } // namespace namespace spine { SpineExtension* getDefaultExtension() { return new DefaultSpineExtension(); } } // namespace spine extern "C" { uint32_t dtsRuntimeGetApiVersion() { return DTS_RUNTIME_API_VERSION; } int32_t dtsRuntimeGetInfo(DtsRuntimeInfo* info) { if (!info || info->structSize < sizeof(DtsRuntimeInfo)) return -1; info->apiVersion = DTS_RUNTIME_API_VERSION; info->spineVersionLine = DTS_SPINE_LINE; info->adapterBuild = "preview-1"; info->capabilities = DTS_RUNTIME_CAP_SKELETON_METADATA | DTS_RUNTIME_CAP_DRAW_COMMANDS; return 0; } DtsRuntimeHandle dtsRuntimeCreate() { return new RuntimeContext; } void dtsRuntimeDestroy(DtsRuntimeHandle handle) { delete context(handle); } int32_t dtsRuntimeLoad(DtsRuntimeHandle handle, const DtsLoadRequest* request) { auto* value = context(handle); if (!value || !request || request->structSize < sizeof(DtsLoadRequest) || !request->skeletonPathUtf8 || !request->atlasPathUtf8) return -1; value->error.clear(); value->renderBatches.clear(); value->animationState.reset(); value->animationStateData.reset(); value->skeleton.reset(); value->skeletonData.reset(); value->atlas.reset(); value->atlas = std::make_unique(request->atlasPathUtf8, &value->textureLoader, true); const float scale = request->scale > 0 ? request->scale : 1.0F; if (lowercaseExtension(request->skeletonPathUtf8) == ".json") { spine::SkeletonJson loader(value->atlas.get()); loader.setScale(scale); value->skeletonData.reset(loader.readSkeletonDataFile(request->skeletonPathUtf8)); if (!value->skeletonData) value->error = loader.getError().buffer(); } else { spine::SkeletonBinary loader(value->atlas.get()); loader.setScale(scale); value->skeletonData.reset(loader.readSkeletonDataFile(request->skeletonPathUtf8)); if (!value->skeletonData) value->error = loader.getError().buffer(); } if (!value->skeletonData) { if (value->error.empty()) value->error = "Spine Runtime could not load skeleton data."; return -2; } collectMetadata(*value); value->skeleton = std::make_unique(value->skeletonData.get()); value->animationStateData = std::make_unique(value->skeletonData.get()); value->animationState = std::make_unique(value->animationStateData.get()); if (!value->animationNames.empty()) value->animationState->setAnimation(0, value->animationNames.front().c_str(), true); value->animationState->apply(*value->skeleton); value->skeleton->updateWorldTransform(); collectDrawCommands(*value); return 0; } int32_t dtsRuntimeGetSkeletonInfo(DtsRuntimeHandle handle, DtsSkeletonInfo* info) { auto* value = context(handle); if (!value || !value->skeletonData || !info || info->structSize < sizeof(DtsSkeletonInfo)) return -1; auto* data = value->skeletonData.get(); info->boneCount = static_cast(data->getBones().size()); info->slotCount = static_cast(data->getSlots().size()); info->skinCount = static_cast(value->skinNames.size()); info->animationCount = static_cast(value->animationNames.size()); info->eventCount = static_cast(data->getEvents().size()); info->constraintCount = static_cast(data->getIkConstraints().size() + data->getTransformConstraints().size() + data->getPathConstraints().size()); return 0; } const char* dtsRuntimeGetAnimationName(DtsRuntimeHandle handle, uint32_t index) { auto* value = context(handle); return value && index < value->animationNames.size() ? value->animationNames[index].c_str() : nullptr; } float dtsRuntimeGetAnimationDuration(DtsRuntimeHandle handle, uint32_t index) { auto* value = context(handle); return value && index < value->animationDurations.size() ? value->animationDurations[index] : 0.0F; } const char* dtsRuntimeGetSkinName(DtsRuntimeHandle handle, uint32_t index) { auto* value = context(handle); return value && index < value->skinNames.size() ? value->skinNames[index].c_str() : nullptr; } const char* dtsRuntimeGetLastError(DtsRuntimeHandle handle) { auto* value = context(handle); return value ? value->error.c_str() : "Invalid runtime handle."; } int32_t dtsRuntimeSetAnimation(DtsRuntimeHandle handle, const char* name, int32_t loop) { auto* value = context(handle); if (!value || !value->animationState || !name) return -1; if (!value->skeletonData->findAnimation(name)) { value->error = "Animation was not found."; return -2; } value->skeleton->setToSetupPose(); value->animationState->setAnimation(0, name, loop != 0); return 0; } int32_t dtsRuntimeUpdate(DtsRuntimeHandle handle, float seconds) { auto* value = context(handle); if (!value || !value->animationState || !value->skeleton) return -1; value->animationState->update(std::max(0.0F, seconds)); value->animationState->apply(*value->skeleton); value->skeleton->updateWorldTransform(); collectDrawCommands(*value); return 0; } uint32_t dtsRuntimeGetDrawCommandCount(DtsRuntimeHandle handle) { auto* value = context(handle); return value ? static_cast(value->renderBatches.size()) : 0; } int32_t dtsRuntimeGetDrawCommand(DtsRuntimeHandle handle, uint32_t index, DtsDrawCommand* command) { auto* value = context(handle); if (!value || index >= value->renderBatches.size() || !command || command->structSize < sizeof(DtsDrawCommand)) return -1; const auto& batch = value->renderBatches[index]; command->vertices = batch.vertices.data(); command->vertexCount = static_cast(batch.vertices.size()); command->indices = batch.indices.data(); command->indexCount = static_cast(batch.indices.size()); command->texturePathUtf8 = batch.texturePath.c_str(); command->blendMode = batch.blendMode; return 0; } } // extern "C"