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

View File

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

View File

@ -22,7 +22,6 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/ ******************************************************************************/
using System; using System;
using System.IO; using System.IO;
using UnityEngine; using UnityEngine;
@ -55,7 +54,7 @@ public class AtlasAsset : ScriptableObject {
return atlas; return atlas;
try { 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; return atlas;
} catch (Exception) { } catch (Exception) {
Debug.LogException(new Exception("Error reading atlas file for atlas asset: " + name), this); 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 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/ ******************************************************************************/
using System; using System;
using System.IO; using System.IO;
using System.Collections.Generic; 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 + 2] = new Vector3(regionVertices[RegionAttachment.X2], regionVertices[RegionAttachment.Y2], 0);
vertices[vertexIndex + 3] = new Vector3(regionVertices[RegionAttachment.X3], regionVertices[RegionAttachment.Y3], 0); vertices[vertexIndex + 3] = new Vector3(regionVertices[RegionAttachment.X3], regionVertices[RegionAttachment.Y3], 0);
float[] regionUVs = regionAttachment.UVs; AtlasRegion region = regionAttachment.Region;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], 1 - regionUVs[RegionAttachment.Y1]); if (region.rotate) {
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], 1 - regionUVs[RegionAttachment.Y4]); uvs[vertexIndex + 1] = new Vector2(region.u, 1 - region.v2);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], 1 - regionUVs[RegionAttachment.Y2]); uvs[vertexIndex + 2] = new Vector2(region.u2, 1 - region.v2);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], 1 - regionUVs[RegionAttachment.Y3]); 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; int index = quadIndex * 6;
triangles[index] = vertexIndex; triangles[index] = vertexIndex;

View File

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

View File

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

View File

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