This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class AssociateBonesScope : IDisposable
{
private bool m_Disposed;
private bool m_AssociateBones;
private SpriteCache m_Sprite;
public AssociateBonesScope(SpriteCache sprite)
{
m_Sprite = sprite;
m_AssociateBones = m_Sprite.AssociatePossibleBones();
}
~AssociateBonesScope()
{
if (!m_Disposed)
Debug.LogError("Scope was not disposed! You should use the 'using' keyword or manually call Dispose.");
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
if (m_AssociateBones)
m_Sprite.DeassociateUnusedBones();
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using UnityEditor.U2D.Layout;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface ITool {}
internal abstract class BaseTool : SkinningObject, ITool
{
[SerializeField]
private LayoutOverlay m_LayoutOverlay;
internal LayoutOverlay layoutOverlay
{
get
{
return m_LayoutOverlay;
}
}
[SerializeField]
private bool m_IsActive = false;
public bool isActive
{
get { return m_IsActive; }
private set { m_IsActive = value; }
}
public virtual int defaultControlID { get { return 0; } }
public virtual IMeshPreviewBehaviour previewBehaviour
{
get { return null; }
}
internal override void OnDestroy()
{
Deactivate();
}
public void Activate()
{
isActive = true;
OnActivate();
}
public void Deactivate()
{
isActive = false;
OnDeactivate();
}
public void DoGUI()
{
if (isActive)
OnGUI();
}
public virtual void Initialize(LayoutOverlay layout)
{
m_LayoutOverlay = layout;
}
protected virtual void OnActivate() {}
protected virtual void OnDeactivate() {}
protected virtual void OnGUI() {}
}
}

View File

@@ -0,0 +1,84 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class BoneDrawingUtility
{
public static float GetBoneRadius(Vector3 position, float scale = 1.0f)
{
if (Camera.current != null)
{
return 0.15f * scale * HandleUtility.GetHandleSize(position);
}
return 10f * scale / Handles.matrix.GetColumn(0).magnitude;
}
public static void DrawBoneNode(Vector3 position, Vector3 forward, Color color, float scale = 1.0f)
{
Color c = Handles.color;
Handles.color = color;
Handles.DrawSolidDisc(position, -forward, GetBoneRadius(position, scale) * 0.3f);
Handles.color = c;
}
public static void DrawBone(Vector3 position, Vector3 endPosition, Vector3 forward, Color color, float scale = 1.0f)
{
Color c = Handles.color;
Handles.color = color;
var right = Vector3.right;
var v = endPosition - position;
if (v.sqrMagnitude != 0)
right = v.normalized;
var up = Vector3.Cross(right, forward).normalized;
var radius = GetBoneRadius(position, scale) * 0.5f;
var numSamples = 12;
if (v.sqrMagnitude <= radius * radius)
DrawingUtility.DrawSolidArc(position, -forward, up, 360f, radius, numSamples * 2);
else
{
DrawingUtility.DrawSolidArc(position, -forward, up, 180f, radius, numSamples);
DrawingUtility.DrawLine(position, endPosition, forward, radius * 2f, 0f);
}
Handles.color = c;
}
public static void DrawBoneOutline(Vector3 position, Vector3 endPosition, Vector3 forward, Color color, float outlineScale = 1.35f, float scale = 1.0f)
{
outlineScale = Mathf.Max(1f, outlineScale);
Color c = Handles.color;
Handles.color = color;
var right = Vector3.right;
var v = endPosition - position;
if (v.sqrMagnitude != 0)
right = v.normalized;
var up = Vector3.Cross(right, forward).normalized;
var radius = GetBoneRadius(position, scale) * 0.5f;
var outlineWidth = radius * (outlineScale - 1f);
var numSamples = 12;
if (v.sqrMagnitude <= radius * radius)
DrawingUtility.DrawSolidArc(position, -forward, up, 360f, radius, outlineScale, numSamples * 2);
else
{
DrawingUtility.DrawSolidArc(position, -forward, up, 180f, radius, outlineScale, numSamples);
DrawingUtility.DrawSolidArc(endPosition, -forward, -up, 180f, outlineWidth, 0f, numSamples);
DrawingUtility.DrawLine(position + up * (radius + outlineWidth * 0.5f), endPosition + up * outlineWidth * 0.5f, forward, outlineWidth, outlineWidth);
DrawingUtility.DrawLine(position - up * (radius + outlineWidth * 0.5f), endPosition - up * outlineWidth * 0.5f, forward, outlineWidth, outlineWidth);
}
Handles.color = c;
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
#if CODE_COVERAGE
internal class BaseObject
{
public static T CreateInstance<T>()
{
return Activator.CreateInstance<T>();
}
public static void DestroyImmediate(object o)
{
if (o is BaseObject)
{
var obj = o as BaseObject;
obj.OnDestroy();
s_Objects.Remove(obj.GetInstanceID());
}
else if (o is UnityEngine.Object)
{
var obj = o as UnityEngine.Object;
Undo.ClearUndo(obj);
UnityEngine.Object.DestroyImmediate(obj);
}
}
public static BaseObject InstanceIDToObject(int instanceID)
{
var obj = default(BaseObject);
s_Objects.TryGetValue(instanceID, out obj);
return obj;
}
private static Dictionary<int, BaseObject> s_Objects = new Dictionary<int, BaseObject>();
private static int s_InstanceID = 0;
private int m_InstanceID;
public string name { get; set; }
public HideFlags hideFlags = HideFlags.None;
public BaseObject()
{
m_InstanceID = ++s_InstanceID;
s_Objects.Add(m_InstanceID, this);
}
internal virtual void OnEnable() {}
internal virtual void OnDestroy() {}
public int GetInstanceID()
{
return m_InstanceID;
}
public override bool Equals(object other)
{
if ((other == null))
return false;
return object.ReferenceEquals(this, other);
}
public override int GetHashCode()
{
return m_InstanceID.GetHashCode();
}
public static bool operator==(BaseObject t1, BaseObject t2)
{
if (object.ReferenceEquals(t1, null))
return object.ReferenceEquals(t2, null);
return object.ReferenceEquals(t1, t2);
}
public static bool operator!=(BaseObject t1, BaseObject t2)
{
return !(t1 == t2);
}
}
#else
internal class BaseObject : ScriptableObject
{
public static void DestroyImmediate(object o)
{
if (o is UnityEngine.Object)
{
var obj = o as UnityEngine.Object;
Undo.ClearUndo(obj);
UnityEngine.Object.DestroyImmediate(obj);
}
}
public static BaseObject InstanceIDToObject(int instanceID)
{
return EditorUtility.InstanceIDToObject(instanceID) as BaseObject;
}
internal virtual void OnEnable() {}
internal virtual void OnDestroy() {}
}
#endif
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Cache : BaseObject, ICacheUndo
{
public static T Create<T>() where T : Cache
{
var cache = CreateInstance<T>();
cache.hideFlags = HideFlags.DontSave;
return cache;
}
public static void Destroy(Cache cache)
{
cache.Destroy();
DestroyImmediate(cache);
}
[SerializeField]
private List<CacheObject> m_CacheObjects = new List<CacheObject>();
[SerializeField]
private List<CacheObject> m_RemovedCacheObjects = new List<CacheObject>();
private string m_UndoOperationName = null;
private IUndo m_DefaultUndo = new UnityEngineUndo();
private IUndo m_UndoOverride = null;
protected IUndo undo
{
get
{
if (undoOverride != null)
return undoOverride;
return m_DefaultUndo;
}
}
public IUndo undoOverride
{
get { return m_UndoOverride; }
set { m_UndoOverride = value; }
}
public bool isUndoOperationSet
{
get { return string.IsNullOrEmpty(m_UndoOperationName) == false; }
}
public void IncrementCurrentGroup()
{
undo.IncrementCurrentGroup();
}
public virtual void BeginUndoOperation(string operationName)
{
if (isUndoOperationSet == false)
{
Debug.Assert(!m_CacheObjects.Contains(null));
m_UndoOperationName = operationName;
undo.RegisterCompleteObjectUndo(this, m_UndoOperationName);
undo.RegisterCompleteObjectUndo(m_CacheObjects.ToArray(), m_UndoOperationName);
undo.RegisterCompleteObjectUndo(m_RemovedCacheObjects.ToArray(), m_UndoOperationName);
}
}
public void EndUndoOperation()
{
m_UndoOperationName = null;
}
public bool IsRemoved(CacheObject cacheObject)
{
return m_RemovedCacheObjects.Contains(cacheObject);
}
public T CreateCache<T>() where T : CacheObject
{
var cacheObject = FindRemovedCacheObject<T>();
if (cacheObject != null)
{
m_RemovedCacheObjects.Remove(cacheObject);
cacheObject.OnEnable();
}
else
{
cacheObject = CacheObject.Create<T>(this);
}
m_CacheObjects.Add(cacheObject);
cacheObject.OnCreate();
return cacheObject;
}
private T FindRemovedCacheObject<T>() where T : CacheObject
{
return m_RemovedCacheObjects.FirstOrDefault((o) => o.GetType().Equals(typeof(T))) as T;
}
public void Destroy(CacheObject cacheObject)
{
Debug.Assert(cacheObject != null);
Debug.Assert(cacheObject.owner == this);
Debug.Assert(m_CacheObjects.Contains(cacheObject));
m_CacheObjects.Remove(cacheObject);
m_RemovedCacheObjects.Add(cacheObject);
cacheObject.OnDestroy();
}
public void Destroy()
{
Debug.Assert(!m_CacheObjects.Contains(null));
EndUndoOperation();
undo.ClearUndo(this);
var cacheObjects = m_CacheObjects.ToArray();
foreach (var cacheObject in cacheObjects)
DestroyImmediate(cacheObject);
cacheObjects = m_RemovedCacheObjects.ToArray();
foreach (var cacheObject in cacheObjects)
DestroyImmediate(cacheObject);
m_CacheObjects.Clear();
m_RemovedCacheObjects.Clear();
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class CacheObject : BaseObject, ISerializationCallbackReceiver
{
public static T Create<T>(Cache owner) where T : CacheObject
{
var cacheObject = CreateInstance<T>();
cacheObject.hideFlags = HideFlags.HideAndDontSave;
cacheObject.owner = owner;
return cacheObject;
}
[SerializeField]
private Cache m_Owner;
public Cache owner
{
get { return m_Owner; }
set { m_Owner = value; }
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
OnAfterDeserialize();
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
OnBeforeSerialize();
}
internal virtual void OnCreate() {}
protected virtual void OnAfterDeserialize() {}
protected virtual void OnBeforeSerialize() {}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace UnityEditor.U2D.Animation
{
internal class SwitchModeTool : BaseTool
{
protected override void OnActivate()
{
if (skinningCache.mode != SkinningMode.SpriteSheet)
{
skinningCache.mode = SkinningMode.SpriteSheet;
skinningCache.events.skinningModeChanged.Invoke(SkinningMode.SpriteSheet);
}
}
protected override void OnDeactivate()
{
if (skinningCache.mode != SkinningMode.Character)
{
skinningCache.mode = SkinningMode.Character;
skinningCache.events.skinningModeChanged.Invoke(SkinningMode.Character);
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class ColorExtensions
{
public static Color AlphaMultiplied(this Color c, float multiplier)
{
return new Color(c.r, c.g, c.b, c.a * multiplier);
}
}
}

View File

@@ -0,0 +1,664 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor.U2D.Layout;
using UnityEngine.U2D;
namespace UnityEditor.U2D.Animation
{
internal interface ICopyToolStringStore
{
string stringStore
{
get;
set;
}
}
internal class SystemCopyBufferStringStore : ICopyToolStringStore
{
public string stringStore
{
get { return EditorGUIUtility.systemCopyBuffer; }
set { EditorGUIUtility.systemCopyBuffer = value; }
}
}
internal class CopyTool : MeshToolWrapper
{
public class NewBonesStore
{
public BoneCache[] newBones;
public Dictionary<string, string> newBoneNameDict;
public NewBonesStore()
{
newBones = null;
newBoneNameDict = new Dictionary<string, string>();
}
public void MapAllExistingBones()
{
foreach (var bone in newBones)
newBoneNameDict.Add(bone.name, bone.name);
}
}
private ICopyToolStringStore m_CopyToolStringStore;
private CopyToolView m_CopyToolView;
public float pixelsPerUnit
{
private get;
set;
}
public ICopyToolStringStore copyToolStringStore
{
set { m_CopyToolStringStore = value; }
}
internal override void OnCreate()
{
m_CopyToolView = new CopyToolView();
m_CopyToolView.onPasteActivated += OnPasteActivated;
m_CopyToolStringStore = new SystemCopyBufferStringStore();
disableMeshEditor = true;
}
public override void Initialize(LayoutOverlay layout)
{
m_CopyToolView.Initialize(layout);
}
protected override void OnActivate()
{
base.OnActivate();
m_CopyToolView.Show(skinningCache.bonesReadOnly);
}
protected override void OnDeactivate()
{
base.OnDeactivate();
m_CopyToolView.Hide();
}
private void CopyMeshFromSpriteCache(SpriteCache sprite, SkinningCopySpriteData skinningSpriteData)
{
if (meshTool == null)
return;
meshTool.SetupSprite(sprite);
skinningSpriteData.vertices = meshTool.mesh.vertices;
skinningSpriteData.indices = meshTool.mesh.indices;
skinningSpriteData.edges = meshTool.mesh.edges;
skinningSpriteData.boneWeightNames = new List<string>();
foreach (var bone in meshTool.mesh.bones)
{
skinningSpriteData.boneWeightNames.Add(bone.name);
}
}
public void OnCopyActivated()
{
SkinningCopyData skinningCopyData;
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite == null)
{
skinningCopyData = CopyAll();
}
else
{
skinningCopyData = CopySingle(selectedSprite);
}
if (skinningCopyData != null)
m_CopyToolStringStore.stringStore = SkinningCopyUtility.SerializeSkinningCopyDataToString(skinningCopyData);
skinningCache.events.copy.Invoke();
}
public SkinningCopyData CopyAll()
{
var skinningCopyData = new SkinningCopyData();
skinningCopyData.pixelsPerUnit = pixelsPerUnit;
var sprites = skinningCache.GetSprites();
foreach (var sprite in sprites)
{
var skinningSpriteData = new SkinningCopySpriteData();
skinningSpriteData.spriteName = sprite.name;
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
if (skeleton != null && skeleton.BoneCount > 0)
{
if (skinningCache.hasCharacter)
{
// Order doesn't matter for character bones
skinningSpriteData.spriteBones = skeleton.bones.ToSpriteBone(Matrix4x4.identity).Select(x => new SpriteBoneCopyData()
{
spriteBone = x,
order = -1
}).ToList();
}
else
{
skinningSpriteData.spriteBones = new List<SpriteBoneCopyData>();
var bones = skeleton.bones.FindRoots();
foreach (var bone in bones)
GetSpriteBoneDataRecursively(skinningSpriteData.spriteBones, bone, skeleton.bones.ToList());
}
}
if (meshTool != null)
{
CopyMeshFromSpriteCache(sprite, skinningSpriteData);
}
skinningCopyData.copyData.Add(skinningSpriteData);
}
if (meshTool != null)
{
meshTool.SetupSprite(null);
}
return skinningCopyData;
}
public SkinningCopyData CopySingle(SpriteCache sprite)
{
var skinningCopyData = new SkinningCopyData();
skinningCopyData.pixelsPerUnit = pixelsPerUnit;
// Mesh
var skinningSpriteData = new SkinningCopySpriteData();
skinningCopyData.copyData.Add(skinningSpriteData);
CopyMeshFromSpriteCache(sprite, skinningSpriteData);
// Bones
var rootBones = new List<BoneCache>();
BoneCache[] boneCache = null;
if (skinningCache.hasCharacter)
{
var characterPart = skinningCache.GetCharacterPart(sprite);
if (characterPart != null && characterPart.bones != null)
{
boneCache = characterPart.bones;
var bones = characterPart.bones.FindRoots();
foreach (var bone in bones)
rootBones.Add(bone);
}
}
else
{
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
if (skeleton != null && skeleton.BoneCount > 0)
{
boneCache = skeleton.bones;
var bones = boneCache.FindRoots();
foreach (var bone in bones)
rootBones.Add(bone);
}
}
if (rootBones.Count > 0)
{
skinningSpriteData.spriteBones = new List<SpriteBoneCopyData>();
foreach (var rootBone in rootBones)
{
var rootBoneIndex = skinningSpriteData.spriteBones.Count;
GetSpriteBoneDataRecursively(skinningSpriteData.spriteBones, rootBone, boneCache.ToList());
if (skinningCache.hasCharacter)
{
// Offset the bones based on the currently selected Sprite in Character mode
var characterPart = sprite.GetCharacterPart();
if (characterPart != null)
{
var offset = characterPart.position;
var rootSpriteBone = skinningSpriteData.spriteBones[rootBoneIndex];
rootSpriteBone.spriteBone.position = rootSpriteBone.spriteBone.position - offset;
skinningSpriteData.spriteBones[rootBoneIndex] = rootSpriteBone;
}
}
}
}
return skinningCopyData;
}
private void GetSpriteBoneDataRecursively(List<SpriteBoneCopyData> bones, BoneCache rootBone, List<BoneCache> boneCache)
{
AppendSpriteBoneDataRecursively(bones, rootBone, -1, boneCache);
}
private void AppendSpriteBoneDataRecursively(List<SpriteBoneCopyData> spriteBones, BoneCache bone, int parentIndex, List<BoneCache> boneCache)
{
int currentParentIndex = spriteBones.Count;
var boneCopyData = new SpriteBoneCopyData()
{
spriteBone = new SpriteBone()
{
name = bone.name,
guid = bone.guid,
color = bone.bindPoseColor,
parentId = parentIndex
},
order = boneCache.FindIndex(x => x == bone)
};
if (boneCopyData.order < 0)
{
boneCopyData.order = boneCache.Count;
boneCache.Add(bone);
}
if (parentIndex == -1 && bone.parentBone != null)
{
boneCopyData.spriteBone.position = bone.position;
boneCopyData.spriteBone.rotation = bone.rotation;
}
else
{
boneCopyData.spriteBone.position = bone.localPosition;
boneCopyData.spriteBone.rotation = bone.localRotation;
}
boneCopyData.spriteBone.position = new Vector3(boneCopyData.spriteBone.position.x, boneCopyData.spriteBone.position.y, bone.depth);
boneCopyData.spriteBone.length = bone.localLength;
spriteBones.Add(boneCopyData);
foreach (var child in bone)
{
var childBone = child as BoneCache;
if (childBone != null)
AppendSpriteBoneDataRecursively(spriteBones, childBone, currentParentIndex, boneCache);
}
}
public void OnPasteActivated(bool bone, bool mesh, bool flipX, bool flipY)
{
var copyBuffer = m_CopyToolStringStore.stringStore;
if (!SkinningCopyUtility.CanDeserializeStringToSkinningCopyData(copyBuffer))
{
Debug.LogError(TextContent.copyError1);
return;
}
var skinningCopyData = SkinningCopyUtility.DeserializeStringToSkinningCopyData(copyBuffer);
if (skinningCopyData == null || skinningCopyData.copyData.Count == 0)
{
Debug.LogError(TextContent.copyError2);
return;
}
var scale = 1f;
if (skinningCopyData.pixelsPerUnit > 0f)
scale = pixelsPerUnit / skinningCopyData.pixelsPerUnit;
var sprites = skinningCache.GetSprites();
var copyMultiple = skinningCopyData.copyData.Count > 1;
if (copyMultiple && skinningCopyData.copyData.Count != sprites.Length && mesh)
{
Debug.LogError(String.Format(TextContent.copyError3, sprites.Length, skinningCopyData.copyData.Count));
return;
}
using (skinningCache.UndoScope(TextContent.pasteData))
{
NewBonesStore newBonesStore = null;
if (bone && copyMultiple && skinningCache.hasCharacter)
{
newBonesStore = new NewBonesStore();
var skinningSpriteData = skinningCopyData.copyData[0];
newBonesStore.newBones = skinningCache.CreateBoneCacheFromSpriteBones(skinningSpriteData.spriteBones.Select(y => y.spriteBone).ToArray(), scale);
if (flipX || flipY)
{
var characterRect = new Rect(Vector2.zero, skinningCache.character.dimension);
var newPositions = new Vector3[newBonesStore.newBones.Length];
var newRotations = new Quaternion[newBonesStore.newBones.Length];
for (var i = 0; i < newBonesStore.newBones.Length; ++i)
{
newPositions[i] = GetFlippedBonePosition(newBonesStore.newBones[i], Vector2.zero, characterRect, flipX, flipY);
newRotations[i] = GetFlippedBoneRotation(newBonesStore.newBones[i], flipX, flipY);
}
for (var i = 0; i < newBonesStore.newBones.Length; ++i)
{
newBonesStore.newBones[i].position = newPositions[i];
newBonesStore.newBones[i].rotation = newRotations[i];
}
}
newBonesStore.MapAllExistingBones();
var skeleton = skinningCache.character.skeleton;
skeleton.SetBones(newBonesStore.newBones);
skinningCache.events.skeletonTopologyChanged.Invoke(skeleton);
}
foreach (var skinningSpriteData in skinningCopyData.copyData)
{
SpriteCache sprite = null;
if (!String.IsNullOrEmpty(skinningSpriteData.spriteName))
{
sprite = sprites.FirstOrDefault(x => x.name == skinningSpriteData.spriteName);
}
if (sprite == null && (skinningCopyData.copyData.Count == 1 || String.IsNullOrEmpty(skinningSpriteData.spriteName)))
{
sprite = skinningCache.selectedSprite;
}
if (sprite == null)
continue;
if (bone && (!skinningCache.hasCharacter || !copyMultiple))
{
var spriteBones = new SpriteBone[skinningSpriteData.spriteBones.Count];
for (int i = 0; i < skinningSpriteData.spriteBones.Count; ++i)
{
var order = skinningSpriteData.spriteBones[i].order;
spriteBones[order] = skinningSpriteData.spriteBones[i].spriteBone;
var parentId = spriteBones[order].parentId;
if (parentId >= 0)
{
spriteBones[order].parentId = skinningSpriteData.spriteBones[parentId].order;
}
}
newBonesStore = PasteSkeletonBones(sprite, spriteBones.ToList(), flipX, flipY, scale);
}
if (mesh && meshTool != null)
{
PasteMesh(sprite, skinningSpriteData, flipX, flipY, scale, newBonesStore);
}
}
if (newBonesStore != null && newBonesStore.newBones != null)
{
skinningCache.skeletonSelection.elements = newBonesStore.newBones;
skinningCache.events.boneSelectionChanged.Invoke();
}
}
skinningCache.events.paste.Invoke(bone, mesh, flipX, flipY);
}
private Vector3 GetFlippedBonePosition(BoneCache bone, Vector2 startPosition, Rect spriteRect
, bool flipX, bool flipY)
{
Vector3 position = startPosition;
if (flipX)
{
position.x += spriteRect.width - bone.position.x;
}
else
{
position.x += bone.position.x;
}
if (flipY)
{
position.y += spriteRect.height - bone.position.y;
}
else
{
position.y += bone.position.y;
}
position.z = bone.position.z;
return position;
}
private Quaternion GetFlippedBoneRotation(BoneCache bone, bool flipX, bool flipY)
{
var euler = bone.rotation.eulerAngles;
if (flipX)
{
if (euler.z <= 180)
{
euler.z = 180 - euler.z;
}
else
{
euler.z = 540 - euler.z;
}
}
if (flipY)
{
euler.z = 360 - euler.z;
}
return Quaternion.Euler(euler);
}
void SetBonePositionAndRotation(BoneCache[] boneCache, TransformCache bone, Vector3[] position, Quaternion[] rotation)
{
var index = Array.FindIndex(boneCache, x => x == bone);
if (index >= 0)
{
bone.position = position[index];
bone.rotation = rotation[index];
}
foreach (var child in bone.children)
{
SetBonePositionAndRotation(boneCache, child, position, rotation);
}
}
public NewBonesStore PasteSkeletonBones(SpriteCache sprite, List<SpriteBone> spriteBones, bool flipX, bool flipY, float scale = 1.0f)
{
NewBonesStore newBonesStore = new NewBonesStore();
newBonesStore.newBones = skinningCache.CreateBoneCacheFromSpriteBones(spriteBones.ToArray(), scale);
if (newBonesStore.newBones.Length == 0)
return null;
if (sprite == null || (skinningCache.mode == SkinningMode.SpriteSheet && skinningCache.hasCharacter))
return null;
var spriteRect = sprite.textureRect;
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
var rectPosition = spriteRect.position;
if (skinningCache.mode == SkinningMode.Character)
{
var characterPart = sprite.GetCharacterPart();
if (characterPart == null)
return null;
rectPosition = characterPart.position;
}
var newPositions = new Vector3[newBonesStore.newBones.Length];
var newRotations = new Quaternion[newBonesStore.newBones.Length];
for (var i = 0; i < newBonesStore.newBones.Length; ++i)
{
newPositions[i] = GetFlippedBonePosition(newBonesStore.newBones[i], rectPosition, spriteRect, flipX, flipY);
newRotations[i] = GetFlippedBoneRotation(newBonesStore.newBones[i], flipX, flipY);
}
for (var i = 0; i < newBonesStore.newBones.Length; ++i)
{
if(newBonesStore.newBones[i].parent == null)
SetBonePositionAndRotation(newBonesStore.newBones, newBonesStore.newBones[i], newPositions, newRotations);
}
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
newBonesStore.MapAllExistingBones();
skeleton.SetBones(newBonesStore.newBones);
}
else
{
var existingBoneNames = skeleton.bones.Select(x => x.name).ToList();
skeleton.AddBones(newBonesStore.newBones);
var bones = skeleton.bones;
// Update names of all newly pasted bones
foreach (var bone in newBonesStore.newBones)
{
if (existingBoneNames.Contains(bone.name))
{
var oldBoneName = bone.name;
bone.name = SkeletonController.AutoBoneName(bone.parentBone, bones);
existingBoneNames.Add(bone.name);
newBonesStore.newBoneNameDict.Add(oldBoneName, bone.name);
}
else
{
newBonesStore.newBoneNameDict.Add(bone.name, bone.name);
}
}
skeleton.SetDefaultPose();
}
skinningCache.events.skeletonTopologyChanged.Invoke(skeleton);
return newBonesStore;
}
public void PasteMesh(SpriteCache sprite, SkinningCopySpriteData skinningSpriteData, bool flipX, bool flipY, float scale, NewBonesStore newBonesStore)
{
if (sprite == null)
return;
meshTool.SetupSprite(sprite);
meshTool.mesh.vertices = skinningSpriteData.vertices;
if (!Mathf.Approximately(scale, 1f) || flipX || flipY)
{
var spriteRect = sprite.textureRect;
foreach (var vertex in meshTool.mesh.vertices)
{
var position = vertex.position;
if (!Mathf.Approximately(scale, 1f))
position = position * scale;
if (flipX)
position.x = spriteRect.width - vertex.position.x;
if (flipY)
position.y = spriteRect.height - vertex.position.y;
vertex.position = position;
}
}
meshTool.mesh.indices = skinningSpriteData.indices;
meshTool.mesh.edges = skinningSpriteData.edges;
int[] copyBoneToNewBones = new int[skinningSpriteData.boneWeightNames.Count];
BoneCache[] setBones = null;
if (newBonesStore != null && newBonesStore.newBones != null)
{
// Update bone weights with new bone indices
var setBonesList = new List<BoneCache>();
copyBoneToNewBones = new int[skinningSpriteData.boneWeightNames.Count];
int index = 0;
for (int i = 0; i < skinningSpriteData.boneWeightNames.Count; ++i)
{
string oldBoneName = skinningSpriteData.boneWeightNames[i];
string newBoneName;
newBonesStore.newBoneNameDict.TryGetValue(oldBoneName, out newBoneName);
var newBone = newBonesStore.newBones.FirstOrDefault(bone => bone.name == newBoneName);
copyBoneToNewBones[i] = -1;
if (newBone == null)
continue;
for (int j = 0; j < skinningSpriteData.spriteBones.Count; ++j)
{
if (skinningSpriteData.spriteBones[j].spriteBone.name == oldBoneName)
{
copyBoneToNewBones[i] = index++;
setBonesList.Add(newBone);
break;
}
}
}
setBones = setBonesList.ToArray();
}
else
{
// Attempt to link weights based on existing bone names
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
var characterBones = new List<BoneCache>();
for (int i = 0; i < skinningSpriteData.boneWeightNames.Count; ++i)
{
copyBoneToNewBones[i] = -1;
var boneName = skinningSpriteData.boneWeightNames[i];
for (int j = 0; j < skeleton.bones.Length; ++j)
{
if (skeleton.bones[j].name == boneName)
{
copyBoneToNewBones[i] = characterBones.Count;
characterBones.Add(skeleton.bones[j]);
break;
}
}
}
setBones = characterBones.ToArray();
}
// Remap new bone indexes from copied bone indexes
foreach (var vertex in meshTool.mesh.vertices)
{
var editableBoneWeight = vertex.editableBoneWeight;
for (var i = 0; i < editableBoneWeight.Count; ++i)
{
if (!editableBoneWeight[i].enabled)
continue;
if (copyBoneToNewBones.Length > editableBoneWeight[i].boneIndex)
{
var boneIndex = copyBoneToNewBones[editableBoneWeight[i].boneIndex];
if (boneIndex != -1)
editableBoneWeight[i].boneIndex = boneIndex;
}
}
}
// Update associated bones for mesh
meshTool.mesh.SetCompatibleBoneSet(setBones);
meshTool.mesh.bones = setBones; // Fixes weights for bones that do not exist
// Update associated bones for character
if (skinningCache.hasCharacter)
{
var characterPart = sprite.GetCharacterPart();
if (characterPart != null)
{
characterPart.bones = setBones;
skinningCache.events.characterPartChanged.Invoke(characterPart);
}
}
meshTool.UpdateMesh();
}
}
internal class CopyToolView
{
private PastePanel m_PastePanel;
public event Action<bool, bool, bool, bool> onPasteActivated = (bone, mesh, flipX, flipY) => {};
public void Show(bool readonlyBone)
{
m_PastePanel.SetHiddenFromLayout(false);
m_PastePanel.BonePasteEnable(!readonlyBone);
}
public void Hide()
{
m_PastePanel.SetHiddenFromLayout(true);
}
public void Initialize(LayoutOverlay layoutOverlay)
{
m_PastePanel = PastePanel.GenerateFromUXML();
BindElements();
layoutOverlay.rightOverlay.Add(m_PastePanel);
m_PastePanel.SetHiddenFromLayout(true);
}
void BindElements()
{
m_PastePanel.onPasteActivated += OnPasteActivated;
}
void OnPasteActivated(bool bone, bool mesh, bool flipX, bool flipY)
{
onPasteActivated(bone, mesh, flipX, flipY);
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class DefaultPoseScope : IDisposable
{
private bool m_Disposed;
private SkeletonCache m_Skeleton;
private BonePose[] m_Pose;
private bool m_DoRestorePose = false;
private bool m_UseLocalPose;
public DefaultPoseScope(SkeletonCache skeleton, bool useLocalPose = true)
{
Debug.Assert(skeleton != null);
m_Skeleton = skeleton;
m_UseLocalPose = useLocalPose;
if (m_Skeleton.isPosePreview)
{
m_DoRestorePose = true;
if(useLocalPose)
m_Pose = m_Skeleton.GetLocalPose();
else
m_Pose = m_Skeleton.GetWorldPose();
m_Skeleton.RestoreDefaultPose();
}
}
~DefaultPoseScope()
{
if (!m_Disposed)
Debug.LogError("Scope was not disposed! You should use the 'using' keyword or manually call Dispose.");
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
if (m_Skeleton != null && m_DoRestorePose)
{
if(m_UseLocalPose)
m_Skeleton.SetLocalPose(m_Pose);
else
m_Skeleton.SetWorldPose(m_Pose);
}
}
}
}

View File

@@ -0,0 +1,261 @@
using UnityEditor.U2D.Common;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class DrawingUtility
{
public static readonly Color kSpriteBorderColor = new Color(0.25f, 0.5f, 1f, 0.75f);
public static void DrawLine(Vector3 p1, Vector3 p2, Vector3 normal, float width)
{
DrawLine(p1, p2, normal, width, width);
}
public static void DrawLine(Vector3 p1, Vector3 p2, Vector3 normal, float widthP1, float widthP2)
{
DrawLine(p1, p2, normal, widthP1, widthP2, Handles.color);
}
public static void DrawLine(Vector3 p1, Vector3 p2, Vector3 normal, float widthP1, float widthP2, Color color)
{
if (Event.current.type != EventType.Repaint)
return;
Vector3 up = Vector3.Cross(normal, p2 - p1).normalized;
Shader.SetGlobalFloat("_HandleSize", 1);
InternalEditorBridge.ApplyWireMaterial();
GL.PushMatrix();
GL.MultMatrix(Handles.matrix);
GL.Begin(4);
GL.Color(color);
GL.Vertex(p1 + up * widthP1 * 0.5f);
GL.Vertex(p1 - up * widthP1 * 0.5f);
GL.Vertex(p2 - up * widthP2 * 0.5f);
GL.Vertex(p1 + up * widthP1 * 0.5f);
GL.Vertex(p2 - up * widthP2 * 0.5f);
GL.Vertex(p2 + up * widthP2 * 0.5f);
GL.End();
GL.PopMatrix();
}
public static void BeginLines(Color color)
{
InternalEditorBridge.ApplyWireMaterial();
GL.PushMatrix();
GL.MultMatrix(Handles.matrix);
GL.Begin(GL.LINES);
GL.Color(color);
}
public static void BeginSolidLines()
{
InternalEditorBridge.ApplyWireMaterial();
GL.PushMatrix();
GL.MultMatrix(Handles.matrix);
GL.Begin(GL.TRIANGLES);
}
public static void EndLines()
{
GL.End();
GL.PopMatrix();
}
public static void DrawLine(Vector3 p1, Vector3 p2)
{
GL.Vertex(p1);
GL.Vertex(p2);
}
public static void DrawSolidLine(float width, Vector3 p1, Vector3 p2)
{
DrawSolidLine(p1, p2, Vector3.forward, width, width);
}
public static void DrawSolidLine(Vector3 p1, Vector3 p2, Vector3 normal, float widthP1, float widthP2)
{
GL.Color(Handles.color);
Vector3 right = Vector3.Cross(normal, p2 - p1).normalized;
GL.Vertex(p1 + right * widthP1 * 0.5f);
GL.Vertex(p1 - right * widthP1 * 0.5f);
GL.Vertex(p2 - right * widthP2 * 0.5f);
GL.Vertex(p1 + right * widthP1 * 0.5f);
GL.Vertex(p2 - right * widthP2 * 0.5f);
GL.Vertex(p2 + right * widthP2 * 0.5f);
}
public static void DrawBox(Rect position)
{
Vector3[] points = new Vector3[5];
int i = 0;
points[i++] = new Vector3(position.xMin, position.yMin, 0f);
points[i++] = new Vector3(position.xMax, position.yMin, 0f);
points[i++] = new Vector3(position.xMax, position.yMax, 0f);
points[i++] = new Vector3(position.xMin, position.yMax, 0f);
DrawLine(points[0], points[1]);
DrawLine(points[1], points[2]);
DrawLine(points[2], points[3]);
DrawLine(points[3], points[0]);
}
public static void DrawMesh(Mesh mesh, Material material, Matrix4x4 matrix)
{
Debug.Assert(mesh != null);
Debug.Assert(material != null);
if (Event.current.type != EventType.Repaint)
return;
material.SetFloat("_AdjustLinearForGamma", PlayerSettings.colorSpace == ColorSpace.Linear ? 1.0f : 0.0f);
material.SetPass(0);
Graphics.DrawMeshNow(mesh, Handles.matrix * matrix);
}
public static void DrawGUIStyleCap(int controlID, Vector3 position, Quaternion rotation, float size, GUIStyle guiStyle)
{
if (Event.current.type != EventType.Repaint)
return;
if (Camera.current && Vector3.Dot(position - Camera.current.transform.position, Camera.current.transform.forward) < 0f)
return;
Handles.BeginGUI();
guiStyle.Draw(GetGUIStyleRect(guiStyle, position), GUIContent.none, controlID);
Handles.EndGUI();
}
private static Rect GetGUIStyleRect(GUIStyle style, Vector3 position)
{
Vector2 vector = HandleUtility.WorldToGUIPoint(position);
float fixedWidth = style.fixedWidth;
float fixedHeight = style.fixedHeight;
return new Rect(vector.x - fixedWidth / 2f, vector.y - fixedHeight / 2f, fixedWidth, fixedHeight);
}
public static void DrawRect(Rect rect, Vector3 position, Quaternion rotation, Color color, float rectAlpha, float outlineAlpha)
{
if (Event.current.type != EventType.Repaint)
return;
Vector3[] corners = new Vector3[4];
for (int i = 0; i < 4; i++)
{
Vector3 point = GetLocalRectPoint(rect, i);
corners[i] = rotation * point + position;
}
Vector3[] points = new Vector3[]
{
corners[0],
corners[1],
corners[2],
corners[3],
corners[0]
};
Color l_color = Handles.color;
Handles.color = color;
Vector2 offset = new Vector2(1f, 1f);
if (!Camera.current)
{
offset.y *= -1;
}
Handles.DrawSolidRectangleWithOutline(points, new Color(1f, 1f, 1f, rectAlpha), new Color(1f, 1f, 1f, outlineAlpha));
Handles.color = l_color;
}
private static Vector2 GetLocalRectPoint(Rect rect, int index)
{
switch (index)
{
case (0): return new Vector2(rect.xMin, rect.yMax);
case (1): return new Vector2(rect.xMax, rect.yMax);
case (2): return new Vector2(rect.xMax, rect.yMin);
case (3): return new Vector2(rect.xMin, rect.yMin);
}
return Vector3.zero;
}
private static void SetDiscSectionPoints(Vector3[] dest, int count, Vector3 normal, Vector3 from, float angle)
{
from.Normalize();
Quaternion rotation = Quaternion.AngleAxis(angle / (float)(count - 1), normal);
Vector3 vector = from;
for (int i = 0; i < count; i++)
{
dest[i] = vector;
vector = rotation * vector;
}
}
static Vector3[] s_array;
public static void DrawSolidArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius, int numSamples = 60)
{
if (Event.current.type != EventType.Repaint)
return;
numSamples = Mathf.Clamp(numSamples, 3, 60);
if (s_array == null)
s_array = new Vector3[60];
Color color = Handles.color;
SetDiscSectionPoints(s_array, numSamples, normal, from, angle);
InternalEditorBridge.ApplyWireMaterial();
GL.PushMatrix();
GL.MultMatrix(Handles.matrix);
GL.Begin(GL.TRIANGLES);
for (int i = 1; i < numSamples; i++)
{
GL.Color(color);
GL.Vertex(center);
GL.Vertex(center + s_array[i - 1] * radius);
GL.Vertex(center + s_array[i] * radius);
}
GL.End();
GL.PopMatrix();
}
public static void DrawSolidArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius, float outlineScale, int numSamples = 60)
{
if (Event.current.type != EventType.Repaint)
return;
numSamples = Mathf.Clamp(numSamples, 3, 60);
if(s_array == null)
s_array = new Vector3[60];
Color color = Handles.color;
SetDiscSectionPoints(s_array, numSamples, normal, from, angle);
InternalEditorBridge.ApplyWireMaterial();
GL.PushMatrix();
GL.MultMatrix(Handles.matrix);
GL.Begin(4);
for (int i = 1; i < numSamples; i++)
{
GL.Color(color);
GL.Vertex(center + s_array[i - 1] * radius * outlineScale);
GL.Vertex(center + s_array[i - 1] * radius);
GL.Vertex(center + s_array[i] * radius);
GL.Vertex(center + s_array[i - 1] * radius * outlineScale);
GL.Vertex(center + s_array[i] * radius);
GL.Vertex(center + s_array[i] * radius * outlineScale);
}
GL.End();
GL.PopMatrix();
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using UnityEditor.U2D.Layout;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class GenerateGeometryTool : MeshToolWrapper
{
private const float kWeightTolerance = 0.1f;
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private ITriangulator m_Triangulator;
private IOutlineGenerator m_OutlineGenerator;
private IWeightsGenerator m_WeightGenerator;
private GenerateGeometryPanel m_GenerateGeometryPanel;
internal override void OnCreate()
{
m_Triangulator = new Triangulator();
m_OutlineGenerator = new OutlineGenerator();
m_WeightGenerator = new BoundedBiharmonicWeightsGenerator();
}
public override void Initialize(LayoutOverlay layout)
{
base.Initialize(layout);
m_GenerateGeometryPanel = GenerateGeometryPanel.GenerateFromUXML();
m_GenerateGeometryPanel.skinningCache = skinningCache;
layout.rightOverlay.Add(m_GenerateGeometryPanel);
BindElements();
Hide();
}
private void BindElements()
{
Debug.Assert(m_GenerateGeometryPanel != null);
m_GenerateGeometryPanel.onAutoGenerateGeometry += (float d, byte a, float s) =>
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite != null)
{
EditorUtility.DisplayProgressBar(TextContent.generatingGeometry, selectedSprite.name, 0f);
using (skinningCache.UndoScope(TextContent.generateGeometry))
{
GenerateGeometry(selectedSprite, d / 100f, a, s);
if (m_GenerateGeometryPanel.generateWeights)
{
EditorUtility.DisplayProgressBar(TextContent.generatingWeights, selectedSprite.name, 1f);
GenerateWeights(selectedSprite);
}
skinningCache.vertexSelection.Clear();
skinningCache.events.meshChanged.Invoke(selectedSprite.GetMesh());
}
EditorUtility.ClearProgressBar();
}
};
m_GenerateGeometryPanel.onAutoGenerateGeometryAll += (float d, byte a, float s) =>
{
var sprites = skinningCache.GetSprites();
using (skinningCache.UndoScope(TextContent.generateGeometry))
{
for (var i = 0; i < sprites.Length; ++i)
{
var sprite = sprites[i];
if (!sprite.IsVisible())
continue;
EditorUtility.DisplayProgressBar(TextContent.generateGeometry, sprite.name, i * 2f / (sprites.Length * 2f));
GenerateGeometry(sprite, d / 100f, a, s);
if (m_GenerateGeometryPanel.generateWeights)
{
EditorUtility.DisplayProgressBar(TextContent.generatingWeights, sprite.name, (i * 2f + 1) / (sprites.Length * 2f));
GenerateWeights(sprite);
}
}
foreach(var sprite in sprites)
skinningCache.events.meshChanged.Invoke(sprite.GetMesh());
EditorUtility.ClearProgressBar();
}
};
}
protected override void OnActivate()
{
base.OnActivate();
UpdateButton();
Show();
skinningCache.events.selectedSpriteChanged.AddListener(OnSelectedSpriteChanged);
}
protected override void OnDeactivate()
{
base.OnDeactivate();
Hide();
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSelectedSpriteChanged);
}
private void Show()
{
m_GenerateGeometryPanel.SetHiddenFromLayout(false);
}
private void Hide()
{
m_GenerateGeometryPanel.SetHiddenFromLayout(true);
}
private void UpdateButton()
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite == null)
m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Multiple);
else
m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Single);
}
private void OnSelectedSpriteChanged(SpriteCache sprite)
{
UpdateButton();
}
private void GenerateGeometry(SpriteCache sprite, float outlineDetail, byte alphaTolerance, float subdivide)
{
Debug.Assert(sprite != null);
var mesh = sprite.GetMesh();
Debug.Assert(mesh != null);
m_SpriteMeshDataController.spriteMeshData = mesh;
m_SpriteMeshDataController.OutlineFromAlpha(m_OutlineGenerator, mesh.textureDataProvider, outlineDetail, alphaTolerance);
m_SpriteMeshDataController.Triangulate(m_Triangulator);
if (subdivide > 0f)
{
var largestAreaFactor = Mathf.Lerp(0.5f, 0.05f, Math.Min(subdivide, 100f) / 100f);
m_SpriteMeshDataController.Subdivide(m_Triangulator, largestAreaFactor);
}
foreach (var vertex in mesh.vertices)
vertex.position -= sprite.textureRect.position;
}
private void GenerateWeights(SpriteCache sprite)
{
Debug.Assert(sprite != null);
var mesh = sprite.GetMesh();
Debug.Assert(mesh != null);
using (new DefaultPoseScope(skinningCache.GetEffectiveSkeleton(sprite)))
{
if (NeedsAssociateBones(sprite.GetCharacterPart()))
{
using (new AssociateBonesScope(sprite))
{
GenerateWeights(mesh);
}
}
else
GenerateWeights(mesh);
}
}
private bool NeedsAssociateBones(CharacterPartCache characterPart)
{
if (characterPart == null)
return false;
var skeleton = characterPart.skinningCache.character.skeleton;
return characterPart.BoneCount == 0 ||
(characterPart.BoneCount == 1 && characterPart.GetBone(0) == skeleton.GetBone(0));
}
private void GenerateWeights(MeshCache mesh)
{
Debug.Assert(mesh != null);
m_SpriteMeshDataController.spriteMeshData = mesh;
m_SpriteMeshDataController.CalculateWeights(m_WeightGenerator, null, kWeightTolerance);
m_SpriteMeshDataController.SortTrianglesByDepth();
}
protected override void OnGUI()
{
m_MeshPreviewBehaviour.showWeightMap = m_GenerateGeometryPanel.generateWeights;
m_MeshPreviewBehaviour.overlaySelected = m_GenerateGeometryPanel.generateWeights;
skeletonTool.skeletonStyle = SkeletonStyles.Default;
if (m_GenerateGeometryPanel.generateWeights)
skeletonTool.skeletonStyle = SkeletonStyles.WeightMap;
DoSkeletonGUI();
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using UnityEditor.U2D.Layout;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class GenerateWeightsTool : MeshToolWrapper
{
private const float kWeightTolerance = 0.01f;
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private IWeightsGenerator m_WeightGenerator;
private GenerateWeightsPanel m_GenerateWeightsPanel;
internal override void OnCreate()
{
m_WeightGenerator = new BoundedBiharmonicWeightsGenerator();
}
public override void Initialize(LayoutOverlay layout)
{
base.Initialize(layout);
m_GenerateWeightsPanel = GenerateWeightsPanel.GenerateFromUXML();
layout.rightOverlay.Add(m_GenerateWeightsPanel);
BindElements();
m_GenerateWeightsPanel.SetHiddenFromLayout(true);
}
private void BindElements()
{
Debug.Assert(m_GenerateWeightsPanel != null);
m_GenerateWeightsPanel.onGenerateWeights += () =>
{
HandleWeights(GenerateWeights, TextContent.generateWeights);
};
m_GenerateWeightsPanel.onNormalizeWeights += () =>
{
HandleWeights(NormalizeWeights, TextContent.normalizeWeights);
};
m_GenerateWeightsPanel.onClearWeights += () =>
{
HandleWeights(ClearWeights, TextContent.clearWeights);
};
}
protected override void OnActivate()
{
base.OnActivate();
m_GenerateWeightsPanel.SetHiddenFromLayout(false);
skinningCache.events.skinningModeChanged.AddListener(OnModeChanged);
skinningCache.events.selectedSpriteChanged.AddListener(OnSpriteSelectionChanged);
m_GenerateWeightsPanel.Update(skinningCache.mode == SkinningMode.Character);
OnSpriteSelectionChanged(skinningCache.selectedSprite);
}
protected override void OnDeactivate()
{
base.OnDeactivate();
skinningCache.events.skinningModeChanged.RemoveListener(OnModeChanged);
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSpriteSelectionChanged);
m_GenerateWeightsPanel.SetHiddenFromLayout(true);
}
void OnModeChanged(SkinningMode mode)
{
m_GenerateWeightsPanel.Update(mode == SkinningMode.Character);
}
void OnSpriteSelectionChanged(SpriteCache sprite)
{
m_GenerateWeightsPanel.generateButtonText = sprite != null ? TextContent.generate : TextContent.generateAll;
}
private void HandleWeights(Action<SpriteCache> action, string undoName)
{
using (skinningCache.UndoScope(undoName))
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite != null)
HandleWeightsForSprite(selectedSprite, action);
else
{
var sprites = skinningCache.GetSprites();
foreach (var sprite in sprites)
{
if (sprite.IsVisible())
HandleWeightsForSprite(sprite, action);
}
}
}
}
private void HandleWeightsForSprite(SpriteCache sprite, Action<SpriteCache> action)
{
Debug.Assert(sprite != null);
using (new DefaultPoseScope(skinningCache.GetEffectiveSkeleton(sprite)))
{
action(sprite);
}
skinningCache.events.meshChanged.Invoke(sprite.GetMesh());
}
private void GenerateWeights(SpriteCache sprite)
{
using (m_GenerateWeightsPanel.associateBones ? new AssociateBonesScope(sprite) : null)
{
m_SpriteMeshDataController.spriteMeshData = sprite.GetMesh();
m_SpriteMeshDataController.CalculateWeights(m_WeightGenerator, skinningCache.vertexSelection, kWeightTolerance);
m_SpriteMeshDataController.SortTrianglesByDepth();
}
}
private void NormalizeWeights(SpriteCache sprite)
{
m_SpriteMeshDataController.spriteMeshData = sprite.GetMesh();
m_SpriteMeshDataController.NormalizeWeights(skinningCache.vertexSelection);
m_SpriteMeshDataController.SortTrianglesByDepth();
}
private void ClearWeights(SpriteCache sprite)
{
m_SpriteMeshDataController.spriteMeshData = sprite.GetMesh();
m_SpriteMeshDataController.ClearWeights(skinningCache.vertexSelection);
}
protected override void OnGUI()
{
m_MeshPreviewBehaviour.showWeightMap = true;
m_MeshPreviewBehaviour.overlaySelected = true;
skeletonMode = SkeletonMode.EditPose;
meshMode = SpriteMeshViewMode.EditGeometry;
disableMeshEditor = true;
skeletonTool.skeletonStyle = SkeletonStyles.WeightMap;
DoSkeletonGUI();
DoMeshGUI();
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class HorizontalToggleTools
{
private static class Styles
{
public static GUIContent visibilityCollapseIcon = new GUIContent(IconUtility.LoadIconResource("Visibility_Tool", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.visibilityIconTooltip));
public static GUIContent visibilityIcon = new GUIContent(L10n.Tr(TextContent.visibilityIconText), IconUtility.LoadIconResource("Visibility_Tool", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.visibilityIconTooltip));
public static GUIContent characterCollapseIcon = new GUIContent(IconUtility.LoadIconResource("character_Mode", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.restorePose));
public static GUIContent characterIcon = new GUIContent(L10n.Tr(TextContent.characterIconText), IconUtility.LoadIconResource("character_Mode", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.restorePose));
public static GUIContent spriteSheetIcon = new GUIContent(L10n.Tr(TextContent.spriteSheetIconText), IconUtility.LoadIconResource("Sprite_Mode", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.spriteSheetIconTooltip));
public static GUIContent spriteSheetCollapseIcon = new GUIContent(IconUtility.LoadIconResource("Sprite_Mode", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.spriteSheetIconTooltip));
public static GUIContent copyIcon = new GUIContent(L10n.Tr(TextContent.copyText), IconUtility.LoadIconResource("Copy", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.copyTooltip));
public static GUIContent copyCollapseIcon = new GUIContent(IconUtility.LoadIconResource("Copy", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.copyTooltip));
public static GUIContent pasteIcon = new GUIContent(L10n.Tr(TextContent.pasteText), IconUtility.LoadIconResource("Paste", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.pasteTooltip));
public static GUIContent pasteCollapseIcon = new GUIContent(IconUtility.LoadIconResource("Paste", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath), L10n.Tr(TextContent.pasteTooltip));
}
private SkinningCache skinningCache { get; set; }
private CopyTool copyTool
{
get { return skinningCache.GetTool(Tools.CopyPaste) as CopyTool; }
}
private VisibilityTool visibilityTool
{
get { return skinningCache.GetTool(Tools.Visibility) as VisibilityTool; }
}
private SwitchModeTool switchmodeTool
{
get { return skinningCache.GetTool(Tools.SwitchMode) as SwitchModeTool; }
}
private GUIContent spriteSheetIcon
{
get { return collapseToolbar ? Styles.spriteSheetCollapseIcon : Styles.spriteSheetIcon; }
}
private GUIContent copyIcon
{
get { return collapseToolbar ? Styles.copyCollapseIcon : Styles.copyIcon; }
}
private GUIContent pasteIcon
{
get { return collapseToolbar ? Styles.pasteCollapseIcon : Styles.pasteIcon; }
}
internal Action<BaseTool> onActivateTool = (b) => {};
private BaseTool m_PreviousTool;
public bool collapseToolbar { get; set; }
internal HorizontalToggleTools(SkinningCache s)
{
skinningCache = s;
}
internal void DoGUI(Rect drawArea, BaseTool currentTool, bool isDisabled)
{
using (new EditorGUI.DisabledScope(isDisabled))
{
GUILayout.BeginArea(drawArea);
EditorGUILayout.BeginHorizontal();
DoPreviewToggle();
DoModeToggle();
DoCopyToggle(currentTool);
GUILayout.FlexibleSpace();
DoVisibilityToggle(currentTool);
EditorGUILayout.EndHorizontal();
GUILayout.EndArea();
}
}
private void StorePreviousTool(BaseTool currentTool)
{
if(currentTool != copyTool && currentTool != visibilityTool)
m_PreviousTool = currentTool;
}
private void DoModeToggle()
{
if (skinningCache.hasCharacter)
{
EditorGUI.BeginChangeCheck();
var isActive = GUILayout.Toggle(switchmodeTool.isActive , spriteSheetIcon, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
{
using (skinningCache.UndoScope(TextContent.setMode))
{
if (isActive)
switchmodeTool.Activate();
else
switchmodeTool.Deactivate();
}
}
}
}
private void DoCopyToggle(BaseTool currentTool)
{
if (GUILayout.Button(copyIcon, EditorStyles.toolbarButton))
{
copyTool.OnCopyActivated();
}
EditorGUI.BeginChangeCheck();
GUILayout.Toggle(copyTool.isActive, pasteIcon, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
TogglePasteTool(currentTool);
}
internal void TogglePasteTool(BaseTool currentTool)
{
if (!copyTool.isActive)
{
onActivateTool(copyTool);
StorePreviousTool(currentTool);
}
else if (m_PreviousTool != null)
{
onActivateTool(m_PreviousTool);
}
}
void DoVisibilityToggle(BaseTool currentTool)
{
EditorGUI.BeginChangeCheck();
GUILayout.Toggle(visibilityTool.isActive, visbilityIcon, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
ToggleVisibilityTool(currentTool);
}
GUIContent visbilityIcon { get { return collapseToolbar ? Styles.visibilityCollapseIcon : Styles.visibilityIcon; } }
internal void ToggleVisibilityTool(BaseTool currentTool)
{
onActivateTool(visibilityTool);
}
private void DoPreviewToggle()
{
var skeleton = skinningCache.GetEffectiveSkeleton(skinningCache.selectedSprite);
EditorGUI.BeginDisabledGroup(skeleton == null || skeleton.isPosePreview == false);
EditorGUI.BeginChangeCheck();
GUILayout.Button(characterIcon, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
{
using (skinningCache.UndoScope("Restore Pose"))
{
skinningCache.RestoreBindPose();
skinningCache.events.restoreBindPose.Invoke();
}
}
EditorGUI.EndDisabledGroup();
}
GUIContent characterIcon { get { return collapseToolbar ? Styles.characterCollapseIcon : Styles.characterIcon; } }
}
}

View File

@@ -0,0 +1,127 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Brush
{
private static readonly float kWheelSizeSpeed = 1f;
private static readonly int kBrushHashCode = "Brush".GetHashCode();
private IGUIWrapper m_GUIWrapper;
private float m_DeltaAcc = 0f;
private int m_ControlID = -1;
private SliderData m_SliderData = SliderData.zero;
public event Action<Brush> onMove = (b) => {};
public event Action<Brush> onSize = (b) => {};
public event Action<Brush> onRepaint = (b) => {};
public event Action<Brush> onStrokeBegin = (b) => {};
public event Action<Brush> onStrokeDelta = (b) => {};
public event Action<Brush> onStrokeStep = (b) => {};
public event Action<Brush> onStrokeEnd = (b) => {};
public bool isHot
{
get { return m_GUIWrapper.IsControlHot(m_ControlID); }
}
public bool isActivable
{
get { return m_GUIWrapper.IsControlHot(0) && m_GUIWrapper.IsControlNearest(m_ControlID); }
}
public int controlID
{
get { return m_ControlID; }
}
public float hardness { get; set; }
public float step { get; set; }
public float size { get; set; }
public Vector3 position
{
get { return m_SliderData.position; }
}
public Brush(IGUIWrapper guiWrapper)
{
m_GUIWrapper = guiWrapper;
size = 25f;
step = 20f;
}
public void OnGUI()
{
m_ControlID = m_GUIWrapper.GetControlID(kBrushHashCode, FocusType.Passive);
var eventType = m_GUIWrapper.eventType;
if (!m_GUIWrapper.isAltDown)
m_GUIWrapper.LayoutControl(controlID, 0f);
if (isActivable)
{
m_SliderData.position = m_GUIWrapper.GUIToWorld(m_GUIWrapper.mousePosition);
if (m_GUIWrapper.IsMouseDown(0))
{
m_DeltaAcc = 0f;
onStrokeBegin(this);
onStrokeStep(this);
m_GUIWrapper.SetGuiChanged(true);
}
if (eventType == EventType.MouseMove)
{
onMove(this);
m_GUIWrapper.UseCurrentEvent();
}
if (m_GUIWrapper.isShiftDown && eventType == EventType.ScrollWheel)
{
var sizeDelta = HandleUtility.niceMouseDeltaZoom * kWheelSizeSpeed;
size = Mathf.Max(1f, size + sizeDelta);
onSize(this);
m_GUIWrapper.UseCurrentEvent();
}
}
if (isHot && m_GUIWrapper.IsMouseUp(0))
onStrokeEnd(this);
if (m_GUIWrapper.IsRepainting() && (isHot || isActivable))
onRepaint(this);
Vector3 position;
if (m_GUIWrapper.DoSlider(m_ControlID, m_SliderData, out position))
{
step = Mathf.Max(step, 1f);
var delta = position - m_SliderData.position;
var direction = delta.normalized;
var magnitude = delta.magnitude;
m_SliderData.position -= direction * m_DeltaAcc;
m_DeltaAcc += magnitude;
if (m_DeltaAcc >= step)
{
var stepVector = direction * step;
while (m_DeltaAcc >= step)
{
m_SliderData.position += stepVector;
onMove(this);
onStrokeStep(this);
m_DeltaAcc -= step;
}
}
m_SliderData.position = position;
onStrokeDelta(this);
}
}
}
}

View File

@@ -0,0 +1,230 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal struct SliderData
{
public Vector3 position;
public Vector3 forward;
public Vector3 up;
public Vector3 right;
public static readonly SliderData zero = new SliderData() { position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
}
internal interface IGUIWrapper
{
Vector2 mousePosition { get; }
int mouseButton { get; }
int clickCount { get; }
bool isShiftDown { get; }
bool isAltDown { get; }
bool isActionKeyDown { get; }
EventType eventType { get; }
string commandName { get; }
bool IsMouseDown(int button);
bool IsMouseUp(int button);
bool IsKeyDown(KeyCode keyCode);
int GetControlID(int hint, FocusType focusType);
void LayoutControl(int controlID, float distance);
bool IsControlNearest(int controlID);
bool IsControlHot(int controlID);
bool IsMultiStepControlHot(int controlID);
void SetControlHot(int controlID);
void SetMultiStepControlHot(int controlID);
bool DoSlider(int id, SliderData sliderData, out Vector3 newPosition);
void UseCurrentEvent();
float DistanceToSegment(Vector3 p1, Vector3 p2);
float DistanceToSegmentClamp(Vector3 p1, Vector3 p2);
float DistanceToCircle(Vector3 center, float radius);
Vector3 GUIToWorld(Vector2 guiPosition);
Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePosition);
void Repaint();
bool IsRepainting();
bool IsEventOutsideWindow();
void SetGuiChanged(bool changed);
float GetHandleSize(Vector3 position);
bool IsViewToolActive();
bool HasCurrentCamera();
}
internal class GUIWrapper : IGUIWrapper
{
private Handles.CapFunction nullCap = (int c, Vector3 p , Quaternion r, float s, EventType ev) => {};
private int m_MultiStepHotControl = 0;
public Vector2 mousePosition
{
get { return Event.current.mousePosition; }
}
public int mouseButton
{
get { return Event.current.button; }
}
public int clickCount
{
get { return Event.current.clickCount; }
}
public bool isShiftDown
{
get { return Event.current.shift; }
}
public bool isAltDown
{
get { return Event.current.alt; }
}
public bool isActionKeyDown
{
get { return EditorGUI.actionKey; }
}
public EventType eventType
{
get { return Event.current.type; }
}
public string commandName
{
get { return Event.current.commandName; }
}
public bool IsMouseDown(int button)
{
return Event.current.type == EventType.MouseDown && Event.current.button == button;
}
public bool IsMouseUp(int button)
{
return Event.current.type == EventType.MouseUp && Event.current.button == button;
}
public bool IsKeyDown(KeyCode keyCode)
{
return Event.current.type == EventType.KeyDown && Event.current.keyCode == keyCode;
}
public int GetControlID(int hint, FocusType focusType)
{
return GUIUtility.GetControlID(hint, focusType);
}
public void LayoutControl(int controlID, float distance)
{
if (Event.current.type == EventType.Layout)
HandleUtility.AddControl(controlID, distance);
}
public bool IsControlNearest(int controlID)
{
return HandleUtility.nearestControl == controlID;
}
public bool IsControlHot(int controlID)
{
return GUIUtility.hotControl == controlID;
}
public bool IsMultiStepControlHot(int controlID)
{
return m_MultiStepHotControl == controlID;
}
public void SetControlHot(int controlID)
{
GUIUtility.hotControl = controlID;
}
public void SetMultiStepControlHot(int controlID)
{
m_MultiStepHotControl = controlID;
}
public bool DoSlider(int id, SliderData sliderData, out Vector3 newPosition)
{
EditorGUI.BeginChangeCheck();
if (HasCurrentCamera())
newPosition = Handles.Slider2D(id, sliderData.position, sliderData.forward, sliderData.right, sliderData.up, 1f, nullCap, Vector2.zero);
else
newPosition = Slider2D.Do(id, sliderData.position, null);
return EditorGUI.EndChangeCheck();
}
public void UseCurrentEvent()
{
Event.current.Use();
}
public float DistanceToSegment(Vector3 p1, Vector3 p2)
{
p1 = HandleUtility.WorldToGUIPoint(p1);
p2 = HandleUtility.WorldToGUIPoint(p2);
return HandleUtility.DistancePointToLineSegment(mousePosition, p1, p2);
}
public float DistanceToSegmentClamp(Vector3 p1, Vector3 p2)
{
p1 = HandleUtility.WorldToGUIPoint(p1);
p2 = HandleUtility.WorldToGUIPoint(p2);
return MathUtility.DistanceToSegmentClamp(mousePosition, p1, p2);
}
public float DistanceToCircle(Vector3 center, float radius)
{
return HandleUtility.DistanceToCircle(center, radius);
}
public Vector3 GUIToWorld(Vector2 guiPosition)
{
return ModuleUtility.GUIToWorld(guiPosition);
}
public Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePosition)
{
return ModuleUtility.GUIToWorld(guiPosition, planeNormal, planePosition);
}
public void Repaint()
{
HandleUtility.Repaint();
}
public bool IsRepainting()
{
return eventType == EventType.Repaint;
}
public void SetGuiChanged(bool changed)
{
GUI.changed = true;
}
public bool IsEventOutsideWindow()
{
return Event.current.type == EventType.Ignore;
}
public float GetHandleSize(Vector3 position)
{
return HandleUtility.GetHandleSize(position);
}
public bool IsViewToolActive()
{
return UnityEditor.Tools.current == Tool.View || isAltDown || mouseButton == 1 || mouseButton == 2;
}
public bool HasCurrentCamera()
{
return Camera.current != null;
}
}
}

View File

@@ -0,0 +1,67 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal enum SkeletonAction
{
None = 0,
Select = 1 << 0,
RotateBone = 1 << 2,
MoveBone = 1 << 3,
FreeMoveBone = 1 << 4,
MoveEndPosition = 1 << 5,
MoveJoint = 1 << 6,
ChangeLength = 1 << 7,
CreateBone = 1 << 8,
SplitBone = 1 << 9,
Remove = 1 << 10,
}
internal enum SkeletonMode
{
Disabled = SkeletonAction.None,
Selection = SkeletonAction.Select,
EditPose = Selection | SkeletonAction.RotateBone | SkeletonAction.MoveBone,
EditJoints = Selection | SkeletonAction.FreeMoveBone | SkeletonAction.MoveEndPosition | SkeletonAction.MoveJoint | SkeletonAction.Remove,
CreateBone = Selection | SkeletonAction.MoveJoint | SkeletonAction.Remove | SkeletonAction.CreateBone,
SplitBone = Selection | SkeletonAction.MoveEndPosition | SkeletonAction.MoveJoint | SkeletonAction.Remove | SkeletonAction.SplitBone,
}
internal interface ISkeletonView
{
int InvalidID { get; set; }
SkeletonMode mode { get; set; }
int defaultControlID { get; set; }
int hoveredBoneID { get; }
int hoveredJointID { get; }
int hoveredBodyID { get; }
int hoveredTailID { get; }
int hotBoneID { get; }
void BeginLayout();
void EndLayout();
bool CanLayout();
Vector3 GetMouseWorldPosition(Vector3 planeNormal, Vector3 planePosition);
void LayoutBone(int id, Vector3 position, Vector3 endPosition, Vector3 forward, Vector3 up, Vector3 right, bool isChainEnd);
bool DoSelectBone(out int id, out bool additive);
bool DoRotateBone(Vector3 pivot, Vector3 normal, out float deltaAngle);
bool DoMoveBone(out Vector3 deltaPosition);
bool DoFreeMoveBone(out Vector3 deltaPosition);
bool DoMoveJoint(out Vector3 deltaPosition);
bool DoMoveEndPosition(out Vector3 endPosition);
bool DoChangeLength(out Vector3 endPosition);
bool DoCreateBoneStart(out Vector3 position);
bool DoCreateBone(out Vector3 position);
bool DoSplitBone(out int id, out Vector3 position);
bool DoRemoveBone();
bool DoCancelMultistepAction(bool force);
bool IsActionActive(SkeletonAction action);
bool IsActionHot(SkeletonAction action);
bool IsActionTriggering(SkeletonAction action);
bool IsActionFinishing(SkeletonAction action);
bool IsRepainting();
void DrawBone(Vector3 position, Vector3 right, Vector3 forward, float length, Color color, bool isChained, bool isSelected, bool isJointHovered, bool isTailHovered, bool isHot);
void DrawBoneParentLink(Vector3 parentPosition, Vector3 position, Vector3 forward, Color color);
void DrawBoneOutline(Vector3 position, Vector3 right, Vector3 forward, float length, Color color, float outlineScale);
void DrawCursors(bool canBeActive);
}
}

View File

@@ -0,0 +1,66 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal enum SpriteMeshViewMode
{
EditGeometry,
CreateVertex,
CreateEdge,
SplitEdge
}
internal enum MeshEditorAction
{
None,
CreateVertex,
MoveVertex,
CreateEdge,
SplitEdge,
MoveEdge,
SelectVertex,
SelectEdge,
Remove
}
internal interface ISpriteMeshView
{
SpriteMeshViewMode mode { get; set; }
ISelection<int> selection { get; set; }
int defaultControlID { get; set; }
Rect frame { get; set; }
Vector2 mouseWorldPosition { get; }
int hoveredVertex { get; }
int hoveredEdge { get; }
int closestEdge { get; }
void CancelMode();
void BeginLayout();
void EndLayout();
void LayoutVertex(Vector2 position, int index);
void LayoutEdge(Vector2 startPosition, Vector2 endPosition, int index);
bool DoCreateVertex();
bool DoSelectVertex(out bool additive);
bool DoMoveVertex(out Vector2 delta);
bool DoMoveEdge(out Vector2 delta);
bool DoCreateEdge();
bool DoSplitEdge();
bool DoSelectEdge(out bool additive);
bool DoRemove();
void DrawVertex(Vector2 position);
void DrawVertexHovered(Vector2 position);
void DrawVertexSelected(Vector2 position);
void BeginDrawEdges();
void EndDrawEdges();
void DrawEdge(Vector2 startPosition, Vector2 endPosition);
void DrawEdgeHovered(Vector2 startPosition, Vector2 endPosition);
void DrawEdgeSelected(Vector2 startPosition, Vector2 endPosition);
bool IsActionTriggered(MeshEditorAction action);
bool IsActionActive(MeshEditorAction action);
bool IsActionHot(MeshEditorAction action);
Vector2 WorldToScreen(Vector2 position);
void DoRepaint();
bool CanRepaint();
bool CanLayout();
}
}

View File

@@ -0,0 +1,70 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class RectSelectionTool<T>
{
private int m_HashCode = "RectSelectionTool".GetHashCode();
private int m_ControlID = -1;
private bool m_Moved = false;
private RectSlider m_RectSlider = new RectSlider();
public int controlID { get { return m_ControlID; } }
public IRectSelector<T> rectSelector { get; set; }
public ICacheUndo cacheUndo { get; set; }
public Action onSelectionStart = () => {};
public Action onSelectionUpdate = () => {};
public Action onSelectionEnd = () => {};
public void OnGUI()
{
Debug.Assert(rectSelector != null);
Debug.Assert(cacheUndo != null);
m_ControlID = GUIUtility.GetControlID(m_HashCode, FocusType.Passive);
Event ev = Event.current;
EventType eventType = ev.GetTypeForControl(m_ControlID);
if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == m_ControlID &&
rectSelector.selection.Count > 0 && eventType == EventType.MouseDown && ev.button == 0 && !ev.alt)
{
m_Moved = false;
onSelectionStart();
}
if (m_Moved && GUIUtility.hotControl == m_ControlID && eventType == EventType.MouseUp && ev.button == 0)
{
cacheUndo.BeginUndoOperation(TextContent.selection);
rectSelector.selection.EndSelection(true);
onSelectionEnd();
}
EditorGUI.BeginChangeCheck();
rectSelector.rect = m_RectSlider.Do(m_ControlID);
if (EditorGUI.EndChangeCheck())
{
if(!m_Moved)
{
cacheUndo.BeginUndoOperation(TextContent.selection);
if(!ev.shift)
rectSelector.selection.Clear();
m_Moved = true;
}
rectSelector.selection.BeginSelection();
rectSelector.Select();
onSelectionUpdate();
}
if (eventType == EventType.Repaint && GUIUtility.hotControl == m_ControlID)
{
DrawingUtility.DrawRect(rectSelector.rect, Vector3.zero, Quaternion.identity, new Color(0f, 1f, 1f, 1f), 0.05f, 0.8f);
}
}
}
}

View File

@@ -0,0 +1,38 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class RectSlider
{
private static readonly int kRectSliderHashCode = "RectSlider".GetHashCode();
private Vector2 m_StartPosition = Vector2.zero;
private Vector2 m_Position = Vector2.zero;
internal Rect Do()
{
return Do(GUIUtility.GetControlID(kRectSliderHashCode, FocusType.Passive));
}
internal Rect Do(int controlID)
{
var eventType = Event.current.GetTypeForControl(controlID);
if (eventType == EventType.MouseDown)
{
m_StartPosition = ModuleUtility.GUIToWorld(Event.current.mousePosition);
m_Position = m_StartPosition;
}
if (eventType == EventType.Layout)
HandleUtility.AddDefaultControl(controlID);
m_Position = Slider2D.Do(controlID, m_Position);
var rect = new Rect();
rect.min = m_StartPosition;
rect.max = m_Position;
return rect;
}
}
}

View File

@@ -0,0 +1,644 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class SkeletonController
{
private static readonly string k_DefaultRootName = "root";
private static readonly string k_DefaultBoneName = "bone";
private static Regex s_Regex = new Regex(@"\w+_\d+$", RegexOptions.IgnoreCase);
private SkeletonCache m_Skeleton;
[SerializeField]
private Vector3 m_CreateBoneStartPosition;
[SerializeField]
private BoneCache m_PrevCreatedBone;
private bool m_Moved = false;
private ISkeletonStyle style
{
get
{
if (styleOverride != null)
return styleOverride;
return SkeletonStyles.Default;
}
}
private SkinningCache skinningCache
{
get { return m_Skeleton.skinningCache; }
}
private BoneCache selectedBone
{
get { return selection.activeElement.ToSpriteSheetIfNeeded(); }
set { selection.activeElement = value.ToCharacterIfNeeded(); }
}
private BoneCache[] selectedBones
{
get { return selection.elements.ToSpriteSheetIfNeeded(); }
set { selection.elements = value.ToCharacterIfNeeded(); }
}
private BoneCache rootBone
{
get { return selection.root.ToSpriteSheetIfNeeded(); }
}
private BoneCache[] rootBones
{
get { return selection.roots.ToSpriteSheetIfNeeded(); }
}
public ISkeletonView view { get; set; }
public ISkeletonStyle styleOverride { get; set; }
public IBoneSelection selection { get; set; }
public bool editBindPose { get; set; }
public SkeletonCache skeleton
{
get { return m_Skeleton; }
set { SetSkeleton(value); }
}
public BoneCache hoveredBone
{
get { return GetBone(view.hoveredBoneID); }
}
public BoneCache hoveredTail
{
get { return GetBone(view.hoveredTailID); }
}
public BoneCache hoveredBody
{
get { return GetBone(view.hoveredBodyID); }
}
public BoneCache hoveredJoint
{
get { return GetBone(view.hoveredJointID); }
}
public BoneCache hotBone
{
get { return GetBone(view.hotBoneID); }
}
private BoneCache GetBone(int instanceID)
{
return BaseObject.InstanceIDToObject(instanceID) as BoneCache;
}
private void SetSkeleton(SkeletonCache newSkeleton)
{
if (skeleton != newSkeleton)
{
m_Skeleton = newSkeleton;
Reset();
}
}
public void Reset()
{
view.DoCancelMultistepAction(true);
}
public void OnGUI()
{
if (skeleton == null)
return;
view.BeginLayout();
if (view.CanLayout())
LayoutBones();
view.EndLayout();
HandleSelectBone();
HandleRotateBone();
HandleMoveBone();
HandleFreeMoveBone();
HandleMoveJoint();
HandleMoveEndPosition();
HandleChangeLength();
HandleCreateBone();
HandleSplitBone();
HandleRemoveBone();
HandleCancelMultiStepAction();
DrawSkeleton();
DrawSplitBonePreview();
DrawCreateBonePreview();
DrawCursors();
}
private void LayoutBones()
{
for (var i = 0; i < skeleton.BoneCount; ++i)
{
var bone = skeleton.GetBone(i);
if (bone.isVisible && bone != hotBone)
view.LayoutBone(bone.GetInstanceID(), bone.position, bone.endPosition, bone.forward, bone.up, bone.right, bone.chainedChild == null);
}
}
private void HandleSelectBone()
{
int instanceID;
bool additive;
if (view.DoSelectBone(out instanceID, out additive))
{
var bone = GetBone(instanceID).ToCharacterIfNeeded();
using (skinningCache.UndoScope(TextContent.boneSelection, true))
{
if (!additive)
{
if (!selection.Contains(bone))
selectedBone = bone;
}
else
selection.Select(bone, !selection.Contains(bone));
skinningCache.events.boneSelectionChanged.Invoke();
}
}
}
private void HandleRotateBone()
{
if (view.IsActionTriggering(SkeletonAction.RotateBone))
m_Moved = false;
var pivot = hoveredBone;
if (view.IsActionHot(SkeletonAction.RotateBone))
pivot = hotBone;
if (pivot == null)
return;
var rootBones = selection.roots.ToSpriteSheetIfNeeded();
pivot = pivot.FindRoot<BoneCache>(rootBones);
if (pivot == null)
return;
float deltaAngle;
if (view.DoRotateBone(pivot.position, pivot.forward, out deltaAngle))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.rotateBone);
m_Moved = true;
}
m_Skeleton.RotateBones(selectedBones, deltaAngle);
InvokePoseChanged();
}
}
private void HandleMoveBone()
{
if (view.IsActionTriggering(SkeletonAction.MoveBone))
m_Moved = false;
Vector3 deltaPosition;
if (view.DoMoveBone(out deltaPosition))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.moveBone);
m_Moved = true;
}
m_Skeleton.MoveBones(rootBones, deltaPosition);
InvokePoseChanged();
}
}
private void HandleFreeMoveBone()
{
if (view.IsActionTriggering(SkeletonAction.FreeMoveBone))
m_Moved = false;
Vector3 deltaPosition;
if (view.DoFreeMoveBone(out deltaPosition))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.freeMoveBone);
m_Moved = true;
}
m_Skeleton.FreeMoveBones(selectedBones, deltaPosition);
InvokePoseChanged();
}
}
private void HandleMoveJoint()
{
if (view.IsActionTriggering(SkeletonAction.MoveJoint))
m_Moved = false;
if (view.IsActionFinishing(SkeletonAction.MoveJoint))
{
if (hoveredTail != null && hoveredTail.chainedChild == null && hotBone.parent == hoveredTail)
hoveredTail.chainedChild = hotBone;
}
Vector3 deltaPosition;
if (view.DoMoveJoint(out deltaPosition))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.moveJoint);
m_Moved = true;
}
//Snap to parent endPosition
if (hoveredTail != null && hoveredTail.chainedChild == null && hotBone.parent == hoveredTail)
deltaPosition = hoveredTail.endPosition - hotBone.position;
m_Skeleton.MoveJoints(selectedBones, deltaPosition);
InvokePoseChanged();
}
}
private void HandleMoveEndPosition()
{
if (view.IsActionTriggering(SkeletonAction.MoveEndPosition))
m_Moved = false;
if (view.IsActionFinishing(SkeletonAction.MoveEndPosition))
{
if (hoveredJoint != null && hoveredJoint.parent == hotBone)
hotBone.chainedChild = hoveredJoint;
}
Vector3 endPosition;
if (view.DoMoveEndPosition(out endPosition))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.moveEndPoint);
m_Moved = true;
}
Debug.Assert(hotBone != null);
Debug.Assert(hotBone.chainedChild == null);
if (hoveredJoint != null && hoveredJoint.parent == hotBone)
endPosition = hoveredJoint.position;
m_Skeleton.SetEndPosition(hotBone, endPosition);
InvokePoseChanged();
}
}
private void HandleChangeLength()
{
if (view.IsActionTriggering(SkeletonAction.ChangeLength))
m_Moved = false;
Vector3 endPosition;
if (view.DoChangeLength(out endPosition))
{
if (!m_Moved)
{
skinningCache.BeginUndoOperation(TextContent.boneLength);
m_Moved = true;
}
Debug.Assert(hotBone != null);
var direction = (Vector3)endPosition - hotBone.position;
hotBone.length = Vector3.Dot(direction, hotBone.right);
InvokePoseChanged();
}
}
private void HandleCreateBone()
{
Vector3 position;
if (view.DoCreateBoneStart(out position))
{
m_PrevCreatedBone = null;
if (hoveredTail != null)
{
m_PrevCreatedBone = hoveredTail;
m_CreateBoneStartPosition = hoveredTail.endPosition;
}
else
{
m_CreateBoneStartPosition = position;
}
}
if (view.DoCreateBone(out position))
{
using (skinningCache.UndoScope(TextContent.createBone))
{
var isChained = m_PrevCreatedBone != null;
var parentBone = isChained ? m_PrevCreatedBone : rootBone;
if (isChained)
m_CreateBoneStartPosition = m_PrevCreatedBone.endPosition;
var name = AutoBoneName(parentBone, skeleton.bones);
var bone = m_Skeleton.CreateBone(parentBone, m_CreateBoneStartPosition, position, isChained, name);
m_PrevCreatedBone = bone;
m_CreateBoneStartPosition = bone.endPosition;
InvokeTopologyChanged();
InvokePoseChanged();
}
}
}
private void HandleSplitBone()
{
int instanceID;
Vector3 position;
if (view.DoSplitBone(out instanceID, out position))
{
using (skinningCache.UndoScope(TextContent.splitBone))
{
var boneToSplit = GetBone(instanceID);
Debug.Assert(boneToSplit != null);
var splitLength = Vector3.Dot(hoveredBone.right, position - boneToSplit.position);
var name = AutoBoneName(boneToSplit, skeleton.bones);
m_Skeleton.SplitBone(boneToSplit, splitLength, name);
InvokeTopologyChanged();
InvokePoseChanged();
}
}
}
private void HandleRemoveBone()
{
if (view.DoRemoveBone())
{
using (skinningCache.UndoScope(TextContent.removeBone))
{
m_Skeleton.DestroyBones(selectedBones);
selection.Clear();
skinningCache.events.boneSelectionChanged.Invoke();
InvokeTopologyChanged();
InvokePoseChanged();
}
}
}
private void HandleCancelMultiStepAction()
{
if (view.DoCancelMultistepAction(false))
m_PrevCreatedBone = null;
}
private void DrawSkeleton()
{
if (!view.IsRepainting())
return;
bool isNotOnVisualElement = !skinningCache.IsOnVisualElement();
if (view.IsActionActive(SkeletonAction.CreateBone) || view.IsActionHot(SkeletonAction.CreateBone))
{
if (isNotOnVisualElement)
{
var endPoint = view.GetMouseWorldPosition(Vector3.forward, Vector3.zero);
if (view.IsActionHot(SkeletonAction.CreateBone))
endPoint = m_CreateBoneStartPosition;
if (m_PrevCreatedBone == null && hoveredTail == null)
{
var root = rootBone;
if (root != null)
view.DrawBoneParentLink(root.position, endPoint, Vector3.forward, style.GetParentLinkPreviewColor(skeleton.BoneCount));
}
}
}
for (var i = 0; i < skeleton.BoneCount; ++i)
{
var bone = skeleton.GetBone(i);
if (bone.isVisible == false || bone.parentBone == null || bone.parentBone.chainedChild == bone)
continue;
view.DrawBoneParentLink(bone.parent.position, bone.position, Vector3.forward, style.GetParentLinkColor(bone));
}
for (var i = 0; i < skeleton.BoneCount; ++i)
{
var bone = skeleton.GetBone(i);
if ((view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone == bone && isNotOnVisualElement) || bone.isVisible == false)
continue;
var isSelected = selection.Contains(bone.ToCharacterIfNeeded());
var isHovered = hoveredBody == bone && view.IsActionHot(SkeletonAction.None) && isNotOnVisualElement;
DrawBoneOutline(bone, style.GetOutlineColor(bone, isSelected, isHovered), style.GetOutlineScale(isSelected));
}
for (var i = 0; i < skeleton.BoneCount; ++i)
{
var bone = skeleton.GetBone(i);
if ((view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone == bone && isNotOnVisualElement) || bone.isVisible == false)
continue;
DrawBone(bone, style.GetColor(bone));
}
}
private void DrawBone(BoneCache bone, Color color)
{
var isSelected = selection.Contains(bone.ToCharacterIfNeeded());
var isNotOnVisualElement = !skinningCache.IsOnVisualElement();
var isJointHovered = view.IsActionHot(SkeletonAction.None) && hoveredJoint == bone && isNotOnVisualElement;
var isTailHovered = view.IsActionHot(SkeletonAction.None) && hoveredTail == bone && isNotOnVisualElement;
view.DrawBone(bone.position, bone.right, Vector3.forward, bone.length, color, bone.chainedChild != null, isSelected, isJointHovered, isTailHovered, bone == hotBone);
}
private void DrawBoneOutline(BoneCache bone, Color color, float outlineScale)
{
view.DrawBoneOutline(bone.position, bone.right, Vector3.forward, bone.length, color, outlineScale);
}
private void DrawSplitBonePreview()
{
if (!view.IsRepainting())
return;
if (skinningCache.IsOnVisualElement())
return;
if (view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone != null)
{
var splitLength = Vector3.Dot(hoveredBone.right, view.GetMouseWorldPosition(hoveredBone.forward, hoveredBody.position) - hoveredBone.position);
var position = hoveredBone.position + hoveredBone.right * splitLength;
var length = hoveredBone.length - splitLength;
var isSelected = selection.Contains(hoveredBone.ToCharacterIfNeeded());
{
var color = style.GetOutlineColor(hoveredBone, false, false);
if (color.a > 0f)
view.DrawBoneOutline(hoveredBone.position, hoveredBone.right, Vector3.forward, splitLength, style.GetOutlineColor(hoveredBone, isSelected, true), style.GetOutlineScale(false));
}
{
var color = style.GetPreviewOutlineColor(skeleton.BoneCount);
if (color.a > 0f)
view.DrawBoneOutline(position, hoveredBone.right, Vector3.forward, length, style.GetPreviewOutlineColor(skeleton.BoneCount), style.GetOutlineScale(false));
}
view.DrawBone(hoveredBone.position,
hoveredBone.right,
Vector3.forward,
splitLength,
style.GetColor(hoveredBone),
hoveredBone.chainedChild != null,
false, false, false, false);
view.DrawBone(position,
hoveredBone.right,
Vector3.forward,
length,
style.GetPreviewColor(skeleton.BoneCount),
hoveredBone.chainedChild != null,
false, false, false, false);
}
}
private void DrawCreateBonePreview()
{
if (!view.IsRepainting())
return;
if (skinningCache.IsOnVisualElement())
return;
var color = style.GetPreviewColor(skeleton.BoneCount);
var outlineColor = style.GetPreviewOutlineColor(skeleton.BoneCount);
var startPosition = m_CreateBoneStartPosition;
var mousePosition = view.GetMouseWorldPosition(Vector3.forward, Vector3.zero);
if (view.IsActionActive(SkeletonAction.CreateBone))
{
startPosition = mousePosition;
if (hoveredTail != null)
startPosition = hoveredTail.endPosition;
if (outlineColor.a > 0f)
view.DrawBoneOutline(startPosition, Vector3.right, Vector3.forward, 0f, outlineColor, style.GetOutlineScale(false));
view.DrawBone(startPosition, Vector3.right, Vector3.forward, 0f, color, false, false, false, false, false);
}
if (view.IsActionHot(SkeletonAction.CreateBone))
{
var direction = (mousePosition - startPosition);
if (outlineColor.a > 0f)
view.DrawBoneOutline(startPosition, direction.normalized, Vector3.forward, direction.magnitude, outlineColor, style.GetOutlineScale(false));
view.DrawBone(startPosition, direction.normalized, Vector3.forward, direction.magnitude, color, false, false, false, false, false);
}
}
private void DrawCursors()
{
if (!view.IsRepainting())
return;
view.DrawCursors(!skinningCache.IsOnVisualElement());
}
public static string AutoBoneName(BoneCache parent, IEnumerable<BoneCache> bones)
{
string parentName = "root";
string inheritedName;
int counter;
if (parent != null)
parentName = parent.name;
DissectBoneName(parentName, out inheritedName, out counter);
int nameCounter = FindBiggestNameCounter(bones);
if (inheritedName == k_DefaultRootName)
inheritedName = k_DefaultBoneName;
return String.Format("{0}_{1}", inheritedName, ++nameCounter);
}
private static int FindBiggestNameCounter(IEnumerable<BoneCache> bones)
{
int autoNameCounter = 0;
string inheritedName;
int counter;
foreach (var bone in bones)
{
DissectBoneName(bone.name, out inheritedName, out counter);
if (counter > autoNameCounter)
autoNameCounter = counter;
}
return autoNameCounter;
}
private static void DissectBoneName(string boneName, out string inheritedName, out int counter)
{
if (IsBoneNameMatchAutoFormat(boneName))
{
var tokens = boneName.Split('_');
var lastTokenIndex = tokens.Length - 1;
var tokensWithoutLast = new string[lastTokenIndex];
Array.Copy(tokens, tokensWithoutLast, lastTokenIndex);
inheritedName = string.Join("_", tokensWithoutLast);
counter = int.Parse(tokens[lastTokenIndex]);
}
else
{
inheritedName = boneName;
counter = -1;
}
}
private static bool IsBoneNameMatchAutoFormat(string boneName)
{
return s_Regex.IsMatch(boneName);
}
private void InvokeTopologyChanged()
{
skinningCache.events.skeletonTopologyChanged.Invoke(skeleton);
}
internal void InvokePoseChanged()
{
skeleton.SetPosePreview();
if (editBindPose)
{
skeleton.SetDefaultPose();
skinningCache.events.skeletonBindPoseChanged.Invoke(skeleton);
}
else
skinningCache.events.skeletonPreviewPoseChanged.Invoke(skeleton);
}
}
}

View File

@@ -0,0 +1,598 @@
using UnityEngine;
using System;
namespace UnityEditor.U2D.Animation
{
internal class SkeletonView : ISkeletonView
{
private const float kPickingRadius = 5f;
internal const string kDeleteCommandName = "Delete";
internal const string kSoftDeleteCommandName = "SoftDelete";
private static readonly int kBodyHashCode = "Body".GetHashCode();
private static readonly int kJointHashCode = "Joint".GetHashCode();
private static readonly int kTailHashCode = "Tail".GetHashCode();
private static readonly int kCreateBoneHashCode = "CreateBone".GetHashCode();
public int InvalidID { get; set; }
public SkeletonMode mode { get; set; }
public int defaultControlID { get; set; }
public int hoveredBoneID { get { return m_HoveredBoneID; } }
public int hoveredJointID { get { return m_HoveredJointID; } }
public int hoveredBodyID { get { return m_HoveredBodyID; } }
public int hoveredTailID { get { return m_HoveredTailID; } }
public int hotBoneID { get { return m_HotBoneID; } }
private IGUIWrapper m_GUIWrapper;
private int m_RotateControlID = -1;
private int m_MoveControlID = -1;
private int m_FreeMoveControlID = -1;
private int m_MoveJointControlID = -1;
private int m_MoveEndPositionControlID = -1;
private int m_ChangeLengthControlID = -1;
private int m_CreateBoneControlID = -1;
private int m_HoveredBoneID = 0;
private int m_PrevHoveredBoneID = 0;
private int m_HoveredBodyID = 0;
private int m_HoveredJointID = 0;
private int m_HoveredTailID = 0;
private int m_HotBoneID = 0;
private int m_HoveredBodyControlID = -1;
private int m_HoveredJointControlID = -1;
private int m_HoveredTailControlID = -1;
private float m_NearestDistance;
private float m_NearestBodyDistance;
private float m_NearestJointDistance;
private float m_NearestTailDistance;
private int m_NearestBodyId = 0;
private int m_NearestJointId = 0;
private int m_NearestTailId = 0;
private SliderData m_HoveredSliderData = SliderData.zero;
private SliderData m_HotSliderData = SliderData.zero;
public SkeletonView(IGUIWrapper gw)
{
m_GUIWrapper = gw;
}
public void BeginLayout()
{
m_HoveredBodyControlID = m_GUIWrapper.GetControlID(kBodyHashCode, FocusType.Passive);
m_HoveredJointControlID = m_GUIWrapper.GetControlID(kJointHashCode, FocusType.Passive);
m_HoveredTailControlID = m_GUIWrapper.GetControlID(kTailHashCode, FocusType.Passive);
m_CreateBoneControlID = m_GUIWrapper.GetControlID(kCreateBoneHashCode, FocusType.Passive);
if (m_GUIWrapper.eventType == EventType.Layout)
{
m_PrevHoveredBoneID = m_HoveredBoneID;
m_NearestDistance = float.MaxValue;
m_NearestBodyDistance = float.MaxValue;
m_NearestJointDistance = float.MaxValue;
m_NearestTailDistance = float.MaxValue;
m_NearestBodyId = InvalidID;
m_NearestJointId = InvalidID;
m_NearestTailId = InvalidID;
m_HoveredBoneID = InvalidID;
m_HoveredBodyID = InvalidID;
m_HoveredJointID = InvalidID;
m_HoveredTailID = InvalidID;
m_HoveredSliderData = SliderData.zero;
if (m_GUIWrapper.IsControlHot(0))
{
m_RotateControlID = -1;
m_MoveControlID = -1;
m_FreeMoveControlID = -1;
m_MoveJointControlID = -1;
m_MoveEndPositionControlID = -1;
m_ChangeLengthControlID = -1;
m_HotBoneID = InvalidID;
}
}
}
public void EndLayout()
{
m_GUIWrapper.LayoutControl(m_HoveredBodyControlID, m_NearestBodyDistance * 0.25f);
m_GUIWrapper.LayoutControl(m_HoveredJointControlID, m_NearestJointDistance);
m_GUIWrapper.LayoutControl(m_HoveredTailControlID, m_NearestTailDistance);
if (m_GUIWrapper.IsControlNearest(m_HoveredBodyControlID))
{
m_HoveredBoneID = m_NearestBodyId;
m_HoveredBodyID = m_NearestBodyId;
}
if (m_GUIWrapper.IsControlNearest(m_HoveredJointControlID))
{
m_HoveredBoneID = m_NearestJointId;
m_HoveredJointID = m_NearestJointId;
}
if (m_GUIWrapper.IsControlNearest(m_HoveredTailControlID))
{
m_HoveredBoneID = m_NearestTailId;
m_HoveredTailID = m_NearestTailId;
}
if ((m_GUIWrapper.eventType == EventType.Layout && m_PrevHoveredBoneID != m_HoveredBoneID) || m_GUIWrapper.eventType == EventType.MouseMove)
m_GUIWrapper.Repaint();
}
public bool CanLayout()
{
return m_GUIWrapper.eventType == EventType.Layout;
}
public void LayoutBone(int id, Vector3 position, Vector3 endPosition, Vector3 forward, Vector3 up, Vector3 right, bool isChainEnd)
{
if (mode == SkeletonMode.Disabled)
return;
var sliderData = new SliderData()
{
position = GetMouseWorldPosition(forward, position),
forward = forward,
up = up,
right = right
};
{
var distance = m_GUIWrapper.DistanceToSegmentClamp(position, endPosition);
if (distance <= m_NearestDistance)
{
m_NearestDistance = distance;
m_NearestBodyDistance = distance;
m_NearestBodyId = id;
m_HoveredSliderData = sliderData;
}
}
{
var distance = m_GUIWrapper.DistanceToCircle(position, GetBoneRadiusForPicking(position) * 2f);
if (distance <= m_NearestDistance)
{
m_NearestDistance = distance;
m_NearestJointDistance = distance;
m_NearestJointId = id;
m_HoveredSliderData = sliderData;
}
}
if (isChainEnd &&
(IsCapable(SkeletonAction.ChangeLength) ||
IsCapable(SkeletonAction.MoveEndPosition) ||
IsCapable(SkeletonAction.CreateBone)))
{
var distance = m_GUIWrapper.DistanceToCircle(endPosition, GetBoneRadiusForPicking(endPosition));
if (distance <= m_NearestDistance)
{
m_NearestDistance = distance;
m_NearestTailDistance = distance;
m_NearestTailId = id;
m_HoveredSliderData = sliderData;
}
}
}
public Vector3 GetMouseWorldPosition(Vector3 planeNormal, Vector3 planePosition)
{
return m_GUIWrapper.GUIToWorld(m_GUIWrapper.mousePosition, planeNormal, planePosition);
}
private float GetBoneRadiusForPicking(Vector3 position)
{
if (m_GUIWrapper.HasCurrentCamera())
return 0.1f * m_GUIWrapper.GetHandleSize(position);
return kPickingRadius;
}
public bool DoSelectBone(out int id, out bool additive)
{
id = 0;
additive = false;
if (IsActionTriggering(SkeletonAction.Select))
{
id = m_HoveredBoneID;
additive = m_GUIWrapper.isActionKeyDown;
if (mode == SkeletonMode.Selection)
{
m_GUIWrapper.UseCurrentEvent();
m_GUIWrapper.SetGuiChanged(true);
}
return true;
}
return false;
}
public bool DoRotateBone(Vector3 pivot, Vector3 normal, out float deltaAngle)
{
deltaAngle = 0f;
Vector3 oldPosition = m_HotSliderData.position;
Vector3 newPosition;
if (DoSliderAction(SkeletonAction.RotateBone, m_HoveredBodyControlID, ref m_RotateControlID, out newPosition))
{
deltaAngle = Vector3.SignedAngle(oldPosition - pivot, (Vector3)newPosition - pivot, normal);
return true;
}
return false;
}
public bool DoMoveBone(out Vector3 deltaPosition)
{
deltaPosition = Vector3.zero;
Vector3 oldPosition = m_HotSliderData.position;
Vector3 newPosition;
if (DoSliderAction(SkeletonAction.MoveBone, m_HoveredJointControlID, ref m_MoveControlID, out newPosition))
{
deltaPosition = newPosition - oldPosition;
return true;
}
return false;
}
public bool DoFreeMoveBone(out Vector3 deltaPosition)
{
deltaPosition = Vector3.zero;
Vector3 oldPosition = m_HotSliderData.position;
Vector3 newPosition;
if (DoSliderAction(SkeletonAction.FreeMoveBone, m_HoveredBodyControlID, ref m_FreeMoveControlID, out newPosition))
{
deltaPosition = newPosition - oldPosition;
return true;
}
return false;
}
public bool DoMoveJoint(out Vector3 deltaPosition)
{
deltaPosition = Vector3.zero;
Vector3 oldPosition = m_HotSliderData.position;
Vector3 newPosition;
if (DoSliderAction(SkeletonAction.MoveJoint, m_HoveredJointControlID, ref m_MoveJointControlID, out newPosition))
{
deltaPosition = newPosition - oldPosition;
return true;
}
return false;
}
public bool DoMoveEndPosition(out Vector3 endPosition)
{
return DoSliderAction(SkeletonAction.MoveEndPosition, m_HoveredTailControlID, ref m_MoveEndPositionControlID, out endPosition);
}
public bool DoChangeLength(out Vector3 endPosition)
{
return DoSliderAction(SkeletonAction.ChangeLength, m_HoveredTailControlID, ref m_ChangeLengthControlID, out endPosition);
}
private bool DoSliderAction(SkeletonAction action, int controlID, ref int actionControlID, out Vector3 newPosition)
{
newPosition = m_HoveredSliderData.position;
if (IsActionTriggering(action))
{
actionControlID = controlID;
m_HotSliderData = m_HoveredSliderData;
m_HotBoneID = hoveredBoneID;
}
if (m_GUIWrapper.DoSlider(actionControlID, m_HotSliderData, out newPosition))
{
m_HotSliderData.position = newPosition;
return true;
}
return false;
}
public bool DoCreateBoneStart(out Vector3 position)
{
position = GetMouseWorldPosition(m_HoveredSliderData.forward, m_HoveredSliderData.position);
if (CanCreateBone())
m_GUIWrapper.LayoutControl(m_CreateBoneControlID, 0f);
if (IsActionActive(SkeletonAction.CreateBone))
ConsumeMouseMoveEvents();
if (IsActionTriggering(SkeletonAction.CreateBone))
{
m_HotBoneID = hoveredBoneID;
m_GUIWrapper.SetMultiStepControlHot(m_CreateBoneControlID);
m_GUIWrapper.UseCurrentEvent();
return true;
}
return false;
}
public bool CanCreateBone()
{
return mode == SkeletonMode.CreateBone && (m_GUIWrapper.IsControlNearest(defaultControlID) || m_GUIWrapper.IsControlNearest(m_HoveredTailControlID));
}
public bool DoCreateBone(out Vector3 position)
{
position = GetMouseWorldPosition(m_HoveredSliderData.forward, m_HoveredSliderData.position);
if (IsActionHot(SkeletonAction.CreateBone))
ConsumeMouseMoveEvents();
if (IsActionFinishing(SkeletonAction.CreateBone))
{
m_GUIWrapper.UseCurrentEvent();
m_GUIWrapper.SetGuiChanged(true);
return true;
}
return false;
}
public bool DoSplitBone(out int id, out Vector3 position)
{
id = m_HoveredBodyID;
position = GetMouseWorldPosition(m_HoveredSliderData.forward, m_HoveredSliderData.position);
if (IsActionActive(SkeletonAction.SplitBone))
ConsumeMouseMoveEvents();
if (IsActionTriggering(SkeletonAction.SplitBone))
{
m_GUIWrapper.UseCurrentEvent();
m_GUIWrapper.SetGuiChanged(true);
return true;
}
return false;
}
public bool DoRemoveBone()
{
if (IsActionTriggering(SkeletonAction.Remove))
{
m_GUIWrapper.UseCurrentEvent();
m_GUIWrapper.SetGuiChanged(true);
return true;
}
return false;
}
public bool DoCancelMultistepAction(bool force)
{
if (force)
{
m_GUIWrapper.SetMultiStepControlHot(0);
return true;
}
if ((!m_GUIWrapper.IsMultiStepControlHot(0) && (m_GUIWrapper.IsMouseDown(1) || m_GUIWrapper.IsKeyDown(KeyCode.Escape))))
{
m_GUIWrapper.SetMultiStepControlHot(0);
m_GUIWrapper.UseCurrentEvent();
return true;
}
return false;
}
public bool IsActionActive(SkeletonAction action)
{
if (m_GUIWrapper.isAltDown || !m_GUIWrapper.IsControlHot(0) || !m_GUIWrapper.IsMultiStepControlHot(0))
return false;
if (action == SkeletonAction.None)
return m_GUIWrapper.IsControlNearest(defaultControlID);
if (!IsCapable(action))
return false;
if (action == SkeletonAction.RotateBone)
return m_GUIWrapper.IsControlNearest(m_HoveredBodyControlID);
if (action == SkeletonAction.ChangeLength)
return m_GUIWrapper.IsControlNearest(m_HoveredTailControlID) && !m_GUIWrapper.isShiftDown;
if (action == SkeletonAction.MoveJoint)
return m_GUIWrapper.IsControlNearest(m_HoveredJointControlID);
if (action == SkeletonAction.MoveEndPosition)
return m_GUIWrapper.IsControlNearest(m_HoveredTailControlID) && !m_GUIWrapper.isShiftDown;
if (action == SkeletonAction.FreeMoveBone)
return m_GUIWrapper.IsControlNearest(m_HoveredBodyControlID);
if (action == SkeletonAction.MoveBone)
return m_GUIWrapper.IsControlNearest(m_HoveredJointControlID);
bool canCreateBone = IsCapable(SkeletonAction.CreateBone) && m_GUIWrapper.IsControlNearest(m_CreateBoneControlID);
bool canSplitBone = IsCapable(SkeletonAction.SplitBone) && m_GUIWrapper.IsControlNearest(m_HoveredBodyControlID);
if (action == SkeletonAction.CreateBone)
return canCreateBone;
if (action == SkeletonAction.SplitBone)
return canSplitBone;
if (action == SkeletonAction.Select)
return (m_GUIWrapper.IsControlNearest(m_HoveredBodyControlID) && !canSplitBone) ||
m_GUIWrapper.IsControlNearest(m_HoveredJointControlID) ||
(m_GUIWrapper.IsControlNearest(m_HoveredTailControlID) && !canCreateBone);
if (action == SkeletonAction.Remove)
return true;
return false;
}
public bool IsActionHot(SkeletonAction action)
{
if (action == SkeletonAction.None)
return m_GUIWrapper.IsControlHot(0) && m_GUIWrapper.IsMultiStepControlHot(0);
if (action == SkeletonAction.RotateBone)
return m_GUIWrapper.IsControlHot(m_RotateControlID);
if (action == SkeletonAction.MoveBone)
return m_GUIWrapper.IsControlHot(m_MoveControlID);
if (action == SkeletonAction.FreeMoveBone)
return m_GUIWrapper.IsControlHot(m_FreeMoveControlID);
if (action == SkeletonAction.MoveJoint)
return m_GUIWrapper.IsControlHot(m_MoveJointControlID);
if (action == SkeletonAction.MoveEndPosition)
return m_GUIWrapper.IsControlHot(m_MoveEndPositionControlID);
if (action == SkeletonAction.ChangeLength)
return m_GUIWrapper.IsControlHot(m_ChangeLengthControlID);
if (action == SkeletonAction.CreateBone)
return m_GUIWrapper.IsMultiStepControlHot(m_CreateBoneControlID) && !m_GUIWrapper.isAltDown;
return false;
}
public bool IsActionTriggering(SkeletonAction action)
{
if (!IsActionActive(action))
return false;
if (action == SkeletonAction.Remove)
{
if ((m_GUIWrapper.eventType == EventType.ValidateCommand || m_GUIWrapper.eventType == EventType.ExecuteCommand)
&& (m_GUIWrapper.commandName == kSoftDeleteCommandName || m_GUIWrapper.commandName == kDeleteCommandName))
{
if (m_GUIWrapper.eventType == EventType.ExecuteCommand)
return true;
m_GUIWrapper.UseCurrentEvent();
}
return false;
}
return m_GUIWrapper.IsMouseDown(0);
}
public bool IsActionFinishing(SkeletonAction action)
{
if (!IsActionHot(action) || !IsCapable(action))
return false;
if (m_GUIWrapper.IsEventOutsideWindow())
return true;
if (action == SkeletonAction.CreateBone)
return m_GUIWrapper.IsMouseDown(0);
return m_GUIWrapper.IsMouseUp(0);
}
public bool IsRepainting()
{
return m_GUIWrapper.IsRepainting();
}
public void DrawBone(Vector3 position, Vector3 right, Vector3 forward, float length, Color color, bool isChained, bool isSelected, bool isJointHovered, bool isTailHovered, bool isHot)
{
var endPosition = position + right * length;
var rotation = Quaternion.LookRotation(forward, Vector3.Cross(right, forward));
var boneBodyColor = color;
var boneJointColor = new Color(0f, 0f, 0f, 0.75f * color.a);
var tailColor = new Color(0f, 0f, 0f, 0.75f * color.a);
var hoveredColor = Handles.preselectionColor;
var selectedColor = Handles.selectedColor;
var drawRectCap = false;
if (isJointHovered)
boneJointColor = hoveredColor;
if (isHot && (IsActionHot(SkeletonAction.MoveBone) || IsActionHot(SkeletonAction.MoveJoint)))
boneJointColor = selectedColor;
if (mode == SkeletonMode.EditPose || mode == SkeletonMode.CreateBone)
{
if (isJointHovered || isSelected)
drawRectCap = true;
}
else if (mode == SkeletonMode.EditJoints || mode == SkeletonMode.SplitBone)
{
rotation = Quaternion.identity;
drawRectCap = true;
}
if (drawRectCap)
Handles.RectangleHandleCap(0, position, rotation, BoneDrawingUtility.GetBoneRadius(position), EventType.Repaint);
BoneDrawingUtility.DrawBone(position, endPosition, forward, boneBodyColor);
BoneDrawingUtility.DrawBoneNode(position, forward, boneJointColor);
if (!isChained &&
(IsCapable(SkeletonAction.ChangeLength) ||
IsCapable(SkeletonAction.MoveEndPosition)))
{
if (isTailHovered)
tailColor = hoveredColor;
if (isHot && (IsActionHot(SkeletonAction.ChangeLength) || IsActionHot(SkeletonAction.MoveEndPosition)))
tailColor = selectedColor;
BoneDrawingUtility.DrawBoneNode(endPosition, forward, tailColor);
}
}
public void DrawBoneParentLink(Vector3 parentPosition, Vector3 position, Vector3 forward, Color color)
{
BoneDrawingUtility.DrawBone(position, parentPosition, forward, color);
}
public void DrawBoneOutline(Vector3 position, Vector3 right, Vector3 forward, float length, Color color, float outlineScale)
{
BoneDrawingUtility.DrawBoneOutline(position, position + right * length, forward, color, outlineScale);
}
public void DrawCursors(bool canBeActive)
{
var mouseScreenRect = new Rect(m_GUIWrapper.mousePosition.x - 100f, m_GUIWrapper.mousePosition.y - 100f, 200f, 200f);
var isRotateHot = IsActionHot(SkeletonAction.RotateBone);
if ((canBeActive && IsActionActive(SkeletonAction.RotateBone)) || isRotateHot)
EditorGUIUtility.AddCursorRect(mouseScreenRect, MouseCursor.RotateArrow);
if ((canBeActive && IsActionActive(SkeletonAction.MoveBone)) || IsActionHot(SkeletonAction.MoveBone) ||
(canBeActive && IsActionActive(SkeletonAction.FreeMoveBone)) || IsActionHot(SkeletonAction.FreeMoveBone) ||
(canBeActive && IsActionActive(SkeletonAction.MoveJoint)) || IsActionHot(SkeletonAction.MoveJoint) ||
(canBeActive && IsActionActive(SkeletonAction.MoveEndPosition)) || IsActionHot(SkeletonAction.MoveEndPosition))
EditorGUIUtility.AddCursorRect(mouseScreenRect, MouseCursor.MoveArrow);
}
private void ConsumeMouseMoveEvents()
{
if (m_GUIWrapper.eventType == EventType.MouseMove || (m_GUIWrapper.eventType == EventType.MouseDrag && m_GUIWrapper.mouseButton == 0))
m_GUIWrapper.UseCurrentEvent();
}
private bool IsCapable(SkeletonAction action)
{
return ((int)mode & (int)action) != 0;
}
}
}

View File

@@ -0,0 +1,78 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Slider2D
{
private static Vector2 s_CurrentMousePosition;
private static Vector2 s_DragStartScreenPosition;
private static Vector2 s_DragScreenOffset;
private static double s_Time;
public static Vector2 Do(int controlID, Vector2 position, Handles.CapFunction drawCapFunction = null)
{
EventType type = Event.current.GetTypeForControl(controlID);
switch (type)
{
case EventType.MouseDown:
if (Event.current.button == 0 && HandleUtility.nearestControl == controlID && !Event.current.alt)
{
s_Time = EditorApplication.timeSinceStartup;
GUIUtility.keyboardControl = controlID;
GUIUtility.hotControl = controlID;
s_CurrentMousePosition = Event.current.mousePosition;
s_DragStartScreenPosition = Event.current.mousePosition;
Vector2 b = HandleUtility.WorldToGUIPoint(position);
s_DragScreenOffset = s_CurrentMousePosition - b;
Event.current.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID && (Event.current.button == 0 || Event.current.button == 2))
{
GUIUtility.hotControl = 0;
Event.current.Use();
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlID)
{
s_CurrentMousePosition = Event.current.mousePosition;
float screenDisplacement = (s_CurrentMousePosition - s_DragStartScreenPosition).magnitude;
Vector2 center = position;
Vector2 screenPosition = s_CurrentMousePosition - s_DragScreenOffset;
position = Handles.inverseMatrix.MultiplyPoint(screenPosition);
float displacement = (center - position).magnitude;
if (!Mathf.Approximately(displacement, 0f) && (EditorApplication.timeSinceStartup - s_Time > 0.15 || screenDisplacement >= 10f))
GUI.changed = true;
Event.current.Use();
}
break;
case EventType.KeyDown:
if (GUIUtility.hotControl == controlID && Event.current.keyCode == KeyCode.Escape)
{
position = Handles.inverseMatrix.MultiplyPoint(s_DragStartScreenPosition - s_DragScreenOffset);
GUIUtility.hotControl = 0;
GUI.changed = true;
Event.current.Use();
}
break;
case EventType.Layout:
if (drawCapFunction != null)
drawCapFunction(controlID, position, Quaternion.identity, 1f, EventType.Layout);
break;
case EventType.Repaint:
if (drawCapFunction != null)
drawCapFunction(controlID, position, Quaternion.identity, 1f, EventType.Repaint);
break;
}
return position;
}
}
}

View File

@@ -0,0 +1,696 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SpriteMeshController
{
private const float kSnapDistance = 10f;
private struct EdgeIntersectionResult
{
public int startVertexIndex;
public int endVertexIndex;
public int intersectEdgeIndex;
public Vector2 endPosition;
}
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private EdgeIntersectionResult m_EdgeIntersectionResult;
public ISpriteMeshView spriteMeshView { get; set; }
public ISpriteMeshData spriteMeshData
{
get { return m_SpriteMeshData; }
set { m_SpriteMeshData = value; }
}
public ISelection<int> selection { get; set; }
public ICacheUndo cacheUndo { get; set; }
public ITriangulator triangulator { get; set; }
public bool disable { get; set; }
public Rect frame { get; set; }
private ISpriteMeshData m_SpriteMeshData;
private bool m_Moved = false;
public void OnGUI()
{
m_SpriteMeshDataController.spriteMeshData = m_SpriteMeshData;
Debug.Assert(spriteMeshView != null);
Debug.Assert(m_SpriteMeshData != null);
Debug.Assert(selection != null);
Debug.Assert(cacheUndo != null);
spriteMeshView.selection = selection;
spriteMeshView.frame = frame;
EditorGUI.BeginDisabledGroup(disable);
spriteMeshView.BeginLayout();
if(spriteMeshView.CanLayout())
{
LayoutVertices();
LayoutEdges();
}
spriteMeshView.EndLayout();
if(spriteMeshView.CanRepaint())
{
DrawEdges();
if(GUI.enabled)
{
PreviewCreateVertex();
PreviewCreateEdge();
PreviewSplitEdge();
}
DrawVertices();
}
HandleSplitEdge();
HandleCreateEdge();
HandleCreateVertex();
EditorGUI.EndDisabledGroup();
HandleSelectVertex();
EditorGUI.BeginDisabledGroup(disable);
HandleMoveVertex();
EditorGUI.EndDisabledGroup();
HandleSelectEdge();
EditorGUI.BeginDisabledGroup(disable);
HandleMoveEdge();
HandleRemoveEdge();
HandleRemoveVertices();
spriteMeshView.DoRepaint();
EditorGUI.EndDisabledGroup();
}
private void LayoutVertices()
{
for (int i = 0; i < m_SpriteMeshData.vertexCount; i++)
{
Vector2 position = m_SpriteMeshData.GetPosition(i);
spriteMeshView.LayoutVertex(position, i);
}
}
private void LayoutEdges()
{
for (int i = 0; i < m_SpriteMeshData.edges.Count; i++)
{
Edge edge = m_SpriteMeshData.edges[i];
Vector2 startPosition = m_SpriteMeshData.GetPosition(edge.index1);
Vector2 endPosition = m_SpriteMeshData.GetPosition(edge.index2);
spriteMeshView.LayoutEdge(startPosition, endPosition, i);
}
}
private void DrawEdges()
{
UpdateEdgeInstersection();
spriteMeshView.BeginDrawEdges();
for (int i = 0; i < m_SpriteMeshData.edges.Count; ++i)
{
if (SkipDrawEdge(i))
continue;
Edge edge = m_SpriteMeshData.edges[i];
Vector2 startPosition = m_SpriteMeshData.GetPosition(edge.index1);
Vector2 endPosition = m_SpriteMeshData.GetPosition(edge.index2);
if (selection.Contains(edge.index1) && selection.Contains(edge.index2))
spriteMeshView.DrawEdgeSelected(startPosition, endPosition);
else
spriteMeshView.DrawEdge(startPosition, endPosition);
}
if (spriteMeshView.IsActionActive(MeshEditorAction.SelectEdge))
{
Edge hoveredEdge = m_SpriteMeshData.edges[spriteMeshView.hoveredEdge];
Vector2 startPosition = m_SpriteMeshData.GetPosition(hoveredEdge.index1);
Vector2 endPosition = m_SpriteMeshData.GetPosition(hoveredEdge.index2);
spriteMeshView.DrawEdgeHovered(startPosition, endPosition);
}
spriteMeshView.EndDrawEdges();
}
private bool SkipDrawEdge(int edgeIndex)
{
if(GUI.enabled == false)
return false;
return edgeIndex == -1 ||
spriteMeshView.hoveredEdge == edgeIndex && spriteMeshView.IsActionActive(MeshEditorAction.SelectEdge) ||
spriteMeshView.hoveredEdge == edgeIndex && spriteMeshView.IsActionActive(MeshEditorAction.CreateVertex) ||
spriteMeshView.closestEdge == edgeIndex && spriteMeshView.IsActionActive(MeshEditorAction.SplitEdge) ||
edgeIndex == m_EdgeIntersectionResult.intersectEdgeIndex && spriteMeshView.IsActionActive(MeshEditorAction.CreateEdge);
}
private void PreviewCreateVertex()
{
if (spriteMeshView.mode == SpriteMeshViewMode.CreateVertex &&
spriteMeshView.IsActionActive(MeshEditorAction.CreateVertex))
{
Vector2 clampedMousePos = ClampToFrame(spriteMeshView.mouseWorldPosition);
if (spriteMeshView.hoveredEdge != -1)
{
Edge edge = m_SpriteMeshData.edges[spriteMeshView.hoveredEdge];
spriteMeshView.BeginDrawEdges();
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(edge.index1), clampedMousePos);
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(edge.index2), clampedMousePos);
spriteMeshView.EndDrawEdges();
}
spriteMeshView.DrawVertex(clampedMousePos);
}
}
private void PreviewCreateEdge()
{
if (!spriteMeshView.IsActionActive(MeshEditorAction.CreateEdge))
return;
spriteMeshView.BeginDrawEdges();
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(m_EdgeIntersectionResult.startVertexIndex), m_EdgeIntersectionResult.endPosition);
if (m_EdgeIntersectionResult.intersectEdgeIndex != -1)
{
Edge intersectingEdge = m_SpriteMeshData.edges[m_EdgeIntersectionResult.intersectEdgeIndex];
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(intersectingEdge.index1), m_EdgeIntersectionResult.endPosition);
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(intersectingEdge.index2), m_EdgeIntersectionResult.endPosition);
}
spriteMeshView.EndDrawEdges();
if (m_EdgeIntersectionResult.endVertexIndex == -1)
spriteMeshView.DrawVertex(m_EdgeIntersectionResult.endPosition);
}
private void PreviewSplitEdge()
{
if (!spriteMeshView.IsActionActive(MeshEditorAction.SplitEdge))
return;
Vector2 clampedMousePos = ClampToFrame(spriteMeshView.mouseWorldPosition);
Edge closestEdge = m_SpriteMeshData.edges[spriteMeshView.closestEdge];
spriteMeshView.BeginDrawEdges();
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(closestEdge.index1), clampedMousePos);
spriteMeshView.DrawEdge(m_SpriteMeshData.GetPosition(closestEdge.index2), clampedMousePos);
spriteMeshView.EndDrawEdges();
spriteMeshView.DrawVertex(clampedMousePos);
}
private void DrawVertices()
{
for (int i = 0; i < m_SpriteMeshData.vertexCount; i++)
{
Vector3 position = m_SpriteMeshData.GetPosition(i);
if (selection.Contains(i))
spriteMeshView.DrawVertexSelected(position);
else if (i == spriteMeshView.hoveredVertex && spriteMeshView.IsActionHot(MeshEditorAction.None))
spriteMeshView.DrawVertexHovered(position);
else
spriteMeshView.DrawVertex(position);
}
}
private void HandleSelectVertex()
{
bool additive;
if (spriteMeshView.DoSelectVertex(out additive))
SelectVertex(spriteMeshView.hoveredVertex, additive);
}
private void HandleSelectEdge()
{
bool additive;
if (spriteMeshView.DoSelectEdge(out additive))
SelectEdge(spriteMeshView.hoveredEdge, additive);
}
private void HandleMoveVertex()
{
if(spriteMeshView.IsActionTriggered(MeshEditorAction.MoveVertex))
m_Moved = false;
Vector2 delta;
if (spriteMeshView.DoMoveVertex(out delta))
{
if(!m_Moved)
{
cacheUndo.BeginUndoOperation(TextContent.moveVertices);
m_Moved = true;
}
MoveSelectedVertices(delta);
}
}
private void HandleCreateVertex()
{
if (spriteMeshView.DoCreateVertex())
CreateVertex(spriteMeshView.mouseWorldPosition, spriteMeshView.hoveredEdge);
}
private void HandleSplitEdge()
{
if (spriteMeshView.DoSplitEdge())
SplitEdge(spriteMeshView.mouseWorldPosition, spriteMeshView.closestEdge);
}
private void HandleCreateEdge()
{
if (spriteMeshView.DoCreateEdge())
CreateEdge(spriteMeshView.mouseWorldPosition, spriteMeshView.hoveredVertex, spriteMeshView.hoveredEdge);
}
private void HandleMoveEdge()
{
if(spriteMeshView.IsActionTriggered(MeshEditorAction.MoveEdge))
m_Moved = false;
Vector2 delta;
if (spriteMeshView.DoMoveEdge(out delta))
{
if(!m_Moved)
{
cacheUndo.BeginUndoOperation(TextContent.moveVertices);
m_Moved = true;
}
MoveSelectedVertices(delta);
}
}
private void HandleRemoveEdge()
{
Edge edge;
if (GetSelectedEdge(out edge) && spriteMeshView.DoRemove())
RemoveEdge(edge);
}
private void HandleRemoveVertices()
{
if (spriteMeshView.DoRemove())
RemoveSelectedVertices();
}
private void CreateVertex(Vector2 position, int edgeIndex)
{
position = MathUtility.ClampPositionToRect(position, frame);
cacheUndo.BeginUndoOperation(TextContent.createVertex);
BoneWeight boneWeight = new BoneWeight();
Vector3Int indices;
Vector3 barycentricCoords;
if (m_SpriteMeshDataController.FindTriangle(position, out indices, out barycentricCoords))
{
EditableBoneWeight bw1 = m_SpriteMeshData.GetWeight(indices.x);
EditableBoneWeight bw2 = m_SpriteMeshData.GetWeight(indices.y);
EditableBoneWeight bw3 = m_SpriteMeshData.GetWeight(indices.z);
EditableBoneWeight result = new EditableBoneWeight();
foreach (BoneWeightChannel channel in bw1)
{
if (!channel.enabled)
continue;
var weight = channel.weight * barycentricCoords.x;
if (weight > 0f)
result.AddChannel(channel.boneIndex, weight, true);
}
foreach (BoneWeightChannel channel in bw2)
{
if (!channel.enabled)
continue;
var weight = channel.weight * barycentricCoords.y;
if (weight > 0f)
result.AddChannel(channel.boneIndex, weight, true);
}
foreach (BoneWeightChannel channel in bw3)
{
if (!channel.enabled)
continue;
var weight = channel.weight * barycentricCoords.z;
if (weight > 0f)
result.AddChannel(channel.boneIndex, weight, true);
}
result.UnifyChannelsWithSameBoneIndex();
result.FilterChannels(0f);
result.Clamp(4, true);
boneWeight = result.ToBoneWeight(true);
}
else if (edgeIndex != -1)
{
Edge edge = m_SpriteMeshData.edges[edgeIndex];
Vector2 pos1 = m_SpriteMeshData.GetPosition(edge.index1);
Vector2 pos2 = m_SpriteMeshData.GetPosition(edge.index2);
Vector2 dir1 = (position - pos1);
Vector2 dir2 = (pos2 - pos1);
float t = Vector2.Dot(dir1, dir2.normalized) / dir2.magnitude;
t = Mathf.Clamp01(t);
BoneWeight bw1 = m_SpriteMeshData.GetWeight(edge.index1).ToBoneWeight(true);
BoneWeight bw2 = m_SpriteMeshData.GetWeight(edge.index2).ToBoneWeight(true);
boneWeight = EditableBoneWeightUtility.Lerp(bw1, bw2, t);
}
m_SpriteMeshDataController.CreateVertex(position, edgeIndex);
m_SpriteMeshData.GetWeight(m_SpriteMeshData.vertexCount - 1).SetFromBoneWeight(boneWeight);
Triangulate();
}
private void SelectVertex(int index, bool additiveToggle)
{
if (index < 0)
throw new ArgumentException("Index out of range");
bool selected = selection.Contains(index);
if (selected)
{
if (additiveToggle)
{
cacheUndo.BeginUndoOperation(TextContent.selection);
selection.Select(index, false);
}
}
else
{
cacheUndo.BeginUndoOperation(TextContent.selection);
if (!additiveToggle)
ClearSelection();
selection.Select(index, true);
}
cacheUndo.IncrementCurrentGroup();
}
private void SelectEdge(int index, bool additiveToggle)
{
Debug.Assert(index >= 0);
Edge edge = m_SpriteMeshData.edges[index];
cacheUndo.BeginUndoOperation(TextContent.selection);
bool selected = selection.Contains(edge.index1) && selection.Contains(edge.index2);
if (selected)
{
if (additiveToggle)
{
selection.Select(edge.index1, false);
selection.Select(edge.index2, false);
}
}
else
{
if (!additiveToggle)
ClearSelection();
selection.Select(edge.index1, true);
selection.Select(edge.index2, true);
}
cacheUndo.IncrementCurrentGroup();
}
private void ClearSelection()
{
cacheUndo.BeginUndoOperation(TextContent.selection);
selection.Clear();
}
private void MoveSelectedVertices(Vector2 delta)
{
delta = MathUtility.MoveRectInsideFrame(CalculateRectFromSelection(), frame, delta);
var indices = selection.elements;
foreach (int index in indices)
{
Vector2 v = m_SpriteMeshData.GetPosition(index);
m_SpriteMeshData.SetPosition(index, ClampToFrame(v + delta));
}
Triangulate();
}
private void CreateEdge(Vector2 position, int hoveredVertexIndex, int hoveredEdgeIndex)
{
position = ClampToFrame(position);
EdgeIntersectionResult edgeIntersectionResult = CalculateEdgeIntersection(selection.activeElement, hoveredVertexIndex, hoveredEdgeIndex, position);
cacheUndo.BeginUndoOperation(TextContent.createEdge);
int selectIndex = -1;
if (edgeIntersectionResult.endVertexIndex == -1)
{
CreateVertex(edgeIntersectionResult.endPosition, edgeIntersectionResult.intersectEdgeIndex);
m_SpriteMeshDataController.CreateEdge(selection.activeElement, m_SpriteMeshData.vertexCount - 1);
selectIndex = m_SpriteMeshData.vertexCount - 1;
}
else
{
m_SpriteMeshDataController.CreateEdge(selection.activeElement, edgeIntersectionResult.endVertexIndex);
Triangulate();
selectIndex = edgeIntersectionResult.endVertexIndex;
}
ClearSelection();
selection.Select(selectIndex, true);
cacheUndo.IncrementCurrentGroup();
}
private void SplitEdge(Vector2 position, int edgeIndex)
{
cacheUndo.BeginUndoOperation(TextContent.splitEdge);
Vector2 clampedMousePos = ClampToFrame(position);
CreateVertex(clampedMousePos, edgeIndex);
cacheUndo.IncrementCurrentGroup();
}
private bool GetSelectedEdge(out Edge edge)
{
edge = default(Edge);
if (selection.Count != 2)
return false;
var indices = selection.elements;
int index1 = indices[0];
int index2 = indices[1];
edge = new Edge(index1, index2);
if (!m_SpriteMeshData.edges.Contains(edge))
return false;
return true;
}
private void RemoveEdge(Edge edge)
{
cacheUndo.BeginUndoOperation(TextContent.removeEdge);
m_SpriteMeshDataController.RemoveEdge(edge);
Triangulate();
}
private void RemoveSelectedVertices()
{
cacheUndo.BeginUndoOperation(TextContent.removeVertices);
m_SpriteMeshDataController.RemoveVertex(selection.elements);
Triangulate();
selection.Clear();
}
private void Triangulate()
{
m_SpriteMeshDataController.Triangulate(triangulator);
m_SpriteMeshDataController.SortTrianglesByDepth();
}
private Vector2 ClampToFrame(Vector2 position)
{
return MathUtility.ClampPositionToRect(position, frame);
}
private Rect CalculateRectFromSelection()
{
Rect rect = new Rect();
Vector2 min = new Vector2(float.MaxValue, float.MaxValue);
Vector2 max = new Vector2(float.MinValue, float.MinValue);
var indices = selection.elements;
foreach (int index in indices)
{
Vector2 v = m_SpriteMeshData.GetPosition(index);
min.x = Mathf.Min(min.x, v.x);
min.y = Mathf.Min(min.y, v.y);
max.x = Mathf.Max(max.x, v.x);
max.y = Mathf.Max(max.y, v.y);
}
rect.min = min;
rect.max = max;
return rect;
}
private void UpdateEdgeInstersection()
{
if (selection.Count == 1)
m_EdgeIntersectionResult = CalculateEdgeIntersection(selection.activeElement, spriteMeshView.hoveredVertex, spriteMeshView.hoveredEdge, ClampToFrame(spriteMeshView.mouseWorldPosition));
}
private EdgeIntersectionResult CalculateEdgeIntersection(int vertexIndex, int hoveredVertexIndex, int hoveredEdgeIndex, Vector2 targetPosition)
{
Debug.Assert(vertexIndex >= 0);
EdgeIntersectionResult edgeIntersection = new EdgeIntersectionResult();
edgeIntersection.startVertexIndex = vertexIndex;
edgeIntersection.endVertexIndex = hoveredVertexIndex;
edgeIntersection.endPosition = targetPosition;
edgeIntersection.intersectEdgeIndex = -1;
Vector2 startPoint = m_SpriteMeshData.GetPosition(edgeIntersection.startVertexIndex);
bool intersectsEdge = false;
int lastIntersectingEdgeIndex = -1;
do
{
lastIntersectingEdgeIndex = edgeIntersection.intersectEdgeIndex;
if (intersectsEdge)
{
Vector2 dir = edgeIntersection.endPosition - startPoint;
edgeIntersection.endPosition += dir.normalized * 10f;
}
intersectsEdge = SegmentIntersectsEdge(startPoint, edgeIntersection.endPosition, vertexIndex, ref edgeIntersection.endPosition, out edgeIntersection.intersectEdgeIndex);
//if we are hovering a vertex and intersect an edge indexing it we forget about the intersection
if (intersectsEdge && m_SpriteMeshData.edges[edgeIntersection.intersectEdgeIndex].Contains(edgeIntersection.endVertexIndex))
{
edgeIntersection.intersectEdgeIndex = -1;
intersectsEdge = false;
edgeIntersection.endPosition = m_SpriteMeshData.GetPosition(edgeIntersection.endVertexIndex);
}
if (intersectsEdge)
{
edgeIntersection.endVertexIndex = -1;
Edge intersectingEdge = m_SpriteMeshData.edges[edgeIntersection.intersectEdgeIndex];
Vector2 newPointScreen = spriteMeshView.WorldToScreen(edgeIntersection.endPosition);
Vector2 edgeV1 = spriteMeshView.WorldToScreen(m_SpriteMeshData.GetPosition(intersectingEdge.index1));
Vector2 edgeV2 = spriteMeshView.WorldToScreen(m_SpriteMeshData.GetPosition(intersectingEdge.index2));
if ((newPointScreen - edgeV1).magnitude <= kSnapDistance)
edgeIntersection.endVertexIndex = intersectingEdge.index1;
else if ((newPointScreen - edgeV2).magnitude <= kSnapDistance)
edgeIntersection.endVertexIndex = intersectingEdge.index2;
if (edgeIntersection.endVertexIndex != -1)
{
edgeIntersection.intersectEdgeIndex = -1;
intersectsEdge = false;
edgeIntersection.endPosition = m_SpriteMeshData.GetPosition(edgeIntersection.endVertexIndex);
}
}
}
while (intersectsEdge && lastIntersectingEdgeIndex != edgeIntersection.intersectEdgeIndex);
edgeIntersection.intersectEdgeIndex = intersectsEdge ? edgeIntersection.intersectEdgeIndex : hoveredEdgeIndex;
if (edgeIntersection.endVertexIndex != -1 && !intersectsEdge)
edgeIntersection.endPosition = m_SpriteMeshData.GetPosition(edgeIntersection.endVertexIndex);
return edgeIntersection;
}
private bool SegmentIntersectsEdge(Vector2 p1, Vector2 p2, int ignoreIndex, ref Vector2 point, out int intersectingEdgeIndex)
{
intersectingEdgeIndex = -1;
float sqrDistance = float.MaxValue;
for (int i = 0; i < m_SpriteMeshData.edges.Count; i++)
{
Edge edge = m_SpriteMeshData.edges[i];
Vector2 v1 = m_SpriteMeshData.GetPosition(edge.index1);
Vector2 v2 = m_SpriteMeshData.GetPosition(edge.index2);
Vector2 pointTmp = Vector2.zero;
if (!edge.Contains(ignoreIndex) && MathUtility.SegmentIntersection(p1, p2, v1, v2, ref pointTmp))
{
float sqrMagnitude = (pointTmp - p1).sqrMagnitude;
if (sqrMagnitude < sqrDistance)
{
sqrDistance = sqrMagnitude;
intersectingEdgeIndex = i;
point = pointTmp;
}
}
}
return intersectingEdgeIndex != -1;
}
}
}

View File

@@ -0,0 +1,539 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SpriteMeshView : ISpriteMeshView
{
readonly int m_VertexHashCode = "Vertex".GetHashCode();
readonly int m_EdgeHashCode = "Edge".GetHashCode();
const string kDeleteCommandName = "Delete";
const string kSoftDeleteCommandName = "SoftDelete";
static readonly Color kEdgeColor = Color.cyan;
static readonly Color kEdgeHoveredColor = Color.yellow;
static readonly Color kEdgeSelectedColor = Color.yellow;
const float kEdgeWidth = 2f;
const float kVertexRadius = 2.5f;
private class Styles
{
public readonly GUIStyle pointNormalStyle;
public readonly GUIStyle pointHoveredStyle;
public readonly GUIStyle pointSelectedStyle;
public Styles()
{
Texture2D pointNormal = ResourceLoader.Load<Texture2D>("SkinningModule/dotCyan.png");
Texture2D pointHovered = ResourceLoader.Load<Texture2D>("SkinningModule/dotYellow.png");
Texture2D pointSelected = ResourceLoader.Load<Texture2D>("SkinningModule/dotYellow.png");
pointNormalStyle = new GUIStyle();
pointNormalStyle.normal.background = pointNormal;
pointNormalStyle.fixedWidth = 8f;
pointNormalStyle.fixedHeight = 8f;
pointHoveredStyle = new GUIStyle();
pointHoveredStyle.normal.background = pointHovered;
pointHoveredStyle.fixedWidth = 10f;
pointHoveredStyle.fixedHeight = 10f;
pointSelectedStyle = new GUIStyle();
pointSelectedStyle.normal.background = pointSelected;
pointSelectedStyle.fixedWidth = 10f;
pointSelectedStyle.fixedHeight = 10f;
}
}
private Styles m_Styles;
private Styles styles
{
get
{
if (m_Styles == null)
m_Styles = new Styles();
return m_Styles;
}
}
int m_HoveredEdge = -1;
int m_HoveredEdgeControlID = -1;
int m_MoveEdgeControlID = -1;
int m_HoveredVertex = -1;
int m_PrevHoveredVertex = -1;
int m_HoveredVertexControlID = -1;
int m_MoveVertexControlID = -1;
Color m_TempColor;
SliderData m_HotSliderData = SliderData.zero;
MeshEditorAction m_PreviousActiveAction = MeshEditorAction.None;
private Vector2 m_MouseWorldPosition;
private float m_NearestVertexDistance;
private float m_NearestEdgeDistance;
private int m_NearestVertex = -1;
private int m_NearestEdge = -1;
public SpriteMeshViewMode mode { get; set; }
public ISelection<int> selection { get; set; }
public int defaultControlID { get; set; }
public Rect frame { get; set; }
private IGUIWrapper guiWrapper { get; set; }
public Vector2 mouseWorldPosition
{
get { return m_MouseWorldPosition; }
}
public int hoveredVertex
{
get { return m_HoveredVertex; }
}
public int hoveredEdge
{
get { return m_HoveredEdge; }
}
public int closestEdge
{
get { return m_NearestEdge; }
}
public SpriteMeshView(IGUIWrapper gw)
{
guiWrapper = gw;
}
public void CancelMode()
{
if (mode != SpriteMeshViewMode.EditGeometry)
{
if (guiWrapper.IsKeyDown(KeyCode.Escape) || guiWrapper.IsMouseDown(1))
{
mode = SpriteMeshViewMode.EditGeometry;
guiWrapper.UseCurrentEvent();
}
}
}
public void BeginLayout()
{
var vertexControlID = guiWrapper.GetControlID(m_VertexHashCode, FocusType.Passive);
var edgeControlID = guiWrapper.GetControlID(m_EdgeHashCode, FocusType.Passive);
if (guiWrapper.eventType == EventType.Layout || guiWrapper.eventType == EventType.MouseMove)
{
m_NearestVertexDistance = float.MaxValue;
m_NearestEdgeDistance = float.MaxValue;
m_NearestVertex = -1;
m_NearestEdge = -1;
m_MouseWorldPosition = guiWrapper.GUIToWorld(guiWrapper.mousePosition);
m_HoveredVertexControlID = vertexControlID;
m_HoveredEdgeControlID = edgeControlID;
m_PrevHoveredVertex = m_HoveredVertex;
m_HoveredVertex = -1;
m_HoveredEdge = -1;
if (guiWrapper.IsControlHot(0))
{
m_MoveVertexControlID = -1;
m_MoveEdgeControlID = -1;
}
}
}
public void EndLayout()
{
guiWrapper.LayoutControl(m_HoveredEdgeControlID, m_NearestEdgeDistance);
guiWrapper.LayoutControl(m_HoveredVertexControlID, m_NearestVertexDistance);
if(guiWrapper.IsControlNearest(m_HoveredVertexControlID))
m_HoveredVertex = m_NearestVertex;
if (guiWrapper.IsControlNearest(m_HoveredEdgeControlID))
m_HoveredEdge = m_NearestEdge;
if (guiWrapper.eventType == EventType.Layout || guiWrapper.eventType == EventType.MouseMove)
if (m_PrevHoveredVertex != m_HoveredVertex)
guiWrapper.Repaint();
}
public void LayoutVertex(Vector2 position, int index)
{
if (guiWrapper.eventType == EventType.Layout)
{
var distance = guiWrapper.DistanceToCircle(position, kVertexRadius);
if (distance <= m_NearestVertexDistance)
{
m_NearestVertexDistance = distance;
m_NearestVertex = index;
}
}
}
public void LayoutEdge(Vector2 startPosition, Vector2 endPosition, int index)
{
if (guiWrapper.eventType == EventType.Layout)
{
var distance = guiWrapper.DistanceToSegment(startPosition, endPosition);
if (distance < m_NearestEdgeDistance)
{
m_NearestEdgeDistance = distance;
m_NearestEdge = index;
}
}
}
public bool DoCreateVertex()
{
if (mode == SpriteMeshViewMode.CreateVertex && IsActionActive(MeshEditorAction.CreateVertex))
ConsumeMouseMoveEvents();
if (IsActionTriggered(MeshEditorAction.CreateVertex))
{
guiWrapper.SetGuiChanged(true);
guiWrapper.UseCurrentEvent();
return true;
}
return false;
}
public bool DoSelectVertex(out bool additive)
{
additive = false;
if (IsActionTriggered(MeshEditorAction.SelectVertex))
{
additive = guiWrapper.isActionKeyDown;
guiWrapper.Repaint();
return true;
}
return false;
}
public bool DoMoveVertex(out Vector2 delta)
{
delta = Vector2.zero;
if (IsActionTriggered(MeshEditorAction.MoveVertex))
{
m_MoveVertexControlID = m_HoveredVertexControlID;
m_HotSliderData.position = mouseWorldPosition;
}
Vector3 newPosition;
if (guiWrapper.DoSlider(m_MoveVertexControlID, m_HotSliderData, out newPosition))
{
delta = newPosition - m_HotSliderData.position;
m_HotSliderData.position = newPosition;
return true;
}
return false;
}
public bool DoMoveEdge(out Vector2 delta)
{
delta = Vector2.zero;
if (IsActionTriggered(MeshEditorAction.MoveEdge))
{
m_MoveEdgeControlID = m_HoveredEdgeControlID;
m_HotSliderData.position = mouseWorldPosition;
}
Vector3 newPosition;
if (guiWrapper.DoSlider(m_MoveEdgeControlID, m_HotSliderData, out newPosition))
{
delta = newPosition - m_HotSliderData.position;
m_HotSliderData.position = newPosition;
return true;
}
return false;
}
public bool DoCreateEdge()
{
if (IsActionActive(MeshEditorAction.CreateEdge))
ConsumeMouseMoveEvents();
if (IsActionTriggered(MeshEditorAction.CreateEdge))
{
guiWrapper.SetGuiChanged(true);
guiWrapper.UseCurrentEvent();
return true;
}
return false;
}
public bool DoSplitEdge()
{
if (IsActionActive(MeshEditorAction.SplitEdge))
ConsumeMouseMoveEvents();
if (IsActionTriggered(MeshEditorAction.SplitEdge))
{
guiWrapper.UseCurrentEvent();
guiWrapper.SetGuiChanged(true);
return true;
}
return false;
}
public bool DoSelectEdge(out bool additive)
{
additive = false;
if (IsActionTriggered(MeshEditorAction.SelectEdge))
{
additive = guiWrapper.isActionKeyDown;
guiWrapper.Repaint();
return true;
}
return false;
}
public bool DoRemove()
{
if (IsActionTriggered(MeshEditorAction.Remove))
{
guiWrapper.UseCurrentEvent();
guiWrapper.SetGuiChanged(true);
return true;
}
return false;
}
public void DrawVertex(Vector2 position)
{
DrawingUtility.DrawGUIStyleCap(0, position, Quaternion.identity, 1f, styles.pointNormalStyle);
}
public void DrawVertexHovered(Vector2 position)
{
DrawingUtility.DrawGUIStyleCap(0, position, Quaternion.identity, 1f, styles.pointHoveredStyle);
}
public void DrawVertexSelected(Vector2 position)
{
DrawingUtility.DrawGUIStyleCap(0, position, Quaternion.identity, 1f, styles.pointSelectedStyle);
}
public void BeginDrawEdges()
{
if (guiWrapper.eventType != EventType.Repaint)
return;
DrawingUtility.BeginSolidLines();
m_TempColor = Handles.color;
}
public void EndDrawEdges()
{
if (guiWrapper.eventType != EventType.Repaint)
return;
DrawingUtility.EndLines();
Handles.color = m_TempColor;
}
public void DrawEdge(Vector2 startPosition, Vector2 endPosition)
{
DrawEdge(startPosition, endPosition, kEdgeColor);
}
public void DrawEdgeHovered(Vector2 startPosition, Vector2 endPosition)
{
DrawEdge(startPosition, endPosition, kEdgeHoveredColor);
}
public void DrawEdgeSelected(Vector2 startPosition, Vector2 endPosition)
{
DrawEdge(startPosition, endPosition, kEdgeSelectedColor);
}
public bool IsActionActive(MeshEditorAction action)
{
if (guiWrapper.isAltDown || !guiWrapper.IsControlHot(0))
return false;
var canCreateEdge = CanCreateEdge();
var canSplitEdge = CanSplitEdge();
if (action == MeshEditorAction.None)
return guiWrapper.IsControlNearest(defaultControlID);
if (action == MeshEditorAction.CreateVertex)
{
if(!frame.Contains(mouseWorldPosition))
return false;
if (mode == SpriteMeshViewMode.EditGeometry)
return guiWrapper.IsControlNearest(defaultControlID);
if (mode == SpriteMeshViewMode.CreateVertex)
return hoveredVertex == -1;
}
if (action == MeshEditorAction.MoveVertex)
return guiWrapper.IsControlNearest(m_HoveredVertexControlID);
if (action == MeshEditorAction.CreateEdge)
return canCreateEdge;
if (action == MeshEditorAction.SplitEdge)
return canSplitEdge;
if (action == MeshEditorAction.MoveEdge)
return guiWrapper.IsControlNearest(m_HoveredEdgeControlID);
if (action == MeshEditorAction.SelectVertex)
return guiWrapper.IsControlNearest(m_HoveredVertexControlID);
if (action == MeshEditorAction.SelectEdge)
return mode == SpriteMeshViewMode.EditGeometry &&
guiWrapper.IsControlNearest(m_HoveredEdgeControlID) &&
!canCreateEdge && !canSplitEdge;
if (action == MeshEditorAction.Remove)
return true;
return false;
}
public bool IsActionHot(MeshEditorAction action)
{
if (action == MeshEditorAction.None)
return guiWrapper.IsControlHot(0);
if (action == MeshEditorAction.MoveVertex)
return guiWrapper.IsControlHot(m_HoveredVertexControlID);
if (action == MeshEditorAction.MoveEdge)
return guiWrapper.IsControlHot(m_HoveredEdgeControlID);
return false;
}
public bool IsActionTriggered(MeshEditorAction action)
{
if (!IsActionActive(action))
return false;
if (action == MeshEditorAction.CreateVertex)
{
if (mode == SpriteMeshViewMode.EditGeometry)
return guiWrapper.IsMouseDown(0) && guiWrapper.clickCount == 2;
}
if (action == MeshEditorAction.Remove)
{
if ((guiWrapper.eventType == EventType.ValidateCommand || guiWrapper.eventType == EventType.ExecuteCommand)
&& (guiWrapper.commandName == kSoftDeleteCommandName || guiWrapper.commandName == kDeleteCommandName))
{
if (guiWrapper.eventType == EventType.ExecuteCommand)
return true;
guiWrapper.UseCurrentEvent();
}
return false;
}
if(action != MeshEditorAction.None)
return guiWrapper.IsMouseDown(0);
return false;
}
public Vector2 WorldToScreen(Vector2 position)
{
return HandleUtility.WorldToGUIPoint(position);
}
private void ConsumeMouseMoveEvents()
{
if (guiWrapper.eventType == EventType.MouseMove || (guiWrapper.eventType == EventType.MouseDrag && guiWrapper.mouseButton == 0))
guiWrapper.UseCurrentEvent();
}
private bool CanCreateEdge()
{
if(!frame.Contains(mouseWorldPosition) || !(guiWrapper.IsControlNearest(defaultControlID) || guiWrapper.IsControlNearest(m_HoveredVertexControlID) || guiWrapper.IsControlNearest(m_HoveredEdgeControlID)))
return false;
if (mode == SpriteMeshViewMode.EditGeometry)
return guiWrapper.isShiftDown && selection.Count == 1 && !selection.Contains(hoveredVertex);
if (mode == SpriteMeshViewMode.CreateEdge)
return selection.Count == 1 && !selection.Contains(hoveredVertex);
return false;
}
private bool CanSplitEdge()
{
if(!frame.Contains(mouseWorldPosition) || !(guiWrapper.IsControlNearest(defaultControlID) || guiWrapper.IsControlNearest(m_HoveredEdgeControlID)))
return false;
if (mode == SpriteMeshViewMode.EditGeometry)
return guiWrapper.isShiftDown && m_NearestEdge != -1 && hoveredVertex == -1 && selection.Count == 0;
if (mode == SpriteMeshViewMode.SplitEdge)
return m_NearestEdge != -1 && hoveredVertex == -1;
return false;
}
private void DrawEdge(Vector2 startPosition, Vector2 endPosition, Color color)
{
if (guiWrapper.eventType != EventType.Repaint)
return;
Handles.color = color;
float width = kEdgeWidth / Handles.matrix.m00;
DrawingUtility.DrawSolidLine(width, startPosition, endPosition);
}
public void DoRepaint()
{
if(guiWrapper.eventType != EventType.Layout)
return;
var action = MeshEditorAction.None;
if(IsActionActive(MeshEditorAction.CreateVertex))
action = MeshEditorAction.CreateVertex;
else if(IsActionActive(MeshEditorAction.CreateEdge))
action = MeshEditorAction.CreateEdge;
else if(IsActionActive(MeshEditorAction.SplitEdge))
action = MeshEditorAction.SplitEdge;
if(m_PreviousActiveAction != action)
{
m_PreviousActiveAction = action;
guiWrapper.Repaint();
}
}
public bool CanRepaint()
{
return guiWrapper.eventType == EventType.Repaint;
}
public bool CanLayout()
{
return guiWrapper.eventType == EventType.Layout;
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class UnselectTool<T>
{
private Unselector<T> m_Unselector = new Unselector<T>();
public ICacheUndo cacheUndo { get; set; }
public ISelection<T> selection
{
get { return m_Unselector.selection; }
set { m_Unselector.selection = value; }
}
public Action onUnselect = () => {};
public void OnGUI()
{
Debug.Assert(cacheUndo != null);
Debug.Assert(selection != null);
var e = Event.current;
if (selection.Count > 0 && e.type == EventType.MouseDown && e.button == 1 && !e.alt)
{
cacheUndo.BeginUndoOperation(TextContent.clearSelection);
m_Unselector.Select();
e.Use();
onUnselect.Invoke();
}
}
}
}

View File

@@ -0,0 +1,129 @@
using UnityEngine;
using UnityEditor.U2D.Sprites;
namespace UnityEditor.U2D.Animation
{
internal class WeightInspector
{
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private GUIContent[] m_BoneNameContents;
public ISpriteMeshData spriteMeshData
{
get { return m_SpriteMeshDataController.spriteMeshData; }
set
{
if (spriteMeshData != value)
m_SpriteMeshDataController.spriteMeshData = value;
}
}
public GUIContent[] boneNames
{
get { return m_BoneNameContents; }
set { m_BoneNameContents = value; }
}
public ICacheUndo cacheUndo { get; set; }
public ISelection<int> selection { get; set; }
public int controlID { get { return 0; } }
private bool m_UndoRegistered = false;
protected ISpriteEditor spriteEditor
{
get; private set;
}
public void OnInspectorGUI()
{
ChannelsGUI();
}
private void ChannelsGUI()
{
if (GUIUtility.hotControl == 0)
m_UndoRegistered = false;
for (int channel = 0; channel < 4; ++channel)
{
var enabled = false;
var boneIndex = -1;
var weight = 0f;
var isChannelEnabledMixed = false;
var isBoneIndexMixed = false;
var isWeightMixed = false;
if (spriteMeshData != null)
m_SpriteMeshDataController.GetMultiEditChannelData(selection, channel, out enabled, out boneIndex, out weight, out isChannelEnabledMixed, out isBoneIndexMixed, out isWeightMixed);
var newEnabled = enabled;
var newBoneIndex = boneIndex;
var newWeight = weight;
EditorGUI.BeginChangeCheck();
WeightChannelDrawer(ref newEnabled, ref newBoneIndex, ref newWeight, isChannelEnabledMixed, isBoneIndexMixed, isWeightMixed);
if (EditorGUI.EndChangeCheck())
{
RegisterUndo();
m_SpriteMeshDataController.SetMultiEditChannelData(selection, channel, enabled, newEnabled, boneIndex, newBoneIndex, weight, newWeight);
}
}
}
private void WeightChannelDrawer(
ref bool isChannelEnabled, ref int boneIndex, ref float weight,
bool isChannelEnabledMixed = false, bool isBoneIndexMixed = false, bool isWeightMixed = false)
{
EditorGUILayout.BeginHorizontal();
EditorGUIUtility.fieldWidth = 1f;
EditorGUIUtility.labelWidth = 1f;
EditorGUI.showMixedValue = isChannelEnabledMixed;
isChannelEnabled = EditorGUILayout.Toggle(GUIContent.none, isChannelEnabled);
EditorGUIUtility.fieldWidth = 30f;
EditorGUIUtility.labelWidth = 30f;
using (new EditorGUI.DisabledScope(!isChannelEnabled && !isChannelEnabledMixed))
{
int tempBoneIndex = GUI.enabled ? boneIndex : -1;
EditorGUI.BeginChangeCheck();
EditorGUIUtility.fieldWidth = 80f;
EditorGUI.showMixedValue = GUI.enabled && isBoneIndexMixed;
tempBoneIndex = EditorGUILayout.Popup(tempBoneIndex, m_BoneNameContents);
if (EditorGUI.EndChangeCheck())
boneIndex = tempBoneIndex;
EditorGUIUtility.fieldWidth = 32f;
EditorGUI.showMixedValue = isWeightMixed;
weight = EditorGUILayout.Slider(GUIContent.none, weight, 0f, 1f);
}
EditorGUILayout.EndHorizontal();
EditorGUI.showMixedValue = false;
EditorGUIUtility.labelWidth = -1;
EditorGUIUtility.fieldWidth = -1;
}
private void RegisterUndo()
{
if (m_UndoRegistered)
return;
Debug.Assert(cacheUndo != null);
cacheUndo.BeginUndoOperation(TextContent.editWeights);
m_UndoRegistered = true;
}
}
}

View File

@@ -0,0 +1,30 @@
using System.IO;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class IconUtility
{
static public readonly string k_LightIconResourcePath = "SkinningModule/Icons/Light";
static public readonly string k_DarkIconResourcePath = "SkinningModule/Icons/Dark";
static public readonly string k_SelectedResourceIconPath = "SkinningModule/Icons/Selected";
public static Texture2D LoadIconResource(string name, string personalPath, string proPath)
{
string iconPath = "";
if (EditorGUIUtility.isProSkin && !string.IsNullOrEmpty(proPath))
iconPath = Path.Combine(proPath, "d_" + name);
else
iconPath = Path.Combine(personalPath, name);
if (EditorGUIUtility.pixelsPerPoint > 1.0f)
{
var icon2x = ResourceLoader.Load<Texture2D>(iconPath + "@2x.png");
if (icon2x != null)
return icon2x;
}
return ResourceLoader.Load<Texture2D>(iconPath+".png");
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class MathUtility
{
public static float DistanceToSegmentClamp(Vector3 p, Vector3 p1, Vector3 p2)
{
float l2 = (p2 - p1).sqrMagnitude; // i.e. |b-a|^2 - avoid a sqrt
if (l2 == 0.0)
return float.MaxValue; // a == b case
float t = Vector3.Dot(p - p1, p2 - p1) / l2;
if (t < 0.0)
return float.MaxValue; // Beyond the 'a' end of the segment
if (t > 1.0)
return float.MaxValue; // Beyond the 'b' end of the segment
Vector3 projection = p1 + t * (p2 - p1); // Projection falls on the segment
return (p - projection).magnitude;
}
public static Vector2 ClampPositionToRect(Vector2 position, Rect rect)
{
return new Vector2(Mathf.Clamp(position.x, rect.xMin, rect.xMax), Mathf.Clamp(position.y, rect.yMin, rect.yMax));
}
public static Vector2 MoveRectInsideFrame(Rect rect, Rect frame, Vector2 delta)
{
if (frame.size.x <= rect.size.x)
delta.x = 0f;
if (frame.size.y <= rect.size.y)
delta.y = 0f;
Vector2 min = rect.min + delta;
Vector2 max = rect.max + delta;
Vector2 size = rect.size;
Vector2 position = rect.position;
max.x = Mathf.Clamp(max.x, frame.min.x, frame.max.x);
max.y = Mathf.Clamp(max.y, frame.min.y, frame.max.y);
min = max - size;
min.x = Mathf.Clamp(min.x, frame.min.x, frame.max.x);
min.y = Mathf.Clamp(min.y, frame.min.y, frame.max.y);
max = min + size;
rect.min = min;
rect.max = max;
delta = rect.position - position;
return delta;
}
public static bool SegmentIntersection(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, ref Vector2 point)
{
Vector2 s1 = p1 - p0;
Vector2 s2 = p3 - p2;
float s, t, determinant;
determinant = (s1.x * s2.y - s2.x * s1.y);
if (Mathf.Approximately(determinant, 0f))
return false;
s = (-s1.y * (p0.x - p2.x) + s1.x * (p0.y - p2.y)) / determinant;
t = (s2.x * (p0.y - p2.y) - s2.y * (p0.x - p2.x)) / determinant;
if (s >= 0f && s <= 1f && t >= 0f && t <= 1f)
{
point = p0 + (t * s1);
return true;
}
return false;
}
//https://gamedev.stackexchange.com/a/49370
public static void Barycentric(Vector2 p, Vector2 a, Vector2 b, Vector2 c, out Vector3 coords)
{
Vector2 v0 = b - a, v1 = c - a, v2 = p - a;
float d00 = Vector2.Dot(v0, v0);
float d01 = Vector2.Dot(v0, v1);
float d11 = Vector2.Dot(v1, v1);
float d20 = Vector2.Dot(v2, v0);
float d21 = Vector2.Dot(v2, v1);
float invDenom = 1f / (d00 * d11 - d01 * d01);
coords.y = (d11 * d20 - d01 * d21) * invDenom;
coords.z = (d00 * d21 - d01 * d20) * invDenom;
coords.x = 1f - coords.y - coords.z;
}
public static Quaternion NormalizeQuaternion(Quaternion q)
{
Vector4 v = new Vector4(q.x, q.y, q.z, q.w).normalized;
return new Quaternion(v.x, v.y, v.z, v.w);
}
//From: https://answers.unity.com/questions/861719/a-fast-triangle-triangle-intersection-algorithm-fo.html
public static bool Intersect(Vector3 p1, Vector3 p2, Vector3 p3, Ray ray)
{
Vector3 e1, e2;
Vector3 p, q, t;
float det, invDet, u, v;
e1 = p2 - p1;
e2 = p3 - p1;
p = Vector3.Cross(ray.direction, e2);
det = Vector3.Dot(e1, p);
if (Mathf.Approximately(det, 0f))
return false;
invDet = 1.0f / det;
t = ray.origin - p1;
u = Vector3.Dot(t, p) * invDet;
if (u < 0 || u > 1)
return false;
q = Vector3.Cross(t, e1);
v = Vector3.Dot(ray.direction, q) * invDet;
if (v < 0 || u + v > 1)
return false;
if ((Vector3.Dot(e2, q) * invDet) > 0f)
return true;
return false;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface IMeshPreviewBehaviour
{
float GetWeightMapOpacity(SpriteCache sprite);
bool DrawWireframe(SpriteCache sprite);
bool Overlay(SpriteCache sprite);
bool OverlayWireframe(SpriteCache sprite);
}
}

View File

@@ -0,0 +1,73 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class DefaultPreviewBehaviour : IMeshPreviewBehaviour
{
public float GetWeightMapOpacity(SpriteCache sprite)
{
return 0f;
}
public bool DrawWireframe(SpriteCache sprite)
{
return false;
}
public bool Overlay(SpriteCache sprite)
{
return false;
}
public bool OverlayWireframe(SpriteCache sprite)
{
return sprite.IsVisible() && sprite.skinningCache.selectedSprite == sprite;
}
}
internal class MeshPreviewBehaviour : IMeshPreviewBehaviour
{
public bool showWeightMap { get; set; }
public bool drawWireframe { get; set; }
public bool overlaySelected { get; set; }
public float GetWeightMapOpacity(SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (showWeightMap)
{
if (skinningCache.selectedSprite == sprite || skinningCache.selectedSprite == null)
return VisibilityToolSettings.meshOpacity;
}
return 0f;
}
public bool DrawWireframe(SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (drawWireframe)
return skinningCache.selectedSprite == null;
return false;
}
public bool Overlay(SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (overlaySelected && skinningCache.selectedSprite == sprite)
return true;
return false;
}
public bool OverlayWireframe(SpriteCache sprite)
{
return sprite.IsVisible() && sprite.skinningCache.selectedSprite == sprite;
}
}
}

View File

@@ -0,0 +1,331 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class MeshPreviewTool : BaseTool
{
[SerializeField]
private Material m_Material;
private List<SpriteCache> m_Sprites;
private IMeshPreviewBehaviour m_DefaultPreviewBehaviour = new DefaultPreviewBehaviour();
public IMeshPreviewBehaviour previewBehaviourOverride { get; set; }
public override IMeshPreviewBehaviour previewBehaviour
{
get
{
if (previewBehaviourOverride != null)
return previewBehaviourOverride;
return m_DefaultPreviewBehaviour;
}
}
internal override void OnCreate()
{
m_Material = new Material(Shader.Find("Hidden/SkinningModule-GUITextureClip"));
m_Material.hideFlags = HideFlags.DontSave;
}
internal override void OnDestroy()
{
base.OnDestroy();
Debug.Assert(m_Material != null);
DestroyImmediate(m_Material);
}
protected override void OnActivate()
{
m_Sprites = new List<SpriteCache>(skinningCache.GetSprites());
DirtyMeshesAll();
skinningCache.events.meshChanged.AddListener(MeshChanged);
skinningCache.events.characterPartChanged.AddListener(CharacterPartChanged);
skinningCache.events.skeletonPreviewPoseChanged.AddListener(SkeletonChanged);
skinningCache.events.skeletonBindPoseChanged.AddListener(SkeletonChanged);
skinningCache.events.skinningModeChanged.AddListener(SkinningModuleModeChanged);
}
protected override void OnDeactivate()
{
skinningCache.events.meshChanged.RemoveListener(MeshChanged);
skinningCache.events.skeletonPreviewPoseChanged.RemoveListener(SkeletonChanged);
skinningCache.events.skeletonBindPoseChanged.RemoveListener(SkeletonChanged);
skinningCache.events.skinningModeChanged.RemoveListener(SkinningModuleModeChanged);
}
protected override void OnGUI()
{
Prepare();
if (Event.current.type == EventType.Repaint)
{
//DrawDefaultSpriteMeshes();
DrawSpriteMeshes();
}
}
public void DrawOverlay()
{
if (Event.current.type != EventType.Repaint)
return;
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
foreach (var sprite in m_Sprites)
{
if (previewBehaviour.Overlay(sprite))
DrawSpriteMesh(sprite);
}
}
else
{
var character = skinningCache.character;
Debug.Assert(character != null);
var parts = character.parts;
foreach (var part in parts)
{
if (part.isVisible && previewBehaviour.Overlay(part.sprite))
DrawSpriteMesh(part.sprite);
}
}
}
public void OverlayWireframe()
{
if (Event.current.type != EventType.Repaint)
return;
foreach (var sprite in m_Sprites)
if (previewBehaviour.OverlayWireframe(sprite))
DrawWireframe(sprite);
}
private void CharacterPartChanged(CharacterPartCache characterPart)
{
var meshPreview = characterPart.sprite.GetMeshPreview();
Debug.Assert(meshPreview != null);
meshPreview.SetSkinningDirty();
}
private void MeshChanged(MeshCache mesh)
{
var meshPreview = mesh.sprite.GetMeshPreview();
Debug.Assert(meshPreview != null);
meshPreview.SetMeshDirty();
}
private void SkeletonChanged(SkeletonCache skeleton)
{
DirtySkinningAll();
}
private void SkinningModuleModeChanged(SkinningMode mode)
{
DirtyMeshesAll();
}
private void DirtyMeshesAll()
{
foreach (var sprite in m_Sprites)
{
var meshPreview = sprite.GetMeshPreview();
if (meshPreview != null)
meshPreview.SetMeshDirty();
}
}
private void DirtySkinningAll()
{
foreach (var sprite in m_Sprites)
{
var meshPreview = sprite.GetMeshPreview();
Debug.Assert(meshPreview != null);
meshPreview.SetSkinningDirty();
}
}
private void Prepare()
{
foreach (var sprite in m_Sprites)
{
var meshPreview = sprite.GetMeshPreview();
Debug.Assert(meshPreview != null);
meshPreview.enableSkinning = true;
meshPreview.Prepare();
}
}
private void DrawDefaultSpriteMeshes()
{
Debug.Assert(Event.current.type == EventType.Repaint);
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
foreach (var sprite in m_Sprites)
DrawDefaultSpriteMesh(sprite);
}
else
{
var character = skinningCache.character;
Debug.Assert(character != null);
var parts = character.parts;
foreach (var part in parts)
{
if (part.isVisible)
DrawDefaultSpriteMesh(part.sprite);
}
}
}
private void DrawDefaultSpriteMesh(SpriteCache sprite)
{
Debug.Assert(m_Material != null);
var meshPreview = sprite.GetMeshPreview();
var meshCache = sprite.GetMesh();
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
Debug.Assert(meshPreview != null);
if (meshPreview.canSkin == false || skeleton.isPosePreview == false)
{
m_Material.mainTexture = meshCache.textureDataProvider.texture;
m_Material.SetFloat("_Opacity", 1f);
m_Material.SetFloat("_VertexColorBlend", 0f);
m_Material.color = new Color(1f, 1f, 1f, 1f);
DrawingUtility.DrawMesh(meshPreview.defaultMesh, m_Material, sprite.GetLocalToWorldMatrixFromMode());
}
}
private void DrawSpriteMeshes()
{
Debug.Assert(Event.current.type == EventType.Repaint);
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
foreach (var sprite in m_Sprites)
{
if (previewBehaviour.Overlay(sprite))
continue;
DrawSpriteMesh(sprite);
}
}
else
{
var character = skinningCache.character;
Debug.Assert(character != null);
var parts = character.parts;
var selected = skinningCache.selectedSprite;
var selectedVisible = false;
foreach (var part in parts)
{
if (previewBehaviour.Overlay(part.sprite))
continue;
if (part.sprite == selected)
selectedVisible = part.isVisible;
else if (part.isVisible)
DrawSpriteMesh(part.sprite);
}
if (selectedVisible && selected != null)
DrawSpriteMesh(selected);
}
}
private void DrawSpriteMesh(SpriteCache sprite)
{
var weightMapOpacity = previewBehaviour.GetWeightMapOpacity(sprite);
DrawSpriteMesh(sprite, weightMapOpacity);
if (previewBehaviour.DrawWireframe(sprite))
DrawWireframe(sprite);
}
private void DrawSpriteMesh(SpriteCache sprite, float weightMapOpacity)
{
Debug.Assert(m_Material != null);
var meshPreview = sprite.GetMeshPreview();
var meshCache = sprite.GetMesh();
Debug.Assert(meshPreview != null);
if (meshPreview.mesh == null || meshPreview.mesh.vertexCount == 0)
{
DrawDefaultSpriteMesh(sprite);
}
else
{
m_Material.mainTexture = meshCache.textureDataProvider.texture;
m_Material.SetFloat("_Opacity", 1f);
m_Material.SetFloat("_VertexColorBlend", weightMapOpacity);
m_Material.color = Color.white;
DrawingUtility.DrawMesh(meshPreview.mesh, m_Material, sprite.GetLocalToWorldMatrixFromMode());
}
}
private void DrawSelectedSpriteWeightMap()
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite != null)
{
var opacity = GetWeightOpacityFromCurrentTool();
if (opacity > 0f)
DrawSpriteMesh(selectedSprite, opacity);
}
}
private float GetWeightOpacityFromCurrentTool()
{
return IsWeightTool() ? VisibilityToolSettings.meshOpacity : 0f;
}
private bool IsWeightTool()
{
var currentTool = skinningCache.selectedTool;
if (currentTool == skinningCache.GetTool(Tools.WeightSlider) ||
currentTool == skinningCache.GetTool(Tools.WeightBrush) ||
currentTool == skinningCache.GetTool(Tools.BoneInfluence) ||
currentTool == skinningCache.GetTool(Tools.GenerateWeights))
return true;
return false;
}
private void DrawWireframe(SpriteCache sprite)
{
Debug.Assert(Event.current.type == EventType.Repaint);
Debug.Assert(sprite != null);
var meshPreview = sprite.GetMeshPreview();
Debug.Assert(meshPreview != null);
m_Material.mainTexture = null;
m_Material.SetFloat("_Opacity", 0.35f);
m_Material.SetFloat("_VertexColorBlend", 0f);
m_Material.color = Color.white;
GL.wireframe = true;
DrawingUtility.DrawMesh(meshPreview.mesh, m_Material, sprite.GetLocalToWorldMatrixFromMode());
GL.wireframe = false;
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal partial class MeshTool : BaseTool
{
private MeshCache m_Mesh;
private ISelection<int> m_SelectionOverride;
private SpriteMeshController m_SpriteMeshController;
private SpriteMeshView m_SpriteMeshView;
private RectSelectionTool<int> m_RectSelectionTool = new RectSelectionTool<int>();
private RectVertexSelector m_RectVertexSelector = new RectVertexSelector();
private UnselectTool<int> m_UnselectTool = new UnselectTool<int>();
private ITriangulator m_Triangulator;
public MeshCache mesh
{
get { return m_Mesh; }
}
public SpriteMeshViewMode mode
{
get { return m_SpriteMeshView.mode; }
set { m_SpriteMeshView.mode = value; }
}
public bool disable
{
get { return m_SpriteMeshController.disable; }
set { m_SpriteMeshController.disable = value; }
}
public ISelection<int> selectionOverride
{
get { return m_SelectionOverride; }
set { m_SelectionOverride = value; }
}
public override int defaultControlID
{
get
{
if (m_Mesh == null)
return 0;
return m_RectSelectionTool.controlID;
}
}
private ISelection<int> selection
{
get
{
if(selectionOverride != null)
return selectionOverride;
return skinningCache.vertexSelection;
}
}
internal override void OnCreate()
{
m_SpriteMeshController = new SpriteMeshController();
m_SpriteMeshView = new SpriteMeshView(new GUIWrapper());
m_Triangulator = new Triangulator();
}
protected override void OnActivate()
{
m_SpriteMeshController.disable = false;
m_SelectionOverride = null;
SetupSprite(skinningCache.selectedSprite);
skinningCache.events.selectedSpriteChanged.AddListener(OnSelectedSpriteChanged);
}
protected override void OnDeactivate()
{
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSelectedSpriteChanged);
}
private void OnSelectedSpriteChanged(SpriteCache sprite)
{
SetupSprite(sprite);
}
internal void SetupSprite(SpriteCache sprite)
{
var mesh = sprite.GetMesh();
if (m_Mesh != null
&& m_Mesh != mesh
&& selection.Count > 0)
selection.Clear();
m_Mesh = mesh;
m_SpriteMeshController.spriteMeshData = m_Mesh;
}
private void SetupGUI()
{
m_SpriteMeshController.spriteMeshView = m_SpriteMeshView;
m_SpriteMeshController.triangulator = m_Triangulator;
m_SpriteMeshController.cacheUndo = skinningCache;
m_RectSelectionTool.cacheUndo = skinningCache;
m_RectSelectionTool.rectSelector = m_RectVertexSelector;
m_RectVertexSelector.selection = selection;
m_UnselectTool.cacheUndo = skinningCache;
m_UnselectTool.selection = selection;
m_SpriteMeshController.frame = new Rect(Vector2.zero, m_Mesh.sprite.textureRect.size);
m_SpriteMeshController.selection = selection;
m_SpriteMeshView.defaultControlID = defaultControlID;
m_RectVertexSelector.spriteMeshData = m_Mesh;
}
protected override void OnGUI()
{
if (m_Mesh == null)
return;
SetupGUI();
var handlesMatrix = Handles.matrix;
Handles.matrix *= m_Mesh.sprite.GetLocalToWorldMatrixFromMode();
BeginPositionOverride();
EditorGUI.BeginChangeCheck();
var guiEnabled = GUI.enabled;
var moveAction = m_SpriteMeshController.spriteMeshView.IsActionHot(MeshEditorAction.MoveEdge) || m_SpriteMeshController.spriteMeshView.IsActionHot(MeshEditorAction.MoveVertex);
GUI.enabled = (!skinningCache.IsOnVisualElement() && guiEnabled) || moveAction;
m_SpriteMeshController.OnGUI();
GUI.enabled = guiEnabled;
if (EditorGUI.EndChangeCheck())
UpdateMesh();
m_RectSelectionTool.OnGUI();
m_UnselectTool.OnGUI();
Handles.matrix = handlesMatrix;
EndPositionOverride();
}
public void BeginPositionOverride()
{
if(m_Mesh != null)
{
m_Mesh.vertexPositionOverride = null;
var skeleton = skinningCache.GetEffectiveSkeleton(m_Mesh.sprite);
Debug.Assert(skeleton != null);
if (skeleton.isPosePreview)
m_Mesh.vertexPositionOverride = m_Mesh.sprite.GetMeshPreview().vertices;
}
}
public void EndPositionOverride()
{
if(m_Mesh != null)
m_Mesh.vertexPositionOverride = null;
}
public void UpdateWeights()
{
InvokeMeshChanged();
}
public void UpdateMesh()
{
InvokeMeshChanged();
}
private void InvokeMeshChanged()
{
if(m_Mesh != null)
skinningCache.events.meshChanged.Invoke(m_Mesh);
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class MeshToolWrapper : BaseTool
{
private MeshTool m_MeshTool;
private SkeletonTool m_SkeletonTool;
private SpriteMeshViewMode m_MeshMode;
private bool m_Disable = false;
private SkeletonMode m_SkeletonMode;
protected MeshPreviewBehaviour m_MeshPreviewBehaviour = new MeshPreviewBehaviour();
public MeshTool meshTool
{
get { return m_MeshTool; }
set { m_MeshTool = value; }
}
public SkeletonTool skeletonTool
{
get { return m_SkeletonTool; }
set { m_SkeletonTool = value; }
}
public SpriteMeshViewMode meshMode
{
get { return m_MeshMode; }
set { m_MeshMode = value; }
}
public bool disableMeshEditor
{
get { return m_Disable; }
set { m_Disable = value; }
}
public SkeletonMode skeletonMode
{
get { return m_SkeletonMode; }
set { m_SkeletonMode = value; }
}
public override int defaultControlID
{
get
{
Debug.Assert(meshTool != null);
return meshTool.defaultControlID;
}
}
public override IMeshPreviewBehaviour previewBehaviour
{
get { return m_MeshPreviewBehaviour; }
}
protected override void OnActivate()
{
Debug.Assert(meshTool != null);
skeletonTool.enableBoneInspector = false;
skeletonTool.Activate();
meshTool.Activate();
m_MeshPreviewBehaviour.drawWireframe = true;
m_MeshPreviewBehaviour.showWeightMap = false;
m_MeshPreviewBehaviour.overlaySelected = false;
}
protected override void OnDeactivate()
{
skeletonTool.Deactivate();
meshTool.Deactivate();
}
protected override void OnGUI()
{
DoSkeletonGUI();
DoMeshGUI();
}
protected void DoSkeletonGUI()
{
Debug.Assert(skeletonTool != null);
skeletonTool.mode = skeletonMode;
skeletonTool.editBindPose = false;
skeletonTool.DoGUI();
}
protected void DoMeshGUI()
{
Debug.Assert(meshTool != null);
meshTool.disable = disableMeshEditor;
meshTool.mode = meshMode;
meshTool.DoGUI();
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class ModuleToolGroup
{
class ToolGroupEntry
{
public BaseTool tool;
public Action activateCallback;
}
class ToolGroup
{
public int groupId;
public List<ToolGroupEntry> tools = new List<ToolGroupEntry>();
public int previousToolIndex;
}
List<ToolGroup> m_ToolGroups = new List<ToolGroup>();
public void AddToolToGroup(int groupId, BaseTool tool, Action toolActivatedCallback)
{
List<ToolGroupEntry> tools = null;
for (int i = 0; i < m_ToolGroups.Count; ++i)
{
if (m_ToolGroups[i].groupId == groupId)
{
tools = m_ToolGroups[i].tools;
}
var toolIndex = m_ToolGroups[i].tools.FindIndex(x => x.tool == tool);
if (toolIndex != -1)
{
Debug.LogError(string.Format("{0} already exist in group.", tool.name));
return;
}
}
if (tools == null)
{
var toolGroup = new ToolGroup()
{
groupId = groupId
};
tools = toolGroup.tools;
m_ToolGroups.Add(toolGroup);
}
tools.Add(new ToolGroupEntry()
{
tool = tool,
activateCallback = toolActivatedCallback
});
}
public void ActivateTool(BaseTool tool)
{
var toolGroupIndex = -1;
var groupTool = m_ToolGroups.FirstOrDefault(x =>
{
toolGroupIndex = x.tools.FindIndex(y => y.tool == tool);
return toolGroupIndex >= 0;
});
if (groupTool != null && toolGroupIndex >= 0)
{
var previousTool = groupTool.previousToolIndex >= 0 ? groupTool.tools[groupTool.previousToolIndex] : null;
if (tool.isActive) // we want to deactivate the tool and switch to original
{
tool.Deactivate();
if (previousTool != null && previousTool.tool != tool && previousTool.tool != null)
{
previousTool.tool.Activate();
groupTool.previousToolIndex = toolGroupIndex;
}
}
else
{
for (int i = 0; i < groupTool.tools.Count; ++i)
{
var gt = groupTool.tools[i];
if (gt.tool.isActive)
{
groupTool.previousToolIndex = i;
gt.tool.Deactivate();
}
}
tool.Activate();
if (groupTool.tools[toolGroupIndex].activateCallback != null)
groupTool.tools[toolGroupIndex].activateCallback();
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal static class ModuleUtility
{
public static Vector3 GUIToWorld(Vector3 guiPosition)
{
return GUIToWorld(guiPosition, Vector3.forward, Vector3.zero);
}
public static Vector3 GUIToWorld(Vector3 guiPosition, Vector3 planeNormal, Vector3 planePos)
{
Vector3 worldPos = Handles.inverseMatrix.MultiplyPoint(guiPosition);
if (Camera.current)
{
Ray ray = HandleUtility.GUIPointToWorldRay(guiPosition);
planeNormal = Handles.matrix.MultiplyVector(planeNormal);
planePos = Handles.matrix.MultiplyPoint(planePos);
Plane plane = new Plane(planeNormal, planePos);
float distance = 0f;
if (plane.Raycast(ray, out distance))
{
worldPos = Handles.inverseMatrix.MultiplyPoint(ray.GetPoint(distance));
}
}
return worldPos;
}
public static GUIContent[] ToGUIContentArray(string[] names)
{
return Array.ConvertAll(names, n => new GUIContent(n));
}
public static Color CalculateNiceColor(int index, int numColors)
{
numColors = Mathf.Clamp(numColors, 1, int.MaxValue);
int loops = index / numColors;
index = index % 360;
int hueAngleStep = 360 / numColors;
float hueLoopOffset = hueAngleStep * 0.5f;
float hue = index * hueAngleStep + loops * hueLoopOffset;
return Color.HSVToRGB(Mathf.Repeat(hue, 360f) / 360f, 1f, 1f);
}
public static void UpdateLocalToWorldMatrices(List<SpriteBoneData> spriteBoneDataList, Matrix4x4 rootMatrix, ref Matrix4x4[] localToWorldMatrices)
{
if (localToWorldMatrices == null || localToWorldMatrices.Length != spriteBoneDataList.Count)
localToWorldMatrices = new Matrix4x4[spriteBoneDataList.Count];
bool[] calculatedMatrix = new bool[spriteBoneDataList.Count];
var processedBoneCount = 0;
while (processedBoneCount < spriteBoneDataList.Count)
{
int oldCount = processedBoneCount;
for (var i = 0; i < spriteBoneDataList.Count; ++i)
{
if (calculatedMatrix[i])
continue;
var sourceBone = spriteBoneDataList[i];
if (sourceBone.parentId != -1 && !calculatedMatrix[sourceBone.parentId])
continue;
var localToWorldMatrix = Matrix4x4.identity;
localToWorldMatrix.SetTRS(sourceBone.localPosition, sourceBone.localRotation, Vector3.one);
if (sourceBone.parentId == -1)
localToWorldMatrix = rootMatrix * localToWorldMatrix;
else if (calculatedMatrix[sourceBone.parentId])
localToWorldMatrix = localToWorldMatrices[sourceBone.parentId] * localToWorldMatrix;
localToWorldMatrices[i] = localToWorldMatrix;
calculatedMatrix[i] = true;
processedBoneCount++;
}
if (oldCount == processedBoneCount)
throw new ArgumentException("Invalid hierarchy detected");
}
}
public static List<SpriteBoneData> CreateSpriteBoneData(UnityEngine.U2D.SpriteBone[] spriteBoneList, Matrix4x4 rootMatrix)
{
List<SpriteBoneData> spriteBoneDataList = new List<SpriteBoneData>(spriteBoneList.Length);
foreach (var spriteBone in spriteBoneList)
{
spriteBoneDataList.Add(new SpriteBoneData()
{
name = spriteBone.name,
parentId = spriteBone.parentId,
localPosition = spriteBone.position,
localRotation = spriteBone.rotation,
depth = spriteBone.position.z,
length = spriteBone.length
});
}
Matrix4x4[] localToWorldMatrices = null;
UpdateLocalToWorldMatrices(spriteBoneDataList, rootMatrix, ref localToWorldMatrices);
for (int i = 0; i < spriteBoneDataList.Count; ++i)
{
var spriteBoneData = spriteBoneDataList[i];
spriteBoneData.position = localToWorldMatrices[i].MultiplyPoint(Vector2.zero);
spriteBoneData.endPosition = localToWorldMatrices[i].MultiplyPoint(Vector2.right * spriteBoneData.length);
}
return spriteBoneDataList;
}
}
}

View File

@@ -0,0 +1,10 @@
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface IOutlineGenerator
{
void GenerateOutline(ITextureDataProvider textureDataProvider, Rect rect, float detail, byte alphaTolerance, bool holeDetection, out Vector2[][] paths);
}
}

View File

@@ -0,0 +1,377 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.U2D.Common;
using UnityEditor.U2D.Animation.ClipperLib;
using UnityEditor.U2D.Sprites;
namespace UnityEditor.U2D.Animation
{
using Path = List<IntPoint>;
using Paths = List<List<IntPoint>>;
internal class OutlineGenerator : IOutlineGenerator
{
const double kClipperScale = 1000.0;
private const float kEpsilon = 1.2e-12f;
private const float kMinLinearizeDistance = 5f;
private Texture2D m_CurrentTexture;
private Rect m_CurrentRect;
private byte m_CurrentAlphaTolerance;
public void GenerateOutline(ITextureDataProvider textureDataProvider, Rect rect, float detail, byte alphaTolerance, bool holeDetection, out Vector2[][] paths)
{
if (alphaTolerance >= 255)
throw new ArgumentException("Alpha tolerance should be lower than 255");
m_CurrentTexture = textureDataProvider.GetReadableTexture2D();
m_CurrentRect = rect;
m_CurrentAlphaTolerance = alphaTolerance;
InternalEditorBridge.GenerateOutline(textureDataProvider.texture, rect, 1f, alphaTolerance, holeDetection, out paths);
if (paths.Length > 0)
{
ClipPaths(ref paths);
Debug.Assert(paths.Length > 0);
var rectSizeMagnitude = rect.size.magnitude;
var minDistance = Mathf.Max(rectSizeMagnitude / 10f, kMinLinearizeDistance);
var maxDistance = Mathf.Max(rectSizeMagnitude / 100f, kMinLinearizeDistance);
var distance = Mathf.Lerp(minDistance, maxDistance, detail);
for (var pathIndex = 0; pathIndex < paths.Length; ++pathIndex)
{
var pathLength = CalculatePathLength(paths[pathIndex]);
if (pathLength > distance)
{
var newPath = Linearize(new List<Vector2>(paths[pathIndex]), distance);
if (newPath.Count > 3)
paths[pathIndex] = newPath.ToArray();
SmoothPath(paths[pathIndex], 5, 0.1f, 135f);
}
}
ClipPaths(ref paths);
}
}
private void ClipPaths(ref Vector2[][] paths)
{
Debug.Assert(paths.Length > 0);
var subj = ToClipper(paths);
var solution = new Paths();
var clipper = new Clipper(Clipper.ioPreserveCollinear);
clipper.AddPaths(subj, PolyType.ptSubject, true);
clipper.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive);
FilterNestedPaths(solution);
paths = ToVector2(solution);
}
private void FilterNestedPaths(Paths paths)
{
var filtered = new List<Path>(paths);
for (var i = 0; i < paths.Count; ++i)
{
var path = paths[i];
if (!filtered.Contains(path))
continue;
for (var j = i + 1; j < paths.Count; ++j)
{
if (!filtered.Contains(path))
continue;
var other = paths[j];
if (IsPathContainedInOtherPath(path, other))
{
filtered.Remove(path);
break;
}
else if (IsPathContainedInOtherPath(other, path))
filtered.Remove(other);
}
}
paths.Clear();
paths.AddRange(filtered);
}
private bool IsPathContainedInOtherPath(Path path, Path other)
{
foreach (var p in path)
{
if (Clipper.PointInPolygon(p, other) < 1)
return false;
}
return true;
}
private Paths ToClipper(Vector2[][] paths)
{
return new Paths(Array.ConvertAll(paths, p => ToClipper(p)));
}
private Path ToClipper(Vector2[] path)
{
return new Path(Array.ConvertAll(path, p => new IntPoint(p.x * kClipperScale, p.y * kClipperScale)));
}
private Vector2[][] ToVector2(Paths paths)
{
return paths.ConvertAll(p => ToVector2(p)).ToArray();
}
private Vector2[] ToVector2(Path path)
{
return path.ConvertAll(p => new Vector2((float)(p.X / kClipperScale), (float)(p.Y / kClipperScale))).ToArray();
}
private float CalculatePathLength(Vector2[] path)
{
var sum = 0f;
for (var i = 0; i < path.Length; i++)
{
var nextIndex = NextIndex(i, path.Length);
var p0 = path[i];
var p1 = path[nextIndex];
sum += Vector2.Distance(p0, p1);
}
return sum;
}
//Adapted from https://github.com/burningmime/curves
private List<Vector2> Linearize(List<Vector2> src, float pointDistance)
{
if (src == null) throw new ArgumentNullException("src");
if (pointDistance <= kEpsilon) throw new InvalidOperationException("pointDistance " + pointDistance + " is less than epislon " + kEpsilon);
var dst = new List<Vector2>();
if (src.Count > 0)
{
var accDistance = 0f;
var lastIndex = 0;
var lastPoint = src[0];
dst.Add(lastPoint);
for (var i = 0; i < src.Count; i++)
{
var nextIndex = NextIndex(i, src.Count);
var p0 = src[i];
var p1 = src[nextIndex];
var edgeDistance = Vector2.Distance(p0, p1);
if (accDistance + edgeDistance > pointDistance || nextIndex == 0)
{
var partialDistance = pointDistance - accDistance;
var newPoint = Vector2.Lerp(p0, p1, partialDistance / edgeDistance);
var remainingDistance = edgeDistance - partialDistance;
//Roll back until we do not intersect any pixel
var step = 1f;
bool finish = false;
while (!finish && IsLineOverImage(newPoint, lastPoint))
{
partialDistance = Vector2.Distance(p0, newPoint) - step;
while (partialDistance < 0f)
{
if (i > lastIndex + 1)
{
accDistance -= edgeDistance;
--i;
p1 = p0;
p0 = src[i];
edgeDistance = Vector2.Distance(p0, p1);
partialDistance += edgeDistance;
}
else
{
partialDistance = 0f;
finish = true;
}
remainingDistance = edgeDistance - partialDistance;
}
newPoint = Vector2.Lerp(p0, p1, partialDistance / edgeDistance);
}
Debug.Assert(lastIndex <= i, "Generate Outline failed");
nextIndex = NextIndex(i, src.Count);
if (nextIndex != 0 || !EqualsOrClose(newPoint, p1))
{
dst.Add(newPoint);
lastPoint = newPoint;
lastIndex = i;
}
while (remainingDistance > pointDistance)
{
remainingDistance -= pointDistance;
newPoint = Vector2.Lerp(p0, p1, (edgeDistance - remainingDistance) / edgeDistance);
if (!EqualsOrClose(newPoint, lastPoint))
{
dst.Add(newPoint);
lastPoint = newPoint;
}
}
accDistance = remainingDistance;
}
else
{
accDistance += edgeDistance;
}
}
}
return dst;
}
private bool EqualsOrClose(Vector2 v1, Vector2 v2)
{
return (v1 - v2).sqrMagnitude < kEpsilon;
}
private void SmoothPath(Vector2[] path, int iterations, float velocity, float minAngle)
{
Debug.Assert(iterations > 0f);
Debug.Assert(minAngle >= 0f);
Debug.Assert(minAngle < 180f);
var cosTolerance = Mathf.Cos(minAngle * Mathf.Deg2Rad);
for (int iteration = 0; iteration < iterations; ++iteration)
for (int i = 0; i < path.Length; ++i)
{
var prevPoint = path[PreviousIndex(i, path.Length)];
var point = path[i];
var nextPoint = path[NextIndex(i, path.Length)];
var t1 = prevPoint - point;
var t2 = nextPoint - point;
var dot = Vector2.Dot(t1.normalized, t2.normalized);
if (dot > cosTolerance)
continue;
var w1 = 1f / (point - prevPoint).magnitude;
var w2 = 1f / (point - nextPoint).magnitude;
var laplacian = (w1 * prevPoint + w2 * nextPoint) / (w1 + w2) - point;
point += laplacian * velocity;
if (!IsLineOverImage(point, nextPoint) && !IsLineOverImage(point, prevPoint))
path[i] = point;
}
}
private Vector2Int ToVector2Int(Vector2 v)
{
return new Vector2Int(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y));
}
private bool IsLineOverImage(Vector2 pointA, Vector2 pointB)
{
var pointAInt = ToVector2Int(pointA);
var pointBInt = ToVector2Int(pointB);
if (IsPointInRectEdge(pointA) && IsPointInRectEdge(pointB) && (pointAInt.x == pointBInt.x || pointAInt.y == pointBInt.y))
return false;
foreach (var point in GetPointsOnLine(pointAInt.x, pointAInt.y, pointBInt.x, pointBInt.y))
{
if (IsPointOverImage(point))
return true;
}
return false;
}
private bool IsPointOverImage(Vector2 point)
{
Debug.Assert(m_CurrentTexture != null);
point += m_CurrentRect.center;
return m_CurrentTexture.GetPixel((int)point.x, (int)point.y).a * 255 > m_CurrentAlphaTolerance;
}
private bool IsPointInRectEdge(Vector2 point)
{
point += m_CurrentRect.center;
var pointInt = ToVector2Int(point);
var minInt = ToVector2Int(m_CurrentRect.min);
var maxInt = ToVector2Int(m_CurrentRect.max);
return minInt.x >= pointInt.x || maxInt.x <= pointInt.x || minInt.y >= pointInt.y || maxInt.y <= pointInt.y;
}
//From http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html
private IEnumerable<Vector2Int> GetPointsOnLine(int x0, int y0, int x1, int y1)
{
bool steep = Mathf.Abs(y1 - y0) > Math.Abs(x1 - x0);
if (steep)
{
int t;
t = x0; // swap x0 and y0
x0 = y0;
y0 = t;
t = x1; // swap x1 and y1
x1 = y1;
y1 = t;
}
if (x0 > x1)
{
int t;
t = x0; // swap x0 and x1
x0 = x1;
x1 = t;
t = y0; // swap y0 and y1
y0 = y1;
y1 = t;
}
int dx = x1 - x0;
int dy = Mathf.Abs(y1 - y0);
int error = dx / 2;
int ystep = (y0 < y1) ? 1 : -1;
int y = y0;
for (int x = x0; x <= x1; x++)
{
yield return new Vector2Int((steep ? y : x), (steep ? x : y));
error = error - dy;
if (error < 0)
{
y += ystep;
error += dx;
}
}
yield break;
}
private int NextIndex(int index, int pointCount)
{
return Mod(index + 1, pointCount);
}
private int PreviousIndex(int index, int pointCount)
{
return Mod(index - 1, pointCount);
}
private int Mod(int x, int m)
{
int r = x % m;
return r < 0 ? r + m : r;
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class BoneSelection : SerializableSelection<BoneCache>, IBoneSelection
{
protected override BoneCache GetInvalidElement() { return null; }
public BoneCache root
{
get { return activeElement.FindRoot<BoneCache>(elements); }
}
public BoneCache[] roots
{
get { return elements.FindRoots<BoneCache>(); }
}
}
}

View File

@@ -0,0 +1,7 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface IBoneSelection : ITransformSelection<BoneCache> {}
}

View File

@@ -0,0 +1,14 @@
namespace UnityEditor.U2D.Animation
{
internal interface ISelection<T>
{
int Count { get; }
T activeElement { get; set; }
T[] elements { get; set; }
void Clear();
void BeginSelection();
void EndSelection(bool select);
void Select(T element, bool select);
bool Contains(T element);
}
}

View File

@@ -0,0 +1,11 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface ITransformSelection<T> : ISelection<T> where T : TransformCache
{
T root { get; }
T[] roots { get; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class IndexedSelection : SerializableSelection<int>
{
protected override int GetInvalidElement() { return -1; }
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal abstract class SerializableSelection<T> : ISelection<T>, ISerializationCallbackReceiver
{
internal readonly static int kInvalidID = -1;
[SerializeField]
private T[] m_Keys = new T[0];
private HashSet<T> m_Selection = new HashSet<T>();
private HashSet<T> m_TemporalSelection = new HashSet<T>();
private bool m_SelectionInProgress = false;
public int Count
{
get { return m_Selection.Count + m_TemporalSelection.Count; }
}
public T activeElement
{
get { return First(); }
set
{
Clear();
Select(value, true);
}
}
public T[] elements
{
get
{
var set = m_Selection;
if (m_SelectionInProgress)
{
var union = new HashSet<T>(m_Selection);
union.UnionWith(m_TemporalSelection);
set = union;
}
return new List<T>(set).ToArray();
}
set
{
Clear();
foreach(var element in value)
Select(element, true);
}
}
protected abstract T GetInvalidElement();
public void Clear()
{
GetSelection().Clear();
}
public void BeginSelection()
{
m_SelectionInProgress = true;
Clear();
}
public void EndSelection(bool select)
{
m_SelectionInProgress = false;
if (select)
m_Selection.UnionWith(m_TemporalSelection);
else
m_Selection.ExceptWith(m_TemporalSelection);
m_TemporalSelection.Clear();
}
public void Select(T element, bool select)
{
if(EqualityComparer<T>.Default.Equals(element, GetInvalidElement()))
return;
if (select)
GetSelection().Add(element);
else if (Contains(element))
GetSelection().Remove(element);
}
public bool Contains(T element)
{
return m_Selection.Contains(element) || m_TemporalSelection.Contains(element);
}
private HashSet<T> GetSelection()
{
if (m_SelectionInProgress)
return m_TemporalSelection;
return m_Selection;
}
private T First()
{
T element = First(m_Selection);
if(EqualityComparer<T>.Default.Equals(element, GetInvalidElement()))
element = First(m_TemporalSelection);
return element;
}
private T First(HashSet<T> set)
{
if(set.Count == 0)
return GetInvalidElement();
using (var enumerator = set.GetEnumerator())
{
Debug.Assert(enumerator.MoveNext());
return enumerator.Current;
}
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
m_Keys = new List<T>(m_Selection).ToArray();
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
elements = m_Keys;
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class SkeletonSelection : IBoneSelection
{
[SerializeField]
private BoneSelection m_BoneSelection = new BoneSelection();
public int Count
{
get { return m_BoneSelection.Count; }
}
public BoneCache activeElement
{
get { return m_BoneSelection.activeElement; }
set
{
ValidateBone(value);
m_BoneSelection.activeElement = value;
}
}
public BoneCache[] elements
{
get { return m_BoneSelection.elements; }
set
{
foreach (var bone in value)
ValidateBone(bone);
m_BoneSelection.elements = value;
}
}
public BoneCache root
{
get { return m_BoneSelection.root; }
}
public BoneCache[] roots
{
get { return m_BoneSelection.roots; }
}
public void BeginSelection()
{
m_BoneSelection.BeginSelection();
}
public void Clear()
{
m_BoneSelection.Clear();
}
public bool Contains(BoneCache element)
{
return m_BoneSelection.Contains(element);
}
public void EndSelection(bool select)
{
m_BoneSelection.EndSelection(select);
}
public void Select(BoneCache element, bool select)
{
ValidateBone(element);
m_BoneSelection.Select(element, select);
}
private void ValidateBone(BoneCache bone)
{
if (bone == null)
return;
var skinningCache = bone.skinningCache;
if (skinningCache.hasCharacter)
{
if (bone.skeleton != skinningCache.character.skeleton)
throw new Exception("Selection Exception: bone does not belong to character skeleton");
}
else
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite == null)
throw new Exception("Selection Exception: skeleton not selected");
else
{
var skeleton = selectedSprite.GetSkeleton();
if (bone.skeleton != skeleton)
throw new Exception("Selection Exception: bone's skeleton does not match selected skeleton");
}
}
}
}
}

View File

@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.U2D.Layout;
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SelectionTool : BaseTool
{
private bool m_ForceSelectedToSpriteEditor = false;
public event Func<bool> CanSelect = () => true;
private List<SpriteCache> m_Sprites;
public ISpriteEditor spriteEditor { get; set; }
private SpriteCache selectedSprite
{
get { return skinningCache.selectedSprite; }
set
{
if (selectedSprite != value)
{
skinningCache.vertexSelection.Clear();
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
skinningCache.skeletonSelection.Clear();
skinningCache.events.boneSelectionChanged.Invoke();
}
skinningCache.selectedSprite = value;
SetToSpriteEditor();
skinningCache.events.selectedSpriteChanged.Invoke(value);
}
}
}
private string selectedSpriteAssetID
{
get
{
var sprite = Selection.activeObject as Sprite;
if (sprite != null)
return sprite.GetSpriteID().ToString();
return "";
}
}
protected override void OnAfterDeserialize()
{
m_ForceSelectedToSpriteEditor = true;
}
public override void Initialize(LayoutOverlay layoutOverlay)
{
m_Sprites = new List<SpriteCache>(skinningCache.GetSprites());
SetFromSpriteEditor();
}
protected override void OnActivate()
{
SetToSpriteEditor();
skinningCache.events.selectedSpriteChanged.AddListener(OnSpriteSelectionChange);
Selection.selectionChanged -= OnSelectionChanged;
Selection.selectionChanged += OnSelectionChanged;
}
protected override void OnDeactivate()
{
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSpriteSelectionChange);
Selection.selectionChanged -= OnSelectionChanged;
}
private void OnSpriteSelectionChange(SpriteCache sprite)
{
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSpriteSelectionChange);
selectedSprite = sprite;
skinningCache.events.selectedSpriteChanged.AddListener(OnSpriteSelectionChange);
}
private void OnSelectionChanged()
{
if (m_ForceSelectedToSpriteEditor)
{
SetToSpriteEditor();
m_ForceSelectedToSpriteEditor = false;
}
else
{
using (skinningCache.UndoScope(TextContent.selectionChange))
{
SetFromSpriteEditor();
}
}
}
private void SetFromSpriteEditor()
{
if (selectedSprite == null)
selectedSprite = skinningCache.GetSprite(selectedSpriteAssetID);
spriteEditor.RequestRepaint();
}
private void SetToSpriteEditor()
{
var id = "";
if (selectedSprite != null)
id = selectedSprite.id;
spriteEditor.selectedSpriteRect = new SpriteRect() { spriteID = new GUID(id) };
}
protected override void OnGUI()
{
HandleSpriteSelection();
}
private void HandleSpriteSelection()
{
Debug.Assert(Event.current != null);
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && GUIUtility.hotControl == 0 &&
!Event.current.alt && Event.current.clickCount == 2 && CanSelect())
{
var mousePosition = Handles.inverseMatrix.MultiplyPoint(Event.current.mousePosition);
var newSelected = TrySelect(mousePosition);
if (selectedSprite != newSelected)
{
using (skinningCache.UndoScope(TextContent.selectionChange))
{
selectedSprite = newSelected;
}
Event.current.Use();
Event.current.clickCount = 0;
}
}
}
private SpriteCache TrySelect(Vector2 mousePosition)
{
m_Sprites.Remove(selectedSprite);
if (selectedSprite != null)
m_Sprites.Add(selectedSprite);
var currentSelectedIndex = m_Sprites.FindIndex(x => x == selectedSprite) + 1;
var notVisiblePart = skinningCache.hasCharacter && skinningCache.mode == SkinningMode.Character
? skinningCache.character.parts.Where(x => !x.isVisible).Select(x => x.sprite) : new SpriteCache[0];
for (int index = 0; index < m_Sprites.Count; ++index)
{
var sprite = m_Sprites[(currentSelectedIndex + index) % m_Sprites.Count];
var meshPreview = sprite.GetMeshPreview();
if (notVisiblePart.Contains(sprite))
continue;
Debug.Assert(meshPreview != null);
var spritePosition = sprite.GetLocalToWorldMatrixFromMode().MultiplyPoint3x4(Vector3.zero);
var ray = new Ray((Vector3)mousePosition - spritePosition + Vector3.back, Vector3.forward);
var bounds = meshPreview.mesh.bounds;
if (sprite.GetMesh().indices.Count >= 3)
{
if (bounds.IntersectRay(ray))
{
var mesh = sprite.GetMesh();
Debug.Assert(mesh != null);
var indices = mesh.indices;
for (var i = 0; i < indices.Count; i += 3)
{
var p1 = meshPreview.vertices[indices[i]];
var p2 = meshPreview.vertices[indices[i + 1]];
var p3 = meshPreview.vertices[indices[i + 2]];
if (MathUtility.Intersect(p1, p2, p3, ray))
return sprite;
}
}
}
else
{
if (meshPreview.defaultMesh.bounds.IntersectRay(ray))
{
return sprite;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class CircleVertexSelector : ICircleSelector<int>
{
public ISelection<int> selection { get; set; }
public ISpriteMeshData spriteMeshData { get; set; }
public Vector2 position { get; set; }
public float radius { get; set; }
public void Select()
{
if(spriteMeshData == null)
return;
var sqrRadius = radius * radius;
for (int i = 0; i < spriteMeshData.vertexCount; i++)
{
if ((spriteMeshData.GetPosition(i) - position).sqrMagnitude <= sqrRadius)
{
selection.Select(i, true);
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class GenericVertexSelector : ISelector<int>
{
public ISelection<int> selection { get; set; }
public ISpriteMeshData spriteMeshData { get; set; }
public Func<int, bool> SelectionCallback;
public void Select()
{
Debug.Assert(selection != null);
Debug.Assert(spriteMeshData != null);
Debug.Assert(SelectionCallback != null);
for (var i = 0; i < spriteMeshData.vertexCount; i++)
selection.Select(i, SelectionCallback(i));
}
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface ICircleSelector<T> : ISelector<T>
{
float radius { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface IRectSelector<T> : ISelector<T>
{
Rect rect { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal interface ISelector<T>
{
ISelection<T> selection { get; set; }
void Select();
}
}

View File

@@ -0,0 +1,35 @@
using UnityEngine;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal class RectBoneSelector : IRectSelector<BoneCache>
{
public ISelection<BoneCache> selection { get; set; }
public BoneCache[] bones { get; set; }
public Rect rect { get; set; }
public void Select()
{
if (bones == null)
return;
foreach (var bone in bones)
{
if (!bone.isVisible)
continue;
Vector2 p1 = bone.position;
Vector2 p2 = bone.endPosition;
Vector2 point = Vector2.zero;
if (rect.Contains(p1, true) || rect.Contains(p2, true) ||
MathUtility.SegmentIntersection(new Vector2(rect.xMin, rect.yMin), new Vector2(rect.xMax, rect.yMin), p1, p2, ref point) ||
MathUtility.SegmentIntersection(new Vector2(rect.xMax, rect.yMin), new Vector2(rect.xMax, rect.yMax), p1, p2, ref point) ||
MathUtility.SegmentIntersection(new Vector2(rect.xMax, rect.yMax), new Vector2(rect.xMin, rect.yMax), p1, p2, ref point) ||
MathUtility.SegmentIntersection(new Vector2(rect.xMin, rect.yMax), new Vector2(rect.xMin, rect.yMin), p1, p2, ref point)
)
selection.Select(bone.ToCharacterIfNeeded(), true);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using UnityEngine;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal class RectVertexSelector : IRectSelector<int>
{
public ISelection<int> selection { get; set; }
public ISpriteMeshData spriteMeshData { get; set; }
public Rect rect { get; set; }
public void Select()
{
for (int i = 0; i < spriteMeshData.vertexCount; i++)
{
if (rect.Contains(spriteMeshData.GetPosition(i), true))
selection.Select(i, true);
}
}
}
}

View File

@@ -0,0 +1,14 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Unselector<T> : ISelector<T>
{
public ISelection<T> selection { get; set; }
public void Select()
{
selection.Clear();
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class SerializableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ISerializationCallbackReceiver
{
[SerializeField]
private List<TKey> m_Keys;
[SerializeField]
private List<TValue> m_Values;
private Dictionary<TKey, TValue> m_Dictionary = new Dictionary<TKey, TValue>();
public TValue this[TKey key]
{
get { return m_Dictionary[key]; }
set { m_Dictionary[key] = value; }
}
public TKey this[TValue value]
{
get
{
m_Keys = new List<TKey>(m_Dictionary.Keys);
m_Values = new List<TValue>(m_Dictionary.Values);
var index = m_Values.FindIndex(x => x.Equals(value));
if (index < 0)
throw new KeyNotFoundException();
return m_Keys[index];
}
}
public ICollection<TKey> Keys
{
get { return m_Dictionary.Keys; }
}
public ICollection<TValue> Values
{
get { return m_Dictionary.Values; }
}
public void Add(TKey key, TValue value)
{
m_Dictionary.Add(key, value);
}
public bool ContainsKey(TKey key)
{
return m_Dictionary.ContainsKey(key);
}
public bool Remove(TKey key)
{
return m_Dictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return m_Dictionary.TryGetValue(key, out value);
}
public void Clear()
{
m_Dictionary.Clear();
}
public int Count
{
get { return m_Dictionary.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
{
get { return (m_Dictionary as ICollection<KeyValuePair<TKey, TValue>>).IsReadOnly; }
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
(m_Dictionary as ICollection<KeyValuePair<TKey, TValue>>).Add(item);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return (m_Dictionary as ICollection<KeyValuePair<TKey, TValue>>).Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
(m_Dictionary as ICollection<KeyValuePair<TKey, TValue>>).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
return (m_Dictionary as ICollection<KeyValuePair<TKey, TValue>>).Remove(item);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return (m_Dictionary as IEnumerable<KeyValuePair<TKey, TValue>>).GetEnumerator();
}
public IEnumerator GetEnumerator()
{
return m_Dictionary.GetEnumerator();
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
m_Keys = new List<TKey>(m_Dictionary.Keys);
m_Values = new List<TValue>(m_Dictionary.Values);
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
Debug.Assert(m_Keys.Count == m_Values.Count);
Clear();
for (var i = 0; i < m_Keys.Count; ++i)
Add(m_Keys[i], m_Values[i]);
}
}
}

View File

@@ -0,0 +1,142 @@
using UnityEngine;
using System;
namespace UnityEditor.U2D.Animation
{
internal interface ISkeletonStyle
{
Color GetColor(BoneCache bone);
Color GetPreviewColor(int index);
Color GetParentLinkColor(BoneCache bone);
Color GetParentLinkPreviewColor(int index);
Color GetOutlineColor(BoneCache bone, bool isSelected, bool isHovered);
Color GetPreviewOutlineColor(int index);
float GetOutlineScale(bool isSelected);
}
internal abstract class SkeletonStyleBase : ISkeletonStyle
{
private const float kOutlineScale = 1.35f;
private const float kSelectedOutlineScale = 1.55f;
public Color GetColor(BoneCache bone)
{
return SetAlpha(GetBoneColorRaw(bone), GetAlpha(bone), VisibilityToolSettings.boneOpacity);
}
public Color GetPreviewColor(int index)
{
return GetBoneColorRaw(index);
}
public Color GetParentLinkColor(BoneCache bone)
{
return SetAlpha(GetBoneColorRaw(bone), 0.2f * GetAlpha(bone), VisibilityToolSettings.boneOpacity);
}
public Color GetParentLinkPreviewColor(int index)
{
return SetAlpha(GetBoneColorRaw(index), 0.2f, 1f);
}
public Color GetOutlineColor(BoneCache bone, bool isSelected, bool isHovered)
{
var skinningCache = bone.skinningCache;
if (isSelected)
return SelectionOutlineSettings.outlineColor;
if (isHovered)
return Handles.preselectionColor;
return SetAlpha(CalculateOutlineColor(GetBoneColorRaw(bone), VisibilityToolSettings.boneOpacity), GetAlpha(bone), VisibilityToolSettings.boneOpacity);
}
public Color GetPreviewOutlineColor(int index)
{
return CalculateOutlineColor(GetBoneColorRaw(index), 1f);
}
public float GetOutlineScale(bool isSelected)
{
if (isSelected)
return 1f + (kSelectedOutlineScale - 1f) * SelectionOutlineSettings.selectedBoneOutlineSize;
return kOutlineScale;
}
private Color CalculateOutlineColor(Color color, float opacity)
{
color *= 0.35f;
return SetAlpha(color, 0.75f, opacity);
}
private Color SetAlpha(Color color, float alpha, float opacity)
{
color.a = alpha * opacity;
return color;
}
protected virtual float GetAlpha(BoneCache bone)
{
return 1f;
}
protected abstract Color GetBoneColorRaw(BoneCache bone);
protected abstract Color GetBoneColorRaw(int index);
}
internal class BoneColorSkeletonStyle : SkeletonStyleBase
{
protected override Color GetBoneColorRaw(BoneCache bone)
{
return bone.bindPoseColor;
}
protected override Color GetBoneColorRaw(int index)
{
return ModuleUtility.CalculateNiceColor(index, 6);
}
protected override float GetAlpha(BoneCache bone)
{
return 0.9f;
}
}
internal class WeightmapSkeletonStyle : SkeletonStyleBase
{
protected override Color GetBoneColorRaw(BoneCache bone)
{
return bone.bindPoseColor;
}
protected override Color GetBoneColorRaw(int index)
{
return ModuleUtility.CalculateNiceColor(index, 6);
}
protected override float GetAlpha(BoneCache bone)
{
var skinningCache = bone.skinningCache;
var selectedSprite = skinningCache.selectedSprite;
var alpha = 0.9f;
if (skinningCache.mode == SkinningMode.Character && skinningCache.selectedSprite != null)
{
var characterPart = selectedSprite.GetCharacterPart();
if (characterPart.Contains(bone) == false)
alpha = 0.25f;
}
return alpha;
}
}
internal static class SkeletonStyles
{
public static readonly ISkeletonStyle Default = new BoneColorSkeletonStyle();
public static readonly ISkeletonStyle WeightMap = new WeightmapSkeletonStyle();
}
}

View File

@@ -0,0 +1,236 @@
using System;
using UnityEditor.U2D.Layout;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SkeletonTool : BaseTool
{
[SerializeField]
private SkeletonController m_SkeletonController;
private SkeletonToolView m_SkeletonToolView;
private RectBoneSelector m_RectBoneSelector = new RectBoneSelector();
private RectSelectionTool<BoneCache> m_RectSelectionTool = new RectSelectionTool<BoneCache>();
private UnselectTool<BoneCache> m_UnselectTool = new UnselectTool<BoneCache>();
public bool enableBoneInspector { get; set; }
public SkeletonMode mode
{
get { return m_SkeletonController.view.mode; }
set { m_SkeletonController.view.mode = value; }
}
public bool editBindPose
{
get { return m_SkeletonController.editBindPose; }
set { m_SkeletonController.editBindPose = value; }
}
public ISkeletonStyle skeletonStyle
{
get { return m_SkeletonController.styleOverride; }
set { m_SkeletonController.styleOverride = value; }
}
public override int defaultControlID
{
get { return 0; }
}
public BoneCache hoveredBone
{
get { return m_SkeletonController.hoveredBone; }
}
public SkeletonCache skeleton
{
get { return m_SkeletonController.skeleton; }
private set { m_SkeletonController.skeleton = value; }
}
internal override void OnCreate()
{
m_SkeletonController = new SkeletonController();
m_SkeletonController.view = new SkeletonView(new GUIWrapper());
m_SkeletonController.view.InvalidID = 0;
m_SkeletonController.selection = skinningCache.skeletonSelection;
m_SkeletonToolView = new SkeletonToolView();
m_SkeletonToolView.onBoneNameChanged += BoneNameChanged;
m_SkeletonToolView.onBoneDepthChanged += BoneDepthChanged;
m_SkeletonToolView.onBonePositionChanged += BonePositionChanged;
m_SkeletonToolView.onBoneRotationChanged += BoneRotationChanged;
m_SkeletonToolView.onBoneColorChanged += BoneColorChanged;
m_RectBoneSelector.selection = skinningCache.skeletonSelection;
m_RectSelectionTool.rectSelector = m_RectBoneSelector;
m_RectSelectionTool.cacheUndo = skinningCache;
m_RectSelectionTool.onSelectionUpdate += () =>
{
skinningCache.events.boneSelectionChanged.Invoke();
};
m_UnselectTool.cacheUndo = skinningCache;
m_UnselectTool.selection = skinningCache.skeletonSelection;
m_UnselectTool.onUnselect += () =>
{
skinningCache.events.boneSelectionChanged.Invoke();
};
}
public override void Initialize(LayoutOverlay layout)
{
m_SkeletonToolView.Initialize(layout);
}
protected override void OnActivate()
{
SetupSkeleton(skinningCache.GetEffectiveSkeleton(skinningCache.selectedSprite));
UpdateBoneInspector();
skinningCache.events.boneSelectionChanged.AddListener(BoneSelectionChanged);
skinningCache.events.selectedSpriteChanged.AddListener(SelectedSpriteChanged);
skinningCache.events.skinningModeChanged.AddListener(SkinningModeChanged);
skinningCache.events.boneDepthChanged.AddListener(BoneDataChanged);
skinningCache.events.boneNameChanged.AddListener(BoneDataChanged);
skeletonStyle = null;
}
protected override void OnDeactivate()
{
m_SkeletonToolView.Hide();
m_SkeletonController.Reset();
skinningCache.events.boneSelectionChanged.RemoveListener(BoneSelectionChanged);
skinningCache.events.selectedSpriteChanged.RemoveListener(SelectedSpriteChanged);
skinningCache.events.skinningModeChanged.RemoveListener(SkinningModeChanged);
skinningCache.events.boneDepthChanged.RemoveListener(BoneDataChanged);
skinningCache.events.boneNameChanged.RemoveListener(BoneDataChanged);
skeletonStyle = null;
}
void BoneDataChanged(BoneCache bone)
{
if(m_SkeletonToolView.target == bone)
m_SkeletonToolView.Update(bone.name, Mathf.RoundToInt(bone.depth), bone.position, bone.rotation.eulerAngles.z, bone.bindPoseColor);
}
private void SelectedSpriteChanged(SpriteCache sprite)
{
SetupSkeleton(skinningCache.GetEffectiveSkeleton(sprite));
}
private void BoneSelectionChanged()
{
UpdateBoneInspector();
}
private void UpdateBoneInspector()
{
var selectedBone = skinningCache.skeletonSelection.activeElement;
var selectionCount = skinningCache.skeletonSelection.Count;
m_SkeletonToolView.Hide();
if (enableBoneInspector && selectedBone != null && selectionCount == 1)
{
m_SkeletonToolView.Update(selectedBone.name, Mathf.RoundToInt(selectedBone.depth), selectedBone.position, selectedBone.rotation.eulerAngles.z, selectedBone.bindPoseColor);
bool isReadOnly = skinningCache.bonesReadOnly;
m_SkeletonToolView.Show(selectedBone, isReadOnly);
}
}
private void SkinningModeChanged(SkinningMode mode)
{
SetupSkeleton(skinningCache.GetEffectiveSkeleton(skinningCache.selectedSprite));
}
private void SetupSkeleton(SkeletonCache sk)
{
m_RectBoneSelector.bones = null;
skeleton = sk;
if (skeleton != null)
m_RectBoneSelector.bones = skeleton.bones;
}
protected override void OnGUI()
{
m_SkeletonController.view.defaultControlID = 0;
if (skeleton != null && mode != SkeletonMode.Disabled)
{
m_RectSelectionTool.OnGUI();
m_SkeletonController.view.defaultControlID = m_RectSelectionTool.controlID;
}
m_SkeletonController.OnGUI();
m_UnselectTool.OnGUI();
}
private void BoneColorChanged(BoneCache selectedBone, Color32 color)
{
if (selectedBone != null)
{
skinningCache.BeginUndoOperation(TextContent.colorBoneChanged);
selectedBone.bindPoseColor = color;
skinningCache.events.dataModified.Invoke();
}
}
private void BonePositionChanged(BoneCache selectedBone, Vector2 position)
{
if (selectedBone != null)
{
skinningCache.BeginUndoOperation(TextContent.moveBone);
selectedBone.position = position;
HandleUtility.Repaint();
m_SkeletonController.InvokePoseChanged();
}
}
private void BoneRotationChanged(BoneCache selectedBone, float rotation)
{
if (selectedBone != null)
{
var euler = selectedBone.rotation.eulerAngles;
euler.z = rotation;
skinningCache.BeginUndoOperation(TextContent.rotateBone);
selectedBone.rotation = Quaternion.Euler(euler);
HandleUtility.Repaint();
m_SkeletonController.InvokePoseChanged();
}
}
private void BoneNameChanged(BoneCache selectedBone, string name)
{
if (selectedBone != null)
{
if (string.Compare(selectedBone.name, name) == 0)
return;
if(string.IsNullOrEmpty(name) || string.IsNullOrWhiteSpace(name))
m_SkeletonToolView.Update(selectedBone.name, Mathf.RoundToInt(selectedBone.depth), selectedBone.position, selectedBone.rotation.eulerAngles.z, selectedBone.bindPoseColor);
else
{
using (skinningCache.UndoScope(TextContent.boneName))
{
selectedBone.name = name;
skinningCache.events.boneNameChanged.Invoke(selectedBone);
}
}
}
}
private void BoneDepthChanged(BoneCache selectedBone, int depth)
{
if (selectedBone != null)
{
if (Mathf.RoundToInt(selectedBone.depth) == depth)
return;
using (skinningCache.UndoScope(TextContent.boneDepth))
{
selectedBone.depth = depth;
skinningCache.events.boneDepthChanged.Invoke(selectedBone);
}
}
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using UnityEditor.U2D.Layout;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SkeletonToolView
{
private BoneInspectorPanel m_BoneInspectorPanel;
public event Action<BoneCache, string> onBoneNameChanged = (b, s) => {};
public event Action<BoneCache, int> onBoneDepthChanged = (b, i) => {};
public event Action<BoneCache, float> onBoneRotationChanged = (b, i) => {};
public event Action<BoneCache, Vector2> onBonePositionChanged = (b, i) => {};
public event Action<BoneCache, Color32> onBoneColorChanged = (b, i) => {};
public SkeletonToolView()
{
m_BoneInspectorPanel = BoneInspectorPanel.GenerateFromUXML();
m_BoneInspectorPanel.onBoneNameChanged += (b, n) => onBoneNameChanged(b, n);
m_BoneInspectorPanel.onBoneDepthChanged += (b, d) => onBoneDepthChanged(b, d);
m_BoneInspectorPanel.onBoneRotationChanged += (b, n) => onBoneRotationChanged(b, n);
m_BoneInspectorPanel.onBonePositionChanged += (b, d) => onBonePositionChanged(b, d);
m_BoneInspectorPanel.onBoneColorChanged += (b, d) => onBoneColorChanged(b, d);
Hide();
}
public void Initialize(LayoutOverlay layout)
{
layout.rightOverlay.Add(m_BoneInspectorPanel);
}
public void Show(BoneCache target, bool isReadOnly)
{
m_BoneInspectorPanel.target = target;
m_BoneInspectorPanel.SetHiddenFromLayout(false);
var readOnlyProperty = BoneInspectorPanel.PropertyReadOnly.None;
if (isReadOnly)
readOnlyProperty = BoneInspectorPanel.PropertyReadOnly.Name |
BoneInspectorPanel.PropertyReadOnly.Depth |
BoneInspectorPanel.PropertyReadOnly.Color;
m_BoneInspectorPanel.SetReadOnly(readOnlyProperty);
}
public BoneCache target => m_BoneInspectorPanel.target;
public void Hide()
{
m_BoneInspectorPanel.HidePanel();
m_BoneInspectorPanel.target = null;
}
public void Update(string name, int depth, Vector2 position, float rotation, Color32 color)
{
m_BoneInspectorPanel.boneName = name;
m_BoneInspectorPanel.boneDepth = depth;
m_BoneInspectorPanel.bonePosition = position;
m_BoneInspectorPanel.boneRotation = rotation;
m_BoneInspectorPanel.boneColor = color;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SkeletonToolWrapper : BaseTool
{
private SkeletonTool m_SkeletonTool;
private SkeletonMode m_Mode;
public SkeletonTool skeletonTool
{
get { return m_SkeletonTool; }
set { m_SkeletonTool = value; }
}
public SkeletonMode mode
{
get { return m_Mode; }
set { m_Mode = value; }
}
public bool editBindPose { get; set; }
public override int defaultControlID
{
get
{
Debug.Assert(skeletonTool != null);
return skeletonTool.defaultControlID;
}
}
protected override void OnActivate()
{
Debug.Assert(skeletonTool != null);
skeletonTool.enableBoneInspector = true;
skeletonTool.Activate();
}
protected override void OnDeactivate()
{
skeletonTool.enableBoneInspector = false;
skeletonTool.Deactivate();
}
private SkeletonMode OverrideMode()
{
var modeOverride = mode;
//Disable SkeletonManipulation if character exists and we are in SpriteSheet mode
if (skinningCache.mode == SkinningMode.SpriteSheet && skinningCache.hasCharacter && editBindPose)
modeOverride = SkeletonMode.Selection;
return modeOverride;
}
protected override void OnGUI()
{
Debug.Assert(skeletonTool != null);
skeletonTool.mode = OverrideMode();
skeletonTool.editBindPose = editBindPose;
skeletonTool.DoGUI();
}
}
}

View File

@@ -0,0 +1,339 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal struct Pose
{
public Vector3 position;
public Quaternion rotation;
public Matrix4x4 matrix
{
get { return Matrix4x4.TRS(position, rotation, Vector3.one); }
}
public static Pose Create(Vector3 p, Quaternion r)
{
var pose = new Pose()
{
position = p,
rotation = r
};
return pose;
}
public override bool Equals(object other)
{
return other is Pose && this == (Pose)other;
}
public override int GetHashCode()
{
return position.GetHashCode() ^ rotation.GetHashCode();
}
public static bool operator==(Pose p1, Pose p2)
{
return p1.position == p2.position && p1.rotation == p2.rotation;
}
public static bool operator!=(Pose p1, Pose p2)
{
return !(p1 == p2);
}
}
[Serializable]
internal struct BonePose
{
public Pose pose;
public float length;
public static BonePose Create(Pose p, float l)
{
var pose = new BonePose()
{
pose = p,
length = l
};
return pose;
}
public override bool Equals(object other)
{
return other is BonePose && this == (BonePose)other;
}
public override int GetHashCode()
{
return pose.GetHashCode() ^ length.GetHashCode();
}
public static bool operator==(BonePose p1, BonePose p2)
{
return p1.pose == p2.pose && p1.length == p2.length;
}
public static bool operator!=(BonePose p1, BonePose p2)
{
return !(p1 == p2);
}
}
internal class BoneCache : TransformCache
{
[SerializeField]
Color32 m_BindPoseColor;
[SerializeField]
private Pose m_BindPose;
[SerializeField]
private BonePose m_DefaultPose;
[SerializeField]
private BoneCache m_ChainedChild;
[SerializeField]
private float m_Depth;
[SerializeField]
private float m_LocalLength = 1f;
[SerializeField]
private bool m_IsVisible = true;
[SerializeField]
private string m_Guid;
public bool NotInDefaultPose()
{
return localPosition != m_DefaultPose.pose.position
|| localRotation != m_DefaultPose.pose.rotation
|| localLength != m_DefaultPose.length;
}
public bool isVisible
{
get { return m_IsVisible; }
set { m_IsVisible = value; }
}
public Color bindPoseColor
{
get { return m_BindPoseColor; }
set { m_BindPoseColor = value; }
}
public virtual BoneCache parentBone
{
get { return parent as BoneCache; }
}
public SkeletonCache skeleton
{
get
{
var skeleton = parent as SkeletonCache;
if (skeleton != null)
return skeleton;
if (parentBone != null)
return parentBone.skeleton;
return null;
}
}
public virtual BoneCache chainedChild
{
get
{
if (m_ChainedChild != null && m_ChainedChild.parentBone == this)
return m_ChainedChild;
return null;
}
set
{
if (m_ChainedChild != value)
{
if (value == null || value.parentBone == this)
{
m_ChainedChild = value;
if(m_ChainedChild != null)
OrientToChainedChild(false);
}
}
}
}
public Vector3 localEndPosition
{
get { return Vector3.right * localLength; }
}
public Vector3 endPosition
{
get { return localToWorldMatrix.MultiplyPoint3x4(localEndPosition); }
set
{
if (chainedChild == null)
{
var direction = value - position;
right = direction;
length = direction.magnitude;
}
}
}
public BonePose localPose
{
get { return BonePose.Create(Pose.Create(localPosition, localRotation), localLength); }
set
{
localPosition = value.pose.position;
localRotation = value.pose.rotation;
localLength = value.length;
}
}
public BonePose worldPose
{
get { return BonePose.Create(Pose.Create(position, rotation), length); }
set
{
position = value.pose.position;
rotation = value.pose.rotation;
length = value.length;
}
}
public Pose bindPose
{
get { return m_BindPose; }
set { m_BindPose = value; }
}
public string guid
{
get { return m_Guid; }
set { m_Guid = value; }
}
public float depth
{
get { return m_Depth; }
set { m_Depth = value; }
}
public float localLength
{
get { return m_LocalLength; }
set { m_LocalLength = Mathf.Max(0f, value); }
}
public float length
{
get { return localToWorldMatrix.MultiplyVector(localEndPosition).magnitude; }
set { m_LocalLength = worldToLocalMatrix.MultiplyVector(right * Mathf.Max(0f, value)).magnitude; }
}
internal Pose[] GetChildrenWoldPose()
{
return Array.ConvertAll(children, c => Pose.Create(c.position, c.rotation));
}
internal void SetChildrenWorldPose(Pose[] worldPose)
{
var childrenArray = children;
Debug.Assert(childrenArray.Length == worldPose.Length);
for (var i = 0; i < childrenArray.Length; ++i)
{
var child = childrenArray[i];
var pose= worldPose[i];
child.position = pose.position;
child.rotation = pose.rotation;
}
}
internal override void OnDestroy()
{
base.OnDestroy();
m_ChainedChild = null;
}
new public void SetParent(TransformCache newParent)
{
SetParent(newParent, true);
}
new public void SetParent(TransformCache newParent, bool worldPositionStays)
{
if (parentBone != null && parentBone.chainedChild == this)
parentBone.chainedChild = null;
base.SetParent(newParent, worldPositionStays);
if (parentBone != null && parentBone.chainedChild == null && (parentBone.endPosition - position).sqrMagnitude < 0.001f)
parentBone.chainedChild = this;
}
public void OrientToChainedChild(bool freezeChildren)
{
Debug.Assert(chainedChild != null);
var childPosition = chainedChild.position;
var childRotation = chainedChild.rotation;
Pose[] childrenWorldPose = null;
if (freezeChildren)
childrenWorldPose = GetChildrenWoldPose();
right = childPosition - position;
if (freezeChildren)
{
SetChildrenWorldPose(childrenWorldPose);
}
else
{
chainedChild.position = childPosition;
chainedChild.rotation = childRotation;
}
length = (childPosition - position).magnitude;
}
public void SetDefaultPose()
{
m_DefaultPose = localPose;
if (IsUnscaled())
m_BindPose = worldPose.pose;
else
throw new Exception("BindPose cannot be set under global scale");
}
public void RestoreDefaultPose()
{
localPose = m_DefaultPose;
}
private bool IsUnscaled()
{
var currentTransform = this as TransformCache;
while (currentTransform != null)
{
var scale = currentTransform.localScale;
var isUnscaled = Mathf.Approximately(scale.x, 1f) && Mathf.Approximately(scale.y, 1f) && Mathf.Approximately(scale.z, 1f);
if (!isUnscaled)
return false;
currentTransform = currentTransform.parent;
}
return true;
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class BoneCacheExtensions
{
public static BoneCache[] ToCharacterIfNeeded(this BoneCache[] bones)
{
return Array.ConvertAll(bones, b => ToCharacterIfNeeded(b));
}
public static BoneCache[] ToSpriteSheetIfNeeded(this BoneCache[] bones)
{
return Array.ConvertAll(bones, b => ToSpriteSheetIfNeeded(b));
}
public static BoneCache ToCharacterIfNeeded(this BoneCache bone)
{
if (bone == null)
return null;
var skinningCache = bone.skinningCache;
if (skinningCache.hasCharacter)
{
if (bone.skeleton != skinningCache.character.skeleton)
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite == null)
return null;
var skeleton = selectedSprite.GetSkeleton();
var characterPart = selectedSprite.GetCharacterPart();
Debug.Assert(skeleton != null);
Debug.Assert(characterPart != null);
Debug.Assert(bone.skeleton == skeleton);
Debug.Assert(skeleton.BoneCount == characterPart.BoneCount);
var index = skeleton.IndexOf(bone);
if (index == -1)
bone = null;
else
bone = characterPart.GetBone(index);
}
}
return bone;
}
public static BoneCache ToSpriteSheetIfNeeded(this BoneCache bone)
{
if (bone == null)
return null;
var skinningCache = bone.skinningCache;
if (skinningCache.hasCharacter && skinningCache.mode == SkinningMode.SpriteSheet)
{
var selectedSprite = skinningCache.selectedSprite;
if (selectedSprite == null)
return null;
var characterSkeleton = skinningCache.character.skeleton;
Debug.Assert(bone.skeleton == characterSkeleton);
var skeleton = selectedSprite.GetSkeleton();
var characterPart = selectedSprite.GetCharacterPart();
Debug.Assert(skeleton != null);
Debug.Assert(characterPart != null);
Debug.Assert(skeleton.BoneCount == characterPart.BoneCount);
var index = characterPart.IndexOf(bone);
if (index == -1)
bone = null;
else
bone = skeleton.GetBone(index);
}
return bone;
}
public static UnityEngine.U2D.SpriteBone ToSpriteBone(this BoneCache bone, Matrix4x4 rootTransform, int parentId)
{
var position = bone.localPosition;
var rotation = bone.localRotation;
if (parentId == -1)
{
rotation = bone.rotation;
position = rootTransform.inverse.MultiplyPoint3x4(bone.position);
}
return new UnityEngine.U2D.SpriteBone()
{
name = bone.name,
guid = bone.guid,
position = new Vector3(position.x, position.y, bone.depth),
rotation = rotation,
length = bone.localLength,
parentId = parentId,
color = bone.bindPoseColor
};
}
public static UnityEngine.U2D.SpriteBone[] ToSpriteBone(this BoneCache[] bones, Matrix4x4 rootTransform)
{
var spriteBones = new List<UnityEngine.U2D.SpriteBone>();
foreach (var bone in bones)
{
var parentId = -1;
if (ArrayUtility.Contains(bones, bone.parentBone))
parentId = Array.IndexOf(bones, bone.parentBone);
spriteBones.Add(bone.ToSpriteBone(rootTransform, parentId));
}
return spriteBones.ToArray();
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class CharacterCache : SkinningObject, IEnumerable<CharacterPartCache>
{
[SerializeField]
private SkeletonCache m_Skeleton;
[SerializeField]
private List<CharacterPartCache> m_Parts = new List<CharacterPartCache>();
[SerializeField]
private Vector2Int m_Dimension;
[SerializeField]
private List<CharacterGroupCache> m_Groups = new List<CharacterGroupCache>();
public SkeletonCache skeleton
{
get { return m_Skeleton; }
set { m_Skeleton = value; }
}
public virtual CharacterPartCache[] parts
{
get { return m_Parts.ToArray(); }
set { m_Parts = new List<CharacterPartCache>(value); }
}
public virtual CharacterGroupCache[] groups
{
get { return m_Groups.ToArray(); }
set { m_Groups = new List<CharacterGroupCache>(value); }
}
public Vector2Int dimension
{
get { return m_Dimension; }
set { m_Dimension = value; }
}
public IEnumerator<CharacterPartCache> GetEnumerator()
{
return ((IEnumerable<CharacterPartCache>)m_Parts).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<CharacterPartCache>)m_Parts).GetEnumerator();
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class CharacterGroupCache : SkinningObject, ICharacterOrder
{
[SerializeField]
public int parentGroup;
[SerializeField]
private bool m_IsVisible = true;
[SerializeField]
private int m_Order = -1;
public bool isVisible
{
get => m_IsVisible;
set
{
m_IsVisible = value;
skinningCache.GroupVisibilityChanged(this);
}
}
public virtual int order
{
get => m_Order;
set => m_Order = value;
}
}
internal class CharacterPartCache : TransformCache, ICharacterOrder
{
[SerializeField]
private SpriteCache m_Sprite;
[SerializeField]
private List<BoneCache> m_Bones = new List<BoneCache>();
[SerializeField]
private bool m_IsVisible = true;
[SerializeField]
private int m_ParentGroup = -1;
[SerializeField]
private int m_Order = -1;
public virtual int order
{
get => m_Order;
set => m_Order = value;
}
public int parentGroup
{
get { return m_ParentGroup; }
set { m_ParentGroup = value; }
}
public virtual bool isVisible
{
get { return m_IsVisible; }
set
{
m_IsVisible = value;
if (skinningCache != null)
skinningCache.SpriteVisibilityChanged(this);
}
}
public int BoneCount { get { return m_Bones.Count; } }
public virtual SpriteCache sprite
{
get { return m_Sprite; }
set { m_Sprite = value; }
}
public BoneCache[] bones
{
get { return m_Bones.ToArray(); }
set { m_Bones = new List<BoneCache>(value); }
}
public BoneCache GetBone(int index)
{
return m_Bones[index];
}
public int IndexOf(BoneCache bone)
{
return m_Bones.IndexOf(bone);
}
public bool Contains(BoneCache bone)
{
return m_Bones.Contains(bone);
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class CharacterPartCacheExtensions
{
public static void SyncSpriteSheetSkeleton(this CharacterPartCache characterPart)
{
var skinningCache = characterPart.skinningCache;
var character = skinningCache.character;
var characterSkeleton = character.skeleton;
var spriteSkeleton = characterPart.sprite.GetSkeleton();
var spriteSkeletonBones = spriteSkeleton.bones;
var characterPartBones = characterPart.bones;
if (spriteSkeletonBones.Length != characterPartBones.Length)
return;
for (var i = 0; i < characterPartBones.Length; ++i)
{
var spriteBone = spriteSkeletonBones[i];
var characterBone = characterPartBones[i];
var childWorldPose = spriteBone.GetChildrenWoldPose();
spriteBone.position = spriteSkeleton.localToWorldMatrix.MultiplyPoint3x4(
characterPart.worldToLocalMatrix.MultiplyPoint3x4(characterBone.position));
spriteBone.rotation = characterBone.rotation;
spriteBone.length = characterBone.length;
spriteBone.guid = characterBone.guid;
spriteBone.name = characterBone.name;
spriteBone.depth = characterBone.depth;
spriteBone.bindPoseColor = characterBone.bindPoseColor;
spriteBone.SetChildrenWorldPose(childWorldPose);
}
if (characterSkeleton.isPosePreview)
spriteSkeleton.SetPosePreview();
else
spriteSkeleton.SetDefaultPose();
}
public static void DeassociateUnusedBones(this CharacterPartCache characterPart)
{
var skinningCache = characterPart.skinningCache;
var bones = characterPart.bones;
if (bones.Length == 0)
return;
Debug.Assert(characterPart.sprite != null);
var mesh = characterPart.sprite.GetMesh();
Debug.Assert(mesh != null);
var vertices = mesh.vertices;
var newBonesSet = new HashSet<BoneCache>();
foreach (var vertex in vertices)
{
var boneWeight = vertex.editableBoneWeight;
foreach (BoneWeightChannel channel in boneWeight)
if (channel.enabled)
newBonesSet.Add(bones[channel.boneIndex]);
}
bones = new List<BoneCache>(newBonesSet).ToArray();
characterPart.bones = bones;
characterPart.sprite.UpdateMesh(bones);
skinningCache.events.characterPartChanged.Invoke(characterPart);
}
}
}

View File

@@ -0,0 +1,203 @@
using System.Collections.Generic;
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class MeshCache : SkinningObject, ISpriteMeshData
{
[SerializeField]
private SpriteCache m_Sprite;
[SerializeField]
private List<Vertex2D> m_Vertices = new List<Vertex2D>();
[SerializeField]
private List<int> m_Indices = new List<int>();
[SerializeField]
private List<Edge> m_Edges = new List<Edge>();
[SerializeField]
private List<BoneCache> m_Bones = new List<BoneCache>();
public ITextureDataProvider textureDataProvider { get; set; }
public SpriteCache sprite
{
get { return m_Sprite; }
set { m_Sprite = value; }
}
public List<Vertex2D> vertices
{
get { return m_Vertices; }
set { m_Vertices = value; }
}
public List<Vector3> vertexPositionOverride { get; set; }
public List<Edge> edges
{
get { return m_Edges; }
set { m_Edges = value; }
}
public List<int> indices
{
get { return m_Indices; }
set { m_Indices = value; }
}
public BoneCache[] bones
{
get { return m_Bones.ToArray(); }
set { SetBones(value); }
}
Rect ISpriteMeshData.frame
{
get { return sprite.textureRect; }
set {}
}
public int vertexCount
{
get { return m_Vertices.Count; }
}
public int boneCount
{
get { return m_Bones.Count; }
}
public Vector2 GetPosition(int index)
{
if (vertexPositionOverride != null)
return vertexPositionOverride[index];
return m_Vertices[index].position;
}
public void SetPosition(int index, Vector2 position)
{
if (vertexPositionOverride != null)
return;
m_Vertices[index].position = position;
}
public EditableBoneWeight GetWeight(int index)
{
return m_Vertices[index].editableBoneWeight;
}
public void SetWeight(int index, EditableBoneWeight weight)
{
m_Vertices[index].editableBoneWeight = weight;
}
public void AddVertex(Vector2 position, BoneWeight weight)
{
m_Vertices.Add(new Vertex2D(position, weight));
}
public void RemoveVertex(int index)
{
m_Vertices.RemoveAt(index);
}
SpriteBoneData ISpriteMeshData.GetBoneData(int index)
{
var worldToLocalMatrix = sprite.worldToLocalMatrix;
//We expect m_Bones to contain character's bones references if character exists. Sprite's skeleton bones otherwise.
if (skinningCache.hasCharacter)
worldToLocalMatrix = sprite.GetCharacterPart().worldToLocalMatrix;
SpriteBoneData spriteBoneData = null;
var bone = m_Bones[index];
if (bone == null)
spriteBoneData = new SpriteBoneData();
else
{
spriteBoneData = new SpriteBoneData()
{
name = bone.name,
parentId = bone.parentBone == null ? -1 : m_Bones.IndexOf(bone.parentBone),
localPosition = bone.localPosition,
localRotation = bone.localRotation,
position = worldToLocalMatrix.MultiplyPoint3x4(bone.position),
endPosition = worldToLocalMatrix.MultiplyPoint3x4(bone.endPosition),
depth = bone.depth,
length = bone.localLength
};
}
return spriteBoneData;
}
float ISpriteMeshData.GetBoneDepth(int index)
{
return m_Bones[index].depth;
}
public void Clear()
{
m_Vertices.Clear();
m_Indices.Clear();
m_Edges.Clear();
}
public bool ContainsBone(BoneCache bone)
{
return m_Bones.Contains(bone);
}
public void SetCompatibleBoneSet(BoneCache[] bones)
{
m_Bones = new List<BoneCache>(bones);
}
private void SetBones(BoneCache[] bones)
{
FixWeights(bones);
SetCompatibleBoneSet(bones);
}
private void FixWeights(BoneCache[] newBones)
{
var newBonesList = new List<BoneCache>(newBones);
var indexMap = new Dictionary<int, int>();
for (var i = 0; i < m_Bones.Count; ++i)
{
var bone = m_Bones[i];
var newIndex = newBonesList.IndexOf(bone);
if (newIndex != -1)
indexMap.Add(i, newIndex);
}
foreach (Vertex2D vertex in vertices)
{
var boneWeight = vertex.editableBoneWeight;
for (var i = 0; i < boneWeight.Count; ++i)
{
var newIndex = 0;
var boneRemoved = indexMap.TryGetValue(boneWeight[i].boneIndex, out newIndex) == false;
if (boneRemoved)
{
boneWeight[i].weight = 0f;
boneWeight[i].enabled = false;
}
boneWeight[i].boneIndex = newIndex;
if (boneRemoved)
boneWeight.CompensateOtherChannels(i);
}
boneWeight.UnifyChannelsWithSameBoneIndex();
}
}
}
}

View File

@@ -0,0 +1,370 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal class MeshPreviewCache : SkinningObject
{
private const int kNiceColorCount = 6;
[SerializeField]
private SpriteCache m_Sprite;
[SerializeField]
private Mesh m_Mesh;
[SerializeField]
private Mesh m_DefaultMesh;
private List<Vector3> m_SkinnedVertices = new List<Vector3>();
private List<Vector3> m_Vertices = new List<Vector3>();
private List<BoneWeight> m_Weights = new List<BoneWeight>();
private List<Vector2> m_TexCoords = new List<Vector2>();
private List<Color> m_Colors = new List<Color>();
private List<Matrix4x4> m_SkinningMatrices = new List<Matrix4x4>();
private bool m_MeshDirty;
private bool m_VerticesDirty;
private bool m_SkinningDirty;
private bool m_WeightsDirty;
private bool m_IndicesDirty;
private bool m_ColorsDirty;
private bool m_EnableSkinning;
public SpriteCache sprite
{
get { return m_Sprite; }
set
{
m_Sprite = value;
InitializeDefaultMesh();
SetMeshDirty();
}
}
public Mesh mesh { get { return m_Mesh; } }
public Mesh defaultMesh { get { return m_DefaultMesh; } }
public bool enableSkinning
{
get
{
return m_EnableSkinning;
}
set
{
if (m_EnableSkinning != value)
{
m_EnableSkinning = value;
SetSkinningDirty();
}
}
}
public bool canSkin
{
get { return CanSkin(); }
}
public List<Vector3> vertices
{
get
{
if (enableSkinning && canSkin)
return m_SkinnedVertices;
return m_Vertices;
}
}
private bool CanSkin()
{
if (m_Vertices.Count == 0 || m_Vertices.Count != m_Weights.Count)
return false;
var bones = sprite.GetBonesFromMode();
Debug.Assert(bones != null);
if (bones.Length == 0)
return false;
foreach (var weight in m_Weights)
{
if (weight.boneIndex0 < 0 || weight.boneIndex0 >= bones.Length ||
weight.boneIndex1 < 0 || weight.boneIndex1 >= bones.Length ||
weight.boneIndex2 < 0 || weight.boneIndex2 >= bones.Length ||
weight.boneIndex3 < 0 || weight.boneIndex3 >= bones.Length)
return false;
}
return true;
}
internal override void OnCreate()
{
m_Mesh = CreateMesh();
m_DefaultMesh = CreateMesh();
}
internal override void OnDestroy()
{
DestroyImmediate(m_Mesh);
DestroyImmediate(m_DefaultMesh);
}
private Mesh CreateMesh()
{
var mesh = new Mesh();
mesh.MarkDynamic();
mesh.hideFlags = HideFlags.DontSave;
return mesh;
}
private void InitializeDefaultMesh()
{
Debug.Assert(sprite != null);
Debug.Assert(m_DefaultMesh != null);
var meshCache = sprite.GetMesh();
Debug.Assert(meshCache != null);
int width, height;
meshCache.textureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
var uvScale = new Vector2(1f / width, 1f / height);
Vector3 position = sprite.textureRect.position;
var size = sprite.textureRect.size;
var vertices = new List<Vector3>()
{
Vector3.zero,
new Vector3(0f, size.y, 0f),
new Vector3(size.x, 0f, 0f),
size,
};
var uvs = new List<Vector2>()
{
Vector3.Scale(vertices[0] + position, uvScale),
Vector3.Scale(vertices[1] + position, uvScale),
Vector3.Scale(vertices[2] + position, uvScale),
Vector3.Scale(vertices[3] + position, uvScale),
};
m_DefaultMesh.SetVertices(vertices);
m_DefaultMesh.SetUVs(0, uvs);
m_DefaultMesh.SetColors(new List<Color>
{
Color.black,
Color.black,
Color.black,
Color.black
});
m_DefaultMesh.SetIndices(new int[]
{
0, 1, 3, 0, 3, 2
},
MeshTopology.Triangles, 0);
m_DefaultMesh.UploadMeshData(false);
}
public void SetMeshDirty()
{
m_MeshDirty = true;
}
public void SetVerticesDirty()
{
m_VerticesDirty = true;
}
public void SetSkinningDirty()
{
m_SkinningDirty = true;
}
public void SetWeightsDirty()
{
m_WeightsDirty = true;
}
public void SetIndicesDirty()
{
m_IndicesDirty = true;
}
public void SetColorsDirty()
{
m_ColorsDirty = true;
}
public void Prepare()
{
var meshChanged = false;
var meshCache = sprite.GetMesh();
Debug.Assert(meshCache != null);
m_MeshDirty |= m_Vertices.Count != meshCache.vertices.Count;
if (m_MeshDirty)
{
m_Mesh.Clear();
m_VerticesDirty = true;
m_WeightsDirty = true;
m_IndicesDirty = true;
m_SkinningDirty = true;
m_MeshDirty = false;
}
if (m_VerticesDirty)
{
m_Vertices.Clear();
m_TexCoords.Clear();
int width, height;
meshCache.textureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
var uvScale = new Vector2(1f / width, 1f / height);
foreach (var vertex in meshCache.vertices)
{
m_Vertices.Add(vertex.position);
m_TexCoords.Add(Vector2.Scale(vertex.position + sprite.textureRect.position, uvScale));
}
m_Mesh.SetVertices(m_Vertices);
m_Mesh.SetUVs(0, m_TexCoords);
meshChanged = true;
m_VerticesDirty = false;
}
if (m_WeightsDirty)
{
m_Weights.Clear();
for (int i = 0; i < meshCache.vertices.Count; ++i)
{
var vertex = meshCache.vertices[i];
m_Weights.Add(vertex.editableBoneWeight.ToBoneWeight(true));
}
SetColorsDirty();
meshChanged = true;
m_WeightsDirty = false;
}
if (m_ColorsDirty)
{
PrepareColors();
m_Mesh.SetColors(m_Colors);
meshChanged = true;
m_ColorsDirty = false;
}
if (m_IndicesDirty)
{
m_Mesh.SetTriangles(meshCache.indices, 0);
meshChanged = true;
m_IndicesDirty = false;
}
if (m_SkinningDirty)
{
if (enableSkinning && canSkin)
{
SkinVertices();
m_Mesh.SetVertices(m_SkinnedVertices);
meshChanged = true;
}
m_SkinningDirty = false;
}
if (meshChanged)
{
m_Mesh.UploadMeshData(false);
m_Mesh.RecalculateBounds();
skinningCache.events.meshPreviewChanged.Invoke(this);
}
}
private void PrepareColors()
{
var bones = sprite.GetBonesFromMode();
Debug.Assert(bones != null);
m_Colors.Clear();
for (var i = 0; i < m_Weights.Count; ++i)
{
var boneWeight = m_Weights[i];
var weightSum = 0f;
var color = Color.black;
for (var j = 0; j < 4; ++j)
{
var boneIndex = boneWeight.GetBoneIndex(j);
var weight = boneWeight.GetWeight(j);
if (boneIndex >= 0 && boneIndex < bones.Length)
color += bones[boneIndex].bindPoseColor * weight;
weightSum += weight;
}
color.a = 1f;
m_Colors.Add(Color.Lerp(Color.black, color, weightSum));
}
}
private void SkinVertices()
{
Debug.Assert(canSkin);
Debug.Assert(sprite != null);
var bones = sprite.GetBonesFromMode();
var originMatrix = Matrix4x4.TRS(sprite.pivotRectSpace, Quaternion.identity, Vector3.one);
var originInverseMatrix = originMatrix.inverse;
var spriteMatrix = sprite.GetLocalToWorldMatrixFromMode();
var spriteMatrixInv = spriteMatrix.inverse;
m_SkinnedVertices.Clear();
m_SkinningMatrices.Clear();
for (int i = 0; i < bones.Length; ++i)
m_SkinningMatrices.Add(spriteMatrixInv * originInverseMatrix * bones[i].localToWorldMatrix * bones[i].bindPose.matrix.inverse * spriteMatrix);
for (int i = 0; i < m_Vertices.Count; ++i)
{
var position = m_Vertices[i];
BoneWeight boneWeight = m_Weights[i];
float weightSum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3;
if (weightSum > 0f)
{
var weightSumInv = 1f / weightSum;
var skinnedPosition = m_SkinningMatrices[boneWeight.boneIndex0].MultiplyPoint3x4(position) * boneWeight.weight0 * weightSumInv +
m_SkinningMatrices[boneWeight.boneIndex1].MultiplyPoint3x4(position) * boneWeight.weight1 * weightSumInv +
m_SkinningMatrices[boneWeight.boneIndex2].MultiplyPoint3x4(position) * boneWeight.weight2 * weightSumInv +
m_SkinningMatrices[boneWeight.boneIndex3].MultiplyPoint3x4(position) * boneWeight.weight3 * weightSumInv;
position = Vector3.Lerp(position, skinnedPosition, weightSum);
}
m_SkinnedVertices.Add(position);
}
}
protected override void OnAfterDeserialize()
{
SetMeshDirty();
}
}
}

View File

@@ -0,0 +1,192 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SkeletonCache : TransformCache
{
[SerializeField]
private bool m_IsPosePreview = false;
[SerializeField]
private List<BoneCache> m_Bones = new List<BoneCache>();
public bool isPosePreview { get { return m_IsPosePreview; } }
public int BoneCount { get { return m_Bones.Count; } }
public virtual BoneCache[] bones
{
get { return m_Bones.ToArray(); }
}
public void AddBone(BoneCache bone)
{
AddBone(bone, true);
}
public void AddBone(BoneCache bone, bool worldPositionStays)
{
Debug.Assert(bone != null);
Debug.Assert(!Contains(bone));
if (bone.parent == null)
bone.SetParent(this, worldPositionStays);
m_Bones.Add(bone);
}
public void ReorderBones(IEnumerable<BoneCache> boneCache)
{
if (boneCache.Count() == m_Bones.Count)
{
foreach (var b in m_Bones)
{
if (!boneCache.Contains(b))
return;
}
m_Bones = boneCache.ToList();
}
}
public void DestroyBone(BoneCache bone)
{
Debug.Assert(bone != null);
Debug.Assert(Contains(bone));
m_Bones.Remove(bone);
var children = bone.children;
foreach (var child in children)
child.SetParent(bone.parent);
skinningCache.Destroy(bone);
}
public void SetDefaultPose()
{
foreach (var bone in m_Bones)
bone.SetDefaultPose();
m_IsPosePreview = false;
}
public void RestoreDefaultPose()
{
foreach (var bone in m_Bones)
bone.RestoreDefaultPose();
m_IsPosePreview = false;
skinningCache.events.skeletonPreviewPoseChanged.Invoke(this);
}
public void SetPosePreview()
{
m_IsPosePreview = true;
}
public BonePose[] GetLocalPose()
{
var pose = new List<BonePose>();
foreach (var bone in m_Bones)
pose.Add(bone.localPose);
return pose.ToArray();
}
public void SetLocalPose(BonePose[] pose)
{
Debug.Assert(m_Bones.Count == pose.Length);
for (var i = 0; i < m_Bones.Count; ++i)
m_Bones[i].localPose = pose[i];
m_IsPosePreview = true;
}
public BonePose[] GetWorldPose()
{
var pose = new List<BonePose>();
foreach (var bone in m_Bones)
pose.Add(bone.worldPose);
return pose.ToArray();
}
public void SetWorldPose(BonePose[] pose)
{
Debug.Assert(m_Bones.Count == pose.Length);
for (var i = 0; i < m_Bones.Count; ++i)
{
var bone = m_Bones[i];
var childWoldPose = bone.GetChildrenWoldPose();
bone.worldPose = pose[i];
bone.SetChildrenWorldPose(childWoldPose);
}
m_IsPosePreview = true;
}
public BoneCache GetBone(int index)
{
return m_Bones[index];
}
public int IndexOf(BoneCache bone)
{
return m_Bones.IndexOf(bone);
}
public bool Contains(BoneCache bone)
{
return m_Bones.Contains(bone);
}
public void Clear()
{
var roots = children;
foreach (var root in roots)
DestroyHierarchy(root);
m_Bones.Clear();
}
public string GetUniqueName(BoneCache bone)
{
Debug.Assert(Contains(bone));
var boneName = bone.name;
var names = m_Bones.ConvertAll(b => b.name);
var index = IndexOf(bone);
var count = 0;
Debug.Assert(index < names.Count);
for (var i = 0; i < index; ++i)
if (names[i].Equals(boneName))
++count;
if (count == 0)
return boneName;
return string.Format("{0} ({1})", boneName, count + 1);
}
private void DestroyHierarchy(TransformCache root)
{
Debug.Assert(root != null);
var children = root.children;
foreach (var child in children)
DestroyHierarchy(child);
skinningCache.Destroy(root);
}
}
}

View File

@@ -0,0 +1,181 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class SkeletonCacheExtensions
{
public static void RotateBones(this SkeletonCache skeleton, BoneCache[] bones, float deltaAngle)
{
Debug.Assert(skeleton != null);
foreach (var bone in bones)
{
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
bone.localRotation *= Quaternion.AngleAxis(deltaAngle, Vector3.forward);
}
}
public static void MoveBones(this SkeletonCache skeleton, BoneCache[] bones, Vector3 deltaPosition)
{
Debug.Assert(skeleton != null);
foreach (var bone in bones)
{
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
bone.position += deltaPosition;
if (bone.parentBone != null && bone.parentBone.chainedChild == bone)
bone.parentBone.OrientToChainedChild(false);
}
}
public static void FreeMoveBones(this SkeletonCache skeleton, BoneCache[] bones, Vector3 deltaPosition)
{
Debug.Assert(skeleton != null);
foreach (var bone in bones)
{
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
var childrenWorldPose = bone.GetChildrenWoldPose();
if (bone.chainedChild != null && ArrayUtility.Contains(bones, bone.chainedChild) == false)
bone.chainedChild = null;
if (bone.parentBone != null && bone.parentBone.chainedChild == bone && ArrayUtility.Contains(bones, bone.parentBone) == false)
bone.parentBone.chainedChild = null;
bone.position += deltaPosition;
bone.SetChildrenWorldPose(childrenWorldPose);
}
}
public static void MoveJoints(this SkeletonCache skeleton, BoneCache[] bones, Vector3 deltaPosition)
{
Debug.Assert(skeleton != null);
foreach (var bone in bones)
{
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
var childrenWorldPose = bone.GetChildrenWoldPose();
var endPosition = bone.endPosition;
bone.position += deltaPosition;
if (bone.localLength > 0f)
bone.endPosition = endPosition;
if (bone.parentBone != null && bone.parentBone.chainedChild == bone)
bone.parentBone.OrientToChainedChild(true);
bone.SetChildrenWorldPose(childrenWorldPose);
if (bone.chainedChild != null)
bone.OrientToChainedChild(true);
}
}
public static void SetEndPosition(this SkeletonCache skeleton, BoneCache bone, Vector3 endPosition)
{
Debug.Assert(skeleton != null);
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
var childrenStorage = bone.GetChildrenWoldPose();
bone.endPosition = endPosition;
bone.SetChildrenWorldPose(childrenStorage);
}
public static BoneCache SplitBone(this SkeletonCache skeleton, BoneCache boneToSplit, float splitLength, string name)
{
Debug.Assert(skeleton.Contains(boneToSplit));
Debug.Assert(boneToSplit.length > splitLength);
var endPosition = boneToSplit.endPosition;
var chainedChild = boneToSplit.chainedChild;
var splitPosition = boneToSplit.position + boneToSplit.right * splitLength;
boneToSplit.length = splitLength;
var bone = skeleton.CreateBone(boneToSplit, splitPosition, endPosition, true, name);
if (chainedChild != null)
{
chainedChild.SetParent(bone);
bone.chainedChild = chainedChild;
}
return bone;
}
public static BoneCache CreateBone(this SkeletonCache skeleton, BoneCache parentBone, Vector3 position, Vector3 endPosition, bool isChained, string name)
{
Debug.Assert(skeleton != null);
if (parentBone != null)
Debug.Assert(skeleton.Contains(parentBone));
var bone = skeleton.skinningCache.CreateCache<BoneCache>();
bone.SetParent(parentBone);
bone.name = name;
bone.bindPoseColor = ModuleUtility.CalculateNiceColor(skeleton.BoneCount, 6);
bone.position = position;
bone.endPosition = endPosition;
bone.guid = GUID.Generate().ToString();
if (isChained && parentBone != null)
parentBone.chainedChild = bone;
skeleton.AddBone(bone);
return bone;
}
public static void SetBones(this SkeletonCache skeleton, BoneCache[] bones)
{
skeleton.SetBones(bones, true);
}
public static void SetBones(this SkeletonCache skeleton, BoneCache[] bones, bool worldPositionStays)
{
skeleton.Clear();
skeleton.AddBones(bones, worldPositionStays);
skeleton.SetDefaultPose();
}
public static void AddBones(this SkeletonCache skeleton, BoneCache[] bones)
{
skeleton.AddBones(bones, true);
}
public static void AddBones(this SkeletonCache skeleton, BoneCache[] bones, bool worldPositionStays)
{
foreach (var bone in bones)
skeleton.AddBone(bone, worldPositionStays);
}
public static void DestroyBones(this SkeletonCache skeleton, BoneCache[] bones)
{
Debug.Assert(skeleton != null);
foreach (var bone in bones)
{
Debug.Assert(bone != null);
Debug.Assert(skeleton.Contains(bone));
skeleton.DestroyBone(bone);
}
}
}
}

View File

@@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface ISkinningCachePersistentState
{
String lastSpriteId
{
get;
set;
}
Tools lastUsedTool
{
get;
set;
}
List<int> lastBoneSelectionIds
{
get;
}
Texture2D lastTexture
{
get;
set;
}
SerializableDictionary<int, BonePose> lastPreviewPose
{
get;
}
SerializableDictionary<int, bool> lastBoneVisibility
{
get;
}
SerializableDictionary<int, bool> lastBoneExpansion
{
get;
}
SerializableDictionary<string, bool> lastSpriteVisibility
{
get;
}
SerializableDictionary<int, bool> lastGroupVisibility
{
get;
}
SkinningMode lastMode
{
get;
set;
}
bool lastVisibilityToolActive
{
get;
set;
}
int lastVisibilityToolIndex
{
get;
set;
}
IndexedSelection lastVertexSelection
{
get;
}
float lastBrushSize
{
get;
set;
}
float lastBrushHardness
{
get;
set;
}
float lastBrushStep
{
get;
set;
}
}
[Serializable]
internal class SkinningCachePersistentState
: ScriptableSingleton<SkinningCachePersistentState>
, ISkinningCachePersistentState
{
[SerializeField] private Tools m_LastUsedTool = Tools.EditPose;
[SerializeField] private SkinningMode m_LastMode = SkinningMode.Character;
[SerializeField] private string m_LastSpriteId = String.Empty;
[SerializeField] private List<int> m_LastBoneSelectionIds = new List<int>();
[SerializeField] private Texture2D m_LastTexture;
[SerializeField]
private SerializableDictionary<int, BonePose> m_SkeletonPreviewPose =
new SerializableDictionary<int, BonePose>();
[SerializeField]
private SerializableDictionary<int, bool> m_BoneVisibility =
new SerializableDictionary<int, bool>();
[SerializeField]
private SerializableDictionary<int, bool> m_BoneExpansion =
new SerializableDictionary<int, bool>();
[SerializeField]
private SerializableDictionary<string, bool> m_SpriteVisibility =
new SerializableDictionary<string, bool>();
[SerializeField]
private SerializableDictionary<int, bool> m_GroupVisibility =
new SerializableDictionary<int, bool>();
[SerializeField] private IndexedSelection m_VertexSelection;
[SerializeField] private bool m_VisibilityToolActive;
[SerializeField] private int m_VisibilityToolIndex = -1;
[SerializeField] private float m_LastBrushSize = 25f;
[SerializeField] private float m_LastBrushHardness = 1f;
[SerializeField] private float m_LastBrushStep = 20f;
public SkinningCachePersistentState()
{
m_VertexSelection = new IndexedSelection();
}
public void SetDefault()
{
m_LastUsedTool = Tools.EditPose;
m_LastMode = SkinningMode.Character;
m_LastSpriteId = String.Empty;
m_LastBoneSelectionIds.Clear();
m_LastTexture = null;
m_VertexSelection.Clear();
m_SkeletonPreviewPose.Clear();
m_BoneVisibility.Clear();
m_BoneExpansion.Clear();
m_SpriteVisibility.Clear();
m_GroupVisibility.Clear();
m_VisibilityToolActive = false;
m_VisibilityToolIndex = -1;
}
public string lastSpriteId
{
get { return m_LastSpriteId; }
set { m_LastSpriteId = value; }
}
public Tools lastUsedTool
{
get { return m_LastUsedTool; }
set { m_LastUsedTool = value; }
}
public List<int> lastBoneSelectionIds
{
get { return m_LastBoneSelectionIds; }
}
public Texture2D lastTexture
{
get { return m_LastTexture; }
set
{
if (value != m_LastTexture)
{
m_LastMode = SkinningMode.Character;
m_LastSpriteId = String.Empty;
m_LastBoneSelectionIds.Clear();
m_VertexSelection.Clear();
m_SkeletonPreviewPose.Clear();
m_BoneVisibility.Clear();
m_BoneExpansion.Clear();
m_SpriteVisibility.Clear();
m_GroupVisibility.Clear();
}
m_LastTexture = value;
}
}
public SerializableDictionary<int, BonePose> lastPreviewPose
{
get { return m_SkeletonPreviewPose; }
}
public SerializableDictionary<int, bool> lastBoneVisibility
{
get { return m_BoneVisibility; }
}
public SerializableDictionary<int, bool> lastBoneExpansion
{
get { return m_BoneExpansion; }
}
public SerializableDictionary<string, bool> lastSpriteVisibility
{
get { return m_SpriteVisibility; }
}
public SerializableDictionary<int, bool> lastGroupVisibility
{
get { return m_GroupVisibility; }
}
public SkinningMode lastMode
{
get { return m_LastMode; }
set { m_LastMode = value; }
}
public bool lastVisibilityToolActive
{
get { return m_VisibilityToolActive; }
set { m_VisibilityToolActive = value; }
}
public int lastVisibilityToolIndex
{
get { return m_VisibilityToolIndex; }
set { m_VisibilityToolIndex = value; }
}
public IndexedSelection lastVertexSelection
{
get { return m_VertexSelection; }
}
public float lastBrushSize
{
get { return m_LastBrushSize; }
set { m_LastBrushSize = value; }
}
public float lastBrushHardness
{
get { return m_LastBrushHardness; }
set { m_LastBrushHardness = value; }
}
public float lastBrushStep
{
get { return m_LastBrushStep; }
set { m_LastBrushStep = value; }
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal enum SkinningMode
{
SpriteSheet,
Character
}
internal enum Tools
{
EditGeometry,
CreateVertex,
CreateEdge,
SplitEdge,
GenerateGeometry,
EditPose,
EditJoints,
CreateBone,
SplitBone,
ReparentBone,
WeightSlider,
WeightBrush,
GenerateWeights,
BoneInfluence,
CopyPaste,
Visibility,
SwitchMode
}
}

View File

@@ -0,0 +1,69 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace UnityEditor.U2D.Animation
{
internal class SkinningEvents
{
public class SpriteEvent : UnityEvent<SpriteCache> {}
public class SkeletonEvent : UnityEvent<SkeletonCache> {}
public class MeshEvent : UnityEvent<MeshCache> {}
public class MeshPreviewEvent : UnityEvent<MeshPreviewCache> {}
public class SkinningModuleModeEvent : UnityEvent<SkinningMode> {}
public class BoneSelectionEvent : UnityEvent {}
public class BoneEvent : UnityEvent<BoneCache> {}
public class CharacterPartEvent : UnityEvent<CharacterPartCache> {}
public class ToolChangeEvent : UnityEvent<ITool> {}
public class RestoreBindPoseEvent : UnityEvent {}
public class CopyEvent : UnityEvent {}
public class PasteEvent : UnityEvent<bool, bool, bool, bool> {}
public class ShortcutEvent : UnityEvent<string> {}
public class BoneVisibilityEvent : UnityEvent<string> {}
public class MeshPreviewBehaviourChangeEvent : UnityEvent<IMeshPreviewBehaviour> {}
private SpriteEvent m_SelectedSpriteChanged = new SpriteEvent();
private SkeletonEvent m_SkeletonPreviewPoseChanged = new SkeletonEvent();
private SkeletonEvent m_SkeletonBindPoseChanged = new SkeletonEvent();
private SkeletonEvent m_SkeletonTopologyChanged = new SkeletonEvent();
private MeshEvent m_MeshChanged = new MeshEvent();
private MeshPreviewEvent m_MeshPreviewChanged = new MeshPreviewEvent();
private SkinningModuleModeEvent m_SkinningModuleModeChanged = new SkinningModuleModeEvent();
private BoneSelectionEvent m_BoneSelectionChangedEvent = new BoneSelectionEvent();
private BoneEvent m_BoneNameChangedEvent = new BoneEvent();
private BoneEvent m_BoneDepthChangedEvent = new BoneEvent();
private CharacterPartEvent m_CharacterPartChanged = new CharacterPartEvent();
private ToolChangeEvent m_ToolChanged = new ToolChangeEvent();
private RestoreBindPoseEvent m_RestoreBindPose = new RestoreBindPoseEvent();
private CopyEvent m_CopyEvent = new CopyEvent();
private PasteEvent m_PasteEvent = new PasteEvent();
private ShortcutEvent m_ShortcutEvent = new ShortcutEvent();
private BoneVisibilityEvent m_BoneVisibilityEvent = new BoneVisibilityEvent();
private MeshPreviewBehaviourChangeEvent m_MeshPreviewBehaviourChange = new MeshPreviewBehaviourChangeEvent();
private UnityEvent m_DataModified = new UnityEvent();
//Setting them as virtual so that we can create mock them
public virtual SpriteEvent selectedSpriteChanged { get { return m_SelectedSpriteChanged; } }
public virtual SkeletonEvent skeletonPreviewPoseChanged { get { return m_SkeletonPreviewPoseChanged; } }
public virtual SkeletonEvent skeletonBindPoseChanged { get { return m_SkeletonBindPoseChanged; } }
public virtual SkeletonEvent skeletonTopologyChanged { get { return m_SkeletonTopologyChanged; } }
public virtual MeshEvent meshChanged { get { return m_MeshChanged; } }
public virtual MeshPreviewEvent meshPreviewChanged { get { return m_MeshPreviewChanged; } }
public virtual SkinningModuleModeEvent skinningModeChanged { get { return m_SkinningModuleModeChanged; } }
public virtual BoneSelectionEvent boneSelectionChanged { get { return m_BoneSelectionChangedEvent; } }
public virtual BoneEvent boneNameChanged { get { return m_BoneNameChangedEvent; } }
public virtual BoneEvent boneDepthChanged { get { return m_BoneDepthChangedEvent; } }
public virtual CharacterPartEvent characterPartChanged { get { return m_CharacterPartChanged; } }
public virtual ToolChangeEvent toolChanged { get { return m_ToolChanged; } }
public virtual RestoreBindPoseEvent restoreBindPose { get { return m_RestoreBindPose; } }
public virtual CopyEvent copy { get { return m_CopyEvent; } }
public virtual PasteEvent paste { get { return m_PasteEvent; } }
public virtual ShortcutEvent shortcut { get { return m_ShortcutEvent; } }
public virtual BoneVisibilityEvent boneVisibility { get { return m_BoneVisibilityEvent; } }
public virtual MeshPreviewBehaviourChangeEvent meshPreviewBehaviourChange { get { return m_MeshPreviewBehaviourChange; } }
public virtual UnityEvent dataModified
{
get => m_DataModified;
}
}
}

View File

@@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SpriteCache : TransformCache
{
[SerializeField]
private string m_ID;
[SerializeField]
private Rect m_TextureRect;
[SerializeField]
private Vector2 m_PivotNormalized;
public string id
{
get { return m_ID; }
internal set { m_ID = value; }
}
public Rect textureRect
{
get { return m_TextureRect; }
set { m_TextureRect = value; }
}
public Vector2 pivotNormalized
{
get { return m_PivotNormalized; }
set { m_PivotNormalized = value; }
}
public Vector2 pivotRectSpace
{
get { return Vector2.Scale(textureRect.size, pivotNormalized); }
}
public Vector2 pivotTextureSpace
{
get { return localToWorldMatrix.MultiplyPoint3x4(pivotRectSpace); }
}
}
}

View File

@@ -0,0 +1,325 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class SpriteCacheExtensions
{
public static MeshCache GetMesh(this SpriteCache sprite)
{
if (sprite != null)
return sprite.skinningCache.GetMesh(sprite);
return null;
}
public static MeshPreviewCache GetMeshPreview(this SpriteCache sprite)
{
if (sprite != null)
return sprite.skinningCache.GetMeshPreview(sprite);
return null;
}
public static SkeletonCache GetSkeleton(this SpriteCache sprite)
{
if (sprite != null)
return sprite.skinningCache.GetSkeleton(sprite);
return null;
}
public static CharacterPartCache GetCharacterPart(this SpriteCache sprite)
{
if (sprite != null)
return sprite.skinningCache.GetCharacterPart(sprite);
return null;
}
public static bool IsVisible(this SpriteCache sprite)
{
var isVisible = true;
var characterPart = sprite.GetCharacterPart();
if (sprite.skinningCache.mode == SkinningMode.Character && characterPart != null)
isVisible = characterPart.isVisible;
return isVisible;
}
public static Matrix4x4 GetLocalToWorldMatrixFromMode(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (skinningCache.mode == SkinningMode.SpriteSheet)
return sprite.localToWorldMatrix;
var characterPart = sprite.GetCharacterPart();
Debug.Assert(characterPart != null);
return characterPart.localToWorldMatrix;
}
public static BoneCache[] GetBonesFromMode(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (skinningCache.mode == SkinningMode.SpriteSheet)
return sprite.GetSkeleton().bones;
var characterPart = sprite.GetCharacterPart();
Debug.Assert(characterPart != null);
return characterPart.bones;
}
public static void UpdateMesh(this SpriteCache sprite, BoneCache[] bones)
{
var mesh = sprite.GetMesh();
var previewMesh = sprite.GetMeshPreview();
Debug.Assert(mesh != null);
Debug.Assert(previewMesh != null);
mesh.bones = bones;
previewMesh.SetWeightsDirty();
}
public static void SmoothFill(this SpriteCache sprite)
{
var mesh = sprite.GetMesh();
if (mesh == null)
return;
var controller = new SpriteMeshDataController();
controller.spriteMeshData = mesh;
controller.SmoothFill();
}
public static void RestoreBindPose(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
var skeleton = sprite.GetSkeleton();
Debug.Assert(skeleton != null);
skeleton.RestoreDefaultPose();
skinningCache.events.skeletonPreviewPoseChanged.Invoke(skeleton);
}
public static bool AssociateAllBones(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (skinningCache.mode == SkinningMode.SpriteSheet)
return false;
var character = skinningCache.character;
Debug.Assert(character != null);
Debug.Assert(character.skeleton != null);
var characterPart = sprite.GetCharacterPart();
Debug.Assert(characterPart != null);
var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray();
characterPart.bones = bones;
characterPart.sprite.UpdateMesh(bones);
return true;
}
public static bool AssociatePossibleBones(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (skinningCache.mode == SkinningMode.SpriteSheet)
return false;
var character = skinningCache.character;
Debug.Assert(character != null);
Debug.Assert(character.skeleton != null);
var characterPart = sprite.GetCharacterPart();
Debug.Assert(characterPart != null);
var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray();
var possibleBones = new List<BoneCache>();
// check if any of the bones overlapped
BoneCache shortestBoneDistance = null;
var minDistances = float.MaxValue;
var characterSpriteRect = new Rect(characterPart.position.x , characterPart.position.y, characterPart.sprite.textureRect.width, characterPart.sprite.textureRect.height);
foreach (var bone in bones)
{
var startPoint = bone.position;
var endPoint = bone.endPosition;
if (IntersectsSegment(characterSpriteRect, startPoint, endPoint))
possibleBones.Add(bone);
if (possibleBones.Count == 0)
{
// compare bone start end with rect's 4 line
// compare rect point with bone line
var points = new Vector2[] { startPoint, endPoint };
var rectLinePoints = new []
{
new Vector2Int(0, 1),
new Vector2Int(0, 2),
new Vector2Int(1, 3),
new Vector2Int(2, 3),
};
var rectPoints = new []
{
new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMin),
new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMax),
new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMin),
new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMax)
};
foreach (var point in points)
{
foreach (var rectLine in rectLinePoints)
{
var distance = PointToLineSegmentDistance(point, rectPoints[rectLine.x], rectPoints[rectLine.y]);
if (distance < minDistances)
{
minDistances = distance;
shortestBoneDistance = bone;
}
}
}
foreach (var rectPoint in rectPoints)
{
var distance = PointToLineSegmentDistance(rectPoint, startPoint, endPoint);
if (distance < minDistances)
{
minDistances = distance;
shortestBoneDistance = bone;
}
}
}
}
// if none overlapped, we use the bone that is closest to us
if (possibleBones.Count == 0 && shortestBoneDistance != null)
{
possibleBones.Add(shortestBoneDistance);
}
characterPart.bones = possibleBones.ToArray();
characterPart.sprite.UpdateMesh(possibleBones.ToArray());
return true;
}
static float PointToLineSegmentDistance(Vector2 p, Vector2 a, Vector2 b)
{
Vector2 n = b - a;
Vector2 pa = a - p;
float c = Vector2.Dot(n, pa);
// Closest point is a
if (c > 0.0f)
return Vector2.Dot(pa, pa);
Vector2 bp = p - b;
// Closest point is b
if (Vector2.Dot(n, bp) > 0.0f)
return Vector2.Dot(bp, bp);
// Closest point is between a and b
Vector2 e = pa - n * (c / Vector2.Dot(n, n));
return Vector2.Dot( e, e );
}
static bool IntersectsSegment(Rect rect, Vector2 p1, Vector2 p2)
{
float minX = Mathf.Min(p1.x, p2.x);
float maxX = Mathf.Max(p1.x, p2.x);
if (maxX > rect.xMax)
{
maxX = rect.xMax;
}
if (minX < rect.xMin)
{
minX = rect.xMin;
}
if (minX > maxX)
{
return false;
}
float minY = Mathf.Min(p1.y, p2.y);
float maxY = Mathf.Max(p1.y, p2.y);
float dx = p2.x - p1.x;
if (Mathf.Abs(dx) > float.Epsilon)
{
float a = (p2.y - p1.y) / dx;
float b = p1.y - a * p1.x;
minY = a * minX + b;
maxY = a * maxX + b;
}
if (minY > maxY)
{
float tmp = maxY;
maxY = minY;
minY = tmp;
}
if (maxY > rect.yMax)
{
maxY = rect.yMax;
}
if (minY < rect.yMin)
{
minY = rect.yMin;
}
if (minY > maxY)
{
return false;
}
return true;
}
public static void DeassociateUnusedBones(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
Debug.Assert(skinningCache.mode == SkinningMode.Character);
var characterPart = sprite.GetCharacterPart();
Debug.Assert(characterPart != null);
characterPart.DeassociateUnusedBones();
}
public static void DeassociateAllBones(this SpriteCache sprite)
{
var skinningCache = sprite.skinningCache;
if (skinningCache.mode == SkinningMode.SpriteSheet)
return;
var part = sprite.GetCharacterPart();
if (part.bones.Length == 0)
return;
Debug.Assert(part.sprite != null);
part.bones = new BoneCache[0];
part.sprite.UpdateMesh(part.bones);
skinningCache.events.characterPartChanged.Invoke(part);
}
}
}

View File

@@ -0,0 +1,287 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class TransformCache : SkinningObject, IEnumerable<TransformCache>
{
[SerializeField]
private TransformCache m_Parent;
[SerializeField]
private List<TransformCache> m_Children = new List<TransformCache>();
[SerializeField]
private Vector3 m_LocalPosition;
[SerializeField]
private Quaternion m_LocalRotation = Quaternion.identity;
[SerializeField]
private Vector3 m_LocalScale = Vector3.one;
[SerializeField]
private Matrix4x4 m_LocalToWorldMatrix = Matrix4x4.identity;
public TransformCache parent
{
get { return m_Parent; }
}
public TransformCache[] children
{
get { return m_Children.ToArray(); }
}
internal virtual int siblingIndex
{
get { return GetSiblingIndex(); }
set { SetSiblingIndex(value); }
}
public int ChildCount
{
get { return m_Children.Count; }
}
public Vector3 localPosition
{
get { return m_LocalPosition; }
set
{
m_LocalPosition = value;
Update();
}
}
public Quaternion localRotation
{
get { return m_LocalRotation; }
set
{
m_LocalRotation = MathUtility.NormalizeQuaternion(value);
Update();
}
}
public Vector3 localScale
{
get { return m_LocalScale; }
set
{
m_LocalScale = value;
Update();
}
}
public Vector3 position
{
get { return parentMatrix.MultiplyPoint3x4(localPosition); }
set { localPosition = parentMatrix.inverse.MultiplyPoint3x4(value); }
}
public Quaternion rotation
{
get { return GetGlobalRotation(); }
set { SetGlobalRotation(value); }
}
public Vector3 right
{
get { return localToWorldMatrix.MultiplyVector(Vector3.right).normalized; }
set { MatchDirection(Vector3.right, value); }
}
public Vector3 up
{
get { return localToWorldMatrix.MultiplyVector(Vector3.up).normalized; }
set { MatchDirection(Vector3.up, value); }
}
public Vector3 forward
{
get { return localToWorldMatrix.MultiplyVector(Vector3.forward).normalized; }
set { MatchDirection(Vector3.forward, value); }
}
public Matrix4x4 localToWorldMatrix
{
get { return m_LocalToWorldMatrix; }
}
public Matrix4x4 worldToLocalMatrix
{
get { return localToWorldMatrix.inverse; }
}
private Matrix4x4 parentMatrix
{
get
{
var parentMatrix = Matrix4x4.identity;
if (parent != null)
parentMatrix = parent.localToWorldMatrix;
return parentMatrix;
}
}
internal override void OnDestroy()
{
if (parent != null)
parent.RemoveChild(this);
m_Parent = null;
m_Children.Clear();
}
private void Update()
{
m_LocalToWorldMatrix = parentMatrix * Matrix4x4.TRS(localPosition, localRotation, localScale);
foreach (var child in m_Children)
child.Update();
}
private void AddChild(TransformCache transform)
{
m_Children.Add(transform);
}
private void InsertChildAt(int index, TransformCache transform)
{
m_Children.Insert(index, transform);
}
private void RemoveChild(TransformCache transform)
{
m_Children.Remove(transform);
}
private void RemoveChildAt(int index)
{
m_Children.RemoveAt(index);
}
private int GetSiblingIndex()
{
if (parent == null)
return -1;
return parent.m_Children.IndexOf(this);
}
private void SetSiblingIndex(int index)
{
if (parent != null)
{
var currentIndex = parent.m_Children.IndexOf(this);
var indexToRemove = index < currentIndex ? currentIndex + 1 : currentIndex;
parent.InsertChildAt(index, this);
parent.RemoveChildAt(indexToRemove);
}
}
public void SetParent(TransformCache newParent)
{
SetParent(newParent, true);
}
public void SetParent(TransformCache newParent, bool worldPositionStays)
{
if (m_Parent == newParent)
return;
var oldPosition = position;
var oldRotation = rotation;
if (m_Parent != null)
m_Parent.RemoveChild(this);
m_Parent = newParent;
if (m_Parent != null)
m_Parent.AddChild(this);
if (worldPositionStays)
{
position = oldPosition;
rotation = oldRotation;
}
else
{
Update();
}
}
private Quaternion GetGlobalRotation()
{
var globalRotation = localRotation;
var currentParent = parent;
while (currentParent != null)
{
globalRotation = ScaleMulQuat(currentParent.localScale, globalRotation);
globalRotation = currentParent.localRotation * globalRotation;
currentParent = currentParent.parent;
}
return globalRotation;
}
private void SetGlobalRotation(Quaternion r)
{
if (parent != null)
r = parent.InverseTransformRotation(r);
localRotation = r;
}
private Quaternion InverseTransformRotation(Quaternion r)
{
if (parent != null)
r = parent.InverseTransformRotation(r);
r = Quaternion.Inverse(localRotation) * r;
r = ScaleMulQuat(localScale, r);
return r;
}
private Quaternion ScaleMulQuat(Vector3 scale, Quaternion q)
{
var s = new Vector3(Chgsign(1f, scale.x), Chgsign(1f, scale.y), Chgsign(1f, scale.z));
q.x = Chgsign(q.x, s.y * s.z);
q.y = Chgsign(q.y, s.x * s.z);
q.z = Chgsign(q.z, s.x * s.y);
return q;
}
private float Chgsign(float x, float y)
{
return y < 0f ? -x : x;
}
private void MatchDirection(Vector3 localDirection, Vector3 worldDirection)
{
var direction = worldToLocalMatrix.MultiplyVector(worldDirection);
direction = Matrix4x4.TRS(Vector3.zero, localRotation, localScale).MultiplyVector(direction);
var scaledLocalDirection = Vector3.Scale(localDirection, localScale);
var deltaRotation = Quaternion.identity;
if (scaledLocalDirection.sqrMagnitude > 0f)
{
var axis = Vector3.Cross(scaledLocalDirection, direction);
var angle = Vector3.SignedAngle(scaledLocalDirection, direction, axis);
deltaRotation = Quaternion.AngleAxis(angle, axis);
}
localRotation = deltaRotation;
}
IEnumerator<TransformCache> IEnumerable<TransformCache>.GetEnumerator()
{
return m_Children.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)m_Children.GetEnumerator();
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Linq;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class TransformCacheExtensions
{
internal static bool IsDescendant<T>(this T transform, T ancestor) where T : TransformCache
{
if (ancestor != null)
{
var parent = transform.parent;
while (parent != null)
{
if (parent == ancestor)
return true;
parent = parent.parent;
}
}
return false;
}
internal static bool IsDescendant<T>(this T transform, T[] ancestors) where T : TransformCache
{
return ancestors.FirstOrDefault( t => transform.IsDescendant<T>(t) ) != null;
}
internal static T[] FindRoots<T>(this T[] transforms) where T : TransformCache
{
return transforms.Where(t => t.IsDescendant(transforms) == false).ToArray();
}
internal static T FindRoot<T>(this T transform, T[] transforms) where T : TransformCache
{
var roots = transforms.FindRoots<T>();
return roots.FirstOrDefault( r => transform == r || IsDescendant<T>(transform, r) );
}
}
}

View File

@@ -0,0 +1,34 @@
namespace UnityEditor.U2D.Animation
{
internal static class SkinningCopyUtility
{
private static ISkinningSerializer s_SkinningSerializer = new SkinningSerializerJSON();
public static ISkinningSerializer Serializer
{
get { return s_SkinningSerializer; }
set { s_SkinningSerializer = value; }
}
public static string SerializeSkinningCopyDataToString(SkinningCopyData skinningData)
{
return s_SkinningSerializer.Serialize(skinningData);
}
public static bool CanDeserializeSystemCopyBufferToSkinningCopyData()
{
if (!string.IsNullOrEmpty(EditorGUIUtility.systemCopyBuffer))
return CanDeserializeStringToSkinningCopyData(EditorGUIUtility.systemCopyBuffer);
return false;
}
public static bool CanDeserializeStringToSkinningCopyData(string data)
{
return s_SkinningSerializer.CanDeserialize(data);
}
public static SkinningCopyData DeserializeStringToSkinningCopyData(string data)
{
return s_SkinningSerializer.Deserialize(data);
}
}
}

View File

@@ -0,0 +1,570 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEditor.U2D.Sprites;
namespace UnityEditor.U2D.Animation
{
[RequireSpriteDataProvider(typeof(ISpriteMeshDataProvider), typeof(ISpriteBoneDataProvider))]
internal partial class SkinningModule : SpriteEditorModuleBase
{
private static class Styles
{
public static string moduleName = L10n.Tr("Skinning Editor");
}
private SkinningCache m_SkinningCache;
private int m_PrevNearestControl = -1;
private SpriteOutlineRenderer m_SpriteOutlineRenderer;
private MeshPreviewTool m_MeshPreviewTool;
private SkinningMode m_PreviousSkinningMode;
private SpriteBoneInfluenceTool m_CharacterSpriteTool;
private HorizontalToggleTools m_HorizontalToggleTools;
private AnimationAnalytics m_Analytics;
private ModuleToolGroup m_ModuleToolGroup;
IMeshPreviewBehaviour m_MeshPreviewBehaviourOverride = null;
bool m_CollapseToolbar;
Texture2D m_WorkspaceBackgroundTexture;
internal SkinningCache skinningCache
{
get { return m_SkinningCache; }
}
private BaseTool currentTool
{
get { return skinningCache.selectedTool; }
set { skinningCache.selectedTool = value; }
}
public override string moduleName
{
get { return Styles.moduleName; }
}
public override void OnModuleActivate()
{
m_SkinningCache = Cache.Create<SkinningCache>();
m_WorkspaceBackgroundTexture = new Texture2D(1, 1, TextureFormat.RGBAHalf, false, true);
m_WorkspaceBackgroundTexture.hideFlags = HideFlags.HideAndDontSave;
m_WorkspaceBackgroundTexture.SetPixel(1, 1, new Color(0, 0, 0, 0));
m_WorkspaceBackgroundTexture.Apply();
AddMainUI(spriteEditor.GetMainVisualContainer());
using (skinningCache.DisableUndoScope())
{
skinningCache.Create(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>(), SkinningCachePersistentState.instance);
skinningCache.CreateToolCache(spriteEditor, m_LayoutOverlay);
m_CharacterSpriteTool = skinningCache.CreateTool<SpriteBoneInfluenceTool>();
m_CharacterSpriteTool.Initialize(m_LayoutOverlay);
m_MeshPreviewTool = skinningCache.CreateTool<MeshPreviewTool>();
SetupModuleToolGroup();
m_MeshPreviewTool.Activate();
m_SpriteOutlineRenderer = new SpriteOutlineRenderer(spriteEditor, skinningCache.events);
spriteEditor.enableMouseMoveEvent = true;
Undo.undoRedoPerformed += UndoRedoPerformed;
skinningCache.events.skeletonTopologyChanged.AddListener(SkeletonTopologyChanged);
skinningCache.events.skeletonPreviewPoseChanged.AddListener(SkeletonPreviewPoseChanged);
skinningCache.events.skeletonBindPoseChanged.AddListener(SkeletonBindPoseChanged);
skinningCache.events.characterPartChanged.AddListener(CharacterPartChanged);
skinningCache.events.skinningModeChanged.AddListener(OnViewModeChanged);
skinningCache.events.meshChanged.AddListener(OnMeshChanged);
skinningCache.events.boneNameChanged.AddListener(OnBoneNameChanged);
skinningCache.events.boneDepthChanged.AddListener(OnBoneDepthChanged);
skinningCache.events.meshPreviewBehaviourChange.AddListener(OnMeshPreviewBehaviourChange);
skinningCache.events.dataModified.AddListener(SetOnDataDirty);
skinningCache.RestoreFromPersistentState();
ActivateTool(skinningCache.selectedTool);
skinningCache.RestoreToolStateFromPersistentState();
// Set state for Switch Mode tool
m_PreviousSkinningMode = skinningCache.mode;
if (skinningCache.mode == SkinningMode.Character)
{
skinningCache.GetTool(Tools.SwitchMode).Deactivate();
}
else
{
skinningCache.GetTool(Tools.SwitchMode).Activate();
}
SetupSpriteEditor(true);
m_HorizontalToggleTools = new HorizontalToggleTools(skinningCache)
{
onActivateTool = (b) =>
{
using (skinningCache.UndoScope(TextContent.setTool))
{
ActivateTool(b);
}
}
};
var ai = spriteEditor.GetDataProvider<ISpriteEditorDataProvider>() as AssetImporter;
m_Analytics = new AnimationAnalytics(new UnityAnalyticsStorage(),
skinningCache.events,
new SkinningModuleAnalyticsModel(skinningCache),
ai == null ? -1 : ai.GetInstanceID());
UpdateCollapseToolbar();
}
}
public override void OnModuleDeactivate()
{
if (m_SpriteOutlineRenderer != null)
m_SpriteOutlineRenderer.Dispose();
spriteEditor.enableMouseMoveEvent = false;
Undo.undoRedoPerformed -= UndoRedoPerformed;
skinningCache.events.skeletonTopologyChanged.RemoveListener(SkeletonTopologyChanged);
skinningCache.events.skeletonPreviewPoseChanged.RemoveListener(SkeletonPreviewPoseChanged);
skinningCache.events.skeletonBindPoseChanged.RemoveListener(SkeletonBindPoseChanged);
skinningCache.events.characterPartChanged.RemoveListener(CharacterPartChanged);
skinningCache.events.skinningModeChanged.RemoveListener(OnViewModeChanged);
skinningCache.events.meshChanged.RemoveListener(OnMeshChanged);
skinningCache.events.boneNameChanged.RemoveListener(OnBoneNameChanged);
skinningCache.events.boneDepthChanged.RemoveListener(OnBoneDepthChanged);
skinningCache.events.meshPreviewBehaviourChange.RemoveListener(OnMeshPreviewBehaviourChange);
RemoveMainUI(spriteEditor.GetMainVisualContainer());
RestoreSpriteEditor();
m_Analytics.Dispose();
m_Analytics = null;
Cache.Destroy(m_SkinningCache);
}
private void UpdateCollapseToolbar()
{
m_CollapseToolbar = SkinningModuleSettings.compactToolBar;
m_WeightToolbar.CollapseToolBar(m_CollapseToolbar);
m_MeshToolbar.CollapseToolBar(m_CollapseToolbar);
m_BoneToolbar.CollapseToolBar(m_CollapseToolbar);
m_LayoutOverlay.verticalToolbar.Collapse(m_CollapseToolbar);
m_HorizontalToggleTools.collapseToolbar = m_CollapseToolbar;
}
private void SetOnDataDirty()
{
DataModified();
}
private void OnBoneNameChanged(BoneCache bone)
{
var character = skinningCache.character;
if (character != null && character.skeleton == bone.skeleton)
skinningCache.SyncSpriteSheetSkeletons();
DataModified();
}
private void OnBoneDepthChanged(BoneCache bone)
{
var sprites = skinningCache.GetSprites();
var controller = new SpriteMeshDataController();
foreach (var sprite in sprites)
{
var mesh = sprite.GetMesh();
if (mesh.ContainsBone(bone))
{
controller.spriteMeshData = mesh;
controller.SortTrianglesByDepth();
skinningCache.events.meshChanged.Invoke(mesh);
}
}
DataModified();
}
private void OnMeshChanged(MeshCache mesh)
{
DataModified();
}
private void DataModified()
{
spriteEditor.SetDataModified();
}
private void OnViewModeChanged(SkinningMode mode)
{
SetupSpriteEditor();
}
private void SetupSpriteEditor(bool setPreviewTexture = false)
{
var textureProvider = spriteEditor.GetDataProvider<ITextureDataProvider>();
if (textureProvider == null)
return;
int width = 0, height = 0;
if (skinningCache.mode == SkinningMode.SpriteSheet)
{
textureProvider.GetTextureActualWidthAndHeight(out width, out height);
}
else
{
width = skinningCache.character.dimension.x;
height = skinningCache.character.dimension.y;
}
if (m_PreviousSkinningMode != skinningCache.mode || setPreviewTexture)
{
spriteEditor.SetPreviewTexture(m_WorkspaceBackgroundTexture, width, height);
if (m_PreviousSkinningMode != skinningCache.mode)
{
m_PreviousSkinningMode = skinningCache.mode;
spriteEditor.ResetZoomAndScroll();
}
}
spriteEditor.spriteRects = new List<SpriteRect>();
}
private void RestoreSpriteEditor()
{
var textureProvider = spriteEditor.GetDataProvider<ITextureDataProvider>();
if (textureProvider != null)
{
int width, height;
textureProvider.GetTextureActualWidthAndHeight(out width, out height);
var texture = textureProvider.previewTexture;
spriteEditor.SetPreviewTexture(texture, width, height);
}
var spriteRectProvider = spriteEditor.GetDataProvider<ISpriteEditorDataProvider>();
if (spriteRectProvider != null)
spriteEditor.spriteRects = new List<SpriteRect>(spriteRectProvider.GetSpriteRects());
}
public override bool CanBeActivated()
{
var dataProvider = spriteEditor.GetDataProvider<ISpriteEditorDataProvider>();
return dataProvider == null ? false : dataProvider.spriteImportMode != SpriteImportMode.None;
}
public override void DoPostGUI()
{
if (!spriteEditor.windowDimension.Contains(Event.current.mousePosition))
HandleUtility.nearestControl = 0;
if (Event.current.type == EventType.Layout && m_PrevNearestControl != HandleUtility.nearestControl)
{
m_PrevNearestControl = HandleUtility.nearestControl;
spriteEditor.RequestRepaint();
}
skinningCache.EndUndoOperation();
}
public override void DoMainGUI()
{
Debug.Assert(currentTool != null);
DoViewGUI();
if (!spriteEditor.editingDisabled)
skinningCache.selectionTool.DoGUI();
m_MeshPreviewTool.previewBehaviourOverride = m_MeshPreviewBehaviourOverride != null ? m_MeshPreviewBehaviourOverride : currentTool.previewBehaviour;
m_MeshPreviewTool.DoGUI();
m_MeshPreviewTool.DrawOverlay();
m_SpriteOutlineRenderer.RenderSpriteOutline(spriteEditor, skinningCache.selectedSprite);
m_MeshPreviewTool.OverlayWireframe();
if (!spriteEditor.editingDisabled)
{
currentTool.DoGUI();
DoCopyPasteKeyboardEventHandling();
}
DrawRectGizmos();
if (SkinningModuleSettings.compactToolBar != m_CollapseToolbar)
UpdateCollapseToolbar();
}
public override void DoToolbarGUI(Rect drawArea)
{
m_HorizontalToggleTools.DoGUI(drawArea, currentTool, spriteEditor.editingDisabled);
}
void DoCopyPasteKeyboardEventHandling()
{
var evt = Event.current;
if (evt.type == EventType.ValidateCommand)
{
if (evt.commandName == "Copy" || evt.commandName == "Paste")
evt.Use();
return;
}
if (evt.type == EventType.ExecuteCommand)
{
var copyTool = skinningCache.GetTool(Tools.CopyPaste) as CopyTool;
if (copyTool != null && evt.commandName == "Copy")
{
copyTool.OnCopyActivated();
evt.Use();
}
else if (copyTool != null && evt.commandName == "Paste")
{
var boneReadOnly = skinningCache.bonesReadOnly;
copyTool.OnPasteActivated(!boneReadOnly, true, false, false);
evt.Use();
}
}
}
private void DrawRectGizmos()
{
if (Event.current.type == EventType.Repaint)
{
var selectedSprite = skinningCache.selectedSprite;
var sprites = skinningCache.GetSprites();
var unselectedRectColor = new Color(1f, 1f, 1f, 0.5f);
foreach (var sprite in sprites)
{
var skeleton = skinningCache.GetEffectiveSkeleton(sprite);
Debug.Assert(skeleton != null);
if (skeleton.isPosePreview)
continue;
var color = unselectedRectColor;
if (sprite == selectedSprite)
color = DrawingUtility.kSpriteBorderColor;
if (skinningCache.mode == SkinningMode.Character
&& sprite != selectedSprite)
continue;
var matrix = sprite.GetLocalToWorldMatrixFromMode();
var rect = new Rect(matrix.MultiplyPoint3x4(Vector3.zero), sprite.textureRect.size);
DrawingUtility.BeginLines(color);
DrawingUtility.DrawBox(rect);
DrawingUtility.EndLines();
}
}
}
private void UndoRedoPerformed()
{
using (new DisableUndoScope(skinningCache))
{
UpdateToggleState();
skinningCache.UndoRedoPerformed();
SetupSpriteEditor();
}
}
#region CharacterConsistency
//TODO: Bring this to a better place, maybe CharacterController
private void SkeletonPreviewPoseChanged(SkeletonCache skeleton)
{
var character = skinningCache.character;
if (character != null && character.skeleton == skeleton)
skinningCache.SyncSpriteSheetSkeletons();
}
private void SkeletonBindPoseChanged(SkeletonCache skeleton)
{
var character = skinningCache.character;
if (character != null && character.skeleton == skeleton)
skinningCache.SyncSpriteSheetSkeletons();
DataModified();
}
private void SkeletonTopologyChanged(SkeletonCache skeleton)
{
var character = skinningCache.character;
if (character == null)
{
var sprite = FindSpriteFromSkeleton(skeleton);
Debug.Assert(sprite != null);
sprite.UpdateMesh(skeleton.bones);
DataModified();
}
else if (character.skeleton == skeleton)
{
skinningCache.CreateSpriteSheetSkeletons();
DataModified();
}
}
private void CharacterPartChanged(CharacterPartCache characterPart)
{
var character = skinningCache.character;
Debug.Assert(character != null);
using (new DefaultPoseScope(character.skeleton))
{
skinningCache.CreateSpriteSheetSkeleton(characterPart);
DataModified();
}
if (skinningCache.mode == SkinningMode.Character)
characterPart.SyncSpriteSheetSkeleton();
}
private SpriteCache FindSpriteFromSkeleton(SkeletonCache skeleton)
{
var sprites = skinningCache.GetSprites();
return sprites.FirstOrDefault(sprite => sprite.GetSkeleton() == skeleton);
}
#endregion
public override bool ApplyRevert(bool apply)
{
if (apply)
{
m_Analytics.FlushEvent();
ApplyChanges(skinningCache, spriteEditor.GetDataProvider<ISpriteEditorDataProvider>());
DoApplyAnalytics();
}
else
skinningCache.Revert();
return true;
}
static internal void ApplyChanges(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
{
skinningCache.applyingChanges = true;
skinningCache.RestoreBindPose();
ApplyBone(skinningCache, dataProvider);
ApplyMesh(skinningCache, dataProvider);
ApplyCharacter(skinningCache, dataProvider);
skinningCache.applyingChanges = false;
}
private void DoApplyAnalytics()
{
var sprites = skinningCache.GetSprites();
var spriteBoneCount = sprites.Select(s => s.GetSkeleton().BoneCount).ToArray();
BoneCache[] bones = null;
if (skinningCache.hasCharacter)
bones = skinningCache.character.skeleton.bones;
else
bones = sprites.SelectMany(s => s.GetSkeleton().bones).ToArray();
m_Analytics.SendApplyEvent(sprites.Length, spriteBoneCount, bones);
}
static void ApplyBone(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
{
var boneDataProvider = dataProvider.GetDataProvider<ISpriteBoneDataProvider>();
if (boneDataProvider != null)
{
var sprites = skinningCache.GetSprites();
foreach (var sprite in sprites)
{
var bones = sprite.GetSkeleton().bones;
boneDataProvider.SetBones(new GUID(sprite.id), bones.ToSpriteBone(sprite.localToWorldMatrix).ToList());
}
}
}
static void ApplyMesh(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
{
var meshDataProvider = dataProvider.GetDataProvider<ISpriteMeshDataProvider>();
if (meshDataProvider != null)
{
var sprites = skinningCache.GetSprites();
foreach (var sprite in sprites)
{
var mesh = sprite.GetMesh();
var guid = new GUID(sprite.id);
meshDataProvider.SetVertices(guid, mesh.vertices.Select(x =>
new Vertex2DMetaData()
{
boneWeight = x.editableBoneWeight.ToBoneWeight(false),
position = x.position
}
).ToArray());
meshDataProvider.SetIndices(guid, mesh.indices.ToArray());
meshDataProvider.SetEdges(guid, mesh.edges.Select(x => new Vector2Int(x.index1, x.index2)).ToArray());
}
}
}
static void ApplyCharacter(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
{
var characterDataProvider = dataProvider.GetDataProvider<ICharacterDataProvider>();
var character = skinningCache.character;
if (characterDataProvider != null && character != null)
{
var data = new CharacterData();
var characterBones = character.skeleton.bones;
data.bones = characterBones.ToSpriteBone(Matrix4x4.identity);
var parts = character.parts;
data.parts = parts.Select(x =>
new CharacterPart()
{
spriteId = x.sprite.id,
spritePosition = new RectInt((int)x.position.x, (int)x.position.y, (int)x.sprite.textureRect.width, (int)x.sprite.textureRect.height),
bones = x.bones.Select(bone => Array.IndexOf(characterBones, bone)).ToArray()
}
).ToArray();
characterDataProvider.SetCharacterData(data);
}
}
void OnMeshPreviewBehaviourChange(IMeshPreviewBehaviour meshPreviewBehaviour)
{
m_MeshPreviewBehaviourOverride = meshPreviewBehaviour;
}
private void SetupModuleToolGroup()
{
m_ModuleToolGroup = new ModuleToolGroup();
m_ModuleToolGroup.AddToolToGroup(0, skinningCache.GetTool(Tools.Visibility), null);
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.EditGeometry), () => currentTool = skinningCache.GetTool(Tools.EditGeometry));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.CreateVertex), () => currentTool = skinningCache.GetTool(Tools.CreateVertex));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.CreateEdge), () => currentTool = skinningCache.GetTool(Tools.CreateEdge));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.SplitEdge), () => currentTool = skinningCache.GetTool(Tools.SplitEdge));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.GenerateGeometry), () => currentTool = skinningCache.GetTool(Tools.GenerateGeometry));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.EditPose), () => currentTool = skinningCache.GetTool(Tools.EditPose));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.EditJoints), () => currentTool = skinningCache.GetTool(Tools.EditJoints));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.CreateBone), () => currentTool = skinningCache.GetTool(Tools.CreateBone));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.SplitBone), () => currentTool = skinningCache.GetTool(Tools.SplitBone));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.WeightSlider), () => currentTool = skinningCache.GetTool(Tools.WeightSlider));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.WeightBrush), () => currentTool = skinningCache.GetTool(Tools.WeightBrush));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.GenerateWeights), () => currentTool = skinningCache.GetTool(Tools.GenerateWeights));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.BoneInfluence), () => currentTool = skinningCache.GetTool(Tools.BoneInfluence));
m_ModuleToolGroup.AddToolToGroup(1, skinningCache.GetTool(Tools.CopyPaste), () => currentTool = skinningCache.GetTool(Tools.CopyPaste));
}
}
}

View File

@@ -0,0 +1,394 @@
using System;
using UnityEditor.U2D.Common;
using UnityEditor.U2D.Layout;
using UnityEngine;
using UnityEditor.ShortcutManagement;
using UnityEngine.UIElements;
namespace UnityEditor.U2D.Animation
{
internal partial class SkinningModule
{
private LayoutOverlay m_LayoutOverlay;
private BoneToolbar m_BoneToolbar;
private MeshToolbar m_MeshToolbar;
private WeightToolbar m_WeightToolbar;
private InternalEditorBridge.ShortcutContext m_ShortcutContext;
private static SkinningModule GetModuleFromContext(ShortcutArguments args)
{
var sc = args.context as InternalEditorBridge.ShortcutContext;
if (sc == null)
return null;
return sc.context as SkinningModule;
}
[Shortcut("2D/Animation/Toggle Tool Text", typeof(InternalEditorBridge.ShortcutContext), KeyCode.BackQuote, ShortcutModifiers.Shift)]
private static void CollapseToolbar(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null)
{
SkinningModuleSettings.compactToolBar = !SkinningModuleSettings.compactToolBar;
}
}
[Shortcut("2D/Animation/Restore Bind Pose", typeof(InternalEditorBridge.ShortcutContext), KeyCode.Alpha1, ShortcutModifiers.Shift)]
private static void DisablePoseModeKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
var effectiveSkeleton = sm.skinningCache.GetEffectiveSkeleton(sm.skinningCache.selectedSprite);
if (effectiveSkeleton != null && effectiveSkeleton.isPosePreview)
{
using (sm.skinningCache.UndoScope(TextContent.restorePose))
{
sm.skinningCache.RestoreBindPose();
sm.skinningCache.events.shortcut.Invoke("#1");
}
}
}
}
[Shortcut("2D/Animation/Toggle Character Mode", typeof(InternalEditorBridge.ShortcutContext), KeyCode.Alpha2, ShortcutModifiers.Shift)]
private static void ToggleCharacterModeKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled && sm.skinningCache.hasCharacter)
{
var tool = sm.skinningCache.GetTool(Tools.SwitchMode);
using (sm.skinningCache.UndoScope(TextContent.setMode))
{
if (tool.isActive)
tool.Deactivate();
else
tool.Activate();
}
sm.skinningCache.events.shortcut.Invoke("#2");
}
}
[Shortcut("2D/Animation/Preview Pose", typeof(InternalEditorBridge.ShortcutContext), KeyCode.Q, ShortcutModifiers.Shift)]
private static void EditPoseKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetSkeletonTool(Tools.EditPose);
sm.skinningCache.events.shortcut.Invoke("#q");
}
}
[Shortcut("2D/Animation/Edit Bone", typeof(InternalEditorBridge.ShortcutContext), KeyCode.W, ShortcutModifiers.Shift)]
private static void EditJointsKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetSkeletonTool(Tools.EditJoints);
sm.skinningCache.events.shortcut.Invoke("#w");
}
}
[Shortcut("2D/Animation/Create Bone", typeof(InternalEditorBridge.ShortcutContext), KeyCode.E, ShortcutModifiers.Shift)]
private static void CreateBoneKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetSkeletonTool(Tools.CreateBone);
sm.skinningCache.events.shortcut.Invoke("#e");
}
}
[Shortcut("2D/Animation/Split Bone", typeof(InternalEditorBridge.ShortcutContext), KeyCode.R, ShortcutModifiers.Shift)]
private static void SplitBoneKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetSkeletonTool(Tools.SplitBone);
sm.skinningCache.events.shortcut.Invoke("#r");
}
}
[Shortcut("2D/Animation/Auto Geometry", typeof(InternalEditorBridge.ShortcutContext), KeyCode.A, ShortcutModifiers.Shift)]
private static void GenerateGeometryKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetMeshTool(Tools.GenerateGeometry);
sm.skinningCache.events.shortcut.Invoke("#a");
}
}
[Shortcut("2D/Animation/Edit Geometry", typeof(InternalEditorBridge.ShortcutContext), KeyCode.S, ShortcutModifiers.Shift)]
private static void MeshSelectionKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetMeshTool(Tools.EditGeometry);
sm.skinningCache.events.shortcut.Invoke("#s");
}
}
[Shortcut("2D/Animation/Create Vertex", typeof(InternalEditorBridge.ShortcutContext), KeyCode.J, ShortcutModifiers.Shift)]
private static void CreateVertex(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetMeshTool(Tools.CreateVertex);
sm.skinningCache.events.shortcut.Invoke("#d");
}
}
[Shortcut("2D/Animation/Create Edge", typeof(InternalEditorBridge.ShortcutContext), KeyCode.G, ShortcutModifiers.Shift)]
private static void CreateEdgeKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetMeshTool(Tools.CreateEdge);
sm.skinningCache.events.shortcut.Invoke("#g");
}
}
[Shortcut("2D/Animation/Split Edge", typeof(InternalEditorBridge.ShortcutContext), KeyCode.H, ShortcutModifiers.Shift)]
private static void SplitEdge(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetMeshTool(Tools.SplitEdge);
sm.skinningCache.events.shortcut.Invoke("#h");
}
}
[Shortcut("2D/Animation/Auto Weights", typeof(InternalEditorBridge.ShortcutContext), KeyCode.Z, ShortcutModifiers.Shift)]
private static void GenerateWeightsKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetWeightTool(Tools.GenerateWeights);
sm.skinningCache.events.shortcut.Invoke("#z");
}
}
[Shortcut("2D/Animation/Weight Slider", typeof(InternalEditorBridge.ShortcutContext), KeyCode.X, ShortcutModifiers.Shift)]
private static void WeightSliderKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetWeightTool(Tools.WeightSlider);
sm.skinningCache.events.shortcut.Invoke("#x");
}
}
[Shortcut("2D/Animation/Weight Brush", typeof(InternalEditorBridge.ShortcutContext), KeyCode.N, ShortcutModifiers.Shift)]
private static void WeightBrushKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.SetWeightTool(Tools.WeightBrush);
sm.skinningCache.events.shortcut.Invoke("#c");
}
}
[Shortcut("2D/Animation/Bone Influence", typeof(InternalEditorBridge.ShortcutContext), KeyCode.V, ShortcutModifiers.Shift)]
private static void BoneInfluenceKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled && sm.skinningCache.mode == SkinningMode.Character)
{
sm.SetWeightTool(Tools.BoneInfluence);
sm.skinningCache.events.shortcut.Invoke("#v");
}
}
[Shortcut("2D/Animation/Paste Panel Weights", typeof(InternalEditorBridge.ShortcutContext), KeyCode.B, ShortcutModifiers.Shift)]
private static void PastePanelKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.m_HorizontalToggleTools.TogglePasteTool(sm.currentTool);
sm.skinningCache.events.shortcut.Invoke("#b");
}
}
[Shortcut("2D/Animation/Visibility Panel", typeof(InternalEditorBridge.ShortcutContext), KeyCode.P, ShortcutModifiers.Shift)]
private static void VisibilityPanelKey(ShortcutArguments args)
{
var sm = GetModuleFromContext(args);
if (sm != null && !sm.spriteEditor.editingDisabled)
{
sm.m_HorizontalToggleTools.ToggleVisibilityTool(sm.currentTool);
sm.skinningCache.events.shortcut.Invoke("#p");
}
}
private void AddMainUI(VisualElement mainView)
{
var visualTree = ResourceLoader.Load<VisualTreeAsset>("LayoutOverlay/LayoutOverlay.uxml");
VisualElement clone = visualTree.CloneTree();
m_LayoutOverlay = clone.Q<LayoutOverlay>("LayoutOverlay");
mainView.Add(m_LayoutOverlay);
m_LayoutOverlay.hasScrollbar = true;
m_LayoutOverlay.StretchToParentSize();
CreateBoneToolbar();
CreateMeshToolbar();
CreateWeightToolbar();
m_ShortcutContext = new InternalEditorBridge.ShortcutContext()
{
isActive = isFocused,
context = this
};
InternalEditorBridge.RegisterShortcutContext(m_ShortcutContext);
InternalEditorBridge.AddEditorApplicationProjectLoadedCallback(OnProjectLoaded);
}
private void OnProjectLoaded()
{
if (m_ShortcutContext != null)
InternalEditorBridge.RegisterShortcutContext(m_ShortcutContext);
}
private void DoViewGUI()
{
if (spriteEditor.editingDisabled == m_BoneToolbar.enabledSelf)
{
m_BoneToolbar.SetEnabled(!spriteEditor.editingDisabled);
m_MeshToolbar.SetEnabled(!spriteEditor.editingDisabled);
m_WeightToolbar.SetEnabled(!spriteEditor.editingDisabled);
}
if (spriteEditor.editingDisabled == m_LayoutOverlay.rightOverlay.enabledSelf)
{
m_LayoutOverlay.rightOverlay.SetEnabled(!spriteEditor.editingDisabled);
m_LayoutOverlay.rightOverlay.visible = !spriteEditor.editingDisabled;
}
}
private bool isFocused()
{
return spriteEditor != null && (EditorWindow.focusedWindow == spriteEditor as EditorWindow);
}
private void CreateBoneToolbar()
{
m_BoneToolbar = BoneToolbar.GenerateFromUXML();
m_BoneToolbar.Setup(skinningCache);
m_LayoutOverlay.verticalToolbar.AddToContainer(m_BoneToolbar);
m_BoneToolbar.SetSkeletonTool += SetSkeletonTool;
m_BoneToolbar.SetEnabled(!spriteEditor.editingDisabled);
}
private void CreateMeshToolbar()
{
m_MeshToolbar = MeshToolbar.GenerateFromUXML();
m_MeshToolbar.skinningCache = skinningCache;
m_LayoutOverlay.verticalToolbar.AddToContainer(m_MeshToolbar);
m_MeshToolbar.SetMeshTool += SetMeshTool;
m_MeshToolbar.SetEnabled(!spriteEditor.editingDisabled);
}
private void CreateWeightToolbar()
{
m_WeightToolbar = WeightToolbar.GenerateFromUXML();
m_WeightToolbar.skinningCache = skinningCache;
m_LayoutOverlay.verticalToolbar.AddToContainer(m_WeightToolbar);
m_WeightToolbar.SetWeightTool += SetWeightTool;
m_WeightToolbar.SetEnabled(!spriteEditor.editingDisabled);
}
private void SetSkeletonTool(Tools toolType)
{
var tool = skinningCache.GetTool(toolType) as SkeletonToolWrapper;
if (currentTool == tool)
return;
using (skinningCache.UndoScope(TextContent.setTool))
{
ActivateTool(tool);
if (tool.editBindPose)
skinningCache.RestoreBindPose();
}
}
private void SetMeshTool(Tools toolType)
{
var tool = skinningCache.GetTool(toolType);
if (currentTool == tool)
return;
using (skinningCache.UndoScope(TextContent.setTool))
{
ActivateTool(tool);
skinningCache.RestoreBindPose();
UnselectBones();
}
}
private void SetWeightTool(Tools toolType)
{
var tool = skinningCache.GetTool(toolType);
if (currentTool == tool)
return;
using (skinningCache.UndoScope(TextContent.setTool))
{
ActivateTool(tool);
}
}
private void ActivateTool(BaseTool tool)
{
m_ModuleToolGroup.ActivateTool(tool);
UpdateToggleState();
skinningCache.events.toolChanged.Invoke(tool);
}
private void UnselectBones()
{
skinningCache.skeletonSelection.Clear();
skinningCache.events.boneSelectionChanged.Invoke();
}
private void UpdateToggleState()
{
Debug.Assert(m_BoneToolbar != null);
Debug.Assert(m_MeshToolbar != null);
Debug.Assert(m_WeightToolbar != null);
m_BoneToolbar.UpdateToggleState();
m_MeshToolbar.UpdateToggleState();
m_WeightToolbar.UpdateToggleState();
}
private void RemoveMainUI(VisualElement mainView)
{
InternalEditorBridge.RemoveEditorApplicationProjectLoadedCallback(OnProjectLoaded);
InternalEditorBridge.UnregisterShortcutContext(m_ShortcutContext);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal interface ISkinningSerializer
{
bool CanDeserialize(string data);
SkinningCopyData Deserialize(string data);
string Serialize(SkinningCopyData skinningData);
}
[Serializable]
internal class SpriteBoneCopyData
{
public UnityEngine.U2D.SpriteBone spriteBone;
public int order;
}
[Serializable]
internal class SkinningCopySpriteData
{
public string spriteName;
public List<SpriteBoneCopyData> spriteBones;
public List<Vertex2D> vertices;
public List<int> indices;
public List<Edge> edges;
public List<string> boneWeightNames;
}
[Serializable]
internal class SkinningCopyData
{
public float pixelsPerUnit;
public List<SkinningCopySpriteData> copyData = new List<SkinningCopySpriteData>();
}
}

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SkinningSerializerJSON : ISkinningSerializer
{
public bool CanDeserialize(string data)
{
bool result = true;
try
{
JsonUtility.FromJson<SkinningCopyData>(data);
}
catch
{
result = false;
}
return result;
}
public SkinningCopyData Deserialize(string data)
{
return JsonUtility.FromJson<SkinningCopyData>(data);
}
public string Serialize(SkinningCopyData skinningData)
{
return JsonUtility.ToJson(skinningData);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace UnityEditor.U2D.Animation
{
internal class SkinningXMLSerializer : ISkinningSerializer
{
public bool CanDeserialize(string data)
{
bool result = false;
try
{
using (TextReader textReader = new StringReader(data))
{
using (XmlTextReader xmlReader = new XmlTextReader(textReader))
{
var xmlSerializer = new XmlSerializer(typeof(List<UnityEngine.U2D.SpriteBone>));
result = xmlSerializer.CanDeserialize(xmlReader);
}
}
}
catch
{
}
return result;
}
public SkinningCopyData Deserialize(string data)
{
SkinningCopyData skinningCopyData;
using (TextReader textReader = new StringReader(data))
{
using (XmlTextReader xmlReader = new XmlTextReader(textReader))
{
var xmlSerializer = new XmlSerializer(typeof(SkinningCopyData));
if (!xmlSerializer.CanDeserialize(xmlReader))
{
throw new InvalidOperationException(
"Unable to deserialize system copy buffer to Skinning Copy Data");
}
skinningCopyData = (SkinningCopyData)xmlSerializer.Deserialize(xmlReader);
}
}
return skinningCopyData;
}
public string Serialize(SkinningCopyData skinningData)
{
XmlSerializer xmlSerializer = new XmlSerializer(skinningData.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, skinningData);
return textWriter.ToString();
}
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.U2D.Animation
{
internal class SelectListView : ListView
{
public class CustomUxmlFactory : UxmlFactory<SelectListView, UxmlTraits> {}
public new void AddToSelection(int index)
{
base.AddToSelection(index);
}
public new void ClearSelection()
{
base.ClearSelection();
}
}
internal class SpriteBoneInfluenceListWidget : VisualElement
{
public class CustomUxmlFactory : UxmlFactory<SpriteBoneInfluenceListWidget, CustomUxmlTraits> {}
public class CustomUxmlTraits : UxmlTraits {}
private List<BoneCache> m_BoneInfluences;
private SelectListView m_ListView;
bool m_IgnoreSelectionChange = false;
private Button m_AddButton;
private Button m_RemoveButton;
public Action onAddBone = () => {};
public Action onRemoveBone = () => {};
public Action<IEnumerable<BoneCache>> onReordered = _ => {};
public Action<IEnumerable<BoneCache>> onSelectionChanged = (s) => {};
public Func<SpriteBoneInfluenceToolController> GetController = () => null;
public SpriteBoneInfluenceListWidget()
{
var visualTree = ResourceLoader.Load<VisualTreeAsset>("SkinningModule/SpriteBoneInfluenceListWidget.uxml");
var ve = visualTree.CloneTree().Q("Container");
ve.styleSheets.Add(ResourceLoader.Load<StyleSheet>("SkinningModule/SpriteBoneInfluenceListWidgetStyle.uss"));
if (EditorGUIUtility.isProSkin)
AddToClassList("Dark");
this.Add(ve);
BindElements();
}
private void BindElements()
{
m_ListView = this.Q<SelectListView>();
m_ListView.selectionType = SelectionType.Multiple;
m_ListView.itemsSource = m_BoneInfluences;
m_ListView.makeItem = () =>
{
var label = new Label()
{
name = "ListRow"
};
return label;
};
m_ListView.bindItem = (e, index) =>
{
if (m_BoneInfluences[index] == null)
return;
(e as Label).text = m_BoneInfluences[index].name;
if (index % 2 == 0)
{
e.RemoveFromClassList("ListRowOddColor");
e.AddToClassList("ListRowEvenColor");
}
else
{
e.RemoveFromClassList("ListRowEvenColor");
e.AddToClassList("ListRowOddColor");
}
};
m_ListView.onSelectionChange += OnListViewSelectionChanged;
m_AddButton = this.Q<Button>("AddButton");
m_AddButton.clickable.clicked += OnAddButtonClick;
m_RemoveButton = this.Q<Button>("RemoveButton");
m_RemoveButton.clickable.clicked += OnRemoveButtonClick;
this.RegisterCallback<DragPerformEvent>(x => onReordered(m_BoneInfluences) );
}
private void OnListViewSelectionChanged(IEnumerable<object> o)
{
if (m_IgnoreSelectionChange)
return;
var selectedBones = o.OfType<BoneCache>().ToArray();
onSelectionChanged(selectedBones);
}
private void OnAddButtonClick()
{
onAddBone();
}
private void OnRemoveButtonClick()
{
onRemoveBone();
}
public void Update()
{
m_BoneInfluences = GetController().GetSelectedSpriteBoneInfluence().ToList();
m_ListView.itemsSource = m_BoneInfluences;
m_ListView.Refresh();
}
internal void OnBoneSelectionChanged()
{
var selectedBones = GetController().GetSelectedBoneForList(m_BoneInfluences);
m_IgnoreSelectionChange = true;
m_ListView.ClearSelection();
foreach (var bone in selectedBones)
{
m_ListView.AddToSelection(bone);
}
m_IgnoreSelectionChange = false;
m_AddButton.SetEnabled(GetController().ShouldEnableAddButton(m_BoneInfluences));
m_RemoveButton.SetEnabled(GetController().InCharacterMode() && m_ListView.selectedIndex >= 0);
}
}
}

View File

@@ -0,0 +1,323 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.U2D.Layout;
namespace UnityEditor.U2D.Animation
{
internal class SpriteBoneInfluenceToolController
{
SkinningEvents m_Events;
ISpriteBoneInfluenceToolModel m_Model;
public SpriteBoneInfluenceToolController(ISpriteBoneInfluenceToolModel model, SkinningEvents events)
{
m_Events = events;
m_Model = model;
}
public void Activate()
{
m_Events.selectedSpriteChanged.AddListener(OnSpriteSelectionChanged);
m_Events.boneSelectionChanged.AddListener(OnBoneSelectionChanged);
m_Events.boneNameChanged.AddListener(OnBoneNameChanged);
m_Events.skeletonTopologyChanged.AddListener(OnSkeletonTopologyChanged);
m_Events.meshChanged.AddListener(OnMeshChanged);
ShowHideView(true);
OnBoneSelectionChanged();
}
public void Deactivate()
{
m_Events.selectedSpriteChanged.RemoveListener(OnSpriteSelectionChanged);
m_Events.boneSelectionChanged.RemoveListener(OnBoneSelectionChanged);
m_Events.boneNameChanged.RemoveListener(OnBoneNameChanged);
m_Events.skeletonTopologyChanged.RemoveListener(OnSkeletonTopologyChanged);
m_Events.meshChanged.RemoveListener(OnMeshChanged);
ShowHideView(false);
}
private void OnMeshChanged(MeshCache mesh)
{
if (m_Model.view.visible)
m_Model.view.OnMeshChanged();
}
private void OnSpriteSelectionChanged(SpriteCache sprite)
{
if (m_Model.view.visible)
{
m_Model.view.OnSpriteSelectionChanged();
SetViewHeaderText();
}
}
private void OnBoneSelectionChanged()
{
if (m_Model.view.visible)
m_Model.view.OnBoneSelectionChanged();
}
private void OnBoneNameChanged(BoneCache bone)
{
if (m_Model.view.visible)
{
m_Model.view.OnSkeletonChanged();
}
}
private void OnSkeletonTopologyChanged(SkeletonCache skeleton)
{
if (m_Model.view.visible)
m_Model.view.OnSkeletonChanged();
}
public void OnViewCreated()
{
m_Model.view.onAddBone += AddSelectedBoneInfluencetoSprite;
m_Model.view.onRemoveBone += RemoveSelectedBoneInfluenceFromSprite;
m_Model.view.onReordered += OnReorderBoneInfluenceFromSprite;
m_Model.view.onSelectionChanged += SelectBones;
m_Model.view.SetController(this);
ShowHideView(false);
}
private void AddSelectedBoneInfluencetoSprite()
{
var character = m_Model.characterSkeleton;
if (character == null)
return;
var characterPart = m_Model.GetSpriteCharacterPart(m_Model.selectedSprite);
var selectedBones = m_Model.selectedBones;
var characterBones = characterPart.bones.ToList();
foreach (var bone in selectedBones)
{
if (!characterBones.Contains(bone))
characterBones.Add(bone);
}
using (m_Model.UndoScope(TextContent.addBoneInfluence))
{
characterPart.bones = characterBones.ToArray();
m_Events.characterPartChanged.Invoke(characterPart);
m_Model.view.OnSkeletonChanged();
m_Model.view.OnBoneSelectionChanged();
}
}
private void RemoveSelectedBoneInfluenceFromSprite()
{
var character = m_Model.characterSkeleton;
if (character == null)
return;
var characterPart = m_Model.GetSpriteCharacterPart(m_Model.selectedSprite);
var selectedBones = m_Model.selectedBones;
var characterBones = characterPart.bones.ToList();
characterBones.RemoveAll(b => selectedBones.Contains(b));
using (m_Model.UndoScope(TextContent.removeBoneInfluence))
{
characterPart.bones = characterBones.ToArray();
m_Events.characterPartChanged.Invoke(characterPart);
characterPart.sprite.SmoothFill();
m_Events.meshChanged.Invoke(characterPart.sprite.GetMesh());
m_Model.view.OnSkeletonChanged();
m_Model.view.OnBoneSelectionChanged();
}
}
private void OnReorderBoneInfluenceFromSprite(IEnumerable<BoneCache> boneCache)
{
var character = m_Model.characterSkeleton;
if (character != null)
{
var characterPart = m_Model.GetSpriteCharacterPart(m_Model.selectedSprite);
using (m_Model.UndoScope(TextContent.reorderBoneInfluence))
{
characterPart.bones = boneCache.ToArray();
m_Events.characterPartChanged.Invoke(characterPart);
m_Model.view.OnSkeletonChanged();
}
}
else
{
using (m_Model.UndoScope(TextContent.reorderBoneInfluence))
{
m_Model.selectedSprite.GetSkeleton().ReorderBones(boneCache);
m_Events.skeletonTopologyChanged.Invoke(m_Model.selectedSprite.GetSkeleton());
}
}
}
private void SelectBones(IEnumerable<BoneCache> selectedBones)
{
using (m_Model.UndoScope(TextContent.boneSelection))
{
m_Model.selectedBones = selectedBones;
m_Events.boneSelectionChanged.Invoke();
}
}
private void ShowHideView(bool show)
{
m_Model.view.SetHiddenFromLayout(!show);
if (show)
{
m_Model.view.OnSpriteSelectionChanged();
SetViewHeaderText();
}
}
private void SetViewHeaderText()
{
var headerText = m_Model.selectedSprite != null ? m_Model.selectedSprite.name : TextContent.noSpriteSelected;
m_Model.view.headerText = headerText;
}
public BoneCache[] GetSelectedSpriteBoneInfluence()
{
var selectedSprite = m_Model.selectedSprite;
if (selectedSprite != null)
{
if (m_Model.hasCharacter)
{
var characterPart = m_Model.GetSpriteCharacterPart(selectedSprite);
return characterPart.bones.ToArray();
}
else
{
return selectedSprite.GetSkeleton().bones;
}
}
return new BoneCache[0];
}
public int[] GetSelectedBoneForList(IEnumerable<BoneCache> bones)
{
var selectedBones = m_Model.selectedBones;
var spriteBones = GetSelectedSpriteBoneInfluence();
var result = new List<int>();
foreach (var bone in selectedBones)
{
var index = Array.IndexOf(spriteBones, bone);
if (index >= 0)
result.Add(index);
}
return result.ToArray();
}
public bool ShouldEnableAddButton(IEnumerable<BoneCache> bones)
{
if (InCharacterMode())
{
var hasSelectedSprite = m_Model.selectedSprite != null;
var selectedBones = m_Model.selectedBones;
return hasSelectedSprite && selectedBones.FirstOrDefault(x => !bones.Contains(x)) != null;
}
return false;
}
public bool InCharacterMode()
{
return m_Model.hasCharacter && m_Model.skinningMode == SkinningMode.Character;
}
}
internal interface ISpriteBoneInfluenceToolModel
{
ISpriteBoneInfluenceWindow view { get; }
IEnumerable<BoneCache> selectedBones { get; set; }
SpriteCache selectedSprite { get; }
bool hasCharacter { get; }
SkinningMode skinningMode { get; }
SkeletonCache characterSkeleton { get; }
UndoScope UndoScope(string description);
CharacterPartCache GetSpriteCharacterPart(SpriteCache sprite);
}
internal class SpriteBoneInfluenceTool : BaseTool, ISpriteBoneInfluenceToolModel
{
SpriteBoneInfluenceToolController m_Controller;
private MeshPreviewBehaviour m_MeshPreviewBehaviour = new MeshPreviewBehaviour();
private SpriteBoneInfluenceWindow m_View;
public SkeletonTool skeletonTool { set; private get; }
public override IMeshPreviewBehaviour previewBehaviour
{
get { return m_MeshPreviewBehaviour; }
}
internal override void OnCreate()
{
m_Controller = new SpriteBoneInfluenceToolController(this, skinningCache.events);
}
ISpriteBoneInfluenceWindow ISpriteBoneInfluenceToolModel.view {get { return m_View; } }
IEnumerable<BoneCache> ISpriteBoneInfluenceToolModel.selectedBones
{
get { return skinningCache.skeletonSelection.elements; }
set { skinningCache.skeletonSelection.elements = value.ToArray(); }
}
SpriteCache ISpriteBoneInfluenceToolModel.selectedSprite { get { return skinningCache.selectedSprite; } }
bool ISpriteBoneInfluenceToolModel.hasCharacter { get { return skinningCache.hasCharacter; } }
SkinningMode ISpriteBoneInfluenceToolModel.skinningMode { get { return skinningCache.mode; } }
SkeletonCache ISpriteBoneInfluenceToolModel.characterSkeleton { get { return skinningCache.character != null ? skinningCache.character.skeleton : null; } }
UndoScope ISpriteBoneInfluenceToolModel.UndoScope(string description)
{
return skinningCache.UndoScope(description);
}
protected override void OnActivate()
{
m_Controller.Activate();
if (skeletonTool != null)
skeletonTool.Activate();
}
protected override void OnDeactivate()
{
m_Controller.Deactivate();
if (skeletonTool != null)
skeletonTool.Deactivate();
}
public override void Initialize(LayoutOverlay layout)
{
if (m_View == null)
{
m_View = SpriteBoneInfluenceWindow.CreateFromUXML();
m_Controller.OnViewCreated();
}
layout.rightOverlay.Add(m_View);
}
protected override void OnGUI()
{
m_MeshPreviewBehaviour.showWeightMap = true;
m_MeshPreviewBehaviour.overlaySelected = true;
m_MeshPreviewBehaviour.drawWireframe = true;
skeletonTool.skeletonStyle = SkeletonStyles.WeightMap;
skeletonTool.mode = SkeletonMode.EditPose;
skeletonTool.editBindPose = false;
skeletonTool.DoGUI();
}
public CharacterPartCache GetSpriteCharacterPart(SpriteCache sprite)
{
return sprite.GetCharacterPart();
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.U2D.Animation
{
using PopupWindow = UnityEngine.UIElements.PopupWindow;
internal interface ISpriteBoneInfluenceWindow
{
string headerText { get; set; }
void SetHiddenFromLayout(bool hide);
void OnSpriteSelectionChanged();
void OnSkeletonChanged();
void OnMeshChanged();
bool visible { get; }
void OnBoneSelectionChanged();
event Action onAddBone;
event Action onRemoveBone;
event Action<IEnumerable<BoneCache>> onReordered;
event Action<IEnumerable<BoneCache>> onSelectionChanged;
void SetController(SpriteBoneInfluenceToolController controller);
}
internal class SpriteBoneInfluenceWindow : VisualElement, ISpriteBoneInfluenceWindow
{
public class CustomUxmlFactory : UxmlFactory<SpriteBoneInfluenceWindow, UxmlTraits> {}
private SpriteBoneInfluenceListWidget m_InfluencesList;
private PopupWindow m_HeaderLabel;
SpriteBoneInfluenceToolController m_Controller;
public event Action onAddBone = () => {};
public event Action onRemoveBone = () => {};
public event Action<IEnumerable<BoneCache>> onReordered = _ => {};
public event Action<IEnumerable<BoneCache>> onSelectionChanged = _ => {};
public string headerText
{
get { return m_HeaderLabel.text; }
set { m_HeaderLabel.text = value; }
}
static internal SpriteBoneInfluenceWindow CreateFromUXML()
{
var visualTree = ResourceLoader.Load<VisualTreeAsset>("SkinningModule/SpriteBoneInfluenceWindow.uxml");
var ve = visualTree.CloneTree().Q("SpriteBoneInfluenceWindow") as SpriteBoneInfluenceWindow;
ve.BindElements();
return ve;
}
internal void BindElements()
{
m_InfluencesList = this.Q<SpriteBoneInfluenceListWidget>();
m_InfluencesList.onAddBone = () => onAddBone();
m_InfluencesList.onRemoveBone = () => onRemoveBone();
m_InfluencesList.onReordered = s => onReordered(s);
m_InfluencesList.onSelectionChanged = (s) => onSelectionChanged(s);
m_InfluencesList.GetController = InternalGetController;
m_HeaderLabel = this.Q<PopupWindow>();
this.styleSheets.Add(ResourceLoader.Load<StyleSheet>("SkinningModule/SpriteBoneInfluenceWindowStyle.uss"));
}
SpriteBoneInfluenceToolController InternalGetController()
{
return m_Controller;
}
public void OnSpriteSelectionChanged()
{
Update();
}
public void OnMeshChanged()
{
Update();
}
public void OnSkeletonChanged()
{
m_InfluencesList.Update();
}
public void OnBoneSelectionChanged()
{
m_InfluencesList.OnBoneSelectionChanged();
}
private void Update()
{
m_InfluencesList.Update();
m_InfluencesList.OnBoneSelectionChanged();
}
void ISpriteBoneInfluenceWindow.SetController(SpriteBoneInfluenceToolController controller)
{
m_Controller = controller;
}
void ISpriteBoneInfluenceWindow.SetHiddenFromLayout(bool hide)
{
this.SetHiddenFromLayout(hide);
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class BoneWeightExtensions
{
public static int GetBoneIndex(this BoneWeight boneWeight, int channelIndex)
{
if (channelIndex < 0 || channelIndex >= 4)
throw new ArgumentOutOfRangeException("Channel index out of range");
if (channelIndex == 0)
return boneWeight.boneIndex0;
if (channelIndex == 1)
return boneWeight.boneIndex1;
if (channelIndex == 2)
return boneWeight.boneIndex2;
if (channelIndex == 3)
return boneWeight.boneIndex3;
return -1;
}
public static void SetBoneIndex(ref BoneWeight boneWeight, int channelIndex, int boneIndex)
{
if (channelIndex < 0 || channelIndex >= 4)
throw new ArgumentOutOfRangeException("Channel index out of range");
if (channelIndex == 0)
boneWeight.boneIndex0 = boneIndex;
if (channelIndex == 1)
boneWeight.boneIndex1 = boneIndex;
if (channelIndex == 2)
boneWeight.boneIndex2 = boneIndex;
if (channelIndex == 3)
boneWeight.boneIndex3 = boneIndex;
}
public static float GetWeight(this BoneWeight boneWeight, int channelIndex)
{
if (channelIndex < 0 || channelIndex >= 4)
throw new ArgumentOutOfRangeException("Channel index out of range");
if (channelIndex == 0)
return boneWeight.weight0;
if (channelIndex == 1)
return boneWeight.weight1;
if (channelIndex == 2)
return boneWeight.weight2;
if (channelIndex == 3)
return boneWeight.weight3;
return 0f;
}
public static void SetWeight(ref BoneWeight boneWeight, int channelIndex, float weight)
{
if (channelIndex < 0 || channelIndex >= 4)
throw new ArgumentOutOfRangeException("Channel index out of range");
if (channelIndex == 0)
boneWeight.weight0 = weight;
if (channelIndex == 1)
boneWeight.weight1 = weight;
if (channelIndex == 2)
boneWeight.weight2 = weight;
if (channelIndex == 3)
boneWeight.weight3 = weight;
}
public static float Sum(this BoneWeight boneWeight)
{
return boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3;
}
public static BoneWeight Normalized(this BoneWeight boneWeight)
{
var sum = boneWeight.Sum();
if (sum == 0 || sum == 1f)
return boneWeight;
var normalized = boneWeight;
var sumInv = 1f / sum;
for (var i = 0; i < 4; ++i)
SetWeight(ref normalized, i, normalized.GetWeight(i) * sumInv);
return normalized;
}
}
}

View File

@@ -0,0 +1,62 @@
using UnityEngine;
using System;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal struct Edge
{
[SerializeField]
int m_Index1;
[SerializeField]
int m_Index2;
public int index1
{
get { return m_Index1; }
set { m_Index1 = value; }
}
public int index2
{
get { return m_Index2; }
set { m_Index2 = value; }
}
public Edge(int inIndex1, int inIndex2)
{
m_Index1 = inIndex1;
m_Index2 = inIndex2;
}
public bool Contains(int index)
{
return index1 == index || index2 == index;
}
public static bool operator==(Edge lhs, Edge rhs)
{
return lhs.Equals(rhs);
}
public static bool operator!=(Edge lhs, Edge rhs)
{
return !lhs.Equals(rhs);
}
public override bool Equals(System.Object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
Edge p = (Edge)obj;
return (index1 == p.index1) && (index2 == p.index2) || (index1 == p.index2) && (index2 == p.index1);
}
public override int GetHashCode()
{
return index1.GetHashCode() ^ index2.GetHashCode();
}
}
}

View File

@@ -0,0 +1,108 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class BoneWeightChannel : IComparable<BoneWeightChannel>
{
[SerializeField]
private bool m_Enabled;
[SerializeField]
private int m_BoneIndex;
[SerializeField]
private float m_Weight;
public bool enabled
{
get { return m_Enabled; }
set { m_Enabled = value; }
}
public int boneIndex
{
get { return m_BoneIndex; }
set { m_BoneIndex = value; }
}
public float weight
{
get { return m_Weight; }
set { m_Weight = value; }
}
public BoneWeightChannel() : this(0, 0f, false)
{
}
public BoneWeightChannel(int i, float w, bool e)
{
enabled = e;
boneIndex = i;
weight = w;
}
public int CompareTo(BoneWeightChannel other)
{
int result = other.enabled.CompareTo(enabled);
if (result == 0)
result = other.weight.CompareTo(weight);
return result;
}
}
[Serializable]
internal class EditableBoneWeight : IEnumerable<BoneWeightChannel>
{
[SerializeField]
private List<BoneWeightChannel> m_Channels = new List<BoneWeightChannel>(5);
public BoneWeightChannel this[int i]
{
get { return m_Channels[i]; }
set { m_Channels[i] = value; }
}
public int Count
{
get { return m_Channels.Count; }
}
public void Clear()
{
m_Channels.Clear();
}
public void AddChannel(int boneIndex, float weight, bool enabled)
{
m_Channels.Add(new BoneWeightChannel(boneIndex, weight, enabled));
}
public void RemoveChannel(int channelIndex)
{
Debug.Assert(channelIndex < Count);
m_Channels.RemoveAt(channelIndex);
}
public void Sort()
{
m_Channels.Sort();
}
public IEnumerator<BoneWeightChannel> GetEnumerator()
{
return ((IEnumerable<BoneWeightChannel>)m_Channels).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<BoneWeightChannel>)m_Channels).GetEnumerator();
}
}
}

View File

@@ -0,0 +1,262 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace UnityEditor.U2D.Animation
{
internal struct BoneWeightData : IComparable<BoneWeightData>
{
public int boneIndex;
public float weight;
public int CompareTo(BoneWeightData other)
{
return other.weight.CompareTo(weight);
}
}
internal static class EditableBoneWeightUtility
{
private static List<BoneWeightData> s_BoneWeightDataList = new List<BoneWeightData>();
private static EditableBoneWeight s_LerpFirst = new EditableBoneWeight();
private static EditableBoneWeight s_LerpSecond = new EditableBoneWeight();
private static EditableBoneWeight s_LerpResult = new EditableBoneWeight();
public static EditableBoneWeight CreateFromBoneWeight(BoneWeight boneWeight)
{
EditableBoneWeight editableBoneWeight = new EditableBoneWeight();
editableBoneWeight.SetFromBoneWeight(boneWeight);
editableBoneWeight.UnifyChannelsWithSameBoneIndex();
return editableBoneWeight;
}
public static void SetFromBoneWeight(this EditableBoneWeight editableBoneWeight, BoneWeight boneWeight)
{
editableBoneWeight.Clamp(4, false);
while (editableBoneWeight.Count < 4)
editableBoneWeight.AddChannel(0, 0f, false);
for (var i = 0; i < 4; ++i)
{
var weight = boneWeight.GetWeight(i);
editableBoneWeight[i].boneIndex = boneWeight.GetBoneIndex(i);
editableBoneWeight[i].weight = weight;
editableBoneWeight[i].enabled = weight > 0f;
}
}
public static BoneWeight ToBoneWeight(this EditableBoneWeight editableBoneWeight, bool sortByWeight)
{
var boneWeight = new BoneWeight();
if (editableBoneWeight.Count > 0)
{
s_BoneWeightDataList.Clear();
s_BoneWeightDataList.Capacity = editableBoneWeight.Count;
for (var i = 0; i < editableBoneWeight.Count; ++i)
{
s_BoneWeightDataList.Add(new BoneWeightData()
{
boneIndex = editableBoneWeight[i].boneIndex,
weight = editableBoneWeight[i].weight
});
}
if (sortByWeight)
s_BoneWeightDataList.Sort();
var count = Mathf.Min(editableBoneWeight.Count, 4);
for (var i = 0; i < count; ++i)
{
BoneWeightExtensions.SetBoneIndex(ref boneWeight, i, s_BoneWeightDataList[i].boneIndex);
BoneWeightExtensions.SetWeight(ref boneWeight, i, s_BoneWeightDataList[i].weight);
}
}
return boneWeight;
}
public static bool ContainsBoneIndex(this EditableBoneWeight editableBoneWeight, int boneIndex)
{
return GetChannelFromBoneIndex(editableBoneWeight, boneIndex) > -1;
}
public static int GetChannelFromBoneIndex(this EditableBoneWeight editableBoneWeight, int boneIndex)
{
for (int i = 0; i < editableBoneWeight.Count; ++i)
if (editableBoneWeight[i].enabled && editableBoneWeight[i].boneIndex == boneIndex)
return i;
return -1;
}
public static void Clamp(this EditableBoneWeight editableBoneWeight, int numChannels, bool sortChannels = true)
{
if (sortChannels)
editableBoneWeight.Sort();
while (editableBoneWeight.Count > numChannels)
editableBoneWeight.RemoveChannel(numChannels);
}
public static void ValidateChannels(this EditableBoneWeight editableBoneWeight)
{
for (int i = 0; i < editableBoneWeight.Count; ++i)
{
var weight = editableBoneWeight[i].weight;
if (!editableBoneWeight[i].enabled)
weight = 0f;
weight = Mathf.Clamp01(weight);
editableBoneWeight[i].weight = weight;
}
}
public static float Sum(this EditableBoneWeight editableBoneWeight)
{
var sum = 0f;
for (var i = 0; i < editableBoneWeight.Count; ++i)
if (editableBoneWeight[i].enabled)
sum += editableBoneWeight[i].weight;
return sum;
}
public static void Normalize(this EditableBoneWeight editableBoneWeight)
{
ValidateChannels(editableBoneWeight);
var sum = editableBoneWeight.Sum();
if (sum == 0f || sum == 1f)
return;
var sumInv = 1f / sum;
for (var i = 0; i < editableBoneWeight.Count; ++i)
if (editableBoneWeight[i].enabled)
editableBoneWeight[i].weight *= sumInv;
}
public static void CompensateOtherChannels(this EditableBoneWeight editableBoneWeight, int masterChannel)
{
ValidateChannels(editableBoneWeight);
var validChannelCount = 0;
var sum = 0f;
for (int i = 0; i < editableBoneWeight.Count; ++i)
{
if (i != masterChannel && editableBoneWeight[i].enabled)
{
sum += editableBoneWeight[i].weight;
++validChannelCount;
}
}
if (validChannelCount == 0)
return;
var targetSum = 1f - editableBoneWeight[masterChannel].weight;
for (var i = 0; i < editableBoneWeight.Count; ++i)
{
if (i != masterChannel && editableBoneWeight[i].enabled)
{
if (sum == 0f)
editableBoneWeight[i].weight = targetSum / validChannelCount;
else
editableBoneWeight[i].weight *= targetSum / sum;
}
}
}
public static void UnifyChannelsWithSameBoneIndex(this EditableBoneWeight editableBoneWeight)
{
for (var i = 0; i < editableBoneWeight.Count; ++i)
{
if (!editableBoneWeight[i].enabled)
continue;
bool weightChanged = false;
for (var j = i + 1; j < editableBoneWeight.Count; ++j)
{
if (editableBoneWeight[j].boneIndex == editableBoneWeight[i].boneIndex)
{
weightChanged = true;
editableBoneWeight[i].weight += editableBoneWeight[j].weight;
editableBoneWeight[j].enabled = false;
}
}
if (weightChanged)
editableBoneWeight.CompensateOtherChannels(i);
}
}
public static void FilterChannels(this EditableBoneWeight editableBoneWeight, float weightTolerance)
{
for (var i = 0; i < editableBoneWeight.Count; ++i)
{
if (editableBoneWeight[i].weight <= weightTolerance)
{
editableBoneWeight[i].boneIndex = 0;
editableBoneWeight[i].weight = 0f;
editableBoneWeight[i].enabled = false;
}
}
}
public static BoneWeight Lerp(BoneWeight first, BoneWeight second, float t)
{
s_LerpFirst.SetFromBoneWeight(first);
s_LerpSecond.SetFromBoneWeight(second);
Lerp(s_LerpFirst, s_LerpSecond, ref s_LerpResult, t);
return s_LerpResult.ToBoneWeight(true);
}
private static void Lerp(EditableBoneWeight first, EditableBoneWeight second, ref EditableBoneWeight result, float t)
{
result.Clear();
foreach (BoneWeightChannel channel in first)
{
if (!channel.enabled)
continue;
var weight = channel.weight * (1f - t);
if (weight > 0f)
result.AddChannel(channel.boneIndex, weight, true);
}
foreach (BoneWeightChannel channel in second)
{
if (!channel.enabled)
continue;
var weight = channel.weight * t;
if (weight > 0f)
result.AddChannel(channel.boneIndex, weight, true);
}
result.UnifyChannelsWithSameBoneIndex();
result.Clamp(4);
if (result.Sum() > 1f)
result.Normalize();
result.FilterChannels(0f);
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class SmoothingUtility
{
private static float[,] m_DataInTemp;
private static float[,] m_DataOutTemp;
private static float[] m_DenominatorTemp;
private static EditableBoneWeight s_BoneWeight = new EditableBoneWeight();
public static void SmoothWeights(BoneWeight[] boneWeightIn, IList<int> indices, int boneCount, out BoneWeight[] boneWeightOut)
{
SmoothWeights(boneWeightIn, indices, boneCount, 1, out boneWeightOut);
}
public static void SmoothWeights(BoneWeight[] boneWeightIn, IList<int> indices, int boneCount, int iterations, out BoneWeight[] boneWeightOut)
{
Debug.Assert(boneWeightIn != null);
boneWeightOut = new BoneWeight[boneWeightIn.Length];
PrepareTempBuffers(boneWeightIn.Length, boneCount);
for (int i = 0; i < boneWeightIn.Length; ++i)
{
s_BoneWeight.SetFromBoneWeight(boneWeightIn[i]);
for (var j = 0; j < s_BoneWeight.Count; ++j)
{
if (s_BoneWeight[j].enabled)
m_DataInTemp[i, s_BoneWeight[j].boneIndex] = s_BoneWeight[j].weight;
}
}
for (var i = 0; i < iterations; ++i)
SmoothPerVertexData(indices, m_DataInTemp, m_DataOutTemp);
for (var i = 0; i < boneWeightIn.Length; ++i)
{
s_BoneWeight.Clear();
for (var j = 0; j < boneCount; ++j)
{
var weight = m_DataOutTemp[i, j];
var boneIndex = weight > 0f ? j : 0;
s_BoneWeight.AddChannel(boneIndex, weight, weight > 0);
}
s_BoneWeight.Clamp(4);
s_BoneWeight.Normalize();
boneWeightOut[i] = s_BoneWeight.ToBoneWeight(false);
}
}
public static void SmoothPerVertexData(IList<int> indices, float[,] dataIn, float[,] dataOut)
{
Debug.Assert(dataIn != null);
Debug.Assert(dataOut != null);
Debug.Assert(dataIn != dataOut);
Debug.Assert(dataIn.Length == dataOut.Length);
int rowLength = dataIn.GetLength(0);
int colLength = dataIn.GetLength(1);
PrepareDenominatorBuffer(rowLength);
for (int i = 0; i < indices.Count / 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
int j1 = (j + 1) % 3;
int j2 = (j + 2) % 3;
for (int k = 0; k < colLength; ++k)
dataOut[indices[i * 3 + j], k] += dataIn[indices[i * 3 + j1], k] + dataIn[indices[i * 3 + j2], k];
m_DenominatorTemp[indices[i * 3 + j]] += 2;
}
}
for (int i = 0; i < rowLength; ++i)
{
var dInv = 1f / Mathf.Max(1f, m_DenominatorTemp[i]);
for (int j = 0; j < colLength; ++j)
dataOut[i, j] *= dInv;
}
}
private static void PrepareDenominatorBuffer(int rowLength)
{
if (m_DenominatorTemp == null || m_DenominatorTemp.Length != rowLength)
m_DenominatorTemp = new float[rowLength];
else
Array.Clear(m_DenominatorTemp, 0, m_DenominatorTemp.Length);
}
private static void PrepareTempBuffers(int rowLength, int colLength)
{
if (m_DataInTemp == null || m_DataInTemp.GetLength(0) != rowLength || m_DataInTemp.GetLength(1) != colLength)
m_DataInTemp = new float[rowLength, colLength];
else
Array.Clear(m_DataInTemp, 0, m_DataInTemp.Length);
if (m_DataOutTemp == null || m_DataOutTemp.GetLength(0) != rowLength || m_DataOutTemp.GetLength(1) != colLength)
m_DataOutTemp = new float[rowLength, colLength];
else
Array.Clear(m_DataOutTemp, 0, m_DataOutTemp.Length);
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class SpriteBoneData
{
public string name;
public int parentId = -1;
public Vector2 localPosition;
public Quaternion localRotation = Quaternion.identity;
public Vector2 position;
public Vector2 endPosition;
public float depth;
public float length;
}
internal interface ISpriteMeshData
{
Rect frame { get; set; }
List<int> indices { get; set; }
List<Edge> edges { get; set; }
int vertexCount { get; }
int boneCount { get; }
Vector2 GetPosition(int index);
void SetPosition(int index, Vector2 position);
EditableBoneWeight GetWeight(int index);
void SetWeight(int index, EditableBoneWeight weight);
void AddVertex(Vector2 position, BoneWeight weight);
void RemoveVertex(int index);
SpriteBoneData GetBoneData(int index);
float GetBoneDepth(int index);
void Clear();
}
[Serializable]
internal class SpriteMeshData : ISpriteMeshData
{
public GUID spriteID = new GUID();
[SerializeField]
private Rect m_Frame;
public Vector2 pivot = Vector2.zero;
[SerializeField]
private List<Vertex2D> m_Vertices = new List<Vertex2D>();
[SerializeField]
private List<int> m_Indices = new List<int>();
[SerializeField]
public List<SpriteBoneData> m_Bones = new List<SpriteBoneData>();
[SerializeField]
private List<Edge> m_Edges = new List<Edge>();
public Rect frame
{
get { return m_Frame; }
set { m_Frame = value; }
}
public List<Vertex2D> vertices
{
get { return m_Vertices; }
set { m_Vertices = value; }
}
public List<int> indices
{
get { return m_Indices; }
set { m_Indices = value; }
}
public List<Edge> edges
{
get { return m_Edges; }
set { m_Edges = value; }
}
public List<SpriteBoneData> bones
{
get { return m_Bones; }
set { m_Bones = value; }
}
public int vertexCount { get { return m_Vertices.Count; } }
public int boneCount { get { return m_Bones.Count; } }
public Vector2 GetPosition(int index)
{
return m_Vertices[index].position;
}
public void SetPosition(int index, Vector2 position)
{
m_Vertices[index].position = position;
}
public EditableBoneWeight GetWeight(int index)
{
return m_Vertices[index].editableBoneWeight;
}
public void SetWeight(int index, EditableBoneWeight weight)
{
m_Vertices[index].editableBoneWeight = weight;
}
public void AddVertex(Vector2 position, BoneWeight weight)
{
m_Vertices.Add(new Vertex2D(position, weight));
}
public void RemoveVertex(int index)
{
m_Vertices.RemoveAt(index);
}
public SpriteBoneData GetBoneData(int index)
{
return m_Bones[index];
}
public float GetBoneDepth(int index)
{
return m_Bones[index].depth;
}
public void Clear()
{
m_Vertices.Clear();
m_Indices.Clear();
m_Edges.Clear();
}
}
}

View File

@@ -0,0 +1,577 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.U2D.Sprites;
namespace UnityEditor.U2D.Animation
{
internal struct WeightedTriangle : IComparable<WeightedTriangle>
{
public int p1;
public int p2;
public int p3;
public float weight;
public int CompareTo(WeightedTriangle other)
{
return weight.CompareTo(other.weight);
}
}
internal class SpriteMeshDataController
{
public ISpriteMeshData spriteMeshData;
private List<Vector2> m_VerticesTemp = new List<Vector2>();
private List<Edge> m_EdgesTemp = new List<Edge>();
public void CreateVertex(Vector2 position)
{
CreateVertex(position, -1);
}
public void CreateVertex(Vector2 position, int edgeIndex)
{
Debug.Assert(spriteMeshData != null);
spriteMeshData.AddVertex(position, default(BoneWeight));
if (edgeIndex != -1)
{
Edge edge = spriteMeshData.edges[edgeIndex];
RemoveEdge(edge);
CreateEdge(edge.index1, spriteMeshData.vertexCount - 1);
CreateEdge(edge.index2, spriteMeshData.vertexCount - 1);
}
}
public void CreateEdge(int index1, int index2)
{
Debug.Assert(spriteMeshData != null);
Debug.Assert(index1 >= 0);
Debug.Assert(index2 >= 0);
Debug.Assert(index1 < spriteMeshData.vertexCount);
Debug.Assert(index2 < spriteMeshData.vertexCount);
Debug.Assert(index1 != index2);
Edge newEdge = new Edge(index1, index2);
if (!spriteMeshData.edges.Contains(newEdge))
spriteMeshData.edges.Add(newEdge);
}
public void RemoveVertex(int index)
{
Debug.Assert(spriteMeshData != null);
//We need to delete the edges that reference the index
List<Edge> edgesWithIndex;
if (FindEdgesContainsIndex(index, out edgesWithIndex))
{
//If there are 2 edges referencing the same index we are removing, we can create a new one that connects the endpoints ("Unsplit").
if (edgesWithIndex.Count == 2)
{
Edge first = edgesWithIndex[0];
Edge second = edgesWithIndex[1];
int index1 = first.index1 != index ? first.index1 : first.index2;
int index2 = second.index1 != index ? second.index1 : second.index2;
CreateEdge(index1, index2);
}
//remove found edges
for (int i = 0; i < edgesWithIndex.Count; i++)
{
RemoveEdge(edgesWithIndex[i]);
}
}
//Fix indices in edges greater than the one we are removing
for (int i = 0; i < spriteMeshData.edges.Count; i++)
{
Edge edge = spriteMeshData.edges[i];
if (edge.index1 > index)
edge.index1--;
if (edge.index2 > index)
edge.index2--;
spriteMeshData.edges[i] = edge;
}
spriteMeshData.RemoveVertex(index);
}
public void RemoveVertex(IEnumerable<int> indices)
{
List<int> sortedIndexList = new List<int>(indices);
if (sortedIndexList.Count == 0)
return;
sortedIndexList.Sort();
for (int i = sortedIndexList.Count - 1; i >= 0; --i)
{
RemoveVertex(sortedIndexList[i]);
}
}
public void RemoveEdge(Edge edge)
{
Debug.Assert(spriteMeshData != null);
spriteMeshData.edges.Remove(edge);
}
public bool FindEdgesContainsIndex(int index, out List<Edge> result)
{
Debug.Assert(spriteMeshData != null);
bool found = false;
result = new List<Edge>();
for (int i = 0; i < spriteMeshData.edges.Count; ++i)
{
Edge edge = spriteMeshData.edges[i];
if (edge.Contains(index))
{
found = true;
result.Add(edge);
}
}
return found;
}
public void Triangulate(ITriangulator triangulator)
{
Debug.Assert(spriteMeshData != null);
Debug.Assert(triangulator != null);
m_VerticesTemp.Clear();
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
m_VerticesTemp.Add(spriteMeshData.GetPosition(i));
triangulator.Triangulate(m_VerticesTemp, spriteMeshData.edges, spriteMeshData.indices);
}
public void Subdivide(ITriangulator triangulator, float largestAreaFactor)
{
Debug.Assert(spriteMeshData != null);
Debug.Assert(triangulator != null);
m_VerticesTemp.Clear();
m_EdgesTemp.Clear();
m_EdgesTemp.AddRange(spriteMeshData.edges);
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
m_VerticesTemp.Add(spriteMeshData.GetPosition(i));
try
{
var indices = new List<int>();
triangulator.Tessellate(0f, 0f, 0f, largestAreaFactor, 100, m_VerticesTemp, m_EdgesTemp, indices);
spriteMeshData.Clear();
for (int i = 0; i < m_VerticesTemp.Count; ++i)
spriteMeshData.AddVertex(m_VerticesTemp[i], default(BoneWeight));
spriteMeshData.edges.AddRange(m_EdgesTemp);
spriteMeshData.indices.AddRange(indices);
}
catch (Exception)
{
}
}
public void ClearWeights(ISelection<int> selection)
{
Debug.Assert(spriteMeshData != null);
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
if (selection == null || (selection.Count == 0 || selection.Contains(i)))
spriteMeshData.GetWeight(i).SetFromBoneWeight(default(BoneWeight));
}
public void OutlineFromAlpha(IOutlineGenerator outlineGenerator, ITextureDataProvider textureDataProvider, float outlineDetail, byte alphaTolerance)
{
Debug.Assert(spriteMeshData != null);
Debug.Assert(textureDataProvider != null);
Debug.Assert(textureDataProvider.texture != null);
int width, height;
textureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
Vector2 scale = new Vector2(textureDataProvider.texture.width / (float)width, textureDataProvider.texture.height / (float)height);
Vector2 scaleInv = new Vector2(1f / scale.x, 1f / scale.y);
Vector2 rectOffset = spriteMeshData.frame.size * 0.5f + spriteMeshData.frame.position;
Rect scaledRect = spriteMeshData.frame;
scaledRect.min = Vector2.Scale(scaledRect.min, scale);
scaledRect.max = Vector2.Scale(scaledRect.max, scale);
spriteMeshData.Clear();
Vector2[][] paths;
outlineGenerator.GenerateOutline(textureDataProvider, scaledRect, outlineDetail, alphaTolerance, false, out paths);
int vertexIndexBase = 0;
for (int i = 0; i < paths.Length; ++i)
{
int numPathVertices = paths[i].Length;
for (int j = 0; j <= numPathVertices; j++)
{
if (j < numPathVertices)
spriteMeshData.AddVertex(Vector2.Scale(paths[i][j], scaleInv) + rectOffset, default(BoneWeight));
if (j > 0)
spriteMeshData.edges.Add(new Edge(vertexIndexBase + j - 1, vertexIndexBase + j % numPathVertices));
}
vertexIndexBase += numPathVertices;
}
}
public void NormalizeWeights(ISelection<int> selection)
{
Debug.Assert(spriteMeshData != null);
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
if (selection == null || (selection.Count == 0 || selection.Contains(i)))
spriteMeshData.GetWeight(i).Normalize();
}
public void CalculateWeights(IWeightsGenerator weightsGenerator, ISelection<int> selection, float filterTolerance)
{
Debug.Assert(spriteMeshData != null);
Vector2[] controlPoints;
Edge[] bones;
int[] pins;
GetControlPoints(out controlPoints, out bones, out pins);
Vector2[] vertices = new Vector2[spriteMeshData.vertexCount];
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
vertices[i] = spriteMeshData.GetPosition(i);
BoneWeight[] boneWeights = weightsGenerator.Calculate(vertices, spriteMeshData.edges.ToArray(), controlPoints, bones, pins);
Debug.Assert(boneWeights.Length == spriteMeshData.vertexCount);
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
{
if (selection == null || (selection.Count == 0 || selection.Contains(i)))
{
EditableBoneWeight editableBoneWeight = EditableBoneWeightUtility.CreateFromBoneWeight(boneWeights[i]);
if (filterTolerance > 0f)
{
editableBoneWeight.FilterChannels(filterTolerance);
editableBoneWeight.Normalize();
}
spriteMeshData.SetWeight(i, editableBoneWeight);
}
}
}
public void CalculateWeightsSafe(IWeightsGenerator weightsGenerator, ISelection<int> selection, float filterTolerance)
{
var tempSelection = new IndexedSelection();
var vertexSelector = new GenericVertexSelector();
vertexSelector.spriteMeshData = spriteMeshData;
vertexSelector.selection = tempSelection;
vertexSelector.SelectionCallback = (int i) => {
return spriteMeshData.GetWeight(i).Sum() == 0f && (selection == null || selection.Count == 0 || selection.Contains(i));
};
vertexSelector.Select();
if (tempSelection.Count > 0)
CalculateWeights(weightsGenerator, tempSelection, filterTolerance);
}
public void SmoothWeights(int iterations, ISelection<int> selection)
{
var boneWeights = new BoneWeight[spriteMeshData.vertexCount];
for (var i = 0; i < spriteMeshData.vertexCount; i++)
boneWeights[i] = spriteMeshData.GetWeight(i).ToBoneWeight(false);
BoneWeight[] smoothedWeights;
SmoothingUtility.SmoothWeights(boneWeights, spriteMeshData.indices, spriteMeshData.boneCount, iterations, out smoothedWeights);
for (var i = 0; i < spriteMeshData.vertexCount; i++)
if (selection == null || (selection.Count == 0 || selection.Contains(i)))
spriteMeshData.GetWeight(i).SetFromBoneWeight(smoothedWeights[i]);
}
public bool FindTriangle(Vector2 point, out Vector3Int indices, out Vector3 barycentricCoords)
{
Debug.Assert(spriteMeshData != null);
indices = Vector3Int.zero;
barycentricCoords = Vector3.zero;
if (spriteMeshData.indices.Count < 3)
return false;
int triangleCount = spriteMeshData.indices.Count / 3;
for (int i = 0; i < triangleCount; ++i)
{
indices.x = spriteMeshData.indices[i * 3];
indices.y = spriteMeshData.indices[i * 3 + 1];
indices.z = spriteMeshData.indices[i * 3 + 2];
MathUtility.Barycentric(
point,
spriteMeshData.GetPosition(indices.x),
spriteMeshData.GetPosition(indices.y),
spriteMeshData.GetPosition(indices.z),
out barycentricCoords);
if (barycentricCoords.x >= 0f && barycentricCoords.y >= 0f && barycentricCoords.z >= 0f)
return true;
}
return false;
}
private List<float> m_VertexOrderList = new List<float>(1000);
private List<WeightedTriangle> m_WeightedTriangles = new List<WeightedTriangle>(1000);
public void SortTrianglesByDepth()
{
Debug.Assert(spriteMeshData != null);
if (spriteMeshData.boneCount == 0)
return;
m_VertexOrderList.Clear();
m_WeightedTriangles.Clear();
for (var i = 0; i < spriteMeshData.vertexCount; i++)
{
var vertexOrder = 0f;
var boneWeight = spriteMeshData.GetWeight(i);
for (var j = 0; j < boneWeight.Count; ++j)
vertexOrder += spriteMeshData.GetBoneDepth(boneWeight[j].boneIndex) * boneWeight[j].weight;
m_VertexOrderList.Add(vertexOrder);
}
for (int i = 0; i < spriteMeshData.indices.Count; i += 3)
{
int p1 = spriteMeshData.indices[i];
int p2 = spriteMeshData.indices[i + 1];
int p3 = spriteMeshData.indices[i + 2];
float weight = (m_VertexOrderList[p1] + m_VertexOrderList[p2] + m_VertexOrderList[p3]) / 3f;
m_WeightedTriangles.Add(new WeightedTriangle() { p1 = p1, p2 = p2, p3 = p3, weight = weight });
}
m_WeightedTriangles.Sort();
spriteMeshData.indices.Clear();
for (var i = 0; i < m_WeightedTriangles.Count; ++i)
{
var triangle = m_WeightedTriangles[i];
spriteMeshData.indices.Add(triangle.p1);
spriteMeshData.indices.Add(triangle.p2);
spriteMeshData.indices.Add(triangle.p3);
}
}
public void GetMultiEditChannelData(ISelection<int> selection, int channel,
out bool enabled, out int boneIndex, out float weight,
out bool isEnabledMixed, out bool isBoneIndexMixed, out bool isWeightMixed)
{
Debug.Assert(spriteMeshData != null);
if (selection == null)
throw new ArgumentNullException("selection is null");
var first = true;
enabled = false;
boneIndex = -1;
weight = 0f;
isEnabledMixed = false;
isBoneIndexMixed = false;
isWeightMixed = false;
var indices = selection.elements;
foreach (int i in indices)
{
var editableBoneWeight = spriteMeshData.GetWeight(i);
if (first)
{
enabled = editableBoneWeight[channel].enabled;
boneIndex = editableBoneWeight[channel].boneIndex;
weight = editableBoneWeight[channel].weight;
first = false;
}
else
{
if (enabled != editableBoneWeight[channel].enabled)
{
isEnabledMixed = true;
enabled = false;
}
if (boneIndex != editableBoneWeight[channel].boneIndex)
{
isBoneIndexMixed = true;
boneIndex = -1;
}
if (weight != editableBoneWeight[channel].weight)
{
isWeightMixed = true;
weight = 0f;
}
}
}
}
public void SetMultiEditChannelData(ISelection<int> selection, int channel,
bool oldEnabled, bool newEnabled, int oldBoneIndex, int newBoneIndex, float oldWeight, float newWeight)
{
Debug.Assert(spriteMeshData != null);
if (selection == null)
throw new ArgumentNullException("selection is null");
bool channelEnabledChanged = oldEnabled != newEnabled;
bool boneIndexChanged = oldBoneIndex != newBoneIndex;
bool weightChanged = oldWeight != newWeight;
var indices = selection.elements;
foreach (int i in indices)
{
var editableBoneWeight = spriteMeshData.GetWeight(i);
if (channelEnabledChanged)
editableBoneWeight[channel].enabled = newEnabled;
if (boneIndexChanged)
editableBoneWeight[channel].boneIndex = newBoneIndex;
if (weightChanged)
editableBoneWeight[channel].weight = newWeight;
if (channelEnabledChanged || weightChanged)
editableBoneWeight.CompensateOtherChannels(channel);
}
}
public void GetControlPoints(out Vector2[] points, out Edge[] edges, out int[] pins)
{
Debug.Assert(spriteMeshData != null);
points = null;
edges = null;
List<Vector2> pointList = new List<Vector2>();
List<Edge> edgeList = new List<Edge>();
List<int> pinList = new List<int>();
List<SpriteBoneData> bones = new List<SpriteBoneData>(spriteMeshData.boneCount);
for (int i = 0; i < spriteMeshData.boneCount; ++i)
bones.Add(spriteMeshData.GetBoneData(i));
foreach (var bone in bones)
{
var length = (bone.endPosition - bone.position).magnitude;
if (length > 0f)
{
int index1 = FindPoint(pointList, bone.position, 0.01f);
int index2 = FindPoint(pointList, bone.endPosition, 0.01f);
if (index1 == -1)
{
pointList.Add(bone.position);
index1 = pointList.Count - 1;
}
if (index2 == -1)
{
pointList.Add(bone.endPosition);
index2 = pointList.Count - 1;
}
edgeList.Add(new Edge(index1, index2));
}
else if (bone.length == 0f)
{
pointList.Add(bone.position);
pinList.Add(pointList.Count - 1);
}
}
points = pointList.ToArray();
edges = edgeList.ToArray();
pins = pinList.ToArray();
}
private int FindPoint(List<Vector2> points, Vector2 point, float distanceTolerance)
{
float sqrTolerance = distanceTolerance * distanceTolerance;
for (int i = 0; i < points.Count; ++i)
{
if ((points[i] - point).sqrMagnitude <= sqrTolerance)
return i;
}
return -1;
}
public void SmoothFill()
{
var tempSelection = new IndexedSelection();
var vertexSelector = new GenericVertexSelector();
var currentWeightSum = 0f;
var prevWeightSum = 0f;
vertexSelector.spriteMeshData = spriteMeshData;
vertexSelector.selection = tempSelection;
vertexSelector.SelectionCallback = (int i) => {
var sum = spriteMeshData.GetWeight(i).Sum();
currentWeightSum += sum;
return sum < 0.99f;
};
do
{
prevWeightSum = currentWeightSum;
currentWeightSum = 0f;
vertexSelector.Select();
if (tempSelection.Count > 0)
SmoothWeights(1, tempSelection);
}
while (currentWeightSum - prevWeightSum > 0.001f);
if (tempSelection.Count > 0)
NormalizeWeights(tempSelection);
}
}
}

View File

@@ -0,0 +1,39 @@
using UnityEngine;
using System;
namespace UnityEditor.U2D.Animation
{
[Serializable]
internal class Vertex2D
{
public Vector2 position
{
get { return m_Position; }
set { m_Position = value; }
}
public EditableBoneWeight editableBoneWeight
{
get { return m_EditableBoneWeight; }
set { m_EditableBoneWeight = value; }
}
public Vertex2D(Vector2 position)
{
m_Position = position;
m_EditableBoneWeight = EditableBoneWeightUtility.CreateFromBoneWeight(new BoneWeight());
}
public Vertex2D(Vector2 position, BoneWeight weights)
{
m_Position = position;
m_EditableBoneWeight = EditableBoneWeightUtility.CreateFromBoneWeight(weights);
}
[SerializeField]
Vector2 m_Position;
[SerializeField]
EditableBoneWeight m_EditableBoneWeight;
}
}

View File

@@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal enum WeightEditorMode
{
AddAndSubtract,
GrowAndShrink,
Smooth
}
internal class WeightEditor
{
public ISpriteMeshData spriteMeshData
{
get { return m_SpriteMeshDataController.spriteMeshData; }
set { m_SpriteMeshDataController.spriteMeshData = value; }
}
public ICacheUndo cacheUndo { get; set; }
public WeightEditorMode mode { get; set; }
public int boneIndex { get; set; }
public ISelection<int> selection { get; set; }
public WeightEditorMode currentMode { get; private set; }
public bool useRelativeValues { get; private set; }
public bool emptySelectionEditsAll { get; set; }
public bool autoNormalize { get; set; }
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private const int maxSmoothIterations = 8;
private float[] m_SmoothValues;
private readonly List<BoneWeight[]> m_SmoothedBoneWeights = new List<BoneWeight[]>();
private readonly List<BoneWeight> m_StoredBoneWeights = new List<BoneWeight>();
private int BoneCount
{
get { return spriteMeshData != null ? spriteMeshData.boneCount : 0; }
}
public WeightEditor()
{
autoNormalize = true;
}
public void OnEditStart(bool relative)
{
Validate();
RegisterUndo();
currentMode = mode;
useRelativeValues = relative;
if (!useRelativeValues)
StoreBoneWeights();
if (mode == WeightEditorMode.Smooth)
PrepareSmoothingBuffers();
}
public void OnEditEnd()
{
Validate();
if (currentMode == WeightEditorMode.AddAndSubtract)
{
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
spriteMeshData.GetWeight(i).Clamp(4);
}
if (autoNormalize)
m_SpriteMeshDataController.NormalizeWeights(null);
m_SpriteMeshDataController.SortTrianglesByDepth();
}
public void DoEdit(float value)
{
Validate();
if (!useRelativeValues)
RestoreBoneWeights();
if (currentMode == WeightEditorMode.AddAndSubtract)
SetWeight(value);
else if (currentMode == WeightEditorMode.GrowAndShrink)
SetWeight(value, false);
else if (currentMode == WeightEditorMode.Smooth)
SmoothWeights(value);
}
private void Validate()
{
if (spriteMeshData == null)
throw (new Exception(TextContent.noSpriteSelected));
}
private void RegisterUndo()
{
Debug.Assert(cacheUndo != null);
cacheUndo.BeginUndoOperation(TextContent.editWeights);
}
private void SetWeight(float value, bool createNewChannel = true)
{
if (boneIndex == -1 || spriteMeshData == null)
return;
Debug.Assert(selection != null);
for (var i = 0; i < spriteMeshData.vertexCount; ++i)
{
if (selection.Count == 0 && emptySelectionEditsAll ||
selection.Count > 0 && selection.Contains(i))
{
var editableBoneWeight = spriteMeshData.GetWeight(i);
int channel = editableBoneWeight.GetChannelFromBoneIndex(boneIndex);
if (channel == -1)
{
if (createNewChannel && value > 0f)
{
editableBoneWeight.AddChannel(boneIndex, 0f, true);
channel = editableBoneWeight.GetChannelFromBoneIndex(boneIndex);
}
else
{
continue;
}
}
editableBoneWeight[channel].weight += value;
if (editableBoneWeight.Sum() > 1f)
editableBoneWeight.CompensateOtherChannels(channel);
editableBoneWeight.FilterChannels(0f);
}
}
}
private void SmoothWeights(float value)
{
Debug.Assert(selection != null);
for (int i = 0; i < spriteMeshData.vertexCount; ++i)
{
if (selection.Count == 0 && emptySelectionEditsAll ||
selection.Count > 0 && selection.Contains(i))
{
var smoothValue = m_SmoothValues[i];
if (smoothValue >= maxSmoothIterations)
continue;
m_SmoothValues[i] = Mathf.Clamp(smoothValue + value, 0f, maxSmoothIterations);
float lerpValue = GetLerpValue(m_SmoothValues[i]);
int lerpIndex = GetLerpIndex(m_SmoothValues[i]);
BoneWeight[] smoothedBoneWeightsFloor = GetSmoothedBoneWeights(lerpIndex - 1);
BoneWeight[] smoothedBoneWeightsCeil = GetSmoothedBoneWeights(lerpIndex);
BoneWeight boneWeight = EditableBoneWeightUtility.Lerp(smoothedBoneWeightsFloor[i], smoothedBoneWeightsCeil[i], lerpValue);
spriteMeshData.GetWeight(i).SetFromBoneWeight(boneWeight);
}
}
}
protected void PrepareSmoothingBuffers()
{
if (m_SmoothValues == null || m_SmoothValues.Length != spriteMeshData.vertexCount)
m_SmoothValues = new float[spriteMeshData.vertexCount];
Array.Clear(m_SmoothValues, 0, m_SmoothValues.Length);
m_SmoothedBoneWeights.Clear();
BoneWeight[] boneWeights = new BoneWeight[spriteMeshData.vertexCount];
for (int i = 0; i < spriteMeshData.vertexCount; i++)
{
EditableBoneWeight editableBoneWeight = spriteMeshData.GetWeight(i);
boneWeights[i] = editableBoneWeight.ToBoneWeight(false);
}
m_SmoothedBoneWeights.Add(boneWeights);
}
private BoneWeight[] GetSmoothedBoneWeights(int lerpIndex)
{
Debug.Assert(lerpIndex >= 0);
while (lerpIndex >= m_SmoothedBoneWeights.Count && lerpIndex <= maxSmoothIterations)
{
BoneWeight[] boneWeights;
SmoothingUtility.SmoothWeights(m_SmoothedBoneWeights[m_SmoothedBoneWeights.Count - 1], spriteMeshData.indices, BoneCount, out boneWeights);
m_SmoothedBoneWeights.Add(boneWeights);
}
return m_SmoothedBoneWeights[Mathf.Min(lerpIndex, maxSmoothIterations)];
}
private float GetLerpValue(float smoothValue)
{
Debug.Assert(smoothValue >= 0f);
return smoothValue - Mathf.Floor(smoothValue);
}
private int GetLerpIndex(float smoothValue)
{
Debug.Assert(smoothValue >= 0f);
return Mathf.RoundToInt(Mathf.Floor(smoothValue) + 1);
}
private void StoreBoneWeights()
{
Debug.Assert(selection != null);
m_StoredBoneWeights.Clear();
for (int i = 0; i < spriteMeshData.vertexCount; i++)
{
EditableBoneWeight editableBoneWeight = spriteMeshData.GetWeight(i);
m_StoredBoneWeights.Add(editableBoneWeight.ToBoneWeight(false));
}
}
private void RestoreBoneWeights()
{
Debug.Assert(selection != null);
for (int i = 0; i < spriteMeshData.vertexCount; i++)
{
EditableBoneWeight editableBoneWeight = spriteMeshData.GetWeight(i);
editableBoneWeight.SetFromBoneWeight(m_StoredBoneWeights[i]);
}
if (m_SmoothValues != null)
Array.Clear(m_SmoothValues, 0, m_SmoothValues.Length);
}
}
}

View File

@@ -0,0 +1,108 @@
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal static class TextContent
{
// Undo
public static string setMode = "Set Mode";
public static string setTool = "Set Tool";
public static string pasteData = "Paste Data";
public static string generateGeometry = "Generate Geometry";
public static string generateWeights = "Generate Weights";
public static string normalizeWeights = "Normalize Weights";
public static string clearWeights = "Clear Weights";
public static string restorePose = "Restore Pose";
public static string selection = "Selection";
public static string clearSelection = "Clear Selection";
public static string editWeights = "Edit Weights";
public static string boneName = "Bone Name";
public static string boneDepth = "Bone Depth";
public static string rotateBone = "Rotate Bone";
public static string moveBone = "Move Bone";
public static string colorBoneChanged = "Bone Color";
public static string freeMoveBone = "Free Move Bone";
public static string moveJoint = "Move Joint";
public static string moveEndPoint = "Move End Point";
public static string boneLength = "Bone Length";
public static string createBone = "Create Bone";
public static string splitBone = "Split Bone";
public static string removeBone = "Remove Bone";
public static string moveVertices = "Move Vertices";
public static string createVertex = "Create Vertex";
public static string createEdge = "Create Edge";
public static string splitEdge = "Split Edge";
public static string removeEdge = "Remove Edge";
public static string removeVertices = "Remove Vertices";
public static string selectionChange = "Selection Change";
public static string boneVisibility = "Bone Visibility";
public static string setParentBone = "Set Parent Bone";
public static string visibilityChange = "VisibilityChange";
public static string boneSelection = "Bone Selection";
public static string expandBones = "Expand Bones";
public static string meshVisibility = "Mesh Visibility";
public static string meshOpacity = "Mesh Opacity";
public static string opacityChange = "Opacity Change";
// Tooltips
public static string visibilityIconTooltip = "Visibility tool";
public static string characterIconTooltip = "Restore bind pose";
public static string spriteSheetIconTooltip = "Switch between Sprite sheet and Character mode";
public static string copyTooltip = "Copy";
public static string pasteTooltip = "Paste";
public static string onTooltip = "On";
public static string offTooltip = "Off";
// Horizontal tool bar button txt
public static string visibilityIconText = "Visibility";
public static string characterIconText = "Reset Pose";
public static string spriteSheetIconText = "Sprite Sheet";
public static string copyText = "Copy";
public static string pasteText = "Paste";
// Settings
public static string selectedOutlineColor = "Selected Outline Color";
public static string spriteOutlineSize = "Sprite Outline Size";
public static string boneOutlineSize = "Bone Outline Size";
// Sprite Library
public static string convertGroupToCategory = "Convert Group to Category";
public static string newTrailingDots = "New...";
public static string removeEmptyCategory = "Remove Empty Category";
public static string convertLayerToCategory = "Convert Layer to Category";
public static string clearAllCategory = "Clear All Category";
public static string spriteCategoryChanged = "Sprite Category Changed";
public static string spriteCategoryIndexChanged = "Sprite Category Index Changed";
public static string category = "Category";
public static string label = "Label";
// Other
public static string generatingGeometry = "Generating Geometry";
public static string generatingWeights = "Generating Weights";
public static string vertexWeight = "Vertex Weight";
public static string vertexWeightToolTip = "Adjust bone weights for selected vertex";
public static string bone = "Bone";
public static string depth = "Depth";
public static string sprite = "Sprite";
public static string spriteVisibility = "SpriteVisibility";
public static string name = "Name";
public static string none = "None";
public static string size = "Size";
public static string visibilityTab = "Visibility Tab";
public static string addBoneInfluence = "Add Bone Influence";
public static string removeBoneInfluence = "Remove Bone Influence";
public static string reorderBoneInfluence = "Reorder Bone Influence";
public static string noSpriteSelected = "No sprite selected";
public static string weightSlider = "Weight Slider";
public static string weightBrush = "Weight Brush";
public static string generateAll = "Generate All";
public static string generate = "Generate";
public static string smoothMeshError = "Generated mesh could not be smoothed. Please try using different parameters";
public static string copyError1 = "Unable to convert copied data to Skinning Module paste data.";
public static string copyError2 = "There is no copied data to paste.";
public static string copyError3 = "Target has a different number of Sprites ({0}) compared to copied source ({1}) for Mesh copy.";
public static string mode = "Mode";
public static string modeTooltip = "Different operation mode for weight adjustment";
public static string boneToolTip = "The bone that is affecting";
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal interface ITriangulator
{
void Triangulate(IList<Vector2> vertices, IList<Edge> edges, IList<int> indices);
void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, int smoothIterations, IList<Vector2> vertices, IList<Edge> edges, IList<int> indices);
}
}

View File

@@ -0,0 +1,133 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.U2D.Animation.TriangleNet.Geometry;
using UnityEngine.U2D.Animation.TriangleNet.Meshing;
using UnityEngine.U2D.Animation.TriangleNet.Smoothing;
using UnityEngine.U2D.Animation.TriangleNet.Tools;
namespace UnityEditor.U2D.Animation
{
internal class TriangulationUtility
{
public static void Triangulate(IList<Vector2> vertices, IList<Edge> edges, IList<int> indices)
{
indices.Clear();
if (vertices.Count < 3)
return;
var polygon = new Polygon(vertices.Count);
for (int i = 0; i < vertices.Count; ++i)
{
Vector2 position = vertices[i];
polygon.Add(new Vertex(position.x, position.y, 1));
}
for (int i = 0; i < edges.Count; ++i)
{
Edge edge = edges[i];
polygon.Add(new Segment(polygon.Points[edge.index1], polygon.Points[edge.index2]));
}
var mesh = polygon.Triangulate();
foreach (ITriangle triangle in mesh.Triangles)
{
int id0 = triangle.GetVertexID(0);
int id1 = triangle.GetVertexID(1);
int id2 = triangle.GetVertexID(2);
if (id0 < 0 || id1 < 0 || id2 < 0 || id0 >= vertices.Count || id1 >= vertices.Count || id2 >= vertices.Count)
continue;
indices.Add(id0);
indices.Add(id2);
indices.Add(id1);
}
}
public static void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, int smoothIterations, IList<Vector2> vertices, IList<Edge> edges, IList<int> indices)
{
if (vertices.Count < 3)
return;
largestTriangleAreaFactor = Mathf.Clamp01(largestTriangleAreaFactor);
var polygon = new Polygon(vertices.Count);
for (int i = 0; i < vertices.Count; ++i)
{
Vector2 position = vertices[i];
polygon.Add(new Vertex(position.x, position.y, 1));
}
for (int i = 0; i < edges.Count; ++i)
{
Edge edge = edges[i];
polygon.Add(new Segment(polygon.Points[edge.index1], polygon.Points[edge.index2]));
}
var mesh = polygon.Triangulate();
var statistic = new Statistic();
statistic.Update((UnityEngine.U2D.Animation.TriangleNet.Mesh)mesh, 1);
if (statistic.LargestArea < 0.01f)
throw new System.Exception("Invalid Mesh: Largest triangle area too small");
var maxAreaToApply = (double)Mathf.Max((float)statistic.LargestArea * largestTriangleAreaFactor, (float)(statistic.MeshArea * meshAreaFactor));
var qualityOptions = new QualityOptions() { SteinerPoints = 0 };
if (maxAreaToApply > 0f)
qualityOptions.MaximumArea = maxAreaToApply;
qualityOptions.MinimumAngle = minAngle;
qualityOptions.MaximumAngle = maxAngle;
mesh.Refine(qualityOptions, false);
mesh.Renumber();
if (smoothIterations > 0)
{
try
{
var smoother = new SimpleSmoother();
smoother.Smooth(mesh, smoothIterations);
}
catch (System.Exception)
{
Debug.Log(TextContent.smoothMeshError);
}
}
vertices.Clear();
edges.Clear();
indices.Clear();
foreach (Vertex vertex in mesh.Vertices)
{
vertices.Add(new Vector2((float)vertex.X, (float)vertex.Y));
}
foreach (ISegment segment in mesh.Segments)
{
edges.Add(new Edge(segment.P0, segment.P1));
}
foreach (ITriangle triangle in mesh.Triangles)
{
int id0 = triangle.GetVertexID(0);
int id1 = triangle.GetVertexID(1);
int id2 = triangle.GetVertexID(2);
if (id0 < 0 || id1 < 0 || id2 < 0 || id0 >= vertices.Count || id1 >= vertices.Count || id2 >= vertices.Count)
continue;
indices.Add(id0);
indices.Add(id2);
indices.Add(id1);
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Triangulator : ITriangulator
{
public void Triangulate(IList<Vector2> vertices, IList<Edge> edges, IList<int> indices)
{
TriangulationUtility.Triangulate(vertices, edges, indices);
}
public void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, int smoothIterations, IList<Vector2> vertices, IList<Edge> edges, IList<int> indices)
{
TriangulationUtility.Tessellate(minAngle, maxAngle, meshAreaFactor, largestTriangleAreaFactor, smoothIterations, vertices, edges, indices);
}
}
}

View File

@@ -0,0 +1,123 @@
using System;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.U2D.Animation
{
internal class BoneInspectorPanel : VisualElement
{
[Flags]
internal enum PropertyReadOnly
{
None,
Name = 1,
Depth = 1 << 2,
Position = 1 << 3,
Rotation = 1 << 4,
Color = 1 << 5
}
public class BoneInspectorPanelFactory : UxmlFactory<BoneInspectorPanel, BoneInspectorPanelUxmlTraits> {}
public class BoneInspectorPanelUxmlTraits : UxmlTraits {}
public event Action<BoneCache, int> onBoneDepthChanged = (bone, depth) => {};
public event Action<BoneCache, Vector2> onBonePositionChanged = (bone, position) => {};
public event Action<BoneCache, float> onBoneRotationChanged = (bone, rotation) => {};
public event Action<BoneCache, string> onBoneNameChanged = (bone, name) => {};
public event Action<BoneCache, Color32> onBoneColorChanged = (bone, color) => {};
private TextField m_BoneNameField;
private IntegerField m_BoneDepthField;
private FloatField m_BoneRotationField;
private Vector2Field m_BonePositionField;
private ColorField m_BoneColorField;
public string boneName
{
get { return m_BoneNameField.value; }
set { m_BoneNameField.value = value; }
}
public BoneCache target { get; set; }
public int boneDepth
{
get { return m_BoneDepthField.value; }
set { m_BoneDepthField.value = value; }
}
public Vector2 bonePosition
{
get { return m_BonePositionField.value; }
set { m_BonePositionField.SetValueWithoutNotify(value);}
}
public float boneRotation
{
get { return m_BoneRotationField.value; }
set { m_BoneRotationField.SetValueWithoutNotify(value);}
}
public Color32 boneColor
{
get => m_BoneColorField.value;
set { m_BoneColorField.SetValueWithoutNotify(value);}
}
public BoneInspectorPanel()
{
styleSheets.Add(ResourceLoader.Load<StyleSheet>("SkinningModule/BoneInspectorPanelStyle.uss"));
RegisterCallback<MouseDownEvent>((e) => { e.StopPropagation(); });
RegisterCallback<MouseUpEvent>((e) => { e.StopPropagation(); });
}
public void BindElements()
{
m_BoneNameField = this.Q<TextField>("BoneNameField");
m_BoneDepthField = this.Q<IntegerField>("BoneDepthField");
m_BoneRotationField = this.Q<FloatField>("BoneRotationField");
m_BonePositionField = this.Q<Vector2Field>("BonePositionField");
m_BoneColorField = this.Q<ColorField>("BoneColorField");
m_BoneNameField.RegisterCallback<FocusOutEvent>(BoneNameFocusChanged);
m_BoneDepthField.RegisterCallback<FocusOutEvent>(BoneDepthFocusChanged);
m_BoneRotationField.RegisterValueChangedCallback(evt => onBoneRotationChanged(target, evt.newValue));
m_BonePositionField.RegisterValueChangedCallback(evt => onBonePositionChanged(target, evt.newValue));
m_BoneColorField.RegisterValueChangedCallback(evt => onBoneColorChanged(target, evt.newValue));
}
private void BoneNameFocusChanged(FocusOutEvent evt)
{
onBoneNameChanged(target, boneName);
}
private void BoneDepthFocusChanged(FocusOutEvent evt)
{
onBoneDepthChanged(target, boneDepth);
}
public void HidePanel()
{
// We are hidding the panel, sent any unchanged value
this.SetHiddenFromLayout(true);
onBoneNameChanged(target, boneName);
onBoneDepthChanged(target, boneDepth);
}
public static BoneInspectorPanel GenerateFromUXML()
{
var visualTree = ResourceLoader.Load<VisualTreeAsset>("SkinningModule/BoneInspectorPanel.uxml");
var clone = visualTree.CloneTree().Q<BoneInspectorPanel>("BoneInspectorPanel");
clone.BindElements();
return clone;
}
public void SetReadOnly(PropertyReadOnly property)
{
m_BoneDepthField.SetEnabled(!property.HasFlag(PropertyReadOnly.Depth));
m_BoneNameField.SetEnabled(!property.HasFlag(PropertyReadOnly.Name));
m_BonePositionField.SetEnabled(!property.HasFlag(PropertyReadOnly.Position));
m_BoneRotationField.SetEnabled(!property.HasFlag(PropertyReadOnly.Rotation));
m_BoneColorField.SetEnabled(!property.HasFlag(PropertyReadOnly.Color));
}
}
}

Some files were not shown because too many files have changed in this diff Show More