Refactoring for multi page atlas support.

This commit is contained in:
NathanSweet 2013-04-17 22:09:23 +02:00
parent 2bc3f511a1
commit 143f99a81f
11 changed files with 208 additions and 167 deletions

View File

@ -28,72 +28,63 @@ using System.IO;
namespace Spine {
public class Atlas {
public Format Format;
public TextureFilter MinFilter;
public TextureFilter MagFilter;
public TextureWrap UWrap;
public TextureWrap VWrap;
public int TextureWidth;
public int TextureHeight;
public List<AtlasRegion> Regions;
public Object Texture;
List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
public Atlas (String path, Object texture, int textureWidth, int textureHeight) {
public Atlas (String path, TextureLoader textureLoader) {
using (StreamReader reader = new StreamReader(path)) {
try {
initialize(reader, texture, textureWidth, textureHeight);
Load(reader, Path.GetDirectoryName(path), textureLoader);
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
public Atlas (TextReader reader, Object texture, int textureWidth, int textureHeight) {
initialize(reader, texture, textureWidth, textureHeight);
public Atlas (TextReader reader, String dir, TextureLoader textureLoader) {
Load(reader, dir, textureLoader);
}
private void initialize (TextReader reader, Object texture, int textureWidth, int textureHeight) {
TextureWidth = textureWidth;
TextureHeight = textureHeight;
Texture = texture;
private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) {
if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null.");
Regions = new List<AtlasRegion>();
float invTexWidth = 1f / textureWidth;
float invTexHeight = 1f / textureHeight;
String[] tuple = new String[4];
// Skip past first page name.
AtlasPage page = null;
while (true) {
String line = reader.ReadLine();
if (line.Trim().Length != 0)
break;
}
if (line == null) break;
if (line.Trim().Length == 0)
page = null;
else if (page == null) {
page = new AtlasPage();
page.name = line;
Format = (Format)Enum.Parse(typeof(Format), readValue(reader), false);
page.format = (Format)Enum.Parse(typeof(Format), readValue(reader), false);
readTuple(reader, tuple);
MinFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0]);
MagFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1]);
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0]);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1]);
String direction = readValue(reader);
UWrap = TextureWrap.ClampToEdge;
VWrap = TextureWrap.ClampToEdge;
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
UWrap = TextureWrap.Repeat;
page.uWrap = TextureWrap.Repeat;
else if (direction == "y")
VWrap = TextureWrap.Repeat;
page.vWrap = TextureWrap.Repeat;
else if (direction == "xy")
UWrap = VWrap = TextureWrap.Repeat;
page.uWrap = page.vWrap = TextureWrap.Repeat;
while (true) {
String line = reader.ReadLine();
if (line == null || line.Trim().Length == 0) break;
textureLoader.Load(page, Path.Combine(imagesDir, line));
pages.Add(page);
} else {
AtlasRegion region = new AtlasRegion();
region.Atlas = this;
region.Name = line;
region.name = line;
region.page = page;
region.Rotate = Boolean.Parse(readValue(reader));
region.rotate = Boolean.Parse(readValue(reader));
readTuple(reader, tuple);
int x = int.Parse(tuple[0]);
@ -103,50 +94,45 @@ namespace Spine {
int width = int.Parse(tuple[0]);
int height = int.Parse(tuple[1]);
region.U = x * invTexWidth;
region.V = y * invTexHeight;
region.U2 = (x + width) * invTexWidth;
region.V2 = (y + height) * invTexHeight;
region.Width = Math.Abs(width);
region.Height = Math.Abs(height);
region.u = x / (float)page.width;
region.v = y / (float)page.height;
region.u2 = (x + width) / (float)page.width;
region.v2 = (y + height) / (float)page.height;
region.x = x;
region.y = y;
region.width = Math.Abs(width);
region.height = Math.Abs(height);
if (readTuple(reader, tuple) == 4) { // split is optional
region.Splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
region.Pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
readTuple(reader, tuple);
}
}
region.OriginalWidth = int.Parse(tuple[0]);
region.OriginalHeight = int.Parse(tuple[1]);
region.originalWidth = int.Parse(tuple[0]);
region.originalHeight = int.Parse(tuple[1]);
readTuple(reader, tuple);
region.OffsetX = int.Parse(tuple[0]);
region.OffsetY = int.Parse(tuple[1]);
region.offsetX = int.Parse(tuple[0]);
region.offsetY = int.Parse(tuple[1]);
region.Index = int.Parse(readValue(reader));
region.index = int.Parse(readValue(reader));
Regions.Add(region);
regions.Add(region);
}
while (true) {
String line = reader.ReadLine();
if (line == null)
break;
if (line.Trim().Length != 0) throw new Exception("An atlas with multiple images is not supported.");
}
}
static String readValue (TextReader reader) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1)
throw new Exception("Invalid line: " + line);
if (colon == -1) throw new Exception("Invalid line: " + line);
return line.Substring(colon + 1).Trim();
}
@ -154,14 +140,12 @@ namespace Spine {
static int readTuple (TextReader reader, String[] tuple) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1)
throw new Exception("Invalid line: " + line);
if (colon == -1) throw new Exception("Invalid line: " + line);
int i = 0, lastMatch = colon + 1;
for (i = 0; i < 3; i++) {
for (; i < 3; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) {
if (i == 0)
throw new Exception("Invalid line: " + line);
if (i == 0) throw new Exception("Invalid line: " + line);
break;
}
tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
@ -175,9 +159,8 @@ namespace Spine {
* should be cached rather than calling this method multiple times.
* @return The region, or null. */
public AtlasRegion FindRegion (String name) {
for (int i = 0, n = Regions.Count; i < n; i++)
if (Regions[i].Name == name)
return Regions[i];
for (int i = 0, n = regions.Count; i < n; i++)
if (regions[i].name == name) return regions[i];
return null;
}
}
@ -208,17 +191,31 @@ namespace Spine {
Repeat
}
public class AtlasPage {
public String name;
public Format format;
public TextureFilter minFilter;
public TextureFilter magFilter;
public TextureWrap uWrap;
public TextureWrap vWrap;
public Object texture;
public int width, height;
}
public class AtlasRegion {
public Atlas Atlas;
public float U, V;
public float U2, V2;
public int Width, Height;
public int Index;
public String Name;
public float OffsetX, OffsetY;
public int OriginalWidth, OriginalHeight;
public bool Rotate;
public int[] Splits;
public int[] Pads;
public AtlasPage page;
public String name;
public int x, y, width, height;
public float u, v, u2, v2;
public float offsetX, offsetY;
public int originalWidth, originalHeight;
public int index;
public bool rotate;
public int[] splits;
public int[] pads;
}
public interface TextureLoader {
void Load (AtlasPage page, String path);
}
}

