[spine-android] Clean-up, batching renderer, clipping TBD

This commit is contained in:
Mario Zechner 2024-04-26 18:00:16 +02:00
parent f8ebef5715
commit 4f32c5bd1b
6 changed files with 471 additions and 378 deletions

View File

@ -34,7 +34,6 @@ fun AppContent() {
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Box { Box {
BackgroundImage()
SpineViewComposable() SpineViewComposable()
} }
} }
@ -52,13 +51,3 @@ fun SpineViewComposable(modifier: Modifier = Modifier.fillMaxSize()) {
modifier = modifier modifier = modifier
) )
} }
@Composable
fun BackgroundImage() {
val image: Painter = painterResource(id = com.esotericsoftware.spine.R.drawable.img) // Replace with your image resource
Image(
painter = image,
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}

View File

@ -29,11 +29,9 @@
package com.esotericsoftware.spine.android; package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skin; import com.esotericsoftware.spine.Skin;
import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;

View File

@ -1,45 +1,70 @@
package com.esotericsoftware.spine.android; package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.ObjectMap;
import com.esotericsoftware.spine.BlendMode;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapShader; import android.graphics.BitmapShader;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader; import android.graphics.Shader;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.TextureData;
public class AndroidTexture extends Texture { public class AndroidTexture extends Texture {
private Bitmap bitmap; private Bitmap bitmap;
private Paint paint; private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>();
protected AndroidTexture(Bitmap bitmap) { protected AndroidTexture (Bitmap bitmap) {
super(); super();
this.bitmap = bitmap; this.bitmap = bitmap;
this.paint = new Paint(); for (BlendMode blendMode : BlendMode.values()) {
Paint paint = new Paint();
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader); paint.setShader(shader);
switch (blendMode) {
case normal:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
break;
case multiply:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
break;
case additive:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
break;
case screen:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
break;
default:
break;
} }
public Bitmap getBitmap() { paints.put(blendMode, paint);
}
}
public Bitmap getBitmap () {
return bitmap; return bitmap;
} }
public Paint getPaint() { public Paint getPaint (BlendMode blendMode) {
return paint; return paints.get(blendMode);
} }
@Override @Override
public int getWidth() { public int getWidth () {
return bitmap.getWidth(); return bitmap.getWidth();
} }
@Override @Override
public int getHeight() { public int getHeight () {
return bitmap.getHeight(); return bitmap.getHeight();
} }
@Override @Override
public void dispose() { public void dispose () {
bitmap.recycle(); bitmap.recycle();
} }
} }

View File

