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,6 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.2D.Animation.Tests.EditorTests")]
[assembly: InternalsVisibleTo("Unity.2D.Animation.Tests.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]
[assembly: InternalsVisibleTo("Unity.2D.PsdImporter.Editor")]
[assembly: InternalsVisibleTo("Unity.2D.Psdimporter.Tests.EditorTests")]

View File

@@ -0,0 +1,17 @@
namespace UnityEngine.U2D.Animation
{
// Component to store id of a SpriteBone so that it can be bind back when needed
[AddComponentMenu("")]
internal class Bone : MonoBehaviour
{
[SerializeField]
[HideInInspector]
string m_Guid;
public string guid
{
get => m_Guid;
set => m_Guid = value;
}
}
}

View File

@@ -0,0 +1,58 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.U2D.Animation
{
internal static class NativeArrayHelpers
{
public static unsafe void ResizeIfNeeded<T>(ref NativeArray<T> nativeArray, int size, Allocator allocator = Allocator.Persistent) where T : struct
{
bool canDispose = nativeArray.IsCreated;
if (canDispose && nativeArray.Length != size)
{
nativeArray.Dispose();
canDispose = false;
}
if(!canDispose)
nativeArray = new NativeArray<T>(size, allocator);
}
public static void ResizeAndCopyIfNeeded<T>(ref NativeArray<T> nativeArray, int size, Allocator allocator = Allocator.Persistent) where T : struct
{
bool canDispose = nativeArray.IsCreated;
if (canDispose && nativeArray.Length == size)
return;
var newArray = new NativeArray<T>(size, allocator);
if (canDispose)
{
NativeArray<T>.Copy(nativeArray, newArray, size < nativeArray.Length ? size : nativeArray.Length);
nativeArray.Dispose();
}
nativeArray = newArray;
}
public static unsafe void DisposeIfCreated<T>(this NativeArray<T> nativeArray) where T : struct
{
if (nativeArray.IsCreated)
nativeArray.Dispose();
}
[WriteAccessRequired]
public static unsafe void CopyFromNativeSlice<T, S>(this NativeArray<T> nativeArray, int dstStartIndex, int dstEndIndex, NativeSlice<S> slice, int srcStartIndex, int srcEndIndex) where T : struct where S : struct
{
if ((dstEndIndex - dstStartIndex) != (srcEndIndex - srcStartIndex))
throw new System.ArgumentException($"Destination and Source copy counts must match.", nameof(slice));
var dstSizeOf = UnsafeUtility.SizeOf<T>();
var srcSizeOf = UnsafeUtility.SizeOf<T>();
byte* srcPtr = (byte*)slice.GetUnsafeReadOnlyPtr();
srcPtr = srcPtr + (srcStartIndex * srcSizeOf);
byte* dstPtr = (byte*)nativeArray.GetUnsafePtr();
dstPtr = dstPtr + (dstStartIndex * dstSizeOf);
UnsafeUtility.MemCpyStride(dstPtr, srcSizeOf, srcPtr, slice.Stride, dstSizeOf, srcEndIndex - srcStartIndex);
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.U2D.Animation
{
internal struct NativeCustomSlice<T> where T : struct
{
[NativeDisableUnsafePtrRestriction] public IntPtr data;
public int length;
public int stride;
public static NativeCustomSlice<T> Default()
{
return new NativeCustomSlice<T>
{
data = IntPtr.Zero,
length = 0,
stride = 0
};
}
public unsafe NativeCustomSlice(NativeSlice<T> nativeSlice)
{
data = new IntPtr(nativeSlice.GetUnsafeReadOnlyPtr());
length = nativeSlice.Length;
stride = nativeSlice.Stride;
}
public unsafe NativeCustomSlice(NativeSlice<byte> slice, int length, int stride)
{
this.data = new IntPtr(slice.GetUnsafeReadOnlyPtr());
this.length = length;
this.stride = stride;
}
public unsafe T this[int index]
{
get { return UnsafeUtility.ReadArrayElementWithStride<T>(data.ToPointer(), index, stride); }
}
public int Length
{
get { return length; }
}
}
internal struct NativeCustomSliceEnumerator<T> : IEnumerable<T>, IEnumerator<T> where T : struct
{
private NativeCustomSlice<T> nativeCustomSlice;
private int index;
internal NativeCustomSliceEnumerator(NativeSlice<byte> slice, int length, int stride)
{
nativeCustomSlice = new NativeCustomSlice<T>(slice, length, stride);
index = -1;
Reset();
}
public IEnumerator<T> GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool MoveNext()
{
if (++index < nativeCustomSlice.length)
{
return true;
}
return false;
}
public void Reset()
{
index = -1;
}
public T Current => nativeCustomSlice[index];
object IEnumerator.Current => Current;
void IDisposable.Dispose() {}
}
}

View File

@@ -0,0 +1,15 @@
using UnityEngine;
using UnityEngine.Scripting;
namespace UnityEngine.U2D.Animation
{
[AddComponentMenu("")]
[System.Obsolete]
internal class SpriteSkinEntity : MonoBehaviour
{
void OnEnable()
{
Debug.LogWarning("SpriteSkinEntity will be removed in 2D Animation 7.0", this);
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.U2D.Animation
{
public class SkeletonAsset : ScriptableObject
{
[SerializeField] private SpriteBone[] m_SpriteBones;
public SpriteBone[] GetSpriteBones()
{
return m_SpriteBones;
}
public void SetSpriteBones(SpriteBone[] spriteBones)
{
m_SpriteBones = spriteBones;
}
}
}

View File

@@ -0,0 +1,333 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.Animation
{
/// <summary>
/// Component that holds a Sprite Library Asset. The component is used by SpriteResolver Component to query for Sprite based on Category and Index
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("2D Animation/Sprite Library")]
[MovedFrom("UnityEngine.Experimental.U2D.Animation")]
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html%23sprite-library-component")]
public class SpriteLibrary : MonoBehaviour
{
struct CategoryEntrySprite
{
public string category;
public string entry;
public Sprite sprite;
}
[SerializeField]
private List<SpriteLibCategory> m_Library = new List<SpriteLibCategory>();
[SerializeField]
private SpriteLibraryAsset m_SpriteLibraryAsset;
// Cache for combining data in sprite library asset and main library
Dictionary<int, CategoryEntrySprite> m_CategoryEntryHashCache = null;
Dictionary<string, HashSet<string>> m_CategoryEntryCache = null;
private int m_PreviousSpriteLibraryAsset;
/// <summary>Get or Set the current SpriteLibraryAsset to use </summary>
public SpriteLibraryAsset spriteLibraryAsset
{
set
{
if (m_SpriteLibraryAsset != value)
{
m_SpriteLibraryAsset = value;
CacheOverrides();
RefreshSpriteResolvers();
}
}
get { return m_SpriteLibraryAsset; }
}
void OnEnable()
{
CacheOverrides();
}
/// <summary>
/// Return the Sprite that is registered for the given Category and Label for the SpriteLibrary
/// </summary>
/// <param name="category">Category name</param>
/// <param name="label">Label name</param>
/// <returns>Sprite associated to the name and index</returns>
public Sprite GetSprite(string category, string label)
{
return GetSprite(GetHashForCategoryAndEntry(category, label));
}
internal Sprite GetSprite(int hash)
{
if (m_CategoryEntryHashCache.ContainsKey(hash))
return m_CategoryEntryHashCache[hash].sprite;
return null;
}
void UpdateCacheOverridesIfNeeded()
{
if(m_CategoryEntryCache == null || m_PreviousSpriteLibraryAsset != m_SpriteLibraryAsset?.GetInstanceID())
CacheOverrides();
}
internal bool GetCategoryAndEntryNameFromHash(int hash, out string category, out string entry)
{
UpdateCacheOverridesIfNeeded();
if (m_CategoryEntryHashCache.ContainsKey(hash))
{
category = m_CategoryEntryHashCache[hash].category;
entry = m_CategoryEntryHashCache[hash].entry;
return true;
}
category = null;
entry = null;
return false;
}
static internal int GetHashForCategoryAndEntry(string category, string entry)
{
return SpriteLibraryAsset.GetStringHash(string.Format("{0}_{1}", category, entry));
}
internal Sprite GetSpriteFromCategoryAndEntryHash(int hash, out bool validEntry)
{
UpdateCacheOverridesIfNeeded();
if (m_CategoryEntryHashCache.ContainsKey(hash))
{
validEntry = true;
return m_CategoryEntryHashCache[hash].sprite;
}
validEntry = false;
return null;
}
List<SpriteCategoryEntry> GetEntries(string category, bool addIfNotExist)
{
var index = m_Library.FindIndex(x => x.name == category);
if (index < 0)
{
if (!addIfNotExist)
return null;
m_Library.Add(new SpriteLibCategory()
{
name = category,
categoryList = new List<SpriteCategoryEntry>()
});
index = m_Library.Count - 1;
}
return m_Library[index].categoryList;
}
SpriteCategoryEntry GetEntry(List<SpriteCategoryEntry> entries, string entry, bool addIfNotExist)
{
var index = entries.FindIndex(x => x.name == entry);
if (index < 0)
{
if (!addIfNotExist)
return null;
entries.Add(new SpriteCategoryEntry()
{
name = entry,
});
index = entries.Count - 1;
}
return entries[index];
}
/// <summary>
/// Add or replace an override when querying for the given Category and Label from a SpriteLibraryAsset
/// </summary>
/// <param name="spriteLib">Sprite Library Asset to query</param>
/// <param name="category">Category name from the Sprite Library Asset to add override</param>
/// <param name="label">Label name to add override</param>
public void AddOverride(SpriteLibraryAsset spriteLib, string category, string label)
{
var sprite = spriteLib.GetSprite(category, label);
var entries = GetEntries(category, true);
var entry = GetEntry(entries, label, true);
entry.sprite = sprite;
CacheOverrides();
}
/// <summary>
/// Add or replace an override when querying for the given Category. All the categories in the Category will be added.
/// </summary>
/// <param name="spriteLib">Sprite Library Asset to query</param>
/// <param name="category">Category name from the Sprite Library Asset to add override</param>
public void AddOverride(SpriteLibraryAsset spriteLib, string category)
{
var categoryHash = SpriteLibraryAsset.GetStringHash(category);
var cat = spriteLib.categories.FirstOrDefault(x => x.hash == categoryHash);
if (cat != null)
{
var entries = GetEntries(category, true);
for (int i = 0; i < cat.categoryList.Count; ++i)
{
var ent = cat.categoryList[i];
GetEntry(entries, ent.name, true).sprite = ent.sprite;
}
CacheOverrides();
}
}
/// <summary>
/// Add or replace an override when querying for the given Category and Label.
/// </summary>
/// <param name="sprite">Sprite to override to</param>
/// <param name="category">Category name to override</param>
/// <param name="label">Label name to override</param>
public void AddOverride(Sprite sprite, string category, string label)
{
GetEntry(GetEntries(category, true), label, true).sprite = sprite;
CacheOverrides();
RefreshSpriteResolvers();
}
/// <summary>
/// Remove all Sprite Library override for a given category
/// </summary>
/// <param name="category">Category overrides to remove</param>
public void RemoveOverride(string category)
{
var index = m_Library.FindIndex(x => x.name == category);
if (index >= 0)
{
m_Library.RemoveAt(index);
CacheOverrides();
RefreshSpriteResolvers();
}
}
/// <summary>
/// Remove Sprite Library override for a given category and label
/// </summary>
/// <param name="category">Category to remove</param>
/// <param name="label">Label to remove</param>
public void RemoveOverride(string category, string label)
{
var entries = GetEntries(category, false);
if (entries != null)
{
var index = entries.FindIndex(x => x.name == label);
if (index >= 0)
{
entries.RemoveAt(index);
CacheOverrides();
RefreshSpriteResolvers();
}
}
}
/// <summary>
/// Method to check if a Category and Label pair has an override
/// </summary>
/// <param name="category">Category name</param>
/// <param name="label">Label name</param>
/// <returns>True if override exist, false otherwise</returns>
public bool HasOverride(string category, string label)
{
var catOverride = GetEntries(category, false);
if (catOverride != null)
return GetEntry(catOverride, label, false) != null;
return false;
}
/// <summary>
/// Request SpriteResolver components that are in the same hierarchy to refresh
/// </summary>
public void RefreshSpriteResolvers()
{
var spriteResolvers = GetComponentsInChildren<SpriteResolver>();
foreach (var sr in spriteResolvers)
{
sr.ResolveSpriteToSpriteRenderer();
#if UNITY_EDITOR
sr.spriteLibChanged = true;
#endif
}
}
internal IEnumerable<string> categoryNames
{
get
{
UpdateCacheOverridesIfNeeded();
return m_CategoryEntryCache.Keys;
}
}
internal IEnumerable<string> GetEntryNames(string category)
{
UpdateCacheOverridesIfNeeded();
if (m_CategoryEntryCache.ContainsKey(category))
return m_CategoryEntryCache[category];
return null;
}
internal void CacheOverrides()
{
m_PreviousSpriteLibraryAsset = 0;
m_CategoryEntryHashCache = new Dictionary<int, CategoryEntrySprite>();
m_CategoryEntryCache = new Dictionary<string, HashSet<string>>();
if (m_SpriteLibraryAsset)
{
m_PreviousSpriteLibraryAsset = m_SpriteLibraryAsset.GetInstanceID();
foreach (var category in m_SpriteLibraryAsset.categories)
{
var catName = category.name;
m_CategoryEntryCache.Add(catName, new HashSet<string>());
var cacheEntryName = m_CategoryEntryCache[catName];
foreach (var entry in category.categoryList)
{
m_CategoryEntryHashCache.Add(GetHashForCategoryAndEntry(catName, entry.name), new CategoryEntrySprite()
{
category = catName,
entry = entry.name,
sprite = entry.sprite
});
cacheEntryName.Add(entry.name);
}
}
}
foreach (var category in m_Library)
{
var catName = category.name;
if(!m_CategoryEntryCache.ContainsKey(catName))
m_CategoryEntryCache.Add(catName, new HashSet<string>());
var cacheEntryName = m_CategoryEntryCache[catName];
foreach (var ent in category.categoryList)
{
if (!cacheEntryName.Contains(ent.name))
cacheEntryName.Add(ent.name);
var hash = GetHashForCategoryAndEntry(catName, ent.name);
if (!m_CategoryEntryHashCache.ContainsKey(hash))
{
m_CategoryEntryHashCache.Add(hash, new CategoryEntrySprite()
{
category = catName,
entry = ent.name,
sprite = ent.sprite
});
}
else
{
var e = m_CategoryEntryHashCache[hash];
e.sprite = ent.sprite;
m_CategoryEntryHashCache[hash] = e;
}
}
}
}
}
}

View File

@@ -0,0 +1,371 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.Assertions;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.Animation
{
internal interface INameHash
{
string name { get; set; }
int hash { get; }
}
[Serializable]
[MovedFrom("UnityEngine.Experimental.U2D.Animation")]
internal class SpriteCategoryEntry : INameHash
{
[SerializeField]
string m_Name;
[SerializeField]
[HideInInspector]
int m_Hash;
[SerializeField]
Sprite m_Sprite;
public string name
{
get { return m_Name; }
set
{
m_Name = value;
m_Hash = SpriteLibraryAsset.GetStringHash(m_Name);
}
}
public int hash { get { return m_Hash; } }
public Sprite sprite {get { return m_Sprite; } set { m_Sprite = value; }}
public void UpdateHash()
{
m_Hash = SpriteLibraryAsset.GetStringHash(m_Name);
}
}
[Serializable]
[MovedFrom("UnityEngine.Experimental.U2D.Animation")]
internal class SpriteLibCategory : INameHash
{
[SerializeField]
string m_Name;
[SerializeField]
int m_Hash;
[SerializeField]
List<SpriteCategoryEntry> m_CategoryList;
public string name
{
get { return m_Name; }
set
{
m_Name = value;
m_Hash = SpriteLibraryAsset.GetStringHash(m_Name);
}
}
public int hash { get { return m_Hash; } }
public List<SpriteCategoryEntry> categoryList
{
get { return m_CategoryList; }
set { m_CategoryList = value; }
}
public void UpdateHash()
{
m_Hash = SpriteLibraryAsset.GetStringHash(m_Name);
foreach (var s in m_CategoryList)
s.UpdateHash();
}
internal void ValidateLabels()
{
SpriteLibraryAsset.RenameDuplicate(m_CategoryList,
(originalName, newName)
=>
{
Debug.LogWarning(string.Format("Label {0} renamed to {1} due to hash clash", originalName, newName));
});
}
}
/// <summary>
/// A custom Asset that stores Sprites grouping
/// </summary>
/// <Description>
/// Sprites are grouped under a given category as categories. Each category and label needs to have
/// a name specified so that it can be queried.
/// </Description>
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html")]
[MovedFrom("UnityEngine.Experimental.U2D.Animation")]
public class SpriteLibraryAsset : ScriptableObject
{
[SerializeField]
private List<SpriteLibCategory> m_Labels = new List<SpriteLibCategory>();
internal List<SpriteLibCategory> categories
{
get
{
return m_Labels;
}
set
{
m_Labels = value;
ValidateCategories();
}
}
internal Sprite GetSprite(int categoryHash, int labelHash)
{
var category = m_Labels.FirstOrDefault(x => x.hash == categoryHash);
if (category != null)
{
var spritelabel = category.categoryList.FirstOrDefault(x => x.hash == labelHash);
if (spritelabel != null)
{
return spritelabel.sprite;
}
}
return null;
}
internal Sprite GetSprite(int categoryHash, int labelHash, out bool validEntry)
{
SpriteLibCategory category = null;
for (int i = 0; i < m_Labels.Count; ++i)
{
if (m_Labels[i].hash == categoryHash)
{
category = m_Labels[i];
break;
}
}
if (category != null)
{
SpriteCategoryEntry spritelabel = null;
for (int i = 0; i < category.categoryList.Count; ++i)
{
if (category.categoryList[i].hash == labelHash)
{
spritelabel = category.categoryList[i];
break;
}
}
if (spritelabel != null)
{
validEntry = true;
return spritelabel.sprite;
}
}
validEntry = false;
return null;
}
/// <summary>
/// Returns the Sprite registered in the Asset given the Category and Label value
/// </summary>
/// <param name="category">Category string value</param>
/// <param name="label">Label string value</param>
/// <returns></returns>
public Sprite GetSprite(string category, string label)
{
var categoryHash = SpriteLibraryAsset.GetStringHash(category);
var labelHash = SpriteLibraryAsset.GetStringHash(label);
return GetSprite(categoryHash, labelHash);
}
/// <summary>
/// Return all the Category names of the Sprite Library Asset that is associated.
/// </summary>
/// <returns>A Enumerable string value representing the name</returns>
public IEnumerable<string> GetCategoryNames()
{
return m_Labels.Select(x => x.name);
}
/// <summary>
/// (Obsolete) Returns the labels' name for the given name
/// </summary>
/// <param name="category">Category name</param>
/// <returns>A Enumerable string representing labels' name</returns>
[Obsolete("GetCategorylabelNames has been deprecated. Please use GetCategoryLabelNames (UnityUpgradable) -> GetCategoryLabelNames(*)")]
public IEnumerable<string> GetCategorylabelNames(string category)
{
return GetCategoryLabelNames(category);
}
/// <summary>
/// Returns the labels' name for the given name
/// </summary>
/// <param name="category">Category name</param>
/// <returns>A Enumerable string representing labels' name</returns>
public IEnumerable<string> GetCategoryLabelNames(string category)
{
var label = m_Labels.FirstOrDefault(x => x.name == category);
return label == null ? new string[0] : label.categoryList.Select(x => x.name);
}
/// <summary>
/// Add or replace and existing Sprite into the given Category and Label
/// </summary>
/// <param name="sprite">Sprite to add</param>
/// <param name="category">Category to add the Sprite to</param>
/// <param name="label">Label of the Category to add the Sprite to. If this parameter is null or an empty string, it will attempt to add a empty category</param>
public void AddCategoryLabel(Sprite sprite, string category, string label)
{
category = category.Trim();
label = label?.Trim();
if (string.IsNullOrEmpty(category))
throw new ArgumentException("Cannot add empty or null Category string");
var catHash = SpriteLibraryAsset.GetStringHash(category);
SpriteCategoryEntry categorylabel = null;
SpriteLibCategory libCategory = null;
libCategory = m_Labels.FirstOrDefault(x => x.hash == catHash);
if (libCategory != null)
{
if(string.IsNullOrEmpty(label))
throw new ArgumentException("Cannot add empty or null Label string");
Assert.AreEqual(libCategory.name, category, "Category string hash clashes with another existing Category. Please use another string");
var labelHash = SpriteLibraryAsset.GetStringHash(label);
categorylabel = libCategory.categoryList.FirstOrDefault(y => y.hash == labelHash);
if (categorylabel != null)
{
Assert.AreEqual(categorylabel.name, label, "Label string hash clashes with another existing label. Please use another string");
categorylabel.sprite = sprite;
}
else
{
categorylabel = new SpriteCategoryEntry()
{
name = label,
sprite = sprite
};
libCategory.categoryList.Add(categorylabel);
}
}
else
{
var slc = new SpriteLibCategory()
{
categoryList = new List<SpriteCategoryEntry>(),
name = category
};
if (!string.IsNullOrEmpty(label))
{
slc.categoryList.Add(new SpriteCategoryEntry()
{
name = label,
sprite = sprite
});
}
m_Labels.Add(slc);
}
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
}
/// <summary>
/// Remove a Label from a given Category
/// </summary>
/// <param name="category">Category to remove from</param>
/// <param name="label">Label to remove</param>
/// <param name="deleteCategory">Indicate to remove the Category if it is empty</param>
public void RemoveCategoryLabel(string category, string label, bool deleteCategory)
{
var catHash = SpriteLibraryAsset.GetStringHash(category);
SpriteLibCategory libCategory = null;
libCategory = m_Labels.FirstOrDefault(x => x.hash == catHash);
if (libCategory != null)
{
var labelHash = SpriteLibraryAsset.GetStringHash(label);
libCategory.categoryList.RemoveAll(x => x.hash == labelHash);
if (deleteCategory && libCategory.categoryList.Count == 0)
m_Labels.RemoveAll(x => x.hash == libCategory.hash);
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
}
}
internal void UpdateHashes()
{
foreach (var e in m_Labels)
e.UpdateHash();
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
internal void ValidateCategories()
{
RenameDuplicate(m_Labels, (originalName, newName)
=>
{
Debug.LogWarning(string.Format("Category {0} renamed to {1} due to hash clash", originalName, newName));
});
for (int i = 0; i < m_Labels.Count; ++i)
{
// Verify categories have no hash clash
var category = m_Labels[i];
// Verify labels have no clash
category.ValidateLabels();
}
}
internal static void RenameDuplicate(IEnumerable<INameHash> nameHashList, Action<string, string> onRename)
{
const int k_IncrementMax = 1000;
for (int i = 0; i < nameHashList.Count(); ++i)
{
// Verify categories have no hash clash
var category = nameHashList.ElementAt(i);
var categoriesClash = nameHashList.Where(x => (x.hash == category.hash || x.name == category.name) && x != category);
int increment = 0;
for (int j = 0; j < categoriesClash.Count(); ++j)
{
var categoryClash = categoriesClash.ElementAt(j);
while (increment < k_IncrementMax)
{
var name = categoryClash.name;
name = string.Format("{0}_{1}", name, increment);
var nameHash = SpriteLibraryAsset.GetStringHash(name);
var exist = nameHashList.FirstOrDefault(x => (x.hash == nameHash || x.name == name) && x != categoryClash);
if (exist == null)
{
onRename(categoryClash.name, name);
categoryClash.name = name;
break;
}
++increment;
}
}
}
}
// Allow delegate override for test
internal static Func<string, int> GetStringHash = Default_GetStringHash;
internal static int Default_GetStringHash(string value)
{
#if DEBUG_GETSTRINGHASH_CLASH
if (value == "abc" || value == "123")
value = "abc";
#endif
var hash = Animator.StringToHash(value);
var bytes = BitConverter.GetBytes(hash);
var exponentialBit = BitConverter.IsLittleEndian ? 3 : 1;
if (bytes[exponentialBit] == 0xFF)
bytes[exponentialBit] -= 1;
return BitConverter.ToInt32(bytes, 0);
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace UnityEngine.U2D.Animation
{
[Serializable]
internal class SpriteCategoryEntryOverride : SpriteCategoryEntry
{
[SerializeField]
bool m_FromMain;
[SerializeField]
Sprite m_SpriteOverride;
public bool fromMain
{
get => m_FromMain;
set => m_FromMain = value;
}
public Sprite spriteOverride
{
get => m_SpriteOverride;
set => m_SpriteOverride = value;
}
}
[Serializable]
internal class SpriteLibCategoryOverride : SpriteLibCategory
{
[SerializeField]
private List<SpriteCategoryEntryOverride> m_OverrideEntries;
[SerializeField]
bool m_FromMain;
[SerializeField]
int m_EntryOverrideCount;
public bool fromMain
{
get => m_FromMain;
set => m_FromMain = value;
}
public int entryOverrideCount
{
get => m_EntryOverrideCount;
set => m_EntryOverrideCount = value;
}
public List<SpriteCategoryEntryOverride> overrideEntries
{
get { return m_OverrideEntries; }
set { m_OverrideEntries = value; }
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections;
using System.Collections.Generic;
namespace UnityEngine.U2D.Animation
{
internal class SpriteLibrarySourceAsset : ScriptableObject
{
[SerializeField]
private List<SpriteLibCategoryOverride> m_Library = new List<SpriteLibCategoryOverride>();
[SerializeField]
private string m_PrimaryLibraryGUID;
public List<SpriteLibCategoryOverride> library
{
get => m_Library;
set => m_Library = value;
}
public string primaryLibraryID
{
get => m_PrimaryLibraryGUID;
set => m_PrimaryLibraryGUID = value;
}
}
}

View File

@@ -0,0 +1,292 @@
using System;
using UnityEngine.Animations;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.Animation
{
/// <summary>
/// Updates a SpriteRenderer's Sprite reference on the Category and Label value it is set
/// </summary>
/// <Description>
/// By setting the SpriteResolver's Category and Label value, it will request for a Sprite from
/// a SpriteLibrary Component the Sprite that is registered for the Category and Label.
/// If a SpriteRenderer is present in the same GameObject, the SpriteResolver will update the
/// SpriteRenderer's Sprite reference to the corresponding Sprite.
/// </Description>
[ExecuteInEditMode]
[DisallowMultipleComponent]
[AddComponentMenu("2D Animation/Sprite Resolver")]
[DefaultExecutionOrder(-2)]
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html%23sprite-resolver-component")]
[MovedFrom("UnityEngine.Experimental.U2D.Animation")]
public class SpriteResolver : MonoBehaviour, ISerializationCallbackReceiver
{
// SpriteKey is the new animation key.
// We are keeping the old ones so that the animation clip doesn't braek
// These are for animation
[SerializeField]
private float m_CategoryHash = 0;
[SerializeField]
private float m_labelHash = 0;
[SerializeField]
private float m_SpriteKey = 0;
// For comparing hash values
private int m_CategoryHashInt;
private int m_LabelHashInt;
private int m_SpriteKeyInt;
// For OnUpdate during animation playback
private int m_PreviousCategoryHash;
private int m_PreviouslabelHash;
private int m_PreviousSpriteKeyInt;
#if UNITY_EDITOR
bool m_SpriteLibChanged;
public event Action onDeserializedCallback = () => { };
#endif
void Reset()
{
// If the Sprite referred to by the SpriteRenderer exist in the library,
// we select the Sprite
if(spriteRenderer)
SetSprite(spriteRenderer.sprite);
}
void SetSprite(Sprite sprite)
{
var sl = spriteLibrary;
if (sl != null && sprite != null)
{
foreach (var cat in sl.categoryNames)
{
var entries = sl.GetEntryNames(cat);
foreach (var ent in entries)
{
if (sl.GetSprite(cat, ent) == sprite)
{
spriteKeyInt = SpriteLibrary.GetHashForCategoryAndEntry(cat, ent);
return;
}
}
}
}
}
void OnEnable()
{
m_CategoryHashInt = ConvertFloatToInt(m_CategoryHash);
m_PreviousCategoryHash = m_CategoryHashInt;
m_LabelHashInt = ConvertFloatToInt(m_labelHash);
m_PreviouslabelHash = m_LabelHashInt;
m_SpriteKeyInt = ConvertFloatToInt(m_SpriteKey);
if (m_SpriteKeyInt == 0)
{
m_SpriteKey = ConvertCategoryLabelHashToSpriteKey(spriteLibrary, m_CategoryHashInt, m_LabelHashInt);
m_SpriteKeyInt = ConvertFloatToInt(m_SpriteKey);
}
m_PreviousSpriteKeyInt = m_SpriteKeyInt;
ResolveSpriteToSpriteRenderer();
}
SpriteRenderer spriteRenderer
{
get { return GetComponent<SpriteRenderer>(); }
}
/// <summary>
/// Set the Category and label to use
/// </summary>
/// <param name="category">Category to use</param>
/// <param name="label">Label to use</param>
public void SetCategoryAndLabel(string category, string label)
{
spriteKeyInt = SpriteLibrary.GetHashForCategoryAndEntry(category, label);
m_PreviousSpriteKeyInt = spriteKeyInt;
ResolveSpriteToSpriteRenderer();
}
/// <summary>
/// Get the Category set for the SpriteResolver
/// </summary>
/// <returns>The Category's name</returns>
public string GetCategory()
{
var returnString = "";
var sl = spriteLibrary;
if (sl)
{
sl.GetCategoryAndEntryNameFromHash(spriteKeyInt, out returnString, out _);
}
return returnString;
}
/// <summary>
/// Get the Label set for the SpriteResolver
/// </summary>
/// <returns>The Label's name</returns>
public string GetLabel()
{
var returnString = "";
var sl = spriteLibrary;
if (sl)
sl.GetCategoryAndEntryNameFromHash(spriteKeyInt, out _, out returnString);
return returnString;
}
/// <summary>
/// Property to get the SpriteLibrary the SpriteResolver is resolving from
/// </summary>
public SpriteLibrary spriteLibrary
{
get
{
var t = transform;
while (t != null)
{
var sl = t.GetComponent<SpriteLibrary>();
if (sl != null)
return sl;
t = t.parent;
}
return null;
}
}
void LateUpdate()
{
m_SpriteKeyInt = ConvertFloatToInt(m_SpriteKey);
if (m_SpriteKeyInt != m_PreviousSpriteKeyInt)
{
m_PreviousSpriteKeyInt = m_SpriteKeyInt;
ResolveSpriteToSpriteRenderer();
}
else
{
m_CategoryHashInt = ConvertFloatToInt(m_CategoryHash);
m_LabelHashInt = ConvertFloatToInt(m_labelHash);
if (m_LabelHashInt != m_PreviouslabelHash || m_CategoryHashInt != m_PreviousCategoryHash)
{
if (spriteLibrary != null)
{
m_PreviousCategoryHash = m_CategoryHashInt;
m_PreviouslabelHash = m_LabelHashInt;
m_SpriteKey = ConvertCategoryLabelHashToSpriteKey(spriteLibrary, m_CategoryHashInt, m_LabelHashInt);
m_SpriteKeyInt = ConvertFloatToInt(m_SpriteKey);
m_PreviousSpriteKeyInt = m_SpriteKeyInt;
ResolveSpriteToSpriteRenderer();
}
}
}
}
internal static float ConvertCategoryLabelHashToSpriteKey(SpriteLibrary library, int categoryHash, int labelHash)
{
if (library != null)
{
foreach(var category in library.categoryNames)
{
if (categoryHash == SpriteLibraryAsset.GetStringHash(category))
{
var entries = library.GetEntryNames(category);
if (entries != null)
{
foreach (var entry in entries)
{
if (labelHash == SpriteLibraryAsset.GetStringHash(entry))
{
var spriteKey = SpriteLibrary.GetHashForCategoryAndEntry(category, entry);
return ConvertIntToFloat(spriteKey);
}
}
}
}
}
}
return 0;
}
internal Sprite GetSprite(out bool validEntry)
{
var lib = spriteLibrary;
if (lib != null)
{
return lib.GetSpriteFromCategoryAndEntryHash(m_SpriteKeyInt, out validEntry);
}
validEntry = false;
return null;
}
/// <summary>
/// Set the Sprite in SpriteResolver to the SpriteRenderer component that is in the same GameObject.
/// </summary>
public void ResolveSpriteToSpriteRenderer()
{
m_PreviousSpriteKeyInt = m_SpriteKeyInt;
bool validEntry;
var sprite = GetSprite(out validEntry);
var sr = spriteRenderer;
if (sr != null && (sprite != null || validEntry))
sr.sprite = sprite;
}
void OnTransformParentChanged()
{
ResolveSpriteToSpriteRenderer();
#if UNITY_EDITOR
spriteLibChanged = true;
#endif
}
int spriteKeyInt
{
get { return m_SpriteKeyInt; }
set
{
m_SpriteKeyInt = value;
m_SpriteKey = ConvertIntToFloat(m_SpriteKeyInt);
}
}
internal unsafe static int ConvertFloatToInt(float f)
{
float* fp = &f;
int* i = (int*)fp;
return *i;
}
internal unsafe static float ConvertIntToFloat(int f)
{
int* fp = &f;
float* i = (float*)fp;
return *i;
}
#if UNITY_EDITOR
internal bool spriteLibChanged
{
get {return m_SpriteLibChanged;}
set { m_SpriteLibChanged = value; }
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
#if UNITY_EDITOR
onDeserializedCallback();
#endif
}
}
}

View File

@@ -0,0 +1,521 @@
#pragma warning disable 0168 // variable declared but not used.
#if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST
#define ENABLE_SPRITESKIN_COMPOSITE
#endif
using System;
using System.Collections.Generic;
using UnityEngine.Scripting;
using UnityEngine.U2D.Common;
using Unity.Collections;
using UnityEngine.Rendering;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.Animation
{
public struct PositionVertex
{
public Vector3 position;
}
public struct PositionTangentVertex
{
public Vector3 position;
public Vector4 tangent;
}
struct DeformVerticesBuffer
{
public const int k_DefaultBufferSize = 2;
int m_BufferCount;
int m_CurrentBuffer;
NativeArray<byte>[] m_DeformedVertices;
public DeformVerticesBuffer(int bufferCount)
{
m_BufferCount = bufferCount;
m_DeformedVertices = new NativeArray<byte>[m_BufferCount];
for (int i = 0; i < m_BufferCount; ++i)
{
m_DeformedVertices[i] = new NativeArray<byte>(1, Allocator.Persistent);
}
m_CurrentBuffer = 0;
}
public void Dispose()
{
for (int i = 0; i < m_BufferCount; ++i)
{
if (m_DeformedVertices[i].IsCreated)
m_DeformedVertices[i].Dispose();
}
}
public ref NativeArray<byte> GetBuffer(int expectedSize)
{
m_CurrentBuffer = (m_CurrentBuffer + 1) % m_BufferCount;
if (m_DeformedVertices[m_CurrentBuffer].IsCreated && m_DeformedVertices[m_CurrentBuffer].Length != expectedSize)
{
m_DeformedVertices[m_CurrentBuffer].Dispose();
m_DeformedVertices[m_CurrentBuffer] = new NativeArray<byte>(expectedSize, Allocator.Persistent);
}
return ref m_DeformedVertices[m_CurrentBuffer];
}
internal ref NativeArray<byte> GetCurrentBuffer()
{
return ref m_DeformedVertices[m_CurrentBuffer];
}
}
/// <summary>
/// Deforms the Sprite that is currently assigned to the SpriteRenderer in the same GameObject
/// </summary>
[Preserve]
[ExecuteInEditMode]
[DefaultExecutionOrder(-1)]
[DisallowMultipleComponent]
[RequireComponent(typeof(SpriteRenderer))]
[AddComponentMenu("2D Animation/Sprite Skin")]
[MovedFrom("UnityEngine.U2D.Experimental.Animation")]
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/index.html%23sprite-skin-component")]
public sealed partial class SpriteSkin : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
private Transform m_RootBone;
[SerializeField]
private Transform[] m_BoneTransforms = new Transform[0];
[SerializeField]
private Bounds m_Bounds;
[SerializeField]
private bool m_UseBatching = true;
[SerializeField]
private bool m_AlwaysUpdate = true;
[SerializeField]
private bool m_AutoRebind = false;
// The deformed m_SpriteVertices stores all 'HOT' channels only in single-stream and essentially depends on Sprite Asset data.
// The order of storage if present is POSITION, NORMALS, TANGENTS.
private DeformVerticesBuffer m_DeformedVertices;
private int m_CurrentDeformVerticesLength = 0;
private SpriteRenderer m_SpriteRenderer;
private int m_CurrentDeformSprite = 0;
private bool m_ForceSkinning;
private bool m_BatchSkinning = false;
bool m_IsValid = false;
int m_TransformsHash = 0;
internal bool batchSkinning
{
get { return m_BatchSkinning; }
set { m_BatchSkinning = value; }
}
internal bool autoRebind
{
get => m_AutoRebind;
set
{
m_AutoRebind = value;
CacheCurrentSprite(m_AutoRebind);
}
}
#if UNITY_EDITOR
internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent();
private void OnDrawGizmos() { onDrawGizmos.Invoke(); }
private bool m_IgnoreNextSpriteChange = true;
internal bool ignoreNextSpriteChange
{
get { return m_IgnoreNextSpriteChange; }
set { m_IgnoreNextSpriteChange = value; }
}
#endif
private int GetSpriteInstanceID()
{
return sprite != null ? sprite.GetInstanceID() : 0;
}
internal void Awake()
{
m_SpriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable()
{
Awake();
m_TransformsHash = 0;
CacheCurrentSprite(false);
OnEnableBatch();
m_DeformedVertices = new DeformVerticesBuffer(DeformVerticesBuffer.k_DefaultBufferSize);
}
internal void OnEditorEnable()
{
Awake();
}
void CacheValidFlag()
{
m_IsValid = isValid;
if(!m_IsValid)
DeactivateSkinning();
}
void Reset()
{
Awake();
if (isActiveAndEnabled)
{
CacheValidFlag();
OnResetBatch();
}
}
internal void UseBatching(bool value)
{
if (m_UseBatching != value)
{
m_UseBatching = value;
UseBatchingBatch();
}
}
internal ref NativeArray<byte> GetDeformedVertices(int spriteVertexCount)
{
if (sprite != null)
{
if (m_CurrentDeformVerticesLength != spriteVertexCount)
{
m_TransformsHash = 0;
m_CurrentDeformVerticesLength = spriteVertexCount;
}
}
else
{
m_CurrentDeformVerticesLength = 0;
}
return ref m_DeformedVertices.GetBuffer(m_CurrentDeformVerticesLength);
}
/// <summary>
/// Returns whether this SpriteSkin has currently deformed vertices.
/// </summary>
/// <returns>Returns true if this SpriteSkin has currently deformed vertices. Returns false otherwise.</returns>
public bool HasCurrentDeformedVertices()
{
if (!m_IsValid)
return false;
#if ENABLE_SPRITESKIN_COMPOSITE
return m_DataIndex >= 0 && SpriteSkinComposite.instance.HasDeformableBufferForSprite(m_DataIndex);
#else
return m_CurrentDeformVerticesLength > 0 && m_DeformedVertices.GetCurrentBuffer().IsCreated;
#endif
}
/// <summary>
/// Gets a byte array to the currently deformed vertices for this SpriteSkin.
/// </summary>
/// <returns>Returns a reference to the currently deformed vertices. This is valid only for this calling frame.</returns>
/// <exception cref="InvalidOperationException">Thrown when there are no currently deformed vertices</exception>
internal NativeArray<byte> GetCurrentDeformedVertices()
{
if (!m_IsValid)
throw new InvalidOperationException("The SpriteSkin deformation is not valid.");
#if ENABLE_SPRITESKIN_COMPOSITE
if (m_DataIndex < 0)
{
throw new InvalidOperationException("There are no currently deformed vertices.");
}
return SpriteSkinComposite.instance.GetDeformableBufferForSprite(m_DataIndex);
#else
if (m_CurrentDeformVerticesLength <= 0)
throw new InvalidOperationException("There are no currently deformed vertices.");
var buffer = m_DeformedVertices.GetCurrentBuffer();
if (!buffer.IsCreated)
throw new InvalidOperationException("There are no currently deformed vertices.");
return buffer;
#endif
}
/// <summary>
/// Gets an array of currently deformed position vertices for this SpriteSkin.
/// </summary>
/// <returns>Returns a reference to the currently deformed vertices. This is valid only for this calling frame.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when there are no currently deformed vertices or if the deformed vertices does not contain only
/// position data.
/// </exception>
internal NativeSlice<PositionVertex> GetCurrentDeformedVertexPositions()
{
if (sprite.HasVertexAttribute(VertexAttribute.Tangent))
throw new InvalidOperationException("This SpriteSkin has deformed tangents");
if (!sprite.HasVertexAttribute(VertexAttribute.Position))
throw new InvalidOperationException("This SpriteSkin does not have deformed positions.");
var deformedBuffer = GetCurrentDeformedVertices();
return deformedBuffer.Slice().SliceConvert<PositionVertex>();
}
/// <summary>
/// Gets an array of currently deformed position and tangent vertices for this SpriteSkin.
/// </summary>
/// <returns>
/// Returns a reference to the currently deformed position and tangent vertices. This is valid only for this calling frame.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when there are no currently deformed vertices or if the deformed vertices does not contain only
/// position and tangent data.
/// </exception>
internal NativeSlice<PositionTangentVertex> GetCurrentDeformedVertexPositionsAndTangents()
{
if (!sprite.HasVertexAttribute(VertexAttribute.Tangent))
throw new InvalidOperationException("This SpriteSkin does not have deformed tangents");
if (!sprite.HasVertexAttribute(VertexAttribute.Position))
throw new InvalidOperationException("This SpriteSkin does not have deformed positions.");
var deformedBuffer = GetCurrentDeformedVertices();
return deformedBuffer.Slice().SliceConvert<PositionTangentVertex>();
}
/// <summary>
/// Gets an enumerable to iterate through all deformed vertex positions of this SpriteSkin.
/// </summary>
/// <returns>Returns an IEnumerable to deformed vertex positions.</returns>
/// <exception cref="InvalidOperationException">Thrown when there is no vertex positions or deformed vertices.</exception>
public IEnumerable<Vector3> GetDeformedVertexPositionData()
{
bool hasPosition = sprite.HasVertexAttribute(Rendering.VertexAttribute.Position);
if (!hasPosition)
throw new InvalidOperationException("Sprite does not have vertex position data.");
var rawBuffer = GetCurrentDeformedVertices();
var rawSlice = rawBuffer.Slice(sprite.GetVertexStreamOffset(VertexAttribute.Position));
return new NativeCustomSliceEnumerator<Vector3>(rawSlice, sprite.GetVertexCount(), sprite.GetVertexStreamSize());
}
/// <summary>
/// Gets an enumerable to iterate through all deformed vertex tangents of this SpriteSkin.
/// </summary>
/// <returns>Returns an IEnumerable to deformed vertex tangents.</returns>
/// <exception cref="InvalidOperationException">Thrown when there is no vertex tangents or deformed vertices.</exception>
public IEnumerable<Vector4> GetDeformedVertexTangentData()
{
bool hasTangent = sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent);
if (!hasTangent)
throw new InvalidOperationException("Sprite does not have vertex tangent data.");
var rawBuffer = GetCurrentDeformedVertices();
var rawSlice = rawBuffer.Slice(sprite.GetVertexStreamOffset(VertexAttribute.Tangent));
return new NativeCustomSliceEnumerator<Vector4>(rawSlice, sprite.GetVertexCount(), sprite.GetVertexStreamSize());
}
void OnDisable()
{
DeactivateSkinning();
m_DeformedVertices.Dispose();
OnDisableBatch();
}
#if ENABLE_SPRITESKIN_COMPOSITE
internal void OnLateUpdate()
#else
void LateUpdate()
#endif
{
CacheCurrentSprite(m_AutoRebind);
if (isValid && !batchSkinning && this.enabled && (this.alwaysUpdate || this.spriteRenderer.isVisible))
{
var transformHash = SpriteSkinUtility.CalculateTransformHash(this);
var spriteVertexCount = sprite.GetVertexStreamSize() * sprite.GetVertexCount();
if (spriteVertexCount > 0 && m_TransformsHash != transformHash)
{
var inputVertices = GetDeformedVertices(spriteVertexCount);
SpriteSkinUtility.Deform(sprite, gameObject.transform.worldToLocalMatrix, boneTransforms, ref inputVertices);
SpriteSkinUtility.UpdateBounds(this, inputVertices);
InternalEngineBridge.SetDeformableBuffer(spriteRenderer, inputVertices);
m_TransformsHash = transformHash;
m_CurrentDeformSprite = GetSpriteInstanceID();
}
}
}
void CacheCurrentSprite(bool rebind)
{
if (m_CurrentDeformSprite != GetSpriteInstanceID())
{
DeactivateSkinning();
m_CurrentDeformSprite = GetSpriteInstanceID();
if (rebind && m_CurrentDeformSprite > 0 && rootBone != null)
{
var spriteBones = sprite.GetBones();
var transforms = new Transform[spriteBones.Length];
if (GetSpriteBonesTransforms(spriteBones, rootBone, transforms))
boneTransforms = transforms;
}
UpdateSpriteDeform();
CacheValidFlag();
m_TransformsHash = 0;
}
}
internal Sprite sprite => spriteRenderer.sprite;
internal SpriteRenderer spriteRenderer => m_SpriteRenderer;
/// <summary>
/// Returns the Transform Components that is used for deformation
/// </summary>
/// <returns>An array of Transform Components</returns>
public Transform[] boneTransforms
{
get { return m_BoneTransforms; }
internal set
{
m_BoneTransforms = value;
CacheValidFlag();
OnBoneTransformChanged();
}
}
/// <summary>
/// Returns the Transform Component that represents the root bone for deformation
/// </summary>
/// <returns>A Transform Component</returns>
public Transform rootBone
{
get { return m_RootBone; }
internal set
{
m_RootBone = value;
CacheValidFlag();
OnRootBoneTransformChanged();
}
}
internal Bounds bounds
{
get { return m_Bounds; }
set { m_Bounds = value; }
}
/// <summary>
/// Determines if the SpriteSkin executes even if the associated
/// SpriteRenderer has been culled from view.
/// </summary>
public bool alwaysUpdate
{
get => m_AlwaysUpdate;
set => m_AlwaysUpdate = value;
}
internal static bool GetSpriteBonesTransforms(SpriteBone[] spriteBones, Transform rootBone, Transform[] outTransform)
{
if(rootBone == null)
throw new ArgumentException("rootBone parameter cannot be null");
if(spriteBones == null)
throw new ArgumentException("spritebone parameter cannot be null");
if(outTransform == null)
throw new ArgumentException("outTransform parameter cannot be null");
if(spriteBones.Length != outTransform.Length)
throw new ArgumentException("spritebone and outTransform array length must be the same");
var boneObjects = rootBone.GetComponentsInChildren<Bone>();
if (boneObjects != null && boneObjects.Length >= spriteBones.Length)
{
int i = 0;
for (; i < spriteBones.Length; ++i)
{
var boneHash = spriteBones[i].guid;
var boneTransform = Array.Find(boneObjects, x => (x.guid == boneHash));
if (boneTransform == null)
break;
outTransform[i] = boneTransform.transform;
}
if(i >= spriteBones.Length)
return true;
}
// If unable to successfuly map via guid, fall back to path
return GetSpriteBonesTranformFromPath(spriteBones, rootBone, outTransform);
}
static bool GetSpriteBonesTranformFromPath(SpriteBone[] spriteBones, Transform rootBone, Transform[] outNewBoneTransform)
{
var bonePath = new string[spriteBones.Length];
for (int i = 0; i < spriteBones.Length; ++i)
{
if (bonePath[i] == null)
CalculateBoneTransformsPath(i, spriteBones, bonePath);
if (rootBone.name == spriteBones[i].name)
outNewBoneTransform[i] = rootBone;
else
{
var bone = rootBone.Find(bonePath[i]);
if (bone == null)
return false;
outNewBoneTransform[i] = bone;
}
}
return true;
}
private static void CalculateBoneTransformsPath(int index, SpriteBone[] spriteBones, string[] paths)
{
var spriteBone = spriteBones[index];
var parentId = spriteBone.parentId;
var bonePath = spriteBone.name;
if (parentId != -1 && spriteBones[parentId].parentId != -1)
{
if (paths[parentId] == null)
CalculateBoneTransformsPath(spriteBone.parentId, spriteBones, paths);
paths[index] = string.Format("{0}/{1}", paths[parentId], bonePath);
}
else
paths[index] = bonePath;
}
internal bool isValid
{
get { return this.Validate() == SpriteSkinValidationResult.Ready; }
}
void OnDestroy()
{
DeactivateSkinning();
}
internal void DeactivateSkinning()
{
var sprite = spriteRenderer.sprite;
if (sprite != null)
InternalEngineBridge.SetLocalAABB(spriteRenderer, sprite.bounds);
SpriteRendererDataAccessExtensions.DeactivateDeformableBuffer(spriteRenderer);
}
internal void ResetSprite()
{
m_CurrentDeformSprite = 0;
CacheValidFlag();
}
public void OnBeforeSerialize()
{
OnBeforeSerializeBatch();
}
public void OnAfterDeserialize()
{
OnAfterSerializeBatch();
}
}
}

View File

@@ -0,0 +1,266 @@
#if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST
#define ENABLE_SPRITESKIN_COMPOSITE
#endif
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.U2D.Animation
{
public sealed partial class SpriteSkin : MonoBehaviour
#if ENABLE_SPRITESKIN_COMPOSITE
{
int m_TransformId;
NativeArray<int> m_BoneTransformId;
int m_RootBoneTransformId;
NativeCustomSlice<Vector2> m_SpriteUVs;
NativeCustomSlice<Vector3> m_SpriteVertices;
NativeCustomSlice<Vector4> m_SpriteTangents;
NativeCustomSlice<BoneWeight> m_SpriteBoneWeights;
NativeCustomSlice<Matrix4x4> m_SpriteBindPoses;
NativeCustomSlice<int> m_BoneTransformIdNativeSlice;
bool m_SpriteHasTangents;
int m_SpriteVertexStreamSize;
int m_SpriteVertexCount;
int m_SpriteTangentVertexOffset;
int m_DataIndex = -1;
bool m_BoneCacheUpdateToDate = false;
void OnEnableBatch()
{
m_TransformId = gameObject.transform.GetInstanceID();
UpdateSpriteDeform();
if (m_UseBatching && m_BatchSkinning == false)
{
CacheBoneTransformIds(true);
SpriteSkinComposite.instance.AddSpriteSkin(this);
m_BatchSkinning = true;
}
else
SpriteSkinComposite.instance.AddSpriteSkinForLateUpdate(this);
}
void OnResetBatch()
{
if (m_UseBatching)
{
CacheBoneTransformIds(true);
SpriteSkinComposite.instance.CopyToSpriteSkinData(this);
}
}
void OnDisableBatch()
{
RemoveTransformFromSpriteSkinComposite();
SpriteSkinComposite.instance.RemoveSpriteSkin(this);
SpriteSkinComposite.instance.RemoveSpriteSkinForLateUpdate(this);
m_BatchSkinning = false;
}
internal void UpdateSpriteDeform()
{
if (sprite == null)
{
m_SpriteUVs = NativeCustomSlice<Vector2>.Default();
m_SpriteVertices = NativeCustomSlice<Vector3>.Default();
m_SpriteTangents = NativeCustomSlice<Vector4>.Default();
m_SpriteBoneWeights = NativeCustomSlice<BoneWeight>.Default();
m_SpriteBindPoses = NativeCustomSlice<Matrix4x4>.Default();
m_SpriteHasTangents = false;
m_SpriteVertexStreamSize = 0;
m_SpriteVertexCount = 0;
m_SpriteTangentVertexOffset = 0;
}
else
{
m_SpriteUVs = new NativeCustomSlice<Vector2>(sprite.GetVertexAttribute<Vector2>(UnityEngine.Rendering.VertexAttribute.TexCoord0));
m_SpriteVertices = new NativeCustomSlice<Vector3>(sprite.GetVertexAttribute<Vector3>(UnityEngine.Rendering.VertexAttribute.Position));
m_SpriteTangents = new NativeCustomSlice<Vector4>(sprite.GetVertexAttribute<Vector4>(UnityEngine.Rendering.VertexAttribute.Tangent));
m_SpriteBoneWeights = new NativeCustomSlice<BoneWeight>(sprite.GetVertexAttribute<BoneWeight>(UnityEngine.Rendering.VertexAttribute.BlendWeight));
m_SpriteBindPoses = new NativeCustomSlice<Matrix4x4>(sprite.GetBindPoses());
m_SpriteHasTangents = sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent);
m_SpriteVertexStreamSize = sprite.GetVertexStreamSize();
m_SpriteVertexCount = sprite.GetVertexCount();
m_SpriteTangentVertexOffset = sprite.GetVertexStreamOffset(Rendering.VertexAttribute.Tangent);
}
SpriteSkinComposite.instance.CopyToSpriteSkinData(this);
}
void CacheBoneTransformIds(bool forceUpdate = false)
{
if (!m_BoneCacheUpdateToDate || forceUpdate)
{
SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId);
if (rootBone != null)
{
m_RootBoneTransformId = rootBone.GetInstanceID();
if (this.enabled)
SpriteSkinComposite.instance.AddSpriteSkinRootBoneTransform(this);
}
else
m_RootBoneTransformId = 0;
if (boneTransforms != null)
{
int boneCount = 0;
for (int i = 0; i < boneTransforms.Length; ++i)
{
if (boneTransforms[i] != null)
++boneCount;
}
if (m_BoneTransformId.IsCreated)
{
for (int i = 0; i < m_BoneTransformId.Length; ++i)
SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]);
NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, boneCount);
}
else
{
m_BoneTransformId = new NativeArray<int>(boneCount, Allocator.Persistent);
}
m_BoneTransformIdNativeSlice = new NativeCustomSlice<int>(m_BoneTransformId);
for (int i = 0, j = 0; i < boneTransforms.Length; ++i)
{
if (boneTransforms[i] != null)
{
m_BoneTransformId[j] = boneTransforms[i].GetInstanceID();
++j;
}
}
if (this.enabled)
{
SpriteSkinComposite.instance.AddSpriteSkinBoneTransform(this);
}
}
else
{
if (m_BoneTransformId.IsCreated)
NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, 0);
else
m_BoneTransformId = new NativeArray<int>(0, Allocator.Persistent);
}
CacheValidFlag();
m_BoneCacheUpdateToDate = true;
SpriteSkinComposite.instance.CopyToSpriteSkinData(this);
}
}
void UseBatchingBatch()
{
if (!this.enabled)
return;
if (m_UseBatching)
{
m_BatchSkinning = true;
CacheBoneTransformIds();
SpriteSkinComposite.instance.AddSpriteSkin(this);
SpriteSkinComposite.instance.RemoveSpriteSkinForLateUpdate(this);
}
else
{
SpriteSkinComposite.instance.RemoveSpriteSkin(this);
SpriteSkinComposite.instance.AddSpriteSkinForLateUpdate(this);
RemoveTransformFromSpriteSkinComposite();
m_BatchSkinning = false;
}
}
void RemoveTransformFromSpriteSkinComposite()
{
if (m_BoneTransformId.IsCreated)
{
for (int i = 0; i < m_BoneTransformId.Length; ++i)
SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]);
m_BoneTransformId.Dispose();
}
SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId);
m_RootBoneTransformId = -1;
m_BoneCacheUpdateToDate = false;
}
internal void CopyToSpriteSkinData(ref SpriteSkinData data, int spriteSkinIndex)
{
CacheBoneTransformIds();
CacheCurrentSprite(m_AutoRebind);
data.vertices = m_SpriteVertices;
data.boneWeights = m_SpriteBoneWeights;
data.bindPoses = m_SpriteBindPoses;
data.tangents = m_SpriteTangents;
data.hasTangents = m_SpriteHasTangents;
data.spriteVertexStreamSize = m_SpriteVertexStreamSize;
data.spriteVertexCount = m_SpriteVertexCount;
data.tangentVertexOffset = m_SpriteTangentVertexOffset;
data.transformId = m_TransformId;
data.boneTransformId = m_BoneTransformIdNativeSlice;
m_DataIndex = spriteSkinIndex;
}
internal bool NeedUpdateCompositeCache()
{
unsafe
{
var iptr = new IntPtr(sprite.GetVertexAttribute<Vector2>(UnityEngine.Rendering.VertexAttribute.TexCoord0).GetUnsafeReadOnlyPtr());
var rs = m_SpriteUVs.data != iptr;
if (rs)
{
UpdateSpriteDeform();
}
return rs;
}
}
internal bool BatchValidate()
{
CacheBoneTransformIds();
CacheCurrentSprite(m_AutoRebind);
return (m_IsValid && spriteRenderer.enabled && (alwaysUpdate || spriteRenderer.isVisible));
}
void OnBoneTransformChanged()
{
if (this.enabled)
{
CacheBoneTransformIds(true);
}
}
void OnRootBoneTransformChanged()
{
if (this.enabled)
{
CacheBoneTransformIds(true);
}
}
void OnBeforeSerializeBatch()
{}
void OnAfterSerializeBatch()
{
#if UNITY_EDITOR
m_BoneCacheUpdateToDate = false;
#endif
}
}
#else
{
void OnEnableBatch(){}
internal void UpdateSpriteDeform(){}
void OnResetBatch(){}
void UseBatchingBatch(){}
void OnDisableBatch(){}
void OnBoneTransformChanged(){}
void OnRootBoneTransformChanged(){}
void OnBeforeSerializeBatch(){}
void OnAfterSerializeBatch(){}
}
#endif
}

View File

@@ -0,0 +1,754 @@
#if ENABLE_ANIMATION_COLLECTION
using System;
using Unity.Mathematics;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.U2D.Common;
using UnityEngine.Profiling;
using Unity.Burst;
using UnityEngine.Assertions;
namespace UnityEngine.U2D.Animation
{
internal struct PerSkinJobData
{
public int deformVerticesStartPos;
public int2 bindPosesIndex;
public int2 verticesIndex;
}
internal struct SpriteSkinData
{
public NativeCustomSlice<Vector3> vertices;
public NativeCustomSlice<BoneWeight> boneWeights;
public NativeCustomSlice<Matrix4x4> bindPoses;
public NativeCustomSlice<Vector4> tangents;
public bool hasTangents;
public int spriteVertexStreamSize;
public int spriteVertexCount;
public int tangentVertexOffset;
public int deformVerticesStartPos;
public int transformId;
public NativeCustomSlice<int> boneTransformId;
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct PrepareDeformJob :IJob
{
[ReadOnly]
public NativeArray<PerSkinJobData> perSkinJobData;
[ReadOnly]
public int batchDataSize;
[WriteOnly]
public NativeArray<int2> boneLookupData;
[WriteOnly]
public NativeArray<int2> vertexLookupData;
public void Execute()
{
for (int i = 0; i < batchDataSize; ++i)
{
var jobData = perSkinJobData[i];
for (int k = 0, j = jobData.bindPosesIndex.x; j < jobData.bindPosesIndex.y; ++j, ++k)
{
boneLookupData[j] = new int2(i, k);
}
for (int k = 0, j = jobData.verticesIndex.x; j < jobData.verticesIndex.y; ++j, ++k)
{
vertexLookupData[j] = new int2(i, k);
}
}
}
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct BoneDeformBatchedJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<float4x4> boneTransform;
[ReadOnly]
public NativeArray<float4x4> rootTransform;
[ReadOnly]
public NativeArray<int2> boneLookupData;
[ReadOnly]
public NativeArray<SpriteSkinData> spriteSkinData;
[ReadOnly]
public NativeHashMap<int, TransformAccessJob.TransformData> rootTransformIndex;
[ReadOnly]
public NativeHashMap<int, TransformAccessJob.TransformData> boneTransformIndex;
[WriteOnly]
public NativeArray<float4x4> finalBoneTransforms;
public void Execute(int i)
{
int x = boneLookupData[i].x;
int y = boneLookupData[i].y;
var ssd = spriteSkinData[x];
var v = ssd.boneTransformId[y];
var index = boneTransformIndex[v].transformIndex;
if (index < 0)
return;
var aa = boneTransform[index];
var bb = ssd.bindPoses[y];
var cc = rootTransformIndex[ssd.transformId].transformIndex;
finalBoneTransforms[i] = math.mul(rootTransform[cc], math.mul(aa, bb));
}
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct SkinDeformBatchedJob : IJobParallelFor
{
public NativeSlice<byte> vertices;
[ReadOnly]
public NativeArray<float4x4> finalBoneTransforms;
[ReadOnly]
public NativeArray<PerSkinJobData> perSkinJobData;
[ReadOnly]
public NativeArray<SpriteSkinData> spriteSkinData;
[ReadOnly]
public NativeArray<int2> vertexLookupData;
public unsafe void Execute(int i)
{
int j = vertexLookupData[i].x;
int k = vertexLookupData[i].y;
PerSkinJobData perSkinData = perSkinJobData[j];
var spriteSkin = spriteSkinData[j];
float3 srcVertex = spriteSkin.vertices[k];
float4 tangents = spriteSkin.tangents[k];
var influence = spriteSkin.boneWeights[k];
int bone0 = influence.boneIndex0 + perSkinData.bindPosesIndex.x;
int bone1 = influence.boneIndex1 + perSkinData.bindPosesIndex.x;
int bone2 = influence.boneIndex2 + perSkinData.bindPosesIndex.x;
int bone3 = influence.boneIndex3 + perSkinData.bindPosesIndex.x;
byte* deformedPosOffset = (byte*)vertices.GetUnsafePtr();
byte* deformedPosStart = deformedPosOffset + spriteSkin.deformVerticesStartPos;
NativeSlice<float3> deformableVerticesFloat3 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float3>(deformedPosStart, spriteSkin.spriteVertexStreamSize, spriteSkin.spriteVertexCount);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref deformableVerticesFloat3, NativeSliceUnsafeUtility.GetAtomicSafetyHandle(vertices));
#endif
if (spriteSkin.hasTangents)
{
byte* deformedTanOffset = deformedPosStart + spriteSkin.tangentVertexOffset;
var deformableTangentsFloat4 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float4>(deformedTanOffset, spriteSkin.spriteVertexStreamSize, spriteSkin.spriteVertexCount);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref deformableTangentsFloat4, NativeSliceUnsafeUtility.GetAtomicSafetyHandle(vertices));
#endif
var tangent = new float4(tangents.xyz, 0.0f);
tangent =
math.mul(finalBoneTransforms[bone0], tangent) * influence.weight0 +
math.mul(finalBoneTransforms[bone1], tangent) * influence.weight1 +
math.mul(finalBoneTransforms[bone2], tangent) * influence.weight2 +
math.mul(finalBoneTransforms[bone3], tangent) * influence.weight3;
deformableTangentsFloat4[k] = new float4(math.normalize(tangent.xyz), tangents.w);
}
deformableVerticesFloat3[k] =
math.transform(finalBoneTransforms[bone0], srcVertex) * influence.weight0 +
math.transform(finalBoneTransforms[bone1], srcVertex) * influence.weight1 +
math.transform(finalBoneTransforms[bone2], srcVertex) * influence.weight2 +
math.transform(finalBoneTransforms[bone3], srcVertex) * influence.weight3;
}
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct CalculateSpriteSkinAABBJob : IJobParallelFor
{
public NativeSlice<byte> vertices;
[ReadOnly]
public NativeArray<bool> isSpriteSkinValidForDeformArray;
[ReadOnly]
public NativeArray<SpriteSkinData> spriteSkinData;
[WriteOnly]
public NativeArray<Bounds> bounds;
public unsafe void Execute(int i)
{
if (!isSpriteSkinValidForDeformArray[i])
return;
var spriteSkin = spriteSkinData[i];
byte* deformedPosOffset = (byte*)vertices.GetUnsafePtr();
NativeSlice<float3> deformableVerticesFloat3 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float3>(deformedPosOffset + spriteSkin.deformVerticesStartPos, spriteSkin.spriteVertexStreamSize, spriteSkin.spriteVertexCount);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref deformableVerticesFloat3, NativeSliceUnsafeUtility.GetAtomicSafetyHandle(vertices));
#endif
bounds[i] = SpriteSkinUtility.CalculateSpriteSkinBounds(deformableVerticesFloat3);
}
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct FillPerSkinJobSingleThread : IJob
{
public PerSkinJobData combinedSkinBatch;
[ReadOnly]
public NativeArray<bool> isSpriteSkinValidForDeformArray;
public NativeArray<SpriteSkinData> spriteSkinDataArray;
public NativeArray<PerSkinJobData> perSkinJobDataArray;
public NativeArray<PerSkinJobData> combinedSkinBatchArray;
public void Execute()
{
var startIndex = 0;
var endIndex = spriteSkinDataArray.Length;
for (int index = startIndex; index < endIndex; ++index)
{
var spriteSkinData = spriteSkinDataArray[index];
spriteSkinData.deformVerticesStartPos = -1;
var vertexBufferSize = 0;
var vertexCount = 0;
var bindPoseCount = 0;
if (isSpriteSkinValidForDeformArray[index])
{
spriteSkinData.deformVerticesStartPos = combinedSkinBatch.deformVerticesStartPos;
vertexBufferSize = spriteSkinData.spriteVertexCount * spriteSkinData.spriteVertexStreamSize;
vertexCount = spriteSkinData.spriteVertexCount;
bindPoseCount = spriteSkinData.bindPoses.Length;
}
combinedSkinBatch.verticesIndex.x = combinedSkinBatch.verticesIndex.y;
combinedSkinBatch.verticesIndex.y = combinedSkinBatch.verticesIndex.x + vertexCount;
combinedSkinBatch.bindPosesIndex.x = combinedSkinBatch.bindPosesIndex.y;
combinedSkinBatch.bindPosesIndex.y = combinedSkinBatch.bindPosesIndex.x + bindPoseCount;
spriteSkinDataArray[index] = spriteSkinData;
perSkinJobDataArray[index] = combinedSkinBatch;
combinedSkinBatch.deformVerticesStartPos += vertexBufferSize;
}
combinedSkinBatchArray[0] = combinedSkinBatch;
}
}
#if ENABLE_ANIMATION_BURST
[BurstCompile]
#endif
internal struct CopySpriteRendererBuffersJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<bool> isSpriteSkinValidForDeformArray;
[ReadOnly]
public NativeArray<SpriteSkinData> spriteSkinData;
[ReadOnly, NativeDisableUnsafePtrRestriction]
public IntPtr ptrVertices;
[WriteOnly]
public NativeArray<IntPtr> buffers;
[WriteOnly]
public NativeArray<int> bufferSizes;
public void Execute(int i)
{
var skinData = spriteSkinData[i];
IntPtr startVertices = default(IntPtr);
var vertexBufferLength = 0;
if (isSpriteSkinValidForDeformArray[i])
{
startVertices = ptrVertices + skinData.deformVerticesStartPos;
vertexBufferLength = skinData.spriteVertexCount * skinData.spriteVertexStreamSize;
}
buffers[i] = startVertices;
bufferSizes[i] = vertexBufferLength;
}
}
internal class SpriteSkinComposite : ScriptableObject
{
static SpriteSkinComposite m_Instance;
public static SpriteSkinComposite instance
{
get
{
if (m_Instance == null)
{
var composite = Resources.FindObjectsOfTypeAll<SpriteSkinComposite>();
if (composite.Length > 0)
m_Instance = composite[0];
else
m_Instance = ScriptableObject.CreateInstance<SpriteSkinComposite>();
m_Instance.hideFlags = HideFlags.HideAndDontSave;
m_Instance.Init();
}
return m_Instance;
}
}
List<SpriteSkin> m_SpriteSkins = new List<SpriteSkin>();
List<SpriteSkin> m_SpriteSkinLateUpdate = new List<SpriteSkin>();
SpriteRenderer[] m_SpriteRenderers = new SpriteRenderer[0];
DeformVerticesBuffer m_DeformedVerticesBuffer;
NativeArray<float4x4> m_FinalBoneTransforms;
NativeArray<bool> m_IsSpriteSkinActiveForDeform;
NativeArray<SpriteSkinData> m_SpriteSkinData;
NativeArray<PerSkinJobData> m_PerSkinJobData;
NativeArray<Bounds> m_BoundsData;
NativeArray<IntPtr> m_Buffers;
NativeArray<int> m_BufferSizes;
NativeArray<int2> m_BoneLookupData;
NativeArray<int2> m_VertexLookupData;
NativeArray<PerSkinJobData> m_SkinBatchArray;
TransformAccessJob m_LocalToWorldTransformAccessJob;
TransformAccessJob m_WorldToLocalTransformAccessJob;
JobHandle m_BoundJobHandle;
JobHandle m_DeformJobHandle;
JobHandle m_CopyJobHandle;
[SerializeField]
GameObject m_Helper;
internal GameObject helperGameObject
{
get => m_Helper;
}
internal void RemoveTransformById(int transformId)
{
m_LocalToWorldTransformAccessJob.RemoveTransformById(transformId);
}
internal void AddSpriteSkinBoneTransform(SpriteSkin spriteSkin)
{
if (spriteSkin == null)
return;
if (spriteSkin.boneTransforms != null)
{
foreach (var t in spriteSkin.boneTransforms)
{
if(t != null)
m_LocalToWorldTransformAccessJob.AddTransform(t);
}
}
}
internal void AddSpriteSkinRootBoneTransform(SpriteSkin spriteSkin)
{
if (spriteSkin == null || spriteSkin.rootBone == null)
return;
m_LocalToWorldTransformAccessJob.AddTransform(spriteSkin.rootBone);
}
internal void AddSpriteSkin(SpriteSkin spriteSkin)
{
if (spriteSkin == null)
return;
bool added = m_SpriteSkins.Contains(spriteSkin);
Debug.Assert(!added, string.Format("SpriteSkin {0} is already added", spriteSkin.gameObject.name));
if (!added)
{
m_SpriteSkins.Add(spriteSkin);
var count = m_SpriteSkins.Count;
Array.Resize(ref m_SpriteRenderers, count);
m_SpriteRenderers[count - 1] = spriteSkin.spriteRenderer;
m_WorldToLocalTransformAccessJob.AddTransform(spriteSkin.transform);
if (m_IsSpriteSkinActiveForDeform.IsCreated)
{
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_IsSpriteSkinActiveForDeform, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_PerSkinJobData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_SpriteSkinData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BoundsData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_Buffers, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BufferSizes, count);
CopyToSpriteSkinData(count - 1);
}
}
}
internal void CopyToSpriteSkinData(SpriteSkin spriteSkin)
{
if (spriteSkin == null)
return;
int index = m_SpriteSkins.IndexOf(spriteSkin);
if (index < 0)
return;
CopyToSpriteSkinData(index);
}
private void CopyToSpriteSkinData(int index)
{
if (index < 0 || index >= m_SpriteSkins.Count || !m_SpriteSkinData.IsCreated)
return;
SpriteSkinData spriteSkinData = default(SpriteSkinData);
var spriteSkin = m_SpriteSkins[index];
spriteSkin.CopyToSpriteSkinData(ref spriteSkinData, index);
m_SpriteSkinData[index] = spriteSkinData;
m_SpriteRenderers[index] = spriteSkin.spriteRenderer;
}
internal void RemoveSpriteSkin(SpriteSkin spriteSkin)
{
int index = m_SpriteSkins.IndexOf(spriteSkin);
if (index < 0)
return;
// Check if it is not the last SpriteSkin
if (index < m_SpriteSkins.Count - 1)
{
m_SpriteSkins.RemoveAtSwapBack(index);
CopyToSpriteSkinData(index);
}
else
{
m_SpriteSkins.RemoveAt(index);
}
var count = m_SpriteSkins.Count;
Array.Resize(ref m_SpriteRenderers, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_IsSpriteSkinActiveForDeform, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_PerSkinJobData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_SpriteSkinData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BoundsData, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_Buffers, count);
NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BufferSizes, count);
m_WorldToLocalTransformAccessJob.RemoveTransform(spriteSkin.transform);
}
internal void AddSpriteSkinForLateUpdate(SpriteSkin spriteSkin)
{
if (spriteSkin == null)
return;
bool added = m_SpriteSkinLateUpdate.Contains(spriteSkin);
Debug.Assert( !added, string.Format("SpriteSkin {0} is already added for LateUpdate", spriteSkin.gameObject.name));
if (!added)
{
m_SpriteSkinLateUpdate.Add(spriteSkin);
}
}
internal void RemoveSpriteSkinForLateUpdate(SpriteSkin spriteSkin)
{
m_SpriteSkinLateUpdate.Remove(spriteSkin);
}
void Init()
{
if(m_LocalToWorldTransformAccessJob == null)
m_LocalToWorldTransformAccessJob = new TransformAccessJob();
if(m_WorldToLocalTransformAccessJob == null)
m_WorldToLocalTransformAccessJob = new TransformAccessJob();
}
internal void ResetComposite()
{
foreach (var spriteSkin in m_SpriteSkins)
spriteSkin.batchSkinning = false;
m_SpriteSkins.Clear();
m_LocalToWorldTransformAccessJob.Destroy();
m_WorldToLocalTransformAccessJob.Destroy();
m_LocalToWorldTransformAccessJob = new TransformAccessJob();
m_WorldToLocalTransformAccessJob = new TransformAccessJob();
}
public void OnEnable()
{
m_Instance = this;
if (m_Helper == null)
{
m_Helper = new GameObject("SpriteSkinManager");
m_Helper.hideFlags = HideFlags.HideAndDontSave;
m_Helper.AddComponent<SpriteSkinManager.SpriteSkinManagerInternal>();
#if !UNITY_EDITOR
GameObject.DontDestroyOnLoad(m_Helper);
#endif
}
m_DeformedVerticesBuffer = new DeformVerticesBuffer(DeformVerticesBuffer.k_DefaultBufferSize);
m_FinalBoneTransforms = new NativeArray<float4x4>(1, Allocator.Persistent);
m_BoneLookupData = new NativeArray<int2>(1, Allocator.Persistent);
m_VertexLookupData = new NativeArray<int2>(1, Allocator.Persistent);
m_SkinBatchArray = new NativeArray<PerSkinJobData>(1, Allocator.Persistent);
Init();
// Initialise all existing SpriteSkins as execution order is indeterminate
int count = m_SpriteSkins.Count;
if (count > 0)
{
m_IsSpriteSkinActiveForDeform = new NativeArray<bool>(count, Allocator.Persistent);
m_PerSkinJobData = new NativeArray<PerSkinJobData>(count, Allocator.Persistent);
m_SpriteSkinData = new NativeArray<SpriteSkinData>(count, Allocator.Persistent);
m_BoundsData = new NativeArray<Bounds>(count, Allocator.Persistent);
m_Buffers = new NativeArray<IntPtr>(count, Allocator.Persistent);
m_BufferSizes = new NativeArray<int>(count, Allocator.Persistent);
for (int i = 0; i < count; ++i)
{
var spriteSkin = m_SpriteSkins[i];
spriteSkin.batchSkinning = true;
CopyToSpriteSkinData(i);
}
}
else
{
m_IsSpriteSkinActiveForDeform = new NativeArray<bool>(1, Allocator.Persistent);
m_PerSkinJobData = new NativeArray<PerSkinJobData>(1, Allocator.Persistent);
m_SpriteSkinData = new NativeArray<SpriteSkinData>(1, Allocator.Persistent);
m_BoundsData = new NativeArray<Bounds>(1, Allocator.Persistent);
m_Buffers = new NativeArray<IntPtr>(1, Allocator.Persistent);
m_BufferSizes = new NativeArray<int>(1, Allocator.Persistent);
}
}
private void OnDisable()
{
m_DeformJobHandle.Complete();
m_BoundJobHandle.Complete();
m_CopyJobHandle.Complete();
foreach (var spriteSkin in m_SpriteSkins)
spriteSkin.batchSkinning = false;
m_SpriteSkins.Clear();
m_SpriteRenderers = new SpriteRenderer[0];
m_DeformedVerticesBuffer.Dispose();
m_IsSpriteSkinActiveForDeform.DisposeIfCreated();
m_PerSkinJobData.DisposeIfCreated();
m_SpriteSkinData.DisposeIfCreated();
m_Buffers.DisposeIfCreated();
m_BufferSizes.DisposeIfCreated();
m_BoneLookupData.DisposeIfCreated();
m_VertexLookupData.DisposeIfCreated();
m_SkinBatchArray.DisposeIfCreated();
m_FinalBoneTransforms.DisposeIfCreated();
m_BoundsData.DisposeIfCreated();
if (m_Helper != null)
GameObject.DestroyImmediate(m_Helper);
m_LocalToWorldTransformAccessJob.Destroy();
m_WorldToLocalTransformAccessJob.Destroy();
}
internal unsafe void LateUpdate()
{
foreach (var ss in m_SpriteSkinLateUpdate)
{
if(ss != null)
ss.OnLateUpdate();
}
var count = m_SpriteSkins.Count;
if (count == 0)
return;
Profiler.BeginSample("SpriteSkinComposite.PrepareData");
Assert.AreEqual(m_IsSpriteSkinActiveForDeform.Length, count);
Assert.AreEqual(m_PerSkinJobData.Length, count);
Assert.AreEqual(m_SpriteSkinData.Length, count);
Assert.AreEqual(m_BoundsData.Length, count);
Assert.AreEqual(m_Buffers.Length, count);
Assert.AreEqual(m_BufferSizes.Length, count);
Assert.AreEqual(m_SpriteRenderers.Length, count);
Profiler.BeginSample("SpriteSkinComposite.ValidateSpriteSkinData");
for (int i = 0; i < m_SpriteSkins.Count; ++i)
{
var spriteSkin = m_SpriteSkins[i];
m_IsSpriteSkinActiveForDeform[i] = spriteSkin.BatchValidate();
if (m_IsSpriteSkinActiveForDeform[i] && spriteSkin.NeedUpdateCompositeCache())
{
CopyToSpriteSkinData(i);
}
}
Profiler.EndSample();
Profiler.BeginSample("SpriteSkinComposite.TransformAccessJob");
var localToWorldJobHandle = m_LocalToWorldTransformAccessJob.StartLocalToWorldJob();
var worldToLocalJobHandle = m_WorldToLocalTransformAccessJob.StartWorldToLocalJob();
Profiler.EndSample();
Profiler.BeginSample("SpriteSkinComposite.GetSpriteSkinBatchData");
NativeArrayHelpers.ResizeIfNeeded(ref m_SkinBatchArray, 1);
FillPerSkinJobSingleThread fillPerSkinJobSingleThread = new FillPerSkinJobSingleThread()
{
isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform,
combinedSkinBatchArray = m_SkinBatchArray,
spriteSkinDataArray = m_SpriteSkinData,
perSkinJobDataArray = m_PerSkinJobData,
};
fillPerSkinJobSingleThread.Run();
Profiler.EndSample();
Profiler.EndSample();
var skinBatch = m_SkinBatchArray[0];
int batchCount = m_SpriteSkinData.Length;
int vertexBufferSize = skinBatch.deformVerticesStartPos;
if (vertexBufferSize <= 0)
{
localToWorldJobHandle.Complete();
worldToLocalJobHandle.Complete();
return;
}
Profiler.BeginSample("SpriteSkinComposite.ResizeBuffers");
var deformVertices = m_DeformedVerticesBuffer.GetBuffer(vertexBufferSize);
NativeArrayHelpers.ResizeIfNeeded(ref m_FinalBoneTransforms, skinBatch.bindPosesIndex.y);
NativeArrayHelpers.ResizeIfNeeded(ref m_BoneLookupData, skinBatch.bindPosesIndex.y);
NativeArrayHelpers.ResizeIfNeeded(ref m_VertexLookupData, skinBatch.verticesIndex.y);
Profiler.EndSample();
Profiler.BeginSample("SpriteSkinComposite.Prepare");
PrepareDeformJob prepareJob = new PrepareDeformJob
{
batchDataSize = batchCount,
perSkinJobData = m_PerSkinJobData,
boneLookupData = m_BoneLookupData,
vertexLookupData = m_VertexLookupData
};
var jobHandle = prepareJob.Schedule();
Profiler.EndSample();
Profiler.BeginSample("SpriteSkinComposite.ScheduleJobs");
BoneDeformBatchedJob boneJobBatched = new BoneDeformBatchedJob()
{
boneTransform = m_LocalToWorldTransformAccessJob.transformMatrix,
rootTransform = m_WorldToLocalTransformAccessJob.transformMatrix,
spriteSkinData = m_SpriteSkinData,
boneLookupData = m_BoneLookupData,
finalBoneTransforms = m_FinalBoneTransforms,
rootTransformIndex = m_WorldToLocalTransformAccessJob.transformData,
boneTransformIndex = m_LocalToWorldTransformAccessJob.transformData
};
jobHandle = JobHandle.CombineDependencies(localToWorldJobHandle, worldToLocalJobHandle, jobHandle);
jobHandle = boneJobBatched.Schedule(skinBatch.bindPosesIndex.y, 8, jobHandle);
SkinDeformBatchedJob skinJobBatched = new SkinDeformBatchedJob()
{
vertices = deformVertices,
vertexLookupData = m_VertexLookupData,
spriteSkinData = m_SpriteSkinData,
perSkinJobData = m_PerSkinJobData,
finalBoneTransforms = m_FinalBoneTransforms,
};
m_DeformJobHandle = skinJobBatched.Schedule(skinBatch.verticesIndex.y, 16, jobHandle);
CopySpriteRendererBuffersJob copySpriteRendererBuffersJob = new CopySpriteRendererBuffersJob()
{
isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform,
spriteSkinData = m_SpriteSkinData,
ptrVertices = (IntPtr) NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(deformVertices),
buffers = m_Buffers,
bufferSizes = m_BufferSizes,
};
m_CopyJobHandle = copySpriteRendererBuffersJob.Schedule(batchCount, 16, jobHandle);
CalculateSpriteSkinAABBJob updateBoundJob = new CalculateSpriteSkinAABBJob
{
vertices = deformVertices,
isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform,
spriteSkinData = m_SpriteSkinData,
bounds = m_BoundsData,
};
m_BoundJobHandle = updateBoundJob.Schedule(batchCount, 4, m_DeformJobHandle);
Profiler.EndSample();
JobHandle.ScheduleBatchedJobs();
jobHandle = JobHandle.CombineDependencies(m_BoundJobHandle, m_CopyJobHandle);
jobHandle.Complete();
Profiler.BeginSample("SpriteSkinComposite.SetBatchDeformableBufferAndLocalAABB");
InternalEngineBridge.SetBatchDeformableBufferAndLocalAABBArray(m_SpriteRenderers, m_Buffers, m_BufferSizes, m_BoundsData);
Profiler.EndSample();
}
internal bool HasDeformableBufferForSprite(int dataIndex)
{
if (dataIndex < 0 && m_IsSpriteSkinActiveForDeform.Length >= dataIndex)
throw new InvalidOperationException("Invalid index for deformable buffer");
return m_IsSpriteSkinActiveForDeform[dataIndex];
}
internal unsafe NativeArray<byte> GetDeformableBufferForSprite(int dataIndex)
{
if (dataIndex < 0 && m_SpriteSkinData.Length >= dataIndex)
throw new InvalidOperationException("Invalid index for deformable buffer");
if (!m_DeformJobHandle.IsCompleted)
m_DeformJobHandle.Complete();
var skinData = m_SpriteSkinData[dataIndex];
if (skinData.deformVerticesStartPos < 0)
throw new InvalidOperationException("There are no currently deformed vertices.");
var vertexBufferLength = skinData.spriteVertexCount * skinData.spriteVertexStreamSize;
var deformVertices = m_DeformedVerticesBuffer.GetCurrentBuffer();
byte* ptrVertices = (byte*)deformVertices.GetUnsafeReadOnlyPtr();
ptrVertices += skinData.deformVerticesStartPos;
var buffer = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(ptrVertices, vertexBufferLength, Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref buffer, NativeArrayUnsafeUtility.GetAtomicSafetyHandle(deformVertices));
#endif
return buffer;
}
// Code for tests
#region Code For Tests
internal string GetDebugLog()
{
var log = "";
log = "====SpriteSkinLateUpdate===\n";
log += "Count: " + m_SpriteSkinLateUpdate.Count +"\n";
foreach (var ss in m_SpriteSkinLateUpdate)
{
log += ss == null ? "null" : ss.name;
log += "\n";
}
log += "\n";
log += "===SpriteSkinBatch===\n";
log += "Count: " + m_SpriteSkins.Count +"\n";
foreach (var ss in m_SpriteSkins)
{
log += ss == null ? "null" : ss.name;
log += "\n";
}
log += "===LocalToWorldTransformAccessJob===\n";
log += m_LocalToWorldTransformAccessJob.GetDebugLog();
log += "\n";
log += "===WorldToLocalTransformAccessJob===\n";
log += "\n";
log += m_WorldToLocalTransformAccessJob.GetDebugLog();
return log;
}
internal SpriteSkin[] GetSpriteSkins()
{
return m_SpriteSkins.ToArray();
}
internal TransformAccessJob GetWorldToLocalTransformAccessJob()
{
return m_WorldToLocalTransformAccessJob;
}
internal TransformAccessJob GetLocalToWorldTransformAccessJob()
{
return m_LocalToWorldTransformAccessJob;
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,23 @@
namespace UnityEngine.U2D.Animation
{
internal class SpriteSkinManager
{
// Doing this to hide it from user adding it from Inspector
[DefaultExecutionOrder(-1)]
[ExecuteInEditMode]
internal class SpriteSkinManagerInternal : MonoBehaviour
{
#if ENABLE_ANIMATION_COLLECTION
void LateUpdate()
{
if (SpriteSkinComposite.instance.helperGameObject != gameObject)
{
GameObject.DestroyImmediate(gameObject);
return;
}
SpriteSkinComposite.instance.LateUpdate();
}
#endif
}
}
}

View File

@@ -0,0 +1,392 @@
using System;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.U2D.Common;
namespace UnityEngine.U2D.Animation
{
internal enum SpriteSkinValidationResult
{
SpriteNotFound,
SpriteHasNoSkinningInformation,
SpriteHasNoWeights,
RootTransformNotFound,
InvalidTransformArray,
InvalidTransformArrayLength,
TransformArrayContainsNull,
RootNotFoundInTransformArray,
Ready
}
internal static class SpriteSkinUtility
{
internal static SpriteSkinValidationResult Validate(this SpriteSkin spriteSkin)
{
if (spriteSkin.spriteRenderer.sprite == null)
return SpriteSkinValidationResult.SpriteNotFound;
var bindPoses = spriteSkin.spriteRenderer.sprite.GetBindPoses();
if (bindPoses.Length == 0)
return SpriteSkinValidationResult.SpriteHasNoSkinningInformation;
if (spriteSkin.rootBone == null)
return SpriteSkinValidationResult.RootTransformNotFound;
if (spriteSkin.boneTransforms == null)
return SpriteSkinValidationResult.InvalidTransformArray;
if (bindPoses.Length != spriteSkin.boneTransforms.Length)
return SpriteSkinValidationResult.InvalidTransformArrayLength;
var rootFound = false;
foreach (var boneTransform in spriteSkin.boneTransforms)
{
if (boneTransform == null)
return SpriteSkinValidationResult.TransformArrayContainsNull;
if (boneTransform == spriteSkin.rootBone)
rootFound = true;
}
if (!rootFound)
return SpriteSkinValidationResult.RootNotFoundInTransformArray;
return SpriteSkinValidationResult.Ready;
}
internal static void CreateBoneHierarchy(this SpriteSkin spriteSkin)
{
if (spriteSkin.spriteRenderer.sprite == null)
throw new InvalidOperationException("SpriteRenderer has no Sprite set");
var spriteBones = spriteSkin.spriteRenderer.sprite.GetBones();
var transforms = new Transform[spriteBones.Length];
Transform root = null;
for (int i = 0; i < spriteBones.Length; ++i)
{
CreateGameObject(i, spriteBones, transforms, spriteSkin.transform);
if (spriteBones[i].parentId < 0 && root == null)
root = transforms[i];
}
spriteSkin.rootBone = root;
spriteSkin.boneTransforms = transforms;
}
internal static int GetVertexStreamSize(this Sprite sprite)
{
int vertexStreamSize = 12;
if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Normal))
vertexStreamSize = vertexStreamSize + 12;
if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent))
vertexStreamSize = vertexStreamSize + 16;
return vertexStreamSize;
}
internal static int GetVertexStreamOffset(this Sprite sprite, Rendering.VertexAttribute channel )
{
bool hasPosition = sprite.HasVertexAttribute(Rendering.VertexAttribute.Position);
bool hasNormals = sprite.HasVertexAttribute(Rendering.VertexAttribute.Normal);
bool hasTangents = sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent);
switch(channel)
{
case Rendering.VertexAttribute.Position:
return hasPosition ? 0 : -1;
case Rendering.VertexAttribute.Normal:
return hasNormals ? 12 : -1;
case Rendering.VertexAttribute.Tangent:
return hasTangents ? (hasNormals ? 24 : 12) : -1;
}
return -1;
}
private static void CreateGameObject(int index, SpriteBone[] spriteBones, Transform[] transforms, Transform root)
{
if (transforms[index] == null)
{
var spriteBone = spriteBones[index];
if (spriteBone.parentId >= 0)
CreateGameObject(spriteBone.parentId, spriteBones, transforms, root);
var go = new GameObject(spriteBone.name);
var transform = go.transform;
if (spriteBone.parentId >= 0)
transform.SetParent(transforms[spriteBone.parentId]);
else
transform.SetParent(root);
transform.localPosition = spriteBone.position;
transform.localRotation = spriteBone.rotation;
transform.localScale = Vector3.one;
transforms[index] = transform;
}
}
internal static void ResetBindPose(this SpriteSkin spriteSkin)
{
if (!spriteSkin.isValid)
throw new InvalidOperationException("SpriteSkin is not valid");
var spriteBones = spriteSkin.spriteRenderer.sprite.GetBones();
var boneTransforms = spriteSkin.boneTransforms;
for (int i = 0; i < boneTransforms.Length; ++i)
{
var boneTransform = boneTransforms[i];
var spriteBone = spriteBones[i];
if (spriteBone.parentId != -1)
{
boneTransform.localPosition = spriteBone.position;
boneTransform.localRotation = spriteBone.rotation;
boneTransform.localScale = Vector3.one;
}
}
}
private static int GetHash(Matrix4x4 matrix)
{
unsafe
{
uint* b = (uint*)&matrix;
{
var c = (char*)b;
return (int)math.hash(c, 16 * sizeof(float));
}
}
}
internal static int CalculateTransformHash(this SpriteSkin spriteSkin)
{
int bits = 0;
int boneTransformHash = GetHash(spriteSkin.transform.localToWorldMatrix) >> bits;
bits++;
foreach (var transform in spriteSkin.boneTransforms)
{
boneTransformHash ^= GetHash(transform.localToWorldMatrix) >> bits;
bits = (bits + 1) % 8;
}
return boneTransformHash;
}
internal unsafe static void Deform(Sprite sprite, Matrix4x4 rootInv, NativeSlice<Vector3> vertices, NativeSlice<Vector4> tangents, NativeSlice<BoneWeight> boneWeights, NativeArray<Matrix4x4> boneTransforms, NativeSlice<Matrix4x4> bindPoses, NativeArray<byte> deformableVertices)
{
var verticesFloat3 = vertices.SliceWithStride<float3>();
var tangentsFloat4 = tangents.SliceWithStride<float4>();
var bindPosesFloat4x4 = bindPoses.SliceWithStride<float4x4>();
var spriteVertexCount = sprite.GetVertexCount();
var spriteVertexStreamSize = sprite.GetVertexStreamSize();
var boneTransformsFloat4x4 = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<float4x4>(boneTransforms.GetUnsafePtr(), boneTransforms.Length, Allocator.None);
byte* deformedPosOffset = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(deformableVertices);
NativeSlice<float3> deformableVerticesFloat3 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float3>(deformedPosOffset, spriteVertexStreamSize, spriteVertexCount);
NativeSlice<float4> deformableTangentsFloat4 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float4>(deformedPosOffset, spriteVertexStreamSize, 1); // Just Dummy.
if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent))
{
byte* deformedTanOffset = deformedPosOffset + sprite.GetVertexStreamOffset(Rendering.VertexAttribute.Tangent);
deformableTangentsFloat4 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float4>(deformedTanOffset, spriteVertexStreamSize, spriteVertexCount);
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
var handle1 = CreateSafetyChecks<float4x4>(ref boneTransformsFloat4x4);
var handle2 = CreateSafetyChecks<float3>(ref deformableVerticesFloat3);
var handle3 = CreateSafetyChecks<float4>(ref deformableTangentsFloat4);
#endif
if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent))
Deform(rootInv, verticesFloat3, tangentsFloat4, boneWeights, boneTransformsFloat4x4, bindPosesFloat4x4, deformableVerticesFloat3, deformableTangentsFloat4);
else
Deform(rootInv, verticesFloat3, boneWeights, boneTransformsFloat4x4, bindPosesFloat4x4, deformableVerticesFloat3);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
DisposeSafetyChecks(handle1);
DisposeSafetyChecks(handle2);
DisposeSafetyChecks(handle3);
#endif
}
internal static void Deform(float4x4 rootInv, NativeSlice<float3> vertices, NativeSlice<BoneWeight> boneWeights, NativeArray<float4x4> boneTransforms, NativeSlice<float4x4> bindPoses, NativeSlice<float3> deformed)
{
if (boneTransforms.Length == 0)
return;
for (var i = 0; i < boneTransforms.Length; i++)
{
var bindPoseMat = bindPoses[i];
var boneTransformMat = boneTransforms[i];
boneTransforms[i] = math.mul(rootInv, math.mul(boneTransformMat, bindPoseMat));
}
for (var i = 0; i < vertices.Length; i++)
{
var bone0 = boneWeights[i].boneIndex0;
var bone1 = boneWeights[i].boneIndex1;
var bone2 = boneWeights[i].boneIndex2;
var bone3 = boneWeights[i].boneIndex3;
var vertex = vertices[i];
deformed[i] =
math.transform(boneTransforms[bone0], vertex) * boneWeights[i].weight0 +
math.transform(boneTransforms[bone1], vertex) * boneWeights[i].weight1 +
math.transform(boneTransforms[bone2], vertex) * boneWeights[i].weight2 +
math.transform(boneTransforms[bone3], vertex) * boneWeights[i].weight3;
}
}
internal static void Deform(float4x4 rootInv, NativeSlice<float3> vertices, NativeSlice<float4> tangents, NativeSlice<BoneWeight> boneWeights, NativeArray<float4x4> boneTransforms, NativeSlice<float4x4> bindPoses, NativeSlice<float3> deformed, NativeSlice<float4> deformedTangents)
{
if(boneTransforms.Length == 0)
return;
for (var i = 0; i < boneTransforms.Length; i++)
{
var bindPoseMat = bindPoses[i];
var boneTransformMat = boneTransforms[i];
boneTransforms[i] = math.mul(rootInv, math.mul(boneTransformMat, bindPoseMat));
}
for (var i = 0; i < vertices.Length; i++)
{
var bone0 = boneWeights[i].boneIndex0;
var bone1 = boneWeights[i].boneIndex1;
var bone2 = boneWeights[i].boneIndex2;
var bone3 = boneWeights[i].boneIndex3;
var vertex = vertices[i];
deformed[i] =
math.transform(boneTransforms[bone0], vertex) * boneWeights[i].weight0 +
math.transform(boneTransforms[bone1], vertex) * boneWeights[i].weight1 +
math.transform(boneTransforms[bone2], vertex) * boneWeights[i].weight2 +
math.transform(boneTransforms[bone3], vertex) * boneWeights[i].weight3;
var tangent = new float4(tangents[i].xyz, 0.0f);
tangent =
math.mul(boneTransforms[bone0], tangent) * boneWeights[i].weight0 +
math.mul(boneTransforms[bone1], tangent) * boneWeights[i].weight1 +
math.mul(boneTransforms[bone2], tangent) * boneWeights[i].weight2 +
math.mul(boneTransforms[bone3], tangent) * boneWeights[i].weight3;
deformedTangents[i] = new float4(math.normalize(tangent.xyz), tangents[i].w);
}
}
internal unsafe static void Deform(Sprite sprite, Matrix4x4 invRoot, Transform[] boneTransformsArray, ref NativeArray<byte> deformVertexData)
{
Debug.Assert(sprite != null);
Debug.Assert(sprite.GetVertexCount() == (deformVertexData.Length / sprite.GetVertexStreamSize()));
var vertices = sprite.GetVertexAttribute<Vector3>(UnityEngine.Rendering.VertexAttribute.Position);
var tangents = sprite.GetVertexAttribute<Vector4>(UnityEngine.Rendering.VertexAttribute.Tangent);
var boneWeights = sprite.GetVertexAttribute<BoneWeight>(UnityEngine.Rendering.VertexAttribute.BlendWeight);
var bindPoses = sprite.GetBindPoses();
Debug.Assert(bindPoses.Length == boneTransformsArray.Length);
Debug.Assert(boneWeights.Length == sprite.GetVertexCount());
var boneTransforms = new NativeArray<Matrix4x4>(boneTransformsArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < boneTransformsArray.Length; ++i)
boneTransforms[i] = boneTransformsArray[i].localToWorldMatrix;
Deform(sprite, invRoot, vertices, tangents, boneWeights, boneTransforms, bindPoses, deformVertexData);
boneTransforms.Dispose();
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
private static AtomicSafetyHandle CreateSafetyChecks<T>(ref NativeArray<T> array) where T : struct
{
var handle = AtomicSafetyHandle.Create();
AtomicSafetyHandle.SetAllowSecondaryVersionWriting(handle, true);
AtomicSafetyHandle.UseSecondaryVersion(ref handle);
NativeArrayUnsafeUtility.SetAtomicSafetyHandle<T>(ref array, handle);
return handle;
}
private static AtomicSafetyHandle CreateSafetyChecks<T>(ref NativeSlice<T> array) where T : struct
{
var handle = AtomicSafetyHandle.Create();
AtomicSafetyHandle.SetAllowSecondaryVersionWriting(handle, true);
AtomicSafetyHandle.UseSecondaryVersion(ref handle);
NativeSliceUnsafeUtility.SetAtomicSafetyHandle<T>(ref array, handle);
return handle;
}
private static void DisposeSafetyChecks(AtomicSafetyHandle handle)
{
AtomicSafetyHandle.Release(handle);
}
#endif
internal static void Bake(this SpriteSkin spriteSkin, ref NativeArray<byte> deformVertexData)
{
if (!spriteSkin.isValid)
throw new Exception("Bake error: invalid SpriteSkin");
var sprite = spriteSkin.spriteRenderer.sprite;
var boneTransformsArray = spriteSkin.boneTransforms;
Deform(sprite, Matrix4x4.identity, boneTransformsArray, ref deformVertexData);
}
internal unsafe static void CalculateBounds(this SpriteSkin spriteSkin)
{
Debug.Assert(spriteSkin.isValid);
var sprite = spriteSkin.sprite;
var deformVertexData = new NativeArray<byte>(sprite.GetVertexStreamSize() * sprite.GetVertexCount(), Allocator.Temp, NativeArrayOptions.UninitializedMemory);
void* dataPtr = NativeArrayUnsafeUtility.GetUnsafePtr(deformVertexData);
var deformedPosSlice = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<Vector3>(dataPtr, sprite.GetVertexStreamSize(), sprite.GetVertexCount());
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref deformedPosSlice, NativeArrayUnsafeUtility.GetAtomicSafetyHandle(deformVertexData));
#endif
spriteSkin.Bake(ref deformVertexData);
UpdateBounds(spriteSkin, deformVertexData);
deformVertexData.Dispose();
}
internal static Bounds CalculateSpriteSkinBounds(NativeSlice<float3> deformablePositions)
{
float3 min = deformablePositions[0];
float3 max = deformablePositions[0];
for (int j = 1; j < deformablePositions.Length; ++j)
{
min = math.min(min, deformablePositions[j]);
max = math.max(max, deformablePositions[j]);
}
float3 ext = (max - min) * 0.5F;
float3 ctr = min + ext;
Bounds bounds = new Bounds();
bounds.center = ctr;
bounds.extents = ext;
return bounds;
}
internal static unsafe void UpdateBounds(this SpriteSkin spriteSkin, NativeArray<byte> deformedVertices)
{
byte* deformedPosOffset = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(deformedVertices);
var spriteVertexCount = spriteSkin.sprite.GetVertexCount();
var spriteVertexStreamSize = spriteSkin.sprite.GetVertexStreamSize();
NativeSlice<float3> deformedPositions = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<float3>(deformedPosOffset, spriteVertexStreamSize, spriteVertexCount);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
var handle = CreateSafetyChecks<float3>(ref deformedPositions);
#endif
spriteSkin.bounds = CalculateSpriteSkinBounds(deformedPositions);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
DisposeSafetyChecks(handle);
#endif
InternalEngineBridge.SetLocalAABB(spriteSkin.spriteRenderer, spriteSkin.bounds);
}
}
}

View File

@@ -0,0 +1,263 @@
#if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine.Jobs;
using UnityEngine.Profiling;
using Unity.Burst;
namespace UnityEngine.U2D.Animation
{
internal class TransformAccessJob
{
internal struct TransformData
{
public int transformIndex;
public int refCount;
public TransformData(int index)
{
transformIndex = index;
refCount = 1;
}
}
Transform[] m_Transform;
TransformAccessArray m_TransformAccessArray;
NativeHashMap<int, TransformData> m_TransformData;
NativeArray<float4x4> m_TransformMatrix;
bool m_Dirty;
JobHandle m_JobHandle;
public TransformAccessJob()
{
m_TransformMatrix = new NativeArray<float4x4>(1, Allocator.Persistent);
m_TransformData = new NativeHashMap<int, TransformData>(1, Allocator.Persistent);
m_Transform = new Transform[0];
m_Dirty = false;
m_JobHandle = default(JobHandle);
}
public void Destroy()
{
m_JobHandle.Complete();
if (m_TransformMatrix.IsCreated)
m_TransformMatrix.Dispose();
if (m_TransformAccessArray.isCreated)
m_TransformAccessArray.Dispose();
if (m_TransformData.IsCreated)
m_TransformData.Dispose();
}
public NativeHashMap<int, TransformData> transformData
{
get { return m_TransformData; }
}
public NativeArray<float4x4> transformMatrix
{
get { return m_TransformMatrix; }
}
public void AddTransform(Transform t)
{
if (t == null || !m_TransformData.IsCreated)
return;
m_JobHandle.Complete();
int instanceId = t.GetInstanceID();
if (m_TransformData.ContainsKey(instanceId))
{
var transformData = m_TransformData[instanceId];
transformData.refCount += 1;
m_TransformData[instanceId] = transformData;
}
else
{
m_TransformData.TryAdd(instanceId, new TransformData(-1));
ArrayAdd(ref m_Transform, t);
m_Dirty = true;
}
}
static void ArrayAdd<T>(ref T[] array, T item)
{
int arraySize = array.Length;
Array.Resize(ref array, arraySize + 1);
array[arraySize] = item;
}
static void ArrayRemove<T>(ref T[] array, T item)
{
List<T> newList = new List<T>(array);
newList.Remove(item);
array = newList.ToArray();
}
public static void ArrayRemoveAt<T>(ref T[] array, int index)
{
List<T> list = new List<T>(array);
list.RemoveAt(index);
array = list.ToArray();
}
public void RemoveTransform(Transform t)
{
if (t == null || !m_TransformData.IsCreated)
return;
m_JobHandle.Complete();
int instanceId = t.GetInstanceID();
if (m_TransformData.ContainsKey(instanceId))
{
var transformData = m_TransformData[instanceId];
if (transformData.refCount == 1)
{
m_TransformData.Remove(instanceId);
ArrayRemove(ref m_Transform, t);
m_Dirty = true;
}
else
{
transformData.refCount -= 1;
m_TransformData[instanceId] = transformData;
}
}
}
void UpdateTransformIndex()
{
if (!m_Dirty)
return;
m_Dirty = false;
Profiler.BeginSample("UpdateTransformIndex");
NativeArrayHelpers.ResizeIfNeeded(ref m_TransformMatrix, m_Transform.Length);
if (!m_TransformAccessArray.isCreated)
TransformAccessArray.Allocate(m_Transform.Length, -1, out m_TransformAccessArray);
else if (m_TransformAccessArray.capacity != m_Transform.Length)
m_TransformAccessArray.capacity = m_Transform.Length;
m_TransformAccessArray.SetTransforms(m_Transform);
for (int i = 0; i < m_Transform.Length; ++i)
{
if (m_Transform[i] != null)
{
var instanceId = m_Transform[i].GetInstanceID();
var transformData = m_TransformData[instanceId];
transformData.transformIndex = i;
m_TransformData[instanceId] = transformData;
}
}
Profiler.EndSample();
}
public JobHandle StartLocalToWorldJob()
{
if (m_Transform.Length > 0)
{
m_JobHandle.Complete();
UpdateTransformIndex();
Profiler.BeginSample("StartLocalToWorldJob");
var job = new LocalToWorldTransformAccessJob()
{
outMatrix = transformMatrix,
};
m_JobHandle = job.Schedule(m_TransformAccessArray);
Profiler.EndSample();
return m_JobHandle;
}
return default(JobHandle);
}
public JobHandle StartWorldToLocalJob()
{
if (m_Transform.Length > 0)
{
m_JobHandle.Complete();
UpdateTransformIndex();
Profiler.BeginSample("StartWorldToLocalJob");
var job = new WorldToLocalTransformAccessJob()
{
outMatrix = transformMatrix,
};
m_JobHandle = job.Schedule(m_TransformAccessArray);
Profiler.EndSample();
return m_JobHandle;
}
return default(JobHandle);
}
internal string GetDebugLog()
{
var log = "";
log += "TransformData Count: " + m_TransformData.Count() + "\n";
log += "Transform Count: " + m_Transform.Length + "\n";
foreach (var ss in m_Transform)
{
log += ss == null ? "null" : ss.name + " " + ss.GetInstanceID();
log += "\n";
if (ss != null)
{
log += "RefCount: " + m_TransformData[ss.GetInstanceID()].refCount + "\n";
}
log += "\n";
}
return log;
}
internal void RemoveTransformById(int transformId)
{
if (!m_TransformData.IsCreated)
return;
m_JobHandle.Complete();
if (m_TransformData.ContainsKey(transformId))
{
var transformData = m_TransformData[transformId];
if (transformData.refCount == 1)
{
m_TransformData.Remove(transformId);
var index = Array.FindIndex(m_Transform, t => t.GetInstanceID() == transformId);
if (index >= 0)
{
ArrayRemoveAt(ref m_Transform, index);
}
m_Dirty = true;
}
else
{
transformData.refCount -= 1;
m_TransformData[transformId] = transformData;
}
}
}
}
[BurstCompile]
internal struct LocalToWorldTransformAccessJob : IJobParallelForTransform
{
[WriteOnly]
public NativeArray<float4x4> outMatrix;
public void Execute(int index, TransformAccess transform)
{
outMatrix[index] = transform.localToWorldMatrix;
}
}
[BurstCompile]
internal struct WorldToLocalTransformAccessJob : IJobParallelForTransform
{
[WriteOnly]
public NativeArray<float4x4> outMatrix;
public void Execute(int index, TransformAccess transform)
{
outMatrix[index] = transform.worldToLocalMatrix;
}
}
}
#endif

View File

@@ -0,0 +1,248 @@
// -----------------------------------------------------------------------
// <copyright file="Behavior.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Controls the behavior of the meshing software.
/// </summary>
class Behavior
{
bool poly = false;
bool quality = false;
bool varArea = false;
bool convex = false;
bool jettison = false;
bool boundaryMarkers = true;
bool noHoles = false;
bool conformDel = false;
Func<ITriangle, double, bool> usertest;
int noBisect = 0;
double minAngle = 0.0;
double maxAngle = 0.0;
double maxArea = -1.0;
internal bool fixedArea = false;
internal bool useSegments = true;
internal bool useRegions = false;
internal double goodAngle = 0.0;
internal double maxGoodAngle = 0.0;
internal double offconstant = 0.0;
/// <summary>
/// Creates an instance of the Behavior class.
/// </summary>
public Behavior(bool quality = false, double minAngle = 20.0)
{
if (quality)
{
this.quality = true;
this.minAngle = minAngle;
Update();
}
}
/// <summary>
/// Update quality options dependencies.
/// </summary>
private void Update()
{
this.quality = true;
if (this.minAngle < 0 || this.minAngle > 60)
{
this.minAngle = 0;
this.quality = false;
Log.Instance.Warning("Invalid quality option (minimum angle).", "Mesh.Behavior");
}
if ((this.maxAngle != 0.0) && (this.maxAngle < 60 || this.maxAngle > 180))
{
this.maxAngle = 0;
this.quality = false;
Log.Instance.Warning("Invalid quality option (maximum angle).", "Mesh.Behavior");
}
this.useSegments = this.Poly || this.Quality || this.Convex;
this.goodAngle = Math.Cos(this.MinAngle * Math.PI / 180.0);
this.maxGoodAngle = Math.Cos(this.MaxAngle * Math.PI / 180.0);
if (this.goodAngle == 1.0)
{
this.offconstant = 0.0;
}
else
{
this.offconstant = 0.475 * Math.Sqrt((1.0 + this.goodAngle) / (1.0 - this.goodAngle));
}
this.goodAngle *= this.goodAngle;
}
#region Static properties
/// <summary>
/// No exact arithmetic.
/// </summary>
internal static bool NoExact { get; set; }
#endregion
#region Public properties
/// <summary>
/// Quality mesh generation.
/// </summary>
public bool Quality
{
get { return quality; }
set
{
quality = value;
if (quality)
{
Update();
}
}
}
/// <summary>
/// Minimum angle constraint.
/// </summary>
public double MinAngle
{
get { return minAngle; }
set { minAngle = value; Update(); }
}
/// <summary>
/// Maximum angle constraint.
/// </summary>
public double MaxAngle
{
get { return maxAngle; }
set { maxAngle = value; Update(); }
}
/// <summary>
/// Maximum area constraint.
/// </summary>
public double MaxArea
{
get { return maxArea; }
set
{
maxArea = value;
fixedArea = value > 0.0;
}
}
/// <summary>
/// Apply a maximum triangle area constraint.
/// </summary>
public bool VarArea
{
get { return varArea; }
set { varArea = value; }
}
/// <summary>
/// Input is a Planar Straight Line Graph.
/// </summary>
public bool Poly
{
get { return poly; }
set { poly = value; }
}
/// <summary>
/// Apply a user-defined triangle constraint.
/// </summary>
public Func<ITriangle, double, bool> UserTest
{
get { return usertest; }
set { usertest = value; }
}
/// <summary>
/// Enclose the convex hull with segments.
/// </summary>
public bool Convex
{
get { return convex; }
set { convex = value; }
}
/// <summary>
/// Conforming Delaunay (all triangles are truly Delaunay).
/// </summary>
public bool ConformingDelaunay
{
get { return conformDel; }
set { conformDel = value; }
}
/// <summary>
/// Suppresses boundary segment splitting.
/// </summary>
/// <remarks>
/// 0 = split segments
/// 1 = no new vertices on the boundary
/// 2 = prevent all segment splitting, including internal boundaries
/// </remarks>
public int NoBisect
{
get { return noBisect; }
set
{
noBisect = value;
if (noBisect < 0 || noBisect > 2)
{
noBisect = 0;
}
}
}
/// <summary>
/// Compute boundary information.
/// </summary>
public bool UseBoundaryMarkers
{
get { return boundaryMarkers; }
set { boundaryMarkers = value; }
}
/// <summary>
/// Ignores holes in polygons.
/// </summary>
public bool NoHoles
{
get { return noHoles; }
set { noHoles = value; }
}
/// <summary>
/// Jettison unused vertices from output.
/// </summary>
public bool Jettison
{
get { return jettison; }
set { jettison = value; }
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="Configuration.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System;
using Animation.TriangleNet.Meshing;
using Animation.TriangleNet.Meshing.Algorithm;
/// <summary>
/// Configure advanced aspects of the library.
/// </summary>
internal class Configuration
{
public Configuration()
: this(() => RobustPredicates.Default, () => new TrianglePool())
{
}
public Configuration(Func<IPredicates> predicates)
: this(predicates, () => new TrianglePool())
{
}
public Configuration(Func<IPredicates> predicates, Func<TrianglePool> trianglePool)
{
Predicates = predicates;
TrianglePool = trianglePool;
}
/// <summary>
/// Gets or sets the factory method for the <see cref="IPredicates"/> implementation.
/// </summary>
public Func<IPredicates> Predicates { get; set; }
/// <summary>
/// Gets or sets the factory method for the <see cref="TrianglePool"/>.
/// </summary>
public Func<TrianglePool> TrianglePool { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
// -----------------------------------------------------------------------
// <copyright file="Enums.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
/// <summary>
/// The type of the mesh vertex.
/// </summary>
internal enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex };
/// <summary>
/// Node renumbering algorithms.
/// </summary>
internal enum NodeNumbering { None, Linear, CuthillMcKee };
/// <summary>
/// Labels that signify the result of point location.
/// </summary>
/// <remarks>The result of a search indicates that the point falls in the
/// interior of a triangle, on an edge, on a vertex, or outside the mesh.
/// </remarks>
internal enum LocateResult { InTriangle, OnEdge, OnVertex, Outside };
/// <summary>
/// Labels that signify the result of vertex insertion.
/// </summary>
/// <remarks>The result indicates that the vertex was inserted with complete
/// success, was inserted but encroaches upon a subsegment, was not inserted
/// because it lies on a segment, or was not inserted because another vertex
/// occupies the same location.
/// </remarks>
enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate };
/// <summary>
/// Labels that signify the result of direction finding.
/// </summary>
/// <remarks>The result indicates that a segment connecting the two query
/// points falls within the direction triangle, along the left edge of the
/// direction triangle, or along the right edge of the direction triangle.
/// </remarks>
enum FindDirectionResult { Within, Leftcollinear, Rightcollinear };
}

View File

@@ -0,0 +1,248 @@
// -----------------------------------------------------------------------
// <copyright file="Contour.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Linq;
using System.Collections.Generic;
internal class Contour
{
int marker;
bool convex;
/// <summary>
/// Gets or sets the list of points making up the contour.
/// </summary>
public List<Vertex> Points { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Contour" /> class.
/// </summary>
/// <param name="points">The points that make up the contour.</param>
public Contour(IEnumerable<Vertex> points)
: this(points, 0, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Contour" /> class.
/// </summary>
/// <param name="points">The points that make up the contour.</param>
/// <param name="marker">Contour marker.</param>
public Contour(IEnumerable<Vertex> points, int marker)
: this(points, marker, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Contour" /> class.
/// </summary>
/// <param name="points">The points that make up the contour.</param>
/// <param name="marker">Contour marker.</param>
/// <param name="convex">The hole is convex.</param>
public Contour(IEnumerable<Vertex> points, int marker, bool convex)
{
AddPoints(points);
this.marker = marker;
this.convex = convex;
}
public List<ISegment> GetSegments()
{
var segments = new List<ISegment>();
var p = this.Points;
int count = p.Count - 1;
for (int i = 0; i < count; i++)
{
// Add segments to polygon.
segments.Add(new Segment(p[i], p[i + 1], marker));
}
// Close the contour.
segments.Add(new Segment(p[count], p[0], marker));
return segments;
}
/// <summary>
/// Try to find a point inside the contour.
/// </summary>
/// <param name="limit">The number of iterations on each segment (default = 5).</param>
/// <param name="eps">Threshold for co-linear points (default = 2e-5).</param>
/// <returns>Point inside the contour</returns>
/// <exception cref="Exception">Throws if no point could be found.</exception>
/// <remarks>
/// For each corner (index i) of the contour, the 3 points with indices i-1, i and i+1
/// are considered and a search on the line through the corner vertex is started (either
/// on the bisecting line, or, if <see cref="IPredicates.CounterClockwise"/> is less than
/// eps, on the perpendicular line.
/// A given number of points will be tested (limit), while the distance to the contour
/// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit).
/// </remarks>
public Point FindInteriorPoint(int limit = 5, double eps = 2e-5)
{
if (convex)
{
int count = this.Points.Count;
var point = new Point(0.0, 0.0);
for (int i = 0; i < count; i++)
{
point.x += this.Points[i].x;
point.y += this.Points[i].y;
}
// If the contour is convex, use its centroid.
point.x /= count;
point.y /= count;
return point;
}
return FindPointInPolygon(this.Points, limit, eps);
}
private void AddPoints(IEnumerable<Vertex> points)
{
this.Points = new List<Vertex>(points);
int count = Points.Count - 1;
// Check if first vertex equals last vertex.
if (Points[0] == Points[count])
{
Points.RemoveAt(count);
}
}
#region Helper methods
private static Point FindPointInPolygon(List<Vertex> contour, int limit, double eps)
{
var bounds = new Rectangle();
bounds.Expand(contour.Cast<Point>());
int length = contour.Count;
var test = new Point();
Point a, b, c; // Current corner points.
double bx, by;
double dx, dy;
double h;
var predicates = new RobustPredicates();
a = contour[0];
b = contour[1];
for (int i = 0; i < length; i++)
{
c = contour[(i + 2) % length];
// Corner point.
bx = b.x;
by = b.y;
// NOTE: if we knew the contour points were in counterclockwise order, we
// could skip concave corners and search only in one direction.
h = predicates.CounterClockwise(a, b, c);
if (Math.Abs(h) < eps)
{
// Points are nearly co-linear. Use perpendicular direction.
dx = (c.y - a.y) / 2;
dy = (a.x - c.x) / 2;
}
else
{
// Direction [midpoint(a-c) -> corner point]
dx = (a.x + c.x) / 2 - bx;
dy = (a.y + c.y) / 2 - by;
}
// Move around the contour.
a = b;
b = c;
h = 1.0;
for (int j = 0; j < limit; j++)
{
// Search in direction.
test.x = bx + dx * h;
test.y = by + dy * h;
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
{
return test;
}
// Search in opposite direction (see NOTE above).
test.x = bx - dx * h;
test.y = by - dy * h;
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
{
return test;
}
h = h / 2;
}
}
throw new Exception();
}
/// <summary>
/// Return true if the given point is inside the polygon, or false if it is not.
/// </summary>
/// <param name="point">The point to check.</param>
/// <param name="poly">The polygon (list of contour points).</param>
/// <returns></returns>
/// <remarks>
/// WARNING: If the point is exactly on the edge of the polygon, then the function
/// may return true or false.
///
/// See http://alienryderflex.com/polygon/
/// </remarks>
private static bool IsPointInPolygon(Point point, List<Vertex> poly)
{
bool inside = false;
double x = point.x;
double y = point.y;
int count = poly.Count;
for (int i = 0, j = count - 1; i < count; i++)
{
if (((poly[i].y < y && poly[j].y >= y) || (poly[j].y < y && poly[i].y >= y))
&& (poly[i].x <= x || poly[j].x <= x))
{
inside ^= (poly[i].x + (y - poly[i].y) / (poly[j].y - poly[i].y) * (poly[j].x - poly[i].x) < x);
}
j = i;
}
return inside;
}
#endregion
}
}

View File

@@ -0,0 +1,59 @@
// -----------------------------------------------------------------------
// <copyright file="Edge.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
/// <summary>
/// Represents a straight line segment in 2D space.
/// </summary>
internal class Edge : IEdge
{
/// <summary>
/// Gets the first endpoints index.
/// </summary>
public int P0
{
get;
private set;
}
/// <summary>
/// Gets the second endpoints index.
/// </summary>
public int P1
{
get;
private set;
}
/// <summary>
/// Gets the segments boundary mark.
/// </summary>
public int Label
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="Edge" /> class.
/// </summary>
public Edge(int p0, int p1)
: this(p0, p1, 0)
{}
/// <summary>
/// Initializes a new instance of the <see cref="Edge" /> class.
/// </summary>
public Edge(int p0, int p1, int label)
{
this.P0 = p0;
this.P1 = p1;
this.Label = label;
}
}
}

View File

@@ -0,0 +1,143 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using Animation.TriangleNet.Meshing;
internal static class ExtensionMethods
{
#region IPolygon extensions
/// <summary>
/// Triangulates a polygon.
/// </summary>
internal static IMesh Triangulate(this IPolygon polygon)
{
return (new GenericMesher()).Triangulate(polygon, null, null);
}
/// <summary>
/// Triangulates a polygon, applying constraint options.
/// </summary>
/// <param name="options">Constraint options.</param>
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options)
{
return (new GenericMesher()).Triangulate(polygon, options, null);
}
/// <summary>
/// Triangulates a polygon, applying quality options.
/// </summary>
/// <param name="quality">Quality options.</param>
internal static IMesh Triangulate(this IPolygon polygon, QualityOptions quality)
{
return (new GenericMesher()).Triangulate(polygon, null, quality);
}
/// <summary>
/// Triangulates a polygon, applying quality and constraint options.
/// </summary>
/// <param name="options">Constraint options.</param>
/// <param name="quality">Quality options.</param>
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality)
{
return (new GenericMesher()).Triangulate(polygon, options, quality);
}
/// <summary>
/// Triangulates a polygon, applying quality and constraint options.
/// </summary>
/// <param name="options">Constraint options.</param>
/// <param name="quality">Quality options.</param>
/// <param name="triangulator">The triangulation algorithm.</param>
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality,
ITriangulator triangulator)
{
return (new GenericMesher(triangulator)).Triangulate(polygon, options, quality);
}
#endregion
#region Rectangle extensions
#endregion
#region ITriangle extensions
/// <summary>
/// Test whether a given point lies inside a triangle or not.
/// </summary>
/// <param name="p">Point to locate.</param>
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
internal static bool Contains(this ITriangle triangle, Point p)
{
return Contains(triangle, p.X, p.Y);
}
/// <summary>
/// Test whether a given point lies inside a triangle or not.
/// </summary>
/// <param name="x">Point to locate.</param>
/// <param name="y">Point to locate.</param>
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
internal static bool Contains(this ITriangle triangle, double x, double y)
{
var t0 = triangle.GetVertex(0);
var t1 = triangle.GetVertex(1);
var t2 = triangle.GetVertex(2);
// TODO: no need to create new Point instances here
Point d0 = new Point(t1.X - t0.X, t1.Y - t0.Y);
Point d1 = new Point(t2.X - t0.X, t2.Y - t0.Y);
Point d2 = new Point(x - t0.X, y - t0.Y);
// crossproduct of (0, 0, 1) and d0
Point c0 = new Point(-d0.Y, d0.X);
// crossproduct of (0, 0, 1) and d1
Point c1 = new Point(-d1.Y, d1.X);
// Linear combination d2 = s * d0 + v * d1.
//
// Multiply both sides of the equation with c0 and c1
// and solve for s and v respectively
//
// s = d2 * c1 / d0 * c1
// v = d2 * c0 / d1 * c0
double s = DotProduct(d2, c1) / DotProduct(d0, c1);
double v = DotProduct(d2, c0) / DotProduct(d1, c0);
if (s >= 0 && v >= 0 && ((s + v) <= 1))
{
// Point is inside or on the edge of this triangle.
return true;
}
return false;
}
internal static Rectangle Bounds(this ITriangle triangle)
{
var bounds = new Rectangle();
for (int i = 0; i < 3; i++)
{
bounds.Expand(triangle.GetVertex(i));
}
return bounds;
}
#endregion
#region Helper methods
internal static double DotProduct(Point p, Point q)
{
return p.X * q.X + p.Y * q.Y;
}
#endregion
}
}

View File

@@ -0,0 +1,30 @@
// -----------------------------------------------------------------------
// <copyright file="IEdge.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
internal interface IEdge
{
/// <summary>
/// Gets the first endpoints index.
/// </summary>
int P0 { get; }
/// <summary>
/// Gets the second endpoints index.
/// </summary>
int P1 { get; }
/// <summary>
/// Gets or sets a general-purpose label.
/// </summary>
/// <remarks>
/// This is used for the segments boundary mark.
/// </remarks>
int Label { get; }
}
}

View File

@@ -0,0 +1,94 @@
// -----------------------------------------------------------------------
// <copyright file="IPolygon.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Collections.Generic;
/// <summary>
/// Polygon interface.
/// </summary>
internal interface IPolygon
{
/// <summary>
/// Gets the vertices of the polygon.
/// </summary>
List<Vertex> Points { get; }
/// <summary>
/// Gets the segments of the polygon.
/// </summary>
List<ISegment> Segments { get; }
/// <summary>
/// Gets a list of points defining the holes of the polygon.
/// </summary>
List<Point> Holes { get; }
/// <summary>
/// Gets a list of pointers defining the regions of the polygon.
/// </summary>
List<RegionPointer> Regions { get; }
/// <summary>
/// Gets or sets a value indicating whether the vertices have marks or not.
/// </summary>
bool HasPointMarkers { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the segments have marks or not.
/// </summary>
bool HasSegmentMarkers { get; set; }
[Obsolete("Use polygon.Add(contour) method instead.")]
void AddContour(IEnumerable<Vertex> points, int marker, bool hole, bool convex);
[Obsolete("Use polygon.Add(contour) method instead.")]
void AddContour(IEnumerable<Vertex> points, int marker, Point hole);
/// <summary>
/// Compute the bounds of the polygon.
/// </summary>
/// <returns>Rectangle defining an axis-aligned bounding box.</returns>
Rectangle Bounds();
/// <summary>
/// Add a vertex to the polygon.
/// </summary>
/// <param name="vertex">The vertex to insert.</param>
void Add(Vertex vertex);
/// <summary>
/// Add a segment to the polygon.
/// </summary>
/// <param name="segment">The segment to insert.</param>
/// <param name="insert">If true, both endpoints will be added to the points list.</param>
void Add(ISegment segment, bool insert = false);
/// <summary>
/// Add a segment to the polygon.
/// </summary>
/// <param name="segment">The segment to insert.</param>
/// <param name="index">The index of the segment endpoint to add to the points list (must be 0 or 1).</param>
void Add(ISegment segment, int index);
/// <summary>
/// Add a contour to the polygon.
/// </summary>
/// <param name="contour">The contour to insert.</param>
/// <param name="hole">Treat contour as a hole.</param>
void Add(Contour contour, bool hole = false);
/// <summary>
/// Add a contour to the polygon.
/// </summary>
/// <param name="contour">The contour to insert.</param>
/// <param name="hole">Point inside the contour, making it a hole.</param>
void Add(Contour contour, Point hole);
}
}

View File

@@ -0,0 +1,27 @@
// -----------------------------------------------------------------------
// <copyright file="ISegment.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
/// <summary>
/// Interface for segment geometry.
/// </summary>
internal interface ISegment : IEdge
{
/// <summary>
/// Gets the vertex at given index.
/// </summary>
/// <param name="index">The local index (0 or 1).</param>
Vertex GetVertex(int index);
/// <summary>
/// Gets an adjoining triangle.
/// </summary>
/// <param name="index">The triangle index (0 or 1).</param>
ITriangle GetTriangle(int index);
}
}

View File

@@ -0,0 +1,70 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using Animation.TriangleNet.Topology;
/// <summary>
/// Triangle interface.
/// </summary>
internal interface ITriangle
{
/// <summary>
/// Gets or sets the triangle ID.
/// </summary>
int ID { get; set; }
/// <summary>
/// Gets or sets a general-purpose label.
/// </summary>
/// <remarks>
/// This is used for region information.
/// </remarks>
int Label { get; set; }
/// <summary>
/// Gets or sets the triangle area constraint.
/// </summary>
double Area { get; set; }
/// <summary>
/// Gets the vertex at given index.
/// </summary>
/// <param name="index">The local index (0, 1 or 2).</param>
/// <returns>The vertex.</returns>
Vertex GetVertex(int index);
/// <summary>
/// Gets the ID of the vertex at given index.
/// </summary>
/// <param name="index">The local index (0, 1 or 2).</param>
/// <returns>The vertex ID.</returns>
int GetVertexID(int index);
/// <summary>
/// Gets the neighbor triangle at given index.
/// </summary>
/// <param name="index">The local index (0, 1 or 2).</param>
/// <returns>The neighbor triangle.</returns>
ITriangle GetNeighbor(int index);
/// <summary>
/// Gets the ID of the neighbor triangle at given index.
/// </summary>
/// <param name="index">The local index (0, 1 or 2).</param>
/// <returns>The neighbor triangle ID.</returns>
int GetNeighborID(int index);
/// <summary>
/// Gets the segment at given index.
/// </summary>
/// <param name="index">The local index (0, 1 or 2).</param>
/// <returns>The segment.</returns>
ISegment GetSegment(int index);
}
}

View File

@@ -0,0 +1,181 @@
// -----------------------------------------------------------------------
// <copyright file="Point.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Diagnostics;
/// <summary>
/// Represents a 2D point.
/// </summary>
#if USE_Z
[DebuggerDisplay("ID {ID} [{X}, {Y}, {Z}]")]
#else
[DebuggerDisplay("ID {ID} [{X}, {Y}]")]
#endif
internal class Point : IComparable<Point>, IEquatable<Point>
{
internal int id;
internal int label;
internal double x;
internal double y;
#if USE_Z
internal double z;
#endif
public Point()
: this(0.0, 0.0, 0)
{
}
public Point(double x, double y)
: this(x, y, 0)
{
}
public Point(double x, double y, int label)
{
this.x = x;
this.y = y;
this.label = label;
}
#region Public properties
/// <summary>
/// Gets or sets the vertex id.
/// </summary>
public int ID
{
get { return this.id; }
set { this.id = value; }
}
/// <summary>
/// Gets or sets the vertex x coordinate.
/// </summary>
public double X
{
get { return this.x; }
set { this.x = value; }
}
/// <summary>
/// Gets or sets the vertex y coordinate.
/// </summary>
public double Y
{
get { return this.y; }
set { this.y = value; }
}
#if USE_Z
/// <summary>
/// Gets or sets the vertex z coordinate.
/// </summary>
public double Z
{
get { return this.z; }
set { this.z = value; }
}
#endif
/// <summary>
/// Gets or sets a general-purpose label.
/// </summary>
/// <remarks>
/// This is used for the vertex boundary mark.
/// </remarks>
public int Label
{
get { return this.label; }
set { this.label = value; }
}
#endregion
#region Operator overloading / overriding Equals
// Compare "Guidelines for Overriding Equals() and Operator =="
// http://msdn.microsoft.com/en-us/library/ms173147.aspx
public static bool operator==(Point a, Point b)
{
// If both are null, or both are same instance, return true.
if (Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
return a.Equals(b);
}
public static bool operator!=(Point a, Point b)
{
return !(a == b);
}
public override bool Equals(object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
Point p = obj as Point;
if ((object)p == null)
{
return false;
}
return (x == p.x) && (y == p.y);
}
public bool Equals(Point p)
{
// If vertex is null return false.
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return (x == p.x) && (y == p.y);
}
#endregion
public int CompareTo(Point other)
{
if (x == other.x && y == other.y)
{
return 0;
}
return (x < other.x || (x == other.x && y < other.y)) ? -1 : 1;
}
public override int GetHashCode()
{
int hash = 19;
hash = hash * 31 + x.GetHashCode();
hash = hash * 31 + y.GetHashCode();
return hash;
}
}
}

View File

@@ -0,0 +1,185 @@
// -----------------------------------------------------------------------
// <copyright file="Polygon.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Linq;
using System.Collections.Generic;
/// <summary>
/// A polygon represented as a planar straight line graph.
/// </summary>
internal class Polygon : IPolygon
{
List<Vertex> points;
List<Point> holes;
List<RegionPointer> regions;
List<ISegment> segments;
/// <inheritdoc />
public List<Vertex> Points
{
get { return points; }
}
/// <inheritdoc />
public List<Point> Holes
{
get { return holes; }
}
/// <inheritdoc />
public List<RegionPointer> Regions
{
get { return regions; }
}
/// <inheritdoc />
public List<ISegment> Segments
{
get { return segments; }
}
/// <inheritdoc />
public bool HasPointMarkers { get; set; }
/// <inheritdoc />
public bool HasSegmentMarkers { get; set; }
/// <inheritdoc />
public int Count
{
get { return points.Count; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> class.
/// </summary>
public Polygon()
: this(3, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> class.
/// </summary>
/// <param name="capacity">The default capacity for the points list.</param>
public Polygon(int capacity)
: this(3, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> class.
/// </summary>
/// <param name="capacity">The default capacity for the points list.</param>
/// <param name="markers">Use point and segment markers.</param>
public Polygon(int capacity, bool markers)
{
points = new List<Vertex>(capacity);
holes = new List<Point>();
regions = new List<RegionPointer>();
segments = new List<ISegment>();
HasPointMarkers = markers;
HasSegmentMarkers = markers;
}
[Obsolete("Use polygon.Add(contour) method instead.")]
public void AddContour(IEnumerable<Vertex> points, int marker = 0,
bool hole = false, bool convex = false)
{
this.Add(new Contour(points, marker, convex), hole);
}
[Obsolete("Use polygon.Add(contour) method instead.")]
public void AddContour(IEnumerable<Vertex> points, int marker, Point hole)
{
this.Add(new Contour(points, marker), hole);
}
/// <inheritdoc />
public Rectangle Bounds()
{
var bounds = new Rectangle();
bounds.Expand(this.points.Cast<Point>());
return bounds;
}
/// <summary>
/// Add a vertex to the polygon.
/// </summary>
/// <param name="vertex">The vertex to insert.</param>
public void Add(Vertex vertex)
{
this.points.Add(vertex);
}
/// <summary>
/// Add a segment to the polygon.
/// </summary>
/// <param name="segment">The segment to insert.</param>
/// <param name="insert">If true, both endpoints will be added to the points list.</param>
public void Add(ISegment segment, bool insert = false)
{
this.segments.Add(segment);
if (insert)
{
this.points.Add(segment.GetVertex(0));
this.points.Add(segment.GetVertex(1));
}
}
/// <summary>
/// Add a segment to the polygon.
/// </summary>
/// <param name="segment">The segment to insert.</param>
/// <param name="index">The index of the segment endpoint to add to the points list (must be 0 or 1).</param>
public void Add(ISegment segment, int index)
{
this.segments.Add(segment);
this.points.Add(segment.GetVertex(index));
}
/// <summary>
/// Add a contour to the polygon.
/// </summary>
/// <param name="contour">The contour to insert.</param>
/// <param name="hole">Treat contour as a hole.</param>
public void Add(Contour contour, bool hole = false)
{
if (hole)
{
this.Add(contour, contour.FindInteriorPoint());
}
else
{
this.points.AddRange(contour.Points);
this.segments.AddRange(contour.GetSegments());
}
}
/// <summary>
/// Add a contour to the polygon.
/// </summary>
/// <param name="contour">The contour to insert.</param>
/// <param name="hole">Point inside the contour, making it a hole.</param>
public void Add(Contour contour, Point hole)
{
this.points.AddRange(contour.Points);
this.segments.AddRange(contour.GetSegments());
this.holes.Add(hole);
}
}
}

View File

@@ -0,0 +1,190 @@
// -----------------------------------------------------------------------
// <copyright file="Rectangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Collections.Generic;
/// <summary>
/// A simple rectangle class.
/// </summary>
internal class Rectangle
{
double xmin, ymin, xmax, ymax;
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle" /> class.
/// </summary>
public Rectangle()
{
this.xmin = this.ymin = double.MaxValue;
this.xmax = this.ymax = -double.MaxValue;
}
public Rectangle(Rectangle other)
: this(other.Left, other.Bottom, other.Right, other.Top)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle" /> class
/// with predefined bounds.
/// </summary>
/// <param name="x">Minimum x value (left).</param>
/// <param name="y">Minimum y value (bottom).</param>
/// <param name="width">Width of the rectangle.</param>
/// <param name="height">Height of the rectangle.</param>
public Rectangle(double x, double y, double width, double height)
{
this.xmin = x;
this.ymin = y;
this.xmax = x + width;
this.ymax = y + height;
}
/// <summary>
/// Gets the minimum x value (left boundary).
/// </summary>
public double Left
{
get { return xmin; }
}
/// <summary>
/// Gets the maximum x value (right boundary).
/// </summary>
public double Right
{
get { return xmax; }
}
/// <summary>
/// Gets the minimum y value (bottom boundary).
/// </summary>
public double Bottom
{
get { return ymin; }
}
/// <summary>
/// Gets the maximum y value (top boundary).
/// </summary>
public double Top
{
get { return ymax; }
}
/// <summary>
/// Gets the width of the rectangle.
/// </summary>
public double Width
{
get { return xmax - xmin; }
}
/// <summary>
/// Gets the height of the rectangle.
/// </summary>
public double Height
{
get { return ymax - ymin; }
}
/// <summary>
/// Update bounds.
/// </summary>
/// <param name="dx">Add dx to left and right bounds.</param>
/// <param name="dy">Add dy to top and bottom bounds.</param>
public void Resize(double dx, double dy)
{
xmin -= dx;
xmax += dx;
ymin -= dy;
ymax += dy;
}
/// <summary>
/// Expand rectangle to include given point.
/// </summary>
/// <param name="p">Point.</param>
public void Expand(Point p)
{
xmin = Math.Min(xmin, p.x);
ymin = Math.Min(ymin, p.y);
xmax = Math.Max(xmax, p.x);
ymax = Math.Max(ymax, p.y);
}
/// <summary>
/// Expand rectangle to include a list of points.
/// </summary>
public void Expand(IEnumerable<Point> points)
{
foreach (var p in points)
{
Expand(p);
}
}
/// <summary>
/// Expand rectangle to include given rectangle.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
public void Expand(Rectangle other)
{
xmin = Math.Min(xmin, other.xmin);
ymin = Math.Min(ymin, other.ymin);
xmax = Math.Max(xmax, other.xmax);
ymax = Math.Max(ymax, other.ymax);
}
/// <summary>
/// Check if given point is inside rectangle.
/// </summary>
/// <param name="x">Point to check.</param>
/// <param name="y">Point to check.</param>
/// <returns>Return true, if rectangle contains given point.</returns>
public bool Contains(double x, double y)
{
return ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax));
}
/// <summary>
/// Check if given point is inside rectangle.
/// </summary>
/// <param name="pt">Point to check.</param>
/// <returns>Return true, if rectangle contains given point.</returns>
public bool Contains(Point pt)
{
return Contains(pt.x, pt.y);
}
/// <summary>
/// Check if this rectangle contains other rectangle.
/// </summary>
/// <param name="other">Rectangle to check.</param>
/// <returns>Return true, if this rectangle contains given rectangle.</returns>
public bool Contains(Rectangle other)
{
return (xmin <= other.Left && other.Right <= xmax
&& ymin <= other.Bottom && other.Top <= ymax);
}
/// <summary>
/// Check if this rectangle intersects other rectangle.
/// </summary>
/// <param name="other">Rectangle to check.</param>
/// <returns>Return true, if given rectangle intersects this rectangle.</returns>
public bool Intersects(Rectangle other)
{
return (other.Left < xmax && xmin < other.Right
&& other.Bottom < ymax && ymin < other.Top);
}
}
}

View File

@@ -0,0 +1,64 @@
// -----------------------------------------------------------------------
// <copyright file="RegionPointer.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using System.Collections.Generic;
/// <summary>
/// Pointer to a region in the mesh geometry. A region is a well-defined
/// subset of the geomerty (enclosed by subsegments).
/// </summary>
internal class RegionPointer
{
internal Point point;
internal int id;
internal double area;
/// <summary>
/// Gets or sets a region area constraint.
/// </summary>
public double Area
{
get { return area; }
set
{
if (value < 0.0)
{
throw new ArgumentException("Area constraints must not be negative.");
}
area = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RegionPointer" /> class.
/// </summary>
/// <param name="x">X coordinate of the region.</param>
/// <param name="y">Y coordinate of the region.</param>
/// <param name="id">Region id.</param>
public RegionPointer(double x, double y, int id)
: this(x, y, id, 0.0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegionPointer" /> class.
/// </summary>
/// <param name="x">X coordinate of the region.</param>
/// <param name="y">Y coordinate of the region.</param>
/// <param name="id">Region id.</param>
/// <param name="area">Area constraint.</param>
public RegionPointer(double x, double y, int id, double area)
{
this.point = new Point(x, y);
this.id = id;
this.area = area;
}
}
}

View File

@@ -0,0 +1,93 @@
// -----------------------------------------------------------------------
// <copyright file="Segment.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
/// <summary>
/// Represents a straight line segment in 2D space.
/// </summary>
internal class Segment : ISegment
{
Vertex v0;
Vertex v1;
int label;
/// <summary>
/// Gets or sets the segments boundary mark.
/// </summary>
public int Label
{
get { return label; }
set { label = value; }
}
/// <summary>
/// Gets the first endpoints index.
/// </summary>
public int P0
{
get { return v0.id; }
}
/// <summary>
/// Gets the second endpoints index.
/// </summary>
public int P1
{
get { return v1.id; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Segment" /> class.
/// </summary>
public Segment(Vertex v0, Vertex v1)
: this(v0, v1, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Segment" /> class.
/// </summary>
public Segment(Vertex v0, Vertex v1, int label)
{
this.v0 = v0;
this.v1 = v1;
this.label = label;
}
/// <summary>
/// Gets the specified segment endpoint.
/// </summary>
/// <param name="index">The endpoint index (0 or 1).</param>
/// <returns></returns>
public Vertex GetVertex(int index)
{
if (index == 0)
{
return v0;
}
if (index == 1)
{
return v1;
}
throw new IndexOutOfRangeException();
}
/// <summary>
/// WARNING: not implemented.
/// </summary>
public ITriangle GetTriangle(int index)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,127 @@
// -----------------------------------------------------------------------
// <copyright file="Vertex.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Geometry
{
using System;
using Animation.TriangleNet.Topology;
/// <summary>
/// The vertex data structure.
/// </summary>
internal class Vertex : Point
{
// Hash for dictionary. Will be set by mesh instance.
internal int hash;
#if USE_ATTRIBS
internal double[] attributes;
#endif
internal VertexType type;
internal Otri tri;
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
public Vertex()
: this(0, 0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate of the vertex.</param>
/// <param name="y">The y coordinate of the vertex.</param>
public Vertex(double x, double y)
: this(x, y, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate of the vertex.</param>
/// <param name="y">The y coordinate of the vertex.</param>
/// <param name="mark">The boundary mark.</param>
public Vertex(double x, double y, int mark)
: base(x, y, mark)
{
this.type = VertexType.InputVertex;
}
#if USE_ATTRIBS
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate of the vertex.</param>
/// <param name="y">The y coordinate of the vertex.</param>
/// <param name="mark">The boundary mark.</param>
/// <param name="attribs">The number of point attributes.</param>
public Vertex(double x, double y, int mark, int attribs)
: this(x, y, mark)
{
if (attribs > 0)
{
this.attributes = new double[attribs];
}
}
#endif
#region Public properties
#if USE_ATTRIBS
/// <summary>
/// Gets the vertex attributes (may be null).
/// </summary>
public double[] Attributes
{
get { return this.attributes; }
}
#endif
/// <summary>
/// Gets the vertex type.
/// </summary>
public VertexType Type
{
get { return this.type; }
}
/// <summary>
/// Gets the specified coordinate of the vertex.
/// </summary>
/// <param name="i">Coordinate index.</param>
/// <returns>X coordinate, if index is 0, Y coordinate, if index is 1.</returns>
public double this[int i]
{
get
{
if (i == 0)
{
return x;
}
if (i == 1)
{
return y;
}
throw new ArgumentOutOfRangeException("Index must be 0 or 1.");
}
}
#endregion
public override int GetHashCode()
{
return this.hash;
}
}
}

View File

@@ -0,0 +1,264 @@
// -----------------------------------------------------------------------
// <copyright file="DebugWriter.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Writes a the current mesh into a text file.
/// </summary>
/// <remarks>
/// File format:
///
/// num_nodes
/// id_1 nx ny mark
/// ...
/// id_n nx ny mark
///
/// num_segs
/// id_1 p1 p2 mark
/// ...
/// id_n p1 p2 mark
///
/// num_tris
/// id_1 p1 p2 p3 n1 n2 n3
/// ...
/// id_n p1 p2 p3 n1 n2 n3
/// </remarks>
class DebugWriter
{
static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
int iteration;
string session;
StreamWriter stream;
string tmpFile;
int[] vertices;
int triangles;
#region Singleton pattern
private static readonly DebugWriter instance = new DebugWriter();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static DebugWriter() {}
private DebugWriter() {}
internal static DebugWriter Session
{
get
{
return instance;
}
}
#endregion
/// <summary>
/// Start a new session with given name.
/// </summary>
/// <param name="name">Name of the session (and output files).</param>
public void Start(string session)
{
this.iteration = 0;
this.session = session;
if (this.stream != null)
{
throw new Exception("A session is active. Finish before starting a new.");
}
this.tmpFile = Path.GetTempFileName();
this.stream = new StreamWriter(tmpFile);
}
/// <summary>
/// Write complete mesh to file.
/// </summary>
public void Write(Mesh mesh, bool skip = false)
{
this.WriteMesh(mesh, skip);
this.triangles = mesh.Triangles.Count;
}
/// <summary>
/// Finish this session.
/// </summary>
public void Finish()
{
this.Finish(session + ".mshx");
}
private void Finish(string path)
{
if (stream != null)
{
stream.Flush();
stream.Dispose();
stream = null;
string header = "#!N" + this.iteration + Environment.NewLine;
using (var gzFile = new FileStream(path, FileMode.Create))
{
using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false))
{
byte[] bytes = Encoding.UTF8.GetBytes(header);
gzStream.Write(bytes, 0, bytes.Length);
// TODO: read with stream
bytes = File.ReadAllBytes(tmpFile);
gzStream.Write(bytes, 0, bytes.Length);
}
}
File.Delete(this.tmpFile);
}
}
private void WriteGeometry(IPolygon geometry)
{
stream.WriteLine("#!G{0}", this.iteration++);
}
private void WriteMesh(Mesh mesh, bool skip)
{
// Mesh may have changed, but we choose to skip
if (triangles == mesh.triangles.Count && skip)
{
return;
}
// Header line
stream.WriteLine("#!M{0}", this.iteration++);
Vertex p1, p2, p3;
if (VerticesChanged(mesh))
{
HashVertices(mesh);
// Number of vertices.
stream.WriteLine("{0}", mesh.vertices.Count);
foreach (var v in mesh.vertices.Values)
{
// Vertex number, x and y coordinates and marker.
stream.WriteLine("{0} {1} {2} {3}", v.id, v.x.ToString(nfi), v.y.ToString(nfi), v.label);
}
}
else
{
stream.WriteLine("0");
}
// Number of segments.
stream.WriteLine("{0}", mesh.subsegs.Count);
Osub subseg = default(Osub);
subseg.orient = 0;
foreach (var item in mesh.subsegs.Values)
{
if (item.hash <= 0)
{
continue;
}
subseg.seg = item;
p1 = subseg.Org();
p2 = subseg.Dest();
// Segment number, indices of its two endpoints, and marker.
stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.id, p2.id, subseg.seg.boundary);
}
Otri tri = default(Otri), trisym = default(Otri);
tri.orient = 0;
int n1, n2, n3, h1, h2, h3;
// Number of triangles.
stream.WriteLine("{0}", mesh.triangles.Count);
foreach (var item in mesh.triangles)
{
tri.tri = item;
p1 = tri.Org();
p2 = tri.Dest();
p3 = tri.Apex();
h1 = (p1 == null) ? -1 : p1.id;
h2 = (p2 == null) ? -1 : p2.id;
h3 = (p3 == null) ? -1 : p3.id;
// Triangle number, indices for three vertices.
stream.Write("{0} {1} {2} {3}", tri.tri.hash, h1, h2, h3);
tri.orient = 1;
tri.Sym(ref trisym);
n1 = trisym.tri.hash;
tri.orient = 2;
tri.Sym(ref trisym);
n2 = trisym.tri.hash;
tri.orient = 0;
tri.Sym(ref trisym);
n3 = trisym.tri.hash;
// Neighboring triangle numbers.
stream.WriteLine(" {0} {1} {2}", n1, n2, n3);
}
}
private bool VerticesChanged(Mesh mesh)
{
if (vertices == null || mesh.Vertices.Count != vertices.Length)
{
return true;
}
int i = 0;
foreach (var v in mesh.Vertices)
{
if (v.id != vertices[i++])
{
return true;
}
}
return false;
}
private void HashVertices(Mesh mesh)
{
if (vertices == null || mesh.Vertices.Count != vertices.Length)
{
vertices = new int[mesh.Vertices.Count];
}
int i = 0;
foreach (var v in mesh.Vertices)
{
vertices[i++] = v.id;
}
}
}
}

View File

@@ -0,0 +1,127 @@
// -----------------------------------------------------------------------
// <copyright file="FileProcessor.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Meshing;
internal static class FileProcessor
{
static List<IFileFormat> formats;
static FileProcessor()
{
formats = new List<IFileFormat>();
// Add Triangle file format as default.
formats.Add(new TriangleFormat());
}
internal static void Add(IFileFormat format)
{
formats.Add(format);
}
internal static bool IsSupported(string file)
{
foreach (var format in formats)
{
if (format.IsSupported(file))
{
return true;
}
}
return false;
}
#region Polygon read/write
/// <summary>
/// Read a file containing polygon geometry.
/// </summary>
/// <param name="filename">The path of the file to read.</param>
/// <returns>An instance of the <see cref="IPolygon" /> class.</returns>
internal static IPolygon Read(string filename)
{
foreach (IPolygonFormat format in formats)
{
if (format != null && format.IsSupported(filename))
{
return format.Read(filename);
}
}
throw new Exception("File format not supported.");
}
/// <summary>
/// Save a polygon geometry to disk.
/// </summary>
/// <param name="mesh">An instance of the <see cref="IPolygon" /> class.</param>
/// <param name="filename">The path of the file to save.</param>
internal static void Write(IPolygon polygon, string filename)
{
foreach (IPolygonFormat format in formats)
{
if (format != null && format.IsSupported(filename))
{
format.Write(polygon, filename);
return;
}
}
throw new Exception("File format not supported.");
}
#endregion
#region Mesh read/write
/// <summary>
/// Read a file containing a mesh.
/// </summary>
/// <param name="filename">The path of the file to read.</param>
/// <returns>An instance of the <see cref="IMesh" /> interface.</returns>
internal static IMesh Import(string filename)
{
foreach (IMeshFormat format in formats)
{
if (format != null && format.IsSupported(filename))
{
return format.Import(filename);
}
}
throw new Exception("File format not supported.");
}
/// <summary>
/// Save a mesh to disk.
/// </summary>
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
/// <param name="filename">The path of the file to save.</param>
internal static void Write(IMesh mesh, string filename)
{
foreach (IMeshFormat format in formats)
{
if (format != null && format.IsSupported(filename))
{
format.Write(mesh, filename);
return;
}
}
throw new Exception("File format not supported.");
}
#endregion
}
}

View File

@@ -0,0 +1,14 @@
// -----------------------------------------------------------------------
// <copyright file="IFileFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
internal interface IFileFormat
{
bool IsSupported(string file);
}
}

View File

@@ -0,0 +1,39 @@
// -----------------------------------------------------------------------
// <copyright file="IMeshFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System.IO;
using Animation.TriangleNet.Meshing;
/// <summary>
/// Interface for mesh I/O.
/// </summary>
internal interface IMeshFormat : IFileFormat
{
/// <summary>
/// Read a file containing a mesh.
/// </summary>
/// <param name="filename">The path of the file to read.</param>
/// <returns>An instance of the <see cref="IMesh" /> interface.</returns>
IMesh Import(string filename);
/// <summary>
/// Save a mesh to disk.
/// </summary>
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
/// <param name="filename">The path of the file to save.</param>
void Write(IMesh mesh, string filename);
/// <summary>
/// Save a mesh to a <see cref="Stream" />.
/// </summary>
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
/// <param name="stream">The stream to save to.</param>
void Write(IMesh mesh, Stream stream);
}
}

View File

@@ -0,0 +1,39 @@
// -----------------------------------------------------------------------
// <copyright file="IPolygonFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System.IO;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for geometry input.
/// </summary>
internal interface IPolygonFormat : IFileFormat
{
/// <summary>
/// Read a file containing polygon geometry.
/// </summary>
/// <param name="filename">The path of the file to read.</param>
/// <returns>An instance of the <see cref="IPolygon" /> class.</returns>
IPolygon Read(string filename);
/// <summary>
/// Save a polygon geometry to disk.
/// </summary>
/// <param name="polygon">An instance of the <see cref="IPolygon" /> class.</param>
/// <param name="filename">The path of the file to save.</param>
void Write(IPolygon polygon, string filename);
/// <summary>
/// Save a polygon geometry to a <see cref="Stream" />.
/// </summary>
/// <param name="polygon">An instance of the <see cref="IPolygon" /> class.</param>
/// <param name="stream">The stream to save to.</param>
void Write(IPolygon polygon, Stream stream);
}
}

View File

@@ -0,0 +1,86 @@
// -----------------------------------------------------------------------
// <copyright file="InputTriangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Simple triangle class for input.
/// </summary>
internal class InputTriangle : ITriangle
{
internal int[] vertices;
internal int label;
internal double area;
public InputTriangle(int p0, int p1, int p2)
{
this.vertices = new int[] { p0, p1, p2 };
}
#region Public properties
/// <summary>
/// Gets the triangle id.
/// </summary>
public int ID
{
get { return 0; }
set {}
}
/// <summary>
/// Region ID the triangle belongs to.
/// </summary>
public int Label
{
get { return label; }
set { label = value; }
}
/// <summary>
/// Gets the triangle area constraint.
/// </summary>
public double Area
{
get { return area; }
set { area = value; }
}
/// <summary>
/// Gets the specified corners vertex.
/// </summary>
public Vertex GetVertex(int index)
{
return null; // TODO: throw NotSupportedException?
}
public int GetVertexID(int index)
{
return vertices[index];
}
public ITriangle GetNeighbor(int index)
{
return null;
}
public int GetNeighborID(int index)
{
return -1;
}
public ISegment GetSegment(int index)
{
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,92 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System;
using System.Collections.Generic;
using System.IO;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Meshing;
/// <summary>
/// Implements geometry and mesh file formats of the the original Triangle code.
/// </summary>
internal class TriangleFormat : IPolygonFormat, IMeshFormat
{
public bool IsSupported(string file)
{
string ext = Path.GetExtension(file).ToLower();
if (ext == ".node" || ext == ".poly" || ext == ".ele")
{
return true;
}
return false;
}
public IMesh Import(string filename)
{
string ext = Path.GetExtension(filename);
if (ext == ".node" || ext == ".poly" || ext == ".ele")
{
List<ITriangle> triangles;
Polygon geometry;
(new TriangleReader()).Read(filename, out geometry, out triangles);
if (geometry != null && triangles != null)
{
return Converter.ToMesh(geometry, triangles.ToArray());
}
}
throw new NotSupportedException("Could not load '" + filename + "' file.");
}
public void Write(IMesh mesh, string filename)
{
var writer = new TriangleWriter();
writer.WritePoly((Mesh)mesh, Path.ChangeExtension(filename, ".poly"));
writer.WriteElements((Mesh)mesh, Path.ChangeExtension(filename, ".ele"));
}
public void Write(IMesh mesh, Stream stream)
{
throw new NotImplementedException();
}
public IPolygon Read(string filename)
{
string ext = Path.GetExtension(filename);
if (ext == ".node")
{
return (new TriangleReader()).ReadNodeFile(filename);
}
else if (ext == ".poly")
{
return (new TriangleReader()).ReadPolyFile(filename);
}
throw new NotSupportedException("File format '" + ext + "' not supported.");
}
public void Write(IPolygon polygon, string filename)
{
(new TriangleWriter()).WritePoly(polygon, filename);
}
public void Write(IPolygon polygon, Stream stream)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,757 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleReader.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Helper methods for reading Triangle file formats.
/// </summary>
internal class TriangleReader
{
static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
int startIndex = 0;
#region Helper methods
private bool TryReadLine(StreamReader reader, out string[] token)
{
token = null;
if (reader.EndOfStream)
{
return false;
}
string line = reader.ReadLine().Trim();
while (IsStringNullOrWhiteSpace(line) || line.StartsWith("#"))
{
if (reader.EndOfStream)
{
return false;
}
line = reader.ReadLine().Trim();
}
token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
return true;
}
/// <summary>
/// Read vertex information of the given line.
/// </summary>
/// <param name="data">The input geometry.</param>
/// <param name="index">The current vertex index.</param>
/// <param name="line">The current line.</param>
/// <param name="attributes">Number of point attributes</param>
/// <param name="marks">Number of point markers (0 or 1)</param>
private void ReadVertex(List<Vertex> data, int index, string[] line, int attributes, int marks)
{
double x = double.Parse(line[1], nfi);
double y = double.Parse(line[2], nfi);
var v = new Vertex(x, y);
// Read a vertex marker.
if (marks > 0 && line.Length > 3 + attributes)
{
v.Label = int.Parse(line[3 + attributes]);
}
if (attributes > 0)
{
#if USE_ATTRIBS
var attribs = new double[attributes];
// Read the vertex attributes.
for (int j = 0; j < attributes; j++)
{
if (line.Length > 3 + j)
{
attribs[j] = double.Parse(line[3 + j], nfi);
}
}
v.attributes = attribs;
#endif
}
data.Add(v);
}
#endregion
#region Main I/O methods
/// <summary>
/// Reads geometry information from .node or .poly files.
/// </summary>
public void Read(string filename, out Polygon polygon)
{
polygon = null;
string path = Path.ChangeExtension(filename, ".poly");
if (File.Exists(path))
{
polygon = ReadPolyFile(path);
}
else
{
path = Path.ChangeExtension(filename, ".node");
polygon = ReadNodeFile(path);
}
}
/// <summary>
/// Reads a mesh from .node, .poly or .ele files.
/// </summary>
public void Read(string filename, out Polygon geometry, out List<ITriangle> triangles)
{
triangles = null;
Read(filename, out geometry);
string path = Path.ChangeExtension(filename, ".ele");
if (File.Exists(path) && geometry != null)
{
triangles = ReadEleFile(path);
}
}
/// <summary>
/// Reads geometry information from .node or .poly files.
/// </summary>
public IPolygon Read(string filename)
{
Polygon geometry = null;
Read(filename, out geometry);
return geometry;
}
#endregion
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public Polygon ReadNodeFile(string nodefilename)
{
return ReadNodeFile(nodefilename, false);
}
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <param name="readElements"></param>
public Polygon ReadNodeFile(string nodefilename, bool readElements)
{
Polygon data;
startIndex = 0;
string[] line;
int invertices = 0, attributes = 0, nodemarkers = 0;
using (var reader = new StreamReader(nodefilename))
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file.");
}
// Read number of vertices, number of dimensions, number of vertex
// attributes, and number of boundary markers.
invertices = int.Parse(line[0]);
if (invertices < 3)
{
throw new Exception("Input must have at least three input vertices.");
}
if (line.Length > 1)
{
if (int.Parse(line[1]) != 2)
{
throw new Exception("Triangle only works with two-dimensional meshes.");
}
}
if (line.Length > 2)
{
attributes = int.Parse(line[2]);
}
if (line.Length > 3)
{
nodemarkers = int.Parse(line[3]);
}
data = new Polygon(invertices);
// Read the vertices.
if (invertices > 0)
{
for (int i = 0; i < invertices; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (vertices).");
}
if (line.Length < 3)
{
throw new Exception("Invalid vertex.");
}
if (i == 0)
{
startIndex = int.Parse(line[0], nfi);
}
ReadVertex(data.Points, i, line, attributes, nodemarkers);
}
}
}
if (readElements)
{
// Read area file
string elefile = Path.ChangeExtension(nodefilename, ".ele");
if (File.Exists(elefile))
{
ReadEleFile(elefile, true);
}
}
return data;
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public Polygon ReadPolyFile(string polyfilename)
{
return ReadPolyFile(polyfilename, false, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <remarks>Will NOT read associated .area by default.</remarks>
public Polygon ReadPolyFile(string polyfilename, bool readElements)
{
return ReadPolyFile(polyfilename, readElements, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <param name="readElements">If true, look for an associated .area file.</param>
public Polygon ReadPolyFile(string polyfilename, bool readElements, bool readArea)
{
// Read poly file
Polygon data;
startIndex = 0;
string[] line;
int invertices = 0, attributes = 0, nodemarkers = 0;
using (var reader = new StreamReader(polyfilename))
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file.");
}
// Read number of vertices, number of dimensions, number of vertex
// attributes, and number of boundary markers.
invertices = int.Parse(line[0]);
if (line.Length > 1)
{
if (int.Parse(line[1]) != 2)
{
throw new Exception("Triangle only works with two-dimensional meshes.");
}
}
if (line.Length > 2)
{
attributes = int.Parse(line[2]);
}
if (line.Length > 3)
{
nodemarkers = int.Parse(line[3]);
}
// Read the vertices.
if (invertices > 0)
{
data = new Polygon(invertices);
for (int i = 0; i < invertices; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (vertices).");
}
if (line.Length < 3)
{
throw new Exception("Invalid vertex.");
}
if (i == 0)
{
// Set the start index!
startIndex = int.Parse(line[0], nfi);
}
ReadVertex(data.Points, i, line, attributes, nodemarkers);
}
}
else
{
// If the .poly file claims there are zero vertices, that means that
// the vertices should be read from a separate .node file.
data = ReadNodeFile(Path.ChangeExtension(polyfilename, ".node"));
invertices = data.Points.Count;
}
var points = data.Points;
if (points.Count == 0)
{
throw new Exception("No nodes available.");
}
// Read the segments from a .poly file.
// Read number of segments and number of boundary markers.
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (segments).");
}
int insegments = int.Parse(line[0]);
int segmentmarkers = 0;
if (line.Length > 1)
{
segmentmarkers = int.Parse(line[1]);
}
int end1, end2, mark;
// Read and insert the segments.
for (int i = 0; i < insegments; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (segments).");
}
if (line.Length < 3)
{
throw new Exception("Segment has no endpoints.");
}
// TODO: startIndex ok?
end1 = int.Parse(line[1]) - startIndex;
end2 = int.Parse(line[2]) - startIndex;
mark = 0;
if (segmentmarkers > 0 && line.Length > 3)
{
mark = int.Parse(line[3]);
}
if ((end1 < 0) || (end1 >= invertices))
{
if (Log.Verbose)
{
Log.Instance.Warning("Invalid first endpoint of segment.",
"MeshReader.ReadPolyfile()");
}
}
else if ((end2 < 0) || (end2 >= invertices))
{
if (Log.Verbose)
{
Log.Instance.Warning("Invalid second endpoint of segment.",
"MeshReader.ReadPolyfile()");
}
}
else
{
data.Add(new Segment(points[end1], points[end2], mark));
}
}
// Read holes from a .poly file.
// Read the holes.
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (holes).");
}
int holes = int.Parse(line[0]);
if (holes > 0)
{
for (int i = 0; i < holes; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (holes).");
}
if (line.Length < 3)
{
throw new Exception("Invalid hole.");
}
data.Holes.Add(new Point(double.Parse(line[1], nfi),
double.Parse(line[2], nfi)));
}
}
// Read area constraints (optional).
if (TryReadLine(reader, out line))
{
int id, regions = int.Parse(line[0]);
if (regions > 0)
{
for (int i = 0; i < regions; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (region).");
}
if (line.Length < 4)
{
throw new Exception("Invalid region attributes.");
}
if (!int.TryParse(line[3], out id))
{
id = i;
}
double area = 0.0;
if (line.Length > 4)
{
double.TryParse(line[4], NumberStyles.Number, nfi, out area);
}
// Triangle's .poly file format allows region definitions with
// either 4 or 5 parameters, and different interpretations for
// them depending on the number of parameters.
//
// See http://www.cs.cmu.edu/~quake/triangle.poly.html
//
// The .NET version will interpret the fourth parameter always
// as an integer region id and the optional fifth parameter as
// an area constraint.
data.Regions.Add(new RegionPointer(
double.Parse(line[1], nfi), // Region x
double.Parse(line[2], nfi), // Region y
id, area));
}
}
}
}
// Read ele file
if (readElements)
{
string elefile = Path.ChangeExtension(polyfilename, ".ele");
if (File.Exists(elefile))
{
ReadEleFile(elefile, readArea);
}
}
return data;
}
/// <summary>
/// Read elements from an .ele file.
/// </summary>
/// <param name="elefilename">The file name.</param>
/// <returns>A list of triangles.</returns>
public List<ITriangle> ReadEleFile(string elefilename)
{
return ReadEleFile(elefilename, false);
}
/// <summary>
/// Read the elements from an .ele file.
/// </summary>
/// <param name="elefilename"></param>
/// <param name="data"></param>
/// <param name="readArea"></param>
private List<ITriangle> ReadEleFile(string elefilename, bool readArea)
{
int intriangles = 0, attributes = 0;
List<ITriangle> triangles;
using (var reader = new StreamReader(elefilename))
{
// Read number of elements and number of attributes.
string[] line;
bool validRegion = false;
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (elements).");
}
intriangles = int.Parse(line[0]);
// We irgnore index 1 (number of nodes per triangle)
attributes = 0;
if (line.Length > 2)
{
attributes = int.Parse(line[2]);
validRegion = true;
}
if (attributes > 1)
{
Log.Instance.Warning("Triangle attributes not supported.", "FileReader.Read");
}
triangles = new List<ITriangle>(intriangles);
InputTriangle tri;
// Read triangles.
for (int i = 0; i < intriangles; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (elements).");
}
if (line.Length < 4)
{
throw new Exception("Triangle has no nodes.");
}
// TODO: startIndex ok?
tri = new InputTriangle(
int.Parse(line[1]) - startIndex,
int.Parse(line[2]) - startIndex,
int.Parse(line[3]) - startIndex);
// Read triangle region
if (attributes > 0 && validRegion)
{
int region = 0;
validRegion = int.TryParse(line[4], out region);
tri.label = region;
}
triangles.Add(tri);
}
}
// Read area file
if (readArea)
{
string areafile = Path.ChangeExtension(elefilename, ".area");
if (File.Exists(areafile))
{
ReadAreaFile(areafile, intriangles);
}
}
return triangles;
}
/// <summary>
/// Read the area constraints from an .area file.
/// </summary>
/// <param name="areafilename"></param>
/// <param name="intriangles"></param>
/// <param name="data"></param>
private double[] ReadAreaFile(string areafilename, int intriangles)
{
double[] data = null;
using (var reader = new StreamReader(areafilename))
{
string[] line;
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (area).");
}
if (int.Parse(line[0]) != intriangles)
{
Log.Instance.Warning("Number of area constraints doesn't match number of triangles.",
"ReadAreaFile()");
return null;
}
data = new double[intriangles];
// Read area constraints.
for (int i = 0; i < intriangles; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (area).");
}
if (line.Length != 2)
{
throw new Exception("Triangle has no nodes.");
}
data[i] = double.Parse(line[1], nfi);
}
}
return data;
}
/// <summary>
/// Read an .edge file.
/// </summary>
/// <param name="edgeFile">The file name.</param>
/// <param name="invertices">The number of input vertices (read from a .node or .poly file).</param>
/// <returns>A List of edges.</returns>
public List<Edge> ReadEdgeFile(string edgeFile, int invertices)
{
// Read poly file
List<Edge> data = null;
startIndex = 0;
string[] line;
using (var reader = new StreamReader(edgeFile))
{
// Read the edges from a .edge file.
// Read number of segments and number of boundary markers.
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (segments).");
}
int inedges = int.Parse(line[0]);
int edgemarkers = 0;
if (line.Length > 1)
{
edgemarkers = int.Parse(line[1]);
}
if (inedges > 0)
{
data = new List<Edge>(inedges);
}
int end1, end2, mark;
// Read and insert the segments.
for (int i = 0; i < inedges; i++)
{
if (!TryReadLine(reader, out line))
{
throw new Exception("Can't read input file (segments).");
}
if (line.Length < 3)
{
throw new Exception("Segment has no endpoints.");
}
// TODO: startIndex ok?
end1 = int.Parse(line[1]) - startIndex;
end2 = int.Parse(line[2]) - startIndex;
mark = 0;
if (edgemarkers > 0 && line.Length > 3)
{
mark = int.Parse(line[3]);
}
if ((end1 < 0) || (end1 >= invertices))
{
if (Log.Verbose)
{
Log.Instance.Warning("Invalid first endpoint of segment.",
"MeshReader.ReadPolyfile()");
}
}
else if ((end2 < 0) || (end2 >= invertices))
{
if (Log.Verbose)
{
Log.Instance.Warning("Invalid second endpoint of segment.",
"MeshReader.ReadPolyfile()");
}
}
else
{
data.Add(new Edge(end1, end2, mark));
}
}
}
return data;
}
bool IsStringNullOrWhiteSpace(string value)
{
if (value != null)
{
for (int i = 0; i < value.Length; i++)
{
if (!char.IsWhiteSpace(value[i]))
{
return false;
}
}
}
return true;
}
}
}

View File

@@ -0,0 +1,460 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleWriter.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.IO
{
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// Helper methods for writing Triangle file formats.
/// </summary>
internal class TriangleWriter
{
static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
/// <summary>
/// Number the vertices and write them to a .node file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public void Write(Mesh mesh, string filename)
{
WritePoly(mesh, Path.ChangeExtension(filename, ".poly"));
WriteElements(mesh, Path.ChangeExtension(filename, ".ele"));
}
/// <summary>
/// Number the vertices and write them to a .node file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public void WriteNodes(Mesh mesh, string filename)
{
using (var writer = new StreamWriter(filename))
{
WriteNodes(writer, mesh);
}
}
/// <summary>
/// Number the vertices and write them to a .node file.
/// </summary>
private void WriteNodes(StreamWriter writer, Mesh mesh)
{
int outvertices = mesh.vertices.Count;
int nextras = mesh.nextras;
Behavior behavior = mesh.behavior;
if (behavior.Jettison)
{
outvertices = mesh.vertices.Count - mesh.undeads;
}
if (writer != null)
{
// Number of vertices, number of dimensions, number of vertex attributes,
// and number of boundary markers (zero or one).
writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, nextras,
behavior.UseBoundaryMarkers ? "1" : "0");
if (mesh.numbering == NodeNumbering.None)
{
// If the mesh isn't numbered yet, use linear node numbering.
mesh.Renumber();
}
if (mesh.numbering == NodeNumbering.Linear)
{
// If numbering is linear, just use the dictionary values.
WriteNodes(writer, mesh.vertices.Values, behavior.UseBoundaryMarkers,
nextras, behavior.Jettison);
}
else
{
// If numbering is not linear, a simple 'foreach' traversal of the dictionary
// values doesn't reflect the actual numbering. Use an array instead.
// TODO: Could use a custom sorting function on dictionary values instead.
Vertex[] nodes = new Vertex[mesh.vertices.Count];
foreach (var node in mesh.vertices.Values)
{
nodes[node.id] = node;
}
WriteNodes(writer, nodes, behavior.UseBoundaryMarkers,
nextras, behavior.Jettison);
}
}
}
/// <summary>
/// Write the vertices to a stream.
/// </summary>
/// <param name="nodes"></param>
/// <param name="writer"></param>
private void WriteNodes(StreamWriter writer, IEnumerable<Vertex> nodes, bool markers,
int attribs, bool jettison)
{
int index = 0;
foreach (var vertex in nodes)
{
if (!jettison || vertex.type != VertexType.UndeadVertex)
{
// Vertex number, x and y coordinates.
writer.Write("{0} {1} {2}", index, vertex.x.ToString(nfi), vertex.y.ToString(nfi));
#if USE_ATTRIBS
// Write attributes.
for (int j = 0; j < attribs; j++)
{
writer.Write(" {0}", vertex.attributes[j].ToString(nfi));
}
#endif
if (markers)
{
// Write the boundary marker.
writer.Write(" {0}", vertex.label);
}
writer.WriteLine();
index++;
}
}
}
/// <summary>
/// Write the triangles to an .ele file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public void WriteElements(Mesh mesh, string filename)
{
Otri tri = default(Otri);
Vertex p1, p2, p3;
bool regions = mesh.behavior.useRegions;
int j = 0;
tri.orient = 0;
using (var writer = new StreamWriter(filename))
{
// Number of triangles, vertices per triangle, attributes per triangle.
writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, regions ? 1 : 0);
foreach (var item in mesh.triangles)
{
tri.tri = item;
p1 = tri.Org();
p2 = tri.Dest();
p3 = tri.Apex();
// Triangle number, indices for three vertices.
writer.Write("{0} {1} {2} {3}", j, p1.id, p2.id, p3.id);
if (regions)
{
writer.Write(" {0}", tri.tri.label);
}
writer.WriteLine();
// Number elements
item.id = j++;
}
}
}
/// <summary>
/// Write the segments and holes to a .poly file.
/// </summary>
/// <param name="polygon">Data source.</param>
/// <param name="filename">File name.</param>
/// <param name="writeNodes">Write nodes into this file.</param>
/// <remarks>If the nodes should not be written into this file,
/// make sure a .node file was written before, so that the nodes
/// are numbered right.</remarks>
public void WritePoly(IPolygon polygon, string filename)
{
bool hasMarkers = polygon.HasSegmentMarkers;
using (var writer = new StreamWriter(filename))
{
// TODO: write vertex attributes
writer.WriteLine("{0} 2 0 {1}", polygon.Points.Count, polygon.HasPointMarkers ? "1" : "0");
// Write nodes to this file.
WriteNodes(writer, polygon.Points, polygon.HasPointMarkers, 0, false);
// Number of segments, number of boundary markers (zero or one).
writer.WriteLine("{0} {1}", polygon.Segments.Count, hasMarkers ? "1" : "0");
Vertex p, q;
int j = 0;
foreach (var seg in polygon.Segments)
{
p = seg.GetVertex(0);
q = seg.GetVertex(1);
// Segment number, indices of its two endpoints, and possibly a marker.
if (hasMarkers)
{
writer.WriteLine("{0} {1} {2} {3}", j, p.ID, q.ID, seg.Label);
}
else
{
writer.WriteLine("{0} {1} {2}", j, p.ID, q.ID);
}
j++;
}
// Holes
j = 0;
writer.WriteLine("{0}", polygon.Holes.Count);
foreach (var hole in polygon.Holes)
{
writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi));
}
// Regions
if (polygon.Regions.Count > 0)
{
j = 0;
writer.WriteLine("{0}", polygon.Regions.Count);
foreach (var region in polygon.Regions)
{
writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi),
region.point.Y.ToString(nfi), region.id);
j++;
}
}
}
}
/// <summary>
/// Write the segments and holes to a .poly file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public void WritePoly(Mesh mesh, string filename)
{
WritePoly(mesh, filename, true);
}
/// <summary>
/// Write the segments and holes to a .poly file.
/// </summary>
/// <param name="mesh">Data source.</param>
/// <param name="filename">File name.</param>
/// <param name="writeNodes">Write nodes into this file.</param>
/// <remarks>If the nodes should not be written into this file,
/// make sure a .node file was written before, so that the nodes
/// are numbered right.</remarks>
public void WritePoly(Mesh mesh, string filename, bool writeNodes)
{
Osub subseg = default(Osub);
Vertex pt1, pt2;
bool useBoundaryMarkers = mesh.behavior.UseBoundaryMarkers;
using (var writer = new StreamWriter(filename))
{
if (writeNodes)
{
// Write nodes to this file.
WriteNodes(writer, mesh);
}
else
{
// The zero indicates that the vertices are in a separate .node file.
// Followed by number of dimensions, number of vertex attributes,
// and number of boundary markers (zero or one).
writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras,
useBoundaryMarkers ? "1" : "0");
}
// Number of segments, number of boundary markers (zero or one).
writer.WriteLine("{0} {1}", mesh.subsegs.Count,
useBoundaryMarkers ? "1" : "0");
subseg.orient = 0;
int j = 0;
foreach (var item in mesh.subsegs.Values)
{
subseg.seg = item;
pt1 = subseg.Org();
pt2 = subseg.Dest();
// Segment number, indices of its two endpoints, and possibly a marker.
if (useBoundaryMarkers)
{
writer.WriteLine("{0} {1} {2} {3}", j, pt1.id, pt2.id, subseg.seg.boundary);
}
else
{
writer.WriteLine("{0} {1} {2}", j, pt1.id, pt2.id);
}
j++;
}
// Holes
j = 0;
writer.WriteLine("{0}", mesh.holes.Count);
foreach (var hole in mesh.holes)
{
writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi));
}
// Regions
if (mesh.regions.Count > 0)
{
j = 0;
writer.WriteLine("{0}", mesh.regions.Count);
foreach (var region in mesh.regions)
{
writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi),
region.point.Y.ToString(nfi), region.id);
j++;
}
}
}
}
/// <summary>
/// Write the edges to an .edge file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public void WriteEdges(Mesh mesh, string filename)
{
Otri tri = default(Otri), trisym = default(Otri);
Osub checkmark = default(Osub);
Vertex p1, p2;
Behavior behavior = mesh.behavior;
using (var writer = new StreamWriter(filename))
{
// Number of edges, number of boundary markers (zero or one).
writer.WriteLine("{0} {1}", mesh.NumberOfEdges, behavior.UseBoundaryMarkers ? "1" : "0");
long index = 0;
// To loop over the set of edges, loop over all triangles, and look at
// the three edges of each triangle. If there isn't another triangle
// adjacent to the edge, operate on the edge. If there is another
// adjacent triangle, operate on the edge only if the current triangle
// has a smaller pointer than its neighbor. This way, each edge is
// considered only once.
foreach (var item in mesh.triangles)
{
tri.tri = item;
for (tri.orient = 0; tri.orient < 3; tri.orient++)
{
tri.Sym(ref trisym);
if ((tri.tri.id < trisym.tri.id) || (trisym.tri.id == Mesh.DUMMY))
{
p1 = tri.Org();
p2 = tri.Dest();
if (behavior.UseBoundaryMarkers)
{
// Edge number, indices of two endpoints, and a boundary marker.
// If there's no subsegment, the boundary marker is zero.
if (behavior.useSegments)
{
tri.Pivot(ref checkmark);
if (checkmark.seg.hash == Mesh.DUMMY)
{
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, 0);
}
else
{
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id,
checkmark.seg.boundary);
}
}
else
{
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id,
trisym.tri.id == Mesh.DUMMY ? "1" : "0");
}
}
else
{
// Edge number, indices of two endpoints.
writer.WriteLine("{0} {1} {2}", index, p1.id, p2.id);
}
index++;
}
}
}
}
}
/// <summary>
/// Write the triangle neighbors to a .neigh file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
/// <remarks>WARNING: Be sure WriteElements has been called before,
/// so the elements are numbered right!</remarks>
public void WriteNeighbors(Mesh mesh, string filename)
{
Otri tri = default(Otri), trisym = default(Otri);
int n1, n2, n3;
int i = 0;
using (StreamWriter writer = new StreamWriter(filename))
{
// Number of triangles, three neighbors per triangle.
writer.WriteLine("{0} 3", mesh.triangles.Count);
foreach (var item in mesh.triangles)
{
tri.tri = item;
tri.orient = 1;
tri.Sym(ref trisym);
n1 = trisym.tri.id;
tri.orient = 2;
tri.Sym(ref trisym);
n2 = trisym.tri.id;
tri.orient = 0;
tri.Sym(ref trisym);
n3 = trisym.tri.id;
// Triangle number, neighboring triangle numbers.
writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3);
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="IPredicates.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using Animation.TriangleNet.Geometry;
internal interface IPredicates
{
double CounterClockwise(Point a, Point b, Point c);
double InCircle(Point a, Point b, Point c, Point p);
Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta);
Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta,
double offconstant);
}
}

View File

@@ -0,0 +1,84 @@
// -----------------------------------------------------------------------
// <copyright file="Log.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System.Collections.Generic;
using Animation.TriangleNet.Logging;
/// <summary>
/// A simple logger, which logs messages to a List.
/// </summary>
/// <remarks>Using singleton pattern as proposed by Jon Skeet.
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </remarks>
internal sealed class Log : ILog<LogItem>
{
/// <summary>
/// Log detailed information.
/// </summary>
internal static bool Verbose { get; set; }
private List<LogItem> log = new List<LogItem>();
private LogLevel level = LogLevel.Info;
#region Singleton pattern
private static readonly Log instance = new Log();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Log() {}
private Log() {}
internal static ILog<LogItem> Instance
{
get
{
return instance;
}
}
#endregion
public void Add(LogItem item)
{
log.Add(item);
}
public void Clear()
{
log.Clear();
}
public void Info(string message)
{
log.Add(new LogItem(LogLevel.Info, message));
}
public void Warning(string message, string location)
{
log.Add(new LogItem(LogLevel.Warning, message, location));
}
public void Error(string message, string location)
{
log.Add(new LogItem(LogLevel.Error, message, location));
}
public IList<LogItem> Data
{
get { return log; }
}
public LogLevel Level
{
get { return level; }
}
}
}

View File

@@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="ILog.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Logging
{
using System.Collections.Generic;
internal enum LogLevel
{
Info = 0,
Warning = 1,
Error = 2
}
/// <summary>
/// A basic log interface.
/// </summary>
internal interface ILog<T> where T : ILogItem
{
void Add(T item);
void Clear();
void Info(string message);
void Error(string message, string info);
void Warning(string message, string info);
IList<T> Data { get; }
LogLevel Level { get; }
}
}

View File

@@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="ILogItem.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Logging
{
using System;
/// <summary>
/// A basic log item interface.
/// </summary>
internal interface ILogItem
{
DateTime Time { get; }
LogLevel Level { get; }
string Message { get; }
string Info { get; }
}
}

View File

@@ -0,0 +1,54 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleLogItem.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Logging
{
using System;
/// <summary>
/// Represents an item stored in the log.
/// </summary>
internal class LogItem : ILogItem
{
DateTime time;
LogLevel level;
string message;
string info;
public DateTime Time
{
get { return time; }
}
public LogLevel Level
{
get { return level; }
}
public string Message
{
get { return message; }
}
public string Info
{
get { return info; }
}
public LogItem(LogLevel level, string message)
: this(level, message, "")
{}
public LogItem(LogLevel level, string message, string info)
{
this.time = DateTime.Now;
this.level = level;
this.message = message;
this.info = info;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
// -----------------------------------------------------------------------
// <copyright file="MeshValidator.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
internal static class MeshValidator
{
private static RobustPredicates predicates = RobustPredicates.Default;
/// <summary>
/// Test the mesh for topological consistency.
/// </summary>
internal static bool IsConsistent(Mesh mesh)
{
Otri tri = default(Otri);
Otri oppotri = default(Otri), oppooppotri = default(Otri);
Vertex org, dest, apex;
Vertex oppoorg, oppodest;
var logger = Log.Instance;
// Temporarily turn on exact arithmetic if it's off.
bool saveexact = Behavior.NoExact;
Behavior.NoExact = false;
int horrors = 0;
// Run through the list of triangles, checking each one.
foreach (var t in mesh.triangles)
{
tri.tri = t;
// Check all three edges of the triangle.
for (tri.orient = 0; tri.orient < 3; tri.orient++)
{
org = tri.Org();
dest = tri.Dest();
if (tri.orient == 0)
{
// Only test for inversion once.
// Test if the triangle is flat or inverted.
apex = tri.Apex();
if (predicates.CounterClockwise(org, dest, apex) <= 0.0)
{
if (Log.Verbose)
{
logger.Warning(String.Format("Triangle is flat or inverted (ID {0}).", t.id),
"MeshValidator.IsConsistent()");
}
horrors++;
}
}
// Find the neighboring triangle on this edge.
tri.Sym(ref oppotri);
if (oppotri.tri.id != Mesh.DUMMY)
{
// Check that the triangle's neighbor knows it's a neighbor.
oppotri.Sym(ref oppooppotri);
if ((tri.tri != oppooppotri.tri) || (tri.orient != oppooppotri.orient))
{
if (tri.tri == oppooppotri.tri && Log.Verbose)
{
logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)",
"MeshValidator.IsConsistent()");
}
horrors++;
}
// Check that both triangles agree on the identities
// of their shared vertices.
oppoorg = oppotri.Org();
oppodest = oppotri.Dest();
if ((org != oppodest) || (dest != oppoorg))
{
if (Log.Verbose)
{
logger.Warning("Mismatched edge coordinates between two triangles.",
"MeshValidator.IsConsistent()");
}
horrors++;
}
}
}
}
// Check for unconnected vertices
mesh.MakeVertexMap();
foreach (var v in mesh.vertices.Values)
{
if (v.tri.tri == null && Log.Verbose)
{
logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)",
"MeshValidator.IsConsistent()");
}
}
// Restore the status of exact arithmetic.
Behavior.NoExact = saveexact;
return (horrors == 0);
}
/// <summary>
/// Check if the mesh is (conforming) Delaunay.
/// </summary>
internal static bool IsDelaunay(Mesh mesh)
{
return IsDelaunay(mesh, false);
}
/// <summary>
/// Check if that the mesh is (constrained) Delaunay.
/// </summary>
internal static bool IsConstrainedDelaunay(Mesh mesh)
{
return IsDelaunay(mesh, true);
}
/// <summary>
/// Ensure that the mesh is (constrained) Delaunay.
/// </summary>
private static bool IsDelaunay(Mesh mesh, bool constrained)
{
Otri loop = default(Otri);
Otri oppotri = default(Otri);
Osub opposubseg = default(Osub);
Vertex org, dest, apex;
Vertex oppoapex;
bool shouldbedelaunay;
var logger = Log.Instance;
// Temporarily turn on exact arithmetic if it's off.
bool saveexact = Behavior.NoExact;
Behavior.NoExact = false;
int horrors = 0;
var inf1 = mesh.infvertex1;
var inf2 = mesh.infvertex2;
var inf3 = mesh.infvertex3;
// Run through the list of triangles, checking each one.
foreach (var tri in mesh.triangles)
{
loop.tri = tri;
// Check all three edges of the triangle.
for (loop.orient = 0; loop.orient < 3; loop.orient++)
{
org = loop.Org();
dest = loop.Dest();
apex = loop.Apex();
loop.Sym(ref oppotri);
oppoapex = oppotri.Apex();
// Only test that the edge is locally Delaunay if there is an
// adjoining triangle whose pointer is larger (to ensure that
// each pair isn't tested twice).
shouldbedelaunay = (loop.tri.id < oppotri.tri.id) &&
!Otri.IsDead(oppotri.tri) && (oppotri.tri.id != Mesh.DUMMY) &&
(org != inf1) && (org != inf2) && (org != inf3) &&
(dest != inf1) && (dest != inf2) && (dest != inf3) &&
(apex != inf1) && (apex != inf2) && (apex != inf3) &&
(oppoapex != inf1) && (oppoapex != inf2) && (oppoapex != inf3);
if (constrained && mesh.checksegments && shouldbedelaunay)
{
// If a subsegment separates the triangles, then the edge is
// constrained, so no local Delaunay test should be done.
loop.Pivot(ref opposubseg);
if (opposubseg.seg.hash != Mesh.DUMMY)
{
shouldbedelaunay = false;
}
}
if (shouldbedelaunay)
{
if (predicates.NonRegular(org, dest, apex, oppoapex) > 0.0)
{
if (Log.Verbose)
{
logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).",
loop.tri.id, oppotri.tri.id), "MeshValidator.IsDelaunay()");
}
horrors++;
}
}
}
}
// Restore the status of exact arithmetic.
Behavior.NoExact = saveexact;
return (horrors == 0);
}
}
}

View File

@@ -0,0 +1,697 @@
// -----------------------------------------------------------------------
// <copyright file="Dwyer.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology;
/// <summary>
/// Builds a delaunay triangulation using the divide-and-conquer algorithm.
/// </summary>
/// <remarks>
/// The divide-and-conquer bounding box
///
/// I originally implemented the divide-and-conquer and incremental Delaunay
/// triangulations using the edge-based data structure presented by Guibas
/// and Stolfi. Switching to a triangle-based data structure doubled the
/// speed. However, I had to think of a few extra tricks to maintain the
/// elegance of the original algorithms.
///
/// The "bounding box" used by my variant of the divide-and-conquer
/// algorithm uses one triangle for each edge of the convex hull of the
/// triangulation. These bounding triangles all share a common apical
/// vertex, which is represented by NULL and which represents nothing.
/// The bounding triangles are linked in a circular fan about this NULL
/// vertex, and the edges on the convex hull of the triangulation appear
/// opposite the NULL vertex. You might find it easiest to imagine that
/// the NULL vertex is a point in 3D space behind the center of the
/// triangulation, and that the bounding triangles form a sort of cone.
///
/// This bounding box makes it easy to represent degenerate cases. For
/// instance, the triangulation of two vertices is a single edge. This edge
/// is represented by two bounding box triangles, one on each "side" of the
/// edge. These triangles are also linked together in a fan about the NULL
/// vertex.
///
/// The bounding box also makes it easy to traverse the convex hull, as the
/// divide-and-conquer algorithm needs to do.
/// </remarks>
internal class Dwyer : ITriangulator
{
// Random is not threadsafe, so don't make this static.
// Random rand = new Random(DateTime.Now.Millisecond);
IPredicates predicates;
public bool UseDwyer = true;
Vertex[] sortarray;
Mesh mesh;
/// <summary>
/// Form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <returns></returns>
/// <remarks>
/// Sorts the vertices, calls a recursive procedure to triangulate them, and
/// removes the bounding box, setting boundary markers as appropriate.
/// </remarks>
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.predicates = config.Predicates();
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
Otri hullleft = default(Otri), hullright = default(Otri);
int i, j, n = points.Count;
// Allocate an array of pointers to vertices for sorting.
this.sortarray = new Vertex[n];
i = 0;
foreach (var v in points)
{
sortarray[i++] = v;
}
// Sort the vertices.
VertexSorter.Sort(sortarray);
// Discard duplicate vertices, which can really mess up the algorithm.
i = 0;
for (j = 1; j < n; j++)
{
if ((sortarray[i].x == sortarray[j].x) && (sortarray[i].y == sortarray[j].y))
{
if (Log.Verbose)
{
Log.Instance.Warning(
String.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].id),
"Dwyer.Triangulate()");
}
sortarray[j].type = VertexType.UndeadVertex;
mesh.undeads++;
}
else
{
i++;
sortarray[i] = sortarray[j];
}
}
i++;
if (UseDwyer)
{
// Re-sort the array of vertices to accommodate alternating cuts.
VertexSorter.Alternate(sortarray, i);
}
// Form the Delaunay triangulation.
DivconqRecurse(0, i - 1, 0, ref hullleft, ref hullright);
this.mesh.hullsize = RemoveGhosts(ref hullleft);
return this.mesh;
}
/// <summary>
/// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation.
/// </summary>
/// <param name="farleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerright">Bounding triangles of the right triangulation.</param>
/// <param name="farright">Bounding triangles of the right triangulation.</param>
/// <param name="axis"></param>
/// <remarks>
/// This is similar to the algorithm given by Guibas and Stolfi, but uses
/// a triangle-based, rather than edge-based, data structure.
///
/// The algorithm walks up the gap between the two triangulations, knitting
/// them together. As they are merged, some of their bounding triangles
/// are converted into real triangles of the triangulation. The procedure
/// pulls each hull's bounding triangles apart, then knits them together
/// like the teeth of two gears. The Delaunay property determines, at each
/// step, whether the next "tooth" is a bounding triangle of the left hull
/// or the right. When a bounding triangle becomes real, its apex is
/// changed from NULL to a real vertex.
///
/// Only two new triangles need to be allocated. These become new bounding
/// triangles at the top and bottom of the seam. They are used to connect
/// the remaining bounding triangles (those that have not been converted
/// into real triangles) into a single fan.
///
/// On label, 'farleft' and 'innerleft' are bounding triangles of the left
/// triangulation. The origin of 'farleft' is the leftmost vertex, and
/// the destination of 'innerleft' is the rightmost vertex of the
/// triangulation. Similarly, 'innerright' and 'farright' are bounding
/// triangles of the right triangulation. The origin of 'innerright' and
/// destination of 'farright' are the leftmost and rightmost vertices.
///
/// On completion, the origin of 'farleft' is the leftmost vertex of the
/// merged triangulation, and the destination of 'farright' is the rightmost
/// vertex.
/// </remarks>
void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright,
ref Otri farright, int axis)
{
Otri leftcand = default(Otri), rightcand = default(Otri);
Otri nextedge = default(Otri);
Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri);
Otri checkedge = default(Otri);
Otri baseedge = default(Otri);
Vertex innerleftdest;
Vertex innerrightorg;
Vertex innerleftapex, innerrightapex;
Vertex farleftpt, farrightpt;
Vertex farleftapex, farrightapex;
Vertex lowerleft, lowerright;
Vertex upperleft, upperright;
Vertex nextapex;
Vertex checkvertex;
bool changemade;
bool badedge;
bool leftfinished, rightfinished;
innerleftdest = innerleft.Dest();
innerleftapex = innerleft.Apex();
innerrightorg = innerright.Org();
innerrightapex = innerright.Apex();
// Special treatment for horizontal cuts.
if (UseDwyer && (axis == 1))
{
farleftpt = farleft.Org();
farleftapex = farleft.Apex();
farrightpt = farright.Dest();
farrightapex = farright.Apex();
// The pointers to the extremal vertices are shifted to point to the
// topmost and bottommost vertex of each hull, rather than the
// leftmost and rightmost vertices.
while (farleftapex.y < farleftpt.y)
{
farleft.Lnext();
farleft.Sym();
farleftpt = farleftapex;
farleftapex = farleft.Apex();
}
innerleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
while (checkvertex.y > innerleftdest.y)
{
checkedge.Lnext(ref innerleft);
innerleftapex = innerleftdest;
innerleftdest = checkvertex;
innerleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
while (innerrightapex.y < innerrightorg.y)
{
innerright.Lnext();
innerright.Sym();
innerrightorg = innerrightapex;
innerrightapex = innerright.Apex();
}
farright.Sym(ref checkedge);
checkvertex = checkedge.Apex();
while (checkvertex.y > farrightpt.y)
{
checkedge.Lnext(ref farright);
farrightapex = farrightpt;
farrightpt = checkvertex;
farright.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
}
// Find a line tangent to and below both hulls.
do
{
changemade = false;
// Make innerleftdest the "bottommost" vertex of the left hull.
if (predicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0)
{
innerleft.Lprev();
innerleft.Sym();
innerleftdest = innerleftapex;
innerleftapex = innerleft.Apex();
changemade = true;
}
// Make innerrightorg the "bottommost" vertex of the right hull.
if (predicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0)
{
innerright.Lnext();
innerright.Sym();
innerrightorg = innerrightapex;
innerrightapex = innerright.Apex();
changemade = true;
}
}
while (changemade);
// Find the two candidates to be the next "gear tooth."
innerleft.Sym(ref leftcand);
innerright.Sym(ref rightcand);
// Create the bottom new bounding triangle.
mesh.MakeTriangle(ref baseedge);
// Connect it to the bounding boxes of the left and right triangulations.
baseedge.Bond(ref innerleft);
baseedge.Lnext();
baseedge.Bond(ref innerright);
baseedge.Lnext();
baseedge.SetOrg(innerrightorg);
baseedge.SetDest(innerleftdest);
// Apex is intentionally left NULL.
// Fix the extreme triangles if necessary.
farleftpt = farleft.Org();
if (innerleftdest == farleftpt)
{
baseedge.Lnext(ref farleft);
}
farrightpt = farright.Dest();
if (innerrightorg == farrightpt)
{
baseedge.Lprev(ref farright);
}
// The vertices of the current knitting edge.
lowerleft = innerleftdest;
lowerright = innerrightorg;
// The candidate vertices for knitting.
upperleft = leftcand.Apex();
upperright = rightcand.Apex();
// Walk up the gap between the two triangulations, knitting them together.
while (true)
{
// Have we reached the top? (This isn't quite the right question,
// because even though the left triangulation might seem finished now,
// moving up on the right triangulation might reveal a new vertex of
// the left triangulation. And vice-versa.)
leftfinished = predicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0;
rightfinished = predicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0;
if (leftfinished && rightfinished)
{
// Create the top new bounding triangle.
mesh.MakeTriangle(ref nextedge);
nextedge.SetOrg(lowerleft);
nextedge.SetDest(lowerright);
// Apex is intentionally left NULL.
// Connect it to the bounding boxes of the two triangulations.
nextedge.Bond(ref baseedge);
nextedge.Lnext();
nextedge.Bond(ref rightcand);
nextedge.Lnext();
nextedge.Bond(ref leftcand);
// Special treatment for horizontal cuts.
if (UseDwyer && (axis == 1))
{
farleftpt = farleft.Org();
farleftapex = farleft.Apex();
farrightpt = farright.Dest();
farrightapex = farright.Apex();
farleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
// The pointers to the extremal vertices are restored to the
// leftmost and rightmost vertices (rather than topmost and
// bottommost).
while (checkvertex.x < farleftpt.x)
{
checkedge.Lprev(ref farleft);
farleftapex = farleftpt;
farleftpt = checkvertex;
farleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
while (farrightapex.x > farrightpt.x)
{
farright.Lprev();
farright.Sym();
farrightpt = farrightapex;
farrightapex = farright.Apex();
}
}
return;
}
// Consider eliminating edges from the left triangulation.
if (!leftfinished)
{
// What vertex would be exposed if an edge were deleted?
leftcand.Lprev(ref nextedge);
nextedge.Sym();
nextapex = nextedge.Apex();
// If nextapex is NULL, then no vertex would be exposed; the
// triangulation would have been eaten right through.
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
while (badedge)
{
// Eliminate the edge with an edge flip. As a result, the
// left triangulation will have one more boundary triangle.
nextedge.Lnext();
nextedge.Sym(ref topcasing);
nextedge.Lnext();
nextedge.Sym(ref sidecasing);
nextedge.Bond(ref topcasing);
leftcand.Bond(ref sidecasing);
leftcand.Lnext();
leftcand.Sym(ref outercasing);
nextedge.Lprev();
nextedge.Bond(ref outercasing);
// Correct the vertices to reflect the edge flip.
leftcand.SetOrg(lowerleft);
leftcand.SetDest(null);
leftcand.SetApex(nextapex);
nextedge.SetOrg(null);
nextedge.SetDest(upperleft);
nextedge.SetApex(nextapex);
// Consider the newly exposed vertex.
upperleft = nextapex;
// What vertex would be exposed if another edge were deleted?
sidecasing.Copy(ref nextedge);
nextapex = nextedge.Apex();
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
}
else
{
// Avoid eating right through the triangulation.
badedge = false;
}
}
}
}
// Consider eliminating edges from the right triangulation.
if (!rightfinished)
{
// What vertex would be exposed if an edge were deleted?
rightcand.Lnext(ref nextedge);
nextedge.Sym();
nextapex = nextedge.Apex();
// If nextapex is NULL, then no vertex would be exposed; the
// triangulation would have been eaten right through.
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
while (badedge)
{
// Eliminate the edge with an edge flip. As a result, the
// right triangulation will have one more boundary triangle.
nextedge.Lprev();
nextedge.Sym(ref topcasing);
nextedge.Lprev();
nextedge.Sym(ref sidecasing);
nextedge.Bond(ref topcasing);
rightcand.Bond(ref sidecasing);
rightcand.Lprev();
rightcand.Sym(ref outercasing);
nextedge.Lnext();
nextedge.Bond(ref outercasing);
// Correct the vertices to reflect the edge flip.
rightcand.SetOrg(null);
rightcand.SetDest(lowerright);
rightcand.SetApex(nextapex);
nextedge.SetOrg(upperright);
nextedge.SetDest(null);
nextedge.SetApex(nextapex);
// Consider the newly exposed vertex.
upperright = nextapex;
// What vertex would be exposed if another edge were deleted?
sidecasing.Copy(ref nextedge);
nextapex = nextedge.Apex();
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
}
else
{
// Avoid eating right through the triangulation.
badedge = false;
}
}
}
}
if (leftfinished || (!rightfinished &&
(predicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0)))
{
// Knit the triangulations, adding an edge from 'lowerleft'
// to 'upperright'.
baseedge.Bond(ref rightcand);
rightcand.Lprev(ref baseedge);
baseedge.SetDest(lowerleft);
lowerright = upperright;
baseedge.Sym(ref rightcand);
upperright = rightcand.Apex();
}
else
{
// Knit the triangulations, adding an edge from 'upperleft'
// to 'lowerright'.
baseedge.Bond(ref leftcand);
leftcand.Lnext(ref baseedge);
baseedge.SetOrg(lowerright);
lowerleft = upperleft;
baseedge.Sym(ref leftcand);
upperleft = leftcand.Apex();
}
}
}
/// <summary>
/// Recursively form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <param name="farleft"></param>
/// <param name="farright"></param>
/// <remarks>
/// Recursively breaks down the problem into smaller pieces, which are
/// knitted together by mergehulls(). The base cases (problems of two or
/// three vertices) are handled specially here.
///
/// On completion, 'farleft' and 'farright' are bounding triangles such that
/// the origin of 'farleft' is the leftmost vertex (breaking ties by
/// choosing the highest leftmost vertex), and the destination of
/// 'farright' is the rightmost vertex (breaking ties by choosing the
/// lowest rightmost vertex).
/// </remarks>
void DivconqRecurse(int left, int right, int axis,
ref Otri farleft, ref Otri farright)
{
Otri midtri = default(Otri);
Otri tri1 = default(Otri);
Otri tri2 = default(Otri);
Otri tri3 = default(Otri);
Otri innerleft = default(Otri), innerright = default(Otri);
double area;
int vertices = right - left + 1;
int divider;
if (vertices == 2)
{
// The triangulation of two vertices is an edge. An edge is
// represented by two bounding triangles.
mesh.MakeTriangle(ref farleft);
farleft.SetOrg(sortarray[left]);
farleft.SetDest(sortarray[left + 1]);
// The apex is intentionally left NULL.
mesh.MakeTriangle(ref farright);
farright.SetOrg(sortarray[left + 1]);
farright.SetDest(sortarray[left]);
// The apex is intentionally left NULL.
farleft.Bond(ref farright);
farleft.Lprev();
farright.Lnext();
farleft.Bond(ref farright);
farleft.Lprev();
farright.Lnext();
farleft.Bond(ref farright);
// Ensure that the origin of 'farleft' is sortarray[0].
farright.Lprev(ref farleft);
return;
}
else if (vertices == 3)
{
// The triangulation of three vertices is either a triangle (with
// three bounding triangles) or two edges (with four bounding
// triangles). In either case, four triangles are created.
mesh.MakeTriangle(ref midtri);
mesh.MakeTriangle(ref tri1);
mesh.MakeTriangle(ref tri2);
mesh.MakeTriangle(ref tri3);
area = predicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]);
if (area == 0.0)
{
// Three collinear vertices; the triangulation is two edges.
midtri.SetOrg(sortarray[left]);
midtri.SetDest(sortarray[left + 1]);
tri1.SetOrg(sortarray[left + 1]);
tri1.SetDest(sortarray[left]);
tri2.SetOrg(sortarray[left + 2]);
tri2.SetDest(sortarray[left + 1]);
tri3.SetOrg(sortarray[left + 1]);
tri3.SetDest(sortarray[left + 2]);
// All apices are intentionally left NULL.
midtri.Bond(ref tri1);
tri2.Bond(ref tri3);
midtri.Lnext();
tri1.Lprev();
tri2.Lnext();
tri3.Lprev();
midtri.Bond(ref tri3);
tri1.Bond(ref tri2);
midtri.Lnext();
tri1.Lprev();
tri2.Lnext();
tri3.Lprev();
midtri.Bond(ref tri1);
tri2.Bond(ref tri3);
// Ensure that the origin of 'farleft' is sortarray[0].
tri1.Copy(ref farleft);
// Ensure that the destination of 'farright' is sortarray[2].
tri2.Copy(ref farright);
}
else
{
// The three vertices are not collinear; the triangulation is one
// triangle, namely 'midtri'.
midtri.SetOrg(sortarray[left]);
tri1.SetDest(sortarray[left]);
tri3.SetOrg(sortarray[left]);
// Apices of tri1, tri2, and tri3 are left NULL.
if (area > 0.0)
{
// The vertices are in counterclockwise order.
midtri.SetDest(sortarray[left + 1]);
tri1.SetOrg(sortarray[left + 1]);
tri2.SetDest(sortarray[left + 1]);
midtri.SetApex(sortarray[left + 2]);
tri2.SetOrg(sortarray[left + 2]);
tri3.SetDest(sortarray[left + 2]);
}
else
{
// The vertices are in clockwise order.
midtri.SetDest(sortarray[left + 2]);
tri1.SetOrg(sortarray[left + 2]);
tri2.SetDest(sortarray[left + 2]);
midtri.SetApex(sortarray[left + 1]);
tri2.SetOrg(sortarray[left + 1]);
tri3.SetDest(sortarray[left + 1]);
}
// The topology does not depend on how the vertices are ordered.
midtri.Bond(ref tri1);
midtri.Lnext();
midtri.Bond(ref tri2);
midtri.Lnext();
midtri.Bond(ref tri3);
tri1.Lprev();
tri2.Lnext();
tri1.Bond(ref tri2);
tri1.Lprev();
tri3.Lprev();
tri1.Bond(ref tri3);
tri2.Lnext();
tri3.Lprev();
tri2.Bond(ref tri3);
// Ensure that the origin of 'farleft' is sortarray[0].
tri1.Copy(ref farleft);
// Ensure that the destination of 'farright' is sortarray[2].
if (area > 0.0)
{
tri2.Copy(ref farright);
}
else
{
farleft.Lnext(ref farright);
}
}
return;
}
else
{
// Split the vertices in half.
divider = vertices >> 1;
// Recursively triangulate each half.
DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft);
DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright);
// Merge the two triangulations into one.
MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis);
}
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
int RemoveGhosts(ref Otri startghost)
{
Otri searchedge = default(Otri);
Otri dissolveedge = default(Otri);
Otri deadtriangle = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
// Find an edge on the convex hull to start point location from.
startghost.Lprev(ref searchedge);
searchedge.Sym();
mesh.dummytri.neighbors[0] = searchedge;
// Remove the bounding box and count the convex hull edges.
startghost.Copy(ref dissolveedge);
hullsize = 0;
do
{
hullsize++;
dissolveedge.Lnext(ref deadtriangle);
dissolveedge.Lprev();
dissolveedge.Sym();
// If no PSLG is involved, set the boundary markers of all the vertices
// on the convex hull. If a PSLG is used, this step is done later.
if (noPoly)
{
// Watch out for the case where all the input vertices are collinear.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Remove a bounding triangle from a convex hull triangle.
dissolveedge.Dissolve(mesh.dummytri);
// Find the next bounding triangle.
deadtriangle.Sym(ref dissolveedge);
// Delete the bounding triangle.
mesh.TriangleDealloc(deadtriangle.tri);
}
while (!dissolveedge.Equals(startghost));
return hullsize;
}
}
}

View File

@@ -0,0 +1,192 @@
// -----------------------------------------------------------------------
// <copyright file="Incremental.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Builds a delaunay triangulation using the incremental algorithm.
/// </summary>
internal class Incremental : ITriangulator
{
Mesh mesh;
/// <summary>
/// Form a Delaunay triangulation by incrementally inserting vertices.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the
/// triangulation.</returns>
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
Otri starttri = new Otri();
// Create a triangular bounding box.
GetBoundingBox();
foreach (var v in mesh.vertices.Values)
{
starttri.tri = mesh.dummytri;
Osub tmp = default(Osub);
if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate)
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored.",
"Incremental.Triangulate()");
}
v.type = VertexType.UndeadVertex;
mesh.undeads++;
}
}
// Remove the bounding box.
this.mesh.hullsize = RemoveBox();
return this.mesh;
}
/// <summary>
/// Form an "infinite" bounding triangle to insert vertices into.
/// </summary>
/// <remarks>
/// The vertices at "infinity" are assigned finite coordinates, which are
/// used by the point location routines, but (mostly) ignored by the
/// Delaunay edge flip routines.
/// </remarks>
void GetBoundingBox()
{
Otri inftri = default(Otri); // Handle for the triangular bounding box.
Rectangle box = mesh.bounds;
// Find the width (or height, whichever is larger) of the triangulation.
double width = box.Width;
if (box.Height > width)
{
width = box.Height;
}
if (width == 0.0)
{
width = 1.0;
}
// Create the vertices of the bounding box.
mesh.infvertex1 = new Vertex(box.Left - 50.0 * width, box.Bottom - 40.0 * width);
mesh.infvertex2 = new Vertex(box.Right + 50.0 * width, box.Bottom - 40.0 * width);
mesh.infvertex3 = new Vertex(0.5 * (box.Left + box.Right), box.Top + 60.0 * width);
// Create the bounding box.
mesh.MakeTriangle(ref inftri);
inftri.SetOrg(mesh.infvertex1);
inftri.SetDest(mesh.infvertex2);
inftri.SetApex(mesh.infvertex3);
// Link dummytri to the bounding box so we can always find an
// edge to begin searching (point location) from.
mesh.dummytri.neighbors[0] = inftri;
}
/// <summary>
/// Remove the "infinite" bounding triangle, setting boundary markers as appropriate.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the triangulation.</returns>
/// <remarks>
/// The triangular bounding box has three boundary triangles (one for each
/// side of the bounding box), and a bunch of triangles fanning out from
/// the three bounding box vertices (one triangle for each edge of the
/// convex hull of the inner mesh). This routine removes these triangles.
/// </remarks>
int RemoveBox()
{
Otri deadtriangle = default(Otri);
Otri searchedge = default(Otri);
Otri checkedge = default(Otri);
Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
// Find a boundary triangle.
nextedge.tri = mesh.dummytri;
nextedge.orient = 0;
nextedge.Sym();
// Mark a place to stop.
nextedge.Lprev(ref finaledge);
nextedge.Lnext();
nextedge.Sym();
// Find a triangle (on the boundary of the vertex set) that isn't
// a bounding box triangle.
nextedge.Lprev(ref searchedge);
searchedge.Sym();
// Check whether nextedge is another boundary triangle
// adjacent to the first one.
nextedge.Lnext(ref checkedge);
checkedge.Sym();
if (checkedge.tri.id == Mesh.DUMMY)
{
// Go on to the next triangle. There are only three boundary
// triangles, and this next triangle cannot be the third one,
// so it's safe to stop here.
searchedge.Lprev();
searchedge.Sym();
}
// Find a new boundary edge to search from, as the current search
// edge lies on a bounding box triangle and will be deleted.
mesh.dummytri.neighbors[0] = searchedge;
hullsize = -2;
while (!nextedge.Equals(finaledge))
{
hullsize++;
nextedge.Lprev(ref dissolveedge);
dissolveedge.Sym();
// If not using a PSLG, the vertices should be marked now.
// (If using a PSLG, markhull() will do the job.)
if (noPoly)
{
// Be careful! One must check for the case where all the input
// vertices are collinear, and thus all the triangles are part of
// the bounding box. Otherwise, the setvertexmark() call below
// will cause a bad pointer reference.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Disconnect the bounding box triangle from the mesh triangle.
dissolveedge.Dissolve(mesh.dummytri);
nextedge.Lnext(ref deadtriangle);
deadtriangle.Sym(ref nextedge);
// Get rid of the bounding box triangle.
mesh.TriangleDealloc(deadtriangle.tri);
// Do we need to turn the corner?
if (nextedge.tri.id == Mesh.DUMMY)
{
// Turn the corner.
dissolveedge.Copy(ref nextedge);
}
}
mesh.TriangleDealloc(finaledge.tri);
return hullsize;
}
}
}

View File

@@ -0,0 +1,812 @@
// -----------------------------------------------------------------------
// <copyright file="SweepLine.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
/// <summary>
/// Builds a delaunay triangulation using the sweepline algorithm.
/// </summary>
internal class SweepLine : ITriangulator
{
static int randomseed = 1;
static int SAMPLERATE = 10;
static int randomnation(int choices)
{
randomseed = (randomseed * 1366 + 150889) % 714025;
return randomseed / (714025 / choices + 1);
}
IPredicates predicates;
Mesh mesh;
double xminextreme; // Nonexistent x value used as a flag in sweepline.
List<SplayNode> splaynodes;
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.predicates = config.Predicates();
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
// Nonexistent x value used as a flag to mark circle events in sweepline
// Delaunay algorithm.
xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right;
SweepEvent[] eventheap;
SweepEvent nextevent;
SweepEvent newevent;
SplayNode splayroot;
Otri bottommost = default(Otri);
Otri searchtri = default(Otri);
Otri fliptri;
Otri lefttri = default(Otri);
Otri righttri = default(Otri);
Otri farlefttri = default(Otri);
Otri farrighttri = default(Otri);
Otri inserttri = default(Otri);
Vertex firstvertex, secondvertex;
Vertex nextvertex, lastvertex;
Vertex connectvertex;
Vertex leftvertex, midvertex, rightvertex;
double lefttest, righttest;
int heapsize;
bool check4events, farrightflag = false;
splaynodes = new List<SplayNode>();
splayroot = null;
heapsize = points.Count;
CreateHeap(out eventheap, heapsize);//, out events, out freeevents);
mesh.MakeTriangle(ref lefttri);
mesh.MakeTriangle(ref righttri);
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
firstvertex = eventheap[0].vertexEvent;
HeapDelete(eventheap, heapsize, 0);
heapsize--;
do
{
if (heapsize == 0)
{
Log.Instance.Error("Input vertices are all identical.", "SweepLine.Triangulate()");
throw new Exception("Input vertices are all identical.");
}
secondvertex = eventheap[0].vertexEvent;
HeapDelete(eventheap, heapsize, 0);
heapsize--;
if ((firstvertex.x == secondvertex.x) &&
(firstvertex.y == secondvertex.y))
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + secondvertex.id + ").",
"SweepLine.Triangulate().1");
}
secondvertex.type = VertexType.UndeadVertex;
mesh.undeads++;
}
}
while ((firstvertex.x == secondvertex.x) &&
(firstvertex.y == secondvertex.y));
lefttri.SetOrg(firstvertex);
lefttri.SetDest(secondvertex);
righttri.SetOrg(secondvertex);
righttri.SetDest(firstvertex);
lefttri.Lprev(ref bottommost);
lastvertex = secondvertex;
while (heapsize > 0)
{
nextevent = eventheap[0];
HeapDelete(eventheap, heapsize, 0);
heapsize--;
check4events = true;
if (nextevent.xkey < mesh.bounds.Left)
{
fliptri = nextevent.otriEvent;
fliptri.Oprev(ref farlefttri);
Check4DeadEvent(ref farlefttri, eventheap, ref heapsize);
fliptri.Onext(ref farrighttri);
Check4DeadEvent(ref farrighttri, eventheap, ref heapsize);
if (farlefttri.Equals(bottommost))
{
fliptri.Lprev(ref bottommost);
}
mesh.Flip(ref fliptri);
fliptri.SetApex(null);
fliptri.Lprev(ref lefttri);
fliptri.Lnext(ref righttri);
lefttri.Sym(ref farlefttri);
if (randomnation(SAMPLERATE) == 0)
{
fliptri.Sym();
leftvertex = fliptri.Dest();
midvertex = fliptri.Apex();
rightvertex = fliptri.Org();
splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey);
}
}
else
{
nextvertex = nextevent.vertexEvent;
if ((nextvertex.x == lastvertex.x) &&
(nextvertex.y == lastvertex.y))
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + nextvertex.id + ").",
"SweepLine.Triangulate().2");
}
nextvertex.type = VertexType.UndeadVertex;
mesh.undeads++;
check4events = false;
}
else
{
lastvertex = nextvertex;
splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag);
//bottommost.Copy(ref searchtri);
//farrightflag = false;
//while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex))
//{
// searchtri.OnextSelf();
// farrightflag = searchtri.Equal(bottommost);
//}
Check4DeadEvent(ref searchtri, eventheap, ref heapsize);
searchtri.Copy(ref farrighttri);
searchtri.Sym(ref farlefttri);
mesh.MakeTriangle(ref lefttri);
mesh.MakeTriangle(ref righttri);
connectvertex = farrighttri.Dest();
lefttri.SetOrg(connectvertex);
lefttri.SetDest(nextvertex);
righttri.SetOrg(nextvertex);
righttri.SetDest(connectvertex);
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref farlefttri);
righttri.Bond(ref farrighttri);
if (!farrightflag && farrighttri.Equals(bottommost))
{
lefttri.Copy(ref bottommost);
}
if (randomnation(SAMPLERATE) == 0)
{
splayroot = SplayInsert(splayroot, lefttri, nextvertex);
}
else if (randomnation(SAMPLERATE) == 0)
{
righttri.Lnext(ref inserttri);
splayroot = SplayInsert(splayroot, inserttri, nextvertex);
}
}
}
if (check4events)
{
leftvertex = farlefttri.Apex();
midvertex = lefttri.Dest();
rightvertex = lefttri.Apex();
lefttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex);
if (lefttest > 0.0)
{
newevent = new SweepEvent();
newevent.xkey = xminextreme;
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest);
newevent.otriEvent = lefttri;
HeapInsert(eventheap, heapsize, newevent);
heapsize++;
lefttri.SetOrg(new SweepEventVertex(newevent));
}
leftvertex = righttri.Apex();
midvertex = righttri.Org();
rightvertex = farrighttri.Apex();
righttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex);
if (righttest > 0.0)
{
newevent = new SweepEvent();
newevent.xkey = xminextreme;
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest);
newevent.otriEvent = farrighttri;
HeapInsert(eventheap, heapsize, newevent);
heapsize++;
farrighttri.SetOrg(new SweepEventVertex(newevent));
}
}
}
splaynodes.Clear();
bottommost.Lprev();
this.mesh.hullsize = RemoveGhosts(ref bottommost);
return this.mesh;
}
#region Heap
void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent)
{
double eventx, eventy;
int eventnum;
int parent;
bool notdone;
eventx = newevent.xkey;
eventy = newevent.ykey;
eventnum = heapsize;
notdone = eventnum > 0;
while (notdone)
{
parent = (eventnum - 1) >> 1;
if ((heap[parent].ykey < eventy) ||
((heap[parent].ykey == eventy)
&& (heap[parent].xkey <= eventx)))
{
notdone = false;
}
else
{
heap[eventnum] = heap[parent];
heap[eventnum].heapposition = eventnum;
eventnum = parent;
notdone = eventnum > 0;
}
}
heap[eventnum] = newevent;
newevent.heapposition = eventnum;
}
void Heapify(SweepEvent[] heap, int heapsize, int eventnum)
{
SweepEvent thisevent;
double eventx, eventy;
int leftchild, rightchild;
int smallest;
bool notdone;
thisevent = heap[eventnum];
eventx = thisevent.xkey;
eventy = thisevent.ykey;
leftchild = 2 * eventnum + 1;
notdone = leftchild < heapsize;
while (notdone)
{
if ((heap[leftchild].ykey < eventy) ||
((heap[leftchild].ykey == eventy)
&& (heap[leftchild].xkey < eventx)))
{
smallest = leftchild;
}
else
{
smallest = eventnum;
}
rightchild = leftchild + 1;
if (rightchild < heapsize)
{
if ((heap[rightchild].ykey < heap[smallest].ykey) ||
((heap[rightchild].ykey == heap[smallest].ykey)
&& (heap[rightchild].xkey < heap[smallest].xkey)))
{
smallest = rightchild;
}
}
if (smallest == eventnum)
{
notdone = false;
}
else
{
heap[eventnum] = heap[smallest];
heap[eventnum].heapposition = eventnum;
heap[smallest] = thisevent;
thisevent.heapposition = smallest;
eventnum = smallest;
leftchild = 2 * eventnum + 1;
notdone = leftchild < heapsize;
}
}
}
void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum)
{
SweepEvent moveevent;
double eventx, eventy;
int parent;
bool notdone;
moveevent = heap[heapsize - 1];
if (eventnum > 0)
{
eventx = moveevent.xkey;
eventy = moveevent.ykey;
do
{
parent = (eventnum - 1) >> 1;
if ((heap[parent].ykey < eventy) ||
((heap[parent].ykey == eventy)
&& (heap[parent].xkey <= eventx)))
{
notdone = false;
}
else
{
heap[eventnum] = heap[parent];
heap[eventnum].heapposition = eventnum;
eventnum = parent;
notdone = eventnum > 0;
}
}
while (notdone);
}
heap[eventnum] = moveevent;
moveevent.heapposition = eventnum;
Heapify(heap, heapsize - 1, eventnum);
}
void CreateHeap(out SweepEvent[] eventheap, int size)
{
Vertex thisvertex;
int maxevents;
int i;
SweepEvent evt;
maxevents = (3 * size) / 2;
eventheap = new SweepEvent[maxevents];
i = 0;
foreach (var v in mesh.vertices.Values)
{
thisvertex = v;
evt = new SweepEvent();
evt.vertexEvent = thisvertex;
evt.xkey = thisvertex.x;
evt.ykey = thisvertex.y;
HeapInsert(eventheap, i++, evt);
}
}
#endregion
#region Splaytree
SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri)
{
SplayNode child, grandchild;
SplayNode lefttree, righttree;
SplayNode leftright;
Vertex checkvertex;
bool rightofroot, rightofchild;
if (splaytree == null)
{
return null;
}
checkvertex = splaytree.keyedge.Dest();
if (checkvertex == splaytree.keydest)
{
rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint);
if (rightofroot)
{
splaytree.keyedge.Copy(ref searchtri);
child = splaytree.rchild;
}
else
{
child = splaytree.lchild;
}
if (child == null)
{
return splaytree;
}
checkvertex = child.keyedge.Dest();
if (checkvertex != child.keydest)
{
child = Splay(child, searchpoint, ref searchtri);
if (child == null)
{
if (rightofroot)
{
splaytree.rchild = null;
}
else
{
splaytree.lchild = null;
}
return splaytree;
}
}
rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint);
if (rightofchild)
{
child.keyedge.Copy(ref searchtri);
grandchild = Splay(child.rchild, searchpoint, ref searchtri);
child.rchild = grandchild;
}
else
{
grandchild = Splay(child.lchild, searchpoint, ref searchtri);
child.lchild = grandchild;
}
if (grandchild == null)
{
if (rightofroot)
{
splaytree.rchild = child.lchild;
child.lchild = splaytree;
}
else
{
splaytree.lchild = child.rchild;
child.rchild = splaytree;
}
return child;
}
if (rightofchild)
{
if (rightofroot)
{
splaytree.rchild = child.lchild;
child.lchild = splaytree;
}
else
{
splaytree.lchild = grandchild.rchild;
grandchild.rchild = splaytree;
}
child.rchild = grandchild.lchild;
grandchild.lchild = child;
}
else
{
if (rightofroot)
{
splaytree.rchild = grandchild.lchild;
grandchild.lchild = splaytree;
}
else
{
splaytree.lchild = child.rchild;
child.rchild = splaytree;
}
child.lchild = grandchild.rchild;
grandchild.rchild = child;
}
return grandchild;
}
else
{
lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri);
righttree = Splay(splaytree.rchild, searchpoint, ref searchtri);
splaynodes.Remove(splaytree);
if (lefttree == null)
{
return righttree;
}
else if (righttree == null)
{
return lefttree;
}
else if (lefttree.rchild == null)
{
lefttree.rchild = righttree.lchild;
righttree.lchild = lefttree;
return righttree;
}
else if (righttree.lchild == null)
{
righttree.lchild = lefttree.rchild;
lefttree.rchild = righttree;
return lefttree;
}
else
{
// printf("Holy Toledo!!!\n");
leftright = lefttree.rchild;
while (leftright.rchild != null)
{
leftright = leftright.rchild;
}
leftright.rchild = righttree;
return lefttree;
}
}
}
SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint)
{
SplayNode newsplaynode;
newsplaynode = new SplayNode(); //poolalloc(m.splaynodes);
splaynodes.Add(newsplaynode);
newkey.Copy(ref newsplaynode.keyedge);
newsplaynode.keydest = newkey.Dest();
if (splayroot == null)
{
newsplaynode.lchild = null;
newsplaynode.rchild = null;
}
else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint))
{
newsplaynode.lchild = splayroot;
newsplaynode.rchild = splayroot.rchild;
splayroot.rchild = null;
}
else
{
newsplaynode.lchild = splayroot.lchild;
newsplaynode.rchild = splayroot;
splayroot.lchild = null;
}
return newsplaynode;
}
SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex,
ref Otri searchtri, ref bool farright)
{
bool farrightflag;
bottommost.Copy(ref searchtri);
splayroot = Splay(splayroot, searchvertex, ref searchtri);
farrightflag = false;
while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex))
{
searchtri.Onext();
farrightflag = searchtri.Equals(bottommost);
}
farright = farrightflag;
return splayroot;
}
SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey,
Vertex pa, Vertex pb, Vertex pc, double topy)
{
double ccwabc;
double xac, yac, xbc, ybc;
double aclen2, bclen2;
Point searchpoint = new Point(); // TODO: mesh.nextras
Otri dummytri = default(Otri);
ccwabc = predicates.CounterClockwise(pa, pb, pc);
xac = pa.x - pc.x;
yac = pa.y - pc.y;
xbc = pb.x - pc.x;
ybc = pb.y - pc.y;
aclen2 = xac * xac + yac * yac;
bclen2 = xbc * xbc + ybc * ybc;
searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc);
searchpoint.y = topy;
return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint);
}
#endregion
bool RightOfHyperbola(ref Otri fronttri, Point newsite)
{
Vertex leftvertex, rightvertex;
double dxa, dya, dxb, dyb;
Statistic.HyperbolaCount++;
leftvertex = fronttri.Dest();
rightvertex = fronttri.Apex();
if ((leftvertex.y < rightvertex.y) ||
((leftvertex.y == rightvertex.y) &&
(leftvertex.x < rightvertex.x)))
{
if (newsite.x >= rightvertex.x)
{
return true;
}
}
else
{
if (newsite.x <= leftvertex.x)
{
return false;
}
}
dxa = leftvertex.x - newsite.x;
dya = leftvertex.y - newsite.y;
dxb = rightvertex.x - newsite.x;
dyb = rightvertex.y - newsite.y;
return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya);
}
double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc)
{
double xac, yac, xbc, ybc, xab, yab;
double aclen2, bclen2, ablen2;
Statistic.CircleTopCount++;
xac = pa.x - pc.x;
yac = pa.y - pc.y;
xbc = pb.x - pc.x;
ybc = pb.y - pc.y;
xab = pa.x - pb.x;
yab = pa.y - pb.y;
aclen2 = xac * xac + yac * yac;
bclen2 = xbc * xbc + ybc * ybc;
ablen2 = xab * xab + yab * yab;
return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc);
}
void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize)
{
SweepEvent deadevent;
SweepEventVertex eventvertex;
int eventnum = -1;
eventvertex = checktri.Org() as SweepEventVertex;
if (eventvertex != null)
{
deadevent = eventvertex.evt;
eventnum = deadevent.heapposition;
HeapDelete(eventheap, heapsize, eventnum);
heapsize--;
checktri.SetOrg(null);
}
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
int RemoveGhosts(ref Otri startghost)
{
Otri searchedge = default(Otri);
Otri dissolveedge = default(Otri);
Otri deadtriangle = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
var dummytri = mesh.dummytri;
// Find an edge on the convex hull to start point location from.
startghost.Lprev(ref searchedge);
searchedge.Sym();
dummytri.neighbors[0] = searchedge;
// Remove the bounding box and count the convex hull edges.
startghost.Copy(ref dissolveedge);
hullsize = 0;
do
{
hullsize++;
dissolveedge.Lnext(ref deadtriangle);
dissolveedge.Lprev();
dissolveedge.Sym();
// If no PSLG is involved, set the boundary markers of all the vertices
// on the convex hull. If a PSLG is used, this step is done later.
if (noPoly)
{
// Watch out for the case where all the input vertices are collinear.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Remove a bounding triangle from a convex hull triangle.
dissolveedge.Dissolve(dummytri);
// Find the next bounding triangle.
deadtriangle.Sym(ref dissolveedge);
// Delete the bounding triangle.
mesh.TriangleDealloc(deadtriangle.tri);
}
while (!dissolveedge.Equals(startghost));
return hullsize;
}
#region Internal classes
/// <summary>
/// A node in a heap used to store events for the sweepline Delaunay algorithm.
/// </summary>
/// <remarks>
/// Only used in the sweepline algorithm.
///
/// Nodes do not point directly to their parents or children in the heap. Instead, each
/// node knows its position in the heap, and can look up its parent and children in a
/// separate array. To distinguish site events from circle events, all circle events are
/// given an invalid (smaller than 'xmin') x-coordinate 'xkey'.
/// </remarks>
class SweepEvent
{
public double xkey, ykey; // Coordinates of the event.
public Vertex vertexEvent; // Vertex event.
public Otri otriEvent; // Circle event.
public int heapposition; // Marks this event's position in the heap.
}
/// <summary>
/// Introducing a new class which aggregates a sweep event is the easiest way
/// to handle the pointer magic of the original code (casting a sweep event
/// to vertex etc.).
/// </summary>
class SweepEventVertex : Vertex
{
public SweepEvent evt;
public SweepEventVertex(SweepEvent e)
{
evt = e;
}
}
/// <summary>
/// A node in the splay tree.
/// </summary>
/// <remarks>
/// Only used in the sweepline algorithm.
///
/// Each node holds an oriented ghost triangle that represents a boundary edge
/// of the growing triangulation. When a circle event covers two boundary edges
/// with a triangle, so that they are no longer boundary edges, those edges are
/// not immediately deleted from the tree; rather, they are lazily deleted when
/// they are next encountered. (Since only a random sample of boundary edges are
/// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that
/// a triangle is still the same as when it entered the splay tree; if it has
/// been rotated (due to a circle event), it no longer represents a boundary
/// edge and should be deleted.
/// </remarks>
class SplayNode
{
public Otri keyedge; // Lprev of an edge on the front.
public Vertex keydest; // Used to verify that splay node is still live.
public SplayNode lchild, rchild; // Children in splay tree.
}
#endregion
}
}

View File

@@ -0,0 +1,40 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
/// <summary>
/// Mesh constraint options for polygon triangulation.
/// </summary>
internal class ConstraintOptions
{
// TODO: remove ConstraintOptions.UseRegions
/// <summary>
/// Gets or sets a value indicating whether to use regions.
/// </summary>
[System.Obsolete("Not used anywhere, will be removed in beta 4.")]
public bool UseRegions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to create a Conforming
/// Delaunay triangulation.
/// </summary>
public bool ConformingDelaunay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enclose the convex
/// hull with segments.
/// </summary>
public bool Convex { get; set; }
/// <summary>
/// Gets or sets a flag indicating whether to suppress boundary
/// segment splitting.
/// </summary>
/// <remarks>
/// 0 = split segments (default)
/// 1 = no new vertices on the boundary
/// 2 = prevent all segment splitting, including internal boundaries
/// </remarks>
public int SegmentSplitting { get; set; }
}
}

View File

@@ -0,0 +1,489 @@
// -----------------------------------------------------------------------
// <copyright file="Converter.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using System.Linq;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Topology.DCEL;
using HVertex = Animation.TriangleNet.Topology.DCEL.Vertex;
using TVertex = Animation.TriangleNet.Geometry.Vertex;
/// <summary>
/// The Converter class provides methods for mesh reconstruction and conversion.
/// </summary>
internal static class Converter
{
#region Triangle mesh conversion
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
internal static Mesh ToMesh(Polygon polygon, IList<ITriangle> triangles)
{
return ToMesh(polygon, triangles.ToArray());
}
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
internal static Mesh ToMesh(Polygon polygon, ITriangle[] triangles)
{
Otri tri = default(Otri);
Osub subseg = default(Osub);
int i = 0;
int elements = triangles == null ? 0 : triangles.Length;
int segments = polygon.Segments.Count;
// TODO: Configuration should be a function argument.
var mesh = new Mesh(new Configuration());
mesh.TransferNodes(polygon.Points);
mesh.regions.AddRange(polygon.Regions);
mesh.behavior.useRegions = polygon.Regions.Count > 0;
if (polygon.Segments.Count > 0)
{
mesh.behavior.Poly = true;
mesh.holes.AddRange(polygon.Holes);
}
// Create the triangles.
for (i = 0; i < elements; i++)
{
mesh.MakeTriangle(ref tri);
}
if (mesh.behavior.Poly)
{
mesh.insegments = segments;
// Create the subsegments.
for (i = 0; i < segments; i++)
{
mesh.MakeSegment(ref subseg);
}
}
var vertexarray = SetNeighbors(mesh, triangles);
SetSegments(mesh, polygon, vertexarray);
return mesh;
}
/// <summary>
/// Finds the adjacencies between triangles by forming a stack of triangles for
/// each vertex. Each triangle is on three different stacks simultaneously.
/// </summary>
private static List<Otri>[] SetNeighbors(Mesh mesh, ITriangle[] triangles)
{
Otri tri = default(Otri);
Otri triangleleft = default(Otri);
Otri checktri = default(Otri);
Otri checkleft = default(Otri);
Otri nexttri;
TVertex tdest, tapex;
TVertex checkdest, checkapex;
int[] corner = new int[3];
int aroundvertex;
int i;
// Allocate a temporary array that maps each vertex to some adjacent triangle.
var vertexarray = new List<Otri>[mesh.vertices.Count];
// Each vertex is initially unrepresented.
for (i = 0; i < mesh.vertices.Count; i++)
{
Otri tmp = default(Otri);
tmp.tri = mesh.dummytri;
vertexarray[i] = new List<Otri>(3);
vertexarray[i].Add(tmp);
}
i = 0;
// Read the triangles from the .ele file, and link
// together those that share an edge.
foreach (var item in mesh.triangles)
{
tri.tri = item;
// Copy the triangle's three corners.
for (int j = 0; j < 3; j++)
{
corner[j] = triangles[i].GetVertexID(j);
if ((corner[j] < 0) || (corner[j] >= mesh.invertices))
{
Log.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()");
throw new Exception("Triangle has an invalid vertex index.");
}
}
// Read the triangle's attributes.
tri.tri.label = triangles[i].Label;
// TODO: VarArea
if (mesh.behavior.VarArea)
{
tri.tri.area = triangles[i].Area;
}
// Set the triangle's vertices.
tri.orient = 0;
tri.SetOrg(mesh.vertices[corner[0]]);
tri.SetDest(mesh.vertices[corner[1]]);
tri.SetApex(mesh.vertices[corner[2]]);
// Try linking the triangle to others that share these vertices.
for (tri.orient = 0; tri.orient < 3; tri.orient++)
{
// Take the number for the origin of triangleloop.
aroundvertex = corner[tri.orient];
int index = vertexarray[aroundvertex].Count - 1;
// Look for other triangles having this vertex.
nexttri = vertexarray[aroundvertex][index];
// Push the current triangle onto the stack.
vertexarray[aroundvertex].Add(tri);
checktri = nexttri;
if (checktri.tri.id != Mesh.DUMMY)
{
tdest = tri.Dest();
tapex = tri.Apex();
// Look for other triangles that share an edge.
do
{
checkdest = checktri.Dest();
checkapex = checktri.Apex();
if (tapex == checkdest)
{
// The two triangles share an edge; bond them together.
tri.Lprev(ref triangleleft);
triangleleft.Bond(ref checktri);
}
if (tdest == checkapex)
{
// The two triangles share an edge; bond them together.
checktri.Lprev(ref checkleft);
tri.Bond(ref checkleft);
}
// Find the next triangle in the stack.
index--;
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
}
while (checktri.tri.id != Mesh.DUMMY);
}
}
i++;
}
return vertexarray;
}
/// <summary>
/// Finds the adjacencies between triangles and subsegments.
/// </summary>
private static void SetSegments(Mesh mesh, Polygon polygon, List<Otri>[] vertexarray)
{
Otri checktri = default(Otri);
Otri nexttri; // Triangle
TVertex checkdest;
Otri checkneighbor = default(Otri);
Osub subseg = default(Osub);
Otri prevlink; // Triangle
TVertex tmp;
TVertex sorg, sdest;
bool notfound;
//bool segmentmarkers = false;
int boundmarker;
int aroundvertex;
int i;
int hullsize = 0;
// Prepare to count the boundary edges.
if (mesh.behavior.Poly)
{
// Link the segments to their neighboring triangles.
boundmarker = 0;
i = 0;
foreach (var item in mesh.subsegs.Values)
{
subseg.seg = item;
sorg = polygon.Segments[i].GetVertex(0);
sdest = polygon.Segments[i].GetVertex(1);
boundmarker = polygon.Segments[i].Label;
if ((sorg.id < 0 || sorg.id >= mesh.invertices) || (sdest.id < 0 || sdest.id >= mesh.invertices))
{
Log.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()");
throw new Exception("Segment has an invalid vertex index.");
}
// set the subsegment's vertices.
subseg.orient = 0;
subseg.SetOrg(sorg);
subseg.SetDest(sdest);
subseg.SetSegOrg(sorg);
subseg.SetSegDest(sdest);
subseg.seg.boundary = boundmarker;
// Try linking the subsegment to triangles that share these vertices.
for (subseg.orient = 0; subseg.orient < 2; subseg.orient++)
{
// Take the number for the destination of subsegloop.
aroundvertex = subseg.orient == 1 ? sorg.id : sdest.id;
int index = vertexarray[aroundvertex].Count - 1;
// Look for triangles having this vertex.
prevlink = vertexarray[aroundvertex][index];
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
tmp = subseg.Org();
notfound = true;
// Look for triangles having this edge. Note that I'm only
// comparing each triangle's destination with the subsegment;
// each triangle's apex is handled through a different vertex.
// Because each triangle appears on three vertices' lists, each
// occurrence of a triangle on a list can (and does) represent
// an edge. In this way, most edges are represented twice, and
// every triangle-subsegment bond is represented once.
while (notfound && (checktri.tri.id != Mesh.DUMMY))
{
checkdest = checktri.Dest();
if (tmp == checkdest)
{
// We have a match. Remove this triangle from the list.
//prevlink = vertexarray[aroundvertex][index];
vertexarray[aroundvertex].Remove(prevlink);
// Bond the subsegment to the triangle.
checktri.SegBond(ref subseg);
// Check if this is a boundary edge.
checktri.Sym(ref checkneighbor);
if (checkneighbor.tri.id == Mesh.DUMMY)
{
// The next line doesn't insert a subsegment (because there's
// already one there), but it sets the boundary markers of
// the existing subsegment and its vertices.
mesh.InsertSubseg(ref checktri, 1);
hullsize++;
}
notfound = false;
}
index--;
// Find the next triangle in the stack.
prevlink = vertexarray[aroundvertex][index];
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
}
}
i++;
}
}
// Mark the remaining edges as not being attached to any subsegment.
// Also, count the (yet uncounted) boundary edges.
for (i = 0; i < mesh.vertices.Count; i++)
{
// Search the stack of triangles adjacent to a vertex.
int index = vertexarray[i].Count - 1;
nexttri = vertexarray[i][index];
checktri = nexttri;
while (checktri.tri.id != Mesh.DUMMY)
{
// Find the next triangle in the stack before this
// information gets overwritten.
index--;
nexttri = vertexarray[i][index];
// No adjacent subsegment. (This overwrites the stack info.)
checktri.SegDissolve(mesh.dummysub);
checktri.Sym(ref checkneighbor);
if (checkneighbor.tri.id == Mesh.DUMMY)
{
mesh.InsertSubseg(ref checktri, 1);
hullsize++;
}
checktri = nexttri;
}
}
mesh.hullsize = hullsize;
}
#endregion
#region DCEL conversion
internal static DcelMesh ToDCEL(Mesh mesh)
{
var dcel = new DcelMesh();
var vertices = new HVertex[mesh.vertices.Count];
var faces = new Face[mesh.triangles.Count];
dcel.HalfEdges.Capacity = 2 * mesh.NumberOfEdges;
mesh.Renumber();
HVertex vertex;
foreach (var v in mesh.vertices.Values)
{
vertex = new HVertex(v.x, v.y);
vertex.id = v.id;
vertex.label = v.label;
vertices[v.id] = vertex;
}
// Maps a triangle to its 3 edges (used to set next pointers).
var map = new List<HalfEdge>[mesh.triangles.Count];
Face face;
foreach (var t in mesh.triangles)
{
face = new Face(null);
face.id = t.id;
faces[t.id] = face;
map[t.id] = new List<HalfEdge>(3);
}
Otri tri = default(Otri), neighbor = default(Otri);
Animation.TriangleNet.Geometry.Vertex org, dest;
int id, nid, count = mesh.triangles.Count;
HalfEdge edge, twin, next;
var edges = dcel.HalfEdges;
// Count half-edges (edge ids).
int k = 0;
// Maps a vertex to its leaving boundary edge.
var boundary = new Dictionary<int, HalfEdge>();
foreach (var t in mesh.triangles)
{
id = t.id;
tri.tri = t;
for (int i = 0; i < 3; i++)
{
tri.orient = i;
tri.Sym(ref neighbor);
nid = neighbor.tri.id;
if (id < nid || nid < 0)
{
face = faces[id];
// Get the endpoints of the current triangle edge.
org = tri.Org();
dest = tri.Dest();
// Create half-edges.
edge = new HalfEdge(vertices[org.id], face);
twin = new HalfEdge(vertices[dest.id], nid < 0 ? Face.Empty : faces[nid]);
map[id].Add(edge);
if (nid >= 0)
{
map[nid].Add(twin);
}
else
{
boundary.Add(dest.id, twin);
}
// Set leaving edges.
edge.origin.leaving = edge;
twin.origin.leaving = twin;
// Set twin edges.
edge.twin = twin;
twin.twin = edge;
edge.id = k++;
twin.id = k++;
edges.Add(edge);
edges.Add(twin);
}
}
}
// Set next pointers for each triangle face.
foreach (var t in map)
{
edge = t[0];
next = t[1];
if (edge.twin.origin.id == next.origin.id)
{
edge.next = next;
next.next = t[2];
t[2].next = edge;
}
else
{
edge.next = t[2];
next.next = edge;
t[2].next = next;
}
}
// Resolve boundary edges.
foreach (var e in boundary.Values)
{
e.next = boundary[e.twin.origin.id];
}
dcel.Vertices.AddRange(vertices);
dcel.Faces.AddRange(faces);
return dcel;
}
#endregion
}
}

View File

@@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="BadSubseg.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using System;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A queue used to store encroached subsegments.
/// </summary>
/// <remarks>
/// Each subsegment's vertices are stored so that we can check whether a
/// subsegment is still the same.
/// </remarks>
class BadSubseg
{
public Osub subseg; // An encroached subsegment.
public Vertex org, dest; // Its two vertices.
public override int GetHashCode()
{
return subseg.seg.hash;
}
public override string ToString()
{
return String.Format("B-SID {0}", subseg.seg.hash);
}
}
}

View File

@@ -0,0 +1,195 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriQueue.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A (priority) queue for bad triangles.
/// </summary>
/// <remarks>
// The queue is actually a set of 4096 queues. I use multiple queues to
// give priority to smaller angles. I originally implemented a heap, but
// the queues are faster by a larger margin than I'd suspected.
/// </remarks>
class BadTriQueue
{
const double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732;
public int Count { get { return this.count; } }
// Variables that maintain the bad triangle queues. The queues are
// ordered from 4095 (highest priority) to 0 (lowest priority).
BadTriangle[] queuefront;
BadTriangle[] queuetail;
int[] nextnonemptyq;
int firstnonemptyq;
int count;
public BadTriQueue()
{
queuefront = new BadTriangle[4096];
queuetail = new BadTriangle[4096];
nextnonemptyq = new int[4096];
firstnonemptyq = -1;
count = 0;
}
/// <summary>
/// Add a bad triangle data structure to the end of a queue.
/// </summary>
/// <param name="badtri">The bad triangle to enqueue.</param>
public void Enqueue(BadTriangle badtri)
{
double length, multiplier;
int exponent, expincrement;
int queuenumber;
int posexponent;
int i;
this.count++;
// Determine the appropriate queue to put the bad triangle into.
// Recall that the key is the square of its shortest edge length.
if (badtri.key >= 1.0)
{
length = badtri.key;
posexponent = 1;
}
else
{
// 'badtri.key' is 2.0 to a negative exponent, so we'll record that
// fact and use the reciprocal of 'badtri.key', which is > 1.0.
length = 1.0 / badtri.key;
posexponent = 0;
}
// 'length' is approximately 2.0 to what exponent? The following code
// determines the answer in time logarithmic in the exponent.
exponent = 0;
while (length > 2.0)
{
// Find an approximation by repeated squaring of two.
expincrement = 1;
multiplier = 0.5;
while (length * multiplier * multiplier > 1.0)
{
expincrement *= 2;
multiplier *= multiplier;
}
// Reduce the value of 'length', then iterate if necessary.
exponent += expincrement;
length *= multiplier;
}
// 'length' is approximately squareroot(2.0) to what exponent?
exponent = 2 * exponent + (length > SQRT2 ? 1 : 0);
// 'exponent' is now in the range 0...2047 for IEEE double precision.
// Choose a queue in the range 0...4095. The shortest edges have the
// highest priority (queue 4095).
if (posexponent > 0)
{
queuenumber = 2047 - exponent;
}
else
{
queuenumber = 2048 + exponent;
}
// Are we inserting into an empty queue?
if (queuefront[queuenumber] == null)
{
// Yes, we are inserting into an empty queue.
// Will this become the highest-priority queue?
if (queuenumber > firstnonemptyq)
{
// Yes, this is the highest-priority queue.
nextnonemptyq[queuenumber] = firstnonemptyq;
firstnonemptyq = queuenumber;
}
else
{
// No, this is not the highest-priority queue.
// Find the queue with next higher priority.
i = queuenumber + 1;
while (queuefront[i] == null)
{
i++;
}
// Mark the newly nonempty queue as following a higher-priority queue.
nextnonemptyq[queuenumber] = nextnonemptyq[i];
nextnonemptyq[i] = queuenumber;
}
// Put the bad triangle at the beginning of the (empty) queue.
queuefront[queuenumber] = badtri;
}
else
{
// Add the bad triangle to the end of an already nonempty queue.
queuetail[queuenumber].next = badtri;
}
// Maintain a pointer to the last triangle of the queue.
queuetail[queuenumber] = badtri;
// Newly enqueued bad triangle has no successor in the queue.
badtri.next = null;
}
/// <summary>
/// Add a bad triangle to the end of a queue.
/// </summary>
/// <param name="enqtri"></param>
/// <param name="minedge"></param>
/// <param name="apex"></param>
/// <param name="org"></param>
/// <param name="dest"></param>
public void Enqueue(ref Otri enqtri, double minedge, Vertex apex, Vertex org, Vertex dest)
{
// Allocate space for the bad triangle.
BadTriangle newbad = new BadTriangle();
newbad.poortri = enqtri;
newbad.key = minedge;
newbad.apex = apex;
newbad.org = org;
newbad.dest = dest;
Enqueue(newbad);
}
/// <summary>
/// Remove a triangle from the front of the queue.
/// </summary>
/// <returns></returns>
public BadTriangle Dequeue()
{
// If no queues are nonempty, return NULL.
if (firstnonemptyq < 0)
{
return null;
}
this.count--;
// Find the first triangle of the highest-priority queue.
BadTriangle result = queuefront[firstnonemptyq];
// Remove the triangle from the queue.
queuefront[firstnonemptyq] = result.next;
// If this queue is now empty, note the new highest-priority
// nonempty queue.
if (result == queuetail[firstnonemptyq])
{
firstnonemptyq = nextnonemptyq[firstnonemptyq];
}
return result;
}
}
}

View File

@@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriangle.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using System;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A queue used to store bad triangles.
/// </summary>
/// <remarks>
/// The key is the square of the cosine of the smallest angle of the triangle.
/// Each triangle's vertices are stored so that one can check whether a
/// triangle is still the same.
/// </remarks>
class BadTriangle
{
public Otri poortri; // A skinny or too-large triangle.
public double key; // cos^2 of smallest (apical) angle.
public Vertex org, dest, apex; // Its three vertices.
public BadTriangle next; // Pointer to next bad triangle.
public override string ToString()
{
return String.Format("B-TID {0}", poortri.tri.hash);
}
}
}

View File

@@ -0,0 +1,234 @@
// -----------------------------------------------------------------------
// <copyright file="GenericMesher.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.IO;
using Animation.TriangleNet.Meshing.Algorithm;
/// <summary>
/// Create meshes of point sets or polygons.
/// </summary>
internal class GenericMesher
{
Configuration config;
ITriangulator triangulator;
public GenericMesher()
: this(new Dwyer(), new Configuration())
{
}
public GenericMesher(ITriangulator triangulator)
: this(triangulator, new Configuration())
{
}
public GenericMesher(Configuration config)
: this(new Dwyer(), config)
{
}
public GenericMesher(ITriangulator triangulator, Configuration config)
{
this.config = config;
this.triangulator = triangulator;
}
/// <inheritdoc />
public IMesh Triangulate(IList<Vertex> points)
{
return triangulator.Triangulate(points, config);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon)
{
return Triangulate(polygon, null, null);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, ConstraintOptions options)
{
return Triangulate(polygon, options, null);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, QualityOptions quality)
{
return Triangulate(polygon, null, quality);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality)
{
var mesh = (Mesh)triangulator.Triangulate(polygon.Points, config);
var cmesher = new ConstraintMesher(mesh, config);
var qmesher = new QualityMesher(mesh, config);
mesh.SetQualityMesher(qmesher);
// Insert segments.
cmesher.Apply(polygon, options);
// Refine mesh.
qmesher.Apply(quality);
return mesh;
}
/// <summary>
/// Generates a structured mesh with bounds [0, 0, width, height].
/// </summary>
/// <param name="width">Width of the mesh (must be > 0).</param>
/// <param name="height">Height of the mesh (must be > 0).</param>
/// <param name="nx">Number of segments in x direction.</param>
/// <param name="ny">Number of segments in y direction.</param>
/// <returns>Mesh</returns>
internal static IMesh StructuredMesh(double width, double height, int nx, int ny)
{
if (width <= 0.0)
{
throw new ArgumentException("width");
}
if (height <= 0.0)
{
throw new ArgumentException("height");
}
return StructuredMesh(new Rectangle(0.0, 0.0, width, height), nx, ny);
}
/// <summary>
/// Generates a structured mesh.
/// </summary>
/// <param name="bounds">Bounds of the mesh.</param>
/// <param name="nx">Number of segments in x direction.</param>
/// <param name="ny">Number of segments in y direction.</param>
/// <returns>Mesh</returns>
internal static IMesh StructuredMesh(Rectangle bounds, int nx, int ny)
{
var polygon = new Polygon((nx + 1) * (ny + 1));
double x, y, dx, dy, left, bottom;
dx = bounds.Width / nx;
dy = bounds.Height / ny;
left = bounds.Left;
bottom = bounds.Bottom;
int i, j, k, l, n = 0;
// Add vertices.
var points = new Vertex[(nx + 1) * (ny + 1)];
for (i = 0; i <= nx; i++)
{
x = left + i * dx;
for (j = 0; j <= ny; j++)
{
y = bottom + j * dy;
points[n++] = new Vertex(x, y);
}
}
polygon.Points.AddRange(points);
n = 0;
// Set vertex hash and id.
foreach (var v in points)
{
v.hash = v.id = n++;
}
// Add boundary segments.
var segments = polygon.Segments;
segments.Capacity = 2 * (nx + ny);
Vertex a, b;
for (j = 0; j < ny; j++)
{
// Left
a = points[j];
b = points[j + 1];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
// Right
a = points[nx * (ny + 1) + j];
b = points[nx * (ny + 1) + (j + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
}
for (i = 0; i < nx; i++)
{
// Bottom
a = points[(ny + 1) * i];
b = points[(ny + 1) * (i + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
// Top
a = points[ny + (ny + 1) * i];
b = points[ny + (ny + 1) * (i + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
}
// Add triangles.
var triangles = new InputTriangle[2 * nx * ny];
n = 0;
for (i = 0; i < nx; i++)
{
for (j = 0; j < ny; j++)
{
k = j + (ny + 1) * i;
l = j + (ny + 1) * (i + 1);
// Create 2 triangles in rectangle [k, l, l + 1, k + 1].
if ((i + j) % 2 == 0)
{
// Diagonal from bottom left to top right.
triangles[n++] = new InputTriangle(k, l, l + 1);
triangles[n++] = new InputTriangle(k, l + 1, k + 1);
}
else
{
// Diagonal from top left to bottom right.
triangles[n++] = new InputTriangle(k, l, k + 1);
triangles[n++] = new InputTriangle(l, l + 1, k + 1);
}
}
}
return Converter.ToMesh(polygon, triangles);
}
}
}

View File

@@ -0,0 +1,26 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation.
/// </summary>
internal interface IConstraintMesher
{
/// <summary>
/// Triangulates a polygon.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon);
/// <summary>
/// Triangulates a polygon, applying constraint options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="options">Constraint options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, ConstraintOptions options);
}
}

View File

@@ -0,0 +1,57 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Mesh interface.
/// </summary>
internal interface IMesh
{
/// <summary>
/// Gets the vertices of the mesh.
/// </summary>
ICollection<Vertex> Vertices { get; }
/// <summary>
/// Gets the edges of the mesh.
/// </summary>
IEnumerable<Edge> Edges { get; }
/// <summary>
/// Gets the segments (constraint edges) of the mesh.
/// </summary>
ICollection<SubSegment> Segments { get; }
/// <summary>
/// Gets the triangles of the mesh.
/// </summary>
ICollection<Triangle> Triangles { get; }
/// <summary>
/// Gets the holes of the mesh.
/// </summary>
IList<Point> Holes { get; }
/// <summary>
/// Gets the bounds of the mesh.
/// </summary>
Rectangle Bounds { get; }
/// <summary>
/// Renumber mesh vertices and triangles.
/// </summary>
void Renumber();
/// <summary>
/// Refine the mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="conforming">
/// A value indicating, if the refined mesh should be Conforming Delaunay.
/// </param>
void Refine(QualityOptions quality, bool delaunay);
}
}

View File

@@ -0,0 +1,28 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation with quality constraints.
/// </summary>
internal interface IQualityMesher
{
/// <summary>
/// Triangulates a polygon, applying quality options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="quality">Quality options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, QualityOptions quality);
/// <summary>
/// Triangulates a polygon, applying quality and constraint options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="options">Constraint options.</param>
/// <param name="quality">Quality options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality);
}
}

View File

@@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangulator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for point set triangulation.
/// </summary>
internal interface ITriangulator
{
/// <summary>
/// Triangulates a point set.
/// </summary>
/// <param name="points">Collection of points.</param>
/// <param name="config"></param>
/// <returns>Mesh</returns>
IMesh Triangulate(IList<Vertex> points, Configuration config);
}
}

View File

@@ -0,0 +1,102 @@
// -----------------------------------------------------------------------
// <copyright file="EdgeEnumerator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Iterators
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Enumerates the edges of a triangulation.
/// </summary>
internal class EdgeIterator : IEnumerator<Edge>
{
IEnumerator<Triangle> triangles;
Otri tri = default(Otri);
Otri neighbor = default(Otri);
Osub sub = default(Osub);
Edge current;
Vertex p1, p2;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeIterator" /> class.
/// </summary>
public EdgeIterator(Mesh mesh)
{
triangles = mesh.triangles.GetEnumerator();
triangles.MoveNext();
tri.tri = triangles.Current;
tri.orient = 0;
}
public Edge Current
{
get { return current; }
}
public void Dispose()
{
this.triangles.Dispose();
}
object System.Collections.IEnumerator.Current
{
get { return current; }
}
public bool MoveNext()
{
if (tri.tri == null)
{
return false;
}
current = null;
while (current == null)
{
if (tri.orient == 3)
{
if (triangles.MoveNext())
{
tri.tri = triangles.Current;
tri.orient = 0;
}
else
{
// Finally no more triangles
return false;
}
}
tri.Sym(ref neighbor);
if ((tri.tri.id < neighbor.tri.id) || (neighbor.tri.id == Mesh.DUMMY))
{
p1 = tri.Org();
p2 = tri.Dest();
tri.Pivot(ref sub);
// Boundary mark of dummysub is 0, so we don't need to worry about that.
current = new Edge(p1.id, p2.id, sub.seg.boundary);
}
tri.orient++;
}
return true;
}
public void Reset()
{
this.triangles.Reset();
}
}
}

View File

@@ -0,0 +1,123 @@
// -----------------------------------------------------------------------
// <copyright file="RegionIterator.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Iterators
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
/// <summary>
/// Iterates the region a given triangle belongs to and applies an action
/// to each connected trianlge in that region.
/// </summary>
/// <remarks>
/// The default action is to set the region id and area constraint.
/// </remarks>
internal class RegionIterator
{
List<Triangle> region;
public RegionIterator(Mesh mesh)
{
this.region = new List<Triangle>();
}
/// <summary>
/// Set the region attribute of all trianlges connected to given triangle.
/// </summary>
/// <param name="triangle">The triangle seed.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, int boundary = 0)
{
this.Process(triangle, (tri) =>
{
// Set the region id and area constraint.
tri.label = triangle.label;
tri.area = triangle.area;
}, boundary);
}
/// <summary>
/// Process all trianlges connected to given triangle and apply given action.
/// </summary>
/// <param name="triangle">The seeding triangle.</param>
/// <param name="action">The action to apply to each triangle.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, Action<Triangle> action, int boundary = 0)
{
// Make sure the triangle under consideration still exists.
// It may have been eaten by the virus.
if (triangle.id == Mesh.DUMMY || Otri.IsDead(triangle))
{
return;
}
// Add the seeding triangle to the region.
region.Add(triangle);
triangle.infected = true;
if (boundary == 0)
{
// Stop at any subsegment.
ProcessRegion(action, seg => seg.hash == Mesh.DUMMY);
}
else
{
// Stop at segments that have the given boundary label.
ProcessRegion(action, seg => seg.boundary != boundary);
}
// Free up memory (virus pool should be empty anyway).
region.Clear();
}
/// <summary>
/// Apply given action to each triangle of selected region.
/// </summary>
/// <param name="action"></param>
/// <param name="protector"></param>
void ProcessRegion(Action<Triangle> action, Func<SubSegment, bool> protector)
{
Otri testtri = default(Otri);
Otri neighbor = default(Otri);
Osub neighborsubseg = default(Osub);
// Loop through all the infected triangles, spreading the attribute
// and/or area constraint to their neighbors, then to their neighbors'
// neighbors.
for (int i = 0; i < region.Count; i++)
{
// WARNING: Don't use foreach, viri list gets modified.
testtri.tri = region[i];
// Apply function.
action(testtri.tri);
// Check each of the triangle's three neighbors.
for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
{
// Find the neighbor.
testtri.Sym(ref neighbor);
// Check for a subsegment between the triangle and its neighbor.
testtri.Pivot(ref neighborsubseg);
// Make sure the neighbor exists, is not already infected, and
// isn't protected by a subsegment.
if ((neighbor.tri.id != Mesh.DUMMY) && !neighbor.IsInfected()
&& protector(neighborsubseg.seg))
{
// Infect the neighbor.
neighbor.Infect();
// Ensure that the neighbor's neighbors will be infected.
region.Add(neighbor.tri);
}
}
}
// Uninfect all triangles.
foreach (var virus in region)
{
virus.infected = false;
}
// Empty the virus pool.
region.Clear();
}
}
}

View File

@@ -0,0 +1,899 @@
// -----------------------------------------------------------------------
// <copyright file="QualityMesher.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Logging;
using Animation.TriangleNet.Meshing.Data;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology;
/// <summary>
/// Provides methods for mesh quality enforcement and testing.
/// </summary>
class QualityMesher
{
IPredicates predicates;
Queue<BadSubseg> badsubsegs;
BadTriQueue queue;
Mesh mesh;
Behavior behavior;
NewLocation newLocation;
ILog<LogItem> logger;
// Stores the vertices of the triangle that contains newvertex
// in SplitTriangle method.
Triangle newvertex_tri;
public QualityMesher(Mesh mesh, Configuration config)
{
logger = Log.Instance;
badsubsegs = new Queue<BadSubseg>();
queue = new BadTriQueue();
this.mesh = mesh;
this.predicates = config.Predicates();
this.behavior = mesh.behavior;
newLocation = new NewLocation(mesh, predicates);
newvertex_tri = new Triangle();
}
/// <summary>
/// Apply quality constraints to a mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="delaunay">A value indicating, if the refined mesh should be Conforming Delaunay.</param>
public void Apply(QualityOptions quality, bool delaunay = false)
{
// Copy quality options
if (quality != null)
{
behavior.Quality = true;
behavior.MinAngle = quality.MinimumAngle;
behavior.MaxAngle = quality.MaximumAngle;
behavior.MaxArea = quality.MaximumArea;
behavior.UserTest = quality.UserTest;
behavior.VarArea = quality.VariableArea;
behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay;
mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints;
}
// TODO: remove
if (!behavior.Poly)
{
// Be careful not to allocate space for element area constraints that
// will never be assigned any value (other than the default -1.0).
behavior.VarArea = false;
}
// Ensure that no vertex can be mistaken for a triangular bounding
// box vertex in insertvertex().
mesh.infvertex1 = null;
mesh.infvertex2 = null;
mesh.infvertex3 = null;
if (behavior.useSegments)
{
mesh.checksegments = true;
}
if (behavior.Quality && mesh.triangles.Count > 0)
{
// Enforce angle and area constraints.
EnforceQuality();
}
}
/// <summary>
/// Add a bad subsegment to the queue.
/// </summary>
/// <param name="badseg">Bad subsegment.</param>
public void AddBadSubseg(BadSubseg badseg)
{
badsubsegs.Enqueue(badseg);
}
#region Check
/// <summary>
/// Check a subsegment to see if it is encroached; add it to the list if it is.
/// </summary>
/// <param name="testsubseg">The subsegment to check.</param>
/// <returns>Returns a nonzero value if the subsegment is encroached.</returns>
/// <remarks>
/// A subsegment is encroached if there is a vertex in its diametral lens.
/// For Ruppert's algorithm (-D switch), the "diametral lens" is the
/// diametral circle. For Chew's algorithm (default), the diametral lens is
/// just big enough to enclose two isosceles triangles whose bases are the
/// subsegment. Each of the two isosceles triangles has two angles equal
/// to 'b.minangle'.
///
/// Chew's algorithm does not require diametral lenses at all--but they save
/// time. Any vertex inside a subsegment's diametral lens implies that the
/// triangle adjoining the subsegment will be too skinny, so it's only a
/// matter of time before the encroaching vertex is deleted by Chew's
/// algorithm. It's faster to simply not insert the doomed vertex in the
/// first place, which is why I use diametral lenses with Chew's algorithm.
/// </remarks>
public int CheckSeg4Encroach(ref Osub testsubseg)
{
Otri neighbortri = default(Otri);
Osub testsym = default(Osub);
BadSubseg encroachedseg;
double dotproduct;
int encroached;
int sides;
Vertex eorg, edest, eapex;
encroached = 0;
sides = 0;
eorg = testsubseg.Org();
edest = testsubseg.Dest();
// Check one neighbor of the subsegment.
testsubseg.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find a vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (the diametral circle if 'conformdel' is set). A dot product
// of two sides of the triangle is used to check whether the angle
// at the apex is greater than (180 - 2 'minangle') degrees (for
// lenses; 90 degrees for diametral circles).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached = 1;
}
}
}
// Check the other neighbor of the subsegment.
testsubseg.Sym(ref testsym);
testsym.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find the other vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (or the diametral circle, if 'conformdel' is set).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached += 2;
}
}
}
if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2))))
{
// Add the subsegment to the list of encroached subsegments.
// Be sure to get the orientation right.
encroachedseg = new BadSubseg();
if (encroached == 1)
{
encroachedseg.subseg = testsubseg;
encroachedseg.org = eorg;
encroachedseg.dest = edest;
}
else
{
encroachedseg.subseg = testsym;
encroachedseg.org = edest;
encroachedseg.dest = eorg;
}
badsubsegs.Enqueue(encroachedseg);
}
return encroached;
}
/// <summary>
/// Test a triangle for quality and size.
/// </summary>
/// <param name="testtri">Triangle to check.</param>
/// <remarks>
/// Tests a triangle to see if it satisfies the minimum angle condition and
/// the maximum area condition. Triangles that aren't up to spec are added
/// to the bad triangle queue.
/// </remarks>
public void TestTriangle(ref Otri testtri)
{
Otri tri1 = default(Otri), tri2 = default(Otri);
Osub testsub = default(Osub);
Vertex torg, tdest, tapex;
Vertex base1, base2;
Vertex org1, dest1, org2, dest2;
Vertex joinvertex;
double dxod, dyod, dxda, dyda, dxao, dyao;
double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
double apexlen, orglen, destlen, minedge;
double angle;
double area;
double dist1, dist2;
double maxangle;
torg = testtri.Org();
tdest = testtri.Dest();
tapex = testtri.Apex();
dxod = torg.x - tdest.x;
dyod = torg.y - tdest.y;
dxda = tdest.x - tapex.x;
dyda = tdest.y - tapex.y;
dxao = tapex.x - torg.x;
dyao = tapex.y - torg.y;
dxod2 = dxod * dxod;
dyod2 = dyod * dyod;
dxda2 = dxda * dxda;
dyda2 = dyda * dyda;
dxao2 = dxao * dxao;
dyao2 = dyao * dyao;
// Find the lengths of the triangle's three edges.
apexlen = dxod2 + dyod2;
orglen = dxda2 + dyda2;
destlen = dxao2 + dyao2;
if ((apexlen < orglen) && (apexlen < destlen))
{
// The edge opposite the apex is shortest.
minedge = apexlen;
// Find the square of the cosine of the angle at the apex.
angle = dxda * dxao + dyda * dyao;
angle = angle * angle / (orglen * destlen);
base1 = torg;
base2 = tdest;
testtri.Copy(ref tri1);
}
else if (orglen < destlen)
{
// The edge opposite the origin is shortest.
minedge = orglen;
// Find the square of the cosine of the angle at the origin.
angle = dxod * dxao + dyod * dyao;
angle = angle * angle / (apexlen * destlen);
base1 = tdest;
base2 = tapex;
testtri.Lnext(ref tri1);
}
else
{
// The edge opposite the destination is shortest.
minedge = destlen;
// Find the square of the cosine of the angle at the destination.
angle = dxod * dxda + dyod * dyda;
angle = angle * angle / (apexlen * orglen);
base1 = tapex;
base2 = torg;
testtri.Lprev(ref tri1);
}
if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null))
{
// Check whether the area is larger than permitted.
area = 0.5 * (dxod * dyda - dyod * dxda);
if (behavior.fixedArea && (area > behavior.MaxArea))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Nonpositive area constraints are treated as unconstrained.
if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Check whether the user thinks this triangle is too large.
if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area))
{
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
}
// find the maximum edge and accordingly the pqr orientation
if ((apexlen > orglen) && (apexlen > destlen))
{
// The edge opposite the apex is longest.
// maxedge = apexlen;
// Find the cosine of the angle at the apex.
maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen));
}
else if (orglen > destlen)
{
// The edge opposite the origin is longest.
// maxedge = orglen;
// Find the cosine of the angle at the origin.
maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen));
}
else
{
// The edge opposite the destination is longest.
// maxedge = destlen;
// Find the cosine of the angle at the destination.
maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen));
}
// Check whether the angle is smaller than permitted.
if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0))
{
// Use the rules of Miller, Pav, and Walkington to decide that certain
// triangles should not be split, even if they have bad angles.
// A skinny triangle is not split if its shortest edge subtends a
// small input angle, and both endpoints of the edge lie on a
// concentric circular shell. For convenience, I make a small
// adjustment to that rule: I check if the endpoints of the edge
// both lie in segment interiors, equidistant from the apex where
// the two segments meet.
// First, check if both points lie in segment interiors.
if ((base1.type == VertexType.SegmentVertex) &&
(base2.type == VertexType.SegmentVertex))
{
// Check if both points lie in a common segment. If they do, the
// skinny triangle is enqueued to be split as usual.
tri1.Pivot(ref testsub);
if (testsub.seg.hash == Mesh.DUMMY)
{
// No common segment. Find a subsegment that contains 'torg'.
tri1.Copy(ref tri2);
do
{
tri1.Oprev();
tri1.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org1 = testsub.SegOrg();
dest1 = testsub.SegDest();
// Find a subsegment that contains 'tdest'.
do
{
tri2.Dnext();
tri2.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org2 = testsub.SegOrg();
dest2 = testsub.SegDest();
// Check if the two containing segments have an endpoint in common.
joinvertex = null;
if ((dest1.x == org2.x) && (dest1.y == org2.y))
{
joinvertex = dest1;
}
else if ((org1.x == dest2.x) && (org1.y == dest2.y))
{
joinvertex = org1;
}
if (joinvertex != null)
{
// Compute the distance from the common endpoint (of the two
// segments) to each of the endpoints of the shortest edge.
dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) +
(base1.y - joinvertex.y) * (base1.y - joinvertex.y));
dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) +
(base2.y - joinvertex.y) * (base2.y - joinvertex.y));
// If the two distances are equal, don't split the triangle.
if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2))
{
// Return now to avoid enqueueing the bad triangle.
return;
}
}
}
}
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
}
}
#endregion
#region Maintanance
/// <summary>
/// Traverse the entire list of subsegments, and check each to see if it
/// is encroached. If so, add it to the list.
/// </summary>
private void TallyEncs()
{
Osub subsegloop = default(Osub);
subsegloop.orient = 0;
foreach (var seg in mesh.subsegs.Values)
{
subsegloop.seg = seg;
// If the segment is encroached, add it to the list.
CheckSeg4Encroach(ref subsegloop);
}
}
/// <summary>
/// Split all the encroached subsegments.
/// </summary>
/// <param name="triflaws">A flag that specifies whether one should take
/// note of new bad triangles that result from inserting vertices to repair
/// encroached subsegments.</param>
/// <remarks>
/// Each encroached subsegment is repaired by splitting it - inserting a
/// vertex at or near its midpoint. Newly inserted vertices may encroach
/// upon other subsegments; these are also repaired.
/// </remarks>
private void SplitEncSegs(bool triflaws)
{
Otri enctri = default(Otri);
Otri testtri = default(Otri);
Osub testsh = default(Osub);
Osub currentenc = default(Osub);
BadSubseg seg;
Vertex eorg, edest, eapex;
Vertex newvertex;
InsertVertexResult success;
double segmentlength, nearestpoweroftwo;
double split;
double multiplier, divisor;
bool acuteorg, acuteorg2, acutedest, acutedest2;
// Note that steinerleft == -1 if an unlimited number
// of Steiner points is allowed.
while (badsubsegs.Count > 0)
{
if (mesh.steinerleft == 0)
{
break;
}
seg = badsubsegs.Dequeue();
currentenc = seg.subseg;
eorg = currentenc.Org();
edest = currentenc.Dest();
// Make sure that this segment is still the same segment it was
// when it was determined to be encroached. If the segment was
// enqueued multiple times (because several newly inserted
// vertices encroached it), it may have already been split.
if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest))
{
// To decide where to split a segment, we need to know if the
// segment shares an endpoint with an adjacent segment.
// The concern is that, if we simply split every encroached
// segment in its center, two adjacent segments with a small
// angle between them might lead to an infinite loop; each
// vertex added to split one segment will encroach upon the
// other segment, which must then be split with a vertex that
// will encroach upon the first segment, and so on forever.
// To avoid this, imagine a set of concentric circles, whose
// radii are powers of two, about each segment endpoint.
// These concentric circles determine where the segment is
// split. (If both endpoints are shared with adjacent
// segments, split the segment in the middle, and apply the
// concentric circles for later splittings.)
// Is the origin shared with another segment?
currentenc.Pivot(ref enctri);
enctri.Lnext(ref testtri);
testtri.Pivot(ref testsh);
acuteorg = testsh.seg.hash != Mesh.DUMMY;
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest = testsh.seg.hash != Mesh.DUMMY;
// If we're using Chew's algorithm (rather than Ruppert's)
// to define encroachment, delete free vertices from the
// subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg && !acutedest)
{
eapex = enctri.Apex();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
currentenc.Pivot(ref enctri);
eapex = enctri.Apex();
enctri.Lprev(ref testtri);
}
}
// Now, check the other side of the segment, if there's a triangle there.
enctri.Sym(ref testtri);
if (testtri.tri.id != Mesh.DUMMY)
{
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest2 = testsh.seg.hash != Mesh.DUMMY;
acutedest = acutedest || acutedest2;
// Is the origin shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acuteorg2 = testsh.seg.hash != Mesh.DUMMY;
acuteorg = acuteorg || acuteorg2;
// Delete free vertices from the subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2)
{
eapex = testtri.Org();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
enctri.Sym(ref testtri);
eapex = testtri.Apex();
testtri.Lprev();
}
}
}
// Use the concentric circles if exactly one endpoint is shared
// with another adjacent segment.
if (acuteorg || acutedest)
{
segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) +
(edest.y - eorg.y) * (edest.y - eorg.y));
// Find the power of two that most evenly splits the segment.
// The worst case is a 2:1 ratio between subsegment lengths.
nearestpoweroftwo = 1.0;
while (segmentlength > 3.0 * nearestpoweroftwo)
{
nearestpoweroftwo *= 2.0;
}
while (segmentlength < 1.5 * nearestpoweroftwo)
{
nearestpoweroftwo *= 0.5;
}
// Where do we split the segment?
split = nearestpoweroftwo / segmentlength;
if (acutedest)
{
split = 1.0 - split;
}
}
else
{
// If we're not worried about adjacent segments, split
// this segment in the middle.
split = 0.5;
}
// Create the new vertex (interpolate coordinates).
newvertex = new Vertex(
eorg.x + split * (edest.x - eorg.x),
eorg.y + split * (edest.y - eorg.y),
currentenc.seg.boundary
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.SegmentVertex;
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
mesh.vertices.Add(newvertex.hash, newvertex);
#if USE_ATTRIBS
// Interpolate attributes.
for (int i = 0; i < mesh.nextras; i++)
{
newvertex.attributes[i] = eorg.attributes[i]
+ split * (edest.attributes[i] - eorg.attributes[i]);
}
#endif
#if USE_Z
newvertex.z = eorg.z + split * (edest.z - eorg.z);
#endif
if (!Behavior.NoExact)
{
// Roundoff in the above calculation may yield a 'newvertex'
// that is not precisely collinear with 'eorg' and 'edest'.
// Improve collinearity by one step of iterative refinement.
multiplier = predicates.CounterClockwise(eorg, edest, newvertex);
divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) +
(eorg.y - edest.y) * (eorg.y - edest.y));
if ((multiplier != 0.0) && (divisor != 0.0))
{
multiplier = multiplier / divisor;
// Watch out for NANs.
if (!double.IsNaN(multiplier))
{
newvertex.x += multiplier * (edest.y - eorg.y);
newvertex.y += multiplier * (eorg.x - edest.x);
}
}
}
// Check whether the new vertex lies on an endpoint.
if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) ||
((newvertex.x == edest.x) && (newvertex.y == edest.y)))
{
logger.Error("Ran out of precision: I attempted to split a"
+ " segment to a smaller size than can be accommodated by"
+ " the finite precision of floating point arithmetic.",
"Quality.SplitEncSegs()");
throw new Exception("Ran out of precision");
}
// Insert the splitting vertex. This should always succeed.
success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws);
if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching))
{
logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()");
throw new Exception("Failure to split a segment.");
}
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
// Check the two new subsegments to see if they're encroached.
CheckSeg4Encroach(ref currentenc);
currentenc.Next();
CheckSeg4Encroach(ref currentenc);
}
// Set subsegment's origin to NULL. This makes it possible to detect dead
// badsubsegs when traversing the list of all badsubsegs.
seg.org = null;
}
}
/// <summary>
/// Test every triangle in the mesh for quality measures.
/// </summary>
private void TallyFaces()
{
Otri triangleloop = default(Otri);
triangleloop.orient = 0;
foreach (var tri in mesh.triangles)
{
triangleloop.tri = tri;
// If the triangle is bad, enqueue it.
TestTriangle(ref triangleloop);
}
}
/// <summary>
/// Inserts a vertex at the circumcenter of a triangle. Deletes
/// the newly inserted vertex if it encroaches upon a segment.
/// </summary>
/// <param name="badtri"></param>
private void SplitTriangle(BadTriangle badtri)
{
Otri badotri = default(Otri);
Vertex borg, bdest, bapex;
Point newloc; // Location of the new vertex
double xi = 0, eta = 0;
InsertVertexResult success;
bool errorflag;
badotri = badtri.poortri;
borg = badotri.Org();
bdest = badotri.Dest();
bapex = badotri.Apex();
// Make sure that this triangle is still the same triangle it was
// when it was tested and determined to be of bad quality.
// Subsequent transformations may have made it a different triangle.
if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) &&
(bdest == badtri.dest) && (bapex == badtri.apex))
{
errorflag = false;
// Create a new vertex at the triangle's circumcenter.
// Using the original (simpler) Steiner point location method
// for mesh refinement.
// TODO: NewLocation doesn't work for refinement. Why? Maybe
// reset VertexType?
if (behavior.fixedArea || behavior.VarArea)
{
newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant);
}
else
{
newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri);
}
// Check whether the new vertex lies on a triangle vertex.
if (((newloc.x == borg.x) && (newloc.y == borg.y)) ||
((newloc.x == bdest.x) && (newloc.y == bdest.y)) ||
((newloc.x == bapex.x) && (newloc.y == bapex.y)))
{
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
else
{
// The new vertex must be in the interior, and therefore is a
// free vertex with a marker of zero.
Vertex newvertex = new Vertex(newloc.x, newloc.y, 0
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.FreeVertex;
// Ensure that the handle 'badotri' does not represent the longest
// edge of the triangle. This ensures that the circumcenter must
// fall to the left of this edge, so point location will work.
// (If the angle org-apex-dest exceeds 90 degrees, then the
// circumcenter lies outside the org-dest edge, and eta is
// negative. Roundoff error might prevent eta from being
// negative when it should be, so I test eta against xi.)
if (eta < xi)
{
badotri.Lprev();
}
// Assign triangle for attributes interpolation.
newvertex.tri.tri = newvertex_tri;
// Insert the circumcenter, searching from the edge of the triangle,
// and maintain the Delaunay property of the triangulation.
Osub tmp = default(Osub);
success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true);
if (success == InsertVertexResult.Successful)
{
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
#if USE_ATTRIBS
if (mesh.nextras > 0)
{
Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras);
}
#endif
#if USE_Z
Interpolation.InterpolateZ(newvertex, newvertex.tri.tri);
#endif
mesh.vertices.Add(newvertex.hash, newvertex);
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
}
else if (success == InsertVertexResult.Encroaching)
{
// If the newly inserted vertex encroaches upon a subsegment,
// delete the new vertex.
mesh.UndoVertex();
}
else if (success == InsertVertexResult.Violating)
{
// Failed to insert the new vertex, but some subsegment was
// marked as being encroached.
}
else
{ // success == DUPLICATEVERTEX
// Couldn't insert the new vertex because a vertex is already there.
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
}
if (errorflag)
{
logger.Error("The new vertex is at the circumcenter of triangle: This probably "
+ "means that I am trying to refine triangles to a smaller size than can be "
+ "accommodated by the finite precision of floating point arithmetic.",
"Quality.SplitTriangle()");
throw new Exception("The new vertex is at the circumcenter of triangle.");
}
}
}
/// <summary>
/// Remove all the encroached subsegments and bad triangles from the triangulation.
/// </summary>
private void EnforceQuality()
{
BadTriangle badtri;
// Test all segments to see if they're encroached.
TallyEncs();
// Fix encroached subsegments without noting bad triangles.
SplitEncSegs(false);
// At this point, if we haven't run out of Steiner points, the
// triangulation should be (conforming) Delaunay.
// Next, we worry about enforcing triangle quality.
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null)
{
// TODO: Reset queue? (Or is it always empty at this point)
// Test all triangles to see if they're bad.
TallyFaces();
mesh.checkquality = true;
while ((queue.Count > 0) && (mesh.steinerleft != 0))
{
// Fix one bad triangle by inserting a vertex at its circumcenter.
badtri = queue.Dequeue();
SplitTriangle(badtri);
if (badsubsegs.Count > 0)
{
// Put bad triangle back in queue for another try later.
queue.Enqueue(badtri);
// Fix any encroached subsegments that resulted.
// Record any new bad triangles that result.
SplitEncSegs(true);
}
}
}
// At this point, if the "-D" switch was selected and we haven't run out
// of Steiner points, the triangulation should be (conforming) Delaunay
// and have no low-quality triangles.
// Might we have run out of Steiner points too soon?
if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0))
{
logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, "
+ "and therefore might not be truly Delaunay. If the Delaunay property is important "
+ "to you, try increasing the number of Steiner points.",
"Quality.EnforceQuality()");
}
}
#endregion
}
}

View File

@@ -0,0 +1,55 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Mesh constraint options for quality triangulation.
/// </summary>
internal class QualityOptions
{
/// <summary>
/// Gets or sets a maximum angle constraint.
/// </summary>
public double MaximumAngle { get; set; }
/// <summary>
/// Gets or sets a minimum angle constraint.
/// </summary>
public double MinimumAngle { get; set; }
/// <summary>
/// Gets or sets a maximum triangle area constraint.
/// </summary>
public double MaximumArea { get; set; }
/// <summary>
/// Gets or sets a user-defined triangle constraint.
/// </summary>
/// <remarks>
/// The test function will be called for each triangle in the mesh. The
/// second argument is the area of the triangle tested. If the function
/// returns true, the triangle is considered bad and will be refined.
/// </remarks>
public Func<ITriangle, double, bool> UserTest { get; set; }
/// <summary>
/// Gets or sets an area constraint per triangle.
/// </summary>
/// <remarks>
/// If this flag is set to true, the <see cref="ITriangle.Area"/> value will
/// be used to check if a triangle needs refinement.
/// </remarks>
public bool VariableArea { get; set; }
/// <summary>
/// Gets or sets the maximum number of Steiner points to be inserted into the mesh.
/// </summary>
/// <remarks>
/// If the value is 0 (default), an unknown number of Steiner points may be inserted
/// to meet the other quality constraints.
/// </remarks>
public int SteinerPoints { get; set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Triangle")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Triangle")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("96a540d0-1772-4bed-8d25-ef5fa23cd1bc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]

View File

@@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="ISmoother.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Smoothing
{
using Animation.TriangleNet.Meshing;
/// <summary>
/// Interface for mesh smoothers.
/// </summary>
internal interface ISmoother
{
void Smooth(IMesh mesh);
void Smooth(IMesh mesh, int limit);
}
}

View File

@@ -0,0 +1,175 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleSmoother.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Smoothing
{
using System.Linq;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Meshing;
using Animation.TriangleNet.Topology.DCEL;
using Animation.TriangleNet.Voronoi;
/// <summary>
/// Simple mesh smoother implementation.
/// </summary>
/// <remarks>
/// Vertices wich should not move (e.g. segment vertices) MUST have a
/// boundary mark greater than 0.
/// </remarks>
internal class SimpleSmoother : ISmoother
{
TrianglePool pool;
Configuration config;
IVoronoiFactory factory;
ConstraintOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
public SimpleSmoother()
: this(new VoronoiFactory())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
public SimpleSmoother(IVoronoiFactory factory)
{
this.factory = factory;
this.pool = new TrianglePool();
this.config = new Configuration(
() => RobustPredicates.Default,
() => pool.Restart());
this.options = new ConstraintOptions() { ConformingDelaunay = true };
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
/// <param name="factory">Voronoi object factory.</param>
/// <param name="config">Configuration.</param>
public SimpleSmoother(IVoronoiFactory factory, Configuration config)
{
this.factory = factory;
this.config = config;
this.options = new ConstraintOptions() { ConformingDelaunay = true };
}
public void Smooth(IMesh mesh)
{
Smooth(mesh, 10);
}
public void Smooth(IMesh mesh, int limit)
{
var smoothedMesh = (Mesh)mesh;
var mesher = new GenericMesher(config);
var predicates = config.Predicates();
// The smoother should respect the mesh segment splitting behavior.
this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect;
// Take a few smoothing rounds (Lloyd's algorithm).
for (int i = 0; i < limit; i++)
{
Step(smoothedMesh, factory, predicates);
// Actually, we only want to rebuild, if the mesh is no longer
// Delaunay. Flipping edges could be the right choice instead
// of re-triangulating...
smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options);
factory.Reset();
}
smoothedMesh.CopyTo((Mesh)mesh);
}
private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
{
var voronoi = new BoundedVoronoi(mesh, factory, predicates);
if (!voronoi.IsConsistent())
return;
double x, y;
foreach (var face in voronoi.Faces)
{
if (face.generator.label == 0)
{
Centroid(face, out x, out y);
face.generator.x = x;
face.generator.y = y;
}
}
}
/// <summary>
/// Calculate the centroid of a polygon.
/// </summary>
private void Centroid(Face face, out double x, out double y)
{
double ai, atmp = 0, xtmp = 0, ytmp = 0;
var edge = face.Edge;
var first = edge.Next.ID;
Point p, q;
do
{
p = edge.Origin;
q = edge.Twin.Origin;
ai = p.x * q.y - q.x * p.y;
atmp += ai;
xtmp += (q.x + p.x) * ai;
ytmp += (q.y + p.y) * ai;
edge = edge.Next;
}
while (edge.Next.ID != first);
x = xtmp / (3 * atmp);
y = ytmp / (3 * atmp);
//area = atmp / 2;
}
/// <summary>
/// Rebuild the input geometry.
/// </summary>
private Polygon Rebuild(Mesh mesh)
{
var data = new Polygon(mesh.vertices.Count);
foreach (var v in mesh.vertices.Values)
{
// Reset to input vertex.
v.type = VertexType.InputVertex;
data.Points.Add(v);
}
data.Segments.AddRange(mesh.subsegs.Values.Cast<ISegment>());
data.Holes.AddRange(mesh.holes);
data.Regions.AddRange(mesh.regions);
return data;
}
}
}

View File

@@ -0,0 +1,201 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Smoothing
{
using System;
using Animation.TriangleNet.Topology.DCEL;
using Animation.TriangleNet.Voronoi;
/// <summary>
/// Factory which re-uses objects in the smoothing loop to enhance performance.
/// </summary>
/// <remarks>
/// See <see cref="SimpleSmoother"/>.
/// </remarks>
class VoronoiFactory : IVoronoiFactory
{
ObjectPool<Vertex> vertices;
ObjectPool<HalfEdge> edges;
ObjectPool<Face> faces;
public VoronoiFactory()
{
vertices = new ObjectPool<Vertex>();
edges = new ObjectPool<HalfEdge>();
faces = new ObjectPool<Face>();
}
public void Initialize(int vertexCount, int edgeCount, int faceCount)
{
vertices.Capacity = vertexCount;
edges.Capacity = edgeCount;
faces.Capacity = faceCount;
for (int i = vertices.Count; i < vertexCount; i++)
{
vertices.Put(new Vertex(0, 0));
}
for (int i = edges.Count; i < edgeCount; i++)
{
edges.Put(new HalfEdge(null));
}
for (int i = faces.Count; i < faceCount; i++)
{
faces.Put(new Face(null));
}
Reset();
}
public void Reset()
{
vertices.Release();
edges.Release();
faces.Release();
}
public Vertex CreateVertex(double x, double y)
{
Vertex vertex;
if (vertices.TryGet(out vertex))
{
vertex.x = x;
vertex.y = y;
vertex.leaving = null;
return vertex;
}
vertex = new Vertex(x, y);
vertices.Put(vertex);
return vertex;
}
public HalfEdge CreateHalfEdge(Vertex origin, Face face)
{
HalfEdge edge;
if (edges.TryGet(out edge))
{
edge.origin = origin;
edge.face = face;
edge.next = null;
edge.twin = null;
if (face != null && face.edge == null)
{
face.edge = edge;
}
return edge;
}
edge = new HalfEdge(origin, face);
edges.Put(edge);
return edge;
}
public Face CreateFace(Geometry.Vertex vertex)
{
Face face;
if (faces.TryGet(out face))
{
face.id = vertex.id;
face.generator = vertex;
face.edge = null;
return face;
}
face = new Face(vertex);
faces.Put(face);
return face;
}
class ObjectPool<T> where T : class
{
int index, count;
T[] pool;
public int Count
{
get { return count; }
}
public int Capacity
{
get { return this.pool.Length; }
set { Resize(value); }
}
public ObjectPool(int capacity = 3)
{
this.index = 0;
this.count = 0;
this.pool = new T[capacity];
}
public ObjectPool(T[] pool)
{
this.index = 0;
this.count = 0;
this.pool = pool;
}
public bool TryGet(out T obj)
{
if (this.index < this.count)
{
obj = this.pool[this.index++];
return true;
}
obj = null;
return false;
}
public void Put(T obj)
{
var capacity = this.pool.Length;
if (capacity <= this.count)
{
Resize(2 * capacity);
}
this.pool[this.count++] = obj;
this.index++;
}
public void Release()
{
this.index = 0;
}
private void Resize(int size)
{
if (size > this.count)
{
Array.Resize(ref this.pool, size);
}
}
}
}
}

View File

@@ -0,0 +1,286 @@
// -----------------------------------------------------------------------
// <copyright file="AdjacencyMatrix.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
/// <summary>
/// The adjacency matrix of the mesh.
/// </summary>
internal class AdjacencyMatrix
{
// Number of adjacency entries.
int nnz;
// Pointers into the actual adjacency structure adj. Information about row k is
// stored in entries pcol(k) through pcol(k+1)-1 of adj. Size: N + 1
int[] pcol;
// The adjacency structure. For each row, it contains the column indices
// of the nonzero entries. Size: nnz
int[] irow;
/// <summary>
/// Gets the number of columns (nodes of the mesh).
/// </summary>
public readonly int N;
/// <summary>
/// Gets the column pointers.
/// </summary>
public int[] ColumnPointers
{
get { return pcol; }
}
/// <summary>
/// Gets the row indices.
/// </summary>
public int[] RowIndices
{
get { return irow; }
}
public AdjacencyMatrix(Mesh mesh)
{
this.N = mesh.vertices.Count;
// Set up the adj_row adjacency pointer array.
this.pcol = AdjacencyCount(mesh);
this.nnz = pcol[N];
// Set up the adj adjacency array.
this.irow = AdjacencySet(mesh, this.pcol);
SortIndices();
}
public AdjacencyMatrix(int[] pcol, int[] irow)
{
this.N = pcol.Length - 1;
this.nnz = pcol[N];
this.pcol = pcol;
this.irow = irow;
if (pcol[0] != 0)
{
throw new ArgumentException("Expected 0-based indexing.", "pcol");
}
if (irow.Length < nnz)
{
throw new ArgumentException();
}
}
/// <summary>
/// Computes the bandwidth of an adjacency matrix.
/// </summary>
/// <returns>Bandwidth of the adjacency matrix.</returns>
public int Bandwidth()
{
int band_hi;
int band_lo;
int col;
int i, j;
band_lo = 0;
band_hi = 0;
for (i = 0; i < N; i++)
{
for (j = pcol[i]; j < pcol[i + 1]; j++)
{
col = irow[j];
band_lo = Math.Max(band_lo, i - col);
band_hi = Math.Max(band_hi, col - i);
}
}
return band_lo + 1 + band_hi;
}
#region Adjacency matrix
/// <summary>
/// Counts adjacencies in a triangulation.
/// </summary>
/// <remarks>
/// This routine is called to count the adjacencies, so that the
/// appropriate amount of memory can be set aside for storage when
/// the adjacency structure is created.
///
/// The triangulation is assumed to involve 3-node triangles.
///
/// Two nodes are "adjacent" if they are both nodes in some triangle.
/// Also, a node is considered to be adjacent to itself.
/// </remarks>
int[] AdjacencyCount(Mesh mesh)
{
int n = N;
int n1, n2, n3;
int tid, nid;
int[] pcol = new int[n + 1];
// Set every node to be adjacent to itself.
for (int i = 0; i < n; i++)
{
pcol[i] = 1;
}
// Examine each triangle.
foreach (var tri in mesh.triangles)
{
tid = tri.id;
n1 = tri.vertices[0].id;
n2 = tri.vertices[1].id;
n3 = tri.vertices[2].id;
// Add edge (1,2) if this is the first occurrence, that is, if
// the edge (1,2) is on a boundary (nid <= 0) or if this triangle
// is the first of the pair in which the edge occurs (tid < nid).
nid = tri.neighbors[2].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n1] += 1;
pcol[n2] += 1;
}
// Add edge (2,3).
nid = tri.neighbors[0].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n2] += 1;
pcol[n3] += 1;
}
// Add edge (3,1).
nid = tri.neighbors[1].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n3] += 1;
pcol[n1] += 1;
}
}
// We used PCOL to count the number of entries in each column.
// Convert it to pointers into the ADJ array.
for (int i = n; i > 0; i--)
{
pcol[i] = pcol[i - 1];
}
pcol[0] = 0;
for (int i = 1; i <= n; i++)
{
pcol[i] = pcol[i - 1] + pcol[i];
}
return pcol;
}
/// <summary>
/// Sets adjacencies in a triangulation.
/// </summary>
/// <remarks>
/// This routine can be used to create the compressed column storage
/// for a linear triangle finite element discretization of Poisson's
/// equation in two dimensions.
/// </remarks>
int[] AdjacencySet(Mesh mesh, int[] pcol)
{
int n = this.N;
int[] col = new int[n];
// Copy of the adjacency rows input.
Array.Copy(pcol, col, n);
int i, nnz = pcol[n];
// Output list, stores the actual adjacency information.
int[] list = new int[nnz];
// Set every node to be adjacent to itself.
for (i = 0; i < n; i++)
{
list[col[i]] = i;
col[i] += 1;
}
int n1, n2, n3; // Vertex numbers.
int tid, nid; // Triangle and neighbor id.
// Examine each triangle.
foreach (var tri in mesh.triangles)
{
tid = tri.id;
n1 = tri.vertices[0].id;
n2 = tri.vertices[1].id;
n3 = tri.vertices[2].id;
// Add edge (1,2) if this is the first occurrence, that is, if
// the edge (1,2) is on a boundary (nid <= 0) or if this triangle
// is the first of the pair in which the edge occurs (tid < nid).
nid = tri.neighbors[2].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n1]++] = n2;
list[col[n2]++] = n1;
}
// Add edge (2,3).
nid = tri.neighbors[0].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n2]++] = n3;
list[col[n3]++] = n2;
}
// Add edge (3,1).
nid = tri.neighbors[1].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n1]++] = n3;
list[col[n3]++] = n1;
}
}
return list;
}
public void SortIndices()
{
int k1, k2, n = N;
int[] list = this.irow;
// Ascending sort the entries for each column.
for (int i = 0; i < n; i++)
{
k1 = pcol[i];
k2 = pcol[i + 1];
Array.Sort(list, k1, k2 - k1);
}
}
#endregion
}
}

View File

@@ -0,0 +1,676 @@
// -----------------------------------------------------------------------
// <copyright file="CuthillMcKee.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
/// <summary>
/// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of
/// the adjacency matrix associated with the mesh.
/// </summary>
internal class CuthillMcKee
{
// The adjacency matrix of the mesh.
AdjacencyMatrix matrix;
/// <summary>
/// Gets the permutation vector for the Reverse Cuthill-McKee numbering.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <returns>Permutation vector.</returns>
public int[] Renumber(Mesh mesh)
{
// Algorithm needs linear numbering of the nodes.
mesh.Renumber(NodeNumbering.Linear);
return Renumber(new AdjacencyMatrix(mesh));
}
/// <summary>
/// Gets the permutation vector for the Reverse Cuthill-McKee numbering.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <returns>Permutation vector.</returns>
public int[] Renumber(AdjacencyMatrix matrix)
{
this.matrix = matrix;
int bandwidth1 = matrix.Bandwidth();
var pcol = matrix.ColumnPointers;
// Adjust column pointers (1-based indexing).
Shift(pcol, true);
// TODO: Make RCM work with 0-based matrix.
// Compute the RCM permutation.
int[] perm = GenerateRcm();
int[] perm_inv = PermInverse(perm);
int bandwidth2 = PermBandwidth(perm, perm_inv);
if (Log.Verbose)
{
Log.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})",
bandwidth1, bandwidth2));
}
// Adjust column pointers (0-based indexing).
Shift(pcol, false);
return perm_inv;
}
#region RCM
/// <summary>
/// Finds the reverse Cuthill-Mckee ordering for a general graph.
/// </summary>
/// <returns>The RCM ordering.</returns>
/// <remarks>
/// For each connected component in the graph, the routine obtains
/// an ordering by calling RCM.
/// </remarks>
int[] GenerateRcm()
{
// Number of nodes in the mesh.
int n = matrix.N;
int[] perm = new int[n];
int i, num, root;
int iccsze = 0;
int level_num = 0;
/// Index vector for a level structure. The level structure is stored in the
/// currently unused spaces in the permutation vector PERM.
int[] level_row = new int[n + 1];
/// Marks variables that have been numbered.
int[] mask = new int[n];
for (i = 0; i < n; i++)
{
mask[i] = 1;
}
num = 1;
for (i = 0; i < n; i++)
{
// For each masked connected component...
if (mask[i] != 0)
{
root = i;
// Find a pseudo-peripheral node ROOT. The level structure found by
// ROOT_FIND is stored starting at PERM(NUM).
FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1);
// RCM orders the component using ROOT as the starting node.
Rcm(root, mask, perm, num - 1, ref iccsze);
num += iccsze;
// We can stop once every node is in one of the connected components.
if (n < num)
{
return perm;
}
}
}
return perm;
}
/// <summary>
/// RCM renumbers a connected component by the reverse Cuthill McKee algorithm.
/// </summary>
/// <param name="root">the node that defines the connected component. It is used as the starting
/// point for the RCM ordering.</param>
/// <param name="mask">Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with
/// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have
/// their mask values set to zero.</param>
/// <param name="perm">Output, int PERM(NODE_NUM), the RCM ordering.</param>
/// <param name="iccsze">Output, int ICCSZE, the size of the connected component that has been numbered.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The connected component is specified by a node ROOT and a mask.
/// The numbering starts at the root node.
///
/// An outline of the algorithm is as follows:
///
/// X(1) = ROOT.
///
/// for ( I = 1 to N-1)
/// Find all unlabeled neighbors of X(I),
/// assign them the next available labels, in order of increasing degree.
///
/// When done, reverse the ordering.
/// </remarks>
void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int fnbr;
int i, j, k, l;
int jstop, jstrt;
int lbegin, lnbr, lperm, lvlend;
int nbr, node;
// Number of nodes in the mesh.
int n = matrix.N;
/// Workspace, int DEG[NODE_NUM], a temporary vector used to hold
/// the degree of the nodes in the section graph specified by mask and root.
int[] deg = new int[n];
// Find the degrees of the nodes in the component specified by MASK and ROOT.
Degree(root, mask, deg, ref iccsze, perm, offset);
mask[root] = 0;
if (iccsze <= 1)
{
return;
}
lvlend = 0;
lnbr = 1;
// LBEGIN and LVLEND point to the beginning and
// the end of the current level respectively.
while (lvlend < lnbr)
{
lbegin = lvlend + 1;
lvlend = lnbr;
for (i = lbegin; i <= lvlend; i++)
{
// For each node in the current level...
node = perm[offset + i - 1];
jstrt = pcol[node];
jstop = pcol[node + 1] - 1;
// Find the unnumbered neighbors of NODE.
// FNBR and LNBR point to the first and last neighbors
// of the current node in PERM.
fnbr = lnbr + 1;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0)
{
lnbr += 1;
mask[nbr] = 0;
perm[offset + lnbr - 1] = nbr;
}
}
// Node has neighbors
if (lnbr > fnbr)
{
// Sort the neighbors of NODE in increasing order by degree.
// Linear insertion is used.
k = fnbr;
while (k < lnbr)
{
l = k;
k = k + 1;
nbr = perm[offset + k - 1];
while (fnbr < l)
{
lperm = perm[offset + l - 1];
if (deg[lperm - 1] <= deg[nbr - 1])
{
break;
}
perm[offset + l] = lperm;
l = l - 1;
}
perm[offset + l] = nbr;
}
}
}
}
// We now have the Cuthill-McKee ordering. Reverse it.
ReverseVector(perm, offset, iccsze);
}
/// <summary>
/// Finds a pseudo-peripheral node.
/// </summary>
/// <param name="root">On input, ROOT is a node in the the component of the graph for
/// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral
/// node obtained.</param>
/// <param name="mask">MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK
/// is zero are ignored by FNROOT.</param>
/// <param name="level_num">Output, int LEVEL_NUM, is the number of levels in the level
/// structure rooted at the node ROOT.</param>
/// <param name="level_row">Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair
/// containing the level structure found.</param>
/// <param name="level">Output, int LEVEL(NODE_NUM), the level structure array pair
/// containing the level structure found.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The diameter of a graph is the maximum distance (number of edges)
/// between any two nodes of the graph.
///
/// The eccentricity of a node is the maximum distance between that
/// node and any other node of the graph.
///
/// A peripheral node is a node whose eccentricity equals the
/// diameter of the graph.
///
/// A pseudo-peripheral node is an approximation to a peripheral node;
/// it may be a peripheral node, but all we know is that we tried our
/// best.
///
/// The routine is given a graph, and seeks pseudo-peripheral nodes,
/// using a modified version of the scheme of Gibbs, Poole and
/// Stockmeyer. It determines such a node for the section subgraph
/// specified by MASK and ROOT.
///
/// The routine also determines the level structure associated with
/// the given pseudo-peripheral node; that is, how far each node
/// is from the pseudo-peripheral node. The level structure is
/// returned as a list of nodes LS, and pointers to the beginning
/// of the list of nodes that are at a distance of 0, 1, 2, ...,
/// NODE_NUM-1 from the pseudo-peripheral node.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
///
/// Norman Gibbs, William Poole, Paul Stockmeyer,
/// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix,
/// SIAM Journal on Numerical Analysis,
/// Volume 13, pages 236-250, 1976.
///
/// Norman Gibbs,
/// Algorithm 509: A Hybrid Profile Reduction Algorithm,
/// ACM Transactions on Mathematical Software,
/// Volume 2, pages 378-387, 1976.
/// </remarks>
void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row,
int[] level, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int iccsze;
int j, jstrt;
int k, kstop, kstrt;
int mindeg;
int nghbor, ndeg;
int node;
int level_num2 = 0;
// Determine the level structure rooted at ROOT.
GetLevelSet(ref root, mask, ref level_num, level_row, level, offset);
// Count the number of nodes in this level structure.
iccsze = level_row[level_num] - 1;
// Extreme cases:
// A complete graph has a level set of only a single level.
// Every node is equally good (or bad).
// or
// A "line graph" 0--0--0--0--0 has every node in its only level.
// By chance, we've stumbled on the ideal root.
if (level_num == 1 || level_num == iccsze)
{
return;
}
// Pick any node from the last level that has minimum degree
// as the starting point to generate a new level set.
for (;;)
{
mindeg = iccsze;
jstrt = level_row[level_num - 1];
root = level[offset + jstrt - 1];
if (jstrt < iccsze)
{
for (j = jstrt; j <= iccsze; j++)
{
node = level[offset + j - 1];
ndeg = 0;
kstrt = pcol[node - 1];
kstop = pcol[node] - 1;
for (k = kstrt; k <= kstop; k++)
{
nghbor = irow[k - 1];
if (mask[nghbor] > 0)
{
ndeg += 1;
}
}
if (ndeg < mindeg)
{
root = node;
mindeg = ndeg;
}
}
}
// Generate the rooted level structure associated with this node.
GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset);
// If the number of levels did not increase, accept the new ROOT.
if (level_num2 <= level_num)
{
break;
}
level_num = level_num2;
// In the unlikely case that ROOT is one endpoint of a line graph,
// we can exit now.
if (iccsze <= level_num)
{
break;
}
}
}
/// <summary>
/// Generates the connected level structure rooted at a given node.
/// </summary>
/// <param name="root">the node at which the level structure is to be rooted.</param>
/// <param name="mask">MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed.
/// On output, those nodes which were included in the level set have MASK set to 1.</param>
/// <param name="level_num">Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is
/// in level 1. The neighbors of ROOT are in level 2, and so on.</param>
/// <param name="level_row">Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure.</param>
/// <param name="level">Output, int LEVEL[NODE_NUM], the rooted level structure.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// Only nodes for which MASK is nonzero will be considered.
///
/// The root node chosen by the user is assigned level 1, and masked.
/// All (unmasked) nodes reachable from a node in level 1 are
/// assigned level 2 and masked. The process continues until there
/// are no unmasked nodes adjacent to any node in the current level.
/// The number of levels may vary between 2 and NODE_NUM.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
/// </remarks>
void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row,
int[] level, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int i, iccsze;
int j, jstop, jstrt;
int lbegin, lvlend, lvsize;
int nbr;
int node;
mask[root] = 0;
level[offset] = root;
level_num = 0;
lvlend = 0;
iccsze = 1;
// LBEGIN is the pointer to the beginning of the current level, and
// LVLEND points to the end of this level.
for (;;)
{
lbegin = lvlend + 1;
lvlend = iccsze;
level_num += 1;
level_row[level_num - 1] = lbegin;
// Generate the next level by finding all the masked neighbors of nodes
// in the current level.
for (i = lbegin; i <= lvlend; i++)
{
node = level[offset + i - 1];
jstrt = pcol[node];
jstop = pcol[node + 1] - 1;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0)
{
iccsze += 1;
level[offset + iccsze - 1] = nbr;
mask[nbr] = 0;
}
}
}
// Compute the current level width (the number of nodes encountered.)
// If it is positive, generate the next level.
lvsize = iccsze - lvlend;
if (lvsize <= 0)
{
break;
}
}
level_row[level_num] = lvlend + 1;
// Reset MASK to 1 for the nodes in the level structure.
for (i = 0; i < iccsze; i++)
{
mask[level[offset + i]] = 1;
}
}
/// <summary>
/// Computes the degrees of the nodes in the connected component.
/// </summary>
/// <param name="root">the node that defines the connected component.</param>
/// <param name="mask">MASK[NODE_NUM], is nonzero for those nodes which are to be considered.</param>
/// <param name="deg">Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree.</param>
/// <param name="iccsze">Output, int ICCSIZE, the number of nodes in the connected component.</param>
/// <param name="ls">Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the
/// connected component, starting with ROOT, and proceeding by levels.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The connected component is specified by MASK and ROOT.
/// Nodes for which MASK is zero are ignored.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
/// </remarks>
void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int i, ideg;
int j, jstop, jstrt;
int lbegin, lvlend;
int lvsize = 1;
int nbr, node;
// The sign of ADJ_ROW(I) is used to indicate if node I has been considered.
ls[offset] = root;
pcol[root] = -pcol[root];
lvlend = 0;
iccsze = 1;
// If the current level width is nonzero, generate another level.
while (lvsize > 0)
{
// LBEGIN is the pointer to the beginning of the current level, and
// LVLEND points to the end of this level.
lbegin = lvlend + 1;
lvlend = iccsze;
// Find the degrees of nodes in the current level,
// and at the same time, generate the next level.
for (i = lbegin; i <= lvlend; i++)
{
node = ls[offset + i - 1];
jstrt = -pcol[node];
jstop = Math.Abs(pcol[node + 1]) - 1;
ideg = 0;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0) // EDIT: [nbr - 1]
{
ideg = ideg + 1;
if (0 <= pcol[nbr]) // EDIT: [nbr - 1]
{
pcol[nbr] = -pcol[nbr]; // EDIT: [nbr - 1]
iccsze = iccsze + 1;
ls[offset + iccsze - 1] = nbr;
}
}
}
deg[node] = ideg;
}
// Compute the current level width.
lvsize = iccsze - lvlend;
}
// Reset ADJ_ROW to its correct sign and return.
for (i = 0; i < iccsze; i++)
{
node = ls[offset + i];
pcol[node] = -pcol[node];
}
}
#endregion
#region Tools
/// <summary>
/// Computes the bandwidth of a permuted adjacency matrix.
/// </summary>
/// <param name="perm">The permutation.</param>
/// <param name="perm_inv">The inverse permutation.</param>
/// <returns>Bandwidth of the permuted adjacency matrix.</returns>
/// <remarks>
/// The matrix is defined by the adjacency information and a permutation.
/// The routine also computes the bandwidth and the size of the envelope.
/// </remarks>
int PermBandwidth(int[] perm, int[] perm_inv)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int col, i, j;
int band_lo = 0;
int band_hi = 0;
int n = matrix.N;
for (i = 0; i < n; i++)
{
for (j = pcol[perm[i]]; j < pcol[perm[i] + 1]; j++)
{
col = perm_inv[irow[j - 1]];
band_lo = Math.Max(band_lo, i - col);
band_hi = Math.Max(band_hi, col - i);
}
}
return band_lo + 1 + band_hi;
}
/// <summary>
/// Produces the inverse of a given permutation.
/// </summary>
/// <param name="n">Number of items permuted.</param>
/// <param name="perm">PERM[N], a permutation.</param>
/// <returns>The inverse permutation.</returns>
int[] PermInverse(int[] perm)
{
int n = matrix.N;
int[] perm_inv = new int[n];
for (int i = 0; i < n; i++)
{
perm_inv[perm[i]] = i;
}
return perm_inv;
}
/// <summary>
/// Reverses the elements of an integer vector.
/// </summary>
/// <param name="size">number of entries in the array.</param>
/// <param name="a">the array to be reversed.</param>
/// <example>
/// Input:
/// N = 5,
/// A = ( 11, 12, 13, 14, 15 ).
///
/// Output:
/// A = ( 15, 14, 13, 12, 11 ).
/// </example>
void ReverseVector(int[] a, int offset, int size)
{
int i;
int j;
for (i = 0; i < size / 2; i++)
{
j = a[offset + i];
a[offset + i] = a[offset + size - 1 - i];
a[offset + size - 1 - i] = j;
}
}
void Shift(int[] a, bool up)
{
int length = a.Length;
if (up)
{
for (int i = 0; i < length; a[i]++, i++) ;
}
else
{
for (int i = 0; i < length; a[i]--, i++) ;
}
}
#endregion
}
}

View File

@@ -0,0 +1,107 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using Animation.TriangleNet.Geometry;
internal static class Interpolation
{
#if USE_ATTRIBS
/// <summary>
/// Linear interpolation of vertex attributes.
/// </summary>
/// <param name="vertex">The interpolation vertex.</param>
/// <param name="triangle">The triangle containing the vertex.</param>
/// <param name="n">The number of vertex attributes.</param>
/// <remarks>
/// The vertex is expected to lie inside the triangle.
/// </remarks>
internal static void InterpolateAttributes(Vertex vertex, ITriangle triangle, int n)
{
Vertex org = triangle.GetVertex(0);
Vertex dest = triangle.GetVertex(1);
Vertex apex = triangle.GetVertex(2);
double xdo, ydo, xao, yao;
double denominator;
double dx, dy;
double xi, eta;
// Compute the circumcenter of the triangle.
xdo = dest.x - org.x;
ydo = dest.y - org.y;
xao = apex.x - org.x;
yao = apex.y - org.y;
denominator = 0.5 / (xdo * yao - xao * ydo);
//dx = (yao * dodist - ydo * aodist) * denominator;
//dy = (xdo * aodist - xao * dodist) * denominator;
dx = vertex.x - org.x;
dy = vertex.y - org.y;
// To interpolate vertex attributes for the new vertex, define a
// coordinate system with a xi-axis directed from the triangle's
// origin to its destination, and an eta-axis, directed from its
// origin to its apex.
xi = (yao * dx - xao * dy) * (2.0 * denominator);
eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
for (int i = 0; i < n; i++)
{
// Interpolate the vertex attributes.
vertex.attributes[i] = org.attributes[i]
+ xi * (dest.attributes[i] - org.attributes[i])
+ eta * (apex.attributes[i] - org.attributes[i]);
}
}
#endif
#if USE_Z
/// <summary>
/// Linear interpolation of a scalar value.
/// </summary>
/// <param name="p">The interpolation point.</param>
/// <param name="triangle">The triangle containing the point.</param>
/// <remarks>
/// The point is expected to lie inside the triangle.
/// </remarks>
internal static void InterpolateZ(Point p, ITriangle triangle)
{
Vertex org = triangle.GetVertex(0);
Vertex dest = triangle.GetVertex(1);
Vertex apex = triangle.GetVertex(2);
double xdo, ydo, xao, yao;
double denominator;
double dx, dy;
double xi, eta;
// Compute the circumcenter of the triangle.
xdo = dest.x - org.x;
ydo = dest.y - org.y;
xao = apex.x - org.x;
yao = apex.y - org.y;
denominator = 0.5 / (xdo * yao - xao * ydo);
//dx = (yao * dodist - ydo * aodist) * denominator;
//dy = (xdo * aodist - xao * dodist) * denominator;
dx = p.x - org.x;
dy = p.y - org.y;
// To interpolate z value for the given point inserted, define a
// coordinate system with a xi-axis, directed from the triangle's
// origin to its destination, and an eta-axis, directed from its
// origin to its apex.
xi = (yao * dx - xao * dy) * (2.0 * denominator);
eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
p.z = org.z + xi * (dest.z - org.z) + eta * (apex.z - org.z);
}
#endif
}
}

View File

@@ -0,0 +1,226 @@
// -----------------------------------------------------------------------
// <copyright file="IntersectionHelper.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using Animation.TriangleNet.Geometry;
internal static class IntersectionHelper
{
/// <summary>
/// Compute intersection of two segments.
/// </summary>
/// <param name="p0">Segment 1 start point.</param>
/// <param name="p1">Segment 1 end point.</param>
/// <param name="q0">Segment 2 start point.</param>
/// <param name="q1">Segment 2 end point.</param>
/// <param name="c0">The intersection point.</param>
/// <remarks>
/// This is a special case of segment intersection. Since the calling algorithm assures
/// that a valid intersection exists, there's no need to check for any special cases.
/// </remarks>
internal static void IntersectSegments(Point p0, Point p1, Point q0, Point q1, ref Point c0)
{
double ux = p1.x - p0.x;
double uy = p1.y - p0.y;
double vx = q1.x - q0.x;
double vy = q1.y - q0.y;
double wx = p0.x - q0.x;
double wy = p0.y - q0.y;
double d = (ux * vy - uy * vx);
double s = (vx * wy - vy * wx) / d;
// Intersection point
c0.x = p0.X + s * ux;
c0.y = p0.Y + s * uy;
}
/// <summary>
/// Intersect segment with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p0">Segment endpoint.</param>
/// <param name="p1">Segment endpoint.</param>
/// <param name="c0">The new location of p0.</param>
/// <param name="c1">The new location of p1.</param>
/// <returns>Returns true, if segment is clipped.</returns>
/// <remarks>
/// Based on Liang-Barsky function by Daniel White:
/// http://www.skytopia.com/project/articles/compsci/clipping.html
/// </remarks>
internal static bool LiangBarsky(Rectangle rect, Point p0, Point p1, ref Point c0, ref Point c1)
{
// Define the x/y clipping values for the border.
double xmin = rect.Left;
double xmax = rect.Right;
double ymin = rect.Bottom;
double ymax = rect.Top;
// Define the start and end points of the line.
double x0 = p0.X;
double y0 = p0.Y;
double x1 = p1.X;
double y1 = p1.Y;
double t0 = 0.0;
double t1 = 1.0;
double dx = x1 - x0;
double dy = y1 - y0;
double p = 0.0, q = 0.0, r;
for (int edge = 0; edge < 4; edge++)
{
// Traverse through left, right, bottom, top edges.
if (edge == 0) { p = -dx; q = -(xmin - x0); }
if (edge == 1) { p = dx; q = (xmax - x0); }
if (edge == 2) { p = -dy; q = -(ymin - y0); }
if (edge == 3) { p = dy; q = (ymax - y0); }
r = q / p;
if (p == 0 && q < 0) return false; // Don't draw line at all. (parallel line outside)
if (p < 0)
{
if (r > t1) return false; // Don't draw line at all.
else if (r > t0) t0 = r; // Line is clipped!
}
else if (p > 0)
{
if (r < t0) return false; // Don't draw line at all.
else if (r < t1) t1 = r; // Line is clipped!
}
}
c0.X = x0 + t0 * dx;
c0.Y = y0 + t0 * dy;
c1.X = x0 + t1 * dx;
c1.Y = y0 + t1 * dy;
return true; // (clipped) line is drawn
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p0">The ray startpoint (inside the box).</param>
/// <param name="p1">Any point in ray direction (NOT the direction vector).</param>
/// <param name="c1">The intersection point.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
internal static bool BoxRayIntersection(Rectangle rect, Point p0, Point p1, ref Point c1)
{
return BoxRayIntersection(rect, p0, p1.x - p0.x, p1.y - p0.y, ref c1);
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p">The ray startpoint (inside the box).</param>
/// <param name="dx">X direction.</param>
/// <param name="dy">Y direction.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
internal static Point BoxRayIntersection(Rectangle rect, Point p, double dx, double dy)
{
var intersection = new Point();
if (BoxRayIntersection(rect, p, dx, dy, ref intersection))
{
return intersection;
}
return null;
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p">The ray startpoint (inside the box).</param>
/// <param name="dx">X direction.</param>
/// <param name="dy">Y direction.</param>
/// <param name="c">The intersection point.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
internal static bool BoxRayIntersection(Rectangle rect, Point p, double dx, double dy, ref Point c)
{
double x = p.X;
double y = p.Y;
double t1, x1, y1, t2, x2, y2;
// Bounding box
double xmin = rect.Left;
double xmax = rect.Right;
double ymin = rect.Bottom;
double ymax = rect.Top;
// Check if point is inside the bounds
if (x < xmin || x > xmax || y < ymin || y > ymax)
{
return false;
}
// Calculate the cut through the vertical boundaries
if (dx < 0)
{
// Line going to the left: intersect with x = minX
t1 = (xmin - x) / dx;
x1 = xmin;
y1 = y + t1 * dy;
}
else if (dx > 0)
{
// Line going to the right: intersect with x = maxX
t1 = (xmax - x) / dx;
x1 = xmax;
y1 = y + t1 * dy;
}
else
{
// Line going straight up or down: no intersection possible
t1 = double.MaxValue;
x1 = y1 = 0;
}
// Calculate the cut through upper and lower boundaries
if (dy < 0)
{
// Line going downwards: intersect with y = minY
t2 = (ymin - y) / dy;
x2 = x + t2 * dx;
y2 = ymin;
}
else if (dy > 0)
{
// Line going upwards: intersect with y = maxY
t2 = (ymax - y) / dy;
x2 = x + t2 * dx;
y2 = ymax;
}
else
{
// Horizontal line: no intersection possible
t2 = double.MaxValue;
x2 = y2 = 0;
}
if (t1 < t2)
{
c.x = x1;
c.y = y1;
}
else
{
c.x = x2;
c.y = y2;
}
return true;
}
}
}

View File

@@ -0,0 +1,246 @@
// -----------------------------------------------------------------------
// <copyright file="PolygonValidator.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
internal static class PolygonValidator
{
/// <summary>
/// Test the polygon for consistency.
/// </summary>
internal static bool IsConsistent(IPolygon poly)
{
var logger = Log.Instance;
var points = poly.Points;
int horrors = 0;
int i = 0;
int count = points.Count;
if (count < 3)
{
logger.Warning("Polygon must have at least 3 vertices.", "PolygonValidator.IsConsistent()");
return false;
}
foreach (var p in points)
{
if (p == null)
{
horrors++;
logger.Warning(String.Format("Point {0} is null.", i), "PolygonValidator.IsConsistent()");
}
else if (double.IsNaN(p.x) || double.IsNaN(p.y))
{
horrors++;
logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()");
}
else if (double.IsInfinity(p.x) || double.IsInfinity(p.y))
{
horrors++;
logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()");
}
i++;
}
i = 0;
foreach (var seg in poly.Segments)
{
if (seg == null)
{
horrors++;
logger.Warning(String.Format("Segment {0} is null.", i), "PolygonValidator.IsConsistent()");
// Always abort if a NULL-segment is found.
return false;
}
var p = seg.GetVertex(0);
var q = seg.GetVertex(1);
if ((p.x == q.x) && (p.y == q.y))
{
horrors++;
logger.Warning(String.Format("Endpoints of segment {0} are coincident (IDs {1} / {2}).", i, p.id, q.id),
"PolygonValidator.IsConsistent()");
}
i++;
}
if (points[0].id == points[1].id)
{
horrors += CheckVertexIDs(poly, count);
}
else
{
horrors += CheckDuplicateIDs(poly);
}
return horrors == 0;
}
/// <summary>
/// Test the polygon for duplicate vertices.
/// </summary>
internal static bool HasDuplicateVertices(IPolygon poly)
{
var logger = Log.Instance;
int horrors = 0;
var points = poly.Points.ToArray();
VertexSorter.Sort(points);
for (int i = 1; i < points.Length; i++)
{
if (points[i - 1] == points[i])
{
horrors++;
logger.Warning(String.Format("Found duplicate point {0}.", points[i]),
"PolygonValidator.HasDuplicateVertices()");
}
}
return horrors > 0;
}
/// <summary>
/// Test the polygon for 360 degree angles.
/// </summary>
/// <param name="poly">The polygon.</param>
/// <param name="threshold">The angle threshold.</param>
internal static bool HasBadAngles(IPolygon poly, double threshold = 2e-12)
{
var logger = Log.Instance;
int horrors = 0;
int i = 0;
Point p0 = null, p1 = null;
Point q0, q1;
int count = poly.Points.Count;
foreach (var seg in poly.Segments)
{
q0 = p0; // Previous segment start point.
q1 = p1; // Previous segment end point.
p0 = seg.GetVertex(0); // Current segment start point.
p1 = seg.GetVertex(1); // Current segment end point.
if (p0 == p1 || q0 == q1)
{
// Ignore zero-length segments.
continue;
}
if (q0 != null && q1 != null)
{
// The two segments are connected.
if (p0 == q1 && p1 != null)
{
if (IsBadAngle(q0, p0, p1, threshold))
{
horrors++;
logger.Warning(String.Format("Bad segment angle found at index {0}.", i),
"PolygonValidator.HasBadAngles()");
}
}
}
i++;
}
return horrors > 0;
}
private static bool IsBadAngle(Point a, Point b, Point c, double threshold = 0.0)
{
double x = DotProduct(a, b, c);
double y = CrossProductLength(a, b, c);
return Math.Abs(Math.Atan2(y, x)) <= threshold;
}
// Returns the dot product <AB, BC>.
private static double DotProduct(Point a, Point b, Point c)
{
// Calculate the dot product.
return (a.x - b.x) * (c.x - b.x) + (a.y - b.y) * (c.y - b.y);
}
// Returns the length of cross product AB x BC.
private static double CrossProductLength(Point a, Point b, Point c)
{
// Calculate the Z coordinate of the cross product.
return (a.x - b.x) * (c.y - b.y) - (a.y - b.y) * (c.x - b.x);
}
private static int CheckVertexIDs(IPolygon poly, int count)
{
var logger = Log.Instance;
int horrors = 0;
int i = 0;
Vertex p, q;
foreach (var seg in poly.Segments)
{
p = seg.GetVertex(0);
q = seg.GetVertex(1);
if (p.id < 0 || p.id >= count)
{
horrors++;
logger.Warning(String.Format("Segment {0} has invalid startpoint.", i),
"PolygonValidator.IsConsistent()");
}
if (q.id < 0 || q.id >= count)
{
horrors++;
logger.Warning(String.Format("Segment {0} has invalid endpoint.", i),
"PolygonValidator.IsConsistent()");
}
i++;
}
return horrors;
}
private static int CheckDuplicateIDs(IPolygon poly)
{
var ids = new HashSet<int>();
// Check for duplicate ids.
foreach (var p in poly.Points)
{
if (!ids.Add(p.id))
{
Log.Instance.Warning("Found duplicate vertex ids.", "PolygonValidator.IsConsistent()");
return 1;
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,542 @@
// -----------------------------------------------------------------------
// <copyright file="QualityMeasure.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Provides mesh quality information.
/// </summary>
/// <remarks>
/// Given a triangle abc with points A (ax, ay), B (bx, by), C (cx, cy).
///
/// The side lengths are given as
/// a = sqrt((cx - bx)^2 + (cy - by)^2) -- side BC opposite of A
/// b = sqrt((cx - ax)^2 + (cy - ay)^2) -- side CA opposite of B
/// c = sqrt((ax - bx)^2 + (ay - by)^2) -- side AB opposite of C
///
/// The angles are given as
/// ang_a = acos((b^2 + c^2 - a^2) / (2 * b * c)) -- angle at A
/// ang_b = acos((c^2 + a^2 - b^2) / (2 * c * a)) -- angle at B
/// ang_c = acos((a^2 + b^2 - c^2) / (2 * a * b)) -- angle at C
///
/// The semiperimeter is given as
/// s = (a + b + c) / 2
///
/// The area is given as
/// D = abs(ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) / 2
/// = sqrt(s * (s - a) * (s - b) * (s - c))
///
/// The inradius is given as
/// r = D / s
///
/// The circumradius is given as
/// R = a * b * c / (4 * D)
///
/// The altitudes are given as
/// alt_a = 2 * D / a -- altitude above side a
/// alt_b = 2 * D / b -- altitude above side b
/// alt_c = 2 * D / c -- altitude above side c
///
/// The aspect ratio may be given as the ratio of the longest to the
/// shortest edge or, more commonly as the ratio of the circumradius
/// to twice the inradius
/// ar = R / (2 * r)
/// = a * b * c / (8 * (s - a) * (s - b) * (s - c))
/// = a * b * c / ((b + c - a) * (c + a - b) * (a + b - c))
/// </remarks>
internal class QualityMeasure
{
AreaMeasure areaMeasure;
AlphaMeasure alphaMeasure;
Q_Measure qMeasure;
Mesh mesh;
public QualityMeasure()
{
areaMeasure = new AreaMeasure();
alphaMeasure = new AlphaMeasure();
qMeasure = new Q_Measure();
}
#region Public properties
/// <summary>
/// Minimum triangle area.
/// </summary>
public double AreaMinimum
{
get { return areaMeasure.area_min; }
}
/// <summary>
/// Maximum triangle area.
/// </summary>
public double AreaMaximum
{
get { return areaMeasure.area_max; }
}
/// <summary>
/// Ratio of maximum and minimum triangle area.
/// </summary>
public double AreaRatio
{
get { return areaMeasure.area_max / areaMeasure.area_min; }
}
/// <summary>
/// Smallest angle.
/// </summary>
public double AlphaMinimum
{
get { return alphaMeasure.alpha_min; }
}
/// <summary>
/// Maximum smallest angle.
/// </summary>
public double AlphaMaximum
{
get { return alphaMeasure.alpha_max; }
}
/// <summary>
/// Average angle.
/// </summary>
public double AlphaAverage
{
get { return alphaMeasure.alpha_ave; }
}
/// <summary>
/// Average angle weighted by area.
/// </summary>
public double AlphaArea
{
get { return alphaMeasure.alpha_area; }
}
/// <summary>
/// Smallest aspect ratio.
/// </summary>
public double Q_Minimum
{
get { return qMeasure.q_min; }
}
/// <summary>
/// Largest aspect ratio.
/// </summary>
public double Q_Maximum
{
get { return qMeasure.q_max; }
}
/// <summary>
/// Average aspect ratio.
/// </summary>
public double Q_Average
{
get { return qMeasure.q_ave; }
}
/// <summary>
/// Average aspect ratio weighted by area.
/// </summary>
public double Q_Area
{
get { return qMeasure.q_area; }
}
#endregion
public void Update(Mesh mesh)
{
this.mesh = mesh;
// Reset all measures.
areaMeasure.Reset();
alphaMeasure.Reset();
qMeasure.Reset();
Compute();
}
private void Compute()
{
Point a, b, c;
double ab, bc, ca;
double lx, ly;
double area;
int n = 0;
foreach (var tri in mesh.triangles)
{
n++;
a = tri.vertices[0];
b = tri.vertices[1];
c = tri.vertices[2];
lx = a.x - b.x;
ly = a.y - b.y;
ab = Math.Sqrt(lx * lx + ly * ly);
lx = b.x - c.x;
ly = b.y - c.y;
bc = Math.Sqrt(lx * lx + ly * ly);
lx = c.x - a.x;
ly = c.y - a.y;
ca = Math.Sqrt(lx * lx + ly * ly);
area = areaMeasure.Measure(a, b, c);
alphaMeasure.Measure(ab, bc, ca, area);
qMeasure.Measure(ab, bc, ca, area);
}
// Normalize measures
alphaMeasure.Normalize(n, areaMeasure.area_total);
qMeasure.Normalize(n, areaMeasure.area_total);
}
/// <summary>
/// Determines the bandwidth of the coefficient matrix.
/// </summary>
/// <returns>Bandwidth of the coefficient matrix.</returns>
/// <remarks>
/// The quantity computed here is the "geometric" bandwidth determined
/// by the finite element mesh alone.
///
/// If a single finite element variable is associated with each node
/// of the mesh, and if the nodes and variables are numbered in the
/// same way, then the geometric bandwidth is the same as the bandwidth
/// of a typical finite element matrix.
///
/// The bandwidth M is defined in terms of the lower and upper bandwidths:
///
/// M = ML + 1 + MU
///
/// where
///
/// ML = maximum distance from any diagonal label to a nonzero
/// label in the same row, but earlier column,
///
/// MU = maximum distance from any diagonal label to a nonzero
/// label in the same row, but later column.
///
/// Because the finite element node adjacency relationship is symmetric,
/// we are guaranteed that ML = MU.
/// </remarks>
public int Bandwidth()
{
if (mesh == null) return 0;
// Lower and upper bandwidth of the matrix
int ml = 0, mu = 0;
int gi, gj;
foreach (var tri in mesh.triangles)
{
for (int j = 0; j < 3; j++)
{
gi = tri.GetVertex(j).id;
for (int k = 0; k < 3; k++)
{
gj = tri.GetVertex(k).id;
mu = Math.Max(mu, gj - gi);
ml = Math.Max(ml, gi - gj);
}
}
}
return ml + 1 + mu;
}
class AreaMeasure
{
// Minimum area
public double area_min = double.MaxValue;
// Maximum area
public double area_max = -double.MaxValue;
// Total area of geometry
public double area_total = 0;
// Nmber of triangles with zero area
public int area_zero = 0;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
area_min = double.MaxValue;
area_max = -double.MaxValue;
area_total = 0;
area_zero = 0;
}
/// <summary>
/// Compute the area of given triangle.
/// </summary>
/// <param name="a">Triangle corner a.</param>
/// <param name="b">Triangle corner b.</param>
/// <param name="c">Triangle corner c.</param>
/// <returns>Triangle area.</returns>
public double Measure(Point a, Point b, Point c)
{
double area = 0.5 * Math.Abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
area_min = Math.Min(area_min, area);
area_max = Math.Max(area_max, area);
area_total += area;
if (area == 0.0)
{
area_zero = area_zero + 1;
}
return area;
}
}
/// <summary>
/// The alpha measure determines the triangulated pointset quality.
/// </summary>
/// <remarks>
/// The alpha measure evaluates the uniformity of the shapes of the triangles
/// defined by a triangulated pointset.
///
/// We compute the minimum angle among all the triangles in the triangulated
/// dataset and divide by the maximum possible value (which, in degrees,
/// is 60). The best possible value is 1, and the worst 0. A good
/// triangulation should have an alpha score close to 1.
/// </remarks>
class AlphaMeasure
{
// Minimum value over all triangles
public double alpha_min;
// Maximum value over all triangles
public double alpha_max;
// Value averaged over all triangles
public double alpha_ave;
// Value averaged over all triangles and weighted by area
public double alpha_area;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
alpha_min = double.MaxValue;
alpha_max = -double.MaxValue;
alpha_ave = 0;
alpha_area = 0;
}
double acos(double c)
{
if (c <= -1.0)
{
return Math.PI;
}
else if (1.0 <= c)
{
return 0.0;
}
else
{
return Math.Acos(c);
}
}
/// <summary>
/// Compute q value of given triangle.
/// </summary>
/// <param name="ab">Side length ab.</param>
/// <param name="bc">Side length bc.</param>
/// <param name="ca">Side length ca.</param>
/// <param name="area">Triangle area.</param>
/// <returns></returns>
public double Measure(double ab, double bc, double ca, double area)
{
double alpha = double.MaxValue;
double ab2 = ab * ab;
double bc2 = bc * bc;
double ca2 = ca * ca;
double a_angle;
double b_angle;
double c_angle;
// Take care of a ridiculous special case.
if (ab == 0.0 && bc == 0.0 && ca == 0.0)
{
a_angle = 2.0 * Math.PI / 3.0;
b_angle = 2.0 * Math.PI / 3.0;
c_angle = 2.0 * Math.PI / 3.0;
}
else
{
if (ca == 0.0 || ab == 0.0)
{
a_angle = Math.PI;
}
else
{
a_angle = acos((ca2 + ab2 - bc2) / (2.0 * ca * ab));
}
if (ab == 0.0 || bc == 0.0)
{
b_angle = Math.PI;
}
else
{
b_angle = acos((ab2 + bc2 - ca2) / (2.0 * ab * bc));
}
if (bc == 0.0 || ca == 0.0)
{
c_angle = Math.PI;
}
else
{
c_angle = acos((bc2 + ca2 - ab2) / (2.0 * bc * ca));
}
}
alpha = Math.Min(alpha, a_angle);
alpha = Math.Min(alpha, b_angle);
alpha = Math.Min(alpha, c_angle);
// Normalize angle from [0,pi/3] radians into qualities in [0,1].
alpha = alpha * 3.0 / Math.PI;
alpha_ave += alpha;
alpha_area += area * alpha;
alpha_min = Math.Min(alpha, alpha_min);
alpha_max = Math.Max(alpha, alpha_max);
return alpha;
}
/// <summary>
/// Normalize values.
/// </summary>
public void Normalize(int n, double area_total)
{
if (n > 0)
{
alpha_ave /= n;
}
else
{
alpha_ave = 0.0;
}
if (0.0 < area_total)
{
alpha_area /= area_total;
}
else
{
alpha_area = 0.0;
}
}
}
/// <summary>
/// The Q measure determines the triangulated pointset quality.
/// </summary>
/// <remarks>
/// The Q measure evaluates the uniformity of the shapes of the triangles
/// defined by a triangulated pointset. It uses the aspect ratio
///
/// 2 * (incircle radius) / (circumcircle radius)
///
/// In an ideally regular mesh, all triangles would have the same
/// equilateral shape, for which Q = 1. A good mesh would have
/// 0.5 &lt; Q.
/// </remarks>
class Q_Measure
{
// Minimum value over all triangles
public double q_min;
// Maximum value over all triangles
public double q_max;
// Average value
public double q_ave;
// Average value weighted by the area of each triangle
public double q_area;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
q_min = double.MaxValue;
q_max = -double.MaxValue;
q_ave = 0;
q_area = 0;
}
/// <summary>
/// Compute q value of given triangle.
/// </summary>
/// <param name="ab">Side length ab.</param>
/// <param name="bc">Side length bc.</param>
/// <param name="ca">Side length ca.</param>
/// <param name="area">Triangle area.</param>
/// <returns></returns>
public double Measure(double ab, double bc, double ca, double area)
{
double q = (bc + ca - ab) * (ca + ab - bc) * (ab + bc - ca) / (ab * bc * ca);
q_min = Math.Min(q_min, q);
q_max = Math.Max(q_max, q);
q_ave += q;
q_area += q * area;
return q;
}
/// <summary>
/// Normalize values.
/// </summary>
public void Normalize(int n, double area_total)
{
if (n > 0)
{
q_ave /= n;
}
else
{
q_ave = 0.0;
}
if (area_total > 0.0)
{
q_area /= area_total;
}
else
{
q_area = 0.0;
}
}
}
}
}

View File

@@ -0,0 +1,539 @@
// -----------------------------------------------------------------------
// <copyright file="Statistic.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Gather mesh statistics.
/// </summary>
internal class Statistic
{
#region Static members
/// <summary>
/// Number of incircle tests performed.
/// </summary>
internal static long InCircleCount = 0;
internal static long InCircleAdaptCount = 0;
/// <summary>
/// Number of counterclockwise tests performed.
/// </summary>
internal static long CounterClockwiseCount = 0;
internal static long CounterClockwiseAdaptCount = 0;
/// <summary>
/// Number of 3D orientation tests performed.
/// </summary>
internal static long Orient3dCount = 0;
/// <summary>
/// Number of right-of-hyperbola tests performed.
/// </summary>
internal static long HyperbolaCount = 0;
/// <summary>
/// // Number of circumcenter calculations performed.
/// </summary>
internal static long CircumcenterCount = 0;
/// <summary>
/// Number of circle top calculations performed.
/// </summary>
internal static long CircleTopCount = 0;
/// <summary>
/// Number of vertex relocations.
/// </summary>
internal static long RelocationCount = 0;
#endregion
#region Properties
double minEdge = 0;
/// <summary>
/// Gets the shortest edge.
/// </summary>
public double ShortestEdge { get { return minEdge; } }
double maxEdge = 0;
/// <summary>
/// Gets the longest edge.
/// </summary>
public double LongestEdge { get { return maxEdge; } }
//
double minAspect = 0;
/// <summary>
/// Gets the shortest altitude.
/// </summary>
public double ShortestAltitude { get { return minAspect; } }
double maxAspect = 0;
/// <summary>
/// Gets the largest aspect ratio.
/// </summary>
public double LargestAspectRatio { get { return maxAspect; } }
double minArea = 0;
/// <summary>
/// Gets the smallest area.
/// </summary>
public double SmallestArea { get { return minArea; } }
double maxArea = 0;
/// <summary>
/// Gets the largest area.
/// </summary>
public double LargestArea { get { return maxArea; } }
double minAngle = 0;
/// <summary>
/// Gets the smallest angle.
/// </summary>
public double SmallestAngle { get { return minAngle; } }
double maxAngle = 0;
/// <summary>
/// Gets the largest angle.
/// </summary>
public double LargestAngle { get { return maxAngle; } }
int[] angleTable;
/// <summary>
/// Gets the angle histogram.
/// </summary>
public int[] AngleHistogram { get { return angleTable; } }
int[] minAngles;
/// <summary>
/// Gets the min angles histogram.
/// </summary>
public int[] MinAngleHistogram { get { return minAngles; } }
int[] maxAngles;
/// <summary>
/// Gets the max angles histogram.
/// </summary>
public int[] MaxAngleHistogram { get { return maxAngles; } }
double meshArea = 0;
/// <summary>
/// Gets the total mesh area.
/// </summary>
public double MeshArea { get { return meshArea; } }
#endregion
#region Private methods
private void GetAspectHistogram(Mesh mesh)
{
int[] aspecttable;
double[] ratiotable;
aspecttable = new int[16];
ratiotable = new double[]
{
1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0,
100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0
};
Otri tri = default(Otri);
Vertex[] p = new Vertex[3];
double[] dx = new double[3], dy = new double[3];
double[] edgelength = new double[3];
double triarea;
double trilongest2;
double triminaltitude2;
double triaspect2;
int aspectindex;
int i, j, k;
tri.orient = 0;
foreach (var t in mesh.triangles)
{
tri.tri = t;
p[0] = tri.Org();
p[1] = tri.Dest();
p[2] = tri.Apex();
trilongest2 = 0.0;
for (i = 0; i < 3; i++)
{
j = plus1Mod3[i];
k = minus1Mod3[i];
dx[i] = p[j].x - p[k].x;
dy[i] = p[j].y - p[k].y;
edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
if (edgelength[i] > trilongest2)
{
trilongest2 = edgelength[i];
}
}
//triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
triarea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) -
(p[1].x - p[0].x) * (p[2].y - p[0].y)) / 2.0;
triminaltitude2 = triarea * triarea / trilongest2;
triaspect2 = trilongest2 / triminaltitude2;
aspectindex = 0;
while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15))
{
aspectindex++;
}
aspecttable[aspectindex]++;
}
}
#endregion
static readonly int[] plus1Mod3 = { 1, 2, 0 };
static readonly int[] minus1Mod3 = { 2, 0, 1 };
/// <summary>
/// Update statistics about the quality of the mesh.
/// </summary>
/// <param name="mesh"></param>
public void Update(Mesh mesh, int sampleDegrees)
{
Point[] p = new Point[3];
int k1, k2;
int degreeStep;
//sampleDegrees = 36; // sample every 5 degrees
//sampleDegrees = 45; // sample every 4 degrees
sampleDegrees = 60; // sample every 3 degrees
double[] cosSquareTable = new double[sampleDegrees / 2 - 1];
double[] dx = new double[3];
double[] dy = new double[3];
double[] edgeLength = new double[3];
double dotProduct;
double cosSquare;
double triArea;
double triLongest2;
double triMinAltitude2;
double triAspect2;
double radconst = Math.PI / sampleDegrees;
double degconst = 180.0 / Math.PI;
// New angle table
angleTable = new int[sampleDegrees];
minAngles = new int[sampleDegrees];
maxAngles = new int[sampleDegrees];
for (int i = 0; i < sampleDegrees / 2 - 1; i++)
{
cosSquareTable[i] = Math.Cos(radconst * (i + 1));
cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i];
}
for (int i = 0; i < sampleDegrees; i++)
{
angleTable[i] = 0;
}
minAspect = mesh.bounds.Width + mesh.bounds.Height;
minAspect = minAspect * minAspect;
maxAspect = 0.0;
minEdge = minAspect;
maxEdge = 0.0;
minArea = minAspect;
maxArea = 0.0;
minAngle = 0.0;
maxAngle = 2.0;
meshArea = 0.0;
bool acuteBiggest = true;
bool acuteBiggestTri = true;
double triMinAngle, triMaxAngle = 1;
foreach (var tri in mesh.triangles)
{
triMinAngle = 0; // Min angle: 0 < a < 60 degress
triMaxAngle = 1; // Max angle: 60 < a < 180 degress
p[0] = tri.vertices[0];
p[1] = tri.vertices[1];
p[2] = tri.vertices[2];
triLongest2 = 0.0;
for (int i = 0; i < 3; i++)
{
k1 = plus1Mod3[i];
k2 = minus1Mod3[i];
dx[i] = p[k1].x - p[k2].x;
dy[i] = p[k1].y - p[k2].y;
edgeLength[i] = dx[i] * dx[i] + dy[i] * dy[i];
if (edgeLength[i] > triLongest2)
{
triLongest2 = edgeLength[i];
}
if (edgeLength[i] > maxEdge)
{
maxEdge = edgeLength[i];
}
if (edgeLength[i] < minEdge)
{
minEdge = edgeLength[i];
}
}
//triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
triArea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) -
(p[1].x - p[0].x) * (p[2].y - p[0].y));
meshArea += triArea;
if (triArea < minArea)
{
minArea = triArea;
}
if (triArea > maxArea)
{
maxArea = triArea;
}
triMinAltitude2 = triArea * triArea / triLongest2;
if (triMinAltitude2 < minAspect)
{
minAspect = triMinAltitude2;
}
triAspect2 = triLongest2 / triMinAltitude2;
if (triAspect2 > maxAspect)
{
maxAspect = triAspect2;
}
for (int i = 0; i < 3; i++)
{
k1 = plus1Mod3[i];
k2 = minus1Mod3[i];
dotProduct = dx[k1] * dx[k2] + dy[k1] * dy[k2];
cosSquare = dotProduct * dotProduct / (edgeLength[k1] * edgeLength[k2]);
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (cosSquare > cosSquareTable[j])
{
degreeStep = j;
}
}
if (dotProduct <= 0.0)
{
angleTable[degreeStep]++;
if (cosSquare > minAngle)
{
minAngle = cosSquare;
}
if (acuteBiggest && (cosSquare < maxAngle))
{
maxAngle = cosSquare;
}
// Update min/max angle per triangle
if (cosSquare > triMinAngle)
{
triMinAngle = cosSquare;
}
if (acuteBiggestTri && (cosSquare < triMaxAngle))
{
triMaxAngle = cosSquare;
}
}
else
{
angleTable[sampleDegrees - degreeStep - 1]++;
if (acuteBiggest || (cosSquare > maxAngle))
{
maxAngle = cosSquare;
acuteBiggest = false;
}
// Update max angle for (possibly non-acute) triangle
if (acuteBiggestTri || (cosSquare > triMaxAngle))
{
triMaxAngle = cosSquare;
acuteBiggestTri = false;
}
}
}
// Update min angle histogram
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (triMinAngle > cosSquareTable[j])
{
degreeStep = j;
}
}
minAngles[degreeStep]++;
// Update max angle histogram
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (triMaxAngle > cosSquareTable[j])
{
degreeStep = j;
}
}
if (acuteBiggestTri)
{
maxAngles[degreeStep]++;
}
else
{
maxAngles[sampleDegrees - degreeStep - 1]++;
}
acuteBiggestTri = true;
}
minEdge = Math.Sqrt(minEdge);
maxEdge = Math.Sqrt(maxEdge);
minAspect = Math.Sqrt(minAspect);
maxAspect = Math.Sqrt(maxAspect);
minArea *= 0.5;
maxArea *= 0.5;
if (minAngle >= 1.0)
{
minAngle = 0.0;
}
else
{
minAngle = degconst * Math.Acos(Math.Sqrt(minAngle));
}
if (maxAngle >= 1.0)
{
maxAngle = 180.0;
}
else
{
if (acuteBiggest)
{
maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle));
}
else
{
maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle));
}
}
}
/// <summary>
/// Compute angle information for given triangle.
/// </summary>
/// <param name="triangle">The triangle to check.</param>
/// <param name="data">Array of doubles (length 6).</param>
/// <remarks>
/// On return, the squared cosines of the minimum and maximum angle will
/// be stored at position data[0] and data[1] respectively.
/// If the triangle was obtuse, data[2] will be set to -1 and maximum angle
/// is computed as (pi - acos(sqrt(data[1]))).
/// </remarks>
internal static void ComputeAngles(ITriangle triangle, double[] data)
{
double min = 0.0;
double max = 1.0;
var va = triangle.GetVertex(0);
var vb = triangle.GetVertex(1);
var vc = triangle.GetVertex(2);
double dxa = vb.x - vc.x;
double dya = vb.y - vc.y;
double lena = dxa * dxa + dya * dya;
double dxb = vc.x - va.x;
double dyb = vc.y - va.y;
double lenb = dxb * dxb + dyb * dyb;
double dxc = va.x - vb.x;
double dyc = va.y - vb.y;
double lenc = dxc * dxc + dyc * dyc;
// Dot products.
double dota = data[0] = dxb * dxc + dyb * dyc;
double dotb = data[1] = dxc * dxa + dyc * dya;
double dotc = data[2] = dxa * dxb + dya * dyb;
// Squared cosines.
data[3] = (dota * dota) / (lenb * lenc);
data[4] = (dotb * dotb) / (lenc * lena);
data[5] = (dotc * dotc) / (lena * lenb);
// The sign of the dot product will tell us, if the angle is
// acute (value < 0) or obtuse (value > 0).
bool acute = true;
double cos, dot;
for (int i = 0; i < 3; i++)
{
dot = data[i];
cos = data[3 + i];
if (dot <= 0.0)
{
if (cos > min)
{
min = cos;
}
if (acute && (cos < max))
{
max = cos;
}
}
else
{
// Update max angle for (possibly non-acute) triangle
if (acute || (cos > max))
{
max = cos;
acute = false;
}
}
}
data[0] = min;
data[1] = max;
data[2] = acute ? 1.0 : -1.0;
}
}
}

View File

@@ -0,0 +1,427 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleQuadTree.cs" company="">
// Original code by Frank Dockhorn, [not available anymore: http://sourceforge.net/projects/quadtreesim/]
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System.Collections.Generic;
using System.Linq;
using Animation.TriangleNet.Geometry;
/// <summary>
/// A Quadtree implementation optimized for triangles.
/// </summary>
internal class TriangleQuadTree
{
QuadNode root;
internal ITriangle[] triangles;
internal int sizeBound;
internal int maxDepth;
/// <summary>
/// Initializes a new instance of the <see cref="TriangleQuadTree" /> class.
/// </summary>
/// <param name="mesh">Mesh containing triangles.</param>
/// <param name="maxDepth">The maximum depth of the tree.</param>
/// <param name="sizeBound">The maximum number of triangles contained in a leaf.</param>
/// <remarks>
/// The quadtree does not track changes of the mesh. If a mesh is refined or
/// changed in any other way, a new quadtree has to be built to make the point
/// location work.
///
/// A node of the tree will be split, if its level if less than the max depth parameter
/// AND the number of triangles in the node is greater than the size bound.
/// </remarks>
public TriangleQuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10)
{
this.maxDepth = maxDepth;
this.sizeBound = sizeBound;
triangles = mesh.Triangles.ToArray();
int currentDepth = 0;
root = new QuadNode(mesh.Bounds, this, true);
root.CreateSubRegion(++currentDepth);
}
public ITriangle Query(double x, double y)
{
var point = new Point(x, y);
var indices = root.FindTriangles(point);
foreach (var i in indices)
{
var tri = this.triangles[i];
if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2)))
{
return tri;
}
}
return null;
}
/// <summary>
/// Test, if a given point lies inside a triangle.
/// </summary>
/// <param name="p">Point to locate.</param>
/// <param name="t0">Corner point of triangle.</param>
/// <param name="t1">Corner point of triangle.</param>
/// <param name="t2">Corner point of triangle.</param>
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2)
{
// TODO: no need to create new Point instances here
Point d0 = new Point(t1.x - t0.x, t1.y - t0.y);
Point d1 = new Point(t2.x - t0.x, t2.y - t0.y);
Point d2 = new Point(p.x - t0.x, p.y - t0.y);
// crossproduct of (0, 0, 1) and d0
Point c0 = new Point(-d0.y, d0.x);
// crossproduct of (0, 0, 1) and d1
Point c1 = new Point(-d1.y, d1.x);
// Linear combination d2 = s * d0 + v * d1.
//
// Multiply both sides of the equation with c0 and c1
// and solve for s and v respectively
//
// s = d2 * c1 / d0 * c1
// v = d2 * c0 / d1 * c0
double s = DotProduct(d2, c1) / DotProduct(d0, c1);
double v = DotProduct(d2, c0) / DotProduct(d1, c0);
if (s >= 0 && v >= 0 && ((s + v) <= 1))
{
// Point is inside or on the edge of this triangle.
return true;
}
return false;
}
internal static double DotProduct(Point p, Point q)
{
return p.x * q.x + p.y * q.y;
}
/// <summary>
/// A node of the quadtree.
/// </summary>
class QuadNode
{
const int SW = 0;
const int SE = 1;
const int NW = 2;
const int NE = 3;
const double EPS = 1e-6;
static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 };
Rectangle bounds;
Point pivot;
TriangleQuadTree tree;
QuadNode[] regions;
List<int> triangles;
byte bitRegions;
public QuadNode(Rectangle box, TriangleQuadTree tree)
: this(box, tree, false)
{
}
public QuadNode(Rectangle box, TriangleQuadTree tree, bool init)
{
this.tree = tree;
this.bounds = new Rectangle(box.Left, box.Bottom, box.Width, box.Height);
this.pivot = new Point((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2);
this.bitRegions = 0;
this.regions = new QuadNode[4];
this.triangles = new List<int>();
if (init)
{
int count = tree.triangles.Length;
// Allocate memory upfront
triangles.Capacity = count;
for (int i = 0; i < count; i++)
{
triangles.Add(i);
}
}
}
public List<int> FindTriangles(Point searchPoint)
{
int region = FindRegion(searchPoint);
if (regions[region] == null)
{
return triangles;
}
return regions[region].FindTriangles(searchPoint);
}
public void CreateSubRegion(int currentDepth)
{
// The four sub regions of the quad tree
// +--------------+
// | nw 2 | ne 3 |
// |------+pivot--|
// | sw 0 | se 1 |
// +--------------+
Rectangle box;
var width = bounds.Right - pivot.x;
var height = bounds.Top - pivot.y;
// 1. region south west
box = new Rectangle(bounds.Left, bounds.Bottom, width, height);
regions[0] = new QuadNode(box, tree);
// 2. region south east
box = new Rectangle(pivot.x, bounds.Bottom, width, height);
regions[1] = new QuadNode(box, tree);
// 3. region north west
box = new Rectangle(bounds.Left, pivot.y, width, height);
regions[2] = new QuadNode(box, tree);
// 4. region north east
box = new Rectangle(pivot.x, pivot.y, width, height);
regions[3] = new QuadNode(box, tree);
Point[] triangle = new Point[3];
// Find region for every triangle vertex
foreach (var index in triangles)
{
ITriangle tri = tree.triangles[index];
triangle[0] = tri.GetVertex(0);
triangle[1] = tri.GetVertex(1);
triangle[2] = tri.GetVertex(2);
AddTriangleToRegion(triangle, index);
}
for (int i = 0; i < 4; i++)
{
if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth)
{
regions[i].CreateSubRegion(currentDepth + 1);
}
}
}
void AddTriangleToRegion(Point[] triangle, int index)
{
bitRegions = 0;
if (TriangleQuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2]))
{
AddToRegion(index, SW);
AddToRegion(index, SE);
AddToRegion(index, NW);
AddToRegion(index, NE);
return;
}
FindTriangleIntersections(triangle, index);
if (bitRegions == 0)
{
// we didn't find any intersection so we add this triangle to a point's region
int region = FindRegion(triangle[0]);
regions[region].triangles.Add(index);
}
}
void FindTriangleIntersections(Point[] triangle, int index)
{
// PLEASE NOTE:
// Handling of component comparison is tightly associated with the implementation
// of the findRegion() function. That means when the point to be compared equals
// the pivot point the triangle must be put at least into region 2.
//
// Linear equations are in parametric form.
// pivot.x = triangle[0].x + t * (triangle[1].x - triangle[0].x)
// pivot.y = triangle[0].y + t * (triangle[1].y - triangle[0].y)
int k = 2;
double dx, dy;
// Iterate through all triangle laterals and find bounding box intersections
for (int i = 0; i < 3; k = i++)
{
dx = triangle[i].x - triangle[k].x;
dy = triangle[i].y - triangle[k].y;
if (dx != 0.0)
{
FindIntersectionsWithX(dx, dy, triangle, index, k);
}
if (dy != 0.0)
{
FindIntersectionsWithY(dx, dy, triangle, index, k);
}
}
}
void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k)
{
double t;
// find intersection with plane x = m_pivot.dX
t = (pivot.x - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NW);
AddToRegion(index, NE);
}
}
// find intersection with plane x = m_boundingBox[0].dX
t = (bounds.Left - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
}
else if (yComponent <= bounds.Top) // TODO: check && yComponent >= pivot.Y
{
AddToRegion(index, NW);
}
}
// find intersection with plane x = m_boundingBox[1].dX
t = (bounds.Right - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NE);
}
}
}
void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k)
{
double t, xComponent;
// find intersection with plane y = m_pivot.dY
t = (pivot.y - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
AddToRegion(index, NW);
}
}
// find intersection with plane y = m_boundingBox[0].dY
t = (bounds.Bottom - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
}
}
// find intersection with plane y = m_boundingBox[1].dY
t = (bounds.Top - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, NW);
}
}
}
int FindRegion(Point point)
{
int b = 2;
if (point.y < pivot.y)
{
b = 0;
}
if (point.x > pivot.x)
{
b++;
}
return b;
}
void AddToRegion(int index, int region)
{
//if (!(m_bitRegions & BITVECTOR[region]))
if ((bitRegions & BITVECTOR[region]) == 0)
{
regions[region].triangles.Add(index);
bitRegions |= BITVECTOR[region];
}
}
}
}
}

View File

@@ -0,0 +1,372 @@
// -----------------------------------------------------------------------
// <copyright file="VertexSorter.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Sort an array of points using quicksort.
/// </summary>
internal class VertexSorter
{
private const int RANDOM_SEED = 57113;
Random rand;
Vertex[] points;
VertexSorter(Vertex[] points, int seed)
{
this.points = points;
this.rand = new Random(seed);
}
/// <summary>
/// Sorts the given vertex array by x-coordinate.
/// </summary>
/// <param name="array">The vertex array.</param>
/// <param name="seed">Random seed used for pivoting.</param>
internal static void Sort(Vertex[] array, int seed = RANDOM_SEED)
{
var qs = new VertexSorter(array, seed);
qs.QuickSort(0, array.Length - 1);
}
/// <summary>
/// Impose alternating cuts on given vertex array.
/// </summary>
/// <param name="array">The vertex array.</param>
/// <param name="length">The number of vertices to sort.</param>
/// <param name="seed">Random seed used for pivoting.</param>
internal static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED)
{
var qs = new VertexSorter(array, seed);
int divider = length >> 1;
// Re-sort the array of vertices to accommodate alternating cuts.
if (length - divider >= 2)
{
if (divider >= 2)
{
qs.AlternateAxes(0, divider - 1, 1);
}
qs.AlternateAxes(divider, length - 1, 1);
}
}
#region Quicksort
/// <summary>
/// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <remarks>
/// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
/// the usual quicksort mistakes.
/// </remarks>
private void QuickSort(int left, int right)
{
int oleft = left;
int oright = right;
int arraysize = right - left + 1;
int pivot;
double pivotx, pivoty;
Vertex temp;
var array = this.points;
if (arraysize < 32)
{
// Insertion sort
for (int i = left + 1; i <= right; i++)
{
var a = array[i];
int j = i - 1;
while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y)))
{
array[j + 1] = array[j];
j--;
}
array[j + 1] = a;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivotx = array[pivot].x;
pivoty = array[pivot].y;
// Split the array.
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].x < pivotx) ||
((array[left].x == pivotx) && (array[left].y < pivoty))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].x > pivotx) ||
((array[right].x == pivotx) && (array[right].y > pivoty))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
if (left > oleft)
{
// Recursively sort the left subset.
QuickSort(oleft, left);
}
if (oright > right + 1)
{
// Recursively sort the right subset.
QuickSort(right + 1, oright);
}
}
#endregion
#region Alternate axes
/// <summary>
/// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
/// alternating cuts.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <remarks>
/// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1.
/// For the base case, subsets containing only two or three vertices are
/// always sorted by x-coordinate.
/// </remarks>
private void AlternateAxes(int left, int right, int axis)
{
int size = right - left + 1;
int divider = size >> 1;
if (size <= 3)
{
// Recursive base case: subsets of two or three vertices will be
// handled specially, and should always be sorted by x-coordinate.
axis = 0;
}
// Partition with a horizontal or vertical cut.
if (axis == 0)
{
VertexMedianX(left, right, left + divider);
}
else
{
VertexMedianY(left, right, left + divider);
}
// Recursively partition the subsets with a cross cut.
if (size - divider >= 2)
{
if (divider >= 2)
{
AlternateAxes(left, left + divider - 1, 1 - axis);
}
AlternateAxes(left + divider, right, 1 - axis);
}
}
/// <summary>
/// An order statistic algorithm, almost. Shuffles an array of vertices so that the
/// first 'median' vertices occur lexicographically before the remaining vertices.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="median"></param>
/// <remarks>
/// Uses the x-coordinate as the primary key. Very similar to the QuickSort()
/// procedure, but runs in randomized linear time.
/// </remarks>
private void VertexMedianX(int left, int right, int median)
{
int arraysize = right - left + 1;
int oleft = left, oright = right;
int pivot;
double pivot1, pivot2;
Vertex temp;
var array = this.points;
if (arraysize == 2)
{
// Recursive base case.
if ((array[left].x > array[right].x) ||
((array[left].x == array[right].x) &&
(array[left].y > array[right].y)))
{
temp = array[right];
array[right] = array[left];
array[left] = temp;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivot1 = array[pivot].x;
pivot2 = array[pivot].y;
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].x < pivot1) ||
((array[left].x == pivot1) && (array[left].y < pivot2))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].x > pivot1) ||
((array[right].x == pivot1) && (array[right].y > pivot2))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
// Unlike in vertexsort(), at most one of the following conditionals is true.
if (left > median)
{
// Recursively shuffle the left subset.
VertexMedianX(oleft, left - 1, median);
}
if (right < median - 1)
{
// Recursively shuffle the right subset.
VertexMedianX(right + 1, oright, median);
}
}
/// <summary>
/// An order statistic algorithm, almost. Shuffles an array of vertices so that
/// the first 'median' vertices occur lexicographically before the remaining vertices.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="median"></param>
/// <remarks>
/// Uses the y-coordinate as the primary key. Very similar to the QuickSort()
/// procedure, but runs in randomized linear time.
/// </remarks>
private void VertexMedianY(int left, int right, int median)
{
int arraysize = right - left + 1;
int oleft = left, oright = right;
int pivot;
double pivot1, pivot2;
Vertex temp;
var array = this.points;
if (arraysize == 2)
{
// Recursive base case.
if ((array[left].y > array[right].y) ||
((array[left].y == array[right].y) &&
(array[left].x > array[right].x)))
{
temp = array[right];
array[right] = array[left];
array[left] = temp;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivot1 = array[pivot].y;
pivot2 = array[pivot].x;
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].y < pivot1) ||
((array[left].y == pivot1) && (array[left].x < pivot2))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].y > pivot1) ||
((array[right].y == pivot1) && (array[right].x > pivot2))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
// Unlike in QuickSort(), at most one of the following conditionals is true.
if (left > median)
{
// Recursively shuffle the left subset.
VertexMedianY(oleft, left - 1, median);
}
if (right < median - 1)
{
// Recursively shuffle the right subset.
VertexMedianY(right + 1, oright, median);
}
}
#endregion
}
}

View File

@@ -0,0 +1,270 @@
// -----------------------------------------------------------------------
// <copyright file="DcelMesh.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology.DCEL
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
internal class DcelMesh
{
protected List<Vertex> vertices;
protected List<HalfEdge> edges;
protected List<Face> faces;
/// <summary>
/// Initializes a new instance of the <see cref="DcelMesh" /> class.
/// </summary>
public DcelMesh()
: this(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="" /> class.
/// </summary>
/// <param name="initialize">If false, lists will not be initialized.</param>
protected DcelMesh(bool initialize)
{
if (initialize)
{
vertices = new List<Vertex>();
edges = new List<HalfEdge>();
faces = new List<Face>();
}
}
/// <summary>
/// Gets the vertices of the Voronoi diagram.
/// </summary>
public List<Vertex> Vertices
{
get { return vertices; }
}
/// <summary>
/// Gets the list of half-edges specify the Voronoi diagram topology.
/// </summary>
public List<HalfEdge> HalfEdges
{
get { return edges; }
}
/// <summary>
/// Gets the faces of the Voronoi diagram.
/// </summary>
public List<Face> Faces
{
get { return faces; }
}
/// <summary>
/// Gets the collection of edges of the Voronoi diagram.
/// </summary>
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Check if the DCEL is consistend.
/// </summary>
/// <param name="closed">If true, faces are assumed to be closed (i.e. all edges must have
/// a valid next pointer).</param>
/// <param name="depth">Maximum edge count of faces (default = 0 means skip check).</param>
/// <returns></returns>
public virtual bool IsConsistent(bool closed = true, int depth = 0)
{
// Check vertices for null pointers.
foreach (var vertex in vertices)
{
if (vertex.id < 0)
{
continue;
}
if (vertex.leaving == null)
{
return false;
}
if (vertex.Leaving.Origin.id != vertex.id)
{
return false;
}
}
// Check faces for null pointers.
foreach (var face in faces)
{
if (face.ID < 0)
{
continue;
}
if (face.edge == null)
{
return false;
}
if (face.id != face.edge.face.id)
{
return false;
}
}
// Check half-edges for null pointers.
foreach (var edge in edges)
{
if (edge.id < 0)
{
continue;
}
if (edge.twin == null)
{
return false;
}
if (edge.origin == null)
{
return false;
}
if (edge.face == null)
{
return false;
}
if (closed && edge.next == null)
{
return false;
}
}
// Check half-edges (topology).
foreach (var edge in edges)
{
if (edge.id < 0)
{
continue;
}
var twin = edge.twin;
var next = edge.next;
if (edge.id != twin.twin.id)
{
return false;
}
if (closed)
{
if (next.origin.id != twin.origin.id)
{
return false;
}
if (next.twin.next.origin.id != edge.twin.origin.id)
{
return false;
}
}
}
if (closed && depth > 0)
{
// Check if faces are closed.
foreach (var face in faces)
{
if (face.id < 0)
{
continue;
}
var edge = face.edge;
var next = edge.next;
int id = edge.id;
int k = 0;
while (next.id != id && k < depth)
{
next = next.next;
k++;
}
if (next.id != id)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Search for half-edge without twin and add a twin. Connect twins to form connected
/// boundary contours.
/// </summary>
/// <remarks>
/// This method assumes that all faces are closed (i.e. no edge.next pointers are null).
/// </remarks>
public void ResolveBoundaryEdges()
{
// Maps vertices to leaving boundary edge.
var map = new Dictionary<int, HalfEdge>();
// TODO: parallel?
foreach (var edge in this.edges)
{
if (edge.twin == null)
{
var twin = edge.twin = new HalfEdge(edge.next.origin, Face.Empty);
twin.twin = edge;
map.Add(twin.origin.id, twin);
}
}
int j = edges.Count;
foreach (var edge in map.Values)
{
edge.id = j++;
edge.next = map[edge.twin.origin.id];
this.edges.Add(edge);
}
}
/// <summary>
/// Enumerates all edges of the DCEL.
/// </summary>
/// <remarks>
/// This method assumes that each half-edge has a twin (i.e. NOT null).
/// </remarks>
protected virtual IEnumerable<IEdge> EnumerateEdges()
{
var edges = new List<IEdge>(this.edges.Count / 2);
foreach (var edge in this.edges)
{
var twin = edge.twin;
// Report edge only once.
if (edge.id < twin.id)
{
edges.Add(new Edge(edge.origin.id, twin.origin.id));
}
}
return edges;
}
}
}

View File

@@ -0,0 +1,114 @@
// -----------------------------------------------------------------------
// <copyright file="Face.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology.DCEL
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
/// <summary>
/// A face of DCEL mesh.
/// </summary>
internal class Face
{
#region Static initialization of "Outer Space" face
internal static readonly Face Empty;
static Face()
{
Empty = new Face(null);
Empty.id = -1;
}
#endregion
internal int id;
internal int mark = 0;
internal Point generator;
internal HalfEdge edge;
internal bool bounded;
/// <summary>
/// Gets or sets the face id.
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
/// <summary>
/// Gets or sets a half-edge connected to the face.
/// </summary>
public HalfEdge Edge
{
get { return edge; }
set { edge = value; }
}
/// <summary>
/// Gets or sets a value, indicating if the face is bounded (for Voronoi diagram).
/// </summary>
public bool Bounded
{
get { return bounded; }
set { bounded = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Face" /> class.
/// </summary>
/// <param name="generator">The generator of this face (for Voronoi diagram)</param>
public Face(Point generator)
: this(generator, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Face" /> class.
/// </summary>
/// <param name="generator">The generator of this face (for Voronoi diagram)</param>
/// <param name="edge">The half-edge connected to this face.</param>
public Face(Point generator, HalfEdge edge)
{
this.generator = generator;
this.edge = edge;
this.bounded = true;
if (generator != null)
{
this.id = generator.ID;
}
}
/// <summary>
/// Enumerates all half-edges of the face boundary.
/// </summary>
/// <returns></returns>
public IEnumerable<HalfEdge> EnumerateEdges()
{
var edge = this.Edge;
int first = edge.ID;
do
{
yield return edge;
edge = edge.Next;
}
while (edge.ID != first);
}
public override string ToString()
{
return string.Format("F-ID {0}", id);
}
}
}

View File

@@ -0,0 +1,102 @@
// -----------------------------------------------------------------------
// <copyright file="HalfEdge.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology.DCEL
{
internal class HalfEdge
{
internal int id;
internal int mark;
internal Vertex origin;
internal Face face;
internal HalfEdge twin;
internal HalfEdge next;
/// <summary>
/// Gets or sets the half-edge id.
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
public int Boundary
{
get { return mark; }
set { mark = value; }
}
/// <summary>
/// Gets or sets the origin of the half-edge.
/// </summary>
public Vertex Origin
{
get { return origin; }
set { origin = value; }
}
/// <summary>
/// Gets or sets the face connected to the half-edge.
/// </summary>
public Face Face
{
get { return face; }
set { face = value; }
}
/// <summary>
/// Gets or sets the twin of the half-edge.
/// </summary>
public HalfEdge Twin
{
get { return twin; }
set { twin = value; }
}
/// <summary>
/// Gets or sets the next pointer of the half-edge.
/// </summary>
public HalfEdge Next
{
get { return next; }
set { next = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfEdge" /> class.
/// </summary>
/// <param name="origin">The origin of this half-edge.</param>
public HalfEdge(Vertex origin)
{
this.origin = origin;
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfEdge" /> class.
/// </summary>
/// <param name="origin">The origin of this half-edge.</param>
/// <param name="face">The face connected to this half-edge.</param>
public HalfEdge(Vertex origin, Face face)
{
this.origin = origin;
this.face = face;
// IMPORTANT: do not remove the (face.edge == null) check!
if (face != null && face.edge == null)
{
face.edge = this;
}
}
public override string ToString()
{
return string.Format("HE-ID {0} (Origin = VID-{1})", id, origin.id);
}
}
}

View File

@@ -0,0 +1,70 @@
// -----------------------------------------------------------------------
// <copyright file="Vertex.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology.DCEL
{
using System.Collections.Generic;
internal class Vertex : Animation.TriangleNet.Geometry.Point
{
internal HalfEdge leaving;
/// <summary>
/// Gets or sets a half-edge leaving the vertex.
/// </summary>
public HalfEdge Leaving
{
get { return leaving; }
set { leaving = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
public Vertex(double x, double y)
: base(x, y)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="leaving">A half-edge leaving this vertex.</param>
public Vertex(double x, double y, HalfEdge leaving)
: base(x, y)
{
this.leaving = leaving;
}
/// <summary>
/// Enumerates all half-edges leaving this vertex.
/// </summary>
/// <returns></returns>
public IEnumerable<HalfEdge> EnumerateEdges()
{
var edge = this.Leaving;
int first = edge.ID;
do
{
yield return edge;
edge = edge.Twin.Next;
}
while (edge.ID != first);
}
public override string ToString()
{
return string.Format("V-ID {0}", base.id);
}
}
}

View File

@@ -0,0 +1,256 @@
// -----------------------------------------------------------------------
// <copyright file="Osub.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// An oriented subsegment.
/// </summary>
/// <remarks>
/// Includes a pointer to a subsegment and an orientation. The orientation denotes a
/// side of the edge. Hence, there are two possible orientations. By convention, the
/// edge is always directed so that the "side" denoted is the right side of the edge.
/// </remarks>
internal struct Osub
{
internal SubSegment seg;
internal int orient; // Ranges from 0 to 1.
public SubSegment Segment
{
get { return seg; }
}
public override string ToString()
{
if (seg == null)
{
return "O-TID [null]";
}
return String.Format("O-SID {0}", seg.hash);
}
#region Osub primitives
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
public void Sym(ref Osub os)
{
os.seg = seg;
os.orient = 1 - orient;
}
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
public void Sym()
{
orient = 1 - orient;
}
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
/// <remarks>spivot() finds the other subsegment (from the same segment)
/// that shares the same origin.
/// </remarks>
public void Pivot(ref Osub os)
{
os = seg.subsegs[orient];
}
/// <summary>
/// Finds a triangle abutting a subsegment.
/// </summary>
internal void Pivot(ref Otri ot)
{
ot = seg.triangles[orient];
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
public void Next(ref Osub ot)
{
ot = seg.subsegs[1 - orient];
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
public void Next()
{
this = seg.subsegs[1 - orient];
}
/// <summary>
/// Get the origin of a subsegment
/// </summary>
public Vertex Org()
{
return seg.vertices[orient];
}
/// <summary>
/// Get the destination of a subsegment
/// </summary>
public Vertex Dest()
{
return seg.vertices[1 - orient];
}
#endregion
#region Osub primitives (internal)
/// <summary>
/// Set the origin or destination of a subsegment.
/// </summary>
internal void SetOrg(Vertex vertex)
{
seg.vertices[orient] = vertex;
}
/// <summary>
/// Set destination of a subsegment.
/// </summary>
internal void SetDest(Vertex vertex)
{
seg.vertices[1 - orient] = vertex;
}
/// <summary>
/// Get the origin of the segment that includes the subsegment.
/// </summary>
internal Vertex SegOrg()
{
return seg.vertices[2 + orient];
}
/// <summary>
/// Get the destination of the segment that includes the subsegment.
/// </summary>
internal Vertex SegDest()
{
return seg.vertices[3 - orient];
}
/// <summary>
/// Set the origin of the segment that includes the subsegment.
/// </summary>
internal void SetSegOrg(Vertex vertex)
{
seg.vertices[2 + orient] = vertex;
}
/// <summary>
/// Set the destination of the segment that includes the subsegment.
/// </summary>
internal void SetSegDest(Vertex vertex)
{
seg.vertices[3 - orient] = vertex;
}
/* Unused primitives.
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
public void PivotSelf()
{
this = seg.subsegs[orient];
}
/// <summary>
/// Read a boundary marker.
/// </summary>
/// <remarks>Boundary markers are used to hold user-defined tags for
/// setting boundary conditions in finite element solvers.</remarks>
public int Mark()
{
return seg.boundary;
}
/// <summary>
/// Set a boundary marker.
/// </summary>
public void SetMark(int value)
{
seg.boundary = value;
}
/// <summary>
/// Copy a subsegment.
/// </summary>
public void Copy(ref Osub o2)
{
o2.seg = seg;
o2.orient = orient;
}
//*/
/// <summary>
/// Bond two subsegments together. [bond(abc, ba)]
/// </summary>
internal void Bond(ref Osub os)
{
seg.subsegs[orient] = os;
os.seg.subsegs[os.orient] = this;
}
/// <summary>
/// Dissolve a subsegment bond (from one side).
/// </summary>
/// <remarks>Note that the other subsegment will still think it's
/// connected to this subsegment.</remarks>
internal void Dissolve(SubSegment dummy)
{
seg.subsegs[orient].seg = dummy;
}
/// <summary>
/// Test for equality of subsegments.
/// </summary>
internal bool Equal(Osub os)
{
return ((seg == os.seg) && (orient == os.orient));
}
/// <summary>
/// Dissolve a bond (from the subsegment side).
/// </summary>
internal void TriDissolve(Triangle dummy)
{
seg.triangles[orient].tri = dummy;
}
/// <summary>
/// Check a subsegment's deallocation.
/// </summary>
internal static bool IsDead(SubSegment sub)
{
return sub.subsegs[0].seg == null;
}
/// <summary>
/// Set a subsegment's deallocation.
/// </summary>
internal static void Kill(SubSegment sub)
{
sub.subsegs[0].seg = null;
sub.subsegs[1].seg = null;
}
#endregion
}
}

View File

@@ -0,0 +1,482 @@
// -----------------------------------------------------------------------
// <copyright file="Otri.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// An oriented triangle.
/// </summary>
/// <remarks>
/// Includes a pointer to a triangle and orientation. The orientation denotes an edge
/// of the triangle. Hence, there are three possible orientations. By convention, each
/// edge always points counterclockwise about the corresponding triangle.
/// </remarks>
internal struct Otri
{
internal Triangle tri;
internal int orient; // Ranges from 0 to 2.
public Triangle Triangle
{
get { return tri; }
set { tri = value; }
}
public override string ToString()
{
if (tri == null)
{
return "O-TID [null]";
}
return String.Format("O-TID {0}", tri.hash);
}
#region Otri primitives (public)
// For fast access
static readonly int[] plus1Mod3 = { 1, 2, 0 };
static readonly int[] minus1Mod3 = { 2, 0, 1 };
// The following primitives are all described by Guibas and Stolfi.
// However, Guibas and Stolfi use an edge-based data structure,
// whereas I use a triangle-based data structure.
//
// lnext: finds the next edge (counterclockwise) of a triangle.
//
// onext: spins counterclockwise around a vertex; that is, it finds
// the next edge with the same origin in the counterclockwise direction. This
// edge is part of a different triangle.
//
// oprev: spins clockwise around a vertex; that is, it finds the
// next edge with the same origin in the clockwise direction. This edge is
// part of a different triangle.
//
// dnext: spins counterclockwise around a vertex; that is, it finds
// the next edge with the same destination in the counterclockwise direction.
// This edge is part of a different triangle.
//
// dprev: spins clockwise around a vertex; that is, it finds the
// next edge with the same destination in the clockwise direction. This edge
// is part of a different triangle.
//
// rnext: moves one edge counterclockwise about the adjacent
// triangle. (It's best understood by reading Guibas and Stolfi. It
// involves changing triangles twice.)
//
// rprev: moves one edge clockwise about the adjacent triangle.
// (It's best understood by reading Guibas and Stolfi. It involves
// changing triangles twice.)
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
/// Note that the edge direction is necessarily reversed, because the handle specified
/// by an oriented triangle is directed counterclockwise around the triangle.
/// </remarks>
public void Sym(ref Otri ot)
{
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
}
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
public void Sym()
{
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void Lnext(ref Otri ot)
{
ot.tri = tri;
ot.orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void Lnext()
{
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void Lprev(ref Otri ot)
{
ot.tri = tri;
ot.orient = minus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void Lprev()
{
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
public void Onext(ref Otri ot)
{
//Lprev(ref ot);
ot.tri = tri;
ot.orient = minus1Mod3[orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
public void Onext()
{
//LprevSelf();
orient = minus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
public void Oprev(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LnextSelf();
ot.orient = plus1Mod3[ot.orient];
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
public void Oprev()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LnextSelf();
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
public void Dnext(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LprevSelf();
ot.orient = minus1Mod3[ot.orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
public void Dnext()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LprevSelf();
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
public void Dprev(ref Otri ot)
{
//Lnext(ref ot);
ot.tri = tri;
ot.orient = plus1Mod3[orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
public void Dprev()
{
//LnextSelf();
orient = plus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
public void Rnext(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LnextSelf();
ot.orient = plus1Mod3[ot.orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
public void Rnext()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LnextSelf();
orient = plus1Mod3[orient];
//SymSelf();
tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
public void Rprev(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LprevSelf();
ot.orient = minus1Mod3[ot.orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
public void Rprev()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LprevSelf();
orient = minus1Mod3[orient];
//SymSelf();
tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Origin [org(abc) -> a]
/// </summary>
public Vertex Org()
{
return tri.vertices[plus1Mod3[orient]];
}
/// <summary>
/// Destination [dest(abc) -> b]
/// </summary>
public Vertex Dest()
{
return tri.vertices[minus1Mod3[orient]];
}
/// <summary>
/// Apex [apex(abc) -> c]
/// </summary>
public Vertex Apex()
{
return tri.vertices[orient];
}
/// <summary>
/// Copy an oriented triangle.
/// </summary>
public void Copy(ref Otri ot)
{
ot.tri = tri;
ot.orient = orient;
}
/// <summary>
/// Test for equality of oriented triangles.
/// </summary>
public bool Equals(Otri ot)
{
return ((tri == ot.tri) && (orient == ot.orient));
}
#endregion
#region Otri primitives (internal)
/// <summary>
/// Set Origin
/// </summary>
internal void SetOrg(Vertex v)
{
tri.vertices[plus1Mod3[orient]] = v;
}
/// <summary>
/// Set Destination
/// </summary>
internal void SetDest(Vertex v)
{
tri.vertices[minus1Mod3[orient]] = v;
}
/// <summary>
/// Set Apex
/// </summary>
internal void SetApex(Vertex v)
{
tri.vertices[orient] = v;
}
/// <summary>
/// Bond two triangles together at the resepective handles. [bond(abc, bad)]
/// </summary>
internal void Bond(ref Otri ot)
{
tri.neighbors[orient].tri = ot.tri;
tri.neighbors[orient].orient = ot.orient;
ot.tri.neighbors[ot.orient].tri = this.tri;
ot.tri.neighbors[ot.orient].orient = this.orient;
}
/// <summary>
/// Dissolve a bond (from one side).
/// </summary>
/// <remarks>Note that the other triangle will still think it's connected to
/// this triangle. Usually, however, the other triangle is being deleted
/// entirely, or bonded to another triangle, so it doesn't matter.
/// </remarks>
internal void Dissolve(Triangle dummy)
{
tri.neighbors[orient].tri = dummy;
tri.neighbors[orient].orient = 0;
}
/// <summary>
/// Infect a triangle with the virus.
/// </summary>
internal void Infect()
{
tri.infected = true;
}
/// <summary>
/// Cure a triangle from the virus.
/// </summary>
internal void Uninfect()
{
tri.infected = false;
}
/// <summary>
/// Test a triangle for viral infection.
/// </summary>
internal bool IsInfected()
{
return tri.infected;
}
/// <summary>
/// Finds a subsegment abutting a triangle.
/// </summary>
internal void Pivot(ref Osub os)
{
os = tri.subsegs[orient];
}
/// <summary>
/// Bond a triangle to a subsegment.
/// </summary>
internal void SegBond(ref Osub os)
{
tri.subsegs[orient] = os;
os.seg.triangles[os.orient] = this;
}
/// <summary>
/// Dissolve a bond (from the triangle side).
/// </summary>
internal void SegDissolve(SubSegment dummy)
{
tri.subsegs[orient].seg = dummy;
}
/// <summary>
/// Check a triangle's deallocation.
/// </summary>
internal static bool IsDead(Triangle tria)
{
return tria.neighbors[0].tri == null;
}
/// <summary>
/// Set a triangle's deallocation.
/// </summary>
internal static void Kill(Triangle tri)
{
tri.neighbors[0].tri = null;
tri.neighbors[2].tri = null;
}
#endregion
}
}

View File

@@ -0,0 +1,97 @@
// -----------------------------------------------------------------------
// <copyright file="Segment.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// The subsegment data structure.
/// </summary>
internal class SubSegment : ISegment
{
// Hash for dictionary. Will be set by mesh instance.
internal int hash;
internal Osub[] subsegs;
internal Vertex[] vertices;
internal Otri[] triangles;
internal int boundary;
public SubSegment()
{
// Four NULL vertices.
vertices = new Vertex[4];
// Set the boundary marker to zero.
boundary = 0;
// Initialize the two adjoining subsegments to be the omnipresent
// subsegment.
subsegs = new Osub[2];
// Initialize the two adjoining triangles to be "outer space."
triangles = new Otri[2];
}
#region Public properties
/// <summary>
/// Gets the first endpoints vertex id.
/// </summary>
public int P0
{
get { return this.vertices[0].id; }
}
/// <summary>
/// Gets the seconds endpoints vertex id.
/// </summary>
public int P1
{
get { return this.vertices[1].id; }
}
/// <summary>
/// Gets the segment boundary mark.
/// </summary>
public int Label
{
get { return this.boundary; }
}
#endregion
/// <summary>
/// Gets the segments endpoint.
/// </summary>
public Vertex GetVertex(int index)
{
return this.vertices[index]; // TODO: Check range?
}
/// <summary>
/// Gets an adjoining triangle.
/// </summary>
public ITriangle GetTriangle(int index)
{
return triangles[index].tri.hash == Mesh.DUMMY ? null : triangles[index].tri;
}
public override int GetHashCode()
{
return this.hash;
}
public override string ToString()
{
return String.Format("SID {0}", hash);
}
}
}

View File

@@ -0,0 +1,129 @@
// -----------------------------------------------------------------------
// <copyright file="Triangle.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Topology
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// The triangle data structure.
/// </summary>
internal class Triangle : ITriangle
{
// Hash for dictionary. Will be set by mesh instance.
internal int hash;
// The ID is only used for mesh output.
internal int id;
internal Otri[] neighbors;
internal Vertex[] vertices;
internal Osub[] subsegs;
internal int label;
internal double area;
internal bool infected;
/// <summary>
/// Initializes a new instance of the <see cref="Triangle" /> class.
/// </summary>
public Triangle()
{
// Three NULL vertices.
vertices = new Vertex[3];
// Initialize the three adjoining subsegments to be the omnipresent subsegment.
subsegs = new Osub[3];
// Initialize the three adjoining triangles to be "outer space".
neighbors = new Otri[3];
// area = -1.0;
}
#region Public properties
/// <summary>
/// Gets or sets the triangle id.
/// </summary>
public int ID
{
get { return this.id; }
set { this.id = value; }
}
/// <summary>
/// Region ID the triangle belongs to.
/// </summary>
public int Label
{
get { return this.label; }
set { this.label = value; }
}
/// <summary>
/// Gets the triangle area constraint.
/// </summary>
public double Area
{
get { return this.area; }
set { this.area = value; }
}
/// <summary>
/// Gets the specified corners vertex.
/// </summary>
public Vertex GetVertex(int index)
{
return this.vertices[index]; // TODO: Check range?
}
public int GetVertexID(int index)
{
return this.vertices[index].id;
}
/// <summary>
/// Gets a triangles' neighbor.
/// </summary>
/// <param name="index">The neighbor index (0, 1 or 2).</param>
/// <returns>The neigbbor opposite of vertex with given index.</returns>
public ITriangle GetNeighbor(int index)
{
return neighbors[index].tri.hash == Mesh.DUMMY ? null : neighbors[index].tri;
}
/// <inheritdoc />
public int GetNeighborID(int index)
{
return neighbors[index].tri.hash == Mesh.DUMMY ? -1 : neighbors[index].tri.id;
}
/// <summary>
/// Gets a triangles segment.
/// </summary>
/// <param name="index">The vertex index (0, 1 or 2).</param>
/// <returns>The segment opposite of vertex with given index.</returns>
public ISegment GetSegment(int index)
{
return subsegs[index].seg.hash == Mesh.DUMMY ? null : subsegs[index].seg;
}
#endregion
public override int GetHashCode()
{
return this.hash;
}
public override string ToString()
{
return String.Format("TID {0}", hash);
}
}
}

View File

@@ -0,0 +1,363 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleLocator.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// Locate triangles in a mesh.
/// </summary>
/// <remarks>
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
///
/// Based on a paper by Ernst P. Mucke, Isaac Saias, and Binhai Zhu, "Fast
/// Randomized Point Location Without Preprocessing in Two- and Three-Dimensional
/// Delaunay Triangulations," Proceedings of the Twelfth Annual Symposium on
/// Computational Geometry, ACM, May 1996.
/// </remarks>
internal class TriangleLocator
{
TriangleSampler sampler;
Mesh mesh;
IPredicates predicates;
// Pointer to a recently visited triangle. Improves point location if
// proximate vertices are inserted sequentially.
internal Otri recenttri;
public TriangleLocator(Mesh mesh)
: this(mesh, RobustPredicates.Default)
{
}
public TriangleLocator(Mesh mesh, IPredicates predicates)
{
this.mesh = mesh;
this.predicates = predicates;
sampler = new TriangleSampler(mesh);
}
/// <summary>
/// Suggest the given triangle as a starting triangle for point location.
/// </summary>
/// <param name="otri"></param>
public void Update(ref Otri otri)
{
otri.Copy(ref recenttri);
}
public void Reset()
{
sampler.Reset();
recenttri.tri = null; // No triangle has been visited yet.
}
/// <summary>
/// Find a triangle or edge containing a given point.
/// </summary>
/// <param name="searchpoint">The point to locate.</param>
/// <param name="searchtri">The triangle to start the search at.</param>
/// <param name="stopatsubsegment"> If 'stopatsubsegment' is set, the search
/// will stop if it tries to walk through a subsegment, and will return OUTSIDE.</param>
/// <returns>Location information.</returns>
/// <remarks>
/// Begins its search from 'searchtri'. It is important that 'searchtri'
/// be a handle with the property that 'searchpoint' is strictly to the left
/// of the edge denoted by 'searchtri', or is collinear with that edge and
/// does not intersect that edge. (In particular, 'searchpoint' should not
/// be the origin or destination of that edge.)
///
/// These conditions are imposed because preciselocate() is normally used in
/// one of two situations:
///
/// (1) To try to find the location to insert a new point. Normally, we
/// know an edge that the point is strictly to the left of. In the
/// incremental Delaunay algorithm, that edge is a bounding box edge.
/// In Ruppert's Delaunay refinement algorithm for quality meshing,
/// that edge is the shortest edge of the triangle whose circumcenter
/// is being inserted.
///
/// (2) To try to find an existing point. In this case, any edge on the
/// convex hull is a good starting edge. You must screen out the
/// possibility that the vertex sought is an endpoint of the starting
/// edge before you call preciselocate().
///
/// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
///
/// This implementation differs from that given by Guibas and Stolfi. It
/// walks from triangle to triangle, crossing an edge only if 'searchpoint'
/// is on the other side of the line containing that edge. After entering
/// a triangle, there are two edges by which one can leave that triangle.
/// If both edges are valid ('searchpoint' is on the other side of both
/// edges), one of the two is chosen by drawing a line perpendicular to
/// the label edge (whose endpoints are 'forg' and 'fdest') passing through
/// 'fapex'. Depending on which side of this perpendicular 'searchpoint'
/// falls on, an exit edge is chosen.
///
/// This implementation is empirically faster than the Guibas and Stolfi
/// point location routine (which I originally used), which tends to spiral
/// in toward its target.
///
/// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
/// is a handle whose origin is the existing vertex.
///
/// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
/// handle whose primary edge is the edge on which the point lies.
///
/// Returns INTRIANGLE if the point lies strictly within a triangle.
/// 'searchtri' is a handle on the triangle that contains the point.
///
/// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
/// handle whose primary edge the point is to the right of. This might
/// occur when the circumcenter of a triangle falls just slightly outside
/// the mesh due to floating-point roundoff error. It also occurs when
/// seeking a hole or region point that a foolish user has placed outside
/// the mesh.
///
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
/// However, it can still be used to find the circumcenter of a triangle, as
/// long as the search is begun from the triangle in question.</remarks>
public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri,
bool stopatsubsegment)
{
Otri backtracktri = default(Otri);
Osub checkedge = default(Osub);
Vertex forg, fdest, fapex;
double orgorient, destorient;
bool moveleft;
// Where are we?
forg = searchtri.Org();
fdest = searchtri.Dest();
fapex = searchtri.Apex();
while (true)
{
// Check whether the apex is the point we seek.
if ((fapex.x == searchpoint.x) && (fapex.y == searchpoint.y))
{
searchtri.Lprev();
return LocateResult.OnVertex;
}
// Does the point lie on the other side of the line defined by the
// triangle edge opposite the triangle's destination?
destorient = predicates.CounterClockwise(forg, fapex, searchpoint);
// Does the point lie on the other side of the line defined by the
// triangle edge opposite the triangle's origin?
orgorient = predicates.CounterClockwise(fapex, fdest, searchpoint);
if (destorient > 0.0)
{
if (orgorient > 0.0)
{
// Move left if the inner product of (fapex - searchpoint) and
// (fdest - forg) is positive. This is equivalent to drawing
// a line perpendicular to the line (forg, fdest) and passing
// through 'fapex', and determining which side of this line
// 'searchpoint' falls on.
moveleft = (fapex.x - searchpoint.x) * (fdest.x - forg.x) +
(fapex.y - searchpoint.y) * (fdest.y - forg.y) > 0.0;
}
else
{
moveleft = true;
}
}
else
{
if (orgorient > 0.0)
{
moveleft = false;
}
else
{
// The point we seek must be on the boundary of or inside this
// triangle.
if (destorient == 0.0)
{
searchtri.Lprev();
return LocateResult.OnEdge;
}
if (orgorient == 0.0)
{
searchtri.Lnext();
return LocateResult.OnEdge;
}
return LocateResult.InTriangle;
}
}
// Move to another triangle. Leave a trace 'backtracktri' in case
// floating-point roundoff or some such bogey causes us to walk
// off a boundary of the triangulation.
if (moveleft)
{
searchtri.Lprev(ref backtracktri);
fdest = fapex;
}
else
{
searchtri.Lnext(ref backtracktri);
forg = fapex;
}
backtracktri.Sym(ref searchtri);
if (mesh.checksegments && stopatsubsegment)
{
// Check for walking through a subsegment.
backtracktri.Pivot(ref checkedge);
if (checkedge.seg.hash != Mesh.DUMMY)
{
// Go back to the last triangle.
backtracktri.Copy(ref searchtri);
return LocateResult.Outside;
}
}
// Check for walking right out of the triangulation.
if (searchtri.tri.id == Mesh.DUMMY)
{
// Go back to the last triangle.
backtracktri.Copy(ref searchtri);
return LocateResult.Outside;
}
fapex = searchtri.Apex();
}
}
/// <summary>
/// Find a triangle or edge containing a given point.
/// </summary>
/// <param name="searchpoint">The point to locate.</param>
/// <param name="searchtri">The triangle to start the search at.</param>
/// <returns>Location information.</returns>
/// <remarks>
/// Searching begins from one of: the input 'searchtri', a recently
/// encountered triangle 'recenttri', or from a triangle chosen from a
/// random sample. The choice is made by determining which triangle's
/// origin is closest to the point we are searching for. Normally,
/// 'searchtri' should be a handle on the convex hull of the triangulation.
///
/// Details on the random sampling method can be found in the Mucke, Saias,
/// and Zhu paper cited in the header of this code.
///
/// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
///
/// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
/// is a handle whose origin is the existing vertex.
///
/// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
/// handle whose primary edge is the edge on which the point lies.
///
/// Returns INTRIANGLE if the point lies strictly within a triangle.
/// 'searchtri' is a handle on the triangle that contains the point.
///
/// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
/// handle whose primary edge the point is to the right of. This might
/// occur when the circumcenter of a triangle falls just slightly outside
/// the mesh due to floating-point roundoff error. It also occurs when
/// seeking a hole or region point that a foolish user has placed outside
/// the mesh.
///
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
/// </remarks>
public LocateResult Locate(Point searchpoint, ref Otri searchtri)
{
Otri sampletri = default(Otri);
Vertex torg, tdest;
double searchdist, dist;
double ahead;
// Record the distance from the suggested starting triangle to the
// point we seek.
torg = searchtri.Org();
searchdist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
// If a recently encountered triangle has been recorded and has not been
// deallocated, test it as a good starting point.
if (recenttri.tri != null)
{
if (!Otri.IsDead(recenttri.tri))
{
torg = recenttri.Org();
if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y))
{
recenttri.Copy(ref searchtri);
return LocateResult.OnVertex;
}
dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
if (dist < searchdist)
{
recenttri.Copy(ref searchtri);
searchdist = dist;
}
}
}
// TODO: Improve sampling.
sampler.Update();
foreach (var t in sampler)
{
sampletri.tri = t;
if (!Otri.IsDead(sampletri.tri))
{
torg = sampletri.Org();
dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
if (dist < searchdist)
{
sampletri.Copy(ref searchtri);
searchdist = dist;
}
}
}
// Where are we?
torg = searchtri.Org();
tdest = searchtri.Dest();
// Check the starting triangle's vertices.
if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y))
{
return LocateResult.OnVertex;
}
if ((tdest.x == searchpoint.x) && (tdest.y == searchpoint.y))
{
searchtri.Lnext();
return LocateResult.OnVertex;
}
// Orient 'searchtri' to fit the preconditions of calling preciselocate().
ahead = predicates.CounterClockwise(torg, tdest, searchpoint);
if (ahead < 0.0)
{
// Turn around so that 'searchpoint' is to the left of the
// edge specified by 'searchtri'.
searchtri.Sym();
}
else if (ahead == 0.0)
{
// Check if 'searchpoint' is between 'torg' and 'tdest'.
if (((torg.x < searchpoint.x) == (searchpoint.x < tdest.x)) &&
((torg.y < searchpoint.y) == (searchpoint.y < tdest.y)))
{
return LocateResult.OnEdge;
}
}
return PreciseLocate(searchpoint, ref searchtri, false);
}
}
}

View File

@@ -0,0 +1,305 @@
// -----------------------------------------------------------------------
// <copyright file="TrianglePool.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
internal class TrianglePool : ICollection<Triangle>
{
// Determines the size of each block in the pool.
private const int BLOCKSIZE = 1024;
// The total number of currently allocated triangles.
int size;
// The number of triangles currently used.
int count;
// The pool.
Triangle[][] pool;
// A stack of free triangles.
Stack<Triangle> stack;
public TrianglePool()
{
size = 0;
// On startup, the pool should be able to hold 2^16 triangles.
int n = Math.Max(1, 65536 / BLOCKSIZE);
pool = new Triangle[n][];
pool[0] = new Triangle[BLOCKSIZE];
stack = new Stack<Triangle>(BLOCKSIZE);
}
/// <summary>
/// Gets a triangle from the pool.
/// </summary>
/// <returns></returns>
public Triangle Get()
{
Triangle triangle;
if (stack.Count > 0)
{
triangle = stack.Pop();
triangle.hash = -triangle.hash - 1;
Cleanup(triangle);
}
else if (count < size)
{
triangle = pool[count / BLOCKSIZE][count % BLOCKSIZE];
triangle.id = triangle.hash;
Cleanup(triangle);
count++;
}
else
{
triangle = new Triangle();
triangle.hash = size;
triangle.id = triangle.hash;
int block = size / BLOCKSIZE;
if (pool[block] == null)
{
pool[block] = new Triangle[BLOCKSIZE];
// Check if the pool has to be resized.
if (block + 1 == pool.Length)
{
Array.Resize(ref pool, 2 * pool.Length);
}
}
// Add triangle to pool.
pool[block][size % BLOCKSIZE] = triangle;
count = ++size;
}
return triangle;
}
public void Release(Triangle triangle)
{
stack.Push(triangle);
// Mark the triangle as free (used by enumerator).
triangle.hash = -triangle.hash - 1;
}
/// <summary>
/// Restart the triangle pool.
/// </summary>
public TrianglePool Restart()
{
foreach (var triangle in stack)
{
// Reset hash to original value.
triangle.hash = -triangle.hash - 1;
}
stack.Clear();
count = 0;
return this;
}
/// <summary>
/// Samples a number of triangles from the pool.
/// </summary>
/// <param name="k">The number of triangles to sample.</param>
/// <param name="random"></param>
/// <returns></returns>
internal IEnumerable<Triangle> Sample(int k, Random random)
{
int i, count = this.Count;
if (k > count)
{
// TODO: handle Sample special case.
k = count;
}
Triangle t;
// TODO: improve sampling code (to ensure no duplicates).
while (k > 0)
{
i = random.Next(0, count);
t = pool[i / BLOCKSIZE][i % BLOCKSIZE];
if (t.hash >= 0)
{
k--;
yield return t;
}
}
}
private void Cleanup(Triangle triangle)
{
triangle.label = 0;
triangle.area = 0.0;
triangle.infected = false;
for (int i = 0; i < 3; i++)
{
triangle.vertices[i] = null;
triangle.subsegs[i] = default(Osub);
triangle.neighbors[i] = default(Otri);
}
}
public void Add(Triangle item)
{
throw new NotImplementedException();
}
public void Clear()
{
stack.Clear();
int blocks = (size / BLOCKSIZE) + 1;
for (int i = 0; i < blocks; i++)
{
var block = pool[i];
// Number of triangles in current block:
int length = (size - i * BLOCKSIZE) % BLOCKSIZE;
for (int j = 0; j < length; j++)
{
block[j] = null;
}
}
size = count = 0;
}
public bool Contains(Triangle item)
{
int i = item.hash;
if (i < 0 || i > size)
{
return false;
}
return pool[i / BLOCKSIZE][i % BLOCKSIZE].hash >= 0;
}
public void CopyTo(Triangle[] array, int index)
{
var enumerator = GetEnumerator();
while (enumerator.MoveNext())
{
array[index] = enumerator.Current;
index++;
}
}
public int Count
{
get { return count - stack.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(Triangle item)
{
throw new NotImplementedException();
}
public IEnumerator<Triangle> GetEnumerator()
{
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class Enumerator : IEnumerator<Triangle>
{
// TODO: enumerator should be able to tell if collection changed.
int count;
Triangle[][] pool;
Triangle current;
int index, offset;
internal Enumerator(TrianglePool pool)
{
this.count = pool.Count;
this.pool = pool.pool;
index = 0;
offset = 0;
}
public Triangle Current
{
get { return current; }
}
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return current; }
}
public bool MoveNext()
{
while (index < count)
{
current = pool[offset / BLOCKSIZE][offset % BLOCKSIZE];
offset++;
if (current.hash >= 0)
{
index++;
return true;
}
}
return false;
}
public void Reset()
{
index = offset = 0;
}
}
}
}

View File

@@ -0,0 +1,85 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleSampler.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
/// <summary>
/// Used for triangle sampling in the <see cref="TriangleLocator"/> class.
/// </summary>
class TriangleSampler : IEnumerable<Triangle>
{
private const int RANDOM_SEED = 110503;
// Empirically chosen factor.
private const int samplefactor = 11;
private Random random;
private Mesh mesh;
// Number of random samples for point location (at least 1).
private int samples = 1;
// Number of triangles in mesh.
private int triangleCount = 0;
public TriangleSampler(Mesh mesh)
: this(mesh, new Random(RANDOM_SEED))
{
}
public TriangleSampler(Mesh mesh, Random random)
{
this.mesh = mesh;
this.random = random;
}
/// <summary>
/// Reset the sampler.
/// </summary>
public void Reset()
{
this.samples = 1;
this.triangleCount = 0;
}
/// <summary>
/// Update sampling parameters if mesh changed.
/// </summary>
public void Update()
{
int count = mesh.triangles.Count;
if (triangleCount != count)
{
triangleCount = count;
// The number of random samples taken is proportional to the cube root
// of the number of triangles in the mesh. The next bit of code assumes
// that the number of triangles increases monotonically (or at least
// doesn't decrease enough to matter).
while (samplefactor * samples * samples * samples < count)
{
samples++;
}
}
}
public IEnumerator<Triangle> GetEnumerator()
{
return mesh.triangles.Sample(samples, random).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "Unity.2D.Animation.Triangle.Runtime",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

@@ -0,0 +1,183 @@
// -----------------------------------------------------------------------
// <copyright file="BoundedVoronoi.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology.DCEL;
using HVertex = Animation.TriangleNet.Topology.DCEL.Vertex;
using TVertex = Animation.TriangleNet.Geometry.Vertex;
internal class BoundedVoronoi : VoronoiBase
{
int offset;
public BoundedVoronoi(Mesh mesh)
: this(mesh, new DefaultVoronoiFactory(), RobustPredicates.Default)
{
}
public BoundedVoronoi(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
: base(mesh, factory, predicates, true)
{
// We explicitly told the base constructor to call the Generate method, so
// at this point the basic Voronoi diagram is already created.
offset = base.vertices.Count;
// Each vertex of the hull will be part of a Voronoi cell.
base.vertices.Capacity = offset + mesh.hullsize;
// Create bounded Voronoi diagram.
PostProcess();
ResolveBoundaryEdges();
}
/// <summary>
/// Computes edge intersections with mesh boundary edges.
/// </summary>
private void PostProcess()
{
foreach (var edge in rays)
{
var twin = edge.twin;
var v1 = (TVertex)edge.face.generator;
var v2 = (TVertex)twin.face.generator;
double dir = predicates.CounterClockwise(v1, v2, edge.origin);
if (dir <= 0)
{
HandleCase1(edge, v1, v2);
}
else
{
HandleCase2(edge, v1, v2);
}
}
}
/// <summary>
/// Case 1: edge origin lies inside the domain.
/// </summary>
private void HandleCase1(HalfEdge edge, TVertex v1, TVertex v2)
{
//int mark = GetBoundaryMark(v1);
// The infinite vertex.
var v = (Point)edge.twin.origin;
// The half-edge is the bisector of v1 and v2, so the projection onto the
// boundary segment is actually its midpoint.
v.x = (v1.x + v2.x) / 2.0;
v.y = (v1.y + v2.y) / 2.0;
// Close the cell connected to edge.
var gen = factory.CreateVertex(v1.x, v1.y);
var h1 = factory.CreateHalfEdge(edge.twin.origin, edge.face);
var h2 = factory.CreateHalfEdge(gen, edge.face);
edge.next = h1;
h1.next = h2;
h2.next = edge.face.edge;
gen.leaving = h2;
// Let the face edge point to the edge leaving at generator.
edge.face.edge = h2;
base.edges.Add(h1);
base.edges.Add(h2);
int count = base.edges.Count;
h1.id = count;
h2.id = count + 1;
gen.id = offset++;
base.vertices.Add(gen);
}
/// <summary>
/// Case 2: edge origin lies outside the domain.
/// </summary>
private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2)
{
// The vertices of the infinite edge.
var p1 = (Point)edge.origin;
var p2 = (Point)edge.twin.origin;
// The two edges leaving p1, pointing into the mesh.
var e1 = edge.twin.next;
var e2 = e1.twin.next;
// Find the two intersections with boundary edge.
IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2);
IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1);
// The infinite edge will now lie on the boundary. Update pointers:
e1.twin.next = edge.twin;
edge.twin.next = e2;
edge.twin.face = e2.face;
e1.origin = edge.twin.origin;
edge.twin.twin = null;
edge.twin = null;
// Close the cell.
var gen = factory.CreateVertex(v1.x, v1.y);
var he = factory.CreateHalfEdge(gen, edge.face);
edge.next = he;
he.next = edge.face.edge;
// Let the face edge point to the edge leaving at generator.
edge.face.edge = he;
base.edges.Add(he);
he.id = base.edges.Count;
gen.id = offset++;
base.vertices.Add(gen);
}
/*
private int GetBoundaryMark(Vertex v)
{
Otri tri = default(Otri);
Otri next = default(Otri);
Osub seg = default(Osub);
// Get triangle connected to generator.
v.tri.Copy(ref tri);
v.tri.Copy(ref next);
// Find boundary triangle.
while (next.triangle.id != -1)
{
next.Copy(ref tri);
next.OnextSelf();
}
// Find edge dual to current half-edge.
tri.LnextSelf();
tri.LnextSelf();
tri.SegPivot(ref seg);
return seg.seg.boundary;
}
//*/
}
}

View File

@@ -0,0 +1,35 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi
{
using System;
using Animation.TriangleNet.Topology.DCEL;
/// <summary>
/// Default factory for Voronoi / DCEL mesh objects.
/// </summary>
internal class DefaultVoronoiFactory : IVoronoiFactory
{
public void Initialize(int vertexCount, int edgeCount, int faceCount)
{
}
public void Reset()
{
}
public Vertex CreateVertex(double x, double y)
{
return new Vertex(x, y);
}
public HalfEdge CreateHalfEdge(Vertex origin, Face face)
{
return new HalfEdge(origin, face);
}
public Face CreateFace(Geometry.Vertex vertex)
{
return new Face(vertex);
}
}
}

View File

@@ -0,0 +1,18 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi
{
using Animation.TriangleNet.Topology.DCEL;
internal interface IVoronoiFactory
{
void Initialize(int vertexCount, int edgeCount, int faceCount);
void Reset();
Vertex CreateVertex(double x, double y);
HalfEdge CreateHalfEdge(Vertex origin, Face face);
Face CreateFace(Geometry.Vertex vertex);
}
}

View File

@@ -0,0 +1,693 @@
// -----------------------------------------------------------------------
// <copyright file="BoundedVoronoi.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi.Legacy
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// The Bounded Voronoi Diagram is the dual of a PSLG triangulation.
/// </summary>
/// <remarks>
/// 2D Centroidal Voronoi Tessellations with Constraints, 2010,
/// Jane Tournois, Pierre Alliez and Olivier Devillers
/// </remarks>
[Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")]
internal class BoundedVoronoiLegacy : IVoronoi
{
IPredicates predicates = RobustPredicates.Default;
Mesh mesh;
Point[] points;
List<VoronoiRegion> regions;
// Used for new points on segments.
List<Point> segPoints;
int segIndex;
Dictionary<int, SubSegment> subsegMap;
bool includeBoundary = true;
/// <summary>
/// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
/// </summary>
/// <param name="mesh">Mesh instance.</param>
public BoundedVoronoiLegacy(Mesh mesh)
: this(mesh, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
/// </summary>
/// <param name="mesh">Mesh instance.</param>
public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary)
{
this.mesh = mesh;
this.includeBoundary = includeBoundary;
Generate();
}
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
public Point[] Points
{
get { return points; }
}
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
public ICollection<VoronoiRegion> Regions
{
get { return regions; }
}
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Computes the bounded voronoi diagram.
/// </summary>
private void Generate()
{
mesh.Renumber();
mesh.MakeVertexMap();
// Allocate space for voronoi diagram
this.regions = new List<VoronoiRegion>(mesh.vertices.Count);
this.points = new Point[mesh.triangles.Count];
this.segPoints = new List<Point>(mesh.subsegs.Count * 4);
ComputeCircumCenters();
TagBlindTriangles();
foreach (var v in mesh.vertices.Values)
{
// TODO: Need a reliable way to check if a vertex is on a segment
if (v.type == VertexType.FreeVertex || v.label == 0)
{
ConstructCell(v);
}
else if (includeBoundary)
{
ConstructBoundaryCell(v);
}
}
// Add the new points on segments to the point array.
int length = points.Length;
Array.Resize<Point>(ref points, length + segPoints.Count);
for (int i = 0; i < segPoints.Count; i++)
{
points[length + i] = segPoints[i];
}
this.segPoints.Clear();
this.segPoints = null;
}
private void ComputeCircumCenters()
{
Otri tri = default(Otri);
double xi = 0, eta = 0;
Point pt;
// Compue triangle circumcenters
foreach (var item in mesh.triangles)
{
tri.tri = item;
pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta);
pt.id = item.id;
points[item.id] = pt;
}
}
/// <summary>
/// Tag all blind triangles.
/// </summary>
/// <remarks>
/// A triangle is said to be blind if the triangle and its circumcenter
/// lie on two different sides of a constrained edge.
/// </remarks>
private void TagBlindTriangles()
{
int blinded = 0;
Stack<Triangle> triangles;
subsegMap = new Dictionary<int, SubSegment>();
Otri f = default(Otri);
Otri f0 = default(Otri);
Osub e = default(Osub);
Osub sub1 = default(Osub);
// Tag all triangles non-blind
foreach (var t in mesh.triangles)
{
// Use the infected flag for 'blinded' attribute.
t.infected = false;
}
// for each constrained edge e of cdt do
foreach (var ss in mesh.subsegs.Values)
{
// Create a stack: triangles
triangles = new Stack<Triangle>();
// for both adjacent triangles fe to e tagged non-blind do
// Push fe into triangles
e.seg = ss;
e.orient = 0;
e.Pivot(ref f);
if (f.tri.id != Mesh.DUMMY && !f.tri.infected)
{
triangles.Push(f.tri);
}
e.Sym();
e.Pivot(ref f);
if (f.tri.id != Mesh.DUMMY && !f.tri.infected)
{
triangles.Push(f.tri);
}
// while triangles is non-empty
while (triangles.Count > 0)
{
// Pop f from stack triangles
f.tri = triangles.Pop();
f.orient = 0;
// if f is blinded by e (use P) then
if (TriangleIsBlinded(ref f, ref e))
{
// Tag f as blinded by e
f.tri.infected = true;
blinded++;
// Store association triangle -> subseg
subsegMap.Add(f.tri.hash, e.seg);
// for each adjacent triangle f0 to f do
for (f.orient = 0; f.orient < 3; f.orient++)
{
f.Sym(ref f0);
f0.Pivot(ref sub1);
// if f0 is finite and tagged non-blind & the common edge
// between f and f0 is unconstrained then
if (f0.tri.id != Mesh.DUMMY && !f0.tri.infected && sub1.seg.hash == Mesh.DUMMY)
{
// Push f0 into triangles.
triangles.Push(f0.tri);
}
}
}
}
}
blinded = 0;
}
/// <summary>
/// Check if given triangle is blinded by given segment.
/// </summary>
/// <param name="tri">Triangle.</param>
/// <param name="seg">Segments</param>
/// <returns>Returns true, if the triangle is blinded.</returns>
private bool TriangleIsBlinded(ref Otri tri, ref Osub seg)
{
Point c, pt;
Vertex torg = tri.Org();
Vertex tdest = tri.Dest();
Vertex tapex = tri.Apex();
Vertex sorg = seg.Org();
Vertex sdest = seg.Dest();
c = this.points[tri.tri.id];
if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true))
{
return true;
}
if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true))
{
return true;
}
if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true))
{
return true;
}
return false;
}
private void ConstructCell(Vertex vertex)
{
VoronoiRegion region = new VoronoiRegion(vertex);
regions.Add(region);
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Osub sf = default(Osub);
Osub sfn = default(Osub);
Point cc_f, cc_f_next, p;
int n = mesh.triangles.Count;
// Call P the polygon (cell) in construction
List<Point> vpoints = new List<Point>();
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
if (f_init.Org() != vertex)
{
throw new Exception("ConstructCell: inconsistent topology.");
}
// Let f be initialized to f_init
f_init.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_init.Onext(ref f_next);
// repeat ... until f = f_init
do
{
// Call Lffnext the line going through the circumcenters of f and f_next
cc_f = this.points[f.tri.id];
cc_f_next = this.points[f_next.tri.id];
// if f is tagged non-blind then
if (!f.tri.infected)
{
// Insert the circumcenter of f into P
vpoints.Add(cc_f);
if (f_next.tri.infected)
{
// Call S_fnext the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// Insert point Lf,f_next /\ Sf_next into P
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
else
{
// Call Sf the constrained edge blinding f
sf.seg = subsegMap[f.tri.hash];
// if f_next is tagged non-blind then
if (!f_next.tri.infected)
{
// Insert point Lf,f_next /\ Sf into P
if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Call Sf_next the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// if Sf != Sf_next then
if (!sf.Equal(sfn))
{
// Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P
if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
}
// f <- f_next
f_next.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_next.Onext();
}
while (!f.Equals(f_init));
// Output: Bounded Voronoi cell of x in counterclockwise order.
region.Add(vpoints);
}
private void ConstructBoundaryCell(Vertex vertex)
{
VoronoiRegion region = new VoronoiRegion(vertex);
regions.Add(region);
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Otri f_prev = default(Otri);
Osub sf = default(Osub);
Osub sfn = default(Osub);
Vertex torg, tdest, tapex, sorg, sdest;
Point cc_f, cc_f_next, p;
int n = mesh.triangles.Count;
// Call P the polygon (cell) in construction
List<Point> vpoints = new List<Point>();
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
if (f_init.Org() != vertex)
{
throw new Exception("ConstructBoundaryCell: inconsistent topology.");
}
// Let f be initialized to f_init
f_init.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_init.Onext(ref f_next);
f_init.Oprev(ref f_prev);
// Is the border to the left?
if (f_prev.tri.id != Mesh.DUMMY)
{
// Go clockwise until we reach the border (or the initial triangle)
while (f_prev.tri.id != Mesh.DUMMY && !f_prev.Equals(f_init))
{
f_prev.Copy(ref f);
f_prev.Oprev();
}
f.Copy(ref f_init);
f.Onext(ref f_next);
}
if (f_prev.tri.id == Mesh.DUMMY)
{
// For vertices on the domain boundaray, add the vertex. For
// internal boundaries don't add it.
p = new Point(vertex.x, vertex.y);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
// Add midpoint of start triangles' edge.
torg = f.Org();
tdest = f.Dest();
p = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
// repeat ... until f = f_init
do
{
// Call Lffnext the line going through the circumcenters of f and f_next
cc_f = this.points[f.tri.id];
if (f_next.tri.id == Mesh.DUMMY)
{
if (!f.tri.infected)
{
// Add last circumcenter
vpoints.Add(cc_f);
}
// Add midpoint of last triangles' edge (chances are it has already
// been added, so post process cell to remove duplicates???)
torg = f.Org();
tapex = f.Apex();
p = new Point((torg.x + tapex.x) / 2, (torg.y + tapex.y) / 2);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
break;
}
cc_f_next = this.points[f_next.tri.id];
// if f is tagged non-blind then
if (!f.tri.infected)
{
// Insert the circumcenter of f into P
vpoints.Add(cc_f);
if (f_next.tri.infected)
{
// Call S_fnext the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// Insert point Lf,f_next /\ Sf_next into P
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
else
{
// Call Sf the constrained edge blinding f
sf.seg = subsegMap[f.tri.hash];
sorg = sf.Org();
sdest = sf.Dest();
// if f_next is tagged non-blind then
if (!f_next.tri.infected)
{
tdest = f.Dest();
tapex = f.Apex();
// Both circumcenters lie on the blinded side, but we
// have to add the intersection with the segment.
// Center of f edge dest->apex
Point bisec = new Point((tdest.x + tapex.x) / 2, (tdest.y + tapex.y) / 2);
// Find intersection of seg with line through f's bisector and circumcenter
if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
// Insert point Lf,f_next /\ Sf into P
if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Call Sf_next the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// if Sf != Sf_next then
if (!sf.Equal(sfn))
{
// Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P
if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Both circumcenters lie on the blinded side, but we
// have to add the intersection with the segment.
// Center of f_next edge org->dest
Point bisec = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2);
// Find intersection of seg with line through f_next's bisector and circumcenter
if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
}
// f <- f_next
f_next.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_next.Onext();
}
while (!f.Equals(f_init));
// Output: Bounded Voronoi cell of x in counterclockwise order.
region.Add(vpoints);
}
/// <summary>
/// Determines the intersection point of the line segment defined by points A and B with the
/// line segment defined by points C and D.
/// </summary>
/// <param name="seg">The first segment AB.</param>
/// <param name="pc">Endpoint C of second segment.</param>
/// <param name="pd">Endpoint D of second segment.</param>
/// <param name="p">Reference to the intersection point.</param>
/// <param name="strictIntersect">If false, pa and pb represent a line.</param>
/// <returns>Returns true if the intersection point was found, and stores that point in X,Y.
/// Returns false if there is no determinable intersection point, in which case X,Y will
/// be unmodified.
/// </returns>
private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect)
{
p = null; // TODO: Voronoi SegmentsIntersect
double Ax = p1.x, Ay = p1.y;
double Bx = p2.x, By = p2.y;
double Cx = p3.x, Cy = p3.y;
double Dx = p4.x, Dy = p4.y;
double distAB, theCos, theSin, newX, ABpos;
// Fail if either line segment is zero-length.
if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false;
// Fail if the segments share an end-point.
if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy
|| Ax == Dx && Ay == Dy || Bx == Dx && By == Dy)
{
return false;
}
// (1) Translate the system so that point A is on the origin.
Bx -= Ax; By -= Ay;
Cx -= Ax; Cy -= Ay;
Dx -= Ax; Dy -= Ay;
// Discover the length of segment A-B.
distAB = Math.Sqrt(Bx * Bx + By * By);
// (2) Rotate the system so that point B is on the positive X axis.
theCos = Bx / distAB;
theSin = By / distAB;
newX = Cx * theCos + Cy * theSin;
Cy = Cy * theCos - Cx * theSin; Cx = newX;
newX = Dx * theCos + Dy * theSin;
Dy = Dy * theCos - Dx * theSin; Dx = newX;
// Fail if segment C-D doesn't cross line A-B.
if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false;
// (3) Discover the position of the intersection point along line A-B.
ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy);
// Fail if segment C-D crosses line A-B outside of segment A-B.
if (ABpos < 0 || ABpos > distAB && strictIntersect) return false;
// (4) Apply the discovered position to line A-B in the original coordinate system.
p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin);
// Success.
return true;
}
// TODO: Voronoi enumerate edges
private IEnumerable<IEdge> EnumerateEdges()
{
// Copy edges
Point first, last;
var edges = new List<IEdge>(this.Regions.Count * 2);
foreach (var region in this.Regions)
{
first = null;
last = null;
foreach (var pt in region.Vertices)
{
if (first == null)
{
first = pt;
last = pt;
}
else
{
edges.Add(new Edge(last.id, pt.id));
last = pt;
}
}
if (region.Bounded && first != null)
{
edges.Add(new Edge(last.id, first.id));
}
}
return edges;
}
}
}

View File

@@ -0,0 +1,33 @@
// -----------------------------------------------------------------------
// <copyright file="IVoronoi.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi.Legacy
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Voronoi diagram interface.
/// </summary>
internal interface IVoronoi
{
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
Point[] Points { get; }
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
ICollection<VoronoiRegion> Regions { get; }
/// <summary>
/// Gets the list of edges.
/// </summary>
IEnumerable<IEdge> Edges { get; }
}
}

View File

@@ -0,0 +1,307 @@
// -----------------------------------------------------------------------
// <copyright file="Voronoi.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Voronoi.Legacy
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
/// <summary>
/// The Voronoi Diagram is the dual of a pointset triangulation.
/// </summary>
[Obsolete("Use TriangleNet.Voronoi.StandardVoronoi class instead.")]
internal class SimpleVoronoi : IVoronoi
{
IPredicates predicates = RobustPredicates.Default;
Mesh mesh;
Point[] points;
Dictionary<int, VoronoiRegion> regions;
// Stores the endpoints of rays of unbounded Voronoi cells
Dictionary<int, Point> rayPoints;
int rayIndex;
// Bounding box of the triangles circumcenters.
Rectangle bounds;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleVoronoi" /> class.
/// </summary>
/// <param name="mesh"></param>
/// <remarks>
/// Be sure MakeVertexMap has been called (should always be the case).
/// </remarks>
public SimpleVoronoi(Mesh mesh)
{
this.mesh = mesh;
Generate();
}
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
public Point[] Points
{
get { return points; }
}
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
public ICollection<VoronoiRegion> Regions
{
get { return regions.Values; }
}
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Gets the Voronoi diagram as raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
/// <remarks>
/// The Voronoi diagram is the geometric dual of the Delaunay triangulation.
/// Hence, the Voronoi vertices are listed by traversing the Delaunay
/// triangles, and the Voronoi edges are listed by traversing the Delaunay
/// edges.
///</remarks>
private void Generate()
{
mesh.Renumber();
mesh.MakeVertexMap();
// Allocate space for voronoi diagram
this.points = new Point[mesh.triangles.Count + mesh.hullsize];
this.regions = new Dictionary<int, VoronoiRegion>(mesh.vertices.Count);
rayPoints = new Dictionary<int, Point>();
rayIndex = 0;
bounds = new Rectangle();
// Compute triangles circumcenters and setup bounding box
ComputeCircumCenters();
// Add all Voronoi regions to the map.
foreach (var vertex in mesh.vertices.Values)
{
regions.Add(vertex.id, new VoronoiRegion(vertex));
}
// Loop over the mesh vertices (Voronoi generators).
foreach (var region in regions.Values)
{
//if (item.Boundary == 0)
{
ConstructCell(region);
}
}
}
private void ComputeCircumCenters()
{
Otri tri = default(Otri);
double xi = 0, eta = 0;
Point pt;
// Compue triangle circumcenters
foreach (var item in mesh.triangles)
{
tri.tri = item;
pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta);
pt.id = item.id;
points[item.id] = pt;
bounds.Expand(pt);
}
double ds = Math.Max(bounds.Width, bounds.Height);
bounds.Resize(ds / 10, ds / 10);
}
/// <summary>
/// Construct Voronoi region for given vertex.
/// </summary>
/// <param name="region"></param>
private void ConstructCell(VoronoiRegion region)
{
var vertex = region.Generator as Vertex;
var vpoints = new List<Point>();
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Otri f_prev = default(Otri);
Osub sub = default(Osub);
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
f_init.Copy(ref f);
f_init.Onext(ref f_next);
// Check if f_init lies on the boundary of the triangulation.
if (f_next.tri.id == Mesh.DUMMY)
{
f_init.Oprev(ref f_prev);
if (f_prev.tri.id != Mesh.DUMMY)
{
f_init.Copy(ref f_next);
// Move one triangle clockwise
f_init.Oprev();
f_init.Copy(ref f);
}
}
// Go counterclockwise until we reach the border or the initial triangle.
while (f_next.tri.id != Mesh.DUMMY)
{
// Add circumcenter of current triangle
vpoints.Add(points[f.tri.id]);
region.AddNeighbor(f.tri.id, regions[f.Apex().id]);
if (f_next.Equals(f_init))
{
// Voronoi cell is complete (bounded case).
region.Add(vpoints);
return;
}
f_next.Copy(ref f);
f_next.Onext();
}
// Voronoi cell is unbounded
region.Bounded = false;
Vertex torg, tdest, tapex;
Point intersection;
int sid, n = mesh.triangles.Count;
// Find the boundary segment id (we use this id to number the endpoints of infinit rays).
f.Lprev(ref f_next);
f_next.Pivot(ref sub);
sid = sub.seg.hash;
// Last valid f lies at the boundary. Add the circumcenter.
vpoints.Add(points[f.tri.id]);
region.AddNeighbor(f.tri.id, regions[f.Apex().id]);
// Check if the intersection with the bounding box has already been computed.
if (!rayPoints.TryGetValue(sid, out intersection))
{
torg = f.Org();
tapex = f.Apex();
intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], torg.y - tapex.y, tapex.x - torg.x);
// Set the correct id for the vertex
intersection.id = n + rayIndex;
points[n + rayIndex] = intersection;
rayIndex++;
rayPoints.Add(sid, intersection);
}
vpoints.Add(intersection);
// Now walk from f_init clockwise till we reach the boundary.
vpoints.Reverse();
f_init.Copy(ref f);
f.Oprev(ref f_prev);
while (f_prev.tri.id != Mesh.DUMMY)
{
vpoints.Add(points[f_prev.tri.id]);
region.AddNeighbor(f_prev.tri.id, regions[f_prev.Apex().id]);
f_prev.Copy(ref f);
f_prev.Oprev();
}
// Find the boundary segment id.
f.Pivot(ref sub);
sid = sub.seg.hash;
if (!rayPoints.TryGetValue(sid, out intersection))
{
// Intersection has not been computed yet.
torg = f.Org();
tdest = f.Dest();
intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], tdest.y - torg.y, torg.x - tdest.x);
// Set the correct id for the vertex
intersection.id = n + rayIndex;
rayPoints.Add(sid, intersection);
points[n + rayIndex] = intersection;
rayIndex++;
}
vpoints.Add(intersection);
region.AddNeighbor(intersection.id, regions[f.Dest().id]);
// Add the new points to the region (in counter-clockwise order)
vpoints.Reverse();
region.Add(vpoints);
}
// TODO: Voronoi enumerate edges
private IEnumerable<IEdge> EnumerateEdges()
{
// Copy edges
Point first, last;
var edges = new List<IEdge>(this.Regions.Count * 2);
foreach (var region in this.Regions)
{
var ve = region.Vertices.GetEnumerator();
ve.MoveNext();
first = last = ve.Current;
while (ve.MoveNext())
{
if (region.ID < region.GetNeighbor(last).ID)
{
edges.Add(new Edge(last.id, ve.Current.id));
}
last = ve.Current;
}
if (region.Bounded && region.ID < region.GetNeighbor(last).ID)
{
edges.Add(new Edge(last.id, first.id));
}
}
return edges;
}
}
}

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