[flutter] Fix batching, invoke bounds provider once before controller initalization, finish SkinsAndAnimationBounds

This commit is contained in:
Mario Zechner 2022-11-24 07:02:09 +01:00
parent b65fef5b7a
commit 0b84fdbb32
15 changed files with 239 additions and 57 deletions

View File

@ -95,10 +95,16 @@ cp -f ../spineboy/export/spineboy-pro.json "$ROOT/spine-flutter/example/assets/"
cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-flutter/example/assets/"
cp -f ../spineboy/export/spineboy.atlas "$ROOT/spine-flutter/example/assets/"
cp -f ../spineboy/export/spineboy.png "$ROOT/spine-flutter/example/assets/"
cp -f ../mix-and-match/export/mix-and-match-pro.skel "$ROOT/spine-flutter/example/assets/"
cp -f ../mix-and-match/export/mix-and-match.atlas "$ROOT/spine-flutter/example/assets/"
cp -f ../mix-and-match/export/mix-and-match.png "$ROOT/spine-flutter/example/assets/"
cp -f ../dragon/export/dragon-ess.skel "$ROOT/spine-flutter/example/assets/"
cp -f ../dragon/export/dragon.atlas "$ROOT/spine-flutter/example/assets/"
cp -f ../dragon/export/dragon.png "$ROOT/spine-flutter/example/assets/"
cp -f ../dragon/export/dragon_*.png "$ROOT/spine-flutter/example/assets/"
echo "spine-godot"
rm -f "$ROOT"/spine-godot/example/assets/spineboy/*.atlas
rm -f "$ROOT"/spine-godot/example/assets/spineboy/*.png

Binary file not shown.

View File

@ -0,0 +1,123 @@
dragon.png
size: 1024, 1024
filter: Linear, Linear
back
bounds: 564, 534, 190, 185
chest
bounds: 2, 645, 136, 122
chin
bounds: 140, 619, 214, 146
front-toe-a
bounds: 2, 862, 29, 50
rotate: 90
front-toe-b
bounds: 467, 835, 56, 57
rotate: 90
head
bounds: 756, 398, 296, 260
rotate: 90
left-front-leg
bounds: 599, 834, 84, 57
left-front-thigh
bounds: 782, 819, 84, 72
left-rear-leg
bounds: 356, 558, 206, 177
left-rear-thigh
bounds: 216, 767, 91, 149
rotate: 90
left-wing01
bounds: 2, 268, 264, 589
rotate: 90
left-wing02
bounds: 2, 2, 264, 589
rotate: 90
right-front-leg
bounds: 113, 769, 101, 89
right-front-thigh
bounds: 758, 709, 108, 108
right-rear-leg
bounds: 640, 721, 116, 100
right-rear-thigh
bounds: 367, 742, 91, 149
rotate: 90
right-rear-toe
bounds: 2, 781, 109, 77
tail01
bounds: 868, 696, 120, 153
rotate: 90
tail02
bounds: 518, 737, 95, 120
rotate: 90
tail03
bounds: 868, 818, 73, 92
rotate: 90
tail04
bounds: 526, 835, 56, 71
rotate: 90
tail05
bounds: 406, 839, 52, 59
rotate: 90
tail06
bounds: 685, 823, 95, 68
thiagobrayner
bounds: 54, 860, 350, 31
dragon_2.png
size: 1024, 1024
filter: Linear, Linear
left-wing03
bounds: 2, 534, 264, 589
rotate: 90
left-wing04
bounds: 2, 268, 264, 589
rotate: 90
left-wing05
bounds: 593, 209, 264, 589
left-wing06
bounds: 2, 2, 264, 589
rotate: 90
dragon_3.png
size: 1024, 1024
filter: Linear, Linear
left-wing07
bounds: 2, 694, 264, 589
rotate: 90
left-wing08
bounds: 2, 428, 264, 589
rotate: 90
left-wing09
bounds: 593, 369, 264, 589
right-wing01
bounds: 2, 2, 365, 643
rotate: 90
dragon_4.png
size: 1024, 1024
filter: Linear, Linear
right-wing02
bounds: 2, 369, 365, 643
right-wing03
bounds: 369, 369, 365, 643
right-wing04
bounds: 2, 2, 365, 643
rotate: 90
dragon_5.png
size: 1024, 1024
filter: Linear, Linear
right-wing05
bounds: 2, 369, 365, 643
right-wing06
bounds: 369, 369, 365, 643
right-wing07
bounds: 2, 2, 365, 643
rotate: 90
dragon_6.png
size: 1024, 1024
filter: Linear, Linear
right-wing08
bounds: 2, 2, 365, 643
right-wing09
bounds: 369, 2, 365, 643

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -107,7 +107,7 @@ class DressUpState extends State<DressUp> {
),
),
Expanded(
child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(["full-skins/girl"]),)
child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"]),)
)
]
)

View File

@ -16,7 +16,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
void initState() {
super.initState();
controller = SpineWidgetController(onInitialized: (controller) {
controller.animationState.setAnimationByName(0, "walk", true);
controller.animationState.setAnimationByName(0, "flying", true);
});
isPlaying = true;
}
@ -33,7 +33,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
return Scaffold(
appBar: AppBar(title: const Text('Play/Pause')),
body: SpineWidget.asset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller),
body: SpineWidget.asset("assets/dragon.atlas", "assets/dragon-ess.skel", controller, boundsProvider: SkinAndAnimationBounds(animation: "flying"),),
floatingActionButton: FloatingActionButton(
onPressed: _togglePlay,
child: Icon(isPlaying ? Icons.pause : Icons.play_arrow),

View File

@ -70,7 +70,7 @@ class SkinsState extends State<Skins> {
)
),
Expanded(
child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(["full-skins/girl"]))
child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"]))
)
]
)

View File

@ -97,33 +97,54 @@ class RawBounds extends BoundsProvider {
}
class SkinAndAnimationBounds extends BoundsProvider {
final List<String> _skins;
final String? _animation;
final List<String> skins;
final String? animation;
final double stepTime;
SkinAndAnimationBounds(this._skins, [this._animation]);
SkinAndAnimationBounds({List<String>? skins, this.animation, this.stepTime = 0.1}) :
skins = skins == null || skins.isEmpty? ["default"] : skins;
@override
Bounds computeBounds(SkeletonDrawable drawable) {
var data = drawable.skeletonData;
var oldSkin = drawable.skeleton.getSkin();
var customSkin = Skin("custom-skin");
for (var skinName in _skins) {
for (var skinName in skins) {
var skin = data.findSkin(skinName);
if (skin == null) continue;
customSkin.addSkin(skin);
}
drawable.skeleton.setSkin(customSkin);
drawable.skeleton.setToSetupPose();
var bounds = drawable.skeleton.getBounds();
customSkin.dispose();
if (oldSkin == null) {
drawable.skeleton.setSkinByName("");
final animation = data.findAnimation(this.animation!);
double minX = double.infinity;
double minY = double.infinity;
double maxX = double.negativeInfinity;
double maxY = double.negativeInfinity;
if (animation == null) {
final bounds = drawable.skeleton.getBounds();
minX = bounds.x;
minY = bounds.y;
maxX = minX + bounds.width;
maxY = minY + bounds.height;
} else {
drawable.skeleton.setSkin(oldSkin);
drawable.animationState.setAnimation(0, animation, false);
final steps = max(animation.getDuration() / stepTime, 1.0).toInt();
for (int i = 0; i < steps; i++) {
drawable.update(i > 0 ? stepTime : 0);
final bounds = drawable.skeleton.getBounds();
minX = min(minX, bounds.x);
minY = min(minY, bounds.y);
maxX = max(maxX, minX + bounds.width);
maxY = max(maxY, minY + bounds.height);
}
}
customSkin.dispose();
drawable.skeleton.setSkinByName("default");
drawable.animationState.clearTracks();
drawable.skeleton.setToSetupPose();
return bounds;
drawable.update(0);
return Bounds(minX, minY, maxX - minX, maxY - minY);
}
}
@ -189,6 +210,7 @@ class SpineWidget extends StatefulWidget {
}
class _SpineWidgetState extends State<SpineWidget> {
late Bounds _computedBounds;
@override
void initState() {
@ -201,6 +223,7 @@ class _SpineWidgetState extends State<SpineWidget> {
}
void loadDrawable(SkeletonDrawable drawable) {
_computedBounds = widget._boundsProvider.computeBounds(drawable);
widget._controller._initialize(drawable);
setState(() {});
}
@ -224,7 +247,7 @@ class _SpineWidgetState extends State<SpineWidget> {
@override
Widget build(BuildContext context) {
if (widget._controller._drawable != null) {
return _SpineRenderObjectWidget(widget._controller._drawable!, widget._controller, widget._fit, widget._alignment, widget._boundsProvider, widget._sizedByBounds);
return _SpineRenderObjectWidget(widget._controller._drawable!, widget._controller, widget._fit, widget._alignment, _computedBounds, widget._sizedByBounds);
} else {
return const SizedBox();
}
@ -242,14 +265,14 @@ class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
final SpineWidgetController _controller;
final BoxFit _fit;
final Alignment _alignment;
final BoundsProvider _boundsProvider;
final Bounds _bounds;
final bool _sizedByBounds;
const _SpineRenderObjectWidget(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds);
const _SpineRenderObjectWidget(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._bounds, this._sizedByBounds);
@override
RenderObject createRenderObject(BuildContext context) {
return _SpineRenderObject(_skeletonDrawable, _controller, _fit, _alignment, _boundsProvider, _sizedByBounds);
return _SpineRenderObject(_skeletonDrawable, _controller, _fit, _alignment, _bounds, _sizedByBounds);
}
@override
@ -257,7 +280,7 @@ class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
renderObject.skeletonDrawable = _skeletonDrawable;
renderObject.fit = _fit;
renderObject.alignment = _alignment;
renderObject.boundsProvider = _boundsProvider;
renderObject.bounds = _bounds;
renderObject.sizedByBounds = _sizedByBounds;
}
}
@ -269,16 +292,14 @@ class _SpineRenderObject extends RenderBox {
final Stopwatch _stopwatch = Stopwatch();
BoxFit _fit;
Alignment _alignment;
BoundsProvider _boundsProvider;
bool _sizedByBounds;
Bounds _bounds;
_SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds): _bounds = _boundsProvider.computeBounds(_skeletonDrawable);
bool _sizedByBounds;
_SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._bounds, this._sizedByBounds);
set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
if (_skeletonDrawable == skeletonDrawable) return;
_skeletonDrawable = skeletonDrawable;
_bounds = _boundsProvider.computeBounds(_skeletonDrawable);
markNeedsLayout();
markNeedsPaint();
}
@ -303,12 +324,11 @@ class _SpineRenderObject extends RenderBox {
}
}
BoundsProvider get boundsProvider => _boundsProvider;
Bounds get bounds => _bounds;
set boundsProvider(BoundsProvider boundsProvider) {
if (boundsProvider != _boundsProvider) {
_boundsProvider = boundsProvider;
_bounds = boundsProvider.computeBounds(_skeletonDrawable);
set bounds(Bounds bounds) {
if (bounds != _bounds) {
_bounds = bounds;
markNeedsLayout();
markNeedsPaint();
}

View File

@ -3,20 +3,41 @@
using namespace spine;
String blendMode(spine_blend_mode mode) {
switch(mode) {
case SPINE_BLEND_MODE_NORMAL:
return "normal";
case SPINE_BLEND_MODE_ADDITIVE:
return "additiev";
case SPINE_BLEND_MODE_MULTIPLY:
return "multiply";
case SPINE_BLEND_MODE_SCREEN:
return "screen";
}
}
int main(int argc, char** argv) {
int atlasLength = 0;
void* atlasData = SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/spineboy.atlas", &atlasLength);
void* atlasData = SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/dragon.atlas", &atlasLength);
uint8_t* cstringAtlas = SpineExtension::calloc<uint8_t>(atlasLength + 1, __FILE__, __LINE__);
memcpy(cstringAtlas, atlasData, atlasLength);
int dataLength = 0;
uint8_t* data = (uint8_t*)SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/spineboy-pro.skel", &dataLength);
uint8_t* data = (uint8_t*)SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/dragon-ess.skel", &dataLength);
spine_atlas atlas = spine_atlas_load((const utf8*)cstringAtlas);
Vector<String> atlasPages;
for (int i = 0, n = spine_atlas_get_num_image_paths(atlas); i < n; i++) {
atlasPages.add(spine_atlas_get_image_path(atlas, i));
}
spine_skeleton_data_result result = spine_skeleton_data_load_binary(atlas, data, dataLength);
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(spine_skeleton_data_result_get_data(result));
spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
spine_skeleton_update_world_transform(skeleton);
spine_render_command cmd = spine_skeleton_drawable_render(drawable);
int batchId = 0;
while(cmd) {
int numVertices = spine_render_command_get_num_vertices(cmd);
int numIndices = spine_render_command_get_num_indices(cmd);
float *positions = spine_render_command_get_positions(cmd);
@ -24,6 +45,11 @@ int main(int argc, char** argv) {
int32_t *colors = spine_render_command_get_colors(cmd);
uint16_t *indices = spine_render_command_get_indices(cmd);
String str;
int atlasPage = spine_render_command_get_atlas_page(cmd);
str.append(atlasPages[atlasPage]);
str.append("\n");
str.append(blendMode(spine_render_command_get_blend_mode(cmd)));
str.append("\n");
str.append(numVertices);
str.append("\n");
str.append(numIndices);
@ -44,7 +70,14 @@ int main(int argc, char** argv) {
str.append(indices[i]);
str.append("\n");
}
FILE *file = fopen("/Users/badlogic/Desktop/spineboy.mesh", "w");
String outputFile = "";
outputFile.append("/Users/badlogic/Desktop/dragon-");
outputFile.append(batchId);
outputFile.append(".mesh");
FILE *file = fopen(outputFile.buffer(), "w");
fwrite(str.buffer(), str.length(), 1, file);
fclose(file);
batchId++;
cmd = spine_render_command_get_next(cmd);
}
}

View File

@ -657,7 +657,7 @@ void spine_skeleton_drawable_dispose(spine_skeleton_drawable drawable) {
}
static _spine_render_command *batch_sub_commands(BlockAllocator &allocator, Vector<_spine_render_command*> &commands, int first, int last, int numVertices, int numIndices) {
_spine_render_command *batched = spine_render_command_create(allocator, numVertices, numIndices, commands[0]->blendMode, commands[0]->atlasPage);
_spine_render_command *batched = spine_render_command_create(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->atlasPage);
float *positions = batched->positions;
float *uvs = batched->uvs;
int32_t *colors = batched->colors;