mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
[spine-android] Clean-up, batching renderer, clipping TBD
This commit is contained in:
parent
f8ebef5715
commit
4f32c5bd1b
@ -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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user