Cleanup and improved inspector workflow.

This commit is contained in:
pharan 2016-03-20 00:49:13 +08:00
parent 9e8fdeef6c
commit 80d8e9e276
15 changed files with 467 additions and 151 deletions

View File

@ -51,6 +51,7 @@ namespace Spine.Unity {
SpineEditorUtilities.ConfirmInitialization();
atlasFile = serializedObject.FindProperty("atlasFile");
materials = serializedObject.FindProperty("materials");
materials.isExpanded = true;
atlasAsset = (AtlasAsset)target;
UpdateBakedList();
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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 {
/// <param name="triangleBuffer">The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit.</param>
/// <param name="bufferTriangleCount">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.</param>
/// <param name="isLastSubmesh">If set to <c>true</c>, the triangle buffer is allowed to be larger than needed.</param>
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

View File

@ -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();

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh);
bool GenerateNormals { get; set; }
}
/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>

View File

@ -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;

View File

@ -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<int, string, string, string> 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<MeshRenderer>();
var currentMeshRenderer = smr.GetComponent<MeshRenderer>();
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<MeshRenderer>();
var currentMeshRenderer = smr.GetComponent<MeshRenderer>();
if (prevMeshRenderer != null && currentMeshRenderer != null) {
int prevSortingLayer = prevMeshRenderer.sortingLayerID;
int prevSortingOrder = prevMeshRenderer.sortingOrder;
currentMeshRenderer.sortingLayerID = prevSortingLayer;
currentMeshRenderer.sortingOrder = prevSortingOrder + SkeletonRenderSeparator.DefaultSortingOrderIncrement;
}
}
}
}
}
/// <summary>Detects orphaned parts renderers and offers to delete them.</summary>
public void DetectOrphanedPartsRenderers (SkeletonRenderSeparator component) {
var children = component.GetComponentsInChildren<SkeletonPartsRenderer>();
var orphans = new System.Collections.Generic.List<SkeletonPartsRenderer>();
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<SkeletonRenderSeparator>();
}
// Validate
[MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator", true)]
static bool ValidateAddRenderSeparatorComponent (MenuCommand cmd) {
var skeletonRenderer = cmd.context as SkeletonRenderer;
var separator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
bool separatorNotOnObject = separator == null;
return separatorNotOnObject;
}
#endregion
}
}

View File

@ -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<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
public void RenderParts (ExposedList<SubmeshInstruction> 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<SkeletonRenderPart>();
var returnComponent = go.AddComponent<SkeletonPartsRenderer>();
return returnComponent;
}

View File

@ -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<Spine.Unity.SkeletonRenderPart> renderers = new List<SkeletonRenderPart>();
MeshRenderer mainMeshRenderer;
public bool copyPropertyBlock = false;
public bool copyMeshRendererFlags = false;
public List<Spine.Unity.SkeletonPartsRenderer> partsRenderers = new List<SkeletonPartsRenderer>();
#if UNITY_EDITOR
void Reset () {
if (skeletonRenderer == null) {
if (skeletonRenderer == null)
skeletonRenderer = GetComponent<SkeletonRenderer>();
}
}
#endif
#endregion
void OnEnable () {
if (skeletonRenderer == null) return;
if (block == null) block = new MaterialPropertyBlock();
masterMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
if (controlMainMeshRenderer)
masterMeshRenderer.enabled = false;
mainMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
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();
}
}
}

View File

@ -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<Slot> submeshSeparatorSlots = new List<Slot>();
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")]
[SpineSlot]
public string[] separatorSlotNames = new string[0];
[System.NonSerialized]
public List<Slot> separatorSlots = new List<Slot>();
// Custom Slot Material
[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
public Dictionary<Slot, Material> 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<Slot> 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;
}