View File

@ -47,43 +47,12 @@ namespace Spine {
public float[] Offset { get; private set; }
public float[] Vertices { get; private set; }
public float[] UVs { get; private set; }
private AtlasRegion region;
public AtlasRegion Region {
get {
return region;
}
set {
region = value;
float[] uvs = UVs;
if (value.Rotate) {
uvs[X2] = value.U;
uvs[Y2] = value.V2;
uvs[X3] = value.U;
uvs[Y3] = value.V;
uvs[X4] = value.U2;
uvs[Y4] = value.V;
uvs[X1] = value.U2;
uvs[Y1] = value.V2;
} else {
uvs[X1] = value.U;
uvs[Y1] = value.V2;
uvs[X2] = value.U;
uvs[Y2] = value.V;
uvs[X3] = value.U2;
uvs[Y3] = value.V;
uvs[X4] = value.U2;
uvs[Y4] = value.V2;
}
}
}
public AtlasRegion Region { get; set; }
public RegionAttachment (string name)
: base(name) {
Offset = new float[8];
Vertices = new float[8];
UVs = new float[8];
ScaleX = 1;
ScaleY = 1;
}
@ -97,16 +66,16 @@ namespace Spine {
float localY = -localY2;
AtlasRegion region = Region;
if (region != null) {
if (region.Rotate) {
localX += region.OffsetX / region.OriginalWidth * height;
localY += region.OffsetY / region.OriginalHeight * width;
localX2 -= (region.OriginalWidth - region.OffsetX - region.Height) / region.OriginalWidth * width;
localY2 -= (region.OriginalHeight - region.OffsetY - region.Width) / region.OriginalHeight * height;
if (region.rotate) {
localX += region.offsetX / region.originalWidth * height;
localY += region.offsetY / region.originalHeight * width;
localX2 -= (region.originalWidth - region.offsetX - region.height) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.width) / region.originalHeight * height;
} else {
localX += region.OffsetX / region.OriginalWidth * width;
localY += region.OffsetY / region.OriginalHeight * height;
localX2 -= (region.OriginalWidth - region.OffsetX - region.Width) / region.OriginalWidth * width;
localY2 -= (region.OriginalHeight - region.OffsetY - region.Height) / region.OriginalHeight * height;
localX += region.offsetX / region.originalWidth * width;
localY += region.offsetY / region.originalHeight * height;
localX2 -= (region.originalWidth - region.offsetX - region.width) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.height) / region.originalHeight * height;
}
}
float scaleX = ScaleX;

View File

@ -47,6 +47,7 @@ namespace Spine {
}
public SkeletonJson (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}

View File

@ -22,7 +22,6 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.IO;
using UnityEngine;
@ -55,7 +54,7 @@ public class AtlasAsset : ScriptableObject {
return atlas;
try {
atlas = new Atlas(new StringReader(atlasFile.text), material, material.mainTexture.width, material.mainTexture.height);
atlas = new Atlas(new StringReader(atlasFile.text), "", new SingleTextureLoader(material));
return atlas;
} catch (Exception) {
Debug.LogException(new Exception("Error reading atlas file for atlas asset: " + name), this);
@ -63,3 +62,16 @@ public class AtlasAsset : ScriptableObject {
}
}
}
public class SingleTextureLoader : TextureLoader {
Material material;
public SingleTextureLoader (Material material) {
this.material = material;
}
public void Load (AtlasPage page, String path) {
page.width = material.mainTexture.width;
page.height = material.mainTexture.height;
}
}

View File

@ -22,7 +22,6 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
@ -123,11 +122,18 @@ public class SkeletonComponent : MonoBehaviour {
vertices[vertexIndex + 2] = new Vector3(regionVertices[RegionAttachment.X2], regionVertices[RegionAttachment.Y2], 0);
vertices[vertexIndex + 3] = new Vector3(regionVertices[RegionAttachment.X3], regionVertices[RegionAttachment.Y3], 0);
float[] regionUVs = regionAttachment.UVs;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], 1 - regionUVs[RegionAttachment.Y1]);
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], 1 - regionUVs[RegionAttachment.Y4]);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], 1 - regionUVs[RegionAttachment.Y2]);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], 1 - regionUVs[RegionAttachment.Y3]);
AtlasRegion region = regionAttachment.Region;
if (region.rotate) {
uvs[vertexIndex + 1] = new Vector2(region.u, 1 - region.v2);
uvs[vertexIndex + 2] = new Vector2(region.u2, 1 - region.v2);
uvs[vertexIndex + 3] = new Vector2(region.u, 1 - region.v);
uvs[vertexIndex] = new Vector2(region.u2, 1 - region.v);
} else {
uvs[vertexIndex] = new Vector2(region.u, 1 - region.v2);
uvs[vertexIndex + 1] = new Vector2(region.u2, 1 - region.v2);
uvs[vertexIndex + 2] = new Vector2(region.u, 1 - region.v);
uvs[vertexIndex + 3] = new Vector2(region.u2, 1 - region.v);
}
int index = quadIndex * 6;
triangles[index] = vertexIndex;

