[flutter] skeleton drawable, vertices creation

This commit is contained in:
Mario Zechner 2022-08-19 17:38:46 +02:00
parent c1d9a9e23e
commit 887d77ef63
10 changed files with 419 additions and 4 deletions

View File

@ -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) {
}
};

View File

@ -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();

View File

@ -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

View File

@ -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": {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -27,9 +27,13 @@ class _MyAppState extends State<MyApp> {
}
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

View File

@ -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<spine_skeleton_data> loadSkeletonDataBinary(Pointer<spine_atlas> atlas,
return skeletonData;
}
Pointer<spine_skeleton_drawable> createSkeletonDrawable(Pointer<spine_skeleton_data> skeletonData) {
return _bindings.spine_skeleton_drawable_create(skeletonData);
}
void updateSkeletonDrawable(Pointer<spine_skeleton_drawable> drawable, double deltaTime) {
_bindings.spine_skeleton_drawable_update(drawable, deltaTime);
}
class RenderCommand {
late Vertices vertices;
late int atlasPageIndex;
RenderCommand? next;
RenderCommand(Pointer<spine_render_command> 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<RenderCommand> renderSkeletonDrawable(Pointer<spine_skeleton_drawable> drawable) {
Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(drawable);
List<RenderCommand> commands = [];
while(nativeCmd.address != nullptr.address) {
commands.add(RenderCommand(nativeCmd));
nativeCmd = nativeCmd.ref.next;
}
return commands;
}
const String _libName = 'spine_flutter';
final DynamicLibrary _dylib = () {

View File

@ -129,6 +129,76 @@ class SpineFlutterBindings {
'spine_skeleton_data_dispose');
late final _spine_skeleton_data_dispose = _spine_skeleton_data_disposePtr
.asFunction<void Function(ffi.Pointer<spine_skeleton_data>)>();
ffi.Pointer<spine_skeleton_drawable> spine_skeleton_drawable_create(
ffi.Pointer<spine_skeleton_data> skeletonData,
) {
return _spine_skeleton_drawable_create(
skeletonData,
);
}
late final _spine_skeleton_drawable_createPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<spine_skeleton_drawable> Function(
ffi.Pointer<spine_skeleton_data>)>>(
'spine_skeleton_drawable_create');
late final _spine_skeleton_drawable_create =
_spine_skeleton_drawable_createPtr.asFunction<
ffi.Pointer<spine_skeleton_drawable> Function(
ffi.Pointer<spine_skeleton_data>)>();
void spine_skeleton_drawable_update(
ffi.Pointer<spine_skeleton_drawable> drawable,
double deltaTime,
) {
return _spine_skeleton_drawable_update(
drawable,
deltaTime,
);
}
late final _spine_skeleton_drawable_updatePtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<spine_skeleton_drawable>,
ffi.Float)>>('spine_skeleton_drawable_update');
late final _spine_skeleton_drawable_update =
_spine_skeleton_drawable_updatePtr.asFunction<
void Function(ffi.Pointer<spine_skeleton_drawable>, double)>();
ffi.Pointer<spine_render_command> spine_skeleton_drawable_render(
ffi.Pointer<spine_skeleton_drawable> drawable,
) {
return _spine_skeleton_drawable_render(
drawable,
);
}
late final _spine_skeleton_drawable_renderPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<spine_render_command> Function(
ffi.Pointer<spine_skeleton_drawable>)>>(
'spine_skeleton_drawable_render');
late final _spine_skeleton_drawable_render =
_spine_skeleton_drawable_renderPtr.asFunction<
ffi.Pointer<spine_render_command> Function(
ffi.Pointer<spine_skeleton_drawable>)>();
void spine_skeleton_drawable_dispose(
ffi.Pointer<spine_skeleton_drawable> drawable,
) {
return _spine_skeleton_drawable_dispose(
drawable,
);
}
late final _spine_skeleton_drawable_disposePtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<spine_skeleton_drawable>)>>(
'spine_skeleton_drawable_dispose');
late final _spine_skeleton_drawable_dispose =
_spine_skeleton_drawable_disposePtr
.asFunction<void Function(ffi.Pointer<spine_skeleton_drawable>)>();
}
class spine_atlas extends ffi.Struct {
@ -147,3 +217,42 @@ class spine_skeleton_data extends ffi.Struct {
external ffi.Pointer<ffi.Int8> 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<ffi.Float> positions;
external ffi.Pointer<ffi.Float> uvs;
external ffi.Pointer<ffi.Int32> colors;
@ffi.Int32()
external int numVertices;
external ffi.Pointer<ffi.Uint16> indices;
@ffi.Int32()
external int numIndices;
@ffi.Int32()
external int atlasPage;
@ffi.Int32()
external int blendMode;
external ffi.Pointer<spine_render_command> next;
}
class spine_skeleton_drawable extends ffi.Struct {
external ffi.Pointer<ffi.Void> skeleton;
external ffi.Pointer<ffi.Void> animationState;
external ffi.Pointer<spine_render_command> renderCommand;
}

View File

@ -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<spine_atlas>(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<spine_skeleton_data>(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<spine_skeleton_data>(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<spine_skeleton_drawable>(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<spine_render_command>(1, __FILE__, __LINE__);
cmd->positions = SpineExtension::alloc<float>(numVertices * 2, __FILE__, __LINE__);
cmd->uvs = SpineExtension::alloc<float>(numVertices * 2, __FILE__, __LINE__);
cmd->colors = SpineExtension::alloc<int32_t>(numVertices, __FILE__, __LINE__);
cmd->numVertices = numVertices;
cmd->indices = SpineExtension::alloc<uint16_t>(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<unsigned short> quadIndices;
quadIndices.add(0);
quadIndices.add(1);
quadIndices.add(2);
quadIndices.add(2);
quadIndices.add(3);
quadIndices.add(0);
Vector<float> 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<float> *vertices = &worldVertices;
int verticesCount = 0;
Vector<float> *uvs = NULL;
Vector<unsigned short> *indices;
int indicesCount = 0;
Color *attachmentColor;
int pageIndex = -1;
if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
RegionAttachment *regionAttachment = (RegionAttachment *) attachment;
attachmentColor = &regionAttachment->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 = &regionAttachment->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<uint8_t>(skeleton->getColor().r * slot.getColor().r * attachmentColor->r * 255);
uint8_t g = static_cast<uint8_t>(skeleton->getColor().g * slot.getColor().g * attachmentColor->g * 255);
uint8_t b = static_cast<uint8_t>(skeleton->getColor().b * slot.getColor().b * attachmentColor->b * 255);
uint8_t a = static_cast<uint8_t>(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();
}

View File

@ -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);