diff --git a/spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs b/spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs index 93809a90e..77acc3885 100644 --- a/spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs +++ b/spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs @@ -51,6 +51,7 @@ namespace Spine.Unity { SpineEditorUtilities.ConfirmInitialization(); atlasFile = serializedObject.FindProperty("atlasFile"); materials = serializedObject.FindProperty("materials"); + materials.isExpanded = true; atlasAsset = (AtlasAsset)target; UpdateBakedList(); } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs index 7ebbb865c..7fae030e8 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs @@ -39,7 +39,7 @@ namespace Spine.Unity { public class SkeletonRendererInspector : Editor { protected static bool advancedFoldout; - protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, submeshSeparators, front, zSpacing; + protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, front, zSpacing; protected SpineInspectorUtility.SerializedSortingProperties sortingProperties; @@ -51,7 +51,9 @@ namespace Spine.Unity { tangents = serializedObject.FindProperty("calculateTangents"); meshes = serializedObject.FindProperty("renderMeshes"); immutableTriangles = serializedObject.FindProperty("immutableTriangles"); - submeshSeparators = serializedObject.FindProperty("submeshSeparators"); + separatorSlotNames = serializedObject.FindProperty("separatorSlotNames"); + separatorSlotNames.isExpanded = true; + front = serializedObject.FindProperty("frontFacing"); zSpacing = serializedObject.FindProperty("zSpacing"); @@ -108,36 +110,49 @@ namespace Spine.Unity { // More Render Options... { - advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced"); - if(advancedFoldout) { + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(submeshSeparators, true); - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(meshes, - new GUIContent("Render Mesh Attachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments")); - EditorGUILayout.PropertyField(immutableTriangles, - new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility")); - EditorGUILayout.Space(); + advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced"); + if(advancedFoldout) { + EditorGUI.indentLevel++; + SeparatorsField(separatorSlotNames); + EditorGUILayout.PropertyField(meshes, + new GUIContent("Render Mesh Attachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments")); + EditorGUILayout.PropertyField(immutableTriangles, + new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility")); + EditorGUILayout.Space(); - const float MinZSpacing = -0.1f; - const float MaxZSpacing = 0f; - EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing); + const float MinZSpacing = -0.1f; + const float MaxZSpacing = 0f; + EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing); - if (normals != null) { - EditorGUILayout.PropertyField(normals); - EditorGUILayout.PropertyField(tangents); + if (normals != null) { + EditorGUILayout.PropertyField(normals); + EditorGUILayout.PropertyField(tangents); + } + + if (front != null) { + EditorGUILayout.PropertyField(front); + } + EditorGUI.indentLevel--; } - - if (front != null) { - EditorGUILayout.PropertyField(front); - } - - EditorGUILayout.Separator(); EditorGUI.indentLevel--; + } } } + public static void SeparatorsField (SerializedProperty separatorSlotNames) { + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + if (separatorSlotNames.isExpanded) { + EditorGUILayout.PropertyField(separatorSlotNames, includeChildren: true); + } else { + EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format(" [{0}]", separatorSlotNames.arraySize)), includeChildren: true); + } + + } + } + override public void OnInspectorGUI () { serializedObject.Update(); DrawInspectorGUI(); diff --git a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs index ec670431a..4f2cef7d7 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs @@ -33,10 +33,19 @@ using UnityEngine; using System.Collections; using UnityEditor; using System.Reflection; +using System; namespace Spine.Unity { public static class SpineInspectorUtility { + public static string Pluralize (int n, string singular, string plural) { + return n + " " + (n == 1 ? singular : plural); + } + + public static string PluralThenS (int n) { + return n == 1 ? "" : "s"; + } + #region Sorting Layer Field Helpers static readonly GUIContent SortingLayerLabel = new GUIContent("Sorting Layer"); static readonly GUIContent OrderInLayerLabel = new GUIContent("Order in Layer"); @@ -45,7 +54,7 @@ namespace Spine.Unity { static MethodInfo SortingLayerFieldMethod { get { if (m_SortingLayerFieldMethod == null) - m_SortingLayerFieldMethod = typeof(EditorGUILayout).GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic, null, new System.Type[] { typeof(GUIContent), typeof(SerializedProperty), typeof(GUIStyle) }, null); + m_SortingLayerFieldMethod = typeof(EditorGUILayout).GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic, null, new [] { typeof(GUIContent), typeof(SerializedProperty), typeof(GUIStyle) }, null); return m_SortingLayerFieldMethod; } diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs index 56358fcf1..f71feb554 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs @@ -28,6 +28,7 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#define SPINE_OPTIONAL_NORMALS using UnityEngine; namespace Spine.Unity.MeshGeneration { @@ -42,6 +43,33 @@ namespace Spine.Unity.MeshGeneration { protected Color32[] meshColors32; protected Vector2[] meshUVs; + + protected bool generateNormals = false; + public bool GenerateNormals { + get { return generateNormals; } + set { generateNormals = value; } + } + + Vector3[] meshNormals; + + public void TryAddNormalsTo (Mesh mesh, int targetVertexCount) { + #if SPINE_OPTIONAL_NORMALS + if (generateNormals) { + bool verticesWasResized = this.meshNormals == null || targetVertexCount > meshNormals.Length; + if (verticesWasResized) { + this.meshNormals = new Vector3[targetVertexCount]; + Vector3 normal = new Vector3(0, 0, -1); + Vector3[] normals = this.meshNormals; + for (int i = 0; i < targetVertexCount; i++) + normals[i] = normal; + } + + mesh.normals = this.meshNormals; + } + #endif + } + + public static bool EnsureSize (int targetVertexCount, ref Vector3[] vertices, ref Vector2[] uvs, ref Color32[] colors) { Vector3[] verts = vertices; bool verticesWasResized = verts == null || targetVertexCount > verts.Length; @@ -247,23 +275,20 @@ namespace Spine.Unity.MeshGeneration { /// The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit. /// The current triangle count of the submesh buffer. This is not always equal to triangleBuffer.Length because for last submeshes, length may be larger than needed. /// If set to true, the triangle buffer is allowed to be larger than needed. - public static void FillTriangles (Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, ref int[] triangleBuffer, ref int bufferTriangleCount, bool isLastSubmesh) { + public static void FillTriangles (Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, ref int[] triangleBuffer, bool isLastSubmesh) { int trianglesCapacity = triangleBuffer.Length; var tris = triangleBuffer; // Ensure triangleBuffer size. if (isLastSubmesh) { - bufferTriangleCount = triangleCount; if (trianglesCapacity > triangleCount) { for (int i = triangleCount; i < trianglesCapacity; i++) tris[i] = 0; } else if (trianglesCapacity < triangleCount) { triangleBuffer = tris = new int[triangleCount]; - bufferTriangleCount = 0; } } else if (trianglesCapacity != triangleCount) { triangleBuffer = tris = new int[triangleCount]; - bufferTriangleCount = 0; } // Iterate through submesh slots and store the triangles. @@ -313,11 +338,10 @@ namespace Spine.Unity.MeshGeneration { #region SubmeshTriangleBuffer public class SubmeshTriangleBuffer { public int[] triangles; - public int triangleCount; + //public int triangleCount; public SubmeshTriangleBuffer (int triangleCount) { triangles = new int[triangleCount]; - this.triangleCount = triangleCount; } } #endregion diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs index 0cd767dce..ae3c70e58 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs @@ -116,7 +116,7 @@ namespace Spine.Unity.MeshGeneration { } // Step 4 : Update Triangles buffer - ArraysMeshGenerator.FillTriangles(skeleton, totalTriangleCount, 0, 0, drawOrderCount, ref this.triangles, ref this.triangleBufferCount, true); + ArraysMeshGenerator.FillTriangles(skeleton, totalTriangleCount, 0, 0, drawOrderCount, ref this.triangles, true); // Step 5 : Update Mesh with buffers var mesh = doubleBufferedMesh.GetNextMesh(); diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs index 2d7075a0e..1c9145ae6 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs @@ -28,6 +28,7 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#define SPINE_OPTIONAL_NORMALS using UnityEngine; namespace Spine.Unity.MeshGeneration { @@ -107,7 +108,7 @@ namespace Spine.Unity.MeshGeneration { if (structureDoesntMatch) { var currentBuffer = submeshBuffers.Items[submeshIndex]; bool isLastSubmesh = (submeshIndex == submeshCount - 1); - ArraysMeshGenerator.FillTriangles(skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, startSlot, endSlot, ref currentBuffer.triangles, ref currentBuffer.triangleCount, isLastSubmesh); + ArraysMeshGenerator.FillTriangles(skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, startSlot, endSlot, ref currentBuffer.triangles, isLastSubmesh); } } @@ -119,6 +120,9 @@ namespace Spine.Unity.MeshGeneration { // STEP 3: Assign the buffers into the Mesh. smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, currentAttachments, currentInstructions); mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); + #if SPINE_OPTIONAL_NORMALS + this.TryAddNormalsTo(mesh, vertexCount); + #endif if (structureDoesntMatch) { // Push new triangles if doesn't match. diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs index 641fc9cdc..2dd4c3903 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs @@ -202,7 +202,7 @@ namespace Spine.Unity.MeshGeneration { if (structureDoesntMatch) { var currentBuffer = submeshBuffers.Items[submeshIndex]; bool isLastSubmesh = (submeshIndex == submeshCount - 1); - ArraysMeshGenerator.FillTriangles(skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, ref currentBuffer.triangles, ref currentBuffer.triangleCount, isLastSubmesh); + ArraysMeshGenerator.FillTriangles(skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, ref currentBuffer.triangles, isLastSubmesh); } } diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs index 5049da3cc..3662f3aac 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs @@ -22,6 +22,7 @@ namespace Spine.Unity.MeshGeneration { // Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials. public interface ISubmeshSetMeshGenerator { MeshAndMaterials GenerateMesh (ExposedList instructions, int startSubmesh, int endSubmesh); + bool GenerateNormals { get; set; } } /// Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes. diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderPartInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs similarity index 92% rename from spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderPartInspector.cs rename to spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs index c2a192835..fe9afbd72 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderPartInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs @@ -3,7 +3,7 @@ using System.Collections; using UnityEditor; using Spine.Unity; -[CustomEditor(typeof(SkeletonRenderPart))] +[CustomEditor(typeof(SkeletonPartsRenderer))] public class SkeletonRenderPartInspector : Editor { SpineInspectorUtility.SerializedSortingProperties sortingProperties; diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderPartInspector.cs.meta b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs.meta similarity index 100% rename from spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderPartInspector.cs.meta rename to spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs.meta diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs index dfdcfcd86..3ce84e85a 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs @@ -1,57 +1,224 @@ using UnityEngine; -using System.Collections; using UnityEditor; -using Spine.Unity; -[CustomEditor(typeof(SkeletonRenderSeparator))] -public class SkeletonRenderSeparatorInspector : Editor { +namespace Spine.Unity { + + [CustomEditor(typeof(SkeletonRenderSeparator))] + public class SkeletonRenderSeparatorInspector : Editor { + SkeletonRenderSeparator component; - SkeletonRenderSeparator component; + // Properties + SerializedProperty skeletonRenderer_, copyPropertyBlock_, copyMeshRendererFlags_, partsRenderers_; - void OnEnable () { - component = target as SkeletonRenderSeparator; - } + // For separator field. + SerializedObject skeletonRendererSerializedObject; + SerializedProperty separatorNamesProp; + bool separatorExpanded = true; + System.Func Plural = SpineInspectorUtility.Pluralize; - public override void OnInspectorGUI () { - base.OnInspectorGUI(); + void OnEnable () { + if (component == null) + component = target as SkeletonRenderSeparator; - if (GUILayout.Button("Add Renderer")) { - const int SortingOrderIncrement = 5; - int index = component.renderers.Count; - var smr = SkeletonRenderPart.NewSubmeshRendererGameObject(component.transform, index.ToString()); - component.renderers.Add(smr); + skeletonRenderer_ = serializedObject.FindProperty("skeletonRenderer"); + copyPropertyBlock_ = serializedObject.FindProperty("copyPropertyBlock"); + copyMeshRendererFlags_ = serializedObject.FindProperty("copyMeshRendererFlags"); + partsRenderers_ = serializedObject.FindProperty("partsRenderers"); + partsRenderers_.isExpanded = true; + } - // increment renderer sorting order. - if (index != 0) { - var prev = component.renderers[index - 1]; - if (prev != null) { - var prevMeshRenderer = prev.GetComponent(); - var currentMeshRenderer = smr.GetComponent(); - if (prevMeshRenderer != null && currentMeshRenderer != null) { - int prevSortingLayer = prevMeshRenderer.sortingLayerID; - int prevSortingOrder = prevMeshRenderer.sortingOrder; + public override void OnInspectorGUI () { + // TODO: Add Undo support + var componentRenderers = component.partsRenderers; + int separatorCount = 0; + int totalParts; - currentMeshRenderer.sortingLayerID = prevSortingLayer; - currentMeshRenderer.sortingOrder = prevSortingOrder + SortingOrderIncrement; + bool componentEnabled = component.enabled; + bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled); + if (checkBox != componentEnabled) { + component.enabled = checkBox; + } + + EditorGUILayout.PropertyField(copyPropertyBlock_); + EditorGUILayout.PropertyField(copyMeshRendererFlags_); + + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + // Fancy SkeletonRenderer foldout reference field + { + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + var foldoutSkeletonRendererRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); + EditorGUI.PropertyField(foldoutSkeletonRendererRect, skeletonRenderer_); + if (EditorGUI.EndChangeCheck()) + serializedObject.ApplyModifiedProperties(); + if (component.SkeletonRenderer != null) { + separatorExpanded = EditorGUI.Foldout(foldoutSkeletonRendererRect, separatorExpanded, ""); + } + EditorGUI.indentLevel--; + } + + EditorGUI.BeginChangeCheck(); + if (component.SkeletonRenderer != null) { + // SubmeshSeparators from SkeletonRenderer + { + bool skeletonRendererMismatch = skeletonRendererSerializedObject != null && skeletonRendererSerializedObject.targetObject != component.SkeletonRenderer; + if (separatorNamesProp == null || skeletonRendererMismatch) { + if (component.SkeletonRenderer != null) { + skeletonRendererSerializedObject = new SerializedObject(component.SkeletonRenderer); + separatorNamesProp = skeletonRendererSerializedObject.FindProperty("separatorSlotNames"); + separatorNamesProp.isExpanded = true; + } + } + + if (separatorNamesProp != null) { + if (separatorExpanded) { + EditorGUI.indentLevel++; + SkeletonRendererInspector.SeparatorsField(separatorNamesProp); + EditorGUI.indentLevel--; + } + + if (Application.isPlaying) + separatorCount = component.SkeletonRenderer.separatorSlots.Count; + else + separatorCount = separatorNamesProp.arraySize; + + } + } + + if (separatorCount == 0) { + EditorGUILayout.HelpBox("Separators are empty. Change the size to 1 and choose a slot if you want the render to be separated.", MessageType.Info); + } + } + if (EditorGUI.EndChangeCheck()) + skeletonRendererSerializedObject.ApplyModifiedProperties(); + + totalParts = separatorCount + 1; + var counterStyle = separatorExpanded ? EditorStyles.label : EditorStyles.miniLabel; + EditorGUILayout.LabelField(string.Format("{0}: separates into {1}.", Plural(separatorCount, "separator", "separators"), Plural(totalParts, "part", "parts") ), counterStyle); + } + + // Parts renderers + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(this.partsRenderers_, true); + EditorGUI.indentLevel--; + + // (Button) Match Separators count + if (separatorNamesProp != null) { + int currentRenderers = 0; + foreach (var r in componentRenderers) { + if (r != null) + currentRenderers++; + } + + int extraRenderersNeeded = totalParts - currentRenderers; + if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) { + EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning); + string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded)); + //var addMissingContentButtonContent = new GUIContent("Add", GUIUtility.) + if (GUILayout.Button(addMissingLabel, GUILayout.Height(40f))) { + AddPartsRenderer(extraRenderersNeeded); + DetectOrphanedPartsRenderers(component); + } + } + } + + using (new EditorGUILayout.HorizontalScope()) { + // (Button) Destroy Renderers button + if (componentRenderers.Count > 0) { + if (GUILayout.Button("Clear Parts Renderers")) { + // Do you really want to destroy all? + if (EditorUtility.DisplayDialog("Destroy Renderers", "Do you really want to destroy all the Parts Renderer GameObjects in the list? (Undo will not work.)", "Destroy", "Cancel")) { + foreach (var r in componentRenderers) { + if (r != null) + DestroyImmediate(r.gameObject, allowDestroyingAssets: false); + } + componentRenderers.Clear(); + // Do you also want to destroy orphans? (You monster.) + DetectOrphanedPartsRenderers(component); + } + } + } + + // (Button) Add Part Renderer button + if (GUILayout.Button("Add (1) Parts Renderer")) + AddPartsRenderer(1); + } + } + + serializedObject.ApplyModifiedProperties(); + } + + public void AddPartsRenderer (int count) { + var componentRenderers = component.partsRenderers; + bool emptyFound = componentRenderers.Exists(x => x == null); + if (emptyFound) { + bool userClearEntries = EditorUtility.DisplayDialog("Empty entries found", "Null entries found. Do you want to remove null entries before adding the new renderer? ", "Clear Empty Entries", "Don't Clear"); + if (userClearEntries) componentRenderers.RemoveAll(x => x == null); + } + + for (int i = 0; i < count; i++) { + int index = componentRenderers.Count; + var smr = SkeletonPartsRenderer.NewPartsRendererGameObject(component.transform, index.ToString()); + componentRenderers.Add(smr); + EditorGUIUtility.PingObject(smr); + + // increment renderer sorting order. + if (index != 0) { + var prev = componentRenderers[index - 1]; + if (prev != null) { + var prevMeshRenderer = prev.GetComponent(); + var currentMeshRenderer = smr.GetComponent(); + if (prevMeshRenderer != null && currentMeshRenderer != null) { + int prevSortingLayer = prevMeshRenderer.sortingLayerID; + int prevSortingOrder = prevMeshRenderer.sortingOrder; + + currentMeshRenderer.sortingLayerID = prevSortingLayer; + currentMeshRenderer.sortingOrder = prevSortingOrder + SkeletonRenderSeparator.DefaultSortingOrderIncrement; + } + } + } + } + + } + + /// Detects orphaned parts renderers and offers to delete them. + public void DetectOrphanedPartsRenderers (SkeletonRenderSeparator component) { + var children = component.GetComponentsInChildren(); + + var orphans = new System.Collections.Generic.List(); + foreach (var r in children) { + if (!component.partsRenderers.Contains(r)) { + orphans.Add(r); + } + } + + if (orphans.Count > 0) { + if (EditorUtility.DisplayDialog("Destroy Submesh Renderers", "Unassigned renderers were found. Do you want to delete them? (These may belong to another Render Separator in the same hierarchy. If you don't have another Render Separator component in the children of this GameObject, it's likely safe to delete. Warning: This operation cannot be undone.)", "Delete", "Cancel")) { + foreach (var o in orphans) { + DestroyImmediate(o.gameObject, allowDestroyingAssets: false); } } } } - if (GUILayout.Button("Destroy Renderers")) { - if (EditorUtility.DisplayDialog("Destroy Submesh Renderers", "Do you really want to destroy all the SubmeshRenderer GameObjects in the list?", "Destroy", "Cancel")) { - - for (int i = 0; i < component.renderers.Count; i++) { - Debug.LogFormat("Destroying {0}", component.renderers[i].gameObject.name); - DestroyImmediate(component.renderers[i].gameObject); - } - component.renderers.Clear(); - } + #region SkeletonRenderer Context Menu Item + [MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator")] + static void AddRenderSeparatorComponent (MenuCommand cmd) { + var skeletonRenderer = cmd.context as SkeletonRenderer; + skeletonRenderer.gameObject.AddComponent(); } - + + // Validate + [MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator", true)] + static bool ValidateAddRenderSeparatorComponent (MenuCommand cmd) { + var skeletonRenderer = cmd.context as SkeletonRenderer; + var separator = skeletonRenderer.GetComponent(); + bool separatorNotOnObject = separator == null; + return separatorNotOnObject; + } + #endregion + } - - - } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderPart.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs similarity index 77% rename from spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderPart.cs rename to spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs index c7063a872..273121195 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderPart.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs @@ -4,7 +4,9 @@ using Spine.Unity.MeshGeneration; namespace Spine.Unity { [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))] - public class SkeletonRenderPart : MonoBehaviour { + public class SkeletonPartsRenderer : MonoBehaviour { + + #region Properties ISubmeshSetMeshGenerator meshGenerator; public ISubmeshSetMeshGenerator MeshGenerator { get { @@ -28,6 +30,7 @@ namespace Spine.Unity { return meshFilter; } } + #endregion void LazyIntialize () { if (meshGenerator != null) return; @@ -37,10 +40,11 @@ namespace Spine.Unity { } public void ClearMesh () { + LazyIntialize(); meshFilter.sharedMesh = null; } - public void RenderSubmesh (ExposedList instructions, int startSubmesh, int endSubmesh) { + public void RenderParts (ExposedList instructions, int startSubmesh, int endSubmesh) { LazyIntialize(); MeshAndMaterials m = meshGenerator.GenerateMesh(instructions, startSubmesh, endSubmesh); meshFilter.sharedMesh = m.mesh; @@ -52,10 +56,10 @@ namespace Spine.Unity { meshRenderer.SetPropertyBlock(block); } - public static SkeletonRenderPart NewSubmeshRendererGameObject (Transform parent, string name) { + public static SkeletonPartsRenderer NewPartsRendererGameObject (Transform parent, string name) { var go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer)); go.transform.SetParent(parent, false); - var returnComponent = go.AddComponent(); + var returnComponent = go.AddComponent(); return returnComponent; } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderPart.cs.meta b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs.meta similarity index 100% rename from spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderPart.cs.meta rename to spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs.meta diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs index 790e8ebcf..21053876e 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs @@ -4,8 +4,13 @@ using System.Collections.Generic; using Spine.Unity; namespace Spine.Unity { + + [HelpURL("")] [ExecuteInEditMode] public class SkeletonRenderSeparator : MonoBehaviour { + public const int DefaultSortingOrderIncrement = 5; + + #region Inspector [SerializeField] protected SkeletonRenderer skeletonRenderer; public SkeletonRenderer SkeletonRenderer { @@ -13,32 +18,29 @@ namespace Spine.Unity { set { if (skeletonRenderer != null) skeletonRenderer.GenerateMeshOverride -= SeparateSkeletonRender; + skeletonRenderer = value; + this.enabled = false; // Disable if nulled. } } - MeshRenderer masterMeshRenderer; - - [Header("Settings")] - public bool propagateMaterialPropertyBlock = false; - public bool controlMainMeshRenderer = true; - - [Space(10f)] - public List renderers = new List(); + MeshRenderer mainMeshRenderer; + public bool copyPropertyBlock = false; + public bool copyMeshRendererFlags = false; + public List partsRenderers = new List(); + #if UNITY_EDITOR void Reset () { - if (skeletonRenderer == null) { + if (skeletonRenderer == null) skeletonRenderer = GetComponent(); - } } + #endif + #endregion void OnEnable () { if (skeletonRenderer == null) return; if (block == null) block = new MaterialPropertyBlock(); - masterMeshRenderer = skeletonRenderer.GetComponent(); - - if (controlMainMeshRenderer) - masterMeshRenderer.enabled = false; + mainMeshRenderer = skeletonRenderer.GetComponent(); skeletonRenderer.GenerateMeshOverride -= SeparateSkeletonRender; skeletonRenderer.GenerateMeshOverride += SeparateSkeletonRender; @@ -46,55 +48,85 @@ namespace Spine.Unity { void OnDisable () { if (skeletonRenderer == null) return; - - if (controlMainMeshRenderer) - masterMeshRenderer.enabled = true; - skeletonRenderer.GenerateMeshOverride -= SeparateSkeletonRender; - foreach (var s in renderers) + #if UNITY_EDITOR + skeletonRenderer.LateUpdate(); + #endif + + foreach (var s in partsRenderers) s.ClearMesh(); } MaterialPropertyBlock block; void SeparateSkeletonRender (SkeletonRenderer.SmartMesh.Instruction instruction) { - int rendererCount = renderers.Count; + int rendererCount = partsRenderers.Count; if (rendererCount <= 0) return; int rendererIndex = 0; - if (propagateMaterialPropertyBlock) - masterMeshRenderer.GetPropertyBlock(block); + if (copyPropertyBlock) + mainMeshRenderer.GetPropertyBlock(block); var submeshInstructions = instruction.submeshInstructions; var submeshInstructionsItems = submeshInstructions.Items; int lastSubmeshInstruction = submeshInstructions.Count - 1; - var currentRenderer = renderers[rendererIndex]; + var currentRenderer = partsRenderers[rendererIndex]; + bool skeletonRendererCalculateNormals = skeletonRenderer.calculateNormals; + + bool useLightProbes = false; + bool receiveShadows = false; + + if (copyMeshRendererFlags) { + useLightProbes = mainMeshRenderer.useLightProbes; + receiveShadows = mainMeshRenderer.receiveShadows; + } + for (int i = 0, start = 0; i <= lastSubmeshInstruction; i++) { if (submeshInstructionsItems[i].separatedBySlot) { - //Debug.Log(submeshInstructionsItems[i].endSlot); - currentRenderer.RenderSubmesh(instruction.submeshInstructions, start, i + 1); - start = i + 1; - - if (propagateMaterialPropertyBlock) + currentRenderer.RenderParts(instruction.submeshInstructions, start, i + 1); + currentRenderer.MeshGenerator.GenerateNormals = skeletonRendererCalculateNormals; + if (copyMeshRendererFlags) { + var mr = currentRenderer.MeshRenderer; + mr.useLightProbes = useLightProbes; + mr.receiveShadows = receiveShadows; + } + if (copyPropertyBlock) currentRenderer.SetPropertyBlock(block); + start = i + 1; rendererIndex++; if (rendererIndex < rendererCount) { - currentRenderer = renderers[rendererIndex]; + currentRenderer = partsRenderers[rendererIndex]; } else { + // Not enough renderers. Skip the rest of the instructions. break; } } else if (i == lastSubmeshInstruction) { - //Debug.Log(submeshInstructionsItems[i].endSlot); - currentRenderer.RenderSubmesh(instruction.submeshInstructions, start, i + 1); - - if (propagateMaterialPropertyBlock) + currentRenderer.RenderParts(instruction.submeshInstructions, start, i + 1); + currentRenderer.MeshGenerator.GenerateNormals = skeletonRendererCalculateNormals; + if (copyMeshRendererFlags) { + var mr = currentRenderer.MeshRenderer; + mr.useLightProbes = useLightProbes; + mr.receiveShadows = receiveShadows; + } + if (copyPropertyBlock) currentRenderer.SetPropertyBlock(block); + + rendererIndex++; } } + + // Too many renderers. Clear the rest. + if (rendererIndex < rendererCount - 1) { + for (int i = rendererIndex; i < rendererCount; i++) { + currentRenderer = partsRenderers[i]; + currentRenderer.ClearMesh(); + } + } + } diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs index 870a5bec8..cbe4ddb21 100644 --- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs +++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs @@ -30,6 +30,7 @@ *****************************************************************************/ #define SPINE_OPTIONAL_NORMALS #define SPINE_OPTIONAL_FRONTFACING +#define SPINE_OPTIONAL_RENDEROVERRIDE using System; using System.Collections.Generic; @@ -48,29 +49,51 @@ public class SkeletonRenderer : MonoBehaviour { public SkeletonDataAsset skeletonDataAsset; public String initialSkinName; - #region Advanced +#region Advanced #if SPINE_OPTIONAL_NORMALS public bool calculateNormals, calculateTangents; #endif public float zSpacing; public bool renderMeshes = true, immutableTriangles; + public bool pmaVertexColors = true; #if SPINE_OPTIONAL_FRONTFACING public bool frontFacing; #endif + + #if SPINE_OPTIONAL_RENDEROVERRIDE + public bool disableMeshRendererOnOverride = true; + public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction); + event InstructionDelegate MeshOverrideDelegate; + public event InstructionDelegate GenerateMeshOverride { + add { + MeshOverrideDelegate += value; + if (disableMeshRendererOnOverride && MeshOverrideDelegate != null) { + meshRenderer.enabled = false; + } + } + remove { + MeshOverrideDelegate -= value; + if (disableMeshRendererOnOverride && MeshOverrideDelegate == null) { + meshRenderer.enabled = true; + } + } + } + + #endif + public bool logErrors = false; // Submesh Separation - [SpineSlot] public string[] submeshSeparators = new string[0]; - [System.NonSerialized] public List submeshSeparatorSlots = new List(); + [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] + [SpineSlot] + public string[] separatorSlotNames = new string[0]; + [System.NonSerialized] + public List separatorSlots = new List(); // Custom Slot Material [System.NonSerialized] readonly Dictionary customSlotMaterials = new Dictionary(); public Dictionary CustomSlotMaterials { get { return customSlotMaterials; } } - - // Custom Mesh Generation Override - public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction); - public event InstructionDelegate GenerateMeshOverride; - #endregion +#endregion [System.NonSerialized] public bool valid; [System.NonSerialized] public Skeleton skeleton; @@ -160,9 +183,9 @@ public class SkeletonRenderer : MonoBehaviour { if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default") skeleton.SetSkin(initialSkinName); - submeshSeparatorSlots.Clear(); - for (int i = 0; i < submeshSeparators.Length; i++) { - submeshSeparatorSlots.Add(skeleton.FindSlot(submeshSeparators[i])); + separatorSlots.Clear(); + for (int i = 0; i < separatorSlotNames.Length; i++) { + separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i])); } LateUpdate(); @@ -175,7 +198,14 @@ public class SkeletonRenderer : MonoBehaviour { if (!valid) return; - if (!meshRenderer.enabled && GenerateMeshOverride == null) + if ( + !meshRenderer.enabled + + #if SPINE_OPTIONAL_RENDEROVERRIDE + && MeshOverrideDelegate == null + #endif + + ) return; // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. @@ -185,7 +215,7 @@ public class SkeletonRenderer : MonoBehaviour { ExposedList drawOrder = skeleton.drawOrder; var drawOrderItems = drawOrder.Items; int drawOrderCount = drawOrder.Count; - int submeshSeparatorSlotsCount = submeshSeparatorSlots.Count; + int separatorSlotCount = separatorSlots.Count; bool renderMeshes = this.renderMeshes; // Clear last state of attachments and submeshes @@ -267,7 +297,7 @@ public class SkeletonRenderer : MonoBehaviour { #endif // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator) - bool separatedBySlot = (submeshSeparatorSlotsCount > 0 && submeshSeparatorSlots.Contains(slot)); + bool separatedBySlot = (separatorSlotCount > 0 && separatorSlots.Contains(slot)); if ((vertexCount > 0 && lastMaterial.GetInstanceID() != material.GetInstanceID()) || separatedBySlot) { workingSubmeshInstructions.Add( new Spine.Unity.MeshGeneration.SubmeshInstruction { @@ -313,10 +343,11 @@ public class SkeletonRenderer : MonoBehaviour { workingInstruction.frontFacing = this.frontFacing; #endif - if (GenerateMeshOverride != null) { - GenerateMeshOverride(workingInstruction); - return; + #if SPINE_OPTIONAL_RENDEROVERRIDE + if (MeshOverrideDelegate != null) { + MeshOverrideDelegate(workingInstruction); } + #endif // STEP 2. Update vertex buffer based on verts from the attachments. // Uses values that were also stored in workingInstruction. @@ -334,13 +365,6 @@ public class SkeletonRenderer : MonoBehaviour { Vector3 normal = new Vector3(0, 0, -1); 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; - } } #endif } else { @@ -354,6 +378,7 @@ public class SkeletonRenderer : MonoBehaviour { Vector2[] uvs = this.uvs; Color32[] colors = this.colors; int vertexIndex = 0; + bool pmaVertexColors = this.pmaVertexColors; Color32 color; float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; @@ -400,11 +425,19 @@ public class SkeletonRenderer : MonoBehaviour { vertices[vertexIndex + 3].y = y3; vertices[vertexIndex + 3].z = z; - color.a = (byte)(a * slot.a * regionAttachment.a); - color.r = (byte)(r * slot.r * regionAttachment.r * color.a); - color.g = (byte)(g * slot.g * regionAttachment.g * color.a); - color.b = (byte)(b * slot.b * regionAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.additive) color.a = 0; + if (pmaVertexColors) { + color.a = (byte)(a * slot.a * regionAttachment.a); + color.r = (byte)(r * slot.r * regionAttachment.r * color.a); + color.g = (byte)(g * slot.g * regionAttachment.g * color.a); + color.b = (byte)(b * slot.b * regionAttachment.b * color.a); + if (slot.data.blendMode == BlendMode.additive) color.a = 0; + } else { + color.a = (byte)(a * slot.a * regionAttachment.a); + color.r = (byte)(r * slot.r * regionAttachment.r * 255); + color.g = (byte)(g * slot.g * regionAttachment.g * 255); + color.b = (byte)(b * slot.b * regionAttachment.b * 255); + } + colors[vertexIndex] = color; colors[vertexIndex + 1] = color; colors[vertexIndex + 2] = color; @@ -467,11 +500,18 @@ public class SkeletonRenderer : MonoBehaviour { this.tempVertices = tempVertices = new float[meshVertexCount]; meshAttachment.ComputeWorldVertices(slot, tempVertices); - color.a = (byte)(a * slot.a * meshAttachment.a); - color.r = (byte)(r * slot.r * meshAttachment.r * color.a); - color.g = (byte)(g * slot.g * meshAttachment.g * color.a); - color.b = (byte)(b * slot.b * meshAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.additive) color.a = 0; + if (pmaVertexColors) { + color.a = (byte)(a * slot.a * meshAttachment.a); + color.r = (byte)(r * slot.r * meshAttachment.r * color.a); + color.g = (byte)(g * slot.g * meshAttachment.g * color.a); + color.b = (byte)(b * slot.b * meshAttachment.b * color.a); + if (slot.data.blendMode == BlendMode.additive) color.a = 0; + } else { + color.a = (byte)(a * slot.a * meshAttachment.a); + color.r = (byte)(r * slot.r * meshAttachment.r * 255); + color.g = (byte)(g * slot.g * meshAttachment.g * 255); + color.b = (byte)(b * slot.b * meshAttachment.b * 255); + } float[] meshUVs = meshAttachment.uvs; float z = i * zSpacing; @@ -501,11 +541,18 @@ public class SkeletonRenderer : MonoBehaviour { this.tempVertices = tempVertices = new float[meshVertexCount]; weightedMeshAttachment.ComputeWorldVertices(slot, tempVertices); - color.a = (byte)(a * slot.a * weightedMeshAttachment.a); - color.r = (byte)(r * slot.r * weightedMeshAttachment.r * color.a); - color.g = (byte)(g * slot.g * weightedMeshAttachment.g * color.a); - color.b = (byte)(b * slot.b * weightedMeshAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.additive) color.a = 0; + if (pmaVertexColors) { + color.a = (byte)(a * slot.a * weightedMeshAttachment.a); + color.r = (byte)(r * slot.r * weightedMeshAttachment.r * color.a); + color.g = (byte)(g * slot.g * weightedMeshAttachment.g * color.a); + color.b = (byte)(b * slot.b * weightedMeshAttachment.b * color.a); + if (slot.data.blendMode == BlendMode.additive) color.a = 0; + } else { + color.a = (byte)(a * slot.a * weightedMeshAttachment.a); + color.r = (byte)(r * slot.r * weightedMeshAttachment.r * 255); + color.g = (byte)(g * slot.g * weightedMeshAttachment.g * 255); + color.b = (byte)(b * slot.b * weightedMeshAttachment.b * 255); + } float[] meshUVs = weightedMeshAttachment.uvs; float z = i * zSpacing; @@ -545,9 +592,19 @@ public class SkeletonRenderer : MonoBehaviour { if (currentSmartMeshInstructionUsed.vertexCount < vertexCount) { if (calculateNormals) { currentMesh.normals = normals; - if (calculateTangents) { - currentMesh.tangents = tangents; + } + + + // For dynamic calculated tangents, this needs to be moved out of the if block when replacing the logic. + if (calculateTangents) { + if (tangents == null || vertexCount > tangents.Length) { + 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; } + + currentMesh.tangents = this.tangents; } } #endif @@ -893,6 +950,8 @@ public class SkeletonRenderer : MonoBehaviour { class SubmeshTriangleBuffer { public int[] triangles = new int[0]; + + // These two fields are used when renderMeshes == false public int triangleCount; public int firstVertex = -1; }