214 lines
9.6 KiB
C++
214 lines
9.6 KiB
C++
#include "RuntimeApi.h"
|
|
|
|
#include <spine/Animation.h>
|
|
#include <spine/AnimationState.h>
|
|
#include <spine/AnimationStateData.h>
|
|
#include <spine/Atlas.h>
|
|
#include <spine/Extension.h>
|
|
#include <spine/Skeleton.h>
|
|
#include <spine/SkeletonBinary.h>
|
|
#include <spine/SkeletonData.h>
|
|
#include <spine/SkeletonJson.h>
|
|
#include <spine/SkeletonRenderer.h>
|
|
#include <spine/Skin.h>
|
|
#include <spine/TextureLoader.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
struct TextureRecord { std::string path; };
|
|
class MetadataTextureLoader final : public spine::TextureLoader {
|
|
public:
|
|
void load(spine::AtlasPage& page, const spine::String& path) override {
|
|
page.texture = new TextureRecord{path.buffer()};
|
|
}
|
|
void unload(void* texture) override { delete static_cast<TextureRecord*>(texture); }
|
|
};
|
|
struct RenderBatch {
|
|
std::vector<DtsRenderVertex> vertices;
|
|
std::vector<std::uint16_t> indices;
|
|
std::string texturePath;
|
|
std::uint32_t blendMode = 0;
|
|
};
|
|
struct RuntimeContext {
|
|
MetadataTextureLoader textureLoader;
|
|
std::unique_ptr<spine::Atlas> atlas;
|
|
std::unique_ptr<spine::SkeletonData> skeletonData;
|
|
std::unique_ptr<spine::Skeleton> skeleton;
|
|
std::unique_ptr<spine::AnimationStateData> animationStateData;
|
|
std::unique_ptr<spine::AnimationState> animationState;
|
|
std::unique_ptr<spine::SkeletonRenderer> renderer;
|
|
std::vector<std::string> animationNames;
|
|
std::vector<float> animationDurations;
|
|
std::vector<std::string> skinNames;
|
|
std::vector<RenderBatch> renderBatches;
|
|
std::string error;
|
|
};
|
|
RuntimeContext* context(DtsRuntimeHandle handle) { return static_cast<RuntimeContext*>(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<char>(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());
|
|
}
|
|
void collectDrawCommands(RuntimeContext& value) {
|
|
value.renderBatches.clear();
|
|
for (auto* command = value.renderer->render(*value.skeleton); command; command = command->next) {
|
|
RenderBatch batch;
|
|
batch.blendMode = static_cast<std::uint32_t>(command->blendMode);
|
|
if (const auto* texture = static_cast<TextureRecord*>(command->texture))
|
|
batch.texturePath = texture->path;
|
|
for (int index = 0; index < command->numVertices; ++index)
|
|
batch.vertices.push_back({command->positions[index * 2], -command->positions[index * 2 + 1],
|
|
command->uvs[index * 2], command->uvs[index * 2 + 1], command->colors[index]});
|
|
batch.indices.assign(command->indices, command->indices + command->numIndices);
|
|
value.renderBatches.push_back(std::move(batch));
|
|
}
|
|
}
|
|
} // 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 = "4.2";
|
|
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->renderer.reset();
|
|
value->animationState.reset();
|
|
value->animationStateData.reset();
|
|
value->skeleton.reset();
|
|
value->skeletonData.reset();
|
|
value->atlas.reset();
|
|
value->atlas = std::make_unique<spine::Atlas>(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<spine::Skeleton>(value->skeletonData.get());
|
|
value->animationStateData = std::make_unique<spine::AnimationStateData>(value->skeletonData.get());
|
|
value->animationState = std::make_unique<spine::AnimationState>(value->animationStateData.get());
|
|
value->renderer = std::make_unique<spine::SkeletonRenderer>();
|
|
if (!value->animationNames.empty())
|
|
value->animationState->setAnimation(0, value->animationNames.front().c_str(), true);
|
|
value->animationState->apply(*value->skeleton);
|
|
value->skeleton->updateWorldTransform(spine::Physics_Update);
|
|
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<uint32_t>(data->getBones().size());
|
|
info->slotCount = static_cast<uint32_t>(data->getSlots().size());
|
|
info->skinCount = static_cast<uint32_t>(value->skinNames.size());
|
|
info->animationCount = static_cast<uint32_t>(value->animationNames.size());
|
|
info->eventCount = static_cast<uint32_t>(data->getEvents().size());
|
|
info->constraintCount = static_cast<uint32_t>(data->getIkConstraints().size()
|
|
+ data->getTransformConstraints().size() + data->getPathConstraints().size()
|
|
+ data->getPhysicsConstraints().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 || !value->renderer) return -1;
|
|
value->animationState->update(std::max(0.0F, seconds));
|
|
value->animationState->apply(*value->skeleton);
|
|
value->skeleton->updateWorldTransform(spine::Physics_Update);
|
|
collectDrawCommands(*value);
|
|
return 0;
|
|
}
|
|
uint32_t dtsRuntimeGetDrawCommandCount(DtsRuntimeHandle handle) {
|
|
auto* value = context(handle);
|
|
return value ? static_cast<uint32_t>(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<uint32_t>(batch.vertices.size());
|
|
command->indices = batch.indices.data();
|
|
command->indexCount = static_cast<uint32_t>(batch.indices.size());
|
|
command->texturePathUtf8 = batch.texturePath.c_str();
|
|
command->blendMode = batch.blendMode;
|
|
return 0;
|
|
}
|
|
} // extern "C"
|