From 2c8dd49a8597edc5d5df1b123fd099a5060afe61 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 4 Jul 2024 11:32:49 +0200 Subject: [PATCH] Add `SkinAndAnimationBounds` and use in `Play/Pause` Sample --- .../com/esotericsoftware/spine/PlayPause.kt | 21 +++--- .../spine/android/SpineView.java | 34 ++++++++- .../spine/android/bounds/Bounds.java | 17 +++++ .../spine/android/bounds/SetupPoseBounds.java | 9 +-- .../bounds/SkinAndAnimationBounds.java | 75 +++++++++++++++++++ 5 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SkinAndAnimationBounds.java diff --git a/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt b/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt index 8fc9e489a..adc78f689 100644 --- a/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt +++ b/spine-android/app/src/main/java/com/esotericsoftware/spine/PlayPause.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.navigation.NavHostController import com.esotericsoftware.spine.android.SpineController import com.esotericsoftware.spine.android.SpineView +import com.esotericsoftware.spine.android.bounds.SkinAndAnimationBounds @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -58,16 +59,18 @@ fun PlayPause( AndroidView( factory = { ctx -> - SpineView(ctx).apply { - loadFromAsset( - "dragon.atlas", - "dragon-ess.skel", - controller - ) - } + SpineView.Builder(ctx) + .setBoundsProvider(SkinAndAnimationBounds("flying")) + .build() + .apply { + loadFromAsset( + "dragon.atlas", + "dragon-ess.skel", + controller + ) + } }, modifier = Modifier.padding(paddingValues) ) } - -} \ No newline at end of file +} diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java index c00cc2fb3..615f4a08a 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java @@ -50,6 +50,35 @@ import java.io.File; import java.net.URL; public class SpineView extends View implements Choreographer.FrameCallback { + + public static class Builder { + private final Context context; + + private BoundsProvider boundsProvider = new SetupPoseBounds(); + private Alignment alignment = Alignment.CENTER; + + public Builder(Context context) { + this.context = context; + } + + public Builder setBoundsProvider(BoundsProvider boundsProvider) { + this.boundsProvider = boundsProvider; + return this; + } + + public Builder setAlignment(Alignment alignment) { + this.alignment = alignment; + return this; + } + + public SpineView build() { + SpineView spineView = new SpineView(context); + spineView.boundsProvider = boundsProvider; + spineView.alignment = alignment; + return spineView; + } + } + private long lastTime = 0; private float delta = 0; private float offsetX = 0; @@ -59,12 +88,13 @@ public class SpineView extends View implements Choreographer.FrameCallback { private float x = 0; private float y = 0; - SkeletonRenderer renderer = new SkeletonRenderer(); + private final SkeletonRenderer renderer = new SkeletonRenderer(); + private Bounds computedBounds = new Bounds(); + SpineController controller; BoundsProvider boundsProvider = new SetupPoseBounds(); - Bounds computedBounds = new Bounds(); Alignment alignment = Alignment.CENTER; public SpineView (Context context) { diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/Bounds.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/Bounds.java index c0676b627..e66afcd7e 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/Bounds.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/Bounds.java @@ -1,5 +1,9 @@ package com.esotericsoftware.spine.android.bounds; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.FloatArray; +import com.esotericsoftware.spine.Skeleton; + public class Bounds { private double x; private double y; @@ -20,6 +24,19 @@ public class Bounds { this.height = height; } + public Bounds(Skeleton skeleton) { + Vector2 offset = new Vector2(0, 0); + Vector2 size = new Vector2(0, 0); + FloatArray floatArray = new FloatArray(); + + skeleton.getBounds(offset, size, floatArray); + + x = offset.x; + y = offset.y; + width = size.x; + height = size.y; + } + public double getX() { return x; } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SetupPoseBounds.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SetupPoseBounds.java index c0497b78e..a7e3a6014 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SetupPoseBounds.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SetupPoseBounds.java @@ -8,13 +8,6 @@ public class SetupPoseBounds implements BoundsProvider { @Override public Bounds computeBounds(AndroidSkeletonDrawable drawable) { - - Vector2 offset = new Vector2(0, 0); - Vector2 size = new Vector2(0, 0); - FloatArray floatArray = new FloatArray(); - - drawable.getSkeleton().getBounds(offset, size, floatArray); - - return new Bounds(offset.x, offset.y, size.x, size.y); + return new Bounds(drawable.getSkeleton()); } } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SkinAndAnimationBounds.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SkinAndAnimationBounds.java new file mode 100644 index 000000000..c608607f9 --- /dev/null +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/bounds/SkinAndAnimationBounds.java @@ -0,0 +1,75 @@ +package com.esotericsoftware.spine.android.bounds; + +import com.esotericsoftware.spine.Animation; +import com.esotericsoftware.spine.SkeletonData; +import com.esotericsoftware.spine.Skin; +import com.esotericsoftware.spine.android.AndroidSkeletonDrawable; + +import java.util.Collections; +import java.util.List; + +public class SkinAndAnimationBounds implements BoundsProvider { + private final List skins; + private final String animation; + private final double stepTime; + + // Constructor + public SkinAndAnimationBounds(List skins, String animation, double stepTime) { + this.skins = (skins == null || skins.isEmpty()) ? Collections.singletonList("default") : skins; + this.animation = animation; + this.stepTime = stepTime; + } + + public SkinAndAnimationBounds(List skins, String animation) { + this(skins, animation, 0.1); + } + + public SkinAndAnimationBounds(String animation) { + this(Collections.emptyList(), animation, 0.1); + } + + @Override + public Bounds computeBounds(AndroidSkeletonDrawable drawable) { + SkeletonData data = drawable.getSkeletonData(); + Skin oldSkin = drawable.getSkeleton().getSkin(); + Skin customSkin = new Skin("custom-skin"); + for (String skinName : skins) { + Skin skin = data.findSkin(skinName); + if (skin == null) continue; + customSkin.addSkin(skin); + } + drawable.getSkeleton().setSkin(customSkin); + drawable.getSkeleton().setToSetupPose(); + + Animation animation = (this.animation != null) ? data.findAnimation(this.animation) : null; + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + if (animation == null) { + Bounds bounds = new Bounds(drawable.getSkeleton()); + minX = bounds.getX(); + minY = bounds.getY(); + maxX = minX + bounds.getWidth(); + maxY = minY + bounds.getHeight(); + } else { + drawable.getAnimationState().setAnimation(0, animation, false); + int steps = (int) Math.max( (animation.getDuration() / stepTime), 1.0); + for (int i = 0; i < steps; i++) { + drawable.update(i > 0 ? (float) stepTime : 0); + Bounds bounds = new Bounds(drawable.getSkeleton()); + minX = Math.min(minX, bounds.getX()); + minY = Math.min(minY, bounds.getY()); + maxX = Math.max(maxX, minX + bounds.getWidth()); + maxY = Math.max(maxY, minY + bounds.getHeight()); + } + } + + drawable.getSkeleton().setSkin("default"); + drawable.getAnimationState().clearTracks(); + if (oldSkin != null) drawable.getSkeleton().setSkin(oldSkin); + drawable.getSkeleton().setToSetupPose(); + drawable.update(0); + return new Bounds(minX, minY, maxX - minX, maxY - minY); + } +}