diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 6230d3e36..3d6a2fa73 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -7,21 +7,66 @@ void main() { runApp(const MyApp()); } -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); +class SpineWidget extends StatefulWidget { + final String skeletonFile; + final String atlasFile; + + const SpineWidget(this.skeletonFile, this.atlasFile, {super.key}); @override - _MyAppState createState() => _MyAppState(); + State createState() => _SpineWidgetState(); } -class SpinePainter extends CustomPainter { +class _SpineWidgetState extends State { + spine_flutter.SpineSkeletonDrawable? skeletonDrawable; + + @override + void initState() { + super.initState(); + loadSkeleton(widget.skeletonFile, widget.atlasFile); + } + + void loadSkeleton(String skeletonFile, String atlasFile) async { + final atlas = await spine_flutter.SpineAtlas.fromAsset(rootBundle, atlasFile); + final skeletonData = skeletonFile.endsWith(".json") ? + spine_flutter.SpineSkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile)) + : spine_flutter.SpineSkeletonData.fromBinary(atlas, await rootBundle.load(skeletonFile)); + skeletonDrawable = spine_flutter.SpineSkeletonDrawable(atlas, skeletonData); + skeletonDrawable?.update(0.016); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + if (skeletonDrawable != null) { + print("Skeleton loaded, creating painter"); + return CustomPaint( + painter: _SpinePainter(this), + child: Container() + ); + } else { + print("Skeleton not loaded yet"); + return Container(); + } + } +} + +class _SpinePainter extends CustomPainter { + final _SpineWidgetState state; + + _SpinePainter(this.state); + @override void paint(Canvas canvas, Size size) { - var paint = Paint() - ..color = Colors.teal - ..strokeWidth = 5 - ..strokeCap = StrokeCap.round; - canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint); + print("painting"); + final drawable = state.skeletonDrawable; + if (drawable == null) return; + final commands = drawable.render(); + canvas.translate(size.width / 2, size.height); + for (final cmd in commands) { + canvas.drawVertices(cmd.vertices, BlendMode.srcOut, Paint()..color = Colors.white); //drawable.atlas.atlasPagePaints[cmd.atlasPageIndex]); + } + canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), Paint()..color = Colors.blue); } @override @@ -30,32 +75,11 @@ class SpinePainter extends CustomPainter { } } -class SpineWidget extends StatelessWidget { - String skeletonFile; - String atlasFile; - late spine_flutter.SpineSkeletonDrawable skeletonDrawable; - - SpineWidget(this.skeletonFile, this.atlasFile) { - loadSkeleton(); - } - - void loadSkeleton() async { - final atlas = await spine_flutter.SpineAtlas.fromAsset(rootBundle, atlasFile); - final skeletonData = skeletonFile.endsWith(".json") ? - spine_flutter.SpineSkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile)) - : spine_flutter.SpineSkeletonData.fromBinary(atlas, await rootBundle.load(skeletonFile)); - skeletonDrawable = spine_flutter.SpineSkeletonDrawable(atlas, skeletonData); - skeletonDrawable.update(0.016); - print("Loaded skeleton"); - } +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return CustomPaint( - painter: SpinePainter(), - child: Container() - ); - } + _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @@ -68,8 +92,8 @@ class _MyAppState extends State { Widget build(BuildContext context) { const textStyle = TextStyle(fontSize: 25); const spacerSmall = SizedBox(height: 10); - return MaterialApp( - home: SpineWidget("assets/skeleton.json", "assets/skeleton.atlas") + return const MaterialApp( + home: SpineWidget("assets/spineboy-pro.json", "assets/spineboy.atlas") ); } } diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 0f12882c2..44846f818 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -1,12 +1,11 @@ - import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; import 'package:ffi/ffi.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'spine_flutter_bindings_generated.dart'; import 'package:path/path.dart' as Path; @@ -16,8 +15,9 @@ int minorVersion() => _bindings.spine_minor_version(); class SpineAtlas { Pointer _atlas; List _atlasPages; + List atlasPagePaints; - SpineAtlas(this._atlas, this._atlasPages); + SpineAtlas(this._atlas, this._atlasPages, this.atlasPagePaints); static Future fromAsset(AssetBundle assetBundle, String atlasFileName) async { final atlasData = await assetBundle.loadString(atlasFileName); @@ -33,13 +33,19 @@ class SpineAtlas { final atlasDir = Path.dirname(atlasFileName); List atlasPages = []; + List atlasPagePaints = []; for (int i = 0; i < atlas.ref.numImagePaths; i++) { final Pointer atlasPageFile = atlas.ref.imagePaths[i].cast(); final imagePath = Path.join(atlasDir, atlasPageFile.toDartString()); - atlasPages.add(Image(image: AssetImage(imagePath))); + var imageData = await assetBundle.load(imagePath); + final Codec codec = await instantiateImageCodec(imageData.buffer.asUint8List()); + final FrameInfo frameInfo = await codec.getNextFrame(); + final Image image = frameInfo.image; + atlasPages.add(image); + atlasPagePaints.add(Paint()..shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Matrix4.identity().storage)); } - return SpineAtlas(atlas, atlasPages); + return SpineAtlas(atlas, atlasPages, atlasPagePaints); } } @@ -92,7 +98,8 @@ class SpineSkeletonDrawable { Pointer nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable); List commands = []; while(nativeCmd.address != nullptr.address) { - commands.add(SpineRenderCommand(nativeCmd)); + final atlasPage = atlas._atlasPages[nativeCmd.ref.atlasPage]; + commands.add(SpineRenderCommand(nativeCmd, atlasPage.width.toDouble(), atlasPage.height.toDouble())); nativeCmd = nativeCmd.ref.next; } return commands; @@ -103,19 +110,28 @@ class SpineRenderCommand { late Vertices vertices; late int atlasPageIndex; - SpineRenderCommand(Pointer nativeCmd) { + SpineRenderCommand(Pointer nativeCmd, double pageWidth, double pageHeight) { atlasPageIndex = nativeCmd.ref.atlasPage; int numVertices = nativeCmd.ref.numVertices; int numIndices = nativeCmd.ref.numIndices; + final positions = Float32List.fromList(nativeCmd.ref.positions.asTypedList(numVertices * 2)); + final uvs = Float32List.fromList(nativeCmd.ref.uvs.asTypedList(numVertices * 2)); + final colors = Int32List.fromList(nativeCmd.ref.colors.asTypedList(numVertices)); + final indices = Uint16List.fromList(nativeCmd.ref.indices.asTypedList(numIndices)); + for (int i = 0; i < numVertices * 2; i += 2) { + uvs[i] *= pageWidth; + uvs[i+1] *= pageHeight; + } // 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)); + positions, + textureCoordinates: uvs, + colors: colors, + indices: indices + ); } } diff --git a/spine-flutter/src/spine_flutter.cpp b/spine-flutter/src/spine_flutter.cpp index 09448b381..b1efa4999 100644 --- a/spine-flutter/src/spine_flutter.cpp +++ b/spine-flutter/src/spine_flutter.cpp @@ -37,6 +37,7 @@ 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) { + Bone::setYDown(true); if (!atlas) return nullptr; if (!atlas->atlas) return nullptr; if (!skeletonData) return nullptr; @@ -51,6 +52,7 @@ 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) { + Bone::setYDown(true); if (!atlas) return nullptr; if (!atlas->atlas) return nullptr; if (!skeletonData) return nullptr; @@ -76,6 +78,7 @@ FFI_PLUGIN_EXPORT spine_skeleton_drawable *spine_skeleton_drawable_create(spine_ 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)); + drawable->clipping = new SkeletonClipping(); return drawable; } @@ -83,6 +86,7 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *d if (!drawable) return; if (!drawable->skeleton) return; if (!drawable->animationState) return; + if (!drawable->clipping) return; Skeleton *skeleton = (Skeleton*)drawable->skeleton; AnimationState *animationState = (AnimationState*)drawable->animationState; @@ -132,7 +136,7 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske quadIndices.add(3); quadIndices.add(0); Vector worldVertices; - SkeletonClipping clipper; + SkeletonClipping &clipper = *(SkeletonClipping*)drawable->clipping; Skeleton *skeleton = (Skeleton*)drawable->skeleton; spine_render_command *lastCommand = nullptr; @@ -238,6 +242,7 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_dispose(spine_skeleton_drawable * if (!drawable) return; if (drawable->skeleton) delete (Skeleton*)drawable->skeleton; if (drawable->animationState) delete (AnimationState*)drawable->animationState; + if (drawable->clipping) delete (SkeletonClipping*)drawable->clipping; while (drawable->renderCommand) { spine_render_command *cmd = drawable->renderCommand; drawable->renderCommand = cmd->next; diff --git a/spine-flutter/src/spine_flutter.h b/spine-flutter/src/spine_flutter.h index 135ba626e..3fdb7bdfc 100644 --- a/spine-flutter/src/spine_flutter.h +++ b/spine-flutter/src/spine_flutter.h @@ -67,6 +67,7 @@ typedef struct spine_render_command { typedef struct spine_skeleton_drawable { void *skeleton; void *animationState; + void *clipping; spine_render_command *renderCommand; } spine_skeleton_drawable;