View File

@ -59,8 +59,7 @@ namespace Spine {
protected override void LoadContent () {
skeletonRenderer = new SkeletonRenderer(GraphicsDevice);
Texture2D texture = Util.LoadTexture(GraphicsDevice, "data/goblins.png");
Atlas atlas = new Atlas("data/goblins.atlas", texture, texture.Width, texture.Height);
Atlas atlas = new Atlas("data/goblins.atlas", new XnaTextureLoader(GraphicsDevice));
SkeletonJson json = new SkeletonJson(atlas);
skeleton = new Skeleton(json.ReadSkeletonData("data/goblins.json"));
skeleton.SetSkin("goblingirl");

View File

@ -109,6 +109,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="src\XnaTextureLoader.cs" />
<Compile Include="src\Util.cs" />
<Compile Include="src\SkeletonRenderer.cs" />
<Compile Include="src\SpriteBatcher.cs" />

View File

@ -78,12 +78,12 @@ namespace Spine {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
SpriteBatchItem item = batcher.CreateBatchItem();
item.Texture = (Texture2D)regionAttachment.Region.Atlas.Texture;
item.Texture = (Texture2D)regionAttachment.Region.page.texture;
byte r = (byte)(slot.R * 255);
byte g = (byte)(slot.G * 255);
byte b = (byte)(slot.B * 255);
byte a = (byte)(slot.A * 255);
byte r = (byte)(skeleton.R * slot.R * 255);
byte g = (byte)(skeleton.G * slot.G * 255);
byte b = (byte)(skeleton.B * slot.B * 255);
byte a = (byte)(skeleton.A * slot.A * 255);
item.vertexTL.Color.R = r;
item.vertexTL.Color.G = g;
item.vertexTL.Color.B = b;
@ -116,15 +116,26 @@ namespace Spine {
item.vertexTR.Position.Y = vertices[RegionAttachment.Y4];
item.vertexTR.Position.Z = 0;
float[] uvs = regionAttachment.UVs;
item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1];
item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1];
item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2];
item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2];
item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3];
item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3];
item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4];
item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4];
AtlasRegion region = regionAttachment.Region;
if (region.rotate) {
item.vertexBL.TextureCoordinate.X = region.u;
item.vertexBL.TextureCoordinate.Y = region.v2;
item.vertexBR.TextureCoordinate.X = region.u;
item.vertexBR.TextureCoordinate.Y = region.v;
item.vertexTR.TextureCoordinate.X = region.u2;
item.vertexTR.TextureCoordinate.Y = region.v;
item.vertexTL.TextureCoordinate.X = region.u2;
item.vertexTL.TextureCoordinate.Y = region.v2;
} else {
item.vertexTL.TextureCoordinate.X = region.u;
item.vertexTL.TextureCoordinate.Y = region.v2;
item.vertexBL.TextureCoordinate.X = region.u;
item.vertexBL.TextureCoordinate.Y = region.v;
item.vertexBR.TextureCoordinate.X = region.u2;
item.vertexBR.TextureCoordinate.Y = region.v;
item.vertexTR.TextureCoordinate.X = region.u2;
item.vertexTR.TextureCoordinate.Y = region.v2;
}
}
}
}

View File

@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Spine {
public class XnaTextureLoader : TextureLoader {
GraphicsDevice device;
public XnaTextureLoader (GraphicsDevice device) {
this.device = device;
}
public void Load (AtlasPage page, String path) {
Texture2D texture = Util.LoadTexture(device, path);
page.texture = texture;
page.width = texture.Width;
page.height = texture.Height;
}
}
}