diff --git a/spine-cpp/spine-cpp/include/spine/Atlas.h b/spine-cpp/spine-cpp/include/spine/Atlas.h index 820609ee3..2cdabe762 100644 --- a/spine-cpp/spine-cpp/include/spine/Atlas.h +++ b/spine-cpp/spine-cpp/include/spine/Atlas.h @@ -84,11 +84,12 @@ namespace spine { TextureWrap vWrap; int width, height; bool pma; + int index; explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888), minFilter(TextureFilter_Nearest), magFilter(TextureFilter_Nearest), uWrap(TextureWrap_ClampToEdge), - vWrap(TextureWrap_ClampToEdge), width(0), height(0), pma(false) { + vWrap(TextureWrap_ClampToEdge), width(0), height(0), pma(false), index(0) { } }; diff --git a/spine-cpp/spine-cpp/src/spine/Atlas.cpp b/spine-cpp/spine-cpp/src/spine/Atlas.cpp index 6930ef3dc..d13a0806c 100644 --- a/spine-cpp/spine-cpp/src/spine/Atlas.cpp +++ b/spine-cpp/spine-cpp/src/spine/Atlas.cpp @@ -286,6 +286,7 @@ void Atlas::load(const char *begin, int length, const char *dir, bool createText } else { page->texturePath = String(path, true); } + page->index = _pages.size(); _pages.add(page); } else { AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion(); diff --git a/spine-flutter/example/assets/skeleton.atlas b/spine-flutter/example/assets/skeleton.atlas new file mode 100644 index 000000000..24c816d99 --- /dev/null +++ b/spine-flutter/example/assets/skeleton.atlas @@ -0,0 +1,6 @@ +skeleton.png +size:188,198 +filter:Linear,Linear +pma:true +Screenshot 2022-08-19 at 13.45.53 +bounds:2,2,184,194 diff --git a/spine-flutter/example/assets/skeleton.json b/spine-flutter/example/assets/skeleton.json new file mode 100644 index 000000000..d526e9ced --- /dev/null +++ b/spine-flutter/example/assets/skeleton.json @@ -0,0 +1,40 @@ +{ + "skeleton": { + "hash": "EzIWQLMq9sw", + "spine": "4.1.08", + "x": -92, + "y": -97, + "width": 184, + "height": 194, + "images": "", + "audio": "" + }, + "bones": [ + { + "name": "root" + } + ], + "slots": [ + { + "name": "image", + "bone": "root", + "attachment": "Screenshot 2022-08-19 at 13.45.53" + } + ], + "skins": [ + { + "name": "default", + "attachments": { + "image": { + "Screenshot 2022-08-19 at 13.45.53": { + "width": 184, + "height": 194 + } + } + } + } + ], + "animations": { + "animation": {} + } +} \ No newline at end of file diff --git a/spine-flutter/example/assets/skeleton.png b/spine-flutter/example/assets/skeleton.png new file mode 100644 index 000000000..abf133379 Binary files /dev/null and b/spine-flutter/example/assets/skeleton.png differ diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 22c93c6c8..781421cd0 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -27,9 +27,13 @@ class _MyAppState extends State { } void loadSkeleton() async { - final atlas = await spine_flutter.loadAtlas(rootBundle, "assets/spineboy.atlas"); - final skeletonData = spine_flutter.loadSkeletonDataJson(atlas, await rootBundle.loadString("assets/spineboy-pro.json")); - final skeletonDataBinary = spine_flutter.loadSkeletonDataBinary(atlas, await rootBundle.load("assets/spineboy-pro.skel")); + final atlas = await spine_flutter.loadAtlas(rootBundle, "assets/skeleton.atlas"); + final skeletonData = spine_flutter.loadSkeletonDataJson(atlas, await rootBundle.loadString("assets/skeleton.json")); + // final skeletonDataBinary = spine_flutter.loadSkeletonDataBinary(atlas, await rootBundle.load("assets/spineboy-pro.skel")); + final skeletonDrawable = spine_flutter.createSkeletonDrawable(skeletonData); + spine_flutter.updateSkeletonDrawable(skeletonDrawable, 0.016); + final renderCommands = spine_flutter.renderSkeletonDrawable(skeletonDrawable); + print(renderCommands[0].vertices); } @override diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 4205c3439..05d7cfd2b 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -1,6 +1,8 @@ import 'dart:ffi'; import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; import 'package:ffi/ffi.dart'; import 'package:flutter/services.dart'; @@ -60,6 +62,46 @@ Pointer loadSkeletonDataBinary(Pointer atlas, return skeletonData; } +Pointer createSkeletonDrawable(Pointer skeletonData) { + return _bindings.spine_skeleton_drawable_create(skeletonData); +} + +void updateSkeletonDrawable(Pointer drawable, double deltaTime) { + _bindings.spine_skeleton_drawable_update(drawable, deltaTime); +} + +class RenderCommand { + late Vertices vertices; + late int atlasPageIndex; + RenderCommand? next; + + RenderCommand(Pointer nativeCmd) { + atlasPageIndex = nativeCmd.ref.atlasPage; + int numVertices = nativeCmd.ref.numVertices; + int numIndices = nativeCmd.ref.numIndices; + // We pass the native data as views directly to Vertices.raw. According to the sources, the data + // is copied, so it doesn't matter that we free up the underlying memory on the next + // render call. See the implementation of Vertices.raw() here: + // https://github.com/flutter/engine/blob/5c60785b802ad2c8b8899608d949342d5c624952/lib/ui/painting/vertices.cc#L21 + vertices = Vertices.raw(VertexMode.triangles, + nativeCmd.ref.positions.asTypedList(numVertices * 2), + textureCoordinates: nativeCmd.ref.uvs.asTypedList(numVertices * 2), + colors: nativeCmd.ref.colors.asTypedList(numVertices), + indices: nativeCmd.ref.indices.asTypedList(numIndices)); + } +} + +List renderSkeletonDrawable(Pointer drawable) { + Pointer nativeCmd = _bindings.spine_skeleton_drawable_render(drawable); + List commands = []; + while(nativeCmd.address != nullptr.address) { + commands.add(RenderCommand(nativeCmd)); + nativeCmd = nativeCmd.ref.next; + } + return commands; + +} + const String _libName = 'spine_flutter'; final DynamicLibrary _dylib = () { diff --git a/spine-flutter/lib/spine_flutter_bindings_generated.dart b/spine-flutter/lib/spine_flutter_bindings_generated.dart index 63946fb60..fea84fb18 100644 --- a/spine-flutter/lib/spine_flutter_bindings_generated.dart +++ b/spine-flutter/lib/spine_flutter_bindings_generated.dart @@ -129,6 +129,76 @@ class SpineFlutterBindings { 'spine_skeleton_data_dispose'); late final _spine_skeleton_data_dispose = _spine_skeleton_data_disposePtr .asFunction)>(); + + ffi.Pointer spine_skeleton_drawable_create( + ffi.Pointer skeletonData, + ) { + return _spine_skeleton_drawable_create( + skeletonData, + ); + } + + late final _spine_skeleton_drawable_createPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>( + 'spine_skeleton_drawable_create'); + late final _spine_skeleton_drawable_create = + _spine_skeleton_drawable_createPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer)>(); + + void spine_skeleton_drawable_update( + ffi.Pointer drawable, + double deltaTime, + ) { + return _spine_skeleton_drawable_update( + drawable, + deltaTime, + ); + } + + late final _spine_skeleton_drawable_updatePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, + ffi.Float)>>('spine_skeleton_drawable_update'); + late final _spine_skeleton_drawable_update = + _spine_skeleton_drawable_updatePtr.asFunction< + void Function(ffi.Pointer, double)>(); + + ffi.Pointer spine_skeleton_drawable_render( + ffi.Pointer drawable, + ) { + return _spine_skeleton_drawable_render( + drawable, + ); + } + + late final _spine_skeleton_drawable_renderPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>( + 'spine_skeleton_drawable_render'); + late final _spine_skeleton_drawable_render = + _spine_skeleton_drawable_renderPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer)>(); + + void spine_skeleton_drawable_dispose( + ffi.Pointer drawable, + ) { + return _spine_skeleton_drawable_dispose( + drawable, + ); + } + + late final _spine_skeleton_drawable_disposePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer)>>( + 'spine_skeleton_drawable_dispose'); + late final _spine_skeleton_drawable_dispose = + _spine_skeleton_drawable_disposePtr + .asFunction)>(); } class spine_atlas extends ffi.Struct { @@ -147,3 +217,42 @@ class spine_skeleton_data extends ffi.Struct { external ffi.Pointer error; } + +abstract class spine_blend_mode { + static const int SPINE_BLEND_MODE_NORMAL = 0; + static const int SPINE_BLEND_MODE_ADDITIVE = 1; + static const int SPINE_BLEND_MODE_MULTIPLY = 2; + static const int SPINE_BLEND_MODE_SCREEN = 3; +} + +class spine_render_command extends ffi.Struct { + external ffi.Pointer positions; + + external ffi.Pointer uvs; + + external ffi.Pointer colors; + + @ffi.Int32() + external int numVertices; + + external ffi.Pointer indices; + + @ffi.Int32() + external int numIndices; + + @ffi.Int32() + external int atlasPage; + + @ffi.Int32() + external int blendMode; + + external ffi.Pointer next; +} + +class spine_skeleton_drawable extends ffi.Struct { + external ffi.Pointer skeleton; + + external ffi.Pointer animationState; + + external ffi.Pointer renderCommand; +} diff --git a/spine-flutter/src/spine_flutter.cpp b/spine-flutter/src/spine_flutter.cpp index f031a4191..09448b381 100644 --- a/spine-flutter/src/spine_flutter.cpp +++ b/spine-flutter/src/spine_flutter.cpp @@ -13,6 +13,7 @@ FFI_PLUGIN_EXPORT int32_t spine_minor_version() { } FFI_PLUGIN_EXPORT spine_atlas* spine_atlas_load(const char *atlasData) { + if (!atlasData) return nullptr; int length = strlen(atlasData); auto atlas = new Atlas(atlasData, length, "", (TextureLoader*)nullptr, false); spine_atlas *result = SpineExtension::calloc(1, __FILE__, __LINE__); @@ -36,6 +37,9 @@ FFI_PLUGIN_EXPORT void spine_atlas_dispose(spine_atlas *atlas) { } FFI_PLUGIN_EXPORT spine_skeleton_data *spine_skeleton_data_load_json(spine_atlas *atlas, const char *skeletonData) { + if (!atlas) return nullptr; + if (!atlas->atlas) return nullptr; + if (!skeletonData) return nullptr; SkeletonJson json((Atlas*)atlas->atlas); SkeletonData *data = json.readSkeletonData(skeletonData); spine_skeleton_data *result = SpineExtension::calloc(1, __FILE__, __LINE__); @@ -47,6 +51,10 @@ FFI_PLUGIN_EXPORT spine_skeleton_data *spine_skeleton_data_load_json(spine_atlas } FFI_PLUGIN_EXPORT spine_skeleton_data* spine_skeleton_data_load_binary(spine_atlas *atlas, const unsigned char *skeletonData, int32_t length) { + if (!atlas) return nullptr; + if (!atlas->atlas) return nullptr; + if (!skeletonData) return nullptr; + if (length <= 0) return nullptr; SkeletonBinary binary((Atlas*)atlas->atlas); SkeletonData *data = binary.readSkeletonData(skeletonData, length); spine_skeleton_data *result = SpineExtension::calloc(1, __FILE__, __LINE__); @@ -64,6 +72,180 @@ FFI_PLUGIN_EXPORT void spine_skeleton_data_dispose(spine_skeleton_data *skeleton SpineExtension::free(skeletonData, __FILE__, __LINE__); } +FFI_PLUGIN_EXPORT spine_skeleton_drawable *spine_skeleton_drawable_create(spine_skeleton_data *skeletonData) { + spine_skeleton_drawable *drawable = SpineExtension::calloc(1, __FILE__, __LINE__); + drawable->skeleton = new Skeleton((SkeletonData*)skeletonData->skeletonData); + drawable->animationState = new AnimationState(new AnimationStateData((SkeletonData*)skeletonData->skeletonData)); + return drawable; +} + +FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *drawable, float deltaTime) { + if (!drawable) return; + if (!drawable->skeleton) return; + if (!drawable->animationState) return; + + Skeleton *skeleton = (Skeleton*)drawable->skeleton; + AnimationState *animationState = (AnimationState*)drawable->animationState; + animationState->update(deltaTime); + animationState->apply(*skeleton); + skeleton->updateWorldTransform(); +} + +spine_render_command *spine_render_command_create(int32_t numVertices, int32_t numIndices, spine_blend_mode blendMode, int pageIndex) { + spine_render_command *cmd = SpineExtension::alloc(1, __FILE__, __LINE__); + cmd->positions = SpineExtension::alloc(numVertices * 2, __FILE__, __LINE__); + cmd->uvs = SpineExtension::alloc(numVertices * 2, __FILE__, __LINE__); + cmd->colors = SpineExtension::alloc(numVertices, __FILE__, __LINE__); + cmd->numVertices = numVertices; + cmd->indices = SpineExtension::alloc(numIndices, __FILE__, __LINE__); + cmd->numIndices = numIndices; + cmd->blendMode = blendMode; + cmd->atlasPage = pageIndex; + cmd->next = nullptr; + return cmd; +} + +void spine_render_command_dispose(spine_render_command *cmd) { + if (!cmd) return; + if (cmd->positions) SpineExtension::free(cmd->positions, __FILE__, __LINE__); + if (cmd->uvs) SpineExtension::free(cmd->uvs, __FILE__, __LINE__); + if (cmd->colors) SpineExtension::free(cmd->colors, __FILE__, __LINE__); + if (cmd->indices) SpineExtension::free(cmd->indices, __FILE__, __LINE__); + SpineExtension::free(cmd, __FILE__, __LINE__); +} + +FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_skeleton_drawable *drawable) { + if (!drawable) return nullptr; + if (!drawable->skeleton) return nullptr; + + while (drawable->renderCommand) { + spine_render_command *cmd = drawable->renderCommand; + drawable->renderCommand = cmd->next; + spine_render_command_dispose(cmd); + } + + Vector quadIndices; + quadIndices.add(0); + quadIndices.add(1); + quadIndices.add(2); + quadIndices.add(2); + quadIndices.add(3); + quadIndices.add(0); + Vector worldVertices; + SkeletonClipping clipper; + Skeleton *skeleton = (Skeleton*)drawable->skeleton; + spine_render_command *lastCommand = nullptr; + + for (unsigned i = 0; i < skeleton->getSlots().size(); ++i) { + Slot &slot = *skeleton->getDrawOrder()[i]; + Attachment *attachment = slot.getAttachment(); + if (!attachment) continue; + + // Early out if the slot color is 0 or the bone is not active + if (slot.getColor().a == 0 || !slot.getBone().isActive()) { + clipper.clipEnd(slot); + continue; + } + + Vector *vertices = &worldVertices; + int verticesCount = 0; + Vector *uvs = NULL; + Vector *indices; + int indicesCount = 0; + Color *attachmentColor; + int pageIndex = -1; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor = ®ionAttachment->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices.setSize(8, 0); + regionAttachment->computeWorldVertices(slot, worldVertices, 0, 2); + verticesCount = 4; + uvs = ®ionAttachment->getUVs(); + indices = &quadIndices; + indicesCount = 6; + pageIndex = ((AtlasRegion *) regionAttachment->getRendererObject())->page->index; + + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + attachmentColor = &mesh->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices.setSize(mesh->getWorldVerticesLength(), 0); + pageIndex = ((AtlasRegion *) mesh->getRendererObject())->page->index; + mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices.buffer(), 0, 2); + verticesCount = mesh->getWorldVerticesLength() >> 1; + uvs = &mesh->getUVs(); + indices = &mesh->getTriangles(); + indicesCount = indices->size(); + + } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); + clipper.clipStart(slot, clip); + continue; + } else + continue; + + uint8_t r = static_cast(skeleton->getColor().r * slot.getColor().r * attachmentColor->r * 255); + uint8_t g = static_cast(skeleton->getColor().g * slot.getColor().g * attachmentColor->g * 255); + uint8_t b = static_cast(skeleton->getColor().b * slot.getColor().b * attachmentColor->b * 255); + uint8_t a = static_cast(skeleton->getColor().a * slot.getColor().a * attachmentColor->a * 255); + uint32_t color = (a << 24) | (r << 16) | (g << 8) | b; + + if (clipper.isClipping()) { + clipper.clipTriangles(worldVertices, *indices, *uvs, 2); + vertices = &clipper.getClippedVertices(); + verticesCount = clipper.getClippedVertices().size() >> 1; + uvs = &clipper.getClippedUVs(); + indices = &clipper.getClippedTriangles(); + indicesCount = clipper.getClippedTriangles().size(); + } + + spine_render_command *cmd = spine_render_command_create(verticesCount, indicesCount, (spine_blend_mode)slot.getData().getBlendMode(), pageIndex); + + memcpy(cmd->positions, vertices->buffer(), (verticesCount << 2) * sizeof(float)); + memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 2) * sizeof(float)); + for (int ii = 0; ii < verticesCount; ii++) cmd->colors[ii] = color; + memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t)); + + if (!lastCommand) { + drawable->renderCommand = lastCommand = cmd; + } else { + lastCommand->next = cmd; + lastCommand = cmd; + } + + clipper.clipEnd(slot); + } + clipper.clipEnd(); + + return drawable->renderCommand; +} + +FFI_PLUGIN_EXPORT void spine_skeleton_drawable_dispose(spine_skeleton_drawable *drawable) { + if (!drawable) return; + if (drawable->skeleton) delete (Skeleton*)drawable->skeleton; + if (drawable->animationState) delete (AnimationState*)drawable->animationState; + while (drawable->renderCommand) { + spine_render_command *cmd = drawable->renderCommand; + drawable->renderCommand = cmd->next; + spine_render_command_dispose(cmd); + } + SpineExtension::free(drawable, __FILE__, __LINE__); +} + spine::SpineExtension *spine::getDefaultExtension() { return new spine::DefaultSpineExtension(); } diff --git a/spine-flutter/src/spine_flutter.h b/spine-flutter/src/spine_flutter.h index 030a668a9..135ba626e 100644 --- a/spine-flutter/src/spine_flutter.h +++ b/spine-flutter/src/spine_flutter.h @@ -45,3 +45,33 @@ FFI_PLUGIN_EXPORT spine_skeleton_data* spine_skeleton_data_load_json(spine_atlas FFI_PLUGIN_EXPORT spine_skeleton_data* spine_skeleton_data_load_binary(spine_atlas *atlas, const unsigned char *skeletonData, int32_t length); FFI_PLUGIN_EXPORT void spine_skeleton_data_dispose(spine_skeleton_data *skeletonData); +typedef enum spine_blend_mode { + SPINE_BLEND_MODE_NORMAL = 0, + SPINE_BLEND_MODE_ADDITIVE, + SPINE_BLEND_MODE_MULTIPLY, + SPINE_BLEND_MODE_SCREEN +} spine_blend_mode; + +typedef struct spine_render_command { + float *positions; + float *uvs; + int32_t *colors; + int32_t numVertices; + uint16_t *indices; + int32_t numIndices; + int32_t atlasPage; + spine_blend_mode blendMode; + struct spine_render_command *next; +} spine_render_command; + +typedef struct spine_skeleton_drawable { + void *skeleton; + void *animationState; + spine_render_command *renderCommand; +} spine_skeleton_drawable; + +FFI_PLUGIN_EXPORT spine_skeleton_drawable *spine_skeleton_drawable_create(spine_skeleton_data *skeletonData); +FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *drawable, float deltaTime); +FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_skeleton_drawable *drawable); +FFI_PLUGIN_EXPORT void spine_skeleton_drawable_dispose(spine_skeleton_drawable *drawable); +