From 5ed26e6ef5c144acbf4ddd763f11b1099938c45f Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 4 Jul 2024 18:32:07 +0200 Subject: [PATCH] Add `Debug Renderer` with and before/after paint callbacks --- .../esotericsoftware/spine/DebugRendering.kt | 64 +++++++++++++++++++ .../esotericsoftware/spine/MainActivity.kt | 8 +++ .../spine/android/DebugRenderer.java | 23 +++++++ .../spine/android/SkeletonRenderer.java | 3 +- .../spine/android/SpineController.java | 34 +++++++++- .../spine/android/SpineView.java | 8 ++- .../SpineControllerAfterPaintCallback.java | 14 ++++ .../SpineControllerBeforePaintCallback.java | 13 ++++ 8 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 spine-android/app/src/main/java/com/esotericsoftware/spine/DebugRendering.kt create mode 100644 spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/DebugRenderer.java create mode 100644 spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerAfterPaintCallback.java create mode 100644 spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerBeforePaintCallback.java diff --git a/spine-android/app/src/main/java/com/esotericsoftware/spine/DebugRendering.kt b/spine-android/app/src/main/java/com/esotericsoftware/spine/DebugRendering.kt new file mode 100644 index 000000000..cbd9952b7 --- /dev/null +++ b/spine-android/app/src/main/java/com/esotericsoftware/spine/DebugRendering.kt @@ -0,0 +1,64 @@ +package com.esotericsoftware.spine + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import androidx.navigation.NavHostController +import com.esotericsoftware.spine.android.DebugRenderer +import com.esotericsoftware.spine.android.SpineController +import com.esotericsoftware.spine.android.SpineView + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DebugRendering(nav: NavHostController) { + + val debugRenderer = remember { + DebugRenderer() + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = Destination.DebugRendering.title) }, + navigationIcon = { + IconButton({ nav.navigateUp() }) { + Icon( + Icons.Rounded.ArrowBack, + null, + ) + } + } + ) + } + ) { paddingValues -> + AndroidView( + factory = { ctx -> + SpineView(ctx).apply { + loadFromAsset( + "spineboy.atlas", + "spineboy-pro.json", + SpineController.Builder() + .setOnInitialized { + it.animationState.setAnimation(0, "walk", true) + } + .setOnAfterPaint { controller, canvas, commands -> + debugRenderer.render(controller.drawable, canvas, commands) + } + .build() + ) + } + }, + modifier = Modifier.padding(paddingValues) + ) + } +} diff --git a/spine-android/app/src/main/java/com/esotericsoftware/spine/MainActivity.kt b/spine-android/app/src/main/java/com/esotericsoftware/spine/MainActivity.kt index 39e59b1e3..4480e78e5 100644 --- a/spine-android/app/src/main/java/com/esotericsoftware/spine/MainActivity.kt +++ b/spine-android/app/src/main/java/com/esotericsoftware/spine/MainActivity.kt @@ -61,6 +61,7 @@ fun AppContent() { Destination.SimpleAnimation, Destination.PlayPause, Destination.AnimationStateEvents, + Destination.DebugRendering, Destination.IKFollowing, Destination.Physics ), @@ -87,6 +88,12 @@ fun AppContent() { AnimationState(navController) } + composable( + Destination.DebugRendering.route + ) { + DebugRendering(navController) + } + composable( Destination.IKFollowing.route ) { @@ -134,6 +141,7 @@ sealed class Destination(val route: String, val title: String) { data object Samples: Destination("samples", "Spine Android Examples") data object SimpleAnimation : Destination("simpleAnimation", "Simple Animation") data object PlayPause : Destination("playPause", "Play/Pause") + data object DebugRendering: Destination("debugRendering", "Debug Renderer") data object AnimationStateEvents : Destination("animationStateEvents", "Animation State Listener") data object IKFollowing : Destination("ikFollowing", "IK Following") data object Physics: Destination("physics", "Physics (drag anywhere)") diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/DebugRenderer.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/DebugRenderer.java new file mode 100644 index 000000000..a3e6b7695 --- /dev/null +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/DebugRenderer.java @@ -0,0 +1,23 @@ +package com.esotericsoftware.spine.android; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; + +import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.spine.Bone; + +public class DebugRenderer { + + public void render(AndroidSkeletonDrawable drawable, Canvas canvas, Array commands) { + Paint bonePaint = new Paint(); + bonePaint.setColor(android.graphics.Color.BLUE); + bonePaint.setStyle(Paint.Style.FILL); + + for (Bone bone : drawable.getSkeleton().getBones()) { + float x = bone.getWorldX(); + float y = bone.getWorldY(); + canvas.drawRect(new RectF(x - 2.5f, y - 2.5f, x + 2.5f, y + 2.5f), bonePaint); + } + } +} diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SkeletonRenderer.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SkeletonRenderer.java index 8362ede18..4db4726d5 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SkeletonRenderer.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SkeletonRenderer.java @@ -205,8 +205,7 @@ public class SkeletonRenderer { return commandList; } - public void render (Canvas canvas, Skeleton skeleton) { - Array commands = render(skeleton); + public void render (Canvas canvas, Array commands) { for (int i = 0; i < commands.size; i++) { RenderCommand command = commands.get(i); canvas.drawVertices(Canvas.VertexMode.TRIANGLES, command.vertices.size, command.vertices.items, 0, command.uvs.items, 0, diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java index bb15a8bd0..b5b05c9b0 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineController.java @@ -1,13 +1,17 @@ package com.esotericsoftware.spine.android; +import android.graphics.Canvas; import android.graphics.Point; import androidx.annotation.Nullable; +import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.AnimationState; import com.esotericsoftware.spine.AnimationStateData; import com.esotericsoftware.spine.Skeleton; import com.esotericsoftware.spine.SkeletonData; +import com.esotericsoftware.spine.android.utils.SpineControllerAfterPaintCallback; +import com.esotericsoftware.spine.android.utils.SpineControllerBeforePaintCallback; import com.esotericsoftware.spine.android.utils.SpineControllerCallback; public class SpineController { @@ -16,6 +20,8 @@ public class SpineController { private SpineControllerCallback onInitialized; private SpineControllerCallback onBeforeUpdateWorldTransforms; private SpineControllerCallback onAfterUpdateWorldTransforms; + private SpineControllerBeforePaintCallback onBeforePaint; + private SpineControllerAfterPaintCallback onAfterPaint; public Builder setOnInitialized(SpineControllerCallback onInitialized) { this.onInitialized = onInitialized; @@ -32,11 +38,23 @@ public class SpineController { return this; } + public Builder setOnBeforePaint(SpineControllerBeforePaintCallback onBeforePaint) { + this.onBeforePaint = onBeforePaint; + return this; + } + + public Builder setOnAfterPaint(SpineControllerAfterPaintCallback onAfterPaint) { + this.onAfterPaint = onAfterPaint; + return this; + } + public SpineController build() { SpineController spineController = new SpineController(); spineController.onInitialized = onInitialized; spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms; spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms; + spineController.onBeforePaint = onBeforePaint; + spineController.onAfterPaint = onAfterPaint; return spineController; } } @@ -44,6 +62,8 @@ public class SpineController { private @Nullable SpineControllerCallback onInitialized; private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms; private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms; + private @Nullable SpineControllerBeforePaintCallback onBeforePaint; + private @Nullable SpineControllerAfterPaintCallback onAfterPaint; private AndroidSkeletonDrawable drawable; private boolean playing = true; private double offsetX = 0; @@ -83,7 +103,7 @@ public class SpineController { return drawable.getAnimationState(); } - AndroidSkeletonDrawable getDrawable() { + public AndroidSkeletonDrawable getDrawable() { if (drawable == null) throw new RuntimeException("Controller is not initialized yet."); return drawable; } @@ -128,4 +148,16 @@ public class SpineController { onAfterUpdateWorldTransforms.execute(this); } } + + protected void callOnBeforePaint(Canvas canvas) { + if (onBeforePaint != null) { + onBeforePaint.execute(this, canvas); + } + } + + protected void callOnAfterPaint(Canvas canvas, Array renderCommands) { + if (onAfterPaint != null) { + onAfterPaint.execute(this, canvas, renderCommands); + } + } } 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 f7f89bbed..24610f3d5 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 @@ -29,7 +29,7 @@ package com.esotericsoftware.spine.android; -import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.android.bounds.Alignment; import com.esotericsoftware.spine.android.bounds.Bounds; import com.esotericsoftware.spine.android.bounds.BoundsProvider; @@ -153,11 +153,15 @@ public class SpineView extends View implements Choreographer.FrameCallback { } canvas.save(); + canvas.translate(offsetX, offsetY); canvas.scale(scaleX, scaleY * -1); canvas.translate(x, y); - renderer.render(canvas, controller.getSkeleton()); + controller.callOnBeforePaint(canvas); + Array commands = renderer.render(controller.getSkeleton()); + renderer.render(canvas, commands); + controller.callOnAfterPaint(canvas, commands); canvas.restore(); } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerAfterPaintCallback.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerAfterPaintCallback.java new file mode 100644 index 000000000..96e824ac8 --- /dev/null +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerAfterPaintCallback.java @@ -0,0 +1,14 @@ +package com.esotericsoftware.spine.android.utils; + +import android.graphics.Canvas; + +import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.spine.android.SkeletonRenderer; +import com.esotericsoftware.spine.android.SpineController; + +import java.util.List; + +@FunctionalInterface +public interface SpineControllerAfterPaintCallback { + void execute (SpineController controller, Canvas canvas, Array commands); +} diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerBeforePaintCallback.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerBeforePaintCallback.java new file mode 100644 index 000000000..16218fda4 --- /dev/null +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineControllerBeforePaintCallback.java @@ -0,0 +1,13 @@ +package com.esotericsoftware.spine.android.utils; + +import android.graphics.Canvas; + +import com.esotericsoftware.spine.android.SkeletonRenderer; +import com.esotericsoftware.spine.android.SpineController; + +import java.util.List; + +@FunctionalInterface +public interface SpineControllerBeforePaintCallback { + void execute (SpineController controller, Canvas canvas); +}