Cleanup SkeletonRenderer.cs

Cleaned up the SkeletonRenderer code. Clearly outlines mesh generation steps. Abstracted double-buffering and paired meshes with the information used to generate them (for easy checking) See "SmartMesh" and "Instruction" classes. Fixed some long-standing bugs and removed unnecessary normals, tangents and flips updates. Renamed old variables to reflect new logic.

Also marked spots where triangle winding was considered (frontFacing and flip). This is a candidate for removal.
This commit is contained in:
John 2016-03-08 07:13:21 +08:00
parent 4f6fd8aa48
commit 5538c0adde

View File

@ -28,7 +28,6 @@
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@ -63,41 +62,41 @@ public class SkeletonRenderer : MonoBehaviour {
[System.NonSerialized] public bool valid; [System.NonSerialized] public bool valid;
[System.NonSerialized] public Skeleton skeleton; [System.NonSerialized] public Skeleton skeleton;
private MeshRenderer meshRenderer; MeshRenderer meshRenderer;
private MeshFilter meshFilter; MeshFilter meshFilter;
private Mesh mesh1, mesh2; DoubleBufferedSmartMesh doubleBufferedMesh;
private bool useMesh1; readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction();
readonly ExposedList<SubmeshTriangleBuffer> submeshes = new ExposedList<SubmeshTriangleBuffer>();
private float[] tempVertices = new float[8]; float[] tempVertices = new float[8];
private Vector3[] vertices; Vector3[] vertices;
private Color32[] colors; Color32[] colors;
private Vector2[] uvs; Vector2[] uvs;
private Material[] sharedMaterials = new Material[0];
readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>();
Material[] sharedMaterials = new Material[0];
private MeshState meshState = new MeshState(); Vector3[] normals;
private readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>(); Vector4[] tangents;
private readonly ExposedList<Submesh> submeshes = new ExposedList<Submesh>();
private SkeletonUtilitySubmeshRenderer[] submeshRenderers; SkeletonUtilitySubmeshRenderer[] submeshRenderers;
#region Runtime Instantiation #region Runtime Instantiation
public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset);
}
/// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary> /// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
/// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam> /// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer { public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
var c = gameObject.AddComponent<T>(); var c = gameObject.AddComponent<T>();
if (skeletonDataAsset != null) { if (skeletonDataAsset != null) {
c.skeletonDataAsset = skeletonDataAsset; c.skeletonDataAsset = skeletonDataAsset;
c.Initialize(false); c.Initialize(false);
} }
return c; return c;
} }
public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset);
}
#endregion #endregion
public virtual void Awake () { public virtual void Awake () {
@ -116,9 +115,7 @@ public class SkeletonRenderer : MonoBehaviour {
meshRenderer = GetComponent<MeshRenderer>(); meshRenderer = GetComponent<MeshRenderer>();
if (meshRenderer != null) meshRenderer.sharedMaterial = null; if (meshRenderer != null) meshRenderer.sharedMaterial = null;
meshState = new MeshState(); currentInstructions.Clear();
mesh1 = null;
mesh2 = null;
vertices = null; vertices = null;
colors = null; colors = null;
uvs = null; uvs = null;
@ -143,8 +140,7 @@ public class SkeletonRenderer : MonoBehaviour {
meshFilter = GetComponent<MeshFilter>(); meshFilter = GetComponent<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>(); meshRenderer = GetComponent<MeshRenderer>();
mesh1 = Spine.Unity.SpineMesh.NewMesh(); doubleBufferedMesh = new DoubleBufferedSmartMesh();
mesh2 = Spine.Unity.SpineMesh.NewMesh();
vertices = new Vector3[0]; vertices = new Vector3[0];
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
@ -172,17 +168,13 @@ public class SkeletonRenderer : MonoBehaviour {
if (!valid) if (!valid)
return; return;
// Exit early if there is nothing to render
if (!meshRenderer.enabled && submeshRenderers.Length == 0) if (!meshRenderer.enabled && submeshRenderers.Length == 0)
return; return;
// This method caches several .Items arrays. Whenever it does, there should be no mutations done on the overlying ExposedList object. // Step 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes.
// Count vertices and submesh triangles. // This method caches several .Items arrays.
int vertexCount = 0; // Never mutate their overlying ExposedList objects.
int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0;
Material lastMaterial = null;
ExposedList<Slot> drawOrder = skeleton.drawOrder; ExposedList<Slot> drawOrder = skeleton.drawOrder;
var drawOrderItems = drawOrder.Items; var drawOrderItems = drawOrder.Items;
int drawOrderCount = drawOrder.Count; int drawOrderCount = drawOrder.Count;
@ -190,49 +182,41 @@ public class SkeletonRenderer : MonoBehaviour {
bool renderMeshes = this.renderMeshes; bool renderMeshes = this.renderMeshes;
// Clear last state of attachments and submeshes // Clear last state of attachments and submeshes
MeshState.SingleMeshState workingState = meshState.buffer; var workingInstruction = this.currentInstructions;
var workingAttachments = workingState.attachments; var workingAttachments = workingInstruction.attachments;
workingAttachments.Clear(true); workingAttachments.Clear(false);
workingState.UpdateAttachmentCount(drawOrderCount); workingAttachments.GrowIfNeeded(drawOrderCount);
var workingAttachmentsItems = workingAttachments.Items; workingAttachments.Count = drawOrderCount;
var workingAttachmentsItems = workingInstruction.attachments.Items;
var workingFlips = workingState.attachmentsFlipState; // SPINE_DETECT_FLIPS
var workingFlipsItems = workingState.attachmentsFlipState.Items; var workingFlips = workingInstruction.attachmentFlips;
workingFlips.Clear(false);
workingFlips.GrowIfNeeded(drawOrderCount);
workingFlips.Count = drawOrderCount;
var workingFlipsItems = workingFlips.Items;
var workingSubmeshArguments = workingState.addSubmeshArguments; // Items array should not be cached. There is dynamic writing to this object. var workingSubmeshInstructions = workingInstruction.submeshInstructions; // Items array should not be cached. There is dynamic writing to this list.
workingSubmeshArguments.Clear(false); workingSubmeshInstructions.Clear(false);
MeshState.SingleMeshState storedState = useMesh1 ? meshState.stateMesh1 : meshState.stateMesh2;
var storedAttachments = storedState.attachments;
var storedAttachmentsItems = storedAttachments.Items;
var storedFlips = storedState.attachmentsFlipState;
var storedFlipsItems = storedFlips.Items;
bool mustUpdateMeshStructure = storedState.requiresUpdate || // Force update if the mesh was cleared. (prevents flickering due to incorrect state)
drawOrderCount != storedAttachments.Count || // Number of slots changed (when does this happen?)
immutableTriangles != storedState.immutableTriangles; // Immutable Triangles flag changed.
bool isCustomMaterialsPopulated = customSlotMaterials.Count > 0; bool isCustomMaterialsPopulated = customSlotMaterials.Count > 0;
int vertexCount = 0;
int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0;
Material lastMaterial = null;
for (int i = 0; i < drawOrderCount; i++) { for (int i = 0; i < drawOrderCount; i++) {
Slot slot = drawOrderItems[i]; Slot slot = drawOrderItems[i];
Bone bone = slot.bone;
Attachment attachment = slot.attachment; Attachment attachment = slot.attachment;
workingAttachmentsItems[i] = attachment;
// SPINE_DETECT_FLIPS
bool flip = frontFacing && (slot.bone.WorldSignX != slot.bone.WorldSignY);
workingFlipsItems[i] = flip;
object rendererObject; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object. object rendererObject; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object.
int attachmentVertexCount, attachmentTriangleCount; int attachmentVertexCount, attachmentTriangleCount;
// Handle flipping for triangle winding (for lighting?).
bool flip = frontFacing && (bone.WorldSignX != bone.WorldSignY);
workingFlipsItems[i] = flip;
workingAttachmentsItems[i] = attachment;
mustUpdateMeshStructure = mustUpdateMeshStructure || // Always prefer short circuited or. || and not |=.
(attachment != storedAttachmentsItems[i]) || // Attachment order changed. // This relies on the drawOrder.Count != storedAttachments.Count check above as a bounds check.
(flip != storedFlipsItems[i]); // Flip states changed.
var regionAttachment = attachment as RegionAttachment; var regionAttachment = attachment as RegionAttachment;
if (regionAttachment != null) { if (regionAttachment != null) {
rendererObject = regionAttachment.RendererObject; rendererObject = regionAttachment.RendererObject;
@ -272,18 +256,17 @@ public class SkeletonRenderer : MonoBehaviour {
Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject; Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
#endif #endif
// Populate submesh when material changes. (or when forced to separate by a submeshSeparator) // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator)
if ((vertexCount > 0 && lastMaterial.GetInstanceID() != material.GetInstanceID()) || if ((vertexCount > 0 && lastMaterial.GetInstanceID() != material.GetInstanceID()) ||
(submeshSeparatorSlotsCount > 0 && submeshSeparatorSlots.Contains(slot))) { (submeshSeparatorSlotsCount > 0 && submeshSeparatorSlots.Contains(slot))) {
workingSubmeshArguments.Add( workingSubmeshInstructions.Add(
new MeshState.AddSubmeshArguments { new SmartMesh.Instruction.SubmeshInstruction {
material = lastMaterial, material = lastMaterial,
startSlot = submeshStartSlotIndex, startSlot = submeshStartSlotIndex,
endSlot = i, endSlot = i,
triangleCount = submeshTriangleCount, triangleCount = submeshTriangleCount,
firstVertex = submeshFirstVertex, firstVertex = submeshFirstVertex,
isLastSubmesh = false
} }
); );
@ -297,77 +280,50 @@ public class SkeletonRenderer : MonoBehaviour {
vertexCount += attachmentVertexCount; vertexCount += attachmentVertexCount;
} }
workingSubmeshInstructions.Add(
workingSubmeshArguments.Add( new SmartMesh.Instruction.SubmeshInstruction {
new MeshState.AddSubmeshArguments {
material = lastMaterial, material = lastMaterial,
startSlot = submeshStartSlotIndex, startSlot = submeshStartSlotIndex,
endSlot = drawOrderCount, endSlot = drawOrderCount,
triangleCount = submeshTriangleCount, triangleCount = submeshTriangleCount,
firstVertex = submeshFirstVertex, firstVertex = submeshFirstVertex,
isLastSubmesh = true
} }
); );
mustUpdateMeshStructure = mustUpdateMeshStructure || workingInstruction.vertexCount = vertexCount;
this.sharedMaterials.Length != workingSubmeshArguments.Count || // Material array changed in size workingInstruction.immutableTriangles = this.immutableTriangles;
CheckIfMustUpdateMeshStructure(workingSubmeshArguments); // Submesh Argument Array changed. workingInstruction.frontFacing = this.frontFacing;
// CheckIfMustUpdateMaterialArray (workingMaterials, sharedMaterials)
if (!mustUpdateMeshStructure) {
// Narrow phase material array check.
var workingMaterials = workingSubmeshArguments.Items;
for (int i = 0, n = sharedMaterials.Length; i < n; i++) {
if (this.sharedMaterials[i] != workingMaterials[i].material) { // Bounds check is implied above.
mustUpdateMeshStructure = true;
break;
}
}
}
// NOT ELSE
if (mustUpdateMeshStructure) {
this.submeshMaterials.Clear();
var workingSubmeshArgumentsItems = workingSubmeshArguments.Items;
for (int i = 0, n = workingSubmeshArguments.Count; i < n; i++) {
AddSubmesh(workingSubmeshArgumentsItems[i], workingFlips);
}
// Set materials.
if (submeshMaterials.Count == sharedMaterials.Length)
submeshMaterials.CopyTo(sharedMaterials);
else
sharedMaterials = submeshMaterials.ToArray();
meshRenderer.sharedMaterials = sharedMaterials;
}
// Ensure mesh data is the right size. // Step 2. Update vertex buffer based on verts from the attachments.
// Uses values that were also stored in workingInstruction.
Vector3[] vertices = this.vertices; Vector3[] vertices = this.vertices;
bool newTriangles = vertexCount > vertices.Length; bool vertexCountIncreased = vertexCount > vertices.Length;
if (newTriangles) {
// Not enough vertices, increase size. if (vertexCountIncreased) {
this.vertices = vertices = new Vector3[vertexCount]; this.vertices = vertices = new Vector3[vertexCount];
this.colors = new Color32[vertexCount]; this.colors = new Color32[vertexCount];
this.uvs = new Vector2[vertexCount]; this.uvs = new Vector2[vertexCount];
mesh1.Clear(); if (calculateNormals) {
mesh2.Clear(); Vector3[] localNormals = this.normals = new Vector3[vertexCount];
meshState.stateMesh1.requiresUpdate = true; Vector3 normal = new Vector3(0, 0, -1);
meshState.stateMesh2.requiresUpdate = true; for (int i = 0; i < vertexCount; i++)
localNormals[i] = normal;
if (calculateTangents) {
Vector4[] localTangents = this.tangents = new Vector4[vertexCount];
Vector4 tangent = new Vector4(1, 0, 0, -1);
for (int i = 0; i < vertexCount; i++)
localTangents[i] = tangent;
}
}
} else { } else {
// Too many vertices, zero the extra.
Vector3 zero = Vector3.zero; Vector3 zero = Vector3.zero;
for (int i = vertexCount, n = meshState.vertexCount; i < n; i++) for (int i = vertexCount, n = vertices.Length; i < n; i++)
vertices[i] = zero; vertices[i] = zero;
} }
meshState.vertexCount = vertexCount;
// Setup mesh.
float zSpacing = this.zSpacing; float zSpacing = this.zSpacing;
float[] tempVertices = this.tempVertices; float[] tempVertices = this.tempVertices;
Vector2[] uvs = this.uvs; Vector2[] uvs = this.uvs;
@ -552,121 +508,173 @@ public class SkeletonRenderer : MonoBehaviour {
} while (++i < drawOrderCount); } while (++i < drawOrderCount);
} }
// Double buffer mesh. // Step 3. Move the mesh data into a UnityEngine.Mesh
Mesh mesh = useMesh1 ? mesh1 : mesh2; var currentSmartMesh = doubleBufferedMesh.GetNextMesh(); // Double-buffer for performance.
meshFilter.sharedMesh = mesh; var currentMesh = currentSmartMesh.mesh;
mesh.vertices = vertices; currentMesh.vertices = vertices;
mesh.colors32 = colors; currentMesh.colors32 = colors;
mesh.uv = uvs; currentMesh.uv = uvs;
var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed;
if (currentSmartMeshInstructionUsed.vertexCount < vertexCount) {
if (calculateNormals) {
currentMesh.normals = normals;
if (calculateTangents) {
currentMesh.tangents = tangents;
}
}
}
// Check if the triangles should also be updated.
// This thorough structure check is cheaper than updating triangles every frame.
bool mustUpdateMeshStructure = CheckIfMustUpdateMeshStructure(workingInstruction, currentSmartMeshInstructionUsed);
if (mustUpdateMeshStructure) { if (mustUpdateMeshStructure) {
int submeshCount = submeshMaterials.Count; var thisSubmeshMaterials = this.submeshMaterials;
mesh.subMeshCount = submeshCount; thisSubmeshMaterials.Clear(false);
for (int i = 0; i < submeshCount; ++i)
mesh.SetTriangles(submeshes.Items[i].triangles, i);
// Done updating mesh. int submeshCount = workingSubmeshInstructions.Count;
storedState.requiresUpdate = false; int oldSubmeshCount = submeshes.Count;
submeshes.Capacity = submeshCount;
for (int i = oldSubmeshCount; i < submeshCount; i++)
submeshes.Items[i] = new SubmeshTriangleBuffer();
var mutableTriangles = !workingInstruction.immutableTriangles;
for (int i = 0, last = submeshCount - 1; i < submeshCount; i++) {
var submeshInstruction = workingSubmeshInstructions.Items[i];
if (mutableTriangles || i >= oldSubmeshCount)
SetSubmesh(i, submeshInstruction, currentInstructions.attachmentFlips, i == last);
thisSubmeshMaterials.Add(submeshInstruction.material);
}
currentMesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; ++i)
currentMesh.SetTriangles(submeshes.Items[i].triangles, i);
} }
Vector3 meshBoundsExtents = meshBoundsMax - meshBoundsMin; Vector3 meshBoundsExtents = meshBoundsMax - meshBoundsMin;
Vector3 meshBoundsCenter = meshBoundsMin + meshBoundsExtents * 0.5f; Vector3 meshBoundsCenter = meshBoundsMin + meshBoundsExtents * 0.5f;
mesh.bounds = new Bounds(meshBoundsCenter, meshBoundsExtents); currentMesh.bounds = new Bounds(meshBoundsCenter, meshBoundsExtents);
if (newTriangles && calculateNormals) { // CheckIfMustUpdateMaterialArray (last pushed materials vs currently parsed materials)
Vector3[] normals = new Vector3[vertexCount]; // Needs to check against the Working Submesh Instructions Materials instead of the cached submeshMaterials.
Vector3 normal = new Vector3(0, 0, -1); {
for (int i = 0; i < vertexCount; i++) var lastPushedMaterials = this.sharedMaterials;
normals[i] = normal; bool mustUpdateRendererMaterials = mustUpdateMeshStructure ||
(useMesh1 ? mesh2 : mesh1).vertices = vertices; // Set other mesh vertices. (lastPushedMaterials.Length != workingSubmeshInstructions.Count);
mesh1.normals = normals;
mesh2.normals = normals;
if (calculateTangents) { if (!mustUpdateRendererMaterials) {
Vector4[] tangents = new Vector4[vertexCount]; var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
Vector4 tangent = new Vector4(1, 0, 0, -1); for (int i = 0, n = lastPushedMaterials.Length; i < n; i++) {
for (int i = 0; i < vertexCount; i++) if (lastPushedMaterials[i].GetInstanceID() != workingSubmeshInstructionsItems[i].material.GetInstanceID()) { // Bounds check is implied above.
tangents[i] = tangent; mustUpdateRendererMaterials = true;
mesh1.tangents = tangents; break;
mesh2.tangents = tangents; }
}
}
if (mustUpdateRendererMaterials) {
if (submeshMaterials.Count == sharedMaterials.Length)
submeshMaterials.CopyTo(sharedMaterials);
else
sharedMaterials = submeshMaterials.ToArray();
meshRenderer.sharedMaterials = sharedMaterials;
} }
} }
// Update previous state
storedState.immutableTriangles = immutableTriangles;
storedAttachments.Clear(true); // Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh.
storedAttachments.GrowIfNeeded(workingAttachments.Capacity); meshFilter.sharedMesh = currentMesh;
storedAttachments.Count = workingAttachments.Count; currentSmartMesh.instructionUsed.Set(workingInstruction);
workingAttachments.CopyTo(storedAttachments.Items);
storedFlips.GrowIfNeeded(workingFlips.Capacity);
storedFlips.Count = workingFlips.Count;
workingFlips.CopyTo(storedFlips.Items);
storedState.addSubmeshArguments.GrowIfNeeded(workingSubmeshArguments.Capacity);
storedState.addSubmeshArguments.Count = workingSubmeshArguments.Count;
workingSubmeshArguments.CopyTo(storedState.addSubmeshArguments.Items);
// Step 5. Miscellaneous
// Submesh Renderers // Submesh Renderers
if (submeshRenderers.Length > 0) { if (submeshRenderers.Length > 0) {
var thisSharedMaterials = this.sharedMaterials;
for (int i = 0; i < submeshRenderers.Length; i++) { for (int i = 0; i < submeshRenderers.Length; i++) {
SkeletonUtilitySubmeshRenderer submeshRenderer = submeshRenderers[i]; SkeletonUtilitySubmeshRenderer submeshRenderer = submeshRenderers[i];
if (submeshRenderer.submeshIndex < sharedMaterials.Length) { if (submeshRenderer.submeshIndex < thisSharedMaterials.Length) {
submeshRenderer.SetMesh(meshRenderer, useMesh1 ? mesh1 : mesh2, sharedMaterials[submeshRenderer.submeshIndex]); submeshRenderer.SetMesh(meshRenderer, currentMesh, thisSharedMaterials[submeshRenderer.submeshIndex]);
} else { } else {
submeshRenderer.GetComponent<Renderer>().enabled = false; submeshRenderer.GetComponent<Renderer>().enabled = false;
} }
} }
} }
useMesh1 = !useMesh1;
} }
private bool CheckIfMustUpdateMeshStructure (ExposedList<MeshState.AddSubmeshArguments> workingAddSubmeshArguments) { static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) {
#if UNITY_EDITOR #if UNITY_EDITOR
if (!Application.isPlaying) if (!Application.isPlaying)
return true; return true;
#endif #endif
// Check if any mesh settings were changed if (a.immutableTriangles != b.immutableTriangles)
MeshState.SingleMeshState currentMeshState = useMesh1 ? meshState.stateMesh1 : meshState.stateMesh2;
ExposedList<MeshState.AddSubmeshArguments> addSubmeshArgumentsCurrentMesh = currentMeshState.addSubmeshArguments;
int submeshCount = workingAddSubmeshArguments.Count;
if (addSubmeshArgumentsCurrentMesh.Count != submeshCount)
return true; return true;
for (int i = 0; i < submeshCount; i++) { int attachmentCountB = b.attachments.Count;
if (!addSubmeshArgumentsCurrentMesh.Items[i].Equals(ref workingAddSubmeshArguments.Items[i])) if (a.attachments.Count != attachmentCountB) // Bounds check for the looped storedAttachments count below.
return true;
var attachmentsA = a.attachments.Items;
var attachmentsB = b.attachments.Items;
for (int i = 0; i < attachmentCountB; i++) {
if (attachmentsA[i] != attachmentsB[i])
return true; return true;
} }
// SPINE_DETECT_FLIPS
if (a.frontFacing != b.frontFacing) { // if settings changed
return true;
} else if (a.frontFacing) { // if settings matched, only need to check one.
var flipsA = a.attachmentFlips.Items;
var flipsB = b.attachmentFlips.Items;
for (int i = 0; i < attachmentCountB; i++) {
if (flipsA[i] != flipsB[i])
return true;
}
}
// Submesh count changed
int submeshCountA = a.submeshInstructions.Count;
int submeshCountB = b.submeshInstructions.Count;
if (submeshCountA != submeshCountB)
return true;
// Submesh Instruction mismatch
var submeshInstructionsItemsA = a.submeshInstructions.Items;
var submeshInstructionsItemsB = b.submeshInstructions.Items;
for (int i = 0; i < submeshCountB; i++) {
var submeshA = submeshInstructionsItemsA[i];
var submeshB = submeshInstructionsItemsB[i];
if (!(submeshA.startSlot == submeshB.startSlot &&
submeshA.endSlot == submeshB.endSlot &&
submeshA.triangleCount == submeshB.triangleCount &&
submeshA.firstVertex == submeshB.firstVertex))
return true;
}
return false; return false;
} }
private void AddSubmesh (MeshState.AddSubmeshArguments submeshArguments, ExposedList<bool> flipStates) { void SetSubmesh (int submeshIndex, SmartMesh.Instruction.SubmeshInstruction submeshInstructions, ExposedList<bool> flipStates, bool isLastSubmesh) {
int submeshIndex = submeshMaterials.Count; SubmeshTriangleBuffer currentSubmesh = submeshes.Items[submeshIndex];
submeshMaterials.Add(submeshArguments.material);
if (submeshes.Count <= submeshIndex)
submeshes.Add(new Submesh());
else if (immutableTriangles)
return;
Submesh currentSubmesh = submeshes.Items[submeshIndex];
int[] triangles = currentSubmesh.triangles; int[] triangles = currentSubmesh.triangles;
int triangleCount = submeshArguments.triangleCount; int triangleCount = submeshInstructions.triangleCount;
int firstVertex = submeshArguments.firstVertex; int firstVertex = submeshInstructions.firstVertex;
int trianglesCapacity = triangles.Length; int trianglesCapacity = triangles.Length;
if (submeshArguments.isLastSubmesh && trianglesCapacity > triangleCount) { if (isLastSubmesh && trianglesCapacity > triangleCount) {
// Last submesh may have more triangles than required, so zero triangles to the end. // Last submesh may have more triangles than required, so zero triangles to the end.
for (int i = triangleCount; i < trianglesCapacity; i++) { for (int i = triangleCount; i < trianglesCapacity; i++)
triangles[i] = 0; triangles[i] = 0;
}
currentSubmesh.triangleCount = triangleCount; currentSubmesh.triangleCount = triangleCount;
} else if (trianglesCapacity != triangleCount) { } else if (trianglesCapacity != triangleCount) {
@ -689,24 +697,27 @@ public class SkeletonRenderer : MonoBehaviour {
triangles[i + 4] = firstVertex + 3; triangles[i + 4] = firstVertex + 3;
triangles[i + 5] = firstVertex + 1; triangles[i + 5] = firstVertex + 1;
} }
} }
return; return;
} }
// This method caches several .Items arrays. Whenever it does, there should be no mutations done on the overlying ExposedList object. // This method caches several .Items arrays.
// Never mutate their overlying ExposedList objects.
// Iterate through all slots and store their triangles. // Iterate through all slots and store their triangles.
var drawOrderItems = skeleton.DrawOrder.Items; var drawOrderItems = skeleton.DrawOrder.Items;
// SPINE_DETECT_FLIPS
var flipStatesItems = flipStates.Items; var flipStatesItems = flipStates.Items;
int triangleIndex = 0; // Modified by loop int triangleIndex = 0; // Modified by loop
for (int i = submeshArguments.startSlot, n = submeshArguments.endSlot; i < n; i++) { for (int i = submeshInstructions.startSlot, n = submeshInstructions.endSlot; i < n; i++) {
Attachment attachment = drawOrderItems[i].attachment; Attachment attachment = drawOrderItems[i].attachment;
// SPINE_DETECT_FLIPS
bool flip = flipStatesItems[i]; bool flip = frontFacing && flipStatesItems[i];
// Add RegionAttachment triangles // Add RegionAttachment triangles
if (attachment is RegionAttachment) { if (attachment is RegionAttachment) {
// SPINE_DETECT_FLIPS
if (!flip) { if (!flip) {
triangles[triangleIndex] = firstVertex; triangles[triangleIndex] = firstVertex;
triangles[triangleIndex + 1] = firstVertex + 2; triangles[triangleIndex + 1] = firstVertex + 2;
@ -744,6 +755,7 @@ public class SkeletonRenderer : MonoBehaviour {
continue; continue;
} }
// SPINE_DETECT_FLIPS
if (flip) { if (flip) {
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii += 3, triangleIndex += 3) { for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii += 3, triangleIndex += 3) {
triangles[triangleIndex + 2] = firstVertex + attachmentTriangles[ii]; triangles[triangleIndex + 2] = firstVertex + attachmentTriangles[ii];
@ -754,12 +766,11 @@ public class SkeletonRenderer : MonoBehaviour {
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) { for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) {
triangles[triangleIndex] = firstVertex + attachmentTriangles[ii]; triangles[triangleIndex] = firstVertex + attachmentTriangles[ii];
} }
} }
firstVertex += attachmentVertexCount; firstVertex += attachmentVertexCount;
} }
} }
#if UNITY_EDITOR #if UNITY_EDITOR
void OnDrawGizmos () { void OnDrawGizmos () {
// Make scene view selection easier by drawing a clear gizmo over the skeleton. // Make scene view selection easier by drawing a clear gizmo over the skeleton.
@ -776,52 +787,72 @@ public class SkeletonRenderer : MonoBehaviour {
} }
#endif #endif
private class MeshState { class DoubleBufferedSmartMesh {
public int vertexCount; readonly SmartMesh mesh1 = new SmartMesh();
public readonly SingleMeshState buffer = new SingleMeshState(); readonly SmartMesh mesh2 = new SmartMesh();
public readonly SingleMeshState stateMesh1 = new SingleMeshState(); bool usingMesh1;
public readonly SingleMeshState stateMesh2 = new SingleMeshState();
public class SingleMeshState { public SmartMesh GetNextMesh () {
public bool immutableTriangles; usingMesh1 = !usingMesh1;
public bool requiresUpdate; return usingMesh1 ? mesh1 : mesh2;
public readonly ExposedList<Attachment> attachments = new ExposedList<Attachment>();
public readonly ExposedList<bool> attachmentsFlipState = new ExposedList<bool>();
public readonly ExposedList<AddSubmeshArguments> addSubmeshArguments = new ExposedList<AddSubmeshArguments>();
public void UpdateAttachmentCount (int attachmentCount) {
attachmentsFlipState.GrowIfNeeded(attachmentCount);
attachmentsFlipState.Count = attachmentCount;
attachments.GrowIfNeeded(attachmentCount);
attachments.Count = attachmentCount;
}
} }
}
public struct AddSubmeshArguments { ///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
public Material material; class SmartMesh {
public int startSlot; public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
public int endSlot; public SmartMesh.Instruction instructionUsed = new SmartMesh.Instruction();
public int triangleCount;
public int firstVertex;
public bool isLastSubmesh;
public bool Equals (ref AddSubmeshArguments other) { public class Instruction {
return public bool immutableTriangles;
//!ReferenceEquals(material, null) && public bool frontFacing;
//!ReferenceEquals(other.material, null) && public int vertexCount = -1;
//material.GetInstanceID() == other.material.GetInstanceID() && public readonly ExposedList<Attachment> attachments = new ExposedList<Attachment>();
startSlot == other.startSlot && public readonly ExposedList<bool> attachmentFlips = new ExposedList<bool>();
endSlot == other.endSlot && public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
triangleCount == other.triangleCount && public struct SubmeshInstruction {
firstVertex == other.firstVertex; public Material material;
public int firstVertex, startSlot, endSlot, triangleCount;
}
public void Clear () {
this.attachments.Clear(false);
this.attachmentFlips.Clear(false);
this.submeshInstructions.Clear(false);
}
public void Set (Instruction other) {
this.immutableTriangles = other.immutableTriangles;
this.frontFacing = other.frontFacing;
//this.requiresUpdate = other.requiresUpdate;
this.vertexCount = other.vertexCount;
this.attachments.Clear(false);
this.attachments.GrowIfNeeded(other.attachments.Capacity);
this.attachments.Count = other.attachments.Count;
other.attachments.CopyTo(this.attachments.Items);
// SPINE_DETECT_FLIPS
this.attachmentFlips.Clear(false);
this.attachmentFlips.GrowIfNeeded(other.attachmentFlips.Capacity);
this.attachmentFlips.Count = other.attachmentFlips.Count;
if (this.frontFacing)
other.attachmentFlips.CopyTo(this.attachmentFlips.Items);
this.submeshInstructions.Clear(false);
this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
this.submeshInstructions.Count = other.submeshInstructions.Count;
other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
} }
} }
} }
class SubmeshTriangleBuffer {
public int[] triangles = new int[0];
public int triangleCount;
public int firstVertex = -1;
}
} }
class Submesh {
public int[] triangles = new int[0];
public int triangleCount;
public int firstVertex = -1;
}