Add Debug Renderer with and before/after paint callbacks

This commit is contained in:
Denis Andrasec 2024-07-04 18:32:07 +02:00
parent 8df1b3ee27
commit 5ed26e6ef5
8 changed files with 162 additions and 5 deletions

View File

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

View File

@ -61,6 +61,7 @@ fun AppContent() {
Destination.SimpleAnimation, Destination.SimpleAnimation,
Destination.PlayPause, Destination.PlayPause,
Destination.AnimationStateEvents, Destination.AnimationStateEvents,
Destination.DebugRendering,
Destination.IKFollowing, Destination.IKFollowing,
Destination.Physics Destination.Physics
), ),
@ -87,6 +88,12 @@ fun AppContent() {
AnimationState(navController) AnimationState(navController)
} }
composable(
Destination.DebugRendering.route
) {
DebugRendering(navController)
}
composable( composable(
Destination.IKFollowing.route 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 Samples: Destination("samples", "Spine Android Examples")
data object SimpleAnimation : Destination("simpleAnimation", "Simple Animation") data object SimpleAnimation : Destination("simpleAnimation", "Simple Animation")
data object PlayPause : Destination("playPause", "Play/Pause") data object PlayPause : Destination("playPause", "Play/Pause")
data object DebugRendering: Destination("debugRendering", "Debug Renderer")
data object AnimationStateEvents : Destination("animationStateEvents", "Animation State Listener") data object AnimationStateEvents : Destination("animationStateEvents", "Animation State Listener")
data object IKFollowing : Destination("ikFollowing", "IK Following") data object IKFollowing : Destination("ikFollowing", "IK Following")
data object Physics: Destination("physics", "Physics (drag anywhere)") data object Physics: Destination("physics", "Physics (drag anywhere)")

View File

@ -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<SkeletonRenderer.RenderCommand> 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);
}
}
}

View File

@ -205,8 +205,7 @@ public class SkeletonRenderer {
return commandList; return commandList;
} }
public void render (Canvas canvas, Skeleton skeleton) { public void render (Canvas canvas, Array<RenderCommand> commands) {
Array<RenderCommand> commands = render(skeleton);
for (int i = 0; i < commands.size; i++) { for (int i = 0; i < commands.size; i++) {
RenderCommand command = commands.get(i); RenderCommand command = commands.get(i);
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, command.vertices.size, command.vertices.items, 0, command.uvs.items, 0, canvas.drawVertices(Canvas.VertexMode.TRIANGLES, command.vertices.size, command.vertices.items, 0, command.uvs.items, 0,

View File

@ -1,13 +1,17 @@
package com.esotericsoftware.spine.android; package com.esotericsoftware.spine.android;
import android.graphics.Canvas;
import android.graphics.Point; import android.graphics.Point;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.AnimationState; import com.esotericsoftware.spine.AnimationState;
import com.esotericsoftware.spine.AnimationStateData; import com.esotericsoftware.spine.AnimationStateData;
import com.esotericsoftware.spine.Skeleton; import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.SkeletonData; 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; import com.esotericsoftware.spine.android.utils.SpineControllerCallback;
public class SpineController { public class SpineController {
@ -16,6 +20,8 @@ public class SpineController {
private SpineControllerCallback onInitialized; private SpineControllerCallback onInitialized;
private SpineControllerCallback onBeforeUpdateWorldTransforms; private SpineControllerCallback onBeforeUpdateWorldTransforms;
private SpineControllerCallback onAfterUpdateWorldTransforms; private SpineControllerCallback onAfterUpdateWorldTransforms;
private SpineControllerBeforePaintCallback onBeforePaint;
private SpineControllerAfterPaintCallback onAfterPaint;
public Builder setOnInitialized(SpineControllerCallback onInitialized) { public Builder setOnInitialized(SpineControllerCallback onInitialized) {
this.onInitialized = onInitialized; this.onInitialized = onInitialized;
@ -32,11 +38,23 @@ public class SpineController {
return this; 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() { public SpineController build() {
SpineController spineController = new SpineController(); SpineController spineController = new SpineController();
spineController.onInitialized = onInitialized; spineController.onInitialized = onInitialized;
spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms; spineController.onBeforeUpdateWorldTransforms = onBeforeUpdateWorldTransforms;
spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms; spineController.onAfterUpdateWorldTransforms = onAfterUpdateWorldTransforms;
spineController.onBeforePaint = onBeforePaint;
spineController.onAfterPaint = onAfterPaint;
return spineController; return spineController;
} }
} }
@ -44,6 +62,8 @@ public class SpineController {
private @Nullable SpineControllerCallback onInitialized; private @Nullable SpineControllerCallback onInitialized;
private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms; private @Nullable SpineControllerCallback onBeforeUpdateWorldTransforms;
private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms; private @Nullable SpineControllerCallback onAfterUpdateWorldTransforms;
private @Nullable SpineControllerBeforePaintCallback onBeforePaint;
private @Nullable SpineControllerAfterPaintCallback onAfterPaint;
private AndroidSkeletonDrawable drawable; private AndroidSkeletonDrawable drawable;
private boolean playing = true; private boolean playing = true;
private double offsetX = 0; private double offsetX = 0;
@ -83,7 +103,7 @@ public class SpineController {
return drawable.getAnimationState(); return drawable.getAnimationState();
} }
AndroidSkeletonDrawable getDrawable() { public AndroidSkeletonDrawable getDrawable() {
if (drawable == null) throw new RuntimeException("Controller is not initialized yet."); if (drawable == null) throw new RuntimeException("Controller is not initialized yet.");
return drawable; return drawable;
} }
@ -128,4 +148,16 @@ public class SpineController {
onAfterUpdateWorldTransforms.execute(this); onAfterUpdateWorldTransforms.execute(this);
} }
} }
protected void callOnBeforePaint(Canvas canvas) {
if (onBeforePaint != null) {
onBeforePaint.execute(this, canvas);
}
}
protected void callOnAfterPaint(Canvas canvas, Array<SkeletonRenderer.RenderCommand> renderCommands) {
if (onAfterPaint != null) {
onAfterPaint.execute(this, canvas, renderCommands);
}
}
} }

View File

@ -29,7 +29,7 @@
package com.esotericsoftware.spine.android; 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.Alignment;
import com.esotericsoftware.spine.android.bounds.Bounds; import com.esotericsoftware.spine.android.bounds.Bounds;
import com.esotericsoftware.spine.android.bounds.BoundsProvider; import com.esotericsoftware.spine.android.bounds.BoundsProvider;
@ -153,11 +153,15 @@ public class SpineView extends View implements Choreographer.FrameCallback {
} }
canvas.save(); canvas.save();
canvas.translate(offsetX, offsetY); canvas.translate(offsetX, offsetY);
canvas.scale(scaleX, scaleY * -1); canvas.scale(scaleX, scaleY * -1);
canvas.translate(x, y); canvas.translate(x, y);
renderer.render(canvas, controller.getSkeleton()); controller.callOnBeforePaint(canvas);
Array<SkeletonRenderer.RenderCommand> commands = renderer.render(controller.getSkeleton());
renderer.render(canvas, commands);
controller.callOnAfterPaint(canvas, commands);
canvas.restore(); canvas.restore();
} }

View File

@ -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<SkeletonRenderer.RenderCommand> commands);
}

View File

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