mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +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
|
||||
) {
|
||||
Box {
|
||||
BackgroundImage()
|
||||
SpineViewComposable()
|
||||
}
|
||||
}
|
||||
@ -52,13 +51,3 @@ fun SpineViewComposable(modifier: Modifier = Modifier.fillMaxSize()) {
|
||||
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;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.utils.Null;
|
||||
|
||||
import com.esotericsoftware.spine.Skin;
|
||||
import com.esotericsoftware.spine.attachments.AttachmentLoader;
|
||||
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
|
||||
@ -50,61 +48,61 @@ import com.esotericsoftware.spine.attachments.Sequence;
|
||||
* Spine Runtimes Guide. */
|
||||
@SuppressWarnings("javadoc")
|
||||
public class AndroidAtlasAttachmentLoader implements AttachmentLoader {
|
||||
private AndroidTextureAtlas atlas;
|
||||
private AndroidTextureAtlas atlas;
|
||||
|
||||
public AndroidAtlasAttachmentLoader (AndroidTextureAtlas atlas) {
|
||||
if (atlas == null) throw new IllegalArgumentException("atlas cannot be null.");
|
||||
this.atlas = atlas;
|
||||
}
|
||||
public AndroidAtlasAttachmentLoader (AndroidTextureAtlas atlas) {
|
||||
if (atlas == null) throw new IllegalArgumentException("atlas cannot be null.");
|
||||
this.atlas = atlas;
|
||||
}
|
||||
|
||||
private void loadSequence (String name, String basePath, Sequence sequence) {
|
||||
TextureRegion[] regions = sequence.getRegions();
|
||||
for (int i = 0, n = regions.length; i < n; i++) {
|
||||
String path = sequence.getPath(basePath, i);
|
||||
regions[i] = atlas.findRegion(path);
|
||||
if (regions[i] == null) throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")");
|
||||
}
|
||||
}
|
||||
private void loadSequence (String name, String basePath, Sequence sequence) {
|
||||
TextureRegion[] regions = sequence.getRegions();
|
||||
for (int i = 0, n = regions.length; i < n; i++) {
|
||||
String path = sequence.getPath(basePath, i);
|
||||
regions[i] = atlas.findRegion(path);
|
||||
if (regions[i] == null) throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
||||
RegionAttachment attachment = new RegionAttachment(name);
|
||||
if (sequence != null)
|
||||
loadSequence(name, path, sequence);
|
||||
else {
|
||||
AtlasRegion region = atlas.findRegion(path);
|
||||
if (region == null)
|
||||
throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
|
||||
attachment.setRegion(region);
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
||||
RegionAttachment attachment = new RegionAttachment(name);
|
||||
if (sequence != null)
|
||||
loadSequence(name, path, sequence);
|
||||
else {
|
||||
AtlasRegion region = atlas.findRegion(path);
|
||||
if (region == null)
|
||||
throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
|
||||
attachment.setRegion(region);
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
||||
MeshAttachment attachment = new MeshAttachment(name);
|
||||
if (sequence != null)
|
||||
loadSequence(name, path, sequence);
|
||||
else {
|
||||
AtlasRegion region = atlas.findRegion(path);
|
||||
if (region == null)
|
||||
throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
|
||||
attachment.setRegion(region);
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
||||
MeshAttachment attachment = new MeshAttachment(name);
|
||||
if (sequence != null)
|
||||
loadSequence(name, path, sequence);
|
||||
else {
|
||||
AtlasRegion region = atlas.findRegion(path);
|
||||
if (region == null)
|
||||
throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
|
||||
attachment.setRegion(region);
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
|
||||
return new BoundingBoxAttachment(name);
|
||||
}
|
||||
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
|
||||
return new BoundingBoxAttachment(name);
|
||||
}
|
||||
|
||||
public ClippingAttachment newClippingAttachment (Skin skin, String name) {
|
||||
return new ClippingAttachment(name);
|
||||
}
|
||||
public ClippingAttachment newClippingAttachment (Skin skin, String name) {
|
||||
return new ClippingAttachment(name);
|
||||
}
|
||||
|
||||
public PathAttachment newPathAttachment (Skin skin, String name) {
|
||||
return new PathAttachment(name);
|
||||
}
|
||||
public PathAttachment newPathAttachment (Skin skin, String name) {
|
||||
return new PathAttachment(name);
|
||||
}
|
||||
|
||||
public PointAttachment newPointAttachment (Skin skin, String name) {
|
||||
return new PointAttachment(name);
|
||||
}
|
||||
public PointAttachment newPointAttachment (Skin skin, String name) {
|
||||
return new PointAttachment(name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,45 +1,70 @@
|
||||
|
||||
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.BitmapShader;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Shader;
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.TextureData;
|
||||
|
||||
public class AndroidTexture extends Texture {
|
||||
private Bitmap bitmap;
|
||||
private Paint paint;
|
||||
private Bitmap bitmap;
|
||||
private ObjectMap<BlendMode, Paint> paints = new ObjectMap<>();
|
||||
|
||||
protected AndroidTexture(Bitmap bitmap) {
|
||||
super();
|
||||
this.bitmap = bitmap;
|
||||
this.paint = new Paint();
|
||||
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
paint.setShader(shader);
|
||||
}
|
||||
protected AndroidTexture (Bitmap bitmap) {
|
||||
super();
|
||||
this.bitmap = bitmap;
|
||||
for (BlendMode blendMode : BlendMode.values()) {
|
||||
Paint paint = new Paint();
|
||||
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
paint.setShader(shader);
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return bitmap;
|
||||
}
|
||||
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 Paint getPaint() {
|
||||
return paint;
|
||||
}
|
||||
paints.put(blendMode, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return bitmap.getWidth();
|
||||
}
|
||||
public Bitmap getBitmap () {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return bitmap.getHeight();
|
||||
}
|
||||
public Paint getPaint (BlendMode blendMode) {
|
||||
return paints.get(blendMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
bitmap.recycle();
|
||||
}
|
||||
@Override
|
||||
public int getWidth () {
|
||||
return bitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight () {
|
||||
return bitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose () {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,98 +1,98 @@
|
||||
|
||||
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.graphics.Bitmap;
|
||||
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 {
|
||||
private static interface BitmapLoader {
|
||||
Bitmap load(String path);
|
||||
}
|
||||
private static interface BitmapLoader {
|
||||
Bitmap load (String path);
|
||||
}
|
||||
|
||||
private Array<AndroidTexture> textures = new Array<>();
|
||||
private Array<AtlasRegion> regions = new Array<>();
|
||||
private AndroidTextureAtlas(TextureAtlasData data, BitmapLoader bitmapLoader) {
|
||||
for (TextureAtlasData.Page page: data.getPages()) {
|
||||
page.texture = new AndroidTexture(bitmapLoader.load(page.textureFile.path()));
|
||||
textures.add((AndroidTexture) page.texture);
|
||||
}
|
||||
private Array<AndroidTexture> textures = new Array<>();
|
||||
private Array<AtlasRegion> regions = new Array<>();
|
||||
|
||||
for (TextureAtlasData.Region region : data.getRegions()) {
|
||||
AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, //
|
||||
region.rotate ? region.height : region.width, //
|
||||
region.rotate ? region.width : region.height);
|
||||
atlasRegion.index = region.index;
|
||||
atlasRegion.name = region.name;
|
||||
atlasRegion.offsetX = region.offsetX;
|
||||
atlasRegion.offsetY = region.offsetY;
|
||||
atlasRegion.originalHeight = region.originalHeight;
|
||||
atlasRegion.originalWidth = region.originalWidth;
|
||||
atlasRegion.rotate = region.rotate;
|
||||
atlasRegion.degrees = region.degrees;
|
||||
atlasRegion.names = region.names;
|
||||
atlasRegion.values = region.values;
|
||||
if (region.flip) atlasRegion.flip(false, true);
|
||||
regions.add(atlasRegion);
|
||||
}
|
||||
}
|
||||
private AndroidTextureAtlas (TextureAtlasData data, BitmapLoader bitmapLoader) {
|
||||
for (TextureAtlasData.Page page : data.getPages()) {
|
||||
page.texture = new AndroidTexture(bitmapLoader.load(page.textureFile.path()));
|
||||
textures.add((AndroidTexture)page.texture);
|
||||
}
|
||||
|
||||
/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the
|
||||
* result should be cached rather than calling this method multiple times. */
|
||||
public @Null AtlasRegion findRegion (String name) {
|
||||
for (int i = 0, n = regions.size; i < n; i++)
|
||||
if (regions.get(i).name.equals(name)) return regions.get(i);
|
||||
return null;
|
||||
}
|
||||
for (TextureAtlasData.Region region : data.getRegions()) {
|
||||
AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, //
|
||||
region.rotate ? region.height : region.width, //
|
||||
region.rotate ? region.width : region.height);
|
||||
atlasRegion.index = region.index;
|
||||
atlasRegion.name = region.name;
|
||||
atlasRegion.offsetX = region.offsetX;
|
||||
atlasRegion.offsetY = region.offsetY;
|
||||
atlasRegion.originalHeight = region.originalHeight;
|
||||
atlasRegion.originalWidth = region.originalWidth;
|
||||
atlasRegion.rotate = region.rotate;
|
||||
atlasRegion.degrees = region.degrees;
|
||||
atlasRegion.names = region.names;
|
||||
atlasRegion.values = region.values;
|
||||
if (region.flip) atlasRegion.flip(false, true);
|
||||
regions.add(atlasRegion);
|
||||
}
|
||||
}
|
||||
|
||||
public Array<AndroidTexture> getTextures() {
|
||||
return textures;
|
||||
}
|
||||
/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the
|
||||
* result should be cached rather than calling this method multiple times. */
|
||||
public @Null AtlasRegion findRegion (String name) {
|
||||
for (int i = 0, n = regions.size; i < n; i++)
|
||||
if (regions.get(i).name.equals(name)) return regions.get(i);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Array<AtlasRegion> getRegions() {
|
||||
return regions;
|
||||
}
|
||||
public Array<AndroidTexture> getTextures () {
|
||||
return textures;
|
||||
}
|
||||
|
||||
static public AndroidTextureAtlas loadFromAssets(String atlasFile, AssetManager assetManager) {
|
||||
TextureAtlasData data = new TextureAtlasData();
|
||||
public Array<AtlasRegion> getRegions () {
|
||||
return regions;
|
||||
}
|
||||
|
||||
try {
|
||||
FileHandle inputFile = new FileHandle() {
|
||||
@Override
|
||||
public InputStream read() {
|
||||
try {
|
||||
return assetManager.open(atlasFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
data.load(inputFile, new FileHandle(atlasFile).parent(), false);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
static public AndroidTextureAtlas loadFromAssets (String atlasFile, AssetManager assetManager) {
|
||||
TextureAtlasData data = new TextureAtlasData();
|
||||
|
||||
return new AndroidTextureAtlas(data, new BitmapLoader() {
|
||||
@Override
|
||||
public Bitmap load(String path) {
|
||||
path = path.startsWith("/") ? path.substring(1) : path;
|
||||
try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
|
||||
return BitmapFactory.decodeStream(in);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
FileHandle inputFile = new FileHandle() {
|
||||
@Override
|
||||
public InputStream read () {
|
||||
try {
|
||||
return assetManager.open(atlasFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
data.load(inputFile, new FileHandle(atlasFile).parent(), false);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
|
||||
return new AndroidTextureAtlas(data, new BitmapLoader() {
|
||||
@Override
|
||||
public Bitmap load (String path) {
|
||||
path = path.startsWith("/") ? path.substring(1) : path;
|
||||
try (InputStream in = new BufferedInputStream(assetManager.open(path))) {
|
||||
return BitmapFactory.decodeStream(in);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
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.res.AssetManager;
|
||||
import android.graphics.Canvas;
|
||||
@ -9,223 +23,99 @@ import android.util.AttributeSet;
|
||||
import android.view.Choreographer;
|
||||
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 {
|
||||
private long lastTime = 0;
|
||||
private long delta = 0;
|
||||
private Paint textPaint;
|
||||
int instances = 100;
|
||||
Vector2[] coords = new Vector2[instances];
|
||||
AndroidTextureAtlas atlas;
|
||||
SkeletonData data;
|
||||
Array<Skeleton> skeletons = new Array<>();
|
||||
private long lastTime = 0;
|
||||
private float delta = 0;
|
||||
private Paint textPaint;
|
||||
int instances = 1;
|
||||
Vector2[] coords = new Vector2[instances];
|
||||
AndroidTextureAtlas atlas;
|
||||
SkeletonData data;
|
||||
Array<Skeleton> skeletons = new Array<>();
|
||||
Array<AnimationState> states = new Array<>();
|
||||
SkeletonRenderer renderer = new SkeletonRenderer();
|
||||
|
||||
Array<AnimationState> states = new Array<>();
|
||||
public SpineView (Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpineView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
public SpineView (Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpineView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
public SpineView (Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpineView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
private void loadSkeleton () {
|
||||
String skel = "spineboy-pro.skel";
|
||||
String atlasFile = "spineboy.atlas";
|
||||
|
||||
private void loadSkeleton() {
|
||||
AssetManager assetManager = this.getContext().getAssets();
|
||||
atlas = AndroidTextureAtlas.loadFromAssets("spineboy.atlas", assetManager);
|
||||
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
|
||||
SkeletonBinary binary = new SkeletonBinary(attachmentLoader);
|
||||
try (InputStream in = new BufferedInputStream(assetManager.open("spineboy-pro.skel"))) {
|
||||
data = binary.readSkeletonData(in);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
AssetManager assetManager = this.getContext().getAssets();
|
||||
atlas = AndroidTextureAtlas.loadFromAssets(atlasFile, assetManager);
|
||||
AndroidAtlasAttachmentLoader attachmentLoader = new AndroidAtlasAttachmentLoader(atlas);
|
||||
SkeletonBinary binary = new SkeletonBinary(attachmentLoader);
|
||||
try (InputStream in = new BufferedInputStream(assetManager.open(skel))) {
|
||||
data = binary.readSkeletonData(in);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
|
||||
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();
|
||||
private void init () {
|
||||
textPaint = new Paint();
|
||||
textPaint.setColor(Color.WHITE); // Set the color of the paint
|
||||
textPaint.setTextSize(48);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
|
||||
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();
|
||||
loadSkeleton();
|
||||
|
||||
} 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();
|
||||
for (int i = 0; i < instances; i++) {
|
||||
Skeleton skeleton = new Skeleton(data);
|
||||
skeleton.setScaleY(-1);
|
||||
skeleton.setToSetupPose();
|
||||
skeletons.add(skeleton);
|
||||
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
ClippingAttachment clip = (ClippingAttachment)attachment;
|
||||
clipper.clipStart(slot, clip);
|
||||
continue;
|
||||
AnimationStateData stateData = new AnimationStateData(data);
|
||||
stateData.setDefaultMix(0.2f);
|
||||
AnimationState state = new AnimationState(stateData);
|
||||
state.setAnimation(0, "hoverboard", true);
|
||||
states.add(state);
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (i == 0) {
|
||||
coords[i] = new Vector2(500, 1000);
|
||||
} else {
|
||||
coords[i] = new Vector2(MathUtils.random(1000), MathUtils.random(3000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (texture != null) {
|
||||
com.badlogic.gdx.graphics.Color slotColor = slot.getColor();
|
||||
float alpha = a * slotColor.a * color.a * 255;
|
||||
float multiplier = 255;
|
||||
@Override
|
||||
public void onDraw (Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
BlendMode slotBlendMode = slot.getData().getBlendMode();
|
||||
if (slotBlendMode != blendMode) {
|
||||
if (slotBlendMode == BlendMode.additive) {
|
||||
slotBlendMode = BlendMode.normal;
|
||||
alpha = 0;
|
||||
}
|
||||
blendMode = slotBlendMode;
|
||||
// FIXME
|
||||
// blendMode.apply(batch, pmaBlendModes);
|
||||
}
|
||||
for (int i = 0; i < instances; i++) {
|
||||
AnimationState state = states.get(i);
|
||||
Skeleton skeleton = skeletons.get(i);
|
||||
state.update(delta);
|
||||
state.apply(skeleton);
|
||||
skeleton.update(delta);
|
||||
skeleton.updateWorldTransform(Skeleton.Physics.update);
|
||||
renderer.render(canvas, skeleton, coords[i].x, coords[i].y);
|
||||
}
|
||||
|
||||
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);
|
||||
canvas.drawText(delta * 1000 + " ms", 100, 100, textPaint);
|
||||
canvas.drawText(instances + " instances", 100, 150, textPaint);
|
||||
}
|
||||
|
||||
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.setColor(Color.WHITE); // Set the color of the paint
|
||||
textPaint.setTextSize(48);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
|
||||
loadSkeleton();
|
||||
|
||||
for (int i = 0; i < instances; i++) {
|
||||
Skeleton skeleton = new Skeleton(data);
|
||||
skeleton.setToSetupPose();
|
||||
skeletons.add(skeleton);
|
||||
|
||||
AnimationStateData stateData = new AnimationStateData(data);
|
||||
stateData.setDefaultMix(0.2f);
|
||||
AnimationState state = new AnimationState(stateData);
|
||||
state.setAnimation(0, "walk", true);
|
||||
states.add(state);
|
||||
|
||||
coords[i] = new Vector2(MathUtils.random(1000), MathUtils.random(2000));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
float deltaF = delta / 1e9f;
|
||||
|
||||
for (int i = 0; i < instances; i++) {
|
||||
AnimationState state = states.get(i);
|
||||
Skeleton skeleton = skeletons.get(i);
|
||||
state.update(deltaF);
|
||||
state.apply(skeleton);
|
||||
skeleton.update(deltaF);
|
||||
skeleton.updateWorldTransform(Skeleton.Physics.update);
|
||||
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(instances + " instances", 100, 150, textPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (lastTime != 0) delta = frameTimeNanos - lastTime;
|
||||
lastTime = frameTimeNanos;
|
||||
|
||||
// Invalidate this view, causing onDraw to be called at the next animation frame
|
||||
invalidate();
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
@Override
|
||||
public void doFrame (long frameTimeNanos) {
|
||||
if (lastTime != 0) delta = (frameTimeNanos - lastTime) / 1e9f;
|
||||
lastTime = frameTimeNanos;
|
||||
invalidate();
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user