@ -1,32 +1,32 @@
package com.esotericsoftware.spine.android; package com.esotericsoftware.spine.android;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
public class AndroidTextureAtlas { public class AndroidTextureAtlas {
private static interface BitmapLoader { private static interface BitmapLoader {
Bitmap load(String path); Bitmap load (String path);
} }
private Array<AndroidTexture> textures = new Array<>(); private Array<AndroidTexture> textures = new Array<>();
private Array<AtlasRegion> regions = new Array<>(); private Array<AtlasRegion> regions = new Array<>();
private AndroidTextureAtlas(TextureAtlasData data, BitmapLoader bitmapLoader) {
for (TextureAtlasData.Page page: data.getPages()) { private AndroidTextureAtlas (TextureAtlasData data, BitmapLoader bitmapLoader) {
for (TextureAtlasData.Page page : data.getPages()) {
page.texture = new AndroidTexture(bitmapLoader.load(page.textureFile.path())); page.texture = new AndroidTexture(bitmapLoader.load(page.textureFile.path()));
textures.add((AndroidTexture) page.texture); textures.add((AndroidTexture)page.texture);
} }
for (TextureAtlasData.Region region : data.getRegions()) { for (TextureAtlasData.Region region : data.getRegions()) {
@ -56,21 +56,21 @@ public class AndroidTextureAtlas {
return null; return null;
} }
public Array<AndroidTexture> getTextures() { public Array<AndroidTexture> getTextures () {
return textures; return textures;
} }
public Array<AtlasRegion> getRegions() { public Array<AtlasRegion> getRegions () {
return regions; return regions;
} }
static public AndroidTextureAtlas loadFromAssets(String atlasFile, AssetManager assetManager) { static public AndroidTextureAtlas loadFromAssets (String atlasFile, AssetManager assetManager) {
TextureAtlasData data = new TextureAtlasData(); TextureAtlasData data = new TextureAtlasData();
try { try {
FileHandle inputFile = new FileHandle() { FileHandle inputFile = new FileHandle() {
@Override @Override
public InputStream read() { public InputStream read () {
try { try {
return assetManager.open(atlasFile); return assetManager.open(atlasFile);
} catch (IOException e) { } catch (IOException e) {
@ -85,7 +85,7 @@ public class AndroidTextureAtlas {
return new AndroidTextureAtlas(data, new BitmapLoader() { return new AndroidTextureAtlas(data, new BitmapLoader() {
@Override @Override
public Bitmap load(String path) { public Bitmap load (String path) {
path = path.startsWith("/") ? path.substring(1) : path; path = path.startsWith("/") ? path.substring(1) : path;
try (InputStream in = new BufferedInputStream(assetManager.open(path))) { try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
return BitmapFactory.decodeStream(in); return BitmapFactory.decodeStream(in);

View File

@ -0,0 +1,191 @@
package com.esotericsoftware.spine.android;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.ShortArray;
import com.esotericsoftware.spine.BlendMode;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.Slot;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.utils.SkeletonClipping;
import android.graphics.Canvas;
public class SkeletonRenderer {
public static class RenderCommand implements Pool.Poolable {
FloatArray vertices = new FloatArray(32);
FloatArray uvs = new FloatArray(32);
IntArray colors = new IntArray(32);
ShortArray indices = new ShortArray(32);
BlendMode blendMode;
AndroidTexture texture;
@Override
public void reset () {
vertices.setSize(0);
uvs.setSize(0);
colors.setSize(0);
indices.setSize(0);
blendMode = null;
texture = null;
}
}
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
private final SkeletonClipping clipper = new SkeletonClipping();
private final Pool<RenderCommand> commandPool = new Pool<RenderCommand>(10) {
@Override
protected RenderCommand newObject () {
return new RenderCommand();
}
};
private final Array<RenderCommand> commandList = new Array<RenderCommand>();
public Array<RenderCommand> render (Skeleton skeleton) {
Color color = null, skeletonColor = skeleton.getColor();
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
commandPool.freeAll(commandList);
commandList.clear();
RenderCommand command = commandPool.obtain();
commandList.add(command);
int vertexStart = 0;
Object[] drawOrder = skeleton.getDrawOrder().items;
for (int i = 0, n = skeleton.getDrawOrder().size; i < n; i++) {
Slot slot = (Slot)drawOrder[i];
if (!slot.getBone().isActive()) {
clipper.clipEnd(slot);
continue;
}
int verticesLength = 0;
int vertexSize = 2;
float[] uvs = null;
short[] indices = null;
Attachment attachment = slot.getAttachment();
if (attachment == null) {
continue;
}
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = vertexSize << 2;
AndroidTexture texture = (AndroidTexture)region.getRegion().getTexture();
BlendMode blendMode = slot.getData().getBlendMode();
if (command.blendMode == null && command.texture == null) {
command.blendMode = blendMode;
command.texture = texture;
}
if (command.blendMode != blendMode || command.texture != texture || command.vertices.size + verticesLength > 64000) {
command = commandPool.obtain();
commandList.add(command);
vertexStart = 0;
command.blendMode = blendMode;
command.texture = texture;
}
command.vertices.setSize(command.vertices.size + verticesLength);
region.computeWorldVertices(slot, command.vertices.items, vertexStart, vertexSize);
uvs = region.getUVs();
indices = quadTriangles;
color = region.getColor();
} else if (attachment instanceof MeshAttachment) {
MeshAttachment mesh = (MeshAttachment)attachment;
verticesLength = mesh.getWorldVerticesLength();
AndroidTexture texture = (AndroidTexture)mesh.getRegion().getTexture();
BlendMode blendMode = slot.getData().getBlendMode();
if (command.blendMode == null && command.texture == null) {
command.blendMode = blendMode;
command.texture = texture;
}
if (command.blendMode != blendMode || command.texture != texture || command.vertices.size + verticesLength > 64000) {
command = commandPool.obtain();
commandList.add(command);
vertexStart = 0;
command.blendMode = blendMode;
command.texture = texture;
}
command.vertices.setSize(command.vertices.size + verticesLength);
mesh.computeWorldVertices(slot, 0, verticesLength, command.vertices.items, vertexStart, vertexSize);
uvs = mesh.getUVs();
indices = mesh.getTriangles();
color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment) {
ClippingAttachment clip = (ClippingAttachment)attachment;
clipper.clipStart(slot, clip);
continue;
} else {
continue;
}
Color slotColor = slot.getColor();
int c = (int)(a * slotColor.a * color.a * 255) << 24 //
| (int)(r * slotColor.r * color.r * 255) << 16 //
| (int)(g * slotColor.g * color.g * 255) << 8 //
| (int)(b * slotColor.b * color.b * 255);
if (clipper.isClipping()) {
// FIXME
throw new RuntimeException("Not implemented, need to split positions, uvs, colors");
// clipper.clipTriangles(vertices, verticesLength, triangles, triangles.length, uvs, c, 0, false);
// FloatArray clippedVertices = clipper.getClippedVertices();
// ShortArray clippedTriangles = clipper.getClippedTriangles();
// batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
// clippedTriangles.size);
} else {
command.uvs.addAll(uvs);
float[] uvsArray = command.uvs.items;
for (int ii = vertexStart, w = command.texture.getWidth(), h = command.texture.getHeight(),
nn = vertexStart + verticesLength; ii < nn; ii += 2) {
uvsArray[ii] = uvsArray[ii] * w;
uvsArray[ii + 1] = uvsArray[ii + 1] * h;
}
command.colors.setSize(command.colors.size + (verticesLength >> 1));
int[] colorsArray = command.colors.items;
for (int ii = vertexStart >> 1, nn = (vertexStart >> 1) + (verticesLength >> 1); ii < nn; ii++) {
colorsArray[ii] = c;
}
int indicesStart = command.indices.size;
command.indices.addAll(indices);
int firstIndex = vertexStart >> 1;
short[] indicesArray = command.indices.items;
for (int ii = indicesStart, nn = indicesStart + indices.length; ii < nn; ii++) {
indicesArray[ii] += firstIndex;
}
}
// FIXME wrt clipping
vertexStart += verticesLength;
clipper.clipEnd(slot);
}
clipper.clipEnd();
return commandList;
}
public void render (Canvas canvas, Skeleton skeleton, float x, float y) {
canvas.save();
canvas.translate(x, y);
Array<RenderCommand> commands = render(skeleton);
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,
command.colors.items, 0, command.indices.items, 0, command.indices.size, command.texture.getPaint(command.blendMode));
}
canvas.restore();
}
}

View File

@ -1,5 +1,19 @@
package com.esotericsoftware.spine.android; package com.esotericsoftware.spine.android;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
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.SkeletonBinary;
import com.esotericsoftware.spine.SkeletonData;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -9,174 +23,49 @@ import android.util.AttributeSet;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.View; import android.view.View;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.IntArray;
import com.esotericsoftware.spine.AnimationState;
import com.esotericsoftware.spine.AnimationStateData;
import com.esotericsoftware.spine.BlendMode;
import com.esotericsoftware.spine.Skeleton;
import com.esotericsoftware.spine.SkeletonBinary;
import com.esotericsoftware.spine.SkeletonData;
import com.esotericsoftware.spine.Slot;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.utils.SkeletonClipping;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class SpineView extends View implements Choreographer.FrameCallback { public class SpineView extends View implements Choreographer.FrameCallback {
private long lastTime = 0; private long lastTime = 0;
private long delta = 0; private float delta = 0;
private Paint textPaint; private Paint textPaint;
int instances = 100; int instances = 1;
Vector2[] coords = new Vector2[instances]; Vector2[] coords = new Vector2[instances];
AndroidTextureAtlas atlas; AndroidTextureAtlas atlas;
SkeletonData data; SkeletonData data;
Array<Skeleton> skeletons = new Array<>(); Array<Skeleton> skeletons = new Array<>();
Array<AnimationState> states = new Array<>(); Array<AnimationState> states = new Array<>();
SkeletonRenderer renderer = new SkeletonRenderer();
public SpineView(Context context) { public SpineView (Context context) {
super(context); super(context);
init(); init();
} }
public SpineView(Context context, AttributeSet attrs) { public SpineView (Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
init(); init();
} }
public SpineView(Context context, AttributeSet attrs, int defStyle) { public SpineView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
init(); init();
} }
private void loadSkeleton() { private void loadSkeleton () {
String skel = "spineboy-pro.skel";
String atlasFile = "spineboy.atlas";
AssetManager assetManager = this.getContext().getAssets(); AssetManager assetManager = this.getContext().getAssets();
atlas = AndroidTextureAtlas.loadFromAssets("spineboy.atlas", assetManager); atlas = AndroidTextureAtlas.loadFromAssets(atlasFile, assetManager);
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas); AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
SkeletonBinary binary = new SkeletonBinary(attachmentLoader); SkeletonBinary binary = new SkeletonBinary(attachmentLoader);
try (InputStream in = new BufferedInputStream(assetManager.open("spineboy-pro.skel"))) { try (InputStream in = new BufferedInputStream(assetManager.open(skel))) {
data = binary.readSkeletonData(in); data = binary.readSkeletonData(in);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0}; private void init () {
private final FloatArray vertices = new FloatArray(32);
private final FloatArray texCoords = new FloatArray(32);
private final IntArray colors = new IntArray(32);
private final SkeletonClipping clipper = new SkeletonClipping();
public void render (Canvas canvas, Skeleton skeleton, float x, float y) {
canvas.save();
canvas.translate(x, y);
canvas.scale(1, -1);
BlendMode blendMode = null;
int verticesLength = 0;
short[] triangles = null;
com.badlogic.gdx.graphics.Color color = null, skeletonColor = skeleton.getColor();
float r = skeletonColor.r, g = skeletonColor.g, b = skeletonColor.b, a = skeletonColor.a;
Object[] drawOrder = skeleton.getDrawOrder().items;
for (int i = 0, n = skeleton.getDrawOrder().size; i < n; i++) {
Slot slot = (Slot)drawOrder[i];
if (!slot.getBone().isActive()) {
clipper.clipEnd(slot);
continue;
}
AndroidTexture texture = null;
int vertexSize = 2;
Attachment attachment = slot.getAttachment();
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = vertexSize << 2;
region.computeWorldVertices(slot, vertices.items, 0, vertexSize);
triangles = quadTriangles;
texture = (AndroidTexture)region.getRegion().getTexture();
texCoords.clear();
texCoords.addAll(region.getUVs());
color = region.getColor();
} else if (attachment instanceof MeshAttachment) {
MeshAttachment mesh = (MeshAttachment)attachment;
int count = mesh.getWorldVerticesLength();
verticesLength = (count >> 1) * vertexSize;
this.vertices.setSize(verticesLength);
mesh.computeWorldVertices(slot, 0, count, vertices.items, 0, vertexSize);
triangles = mesh.getTriangles();
texture = (AndroidTexture)mesh.getRegion().getTexture();
texCoords.clear();;
texCoords.addAll(mesh.getUVs());
color = mesh.getColor();
} else if (attachment instanceof ClippingAttachment) {
ClippingAttachment clip = (ClippingAttachment)attachment;
clipper.clipStart(slot, clip);
continue;
} else {
continue;
}
if (texture != null) {
com.badlogic.gdx.graphics.Color slotColor = slot.getColor();
float alpha = a * slotColor.a * color.a * 255;
float multiplier = 255;
BlendMode slotBlendMode = slot.getData().getBlendMode();
if (slotBlendMode != blendMode) {
if (slotBlendMode == BlendMode.additive) {
slotBlendMode = BlendMode.normal;
alpha = 0;
}
blendMode = slotBlendMode;
// FIXME
// blendMode.apply(batch, pmaBlendModes);
}
int c = (int)alpha << 24 //
| (int)(b * slotColor.b * color.b * multiplier) << 16 //
| (int)(g * slotColor.g * color.g * multiplier) << 8 //
| (int)(r * slotColor.r * color.r * multiplier);
if (clipper.isClipping()) {
// FIXME
throw new RuntimeException("Not implemented, need to split positions, uvs, colors");
// clipper.clipTriangles(vertices, verticesLength, triangles, triangles.length, uvs, c, 0, false);
// FloatArray clippedVertices = clipper.getClippedVertices();
// ShortArray clippedTriangles = clipper.getClippedTriangles();
// batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
// clippedTriangles.size);
} else {
float[] uvsArray = texCoords.items;
for (int ii = 0, w = texture.getWidth(), h = texture.getHeight(); ii < verticesLength; ii += 2) {
uvsArray[ii] = uvsArray[ii] * w;
uvsArray[ii + 1] = uvsArray[ii + 1] * h;
}
colors.setSize(verticesLength >> 1);
int[] colorsArray = colors.items;
for (int ii = 0, nn = verticesLength >> 1; ii < nn; ii++) {
colorsArray[ii] = c;
}
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, verticesLength, vertices.items, 0, uvsArray, 0, colorsArray, 0, triangles, 0, triangles.length, texture.getPaint());
}
}
clipper.clipEnd(slot);
}
clipper.clipEnd();
canvas.restore();
}
private void init() {
textPaint = new Paint(); textPaint = new Paint();
textPaint.setColor(Color.WHITE); // Set the color of the paint textPaint.setColor(Color.WHITE); // Set the color of the paint
textPaint.setTextSize(48); textPaint.setTextSize(48);
@ -186,45 +75,46 @@ public class SpineView extends View implements Choreographer.FrameCallback {
for (int i = 0; i < instances; i++) { for (int i = 0; i < instances; i++) {
Skeleton skeleton = new Skeleton(data); Skeleton skeleton = new Skeleton(data);
skeleton.setScaleY(-1);
skeleton.setToSetupPose(); skeleton.setToSetupPose();
skeletons.add(skeleton); skeletons.add(skeleton);
AnimationStateData stateData = new AnimationStateData(data); AnimationStateData stateData = new AnimationStateData(data);
stateData.setDefaultMix(0.2f); stateData.setDefaultMix(0.2f);
AnimationState state = new AnimationState(stateData); AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true); state.setAnimation(0, "hoverboard", true);
states.add(state); states.add(state);
coords[i] = new Vector2(MathUtils.random(1000), MathUtils.random(2000)); if (i == 0) {
coords[i] = new Vector2(500, 1000);
} else {
coords[i] = new Vector2(MathUtils.random(1000), MathUtils.random(3000));
}
} }
} }
@Override @Override
public void onDraw(Canvas canvas) { public void onDraw (Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
float deltaF = delta / 1e9f;
for (int i = 0; i < instances; i++) { for (int i = 0; i < instances; i++) {
AnimationState state = states.get(i); AnimationState state = states.get(i);
Skeleton skeleton = skeletons.get(i); Skeleton skeleton = skeletons.get(i);
state.update(deltaF); state.update(delta);
state.apply(skeleton); state.apply(skeleton);
skeleton.update(deltaF); skeleton.update(delta);
skeleton.updateWorldTransform(Skeleton.Physics.update); skeleton.updateWorldTransform(Skeleton.Physics.update);
render(canvas, skeleton, coords[i].x, coords[i].y); renderer.render(canvas, skeleton, coords[i].x, coords[i].y);
} }
// canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.size, vertices.items, 0, uvs.items, 0, null, 0, indices.items, 0, 3 * 75, paint);
canvas.drawText(delta / 1e6 + " ms", 100, 100, textPaint); canvas.drawText(delta * 1000 + " ms", 100, 100, textPaint);
canvas.drawText(instances + " instances", 100, 150, textPaint); canvas.drawText(instances + " instances", 100, 150, textPaint);
} }
@Override @Override
public void doFrame(long frameTimeNanos) { public void doFrame (long frameTimeNanos) {
if (lastTime != 0) delta = frameTimeNanos - lastTime; if (lastTime != 0) delta = (frameTimeNanos - lastTime) / 1e9f;
lastTime = frameTimeNanos; lastTime = frameTimeNanos;
// Invalidate this view, causing onDraw to be called at the next animation frame
invalidate(); invalidate();
Choreographer.getInstance().postFrameCallback(this); Choreographer.getInstance().postFrameCallback(this);
} }