Library
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"timestamp": 1666760446,
|
||||
"signature": "lLDmhLAM4KviWa6LSqR9TuwqIDKHtzGW+5ECTr+dtXE9/OhJR30Amv9En08aJAtZhy5QnNU+0YzeZP2XBnlhl8L9eTX44gDuTZOgC44bJAhz/TH6UydLC3mRCFI93TlVndZCzXDu03Btu7/s8HhlEMribWKzoS7Uny61wt76LlumHyqzbuFhjhNi2Ifsg9Mttpsdj5gAGVcA46CKYJuSkt7pByWWs96nWmnIFCysvqbjHY2TR0ZwpQX59R+NcjggPafQqShD5SRCwIy3vCWBRw5HuP8iIL68utXWof2rD80CizpU7y3NOmy4A43D/9I4Q5Orijo80BzRa0gicIEWYPAC/xN0r/iFgXAV2UvZvm04XWvuh8xzG01wPk2EMeiZPAky0ffb1Ljp5LMboiU3KIqfO+SWTG/Vni1mt6SwtrYKnddF55rd7mZk963nkG14eQKCfyUOhs/PqYQWWxczwWbbS9olGty98CABdl30JMXE3hs+vtojY16bAZGtKYan",
|
||||
"publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg"
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
# Changelog
|
||||
|
||||
## [8.0.1] - 2022-10-11
|
||||
### Changed
|
||||
- Refactored the internal triangulation and tessellation APIs.
|
||||
|
||||
## [8.0.0] - 2022-08-03
|
||||
### Changed
|
||||
- Refactored internal triangulation and tessellation APIs.
|
||||
- Update com.unity.burst dependency version to 1.7.3 to support latest PS4 SDK.
|
||||
- Added support for different sized texture inputs in ImagePacker.
|
||||
|
||||
## [8.0.0-pre.2] - 2022-05-31
|
||||
### Added
|
||||
- Moved internal API from animation to common package.
|
||||
|
||||
## [8.0.0-pre.1] - 2022-03-21
|
||||
### Changed
|
||||
- Minimized memory allocated for UTess.
|
||||
|
||||
## [7.0.0] - 2022-01-25
|
||||
### Changed
|
||||
- Package release version.
|
||||
|
||||
### Fixed
|
||||
- 1382695 Fixed case where control point selection flickers when drag and multi-select points in scene
|
||||
- Optimized texture space needed for rect packing
|
||||
|
||||
## [7.0.0-pre.4] - 2021-11-24
|
||||
### Added
|
||||
- Added internal method to get build target's group name.
|
||||
- Added access to the internal helper method IsUsingDeformableBuffer.
|
||||
|
||||
### Fixed
|
||||
- Allow internal TextureGenerator helper consider swizzle data.
|
||||
|
||||
### Fixed
|
||||
- 1368956 Deleting certain vertices in sprite mesh leads to mesh resetted to quad incorrectly
|
||||
|
||||
## [7.0.0-pre.3] - 2021-10-21
|
||||
### Fixed
|
||||
- Fixed passing in invalid argument to TextureGenerator for swizzling.
|
||||
|
||||
## [7.0.0-pre.2] - 2021-10-11
|
||||
### Fixed
|
||||
- 1361541 Fix crash encountered when deleting vertices of sprite mesh in SkinningEditor
|
||||
|
||||
## [7.0.0-pre.1] - 2021-08-06
|
||||
### Changed
|
||||
- Update Unity supported version to 2022.1
|
||||
|
||||
## [6.0.0-pre.4] - 2021-07-05
|
||||
### Added
|
||||
- Internal API for applying Sprite Editor Window changes
|
||||
|
||||
## [6.0.0-pre.3] - 2021-05-19
|
||||
### Fixed
|
||||
- Fixed issues in tesselation library.
|
||||
|
||||
## [6.0.0-pre.2] - 2021-05-14
|
||||
### Fixed
|
||||
- Fixed metafiles conflicts
|
||||
|
||||
## [6.0.0-pre.1] - 2021-05-05
|
||||
### Changed
|
||||
- Version bump for Unity 2021.2
|
||||
|
||||
## [5.0.0] - 2021-03-17
|
||||
### Changed
|
||||
- Update version for release
|
||||
|
||||
## [5.0.0-pre.2] - 2021-01-16
|
||||
### Changed
|
||||
- Update license file
|
||||
|
||||
## [5.0.0-pre.1] - 2020-10-30
|
||||
### Changed
|
||||
- Version bump for Unity 2021.1
|
||||
|
||||
## [4.0.3] - 2020-10-15
|
||||
### Fixed
|
||||
- Allow 2D Packages to access internal constant value for asset creation instance id
|
||||
|
||||
## [4.0.2] - 2020-08-31
|
||||
### Fixed
|
||||
- Allow launching Sprite Editor Window to target a specific asset
|
||||
|
||||
## [4.0.1] - 2020-07-07
|
||||
### Fixed
|
||||
- Updated to use non-experimental AssetImporter namespace (case 1254381)
|
||||
|
||||
## [4.0.0] - 2020-05-11
|
||||
### Changed
|
||||
- Version bump for Unity 2020.2
|
||||
|
||||
## [3.0.0] - 2019-11-06
|
||||
### Changed
|
||||
- Update version number for Unity 2020.1
|
||||
|
||||
## [2.0.2] - 2019-08-09
|
||||
### Added
|
||||
- Add Seconday Texture settings into TextureSettings for TextureGenerator
|
||||
- Add related test packages
|
||||
|
||||
## [2.0.1] - 2019-07-13
|
||||
### Changed
|
||||
- Mark package to support Unity 2019.3.0a10 onwards.
|
||||
|
||||
## [2.0.0] - 2019-06-17
|
||||
### Added
|
||||
- Drop preview tag.
|
||||
- Remove experimental namespace
|
||||
|
||||
## [1.2.0-preview.2] - 2019-06-04
|
||||
### Added
|
||||
- Remove Image Packer Debug Window
|
||||
- Move tests out of package
|
||||
|
||||
## [1.2.0-preview.1] - 2019-02-20
|
||||
### Added
|
||||
- Update for Unity 2019.2 support.
|
||||
|
||||
## [1.1.0-preview.2] - 2019-03-18
|
||||
### Added
|
||||
- Remove deprecated call to Unity internal API
|
||||
|
||||
## [1.1.0-preview.1] - 2019-01-25
|
||||
### Added
|
||||
- Added versioning for CI.
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Common.Tests.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.PsdImporter.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]
|
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal interface IImagePackNodeVisitor
|
||||
{
|
||||
void Visit(ImagePackNode node);
|
||||
}
|
||||
|
||||
class CollectEmptyNodePositionVisitor : IImagePackNodeVisitor
|
||||
{
|
||||
public List<RectInt> emptyAreas = new List<RectInt>();
|
||||
public void Visit(ImagePackNode node)
|
||||
{
|
||||
if (node.imageId == -1)
|
||||
{
|
||||
emptyAreas.Add(node.rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CollectPackNodePositionVisitor : IImagePackNodeVisitor
|
||||
{
|
||||
public CollectPackNodePositionVisitor()
|
||||
{
|
||||
positions = new Vector2Int[0];
|
||||
}
|
||||
|
||||
public void Visit(ImagePackNode node)
|
||||
{
|
||||
if (node.imageId != -1)
|
||||
{
|
||||
if (positions.Length < node.imageId + 1)
|
||||
{
|
||||
var p = positions;
|
||||
Array.Resize(ref p, node.imageId + 1);
|
||||
positions = p;
|
||||
}
|
||||
|
||||
positions[node.imageId].x = node.rect.x;
|
||||
positions[node.imageId].y = node.rect.y;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2Int[] positions { get; private set; }
|
||||
}
|
||||
|
||||
internal class ImagePackNode
|
||||
{
|
||||
public ImagePackNode left;
|
||||
public ImagePackNode right;
|
||||
public RectInt rect;
|
||||
public Vector2Int imageWidth;
|
||||
public int imageId = -1;
|
||||
|
||||
public void AcceptVisitor(IImagePackNodeVisitor visitor)
|
||||
{
|
||||
visitor.Visit(this);
|
||||
if (left != null)
|
||||
left.AcceptVisitor(visitor);
|
||||
if (right != null)
|
||||
right.AcceptVisitor(visitor);
|
||||
}
|
||||
|
||||
public void AdjustSize(int oriWidth, int oriHeight, int deltaW, int deltaH, out int adjustx, out int adjusty)
|
||||
{
|
||||
adjustx = adjusty = 0;
|
||||
int adjustXleft = 0, adjustYleft = 0, adjustXRight = 0, adjustYRight = 0;
|
||||
if (imageId == -1 || left == null)
|
||||
{
|
||||
if (rect.x + rect.width == oriWidth)
|
||||
{
|
||||
rect.width += deltaW;
|
||||
adjustx = deltaW;
|
||||
}
|
||||
if (rect.y + rect.height == oriHeight)
|
||||
{
|
||||
rect.height += deltaH;
|
||||
adjusty = deltaH;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
left.AdjustSize(oriWidth, oriHeight, deltaW, deltaH, out adjustXleft, out adjustYleft);
|
||||
right.AdjustSize(oriWidth, oriHeight, deltaW, deltaH, out adjustXRight, out adjustYRight);
|
||||
|
||||
adjustx = Mathf.Max(adjustXleft, adjustXRight);
|
||||
rect.width += adjustx;
|
||||
adjusty = Mathf.Max(adjustYleft, adjustYRight);
|
||||
rect.height += adjusty;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryInsert(ImagePacker.ImagePackRect insert, int padding, out Vector2Int remainingSpace)
|
||||
{
|
||||
remainingSpace = Vector2Int.zero;
|
||||
int insertWidth = insert.rect.width + padding * 2;
|
||||
int insertHeight = insert.rect.height + padding * 2;
|
||||
if (insertWidth > rect.width || insertHeight > rect.height)
|
||||
return false;
|
||||
|
||||
if (imageId == -1)
|
||||
{
|
||||
remainingSpace.x = rect.width - insertWidth;
|
||||
remainingSpace.y = rect.height - insertHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2Int spaceLeft, spaceRight;
|
||||
bool insertLeft, insertRight;
|
||||
ImagePackNode tryLeft, tryRight;
|
||||
tryLeft = left;
|
||||
tryRight = right;
|
||||
if (left == null && !SplitRects(this, insert, padding, out tryLeft, out tryRight))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
insertLeft = tryLeft.TryInsert(insert, padding, out spaceLeft);
|
||||
insertRight = tryRight.TryInsert(insert, padding, out spaceRight);
|
||||
if (insertLeft && insertRight)
|
||||
{
|
||||
remainingSpace = spaceLeft.sqrMagnitude < spaceRight.sqrMagnitude ? spaceLeft : spaceRight;
|
||||
}
|
||||
else if (insertLeft)
|
||||
remainingSpace = spaceLeft;
|
||||
else if (insertRight)
|
||||
remainingSpace = spaceRight;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SplitRects(ImagePackNode node, ImagePacker.ImagePackRect insert, int padding, out ImagePackNode left, out ImagePackNode right)
|
||||
{
|
||||
// Find the best way to split the rect based on a new rect
|
||||
left = right = null;
|
||||
var tryRects = new[]
|
||||
{
|
||||
new ImagePackNode(), new ImagePackNode(),
|
||||
new ImagePackNode(), new ImagePackNode()
|
||||
};
|
||||
|
||||
tryRects[0].rect = new RectInt(node.rect.x + node.imageWidth.x, node.rect.y, node.rect.width - node.imageWidth.x, node.rect.height);
|
||||
tryRects[1].rect = new RectInt(node.rect.x, node.rect.y + node.imageWidth.y, node.imageWidth.x, node.rect.height - node.imageWidth.y);
|
||||
tryRects[2].rect = new RectInt(node.rect.x, node.rect.y + node.imageWidth.y, node.rect.width, node.rect.height - node.imageWidth.y);
|
||||
tryRects[3].rect = new RectInt(node.rect.x + node.imageWidth.x, node.rect.y, node.rect.width - node.imageWidth.x, node.imageWidth.y);
|
||||
float smallestSpace = float.MinValue;
|
||||
for (int i = 0; i < tryRects.GetLength(0); ++i)
|
||||
{
|
||||
//for (int j = 0; j < tryRects.GetLength(1); ++j)
|
||||
{
|
||||
Vector2Int newSpaceLeft;
|
||||
if (tryRects[i].TryInsert(insert, padding, out newSpaceLeft))
|
||||
{
|
||||
if (smallestSpace < newSpaceLeft.sqrMagnitude)
|
||||
{
|
||||
smallestSpace = newSpaceLeft.sqrMagnitude;
|
||||
int index = i / 2 * 2;
|
||||
left = tryRects[index];
|
||||
right = tryRects[index + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return left != null;
|
||||
}
|
||||
|
||||
public bool Insert(ImagePacker.ImagePackRect insert, int padding)
|
||||
{
|
||||
int insertWidth = insert.rect.width + padding * 2;
|
||||
int insertHeight = insert.rect.height + padding * 2;
|
||||
if (insertWidth > rect.width || insertHeight > rect.height)
|
||||
return false;
|
||||
|
||||
if (imageId == -1)
|
||||
{
|
||||
imageId = insert.index;
|
||||
imageWidth = new Vector2Int(insertWidth, insertHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (left == null && !SplitRects(this, insert, padding, out left, out right))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// We assign to the node that has a better fit for the image
|
||||
Vector2Int spaceLeft, spaceRight;
|
||||
bool insertLeft, insertRight;
|
||||
insertLeft = left.TryInsert(insert, padding, out spaceLeft);
|
||||
insertRight = right.TryInsert(insert, padding, out spaceRight);
|
||||
if (insertLeft && insertRight)
|
||||
{
|
||||
if (spaceLeft.sqrMagnitude < spaceRight.sqrMagnitude)
|
||||
left.Insert(insert, padding);
|
||||
else
|
||||
right.Insert(insert, padding);
|
||||
}
|
||||
else if (insertLeft)
|
||||
left.Insert(insert, padding);
|
||||
else if (insertRight)
|
||||
right.Insert(insert, padding);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
//#define PACKING_DEBUG
|
||||
|
||||
using System;
|
||||
using Unity.Burst;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
[BurstCompile]
|
||||
internal static class ImagePacker
|
||||
{
|
||||
/// <summary>
|
||||
/// Given an array of rects, the method returns an array of rects arranged within outPackedWidth and outPackedHeight
|
||||
/// </summary>
|
||||
/// <param name="rects">Rects to pack</param>
|
||||
/// <param name="padding">Padding between each rect</param>
|
||||
/// <param name="outPackedRects">Rects arranged within outPackedWidth and outPackedHeight</param>
|
||||
/// <param name="outPackedWidth">Width of the packed rects</param>
|
||||
/// <param name="outPackedHeight">Height of the packed rects</param>
|
||||
public static void Pack(RectInt[] rects, int padding, out RectInt[] outPackedRects, out int outPackedWidth, out int outPackedHeight)
|
||||
{
|
||||
var packNode = InternalPack(rects, padding);
|
||||
outPackedWidth = packNode.rect.width;
|
||||
outPackedHeight = packNode.rect.height;
|
||||
var visitor = new CollectPackNodePositionVisitor();
|
||||
packNode.AcceptVisitor(visitor);
|
||||
|
||||
outPackedRects = new RectInt[rects.Length];
|
||||
for (int i = 0; i < rects.Length; ++i)
|
||||
outPackedRects[i] = new RectInt(visitor.positions[i].x + padding, visitor.positions[i].y + padding, rects[i].width, rects[i].height);
|
||||
#if PACKING_DEBUG
|
||||
var emptyNodeCollector = new CollectEmptyNodePositionVisitor();
|
||||
packNode.AcceptVisitor(emptyNodeCollector);
|
||||
Array.Resize(ref outPackedRects, rects.Length + emptyNodeCollector.emptyAreas.Count);
|
||||
for (int i = rects.Length; i < outPackedRects.Length; ++i)
|
||||
outPackedRects[i] = emptyNodeCollector.emptyAreas[i - rects.Length];
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs image buffer into a single buffer. Image buffers are assumed to be 4 bytes per pixel in RGBA format
|
||||
/// </summary>
|
||||
/// <param name="buffers">Image buffers to pack</param>
|
||||
/// <param name="width">Image buffers width</param>
|
||||
/// <param name="height">Image buffers height</param>
|
||||
/// <param name="padding">Padding between each packed image</param>
|
||||
/// <param name="spriteSizeExpand">Pack sprite expand size</param>
|
||||
/// <param name="outPackedBuffer">Packed image buffer</param>
|
||||
/// <param name="outPackedBufferWidth">Packed image buffer's width</param>
|
||||
/// <param name="outPackedBufferHeight">Packed image buffer's height</param>
|
||||
/// <param name="outPackedRect">Location of each image buffers in the packed buffer</param>
|
||||
/// <param name="outUVTransform">Translation data from image original buffer to packed buffer</param>
|
||||
public static void Pack(NativeArray<Color32>[] buffers, int[] width, int[] height, int padding, uint spriteSizeExpand, out NativeArray<Color32> outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("Pack");
|
||||
// Determine the area that contains data in the buffer
|
||||
outPackedBuffer = default(NativeArray<Color32>);
|
||||
try
|
||||
{
|
||||
var tightRects = FindTightRectJob.Execute(buffers, width, height);
|
||||
var tightRectArea = new RectInt[tightRects.Length];
|
||||
for (var i = 0; i < tightRects.Length; ++i)
|
||||
{
|
||||
var t = tightRects[i];
|
||||
t.width = tightRects[i].width + (int)spriteSizeExpand;
|
||||
t.height = tightRects[i].height + (int)spriteSizeExpand;
|
||||
tightRectArea[i] = t;
|
||||
}
|
||||
Pack(tightRectArea, padding, out outPackedRect, out outPackedBufferWidth, out outPackedBufferHeight);
|
||||
var packBufferSize = (ulong)outPackedBufferWidth * (ulong)outPackedBufferHeight;
|
||||
|
||||
if (packBufferSize < 0 || packBufferSize >= int.MaxValue)
|
||||
{
|
||||
throw new ArgumentException("Unable to create pack texture. Image size is too big to pack.");
|
||||
}
|
||||
outUVTransform = new Vector2Int[tightRectArea.Length];
|
||||
for (var i = 0; i < outUVTransform.Length; ++i)
|
||||
{
|
||||
outUVTransform[i] = new Vector2Int(outPackedRect[i].x - tightRects[i].x, outPackedRect[i].y - tightRects[i].y);
|
||||
}
|
||||
outPackedBuffer = new NativeArray<Color32>(outPackedBufferWidth * outPackedBufferHeight, Allocator.Persistent);
|
||||
|
||||
Blit(outPackedBuffer, outPackedRect, outPackedBufferWidth, buffers, tightRects, width, padding);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (outPackedBuffer.IsCreated)
|
||||
outPackedBuffer.Dispose();
|
||||
throw ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
static ImagePackNode InternalPack(RectInt[] rects, int padding)
|
||||
{
|
||||
if (rects == null || rects.Length == 0)
|
||||
return new ImagePackNode() { rect = new RectInt(0, 0, 0, 0)};
|
||||
var sortedRects = new ImagePackRect[rects.Length];
|
||||
for (int i = 0; i < rects.Length; ++i)
|
||||
{
|
||||
sortedRects[i] = new ImagePackRect();
|
||||
sortedRects[i].rect = rects[i];
|
||||
sortedRects[i].index = i;
|
||||
}
|
||||
var initialHeight = (int)NextPowerOfTwo((ulong)rects[0].height);
|
||||
Array.Sort<ImagePackRect>(sortedRects);
|
||||
var root = new ImagePackNode();
|
||||
root.rect = new RectInt(0, 0, (int)NextPowerOfTwo((ulong)rects[0].width), initialHeight);
|
||||
|
||||
for (int i = 0; i < rects.Length; ++i)
|
||||
{
|
||||
if (!root.Insert(sortedRects[i], padding)) // we can't fit
|
||||
{
|
||||
int newWidth = root.rect.width , newHeight = root.rect.height;
|
||||
if (root.rect.width < root.rect.height)
|
||||
{
|
||||
newWidth = (int)NextPowerOfTwo((ulong)root.rect.width + 1);
|
||||
// Every time height changes, we reset height to grow again.
|
||||
newHeight = initialHeight;
|
||||
}
|
||||
else
|
||||
newHeight = (int)NextPowerOfTwo((ulong)root.rect.height + 1);
|
||||
// Reset all packing and try again
|
||||
root = new ImagePackNode();
|
||||
root.rect = new RectInt(0, 0, newWidth, newHeight);
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
public static void Blit(NativeArray<Color32> buffer, RectInt[] blitToArea, int bufferBytesPerRow, NativeArray<Color32>[] originalBuffer, RectInt[] blitFromArea, int[] bytesPerRow, int padding)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample("Blit");
|
||||
|
||||
for (var bufferIndex = 0; bufferIndex < blitToArea.Length && bufferIndex < originalBuffer.Length && bufferIndex < blitFromArea.Length; ++bufferIndex)
|
||||
{
|
||||
var fromArea = new int4(blitFromArea[bufferIndex].x, blitFromArea[bufferIndex].y, blitFromArea[bufferIndex].width, blitFromArea[bufferIndex].height);
|
||||
var toArea = new int4(blitToArea[bufferIndex].x, blitToArea[bufferIndex].y, blitToArea[bufferIndex].width, blitToArea[bufferIndex].height);
|
||||
BurstedBlit(originalBuffer[bufferIndex], in fromArea, in toArea, bytesPerRow[bufferIndex], bufferBytesPerRow, ref buffer);
|
||||
}
|
||||
|
||||
#if PACKING_DEBUG
|
||||
var emptyColors = new Color32[]
|
||||
{
|
||||
new Color32((byte)255, (byte)0, (byte)0, (byte)255),
|
||||
new Color32((byte)255, (byte)255, (byte)0, (byte)255),
|
||||
new Color32((byte)255, (byte)0, (byte)255, (byte)255),
|
||||
new Color32((byte)255, (byte)255, (byte)255, (byte)255),
|
||||
new Color32((byte)0, (byte)255, (byte)0, (byte)255),
|
||||
new Color32((byte)0, (byte)0, (byte)255, (byte)255)
|
||||
};
|
||||
|
||||
for (int k = originalBuffer.Length; k < blitToArea.Length; ++k)
|
||||
{
|
||||
var rectFrom = blitToArea[k];
|
||||
for (int i = 0; i < rectFrom.height; ++i)
|
||||
{
|
||||
for (int j = 0; j < rectFrom.width; ++j)
|
||||
{
|
||||
c[((rectFrom.y + i) * bufferbytesPerRow) + rectFrom.x + j] =
|
||||
emptyColors[k % emptyColors.Length];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
static unsafe void BurstedBlit(in NativeArray<Color32> originalBuffer, in int4 rectFrom, in int4 rectTo, int bytesPerRow, int bufferBytesPerRow, ref NativeArray<Color32> buffer)
|
||||
{
|
||||
var c = (Color32*)buffer.GetUnsafePtr();
|
||||
|
||||
var b = (Color32*)originalBuffer.GetUnsafeReadOnlyPtr();
|
||||
var toXStart = (int)(rectTo.z * 0.5f - rectFrom.z * 0.5f);
|
||||
var toYStart = (int)(rectTo.w * 0.5f - rectFrom.w * 0.5f);
|
||||
toXStart = toXStart <= 0 ? rectTo.x : toXStart + rectTo.x;
|
||||
toYStart = toYStart <= 0 ? rectTo.y : toYStart + rectTo.y;
|
||||
for (var i = 0; i < rectFrom.w && i < rectTo.w; ++i)
|
||||
{
|
||||
for (var j = 0; j < rectFrom.z && j < rectTo.z; ++j)
|
||||
{
|
||||
var cc = b[(rectFrom.y + i) * bytesPerRow + rectFrom.x + j];
|
||||
c[((toYStart + i) * bufferBytesPerRow) + toXStart + j] = cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static ulong NextPowerOfTwo(ulong v)
|
||||
{
|
||||
v -= 1;
|
||||
v |= v >> 16;
|
||||
v |= v >> 8;
|
||||
v |= v >> 4;
|
||||
v |= v >> 2;
|
||||
v |= v >> 1;
|
||||
return v + 1;
|
||||
}
|
||||
|
||||
internal class ImagePackRect : IComparable<ImagePackRect>
|
||||
{
|
||||
public RectInt rect;
|
||||
public int index;
|
||||
|
||||
public int CompareTo(ImagePackRect obj)
|
||||
{
|
||||
var lhsArea = rect.width * rect.height;
|
||||
var rhsArea = obj.rect.width * obj.rect.height;
|
||||
if (lhsArea > rhsArea)
|
||||
return -1;
|
||||
if (lhsArea < rhsArea)
|
||||
return 1;
|
||||
if (index < obj.index)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.U2D.Sprites;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal class ImagePackerDebugEditor : EditorWindow
|
||||
{
|
||||
[MenuItem("internal:Window/2D/Common/Image Packer Debug Editor")]
|
||||
static void Launch()
|
||||
{
|
||||
var window = EditorWindow.GetWindow<ImagePackerDebugEditor>();
|
||||
var pos = window.position;
|
||||
pos.height = pos.width = 400;
|
||||
window.position = pos;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
ReorderableList m_ReorderableList;
|
||||
ImagePacker.ImagePackRect[] m_PackingRect = null;
|
||||
List<RectInt> m_PackRects = new List<RectInt>();
|
||||
RectInt[] m_PackResult = null;
|
||||
SpriteRect[] m_SpriteRects = null;
|
||||
Texture2D m_Texture;
|
||||
int m_TextureActualWidth = 0;
|
||||
int m_TextureActualHeight = 0;
|
||||
int m_PackWidth = 0;
|
||||
int m_PackHeight = 0;
|
||||
int m_Padding = 0;
|
||||
Vector2 m_ConfigScroll = Vector2.zero;
|
||||
float m_Zoom = 1;
|
||||
IMGUIContainer m_PackArea;
|
||||
int m_PackStep = -1;
|
||||
protected const float k_MinZoomPercentage = 0.9f;
|
||||
protected const float k_WheelZoomSpeed = 0.03f;
|
||||
protected const float k_MouseZoomSpeed = 0.005f;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
var visualContainer = new VisualElement()
|
||||
{
|
||||
name = "Container",
|
||||
style =
|
||||
{
|
||||
flexGrow = 1,
|
||||
flexDirection = FlexDirection.Row
|
||||
}
|
||||
};
|
||||
this.rootVisualElement.Add(visualContainer);
|
||||
|
||||
var imgui = new IMGUIContainer(OnConfigGUI)
|
||||
{
|
||||
name = "Config",
|
||||
style =
|
||||
{
|
||||
width = 300
|
||||
}
|
||||
};
|
||||
|
||||
visualContainer.Add(imgui);
|
||||
|
||||
m_PackArea = new IMGUIContainer(OnImagePackerGUI)
|
||||
{
|
||||
name = "ImagePacker",
|
||||
style =
|
||||
{
|
||||
flexGrow = 1,
|
||||
}
|
||||
};
|
||||
visualContainer.Add(m_PackArea);
|
||||
SetupConfigGUI();
|
||||
}
|
||||
|
||||
void SetupConfigGUI()
|
||||
{
|
||||
m_ReorderableList = new ReorderableList(m_PackRects, typeof(RectInt), false, false, true, true);
|
||||
m_ReorderableList.elementHeightCallback = (int index) =>
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight * 2 + 6;
|
||||
};
|
||||
m_ReorderableList.drawElementCallback = DrawListElement;
|
||||
|
||||
m_ReorderableList.onAddCallback = (list) =>
|
||||
{
|
||||
m_PackRects.Add(new RectInt());
|
||||
};
|
||||
m_ReorderableList.onRemoveCallback = (list) =>
|
||||
{
|
||||
m_PackRects.RemoveAt(list.index);
|
||||
};
|
||||
}
|
||||
|
||||
void DrawListElement(Rect rect, int index, bool isactive, bool isfocused)
|
||||
{
|
||||
var rectInt = m_PackRects[index];
|
||||
var name = m_SpriteRects == null || index >= m_SpriteRects.Length ? index.ToString() : m_SpriteRects[index].
|
||||
name;
|
||||
rectInt.size = EditorGUI.Vector2IntField(rect, name, rectInt.size);
|
||||
m_PackRects[index] = rectInt;
|
||||
}
|
||||
|
||||
void OnConfigGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
m_ConfigScroll = EditorGUILayout.BeginScrollView(m_ConfigScroll);
|
||||
m_ReorderableList.DoLayoutList();
|
||||
EditorGUILayout.EndScrollView();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
m_PackStep = EditorGUILayout.IntSlider("Step", m_PackStep, 0, m_PackRects.Count);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_Texture = EditorGUILayout.ObjectField(new GUIContent("Texture"), (Object)m_Texture, typeof(Texture2D), false) as Texture2D;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
UpdateSpriteRect();
|
||||
m_Padding = EditorGUILayout.IntField("Padding", m_Padding);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("<<"))
|
||||
{
|
||||
m_PackStep = m_PackStep <= 0 ? 0 : m_PackStep - 1;
|
||||
Pack();
|
||||
}
|
||||
if (GUILayout.Button("Pack"))
|
||||
Pack();
|
||||
if (GUILayout.Button(">>"))
|
||||
{
|
||||
m_PackStep = m_PackStep > m_PackRects.Count ? m_PackRects.Count : m_PackStep + 1;
|
||||
Pack();
|
||||
}
|
||||
if (GUILayout.Button("Clear"))
|
||||
{
|
||||
m_PackRects.Clear();
|
||||
m_Texture = null;
|
||||
m_PackingRect = null;
|
||||
m_PackResult = null;
|
||||
m_SpriteRects = null;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void UpdateSpriteRect()
|
||||
{
|
||||
var dataProvider = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(m_Texture)) as ISpriteEditorDataProvider;
|
||||
if (dataProvider == null)
|
||||
return;
|
||||
dataProvider.InitSpriteEditorDataProvider();
|
||||
dataProvider.GetDataProvider<ITextureDataProvider>().GetTextureActualWidthAndHeight(out m_TextureActualWidth, out m_TextureActualHeight);
|
||||
m_SpriteRects = dataProvider.GetDataProvider<ISpriteEditorDataProvider>().GetSpriteRects();
|
||||
m_PackRects.Clear();
|
||||
m_PackRects.AddRange(m_SpriteRects.Select(x => new RectInt((int)x.rect.x, (int)x.rect.y, (int)x.rect.width, (int)x.rect.height)));
|
||||
m_PackResult = null;
|
||||
m_PackStep = m_PackRects.Count;
|
||||
}
|
||||
|
||||
void Pack()
|
||||
{
|
||||
int count = m_PackStep > 0 && m_PackStep < m_PackRects.Count ? m_PackStep : m_PackRects.Count;
|
||||
m_PackingRect = new ImagePacker.ImagePackRect[m_PackRects.Count];
|
||||
for (int i = 0; i < m_PackRects.Count; ++i)
|
||||
{
|
||||
m_PackingRect[i] = new ImagePacker.ImagePackRect()
|
||||
{
|
||||
rect = m_PackRects[i],
|
||||
index = i
|
||||
};
|
||||
}
|
||||
Array.Sort(m_PackingRect);
|
||||
ImagePacker.Pack(m_PackingRect.Take(count).Select(x => x.rect).ToArray(), m_Padding, out m_PackResult, out m_PackWidth, out m_PackHeight);
|
||||
}
|
||||
|
||||
void DrawLabel(Rect rect, string label)
|
||||
{
|
||||
rect.position = Handles.matrix.MultiplyPoint(rect.position);
|
||||
GUI.Label(rect, label);
|
||||
}
|
||||
|
||||
void OnImagePackerGUI()
|
||||
{
|
||||
if (m_PackResult == null)
|
||||
return;
|
||||
HandleZoom();
|
||||
var oldMatrix = Handles.matrix;
|
||||
SetupHandlesMatrix();
|
||||
Handles.DrawSolidRectangleWithOutline(new Rect(0, 0, m_PackWidth, m_PackHeight), Color.gray, Color.black);
|
||||
DrawLabel(new Rect(0, 0, m_PackWidth, m_PackHeight), m_PackWidth + "x" + m_PackHeight);
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var rect in m_PackResult)
|
||||
{
|
||||
Handles.DrawSolidRectangleWithOutline(new Rect(rect.x, rect.y, rect.width, rect.height), Color.white, Color.black);
|
||||
var rect1 = new Rect(rect.x, rect.y + rect.height * 0.5f, rect.width, EditorGUIUtility.singleLineHeight);
|
||||
DrawLabel(rect1, m_PackingRect[index].index.ToString());
|
||||
++index;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
if (m_Texture != null && m_SpriteRects != null)
|
||||
{
|
||||
var material = new Material(Shader.Find("Sprites/Default"));
|
||||
material.mainTexture = m_Texture;
|
||||
material.SetPass(0);
|
||||
|
||||
int mouseOverIndex = -1;
|
||||
GL.PushMatrix();
|
||||
GL.LoadIdentity();
|
||||
GL.MultMatrix(GUI.matrix * Handles.matrix);
|
||||
GL.Begin(GL.QUADS);
|
||||
for (int i = 0; i < m_PackResult.Length; ++i)
|
||||
{
|
||||
index = m_PackingRect[i].index;
|
||||
if (index >= m_SpriteRects.Length)
|
||||
continue;
|
||||
var rect = m_PackResult[i];
|
||||
GL.TexCoord(new Vector3(m_SpriteRects[index].rect.x / m_TextureActualWidth, m_SpriteRects[index].rect.y / m_TextureActualHeight, 0));
|
||||
GL.Vertex(new Vector3(rect.x, rect.y, 0));
|
||||
GL.TexCoord(new Vector3(m_SpriteRects[index].rect.xMax / m_TextureActualWidth, m_SpriteRects[index].rect.y / m_TextureActualHeight, 0));
|
||||
GL.Vertex(new Vector3(rect.x + rect.width, rect.y, 0));
|
||||
GL.TexCoord(new Vector3(m_SpriteRects[index].rect.xMax / m_TextureActualWidth, m_SpriteRects[index].rect.yMax / m_TextureActualHeight, 0));
|
||||
GL.Vertex(new Vector3(rect.x + rect.width, rect.y + rect.height, 0));
|
||||
GL.TexCoord(new Vector3(m_SpriteRects[index].rect.x / m_TextureActualWidth, m_SpriteRects[index].rect.yMax / m_TextureActualHeight, 0));
|
||||
GL.Vertex(new Vector3(rect.x, rect.y + rect.height, 0));
|
||||
var m = Handles.matrix.inverse.MultiplyPoint(Event.current.mousePosition);
|
||||
if (rect.Contains(new Vector2Int((int)m.x, (int)m.y)))
|
||||
{
|
||||
mouseOverIndex = index;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
if (mouseOverIndex >= 0)
|
||||
{
|
||||
var text = new GUIContent(m_SpriteRects[mouseOverIndex].name + " " + index);
|
||||
var length = EditorStyles.textArea.CalcSize(text);
|
||||
var rect1 = new Rect(m_PackResult[mouseOverIndex].x, m_PackResult[mouseOverIndex].y + m_PackResult[mouseOverIndex].height * 0.5f, length.x, length.y);
|
||||
rect1.position = Handles.matrix.MultiplyPoint(rect1.position);
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
EditorStyles.textArea.Draw(rect1, text, false, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
Handles.matrix = oldMatrix;
|
||||
}
|
||||
|
||||
void SetupHandlesMatrix()
|
||||
{
|
||||
Vector3 handlesPos = new Vector3(0, m_PackHeight * m_Zoom, 0f);
|
||||
Vector3 handlesScale = new Vector3(m_Zoom, -m_Zoom, 1f);
|
||||
Handles.matrix = Matrix4x4.TRS(handlesPos, Quaternion.identity, handlesScale);
|
||||
}
|
||||
|
||||
protected void HandleZoom()
|
||||
{
|
||||
bool zoomMode = Event.current.alt && Event.current.button == 1;
|
||||
if (zoomMode)
|
||||
{
|
||||
EditorGUIUtility.AddCursorRect(m_PackArea.worldBound, MouseCursor.Zoom);
|
||||
}
|
||||
|
||||
if (
|
||||
((Event.current.type == EventType.MouseUp || Event.current.type == EventType.MouseDown) && zoomMode) ||
|
||||
((Event.current.type == EventType.KeyUp || Event.current.type == EventType.KeyDown) && Event.current.keyCode == KeyCode.LeftAlt)
|
||||
)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.ScrollWheel || (Event.current.type == EventType.MouseDrag && Event.current.alt && Event.current.button == 1))
|
||||
{
|
||||
float zoomMultiplier = 1f - Event.current.delta.y * (Event.current.type == EventType.ScrollWheel ? k_WheelZoomSpeed : -k_MouseZoomSpeed);
|
||||
|
||||
// Clamp zoom
|
||||
float wantedZoom = m_Zoom * zoomMultiplier;
|
||||
|
||||
float currentZoom = Mathf.Clamp(wantedZoom, GetMinZoom(), 1);
|
||||
|
||||
if (currentZoom != m_Zoom)
|
||||
{
|
||||
m_Zoom = currentZoom;
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected float GetMinZoom()
|
||||
{
|
||||
if (m_Texture == null)
|
||||
return 1.0f;
|
||||
return Mathf.Min(m_PackArea.worldBound.width / m_PackWidth, m_PackArea.worldBound.height / m_PackHeight, 0.05f) * k_MinZoomPercentage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using Unity.Burst;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
[BurstCompile]
|
||||
internal struct FindTightRectJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly, DeallocateOnJobCompletion]
|
||||
NativeArray<IntPtr> m_Buffers;
|
||||
[ReadOnly, DeallocateOnJobCompletion]
|
||||
NativeArray<int> m_Width;
|
||||
[ReadOnly, DeallocateOnJobCompletion]
|
||||
NativeArray<int> m_Height;
|
||||
NativeArray<RectInt> m_Output;
|
||||
|
||||
public unsafe void Execute(int index)
|
||||
{
|
||||
var rect = new RectInt(m_Width[index], m_Height[index], 0, 0);
|
||||
|
||||
if (m_Height[index] == 0 || m_Width[index] == 0)
|
||||
{
|
||||
m_Output[index] = rect;
|
||||
return;
|
||||
}
|
||||
|
||||
var color = (Color32*)m_Buffers[index].ToPointer();
|
||||
for (int i = 0; i < m_Height[index]; ++i)
|
||||
{
|
||||
for (int j = 0; j < m_Width[index]; ++j)
|
||||
{
|
||||
if (color->a != 0)
|
||||
{
|
||||
rect.x = Mathf.Min(j, rect.x);
|
||||
rect.y = Mathf.Min(i, rect.y);
|
||||
rect.width = Mathf.Max(j, rect.width);
|
||||
rect.height = Mathf.Max(i, rect.height);
|
||||
}
|
||||
++color;
|
||||
}
|
||||
}
|
||||
rect.width = Mathf.Max(0, rect.width - rect.x + 1);
|
||||
rect.height = Mathf.Max(0, rect.height - rect.y + 1);
|
||||
m_Output[index] = rect;
|
||||
}
|
||||
|
||||
public static unsafe RectInt[] Execute(NativeArray<Color32>[] buffers, int[] width, int[] height)
|
||||
{
|
||||
var job = new FindTightRectJob();
|
||||
job.m_Buffers = new NativeArray<IntPtr>(buffers.Length, Allocator.TempJob);
|
||||
job.m_Width = new NativeArray<int>(width.Length, Allocator.TempJob);
|
||||
job.m_Height = new NativeArray<int>(height.Length, Allocator.TempJob);
|
||||
|
||||
for (var i = 0; i < buffers.Length; ++i)
|
||||
{
|
||||
job.m_Buffers[i] = new IntPtr(buffers[i].GetUnsafeReadOnlyPtr());
|
||||
job.m_Width[i] = width[i];
|
||||
job.m_Height[i] = height[i];
|
||||
}
|
||||
|
||||
job.m_Output = new NativeArray<RectInt>(buffers.Length, Allocator.TempJob);
|
||||
|
||||
// Ensure all jobs are completed before we return since we don't own the buffers
|
||||
job.Schedule(buffers.Length, 1).Complete();
|
||||
var rects = job.m_Output.ToArray();
|
||||
job.m_Output.Dispose();
|
||||
return rects;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities to draw foldout headers in Inspector
|
||||
/// </summary>
|
||||
internal class InspectorUtils
|
||||
{
|
||||
/// <summary>Draw a splitter separator</summary>
|
||||
/// <param name="isBoxed">[Optional] add margin if the splitter is boxed</param>
|
||||
public static void DrawSplitter(bool isBoxed = false)
|
||||
{
|
||||
var rect = GUILayoutUtility.GetRect(1f, 1f);
|
||||
float xMin = rect.xMin;
|
||||
|
||||
// Splitter rect should be full-width
|
||||
rect.xMin = 0f;
|
||||
rect.width += 4f;
|
||||
|
||||
if (isBoxed)
|
||||
{
|
||||
rect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight;
|
||||
rect.width -= 1;
|
||||
}
|
||||
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
EditorGUI.DrawRect(rect, !EditorGUIUtility.isProSkin
|
||||
? new Color(0.6f, 0.6f, 0.6f, 1.333f)
|
||||
: new Color(0.12f, 0.12f, 0.12f, 1.333f));
|
||||
}
|
||||
|
||||
/// <summary>Draw a header</summary>
|
||||
/// <param name="title">Title of the header</param>
|
||||
public static void DrawHeader(string title)
|
||||
=> DrawHeader(EditorGUIUtility.TrTextContent(title));
|
||||
|
||||
/// <summary>Draw a header</summary>
|
||||
/// <param name="title">Title of the header</param>
|
||||
public static void DrawHeader(GUIContent title)
|
||||
{
|
||||
var backgroundRect = GUILayoutUtility.GetRect(1f, 17f);
|
||||
|
||||
var labelRect = backgroundRect;
|
||||
labelRect.xMax -= 20f;
|
||||
|
||||
var foldoutRect = backgroundRect;
|
||||
foldoutRect.y += 1f;
|
||||
foldoutRect.width = 13f;
|
||||
foldoutRect.height = 13f;
|
||||
|
||||
// Background rect should be full-width
|
||||
backgroundRect.xMin = 0f;
|
||||
backgroundRect.width += 4f;
|
||||
|
||||
// Background
|
||||
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f;
|
||||
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f));
|
||||
|
||||
// Title
|
||||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
/// <summary> Draw a foldout header </summary>
|
||||
/// <param name="title"> The title of the header </param>
|
||||
/// <param name="state"> The state of the header </param>
|
||||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
||||
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
||||
/// <param name="toggleMoreOption"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
||||
/// <returns>return the state of the foldout header</returns>
|
||||
public static bool DrawHeaderFoldout(string title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOption = null)
|
||||
=> DrawHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOption);
|
||||
|
||||
/// <summary> Draw a foldout header </summary>
|
||||
/// <param name="title"> The title of the header </param>
|
||||
/// <param name="state"> The state of the header </param>
|
||||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
||||
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
||||
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
||||
/// <returns>return the state of the foldout header</returns>
|
||||
public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
|
||||
{
|
||||
const float height = 17f;
|
||||
var backgroundRect = GUILayoutUtility.GetRect(1f, height);
|
||||
float xMin = backgroundRect.xMin;
|
||||
|
||||
var labelRect = backgroundRect;
|
||||
labelRect.xMin += 16f;
|
||||
labelRect.xMax -= 20f;
|
||||
|
||||
var foldoutRect = backgroundRect;
|
||||
foldoutRect.y += 1f;
|
||||
foldoutRect.width = 13f;
|
||||
foldoutRect.height = 13f;
|
||||
foldoutRect.x = labelRect.xMin + 15 * (EditorGUI.indentLevel - 1); //fix for presset
|
||||
|
||||
// More options 1/2
|
||||
var moreOptionsRect = new Rect();
|
||||
if (hasMoreOptions != null)
|
||||
{
|
||||
moreOptionsRect = backgroundRect;
|
||||
moreOptionsRect.x += moreOptionsRect.width - 16 - 1;
|
||||
moreOptionsRect.height = 15;
|
||||
moreOptionsRect.width = 16;
|
||||
}
|
||||
|
||||
// Background rect should be full-width
|
||||
backgroundRect.xMin = 0f;
|
||||
backgroundRect.width += 4f;
|
||||
|
||||
if (isBoxed)
|
||||
{
|
||||
labelRect.xMin += 5;
|
||||
foldoutRect.xMin += 5;
|
||||
backgroundRect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight;
|
||||
backgroundRect.width -= 1;
|
||||
}
|
||||
|
||||
// Background
|
||||
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f;
|
||||
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f));
|
||||
|
||||
// Title
|
||||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
|
||||
|
||||
// Active checkbox
|
||||
state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout);
|
||||
|
||||
var e = Event.current;
|
||||
if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && !moreOptionsRect.Contains(e.mousePosition) && e.button == 0)
|
||||
{
|
||||
state = !state;
|
||||
e.Use();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Tests.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.PsdImporter.Editor")]
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "Unity.InternalAPIEditorBridge.001",
|
||||
"references": [
|
||||
"Unity.2D.Sprite.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEditor.U2D.Sprites;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal static class InternalEditorBridge
|
||||
{
|
||||
public static void RenderSortingLayerFields(SerializedProperty order, SerializedProperty layer)
|
||||
{
|
||||
SortingLayerEditorUtility.RenderSortingLayerFields(order, layer);
|
||||
}
|
||||
|
||||
public static void RepaintImmediately(EditorWindow window)
|
||||
{
|
||||
window.RepaintImmediately();
|
||||
}
|
||||
|
||||
public static ISpriteEditorDataProvider GetISpriteEditorDataProviderFromPath(string importedAsset)
|
||||
{
|
||||
return AssetImporter.GetAtPath(importedAsset) as ISpriteEditorDataProvider;
|
||||
}
|
||||
|
||||
public static void GenerateOutline(Texture2D texture, Rect rect, float detail, byte alphaTolerance, bool holeDetection, out Vector2[][] paths)
|
||||
{
|
||||
UnityEditor.Sprites.SpriteUtility.GenerateOutline(texture, rect, detail, alphaTolerance, holeDetection, out paths);
|
||||
}
|
||||
|
||||
public static void GenerateOutlineFromSprite(Sprite sprite, float detail, byte alphaTolerance, bool holeDetection, out Vector2[][] paths)
|
||||
{
|
||||
UnityEditor.Sprites.SpriteUtility.GenerateOutlineFromSprite(sprite, detail, alphaTolerance, holeDetection, out paths);
|
||||
}
|
||||
|
||||
public static bool DoesHardwareSupportsFullNPOT()
|
||||
{
|
||||
return ShaderUtil.hardwareSupportsFullNPOT;
|
||||
}
|
||||
|
||||
public static Texture2D CreateTemporaryDuplicate(Texture2D tex, int width, int height)
|
||||
{
|
||||
return UnityEditor.SpriteUtility.CreateTemporaryDuplicate(tex, width, height);
|
||||
}
|
||||
|
||||
public static void ShowSpriteEditorWindow(UnityEngine.Object obj = null)
|
||||
{
|
||||
SpriteUtilityWindow.ShowSpriteEditorWindow(obj);
|
||||
}
|
||||
|
||||
public static void ApplySpriteEditorWindow()
|
||||
{
|
||||
SpriteUtilityWindow.ApplySpriteEditorWindow();
|
||||
}
|
||||
|
||||
public static void ApplyWireMaterial()
|
||||
{
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
}
|
||||
|
||||
public static void ResetSpriteEditorView(ISpriteEditor spriteEditor)
|
||||
{
|
||||
if (spriteEditor != null)
|
||||
{
|
||||
Type t = spriteEditor.GetType();
|
||||
var zoom = t.GetField("m_Zoom", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (zoom != null)
|
||||
{
|
||||
zoom.SetValue(spriteEditor, -1);
|
||||
}
|
||||
|
||||
var scrollPosition = t.GetField("m_ScrollPosition", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (scrollPosition != null)
|
||||
{
|
||||
scrollPosition.SetValue(spriteEditor, new Vector2());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ShortcutContext : IShortcutToolContext
|
||||
{
|
||||
public Func<bool> isActive;
|
||||
public bool active
|
||||
{
|
||||
get
|
||||
{
|
||||
if (isActive != null)
|
||||
return isActive();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public object context { get; set; }
|
||||
}
|
||||
|
||||
public static void RegisterShortcutContext(ShortcutContext context)
|
||||
{
|
||||
ShortcutIntegration.instance.contextManager.RegisterToolContext(context);
|
||||
}
|
||||
|
||||
public static void UnregisterShortcutContext(ShortcutContext context)
|
||||
{
|
||||
ShortcutIntegration.instance.contextManager.DeregisterToolContext(context);
|
||||
}
|
||||
|
||||
public static void AddEditorApplicationProjectLoadedCallback(UnityAction callback)
|
||||
{
|
||||
EditorApplication.projectWasLoaded += callback;
|
||||
}
|
||||
|
||||
public static void RemoveEditorApplicationProjectLoadedCallback(UnityAction callback)
|
||||
{
|
||||
EditorApplication.projectWasLoaded -= callback;
|
||||
}
|
||||
|
||||
public static string GetProjectWindowActiveFolderPath()
|
||||
{
|
||||
return ProjectWindowUtil.GetActiveFolderPath();
|
||||
}
|
||||
|
||||
public static GUIContent GetIconContent<T>() where T : UnityEngine.Object
|
||||
{
|
||||
return EditorGUIUtility.IconContent<T>();
|
||||
}
|
||||
|
||||
public static int GetAssetCreationInstanceID_ForNonExistingAssets()
|
||||
{
|
||||
return ProjectBrowser.kAssetCreationInstanceID_ForNonExistingAssets;
|
||||
}
|
||||
|
||||
public static VisualElement SceneViewCameraViewVisualElement(SceneView sc)
|
||||
{
|
||||
return sc.cameraViewVisualElement;
|
||||
}
|
||||
|
||||
public static Vector2 GetOverlaySize(UnityEditor.Overlays.Overlay o)
|
||||
{
|
||||
return o.rootVisualElement.layout.size;
|
||||
}
|
||||
|
||||
public static Rect GetEditorGUILayoutLastRect()
|
||||
{
|
||||
return EditorGUILayout.s_LastRect;
|
||||
}
|
||||
|
||||
public static string TextureImporterDefaultPlatformName()
|
||||
{
|
||||
return TextureImporter.defaultPlatformName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Build;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal interface ITexturePlatformSettingsDataProvider
|
||||
{
|
||||
bool textureTypeHasMultipleDifferentValues { get; }
|
||||
TextureImporterType textureType { get; }
|
||||
SpriteImportMode spriteImportMode { get; }
|
||||
|
||||
int GetTargetCount();
|
||||
TextureImporterPlatformSettings GetPlatformTextureSettings(int i, string name);
|
||||
bool ShowPresetSettings();
|
||||
bool DoesSourceTextureHaveAlpha(int v);
|
||||
bool IsSourceTextureHDR(int v);
|
||||
void SetPlatformTextureSettings(int i, TextureImporterPlatformSettings platformSettings);
|
||||
void GetImporterSettings(int i, UnityEditor.TextureImporterSettings settings);
|
||||
string GetBuildTargetName(SerializedProperty sp);
|
||||
SerializedProperty platformSettingsArray { get; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TexturePlatformSettings : BaseTextureImportPlatformSettings
|
||||
{
|
||||
[SerializeField]
|
||||
TextureImportPlatformSettingsData m_Data = new TextureImportPlatformSettingsData();
|
||||
ITexturePlatformSettingsDataProvider m_DataProvider;
|
||||
Func<BaseTextureImportPlatformSettings> DefaultImportSettings;
|
||||
|
||||
public override TextureImportPlatformSettingsData model
|
||||
{
|
||||
get => m_Data;
|
||||
}
|
||||
|
||||
public TexturePlatformSettings(string name, BuildTarget target, ITexturePlatformSettingsDataProvider inspector, Func<BaseTextureImportPlatformSettings> defaultPlatform)
|
||||
: base(name, target)
|
||||
{
|
||||
m_DataProvider = inspector;
|
||||
DefaultImportSettings = defaultPlatform;
|
||||
Init();
|
||||
CacheSerializedProperties(inspector.platformSettingsArray);
|
||||
}
|
||||
|
||||
public void CacheSerializedProperties(SerializedProperty platformSettingsArray)
|
||||
{
|
||||
if (model.platformTextureSettingsProp != null && model.platformTextureSettingsProp.isValid && m_DataProvider.GetBuildTargetName(model.platformTextureSettingsProp) == model.platformTextureSettings.name)
|
||||
return;
|
||||
if (platformSettingsArray.arraySize == 0)
|
||||
{
|
||||
model.platformTextureSettingsProp = (SerializedProperty)null;
|
||||
throw new UnityException("Cannot find any Platform Settings, including the Default Platform. This is incorrect, did initialization fail?");
|
||||
}
|
||||
|
||||
for (int index = 0; index < platformSettingsArray.arraySize; ++index)
|
||||
{
|
||||
SerializedProperty arrayElementAtIndex = platformSettingsArray.GetArrayElementAtIndex(index);
|
||||
if (m_DataProvider.GetBuildTargetName(arrayElementAtIndex) == model.platformTextureSettings.name)
|
||||
{
|
||||
model.platformTextureSettingsProp = arrayElementAtIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
model.alphaSplitProperty = model.platformTextureSettingsProp != null ? model.platformTextureSettingsProp.FindPropertyRelative("m_AllowsAlphaSplitting") : throw new UnityException(string.Format("Could not find the requested Platform Texture Settings {0}. This is incorrect, did initialization fail?", model.platformTextureSettings.name));
|
||||
model.androidETC2FallbackOverrideProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_AndroidETC2FallbackOverride");
|
||||
model.compressionQualityProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_CompressionQuality");
|
||||
model.crunchedCompressionProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_CrunchedCompression");
|
||||
model.maxTextureSizeProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_MaxTextureSize");
|
||||
model.overriddenProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_Overridden");
|
||||
model.resizeAlgorithmProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_ResizeAlgorithm");
|
||||
model.textureCompressionProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_TextureCompression");
|
||||
model.textureFormatProperty = model.platformTextureSettingsProp.FindPropertyRelative("m_TextureFormat");
|
||||
}
|
||||
|
||||
public override bool textureTypeHasMultipleDifferentValues
|
||||
{
|
||||
get { return m_DataProvider.textureTypeHasMultipleDifferentValues; }
|
||||
}
|
||||
|
||||
public override TextureImporterType textureType
|
||||
{
|
||||
get { return m_DataProvider.textureType; }
|
||||
}
|
||||
|
||||
public override SpriteImportMode spriteImportMode
|
||||
{
|
||||
get { return m_DataProvider.spriteImportMode; }
|
||||
}
|
||||
|
||||
public override int GetTargetCount()
|
||||
{
|
||||
return m_DataProvider.GetTargetCount();
|
||||
}
|
||||
|
||||
public override bool ShowPresetSettings()
|
||||
{
|
||||
return m_DataProvider.ShowPresetSettings();
|
||||
}
|
||||
|
||||
public override TextureImporterSettings GetImporterSettings(int i)
|
||||
{
|
||||
var textureImporterSettings = new TextureImporterSettings();
|
||||
m_DataProvider.GetImporterSettings(i, textureImporterSettings);
|
||||
return textureImporterSettings;
|
||||
}
|
||||
|
||||
public override bool IsSourceTextureHDR(int i)
|
||||
{
|
||||
return m_DataProvider.IsSourceTextureHDR(i);
|
||||
}
|
||||
|
||||
public override bool DoesSourceTextureHaveAlpha(int i)
|
||||
{
|
||||
return m_DataProvider.DoesSourceTextureHaveAlpha(i);
|
||||
}
|
||||
|
||||
public override TextureImporterPlatformSettings GetPlatformTextureSettings(int i, string name)
|
||||
{
|
||||
var temp = new TextureImporterPlatformSettings();
|
||||
m_DataProvider.GetPlatformTextureSettings(i, name).CopyTo(temp);
|
||||
return temp;
|
||||
}
|
||||
|
||||
public override BaseTextureImportPlatformSettings GetDefaultImportSettings()
|
||||
{
|
||||
return DefaultImportSettings();
|
||||
}
|
||||
|
||||
public override void SetPlatformTextureSettings(int i, TextureImporterPlatformSettings platformSettings)
|
||||
{
|
||||
platformSettings.name = GetFixedPlatformName(platformSettings.name);
|
||||
if (!model.overriddenIsDifferent)
|
||||
model.overriddenProperty.boolValue = model.platformTextureSettings.overridden;
|
||||
if (!model.textureFormatIsDifferent)
|
||||
model.textureFormatProperty.intValue = (int)model.platformTextureSettings.format;
|
||||
if (!model.maxTextureSizeIsDifferent)
|
||||
model.maxTextureSizeProperty.intValue = model.platformTextureSettings.maxTextureSize;
|
||||
if (!model.resizeAlgorithmIsDifferent)
|
||||
model.resizeAlgorithmProperty.intValue = (int)model.platformTextureSettings.resizeAlgorithm;
|
||||
if (!model.textureCompressionIsDifferent)
|
||||
model.textureCompressionProperty.intValue = (int)model.platformTextureSettings.textureCompression;
|
||||
if (!model.compressionQualityIsDifferent)
|
||||
model.compressionQualityProperty.intValue = model.platformTextureSettings.compressionQuality;
|
||||
if (!model.crunchedCompressionIsDifferent)
|
||||
model.crunchedCompressionProperty.boolValue = model.platformTextureSettings.crunchedCompression;
|
||||
if (!model.allowsAlphaSplitIsDifferent)
|
||||
model.alphaSplitProperty.boolValue = model.platformTextureSettings.allowsAlphaSplitting;
|
||||
if (!model.androidETC2FallbackOverrideIsDifferent)
|
||||
model.androidETC2FallbackOverrideProperty.intValue = (int)model.platformTextureSettings.androidETC2FallbackOverride;
|
||||
m_DataProvider.SetPlatformTextureSettings(i, platformSettings);
|
||||
}
|
||||
|
||||
private string GetFixedPlatformName(string platform)
|
||||
{
|
||||
var targetGroup = BuildPipeline.GetBuildTargetGroupByName(platform);
|
||||
if (targetGroup != BuildTargetGroup.Unknown)
|
||||
return BuildPipeline.GetBuildTargetGroupName(targetGroup);
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TexturePlatformSettingsHelper
|
||||
{
|
||||
[SerializeField]
|
||||
List<TexturePlatformSettings> m_PlatformSettings;
|
||||
ITexturePlatformSettingsDataProvider m_DataProvider;
|
||||
|
||||
internal static List<TextureImporterPlatformSettings> PlatformSettingsNeeded(ITexturePlatformSettingsDataProvider dataProvider)
|
||||
{
|
||||
BuildPlatform[] validPlatforms = BaseTextureImportPlatformSettings.GetBuildPlayerValidPlatforms();
|
||||
|
||||
var platformSettings = new List<TextureImporterPlatformSettings>();
|
||||
platformSettings.Add(new TextureImporterPlatformSettings()
|
||||
{
|
||||
name = TextureImporterInspector.s_DefaultPlatformName
|
||||
});
|
||||
|
||||
foreach (BuildPlatform bp in validPlatforms)
|
||||
{
|
||||
platformSettings.Add(new TextureImporterPlatformSettings
|
||||
{
|
||||
name = bp.name,
|
||||
overridden = false
|
||||
});
|
||||
}
|
||||
|
||||
return platformSettings;
|
||||
}
|
||||
|
||||
public TexturePlatformSettingsHelper(ITexturePlatformSettingsDataProvider dataProvider)
|
||||
{
|
||||
m_DataProvider = dataProvider;
|
||||
BuildPlatform[] validPlatforms = BaseTextureImportPlatformSettings.GetBuildPlayerValidPlatforms();
|
||||
|
||||
m_PlatformSettings = new List<TexturePlatformSettings>();
|
||||
m_PlatformSettings.Add(new TexturePlatformSettings(TextureImporterInspector.s_DefaultPlatformName, BuildTarget.StandaloneWindows, dataProvider, DefaultTextureImportPlatformSettings));
|
||||
|
||||
foreach (BuildPlatform bp in validPlatforms)
|
||||
{
|
||||
m_PlatformSettings.Add(new TexturePlatformSettings(bp.name, bp.defaultTarget, dataProvider, DefaultTextureImportPlatformSettings));
|
||||
}
|
||||
}
|
||||
|
||||
BaseTextureImportPlatformSettings DefaultTextureImportPlatformSettings()
|
||||
{
|
||||
return m_PlatformSettings[0];
|
||||
}
|
||||
|
||||
public static string defaultPlatformName
|
||||
{
|
||||
get => TextureImporterInspector.s_DefaultPlatformName;
|
||||
}
|
||||
|
||||
public SpriteImportMode spriteImportMode
|
||||
{
|
||||
get { return m_DataProvider.spriteImportMode; }
|
||||
}
|
||||
|
||||
public TextureImporterType textureType
|
||||
{
|
||||
get { return m_DataProvider.textureType; }
|
||||
}
|
||||
|
||||
public bool textureTypeHasMultipleDifferentValues
|
||||
{
|
||||
get { return m_DataProvider.textureTypeHasMultipleDifferentValues; }
|
||||
}
|
||||
|
||||
public void ShowPlatformSpecificSettings()
|
||||
{
|
||||
// BuildPlatform[] validPlatforms = BuildPlatforms.instance.GetValidPlatforms().ToArray();
|
||||
// int shownTextureFormatPage = EditorGUILayout.BeginPlatformGrouping(validPlatforms, EditorGUIUtility.TrTextContent("Default"));
|
||||
// m_PlatformSettings.ForEach(settings => settings.CacheSerializedProperties(m_DataProvider.platformSettingsArray));
|
||||
// BaseTextureImportPlatformSettings.ShowPlatformSpecificSettings(m_PlatformSettings.ConvertAll<BaseTextureImportPlatformSettings>(x => x as BaseTextureImportPlatformSettings), shownTextureFormatPage);
|
||||
|
||||
BaseTextureImportPlatformSettings.InitPlatformSettings(m_PlatformSettings.ConvertAll<BaseTextureImportPlatformSettings>(x => x as BaseTextureImportPlatformSettings));
|
||||
m_PlatformSettings.ForEach(settings => settings.CacheSerializedProperties(m_DataProvider.platformSettingsArray));
|
||||
//Show platform grouping
|
||||
int selectedPage = EditorGUILayout.BeginPlatformGrouping(BaseTextureImportPlatformSettings.GetBuildPlayerValidPlatforms(), EditorGUIUtility.TrTextContent("Default"), EditorStyles.frameBox, idx =>
|
||||
{
|
||||
var ps = m_PlatformSettings[idx + 1];
|
||||
var model = ps.model;
|
||||
if (model.isDefault)
|
||||
return false;
|
||||
if (model.overriddenIsDifferent || model.allAreOverridden)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
//Show platform settings
|
||||
using (var changed = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
BaseTextureImportPlatformSettings.ShowPlatformSpecificSettings(m_PlatformSettings.ConvertAll<BaseTextureImportPlatformSettings>(x => x as BaseTextureImportPlatformSettings), selectedPage);
|
||||
// Doing it this way is slow, but it ensure Presets get updated correctly whenever the UI is being changed.
|
||||
if (changed.changed)
|
||||
{
|
||||
BaseTextureImportPlatformSettings.ApplyPlatformSettings(m_PlatformSettings.ConvertAll<BaseTextureImportPlatformSettings>(x => x as BaseTextureImportPlatformSettings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasModified()
|
||||
{
|
||||
foreach (var ps in m_PlatformSettings)
|
||||
{
|
||||
if (ps.model.HasChanged())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SyncPlatformSettings()
|
||||
{
|
||||
foreach (var ps in m_PlatformSettings)
|
||||
ps.Sync();
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
foreach (var ps in m_PlatformSettings)
|
||||
ps.Apply();
|
||||
}
|
||||
|
||||
public static string GetBuildTargetGroupName(BuildTarget target)
|
||||
{
|
||||
var targetGroup = BuildPipeline.GetBuildTargetGroup(target);
|
||||
foreach (var bp in BuildPlatforms.instance.buildPlatforms)
|
||||
{
|
||||
if (bp.targetGroup == targetGroup)
|
||||
return bp.name;
|
||||
}
|
||||
return TextureImporter.defaultPlatformName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,618 @@
|
|||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEditor.AssetImporters;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal interface ITextureSettings
|
||||
{
|
||||
void FillTextureGenerationSettings(ref TextureGenerationSettings settings);
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
internal class TextureSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
bool m_ColorTexture;
|
||||
[SerializeField]
|
||||
bool m_Readable;
|
||||
[SerializeField]
|
||||
TextureImporterNPOTScale m_NPOTScale;
|
||||
[SerializeField]
|
||||
FilterMode m_FilterMode;
|
||||
[SerializeField]
|
||||
int m_Aniso;
|
||||
[SerializeField]
|
||||
bool m_EnablePostProcessor;
|
||||
[SerializeField]
|
||||
SecondarySpriteTexture[] m_SecondaryTextures;
|
||||
[SerializeField]
|
||||
TextureImporterSwizzle m_SwizzleR = TextureImporterSwizzle.R;
|
||||
[SerializeField]
|
||||
TextureImporterSwizzle m_SwizzleG = TextureImporterSwizzle.G;
|
||||
[SerializeField]
|
||||
TextureImporterSwizzle m_SwizzleB = TextureImporterSwizzle.B;
|
||||
[SerializeField]
|
||||
TextureImporterSwizzle m_SwizzleA = TextureImporterSwizzle.A;
|
||||
|
||||
public TextureSettings()
|
||||
{
|
||||
colorTexture = true;
|
||||
readable = false;
|
||||
npotScale = TextureImporterNPOTScale.None;
|
||||
filterMode = FilterMode.Bilinear;
|
||||
aniso = 1;
|
||||
}
|
||||
|
||||
public TextureSettings(string assetPath, bool enablePostProcessor, bool colorTexture, bool readable, TextureImporterNPOTScale npotScale, FilterMode filterMode, int aniso, bool sourceContainsAlpha, bool sourceWasHDR)
|
||||
{
|
||||
this.assetPath = assetPath;
|
||||
this.enablePostProcessor = enablePostProcessor;
|
||||
this.colorTexture = colorTexture;
|
||||
this.readable = readable;
|
||||
this.npotScale = npotScale;
|
||||
this.filterMode = filterMode;
|
||||
this.aniso = aniso;
|
||||
this.containsAlpha = sourceContainsAlpha;
|
||||
this.hdr = sourceWasHDR;
|
||||
}
|
||||
|
||||
public bool colorTexture { get { return m_ColorTexture; } set { m_ColorTexture = value; } } //sRGBTexture
|
||||
public bool readable { get { return m_Readable; } set { m_Readable = value; } }
|
||||
public TextureImporterNPOTScale npotScale { get { return m_NPOTScale; } set { m_NPOTScale = value; } }
|
||||
public FilterMode filterMode { get { return m_FilterMode; } set { m_FilterMode = value; } }
|
||||
public int aniso
|
||||
{
|
||||
get { return m_Aniso; }
|
||||
set { m_Aniso = value; }
|
||||
}
|
||||
public bool enablePostProcessor
|
||||
{
|
||||
get { return m_EnablePostProcessor; }
|
||||
set { m_EnablePostProcessor = value; }
|
||||
}
|
||||
|
||||
public string assetPath { get; set; }
|
||||
public bool containsAlpha { get; set; }
|
||||
public bool hdr { get; set; }
|
||||
public TextureImporterSwizzle swizzleR { get { return m_SwizzleR; } set { m_SwizzleR = value; } }
|
||||
public TextureImporterSwizzle swizzleG { get { return m_SwizzleG; } set { m_SwizzleG = value; } }
|
||||
public TextureImporterSwizzle swizzleB { get { return m_SwizzleB; } set { m_SwizzleB = value; } }
|
||||
public TextureImporterSwizzle swizzleA { get { return m_SwizzleA; } set { m_SwizzleA = value; } }
|
||||
public SecondarySpriteTexture[] secondaryTextures { get { return m_SecondaryTextures;} set { m_SecondaryTextures = value; } }
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.sRGBTexture = colorTexture;
|
||||
settings.textureImporterSettings.readable = readable;
|
||||
settings.textureImporterSettings.npotScale = npotScale;
|
||||
settings.textureImporterSettings.filterMode = filterMode;
|
||||
settings.textureImporterSettings.aniso = aniso;
|
||||
settings.assetPath = assetPath;
|
||||
settings.enablePostProcessor = enablePostProcessor;
|
||||
settings.sourceTextureInformation.containsAlpha = containsAlpha;
|
||||
settings.sourceTextureInformation.hdr = hdr;
|
||||
settings.secondarySpriteTextures = secondaryTextures;
|
||||
settings.textureImporterSettings.swizzleR = m_SwizzleR;
|
||||
settings.textureImporterSettings.swizzleG = m_SwizzleG;
|
||||
settings.textureImporterSettings.swizzleB = m_SwizzleB;
|
||||
settings.textureImporterSettings.swizzleA = m_SwizzleA;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TextureSpriteSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
string m_PackingTag;
|
||||
public string packingTag
|
||||
{
|
||||
get { return m_PackingTag; }
|
||||
set { m_PackingTag = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float m_PixelsPerUnit;
|
||||
public float pixelsPerUnit
|
||||
{
|
||||
get { return m_PixelsPerUnit; }
|
||||
set { m_PixelsPerUnit = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
SpriteMeshType m_MeshType;
|
||||
public SpriteMeshType meshType
|
||||
{
|
||||
get { return m_MeshType; }
|
||||
set { m_MeshType = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
uint m_ExtrudeEdges;
|
||||
public uint extrudeEdges
|
||||
{
|
||||
get { return m_ExtrudeEdges; }
|
||||
set { m_ExtrudeEdges = value; }
|
||||
}
|
||||
|
||||
public bool qualifyForPacking { get; set; }
|
||||
public SpriteImportData[] spriteSheetData { get; set; }
|
||||
|
||||
public TextureSpriteSettings()
|
||||
{
|
||||
packingTag = "";
|
||||
pixelsPerUnit = 100;
|
||||
meshType = SpriteMeshType.Tight;
|
||||
extrudeEdges = 1;
|
||||
}
|
||||
|
||||
public TextureSpriteSettings(string packingTag, int pixelsPerUnit, SpriteMeshType meshType, uint extrudeEdges, bool qualifyForPacking, SpriteImportData[] spriteSheetData = null)
|
||||
{
|
||||
this.packingTag = packingTag;
|
||||
this.pixelsPerUnit = pixelsPerUnit;
|
||||
this.meshType = meshType;
|
||||
this.extrudeEdges = extrudeEdges;
|
||||
this.qualifyForPacking = qualifyForPacking;
|
||||
this.spriteSheetData = spriteSheetData;
|
||||
}
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.spritePixelsPerUnit = pixelsPerUnit;
|
||||
settings.textureImporterSettings.spriteMeshType = meshType;
|
||||
settings.textureImporterSettings.spriteExtrude = extrudeEdges;
|
||||
settings.spritePackingTag = packingTag;
|
||||
settings.qualifyForSpritePacking = qualifyForPacking;
|
||||
settings.spriteImportData = spriteSheetData;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TextureWrapSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
TextureWrapMode m_WrapMode;
|
||||
[SerializeField]
|
||||
TextureWrapMode m_WrapModeU;
|
||||
[SerializeField]
|
||||
TextureWrapMode m_WrapModeV;
|
||||
[SerializeField]
|
||||
TextureWrapMode m_WrapModeW;
|
||||
|
||||
public TextureWrapSettings()
|
||||
{
|
||||
wrapMode = wrapModeU = wrapModeV = wrapModeW = TextureWrapMode.Repeat;
|
||||
}
|
||||
|
||||
public TextureWrapSettings(TextureWrapMode wrapMpde, TextureWrapMode wrapModeU, TextureWrapMode wrapModeV, TextureWrapMode wrapModeW)
|
||||
{
|
||||
this.wrapMode = wrapMode;
|
||||
this.wrapModeU = wrapModeU;
|
||||
this.wrapModeV = wrapModeV;
|
||||
this.wrapModeW = wrapModeW;
|
||||
}
|
||||
|
||||
public TextureWrapMode wrapMode { get { return m_WrapMode; } set { m_WrapMode = value; } }
|
||||
public TextureWrapMode wrapModeU { get { return m_WrapModeU; } set { m_WrapModeU = value; } }
|
||||
public TextureWrapMode wrapModeV { get { return m_WrapModeV; } set { m_WrapModeV = value; } }
|
||||
public TextureWrapMode wrapModeW { get { return m_WrapModeW; } set { m_WrapModeW = value; } }
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.wrapMode = wrapMode;
|
||||
settings.textureImporterSettings.wrapModeU = wrapModeU;
|
||||
settings.textureImporterSettings.wrapModeV = wrapModeV;
|
||||
settings.textureImporterSettings.wrapModeW = wrapModeW;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TextureAlphaSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
float m_AlphaTolerance;
|
||||
public float alphaTolerance
|
||||
{
|
||||
get { return m_AlphaTolerance; }
|
||||
set { m_AlphaTolerance = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
TextureImporterAlphaSource m_AlphaSource;
|
||||
public TextureImporterAlphaSource alphaSource
|
||||
{
|
||||
get { return m_AlphaSource; }
|
||||
set { m_AlphaSource = value; }
|
||||
}
|
||||
|
||||
public TextureAlphaSettings()
|
||||
{
|
||||
alphaTolerance = 0.5f;
|
||||
alphaSource = TextureImporterAlphaSource.FromInput;
|
||||
}
|
||||
|
||||
public TextureAlphaSettings(TextureImporterAlphaSource alphaSource, float alphaTolerance)
|
||||
{
|
||||
this.alphaTolerance = alphaTolerance;
|
||||
this.alphaSource = alphaSource;
|
||||
}
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.alphaIsTransparency = alphaSource != TextureImporterAlphaSource.None;
|
||||
settings.textureImporterSettings.alphaSource = alphaSource;
|
||||
settings.textureImporterSettings.alphaTestReferenceValue = alphaTolerance;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TextureMipmapSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
TextureImporterMipFilter m_Filter;
|
||||
public TextureImporterMipFilter filter
|
||||
{
|
||||
get { return m_Filter; }
|
||||
set { m_Filter = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_BorderMipmap;
|
||||
public bool borderMipmap
|
||||
{
|
||||
get { return m_BorderMipmap; }
|
||||
set { m_BorderMipmap = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_Fadeout;
|
||||
public bool fadeout
|
||||
{
|
||||
get { return m_Fadeout; }
|
||||
set { m_Fadeout = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_PreserveCoverage;
|
||||
public bool preserveCoverage
|
||||
{
|
||||
get { return m_PreserveCoverage; }
|
||||
set { m_PreserveCoverage = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
int m_FadeDistanceStart;
|
||||
public int fadeDistanceStart
|
||||
{
|
||||
get { return m_FadeDistanceStart; }
|
||||
set { m_FadeDistanceStart = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
int m_FadeDistanceEnd;
|
||||
public int fadeDistanceEnd
|
||||
{
|
||||
get { return m_FadeDistanceEnd; }
|
||||
set { m_FadeDistanceEnd = value; }
|
||||
}
|
||||
|
||||
public TextureMipmapSettings()
|
||||
{
|
||||
filter = TextureImporterMipFilter.BoxFilter;
|
||||
borderMipmap = false;
|
||||
fadeout = false;
|
||||
preserveCoverage = false;
|
||||
fadeDistanceStart = 1;
|
||||
fadeDistanceEnd = 3;
|
||||
}
|
||||
|
||||
public TextureMipmapSettings(TextureImporterMipFilter filter, bool borderMipmap, bool fadeout, bool preserveCoverage, int fadeDistanceStart, int fadeDistanceEnd)
|
||||
{
|
||||
this.filter = filter;
|
||||
this.borderMipmap = borderMipmap;
|
||||
this.fadeout = fadeout;
|
||||
this.preserveCoverage = preserveCoverage;
|
||||
this.fadeDistanceStart = fadeDistanceStart;
|
||||
this.fadeDistanceEnd = fadeDistanceEnd;
|
||||
}
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.mipmapEnabled = true;
|
||||
settings.textureImporterSettings.mipmapFilter = filter;
|
||||
settings.textureImporterSettings.borderMipmap = borderMipmap;
|
||||
settings.textureImporterSettings.fadeOut = fadeout;
|
||||
settings.textureImporterSettings.mipmapFadeDistanceStart = fadeDistanceStart;
|
||||
settings.textureImporterSettings.mipmapFadeDistanceEnd = fadeDistanceEnd;
|
||||
settings.textureImporterSettings.mipMapsPreserveCoverage = preserveCoverage;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class TextureNormalSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
TextureImporterNormalFilter m_Filter;
|
||||
public TextureImporterNormalFilter filter
|
||||
{
|
||||
get { return m_Filter; }
|
||||
set { m_Filter = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_GenerateFromGrayScale;
|
||||
public bool generateFromGrayScale
|
||||
{
|
||||
get { return m_GenerateFromGrayScale; }
|
||||
set { m_GenerateFromGrayScale = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float m_Bumpiness;
|
||||
public float bumpiness
|
||||
{
|
||||
get { return m_Bumpiness; }
|
||||
set { m_Bumpiness = value; }
|
||||
}
|
||||
|
||||
public TextureNormalSettings()
|
||||
{
|
||||
filter = TextureImporterNormalFilter.Standard;
|
||||
generateFromGrayScale = false;
|
||||
bumpiness = 0.25f;
|
||||
}
|
||||
|
||||
public TextureNormalSettings(TextureImporterNormalFilter filter, bool generateFromGrayScale, float bumpiness)
|
||||
{
|
||||
this.filter = filter;
|
||||
this.generateFromGrayScale = generateFromGrayScale;
|
||||
this.bumpiness = bumpiness;
|
||||
}
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.normalMapFilter = filter;
|
||||
settings.textureImporterSettings.convertToNormalMap = generateFromGrayScale;
|
||||
settings.textureImporterSettings.heightmapScale = bumpiness;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is provided, textureType will be cubemap
|
||||
[Serializable]
|
||||
internal class TextureCubemapSettings : ITextureSettings
|
||||
{
|
||||
[SerializeField]
|
||||
TextureImporterCubemapConvolution m_Convolution;
|
||||
public TextureImporterCubemapConvolution convolution
|
||||
{
|
||||
get { return m_Convolution; }
|
||||
set { m_Convolution = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
TextureImporterGenerateCubemap m_Mode;
|
||||
public TextureImporterGenerateCubemap mode
|
||||
{
|
||||
get { return m_Mode; }
|
||||
set { m_Mode = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
bool m_Seamless;
|
||||
public bool seamless
|
||||
{
|
||||
get { return m_Seamless; }
|
||||
set { m_Seamless = value; }
|
||||
}
|
||||
public TextureCubemapSettings()
|
||||
{
|
||||
convolution = TextureImporterCubemapConvolution.None;
|
||||
mode = TextureImporterGenerateCubemap.AutoCubemap;
|
||||
seamless = false;
|
||||
}
|
||||
|
||||
public TextureCubemapSettings(TextureImporterCubemapConvolution convolution, TextureImporterGenerateCubemap mode, bool seamless)
|
||||
{
|
||||
this.convolution = convolution;
|
||||
this.mode = mode;
|
||||
this.seamless = seamless;
|
||||
}
|
||||
|
||||
void ITextureSettings.FillTextureGenerationSettings(ref TextureGenerationSettings settings)
|
||||
{
|
||||
settings.textureImporterSettings.textureShape = TextureImporterShape.TextureCube;
|
||||
settings.textureImporterSettings.cubemapConvolution = convolution;
|
||||
settings.textureImporterSettings.generateCubemap = mode;
|
||||
settings.textureImporterSettings.seamlessCubemap = seamless;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TextureGeneratorHelper
|
||||
{
|
||||
public static TextureGenerationOutput GenerateTextureSprite(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureSpriteSettings spriteSettings, TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
if (alphaSettings == null)
|
||||
alphaSettings = new TextureAlphaSettings(TextureImporterAlphaSource.FromInput, 0.5f);
|
||||
if (wrapSettings == null)
|
||||
wrapSettings = new TextureWrapSettings(TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp);
|
||||
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.Sprite, platformSettings, settings, spriteSettings, alphaSettings, mipmapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateLightmap(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureMipmapSettings mipmapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
settings.colorTexture = true;
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.Lightmap, platformSettings, settings, mipmapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateCookie(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureCubemapSettings cubemapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.Cookie, platformSettings, settings, alphaSettings, mipmapSettings, cubemapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateNormalMap(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureNormalSettings normalSettings, TextureMipmapSettings mipmapSettings = null, TextureCubemapSettings cubemapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
settings.colorTexture = false;
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.NormalMap, platformSettings, settings, normalSettings, mipmapSettings, cubemapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateTextureGUI(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
settings.colorTexture = false;
|
||||
if (wrapSettings == null)
|
||||
wrapSettings = new TextureWrapSettings(TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp);
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.GUI, platformSettings, settings, alphaSettings, mipmapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateTextureSingleChannel(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureCubemapSettings cubemapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
settings.colorTexture = false;
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.SingleChannel, platformSettings, settings, alphaSettings, mipmapSettings, cubemapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateTextureCursor(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
if (alphaSettings == null)
|
||||
alphaSettings = new TextureAlphaSettings(TextureImporterAlphaSource.FromInput, 0.5f);
|
||||
if (wrapSettings == null)
|
||||
wrapSettings = new TextureWrapSettings(TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp, TextureWrapMode.Clamp);
|
||||
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.Cursor, platformSettings, settings, alphaSettings, mipmapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
public static TextureGenerationOutput GenerateTextureDefault(NativeArray<Color32> buffer, int bufferWidth, int bufferHeight, TextureSettings settings, TextureImporterPlatformSettings platformSettings,
|
||||
TextureAlphaSettings alphaSettings = null, TextureMipmapSettings mipmapSettings = null, TextureCubemapSettings cubemapSettings = null, TextureWrapSettings wrapSettings = null)
|
||||
{
|
||||
if (mipmapSettings == null)
|
||||
mipmapSettings = new TextureMipmapSettings(TextureImporterMipFilter.BoxFilter, false, false, false, 1, 3);
|
||||
|
||||
return GenerateTexture(buffer, bufferWidth, bufferHeight, TextureImporterType.Default, platformSettings, settings, alphaSettings, mipmapSettings, cubemapSettings, wrapSettings);
|
||||
}
|
||||
|
||||
static TextureGenerationOutput GenerateTexture(NativeArray<Color32> imageBuffer, int imageBufferWidth, int imageBufferHeight, TextureImporterType type, TextureImporterPlatformSettings platformSettings, params ITextureSettings[] otherSettings)
|
||||
{
|
||||
var textureGenerationSettings = new TextureGenerationSettings();
|
||||
textureGenerationSettings.platformSettings = platformSettings;
|
||||
|
||||
textureGenerationSettings.sourceTextureInformation = new SourceTextureInformation();
|
||||
textureGenerationSettings.sourceTextureInformation.height = imageBufferHeight;
|
||||
textureGenerationSettings.sourceTextureInformation.width = imageBufferWidth;
|
||||
|
||||
textureGenerationSettings.textureImporterSettings = new TextureImporterSettings();
|
||||
textureGenerationSettings.textureImporterSettings.textureType = type;
|
||||
textureGenerationSettings.textureImporterSettings.textureShape = TextureImporterShape.Texture2D;
|
||||
|
||||
textureGenerationSettings.textureImporterSettings.alphaIsTransparency = false;
|
||||
textureGenerationSettings.textureImporterSettings.convertToNormalMap = false;
|
||||
textureGenerationSettings.textureImporterSettings.mipmapEnabled = false;
|
||||
textureGenerationSettings.textureImporterSettings.sRGBTexture = true;
|
||||
textureGenerationSettings.textureImporterSettings.readable = false;
|
||||
textureGenerationSettings.textureImporterSettings.fadeOut = false;
|
||||
textureGenerationSettings.textureImporterSettings.wrapMode = TextureWrapMode.Repeat;
|
||||
textureGenerationSettings.textureImporterSettings.wrapModeU = TextureWrapMode.Repeat;
|
||||
textureGenerationSettings.textureImporterSettings.wrapModeV = TextureWrapMode.Repeat;
|
||||
textureGenerationSettings.textureImporterSettings.wrapModeW = TextureWrapMode.Repeat;
|
||||
textureGenerationSettings.textureImporterSettings.swizzleR = TextureImporterSwizzle.R;
|
||||
textureGenerationSettings.textureImporterSettings.swizzleG = TextureImporterSwizzle.G;
|
||||
textureGenerationSettings.textureImporterSettings.swizzleB = TextureImporterSwizzle.B;
|
||||
textureGenerationSettings.textureImporterSettings.swizzleA = TextureImporterSwizzle.A;
|
||||
|
||||
|
||||
foreach (var otherSetting in otherSettings)
|
||||
{
|
||||
if (otherSetting != null)
|
||||
otherSetting.FillTextureGenerationSettings(ref textureGenerationSettings);
|
||||
}
|
||||
return TextureGenerator.GenerateTexture(textureGenerationSettings, imageBuffer);
|
||||
}
|
||||
|
||||
static public TextureSettings ExtractTextureSettings(this TextureImporterSettings tis)
|
||||
{
|
||||
var ts = new TextureSettings();
|
||||
ts.colorTexture = tis.sRGBTexture;
|
||||
ts.readable = tis.readable;
|
||||
ts.npotScale = tis.npotScale;
|
||||
ts.filterMode = tis.filterMode;
|
||||
ts.aniso = tis.aniso;
|
||||
ts.swizzleR = tis.swizzleR;
|
||||
ts.swizzleG = tis.swizzleG;
|
||||
ts.swizzleB = tis.swizzleB;
|
||||
ts.swizzleA = tis.swizzleA;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureSpriteSettings ExtractTextureSpriteSettings(this TextureImporterSettings tis)
|
||||
{
|
||||
var ts = new TextureSpriteSettings();
|
||||
ts.pixelsPerUnit = tis.spritePixelsPerUnit;
|
||||
ts.meshType = tis.spriteMeshType;
|
||||
ts.extrudeEdges = tis.spriteExtrude;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureWrapSettings ExtractTextureWrapSettings(this TextureImporterSettings tis)
|
||||
{
|
||||
var ts = new TextureWrapSettings();
|
||||
ts.wrapMode = tis.wrapMode;
|
||||
ts.wrapModeU = tis.wrapModeU;
|
||||
ts.wrapModeV = tis.wrapModeV;
|
||||
ts.wrapModeW = tis.wrapModeW;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureAlphaSettings ExtractTextureAlphaSettings(this TextureImporterSettings settings)
|
||||
{
|
||||
if (settings.alphaIsTransparency == false)
|
||||
return null;
|
||||
|
||||
var ts = new TextureAlphaSettings();
|
||||
ts.alphaSource = settings.alphaSource;
|
||||
ts.alphaTolerance = settings.alphaTestReferenceValue;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureMipmapSettings ExtractTextureMipmapSettings(this TextureImporterSettings settings)
|
||||
{
|
||||
if (!settings.mipmapEnabled)
|
||||
return null;
|
||||
|
||||
var ts = new TextureMipmapSettings();
|
||||
ts.filter = settings.mipmapFilter;
|
||||
ts.borderMipmap = settings.borderMipmap;
|
||||
ts.fadeout = settings.fadeOut;
|
||||
ts.fadeDistanceStart = settings.mipmapFadeDistanceStart;
|
||||
ts.fadeDistanceEnd = settings.mipmapFadeDistanceEnd;
|
||||
ts.preserveCoverage = settings.mipMapsPreserveCoverage;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureNormalSettings ExtractTextureNormalSettings(this TextureImporterSettings settings)
|
||||
{
|
||||
var ts = new TextureNormalSettings();
|
||||
ts.filter = settings.normalMapFilter;
|
||||
ts.generateFromGrayScale = settings.convertToNormalMap;
|
||||
ts.bumpiness = settings.heightmapScale;
|
||||
return ts;
|
||||
}
|
||||
|
||||
static public TextureCubemapSettings ExtractTextureCubemapSettings(this TextureImporterSettings settings)
|
||||
{
|
||||
if (settings.textureShape != TextureImporterShape.TextureCube)
|
||||
return null;
|
||||
var ts = new TextureCubemapSettings();
|
||||
ts.convolution = settings.cubemapConvolution;
|
||||
ts.mode = settings.generateCubemap;
|
||||
ts.seamless = settings.seamlessCubemap;
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Unity.2D.Common.Editor",
|
||||
"references": [
|
||||
"Unity.2D.Common.Runtime",
|
||||
"Unity.2D.Sprite.Editor",
|
||||
"Unity.Mathematics",
|
||||
"Unity.Burst"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.U2D.Common
|
||||
{
|
||||
internal static class VisualElementExtensions
|
||||
{
|
||||
public static void SetHiddenFromLayout(this VisualElement element, bool isHidden)
|
||||
{
|
||||
if (isHidden)
|
||||
{
|
||||
element.SetEnabled(false);
|
||||
element.style.display = DisplayStyle.None;
|
||||
element.style.position = Position.Absolute;
|
||||
}
|
||||
else
|
||||
{
|
||||
element.SetEnabled(true);
|
||||
element.style.display = DisplayStyle.Flex;
|
||||
element.style.position = Position.Relative;
|
||||
}
|
||||
}
|
||||
|
||||
public static void LocalizeTextInChildren(this VisualElement element)
|
||||
{
|
||||
element.Query<TextElement>().ForEach((e) => e.text = L10n.Tr(e.text));
|
||||
element.Query<Button>().ForEach((e) => e.tooltip = L10n.Tr(e.tooltip));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
com.unity.2d.common copyright © 2020 Unity Technologies ApS
|
||||
|
||||
Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license).
|
||||
|
||||
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Common.Path.Tests.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Editor")]
|
|
@ -0,0 +1,223 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal static class BezierUtility
|
||||
{
|
||||
static Vector3[] s_TempPoints = new Vector3[3];
|
||||
|
||||
public static Vector3 BezierPoint(Vector3 startPosition, Vector3 startTangent, Vector3 endTangent, Vector3 endPosition, float t)
|
||||
{
|
||||
float s = 1.0f - t;
|
||||
return startPosition * s * s * s + startTangent * s * s * t * 3.0f + endTangent * s * t * t * 3.0f + endPosition * t * t * t;
|
||||
}
|
||||
|
||||
public static Vector3 ClosestPointOnCurve(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, out float t)
|
||||
{
|
||||
Vector3 startToEnd = endPosition - startPosition;
|
||||
Vector3 startToTangent = (startTangent - startPosition);
|
||||
Vector3 endToTangent = (endTangent - endPosition);
|
||||
|
||||
float sqrError = 0.001f;
|
||||
|
||||
if (Colinear(startToTangent, startToEnd, sqrError) && Colinear(endToTangent, startToEnd, sqrError))
|
||||
return ClosestPointToSegment(point, startPosition, endPosition, out t);
|
||||
|
||||
Vector3 leftStartPosition;
|
||||
Vector3 leftEndPosition;
|
||||
Vector3 leftStartTangent;
|
||||
Vector3 leftEndTangent;
|
||||
|
||||
Vector3 rightStartPosition;
|
||||
Vector3 rightEndPosition;
|
||||
Vector3 rightStartTangent;
|
||||
Vector3 rightEndTangent;
|
||||
|
||||
float leftStartT = 0f;
|
||||
float leftEndT = 0.5f;
|
||||
float rightStartT = 0.5f;
|
||||
float rightEndT = 1f;
|
||||
|
||||
SplitBezier(0.5f, startPosition, endPosition, startTangent, endTangent,
|
||||
out leftStartPosition, out leftEndPosition, out leftStartTangent, out leftEndTangent,
|
||||
out rightStartPosition, out rightEndPosition, out rightStartTangent, out rightEndTangent);
|
||||
|
||||
Vector3 pointLeft = ClosestPointOnCurveIterative(point, leftStartPosition, leftEndPosition, leftStartTangent, leftEndTangent, sqrError, ref leftStartT, ref leftEndT);
|
||||
Vector3 pointRight = ClosestPointOnCurveIterative(point, rightStartPosition, rightEndPosition, rightStartTangent, rightEndTangent, sqrError, ref rightStartT, ref rightEndT);
|
||||
|
||||
if ((point - pointLeft).sqrMagnitude < (point - pointRight).sqrMagnitude)
|
||||
{
|
||||
t = leftStartT;
|
||||
return pointLeft;
|
||||
}
|
||||
|
||||
t = rightStartT;
|
||||
return pointRight;
|
||||
}
|
||||
|
||||
public static Vector3 ClosestPointOnCurveFast(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, out float t)
|
||||
{
|
||||
float sqrError = 0.001f;
|
||||
float startT = 0f;
|
||||
float endT = 1f;
|
||||
|
||||
Vector3 closestPoint = ClosestPointOnCurveIterative(point, startPosition, endPosition, startTangent, endTangent, sqrError, ref startT, ref endT);
|
||||
|
||||
t = startT;
|
||||
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
private static Vector3 ClosestPointOnCurveIterative(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float sqrError, ref float startT, ref float endT)
|
||||
{
|
||||
while ((startPosition - endPosition).sqrMagnitude > sqrError)
|
||||
{
|
||||
Vector3 startToEnd = endPosition - startPosition;
|
||||
Vector3 startToTangent = (startTangent - startPosition);
|
||||
Vector3 endToTangent = (endTangent - endPosition);
|
||||
|
||||
if (Colinear(startToTangent, startToEnd, sqrError) && Colinear(endToTangent, startToEnd, sqrError))
|
||||
{
|
||||
float t;
|
||||
Vector3 closestPoint = ClosestPointToSegment(point, startPosition, endPosition, out t);
|
||||
t *= (endT - startT);
|
||||
startT += t;
|
||||
endT -= t;
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
Vector3 leftStartPosition;
|
||||
Vector3 leftEndPosition;
|
||||
Vector3 leftStartTangent;
|
||||
Vector3 leftEndTangent;
|
||||
|
||||
Vector3 rightStartPosition;
|
||||
Vector3 rightEndPosition;
|
||||
Vector3 rightStartTangent;
|
||||
Vector3 rightEndTangent;
|
||||
|
||||
SplitBezier(0.5f, startPosition, endPosition, startTangent, endTangent,
|
||||
out leftStartPosition, out leftEndPosition, out leftStartTangent, out leftEndTangent,
|
||||
out rightStartPosition, out rightEndPosition, out rightStartTangent, out rightEndTangent);
|
||||
|
||||
s_TempPoints[0] = leftStartPosition;
|
||||
s_TempPoints[1] = leftStartTangent;
|
||||
s_TempPoints[2] = leftEndTangent;
|
||||
|
||||
float sqrDistanceLeft = SqrDistanceToPolyLine(point, s_TempPoints);
|
||||
|
||||
s_TempPoints[0] = rightEndPosition;
|
||||
s_TempPoints[1] = rightEndTangent;
|
||||
s_TempPoints[2] = rightStartTangent;
|
||||
|
||||
float sqrDistanceRight = SqrDistanceToPolyLine(point, s_TempPoints);
|
||||
|
||||
if (sqrDistanceLeft < sqrDistanceRight)
|
||||
{
|
||||
startPosition = leftStartPosition;
|
||||
endPosition = leftEndPosition;
|
||||
startTangent = leftStartTangent;
|
||||
endTangent = leftEndTangent;
|
||||
|
||||
endT -= (endT - startT) * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPosition = rightStartPosition;
|
||||
endPosition = rightEndPosition;
|
||||
startTangent = rightStartTangent;
|
||||
endTangent = rightEndTangent;
|
||||
|
||||
startT += (endT - startT) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
public static void SplitBezier(float t, Vector3 startPosition, Vector3 endPosition, Vector3 startRightTangent, Vector3 endLeftTangent,
|
||||
out Vector3 leftStartPosition, out Vector3 leftEndPosition, out Vector3 leftStartTangent, out Vector3 leftEndTangent,
|
||||
out Vector3 rightStartPosition, out Vector3 rightEndPosition, out Vector3 rightStartTangent, out Vector3 rightEndTangent)
|
||||
{
|
||||
Vector3 tangent0 = (startRightTangent - startPosition);
|
||||
Vector3 tangent1 = (endLeftTangent - endPosition);
|
||||
Vector3 tangentEdge = (endLeftTangent - startRightTangent);
|
||||
|
||||
Vector3 tangentPoint0 = startPosition + tangent0 * t;
|
||||
Vector3 tangentPoint1 = endPosition + tangent1 * (1f - t);
|
||||
Vector3 tangentEdgePoint = startRightTangent + tangentEdge * t;
|
||||
|
||||
Vector3 newTangent0 = tangentPoint0 + (tangentEdgePoint - tangentPoint0) * t;
|
||||
Vector3 newTangent1 = tangentPoint1 + (tangentEdgePoint - tangentPoint1) * (1f - t);
|
||||
Vector3 newTangentEdge = newTangent1 - newTangent0;
|
||||
|
||||
Vector3 bezierPoint = newTangent0 + newTangentEdge * t;
|
||||
|
||||
leftStartPosition = startPosition;
|
||||
leftEndPosition = bezierPoint;
|
||||
leftStartTangent = tangentPoint0;
|
||||
leftEndTangent = newTangent0;
|
||||
|
||||
rightStartPosition = bezierPoint;
|
||||
rightEndPosition = endPosition;
|
||||
rightStartTangent = newTangent1;
|
||||
rightEndTangent = tangentPoint1;
|
||||
}
|
||||
|
||||
private static Vector3 ClosestPointToSegment(Vector3 point, Vector3 segmentStart, Vector3 segmentEnd, out float t)
|
||||
{
|
||||
Vector3 relativePoint = point - segmentStart;
|
||||
Vector3 segment = (segmentEnd - segmentStart);
|
||||
Vector3 segmentDirection = segment.normalized;
|
||||
float length = segment.magnitude;
|
||||
|
||||
float dot = Vector3.Dot(relativePoint, segmentDirection);
|
||||
|
||||
if (dot <= 0f)
|
||||
dot = 0f;
|
||||
else if (dot >= length)
|
||||
dot = length;
|
||||
|
||||
t = dot / length;
|
||||
|
||||
return segmentStart + segment * t;
|
||||
}
|
||||
|
||||
private static float SqrDistanceToPolyLine(Vector3 point, Vector3[] points)
|
||||
{
|
||||
float minDistance = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < points.Length - 1; ++i)
|
||||
{
|
||||
float distance = SqrDistanceToSegment(point, points[i], points[i + 1]);
|
||||
|
||||
if (distance < minDistance)
|
||||
minDistance = distance;
|
||||
}
|
||||
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
private static float SqrDistanceToSegment(Vector3 point, Vector3 segmentStart, Vector3 segmentEnd)
|
||||
{
|
||||
Vector3 relativePoint = point - segmentStart;
|
||||
Vector3 segment = (segmentEnd - segmentStart);
|
||||
Vector3 segmentDirection = segment.normalized;
|
||||
float length = segment.magnitude;
|
||||
|
||||
float dot = Vector3.Dot(relativePoint, segmentDirection);
|
||||
|
||||
if (dot <= 0f)
|
||||
return (point - segmentStart).sqrMagnitude;
|
||||
else if (dot >= length)
|
||||
return (point - segmentEnd).sqrMagnitude;
|
||||
|
||||
return Vector3.Cross(relativePoint, segmentDirection).sqrMagnitude;
|
||||
}
|
||||
|
||||
private static bool Colinear(Vector3 v1, Vector3 v2, float error = 0.0001f)
|
||||
{
|
||||
return Mathf.Abs(v1.x * v2.y - v1.y * v2.x + v1.x * v2.z - v1.z * v2.x + v1.y * v2.z - v1.z * v2.y) < error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal enum TangentMode
|
||||
{
|
||||
Linear = 0,
|
||||
Continuous = 1,
|
||||
Broken = 2
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct TangentCache
|
||||
{
|
||||
public Vector3 leftTangent;
|
||||
public Vector3 rightTangent;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct ControlPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector3 localLeftTangent;
|
||||
public Vector3 localRightTangent;
|
||||
public TangentMode tangentMode;
|
||||
public TangentCache continuousCache;
|
||||
public TangentCache brokenCache;
|
||||
public bool mirrorLeft;
|
||||
|
||||
public Vector3 leftTangent
|
||||
{
|
||||
get { return localLeftTangent + position; }
|
||||
set { localLeftTangent = value - position; }
|
||||
}
|
||||
|
||||
public Vector3 rightTangent
|
||||
{
|
||||
get { return localRightTangent + position; }
|
||||
set { localRightTangent = value - position; }
|
||||
}
|
||||
|
||||
public void StoreTangents()
|
||||
{
|
||||
if (tangentMode == TangentMode.Continuous)
|
||||
{
|
||||
continuousCache.leftTangent = localLeftTangent;
|
||||
continuousCache.rightTangent = localRightTangent;
|
||||
}
|
||||
else if (tangentMode == TangentMode.Broken)
|
||||
{
|
||||
brokenCache.leftTangent = localLeftTangent;
|
||||
brokenCache.rightTangent = localRightTangent;
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreTangents()
|
||||
{
|
||||
if (tangentMode == TangentMode.Continuous)
|
||||
{
|
||||
localLeftTangent = continuousCache.leftTangent;
|
||||
localRightTangent = continuousCache.rightTangent;
|
||||
}
|
||||
else if (tangentMode == TangentMode.Broken)
|
||||
{
|
||||
localLeftTangent = brokenCache.leftTangent;
|
||||
localRightTangent = brokenCache.rightTangent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
[Serializable]
|
||||
internal class EditablePath : IEditablePath
|
||||
{
|
||||
[SerializeField]
|
||||
private ShapeType m_ShapeType;
|
||||
[SerializeField]
|
||||
private IndexedSelection m_Selection = new IndexedSelection();
|
||||
[SerializeField]
|
||||
private List<ControlPoint> m_ControlPoints = new List<ControlPoint>();
|
||||
[SerializeField]
|
||||
private bool m_IsOpenEnded;
|
||||
private Matrix4x4 m_LocalToWorldMatrix = Matrix4x4.identity;
|
||||
private Matrix4x4 m_WorldToLocalMatrix = Matrix4x4.identity;
|
||||
private Vector3 m_Forward = Vector3.forward;
|
||||
private Vector3 m_Up = Vector3.up;
|
||||
private Vector3 m_Right = Vector3.right;
|
||||
|
||||
public ShapeType shapeType
|
||||
{
|
||||
get { return m_ShapeType; }
|
||||
set { m_ShapeType = value; }
|
||||
}
|
||||
|
||||
public IUndoObject undoObject { get; set; }
|
||||
|
||||
public Matrix4x4 localToWorldMatrix
|
||||
{
|
||||
get { return m_LocalToWorldMatrix; }
|
||||
set
|
||||
{
|
||||
m_LocalToWorldMatrix = value;
|
||||
m_WorldToLocalMatrix = value.inverse;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 forward
|
||||
{
|
||||
get { return m_Forward; }
|
||||
set { m_Forward = value; }
|
||||
}
|
||||
|
||||
public Vector3 up
|
||||
{
|
||||
get { return m_Up; }
|
||||
set { m_Up = value; }
|
||||
}
|
||||
|
||||
public Vector3 right
|
||||
{
|
||||
get { return m_Right; }
|
||||
set { m_Right = value; }
|
||||
}
|
||||
|
||||
public Matrix4x4 worldToLocalMatrix
|
||||
{
|
||||
get { return m_WorldToLocalMatrix; }
|
||||
}
|
||||
|
||||
public bool isOpenEnded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (pointCount < 3)
|
||||
return true;
|
||||
|
||||
return m_IsOpenEnded;
|
||||
}
|
||||
set { m_IsOpenEnded = value; }
|
||||
}
|
||||
|
||||
public ISelection<int> selection
|
||||
{
|
||||
get { return m_Selection; }
|
||||
}
|
||||
|
||||
public int pointCount
|
||||
{
|
||||
get { return m_ControlPoints.Count; }
|
||||
}
|
||||
|
||||
public ControlPoint GetPointLocal(int index)
|
||||
{
|
||||
return m_ControlPoints[index];
|
||||
}
|
||||
|
||||
public ControlPoint GetPoint(int index)
|
||||
{
|
||||
return TransformPoint(localToWorldMatrix, m_ControlPoints[index]);
|
||||
}
|
||||
|
||||
public void SetPoint(int index, ControlPoint controlPoint)
|
||||
{
|
||||
m_ControlPoints[index] = TransformPoint(worldToLocalMatrix, controlPoint);
|
||||
}
|
||||
|
||||
public void AddPoint(ControlPoint controlPoint)
|
||||
{
|
||||
m_ControlPoints.Insert(pointCount, TransformPoint(worldToLocalMatrix, controlPoint));
|
||||
}
|
||||
|
||||
public void InsertPoint(int index, ControlPoint controlPoint)
|
||||
{
|
||||
m_ControlPoints.Insert(index, TransformPoint(worldToLocalMatrix, controlPoint));
|
||||
}
|
||||
|
||||
public void RemovePoint(int index)
|
||||
{
|
||||
m_ControlPoints.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_ControlPoints.Clear();
|
||||
}
|
||||
|
||||
private ControlPoint TransformPoint(Matrix4x4 transformMatrix, ControlPoint controlPoint)
|
||||
{
|
||||
if (transformMatrix == Matrix4x4.identity)
|
||||
return controlPoint;
|
||||
|
||||
var newControlPoint = new ControlPoint()
|
||||
{
|
||||
position = transformMatrix.MultiplyPoint3x4(controlPoint.position),
|
||||
tangentMode = controlPoint.tangentMode,
|
||||
continuousCache = controlPoint.continuousCache,
|
||||
brokenCache = controlPoint.brokenCache,
|
||||
mirrorLeft = controlPoint.mirrorLeft
|
||||
};
|
||||
|
||||
newControlPoint.rightTangent = transformMatrix.MultiplyPoint3x4(controlPoint.rightTangent);
|
||||
newControlPoint.leftTangent = transformMatrix.MultiplyPoint3x4(controlPoint.leftTangent);
|
||||
|
||||
return newControlPoint;
|
||||
}
|
||||
|
||||
public bool Select(ISelector<Vector3> selector)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
for (var i = 0; i < pointCount; ++i)
|
||||
changed |= selection.Select(i, selector.Select(GetPoint(i).position));
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class EditablePathController : IEditablePathController
|
||||
{
|
||||
private ISnapping<Vector3> m_Snapping = new Snapping();
|
||||
|
||||
public IEditablePath editablePath { get; set; }
|
||||
public IEditablePath closestEditablePath { get { return editablePath; } }
|
||||
|
||||
public ISnapping<Vector3> snapping
|
||||
{
|
||||
get { return m_Snapping; }
|
||||
set { m_Snapping = value; }
|
||||
}
|
||||
|
||||
public bool enableSnapping { get; }
|
||||
|
||||
public void RegisterUndo(string name)
|
||||
{
|
||||
if (editablePath.undoObject != null)
|
||||
editablePath.undoObject.RegisterUndo(name);
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
editablePath.selection.Clear();
|
||||
}
|
||||
|
||||
public void SelectPoint(int index, bool select)
|
||||
{
|
||||
editablePath.selection.Select(index, select);
|
||||
}
|
||||
|
||||
public void CreatePoint(int index, Vector3 position)
|
||||
{
|
||||
ClearSelection();
|
||||
|
||||
if (editablePath.shapeType == ShapeType.Polygon)
|
||||
{
|
||||
editablePath.InsertPoint(index + 1, new ControlPoint() { position = position });
|
||||
}
|
||||
else if (editablePath.shapeType == ShapeType.Spline)
|
||||
{
|
||||
var nextIndex = NextIndex(index);
|
||||
var currentPoint = editablePath.GetPoint(index);
|
||||
var nextPoint = editablePath.GetPoint(nextIndex);
|
||||
|
||||
float t;
|
||||
var closestPoint = BezierUtility.ClosestPointOnCurve(
|
||||
position,
|
||||
currentPoint.position,
|
||||
nextPoint.position,
|
||||
GetRightTangentPosition(index),
|
||||
GetLeftTangentPosition(nextIndex),
|
||||
out t);
|
||||
|
||||
Vector3 leftStartPosition;
|
||||
Vector3 leftEndPosition;
|
||||
Vector3 leftStartTangent;
|
||||
Vector3 leftEndTangent;
|
||||
|
||||
Vector3 rightStartPosition;
|
||||
Vector3 rightEndPosition;
|
||||
Vector3 rightStartTangent;
|
||||
Vector3 rightEndTangent;
|
||||
|
||||
BezierUtility.SplitBezier(t, currentPoint.position, nextPoint.position, GetRightTangentPosition(index), GetLeftTangentPosition(nextIndex),
|
||||
out leftStartPosition, out leftEndPosition, out leftStartTangent, out leftEndTangent,
|
||||
out rightStartPosition, out rightEndPosition, out rightStartTangent, out rightEndTangent);
|
||||
|
||||
var newPointIndex = index + 1;
|
||||
var newPoint = new ControlPoint()
|
||||
{
|
||||
position = closestPoint,
|
||||
leftTangent = leftEndTangent,
|
||||
rightTangent = rightStartTangent,
|
||||
tangentMode = TangentMode.Continuous
|
||||
};
|
||||
|
||||
currentPoint.rightTangent = leftStartTangent;
|
||||
nextPoint.leftTangent = rightEndTangent;
|
||||
|
||||
if (currentPoint.tangentMode == TangentMode.Linear && nextPoint.tangentMode == TangentMode.Linear)
|
||||
{
|
||||
newPoint.tangentMode = TangentMode.Linear;
|
||||
newPoint.localLeftTangent = Vector3.zero;
|
||||
newPoint.localRightTangent = Vector3.zero;
|
||||
currentPoint.localRightTangent = Vector3.zero;
|
||||
nextPoint.localLeftTangent = Vector3.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentPoint.tangentMode == TangentMode.Linear)
|
||||
currentPoint.tangentMode = TangentMode.Broken;
|
||||
|
||||
if (nextPoint.tangentMode == TangentMode.Linear)
|
||||
nextPoint.tangentMode = TangentMode.Broken;
|
||||
}
|
||||
|
||||
editablePath.SetPoint(index, currentPoint);
|
||||
editablePath.SetPoint(nextIndex, nextPoint);
|
||||
editablePath.InsertPoint(newPointIndex, newPoint);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSelectedPoints()
|
||||
{
|
||||
var minPointCount = editablePath.isOpenEnded ? 2 : 3;
|
||||
|
||||
if (editablePath.pointCount > minPointCount)
|
||||
{
|
||||
var indices = editablePath.selection.elements.OrderByDescending( i => i);
|
||||
|
||||
foreach (var index in indices)
|
||||
if (editablePath.pointCount > minPointCount)
|
||||
editablePath.RemovePoint(index);
|
||||
|
||||
ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveSelectedPoints(Vector3 delta)
|
||||
{
|
||||
delta = Vector3.ProjectOnPlane(delta, editablePath.forward);
|
||||
|
||||
for (var i = 0; i < editablePath.pointCount; ++i)
|
||||
{
|
||||
if (editablePath.selection.Contains(i))
|
||||
{
|
||||
var controlPoint = editablePath.GetPoint(i);
|
||||
controlPoint.position += delta;
|
||||
editablePath.SetPoint(i, controlPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveEdge(int index, Vector3 delta)
|
||||
{
|
||||
if (editablePath.isOpenEnded && index == editablePath.pointCount - 1)
|
||||
return;
|
||||
|
||||
var controlPoint = editablePath.GetPoint(index);
|
||||
controlPoint.position += delta;
|
||||
editablePath.SetPoint(index, controlPoint);
|
||||
controlPoint = NextControlPoint(index);
|
||||
controlPoint.position += delta;
|
||||
editablePath.SetPoint(NextIndex(index), controlPoint);
|
||||
}
|
||||
|
||||
public void SetLeftTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedRightTangent, TangentMode cachedTangentMode)
|
||||
{
|
||||
var controlPoint = editablePath.GetPoint(index);
|
||||
controlPoint.tangentMode = cachedTangentMode;
|
||||
controlPoint.leftTangent = position;
|
||||
controlPoint.mirrorLeft = false;
|
||||
|
||||
if (setToLinear)
|
||||
{
|
||||
controlPoint.leftTangent = controlPoint.position;
|
||||
controlPoint.rightTangent = cachedRightTangent;
|
||||
}
|
||||
else if (controlPoint.tangentMode == TangentMode.Continuous || mirror)
|
||||
{
|
||||
var magnitude = controlPoint.localRightTangent.magnitude;
|
||||
|
||||
if (mirror)
|
||||
magnitude = controlPoint.localLeftTangent.magnitude;
|
||||
|
||||
controlPoint.localRightTangent = magnitude * -controlPoint.localLeftTangent.normalized;
|
||||
}
|
||||
|
||||
editablePath.SetPoint(index, controlPoint);
|
||||
editablePath.UpdateTangentMode(index);
|
||||
}
|
||||
|
||||
public void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent, TangentMode cachedTangentMode)
|
||||
{
|
||||
var controlPoint = editablePath.GetPoint(index);
|
||||
controlPoint.tangentMode = cachedTangentMode;
|
||||
controlPoint.rightTangent = position;
|
||||
controlPoint.mirrorLeft = true;
|
||||
|
||||
if (setToLinear)
|
||||
{
|
||||
controlPoint.rightTangent = controlPoint.position;
|
||||
controlPoint.leftTangent = cachedLeftTangent;
|
||||
}
|
||||
else if (controlPoint.tangentMode == TangentMode.Continuous || mirror)
|
||||
{
|
||||
var magnitude = controlPoint.localLeftTangent.magnitude;
|
||||
|
||||
if (mirror)
|
||||
magnitude = controlPoint.localRightTangent.magnitude;
|
||||
|
||||
controlPoint.localLeftTangent = magnitude * -controlPoint.localRightTangent.normalized;
|
||||
}
|
||||
|
||||
editablePath.SetPoint(index, controlPoint);
|
||||
editablePath.UpdateTangentMode(index);
|
||||
}
|
||||
|
||||
public void ClearClosestPath() { }
|
||||
public void AddClosestPath(float distance) { }
|
||||
|
||||
private Vector3 GetLeftTangentPosition(int index)
|
||||
{
|
||||
var isLinear = Mathf.Approximately(editablePath.GetPoint(index).localLeftTangent.sqrMagnitude, 0f);
|
||||
|
||||
if (isLinear)
|
||||
{
|
||||
var position = editablePath.GetPoint(index).position;
|
||||
var prevPosition = PrevControlPoint(index).position;
|
||||
|
||||
return (1f / 3f) * (prevPosition - position) + position;
|
||||
}
|
||||
|
||||
return editablePath.GetPoint(index).leftTangent;
|
||||
}
|
||||
|
||||
private Vector3 GetRightTangentPosition(int index)
|
||||
{
|
||||
var isLinear = Mathf.Approximately(editablePath.GetPoint(index).localRightTangent.sqrMagnitude, 0f);
|
||||
|
||||
if (isLinear)
|
||||
{
|
||||
var position = editablePath.GetPoint(index).position;
|
||||
var nextPosition = NextControlPoint(index).position;
|
||||
|
||||
return (1f / 3f) * (nextPosition - position) + position;
|
||||
}
|
||||
|
||||
return editablePath.GetPoint(index).rightTangent;
|
||||
}
|
||||
|
||||
private int NextIndex(int index)
|
||||
{
|
||||
return EditablePathUtility.Mod(index + 1, editablePath.pointCount);
|
||||
}
|
||||
|
||||
private ControlPoint NextControlPoint(int index)
|
||||
{
|
||||
return editablePath.GetPoint(NextIndex(index));
|
||||
}
|
||||
|
||||
private int PrevIndex(int index)
|
||||
{
|
||||
return EditablePathUtility.Mod(index - 1, editablePath.pointCount);
|
||||
}
|
||||
|
||||
private ControlPoint PrevControlPoint(int index)
|
||||
{
|
||||
return editablePath.GetPoint(PrevIndex(index));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal static class EditablePathExtensions
|
||||
{
|
||||
public static Polygon ToPolygon(this IEditablePath path)
|
||||
{
|
||||
var polygon = new Polygon()
|
||||
{
|
||||
isOpenEnded = path.isOpenEnded,
|
||||
points = new Vector3[path.pointCount]
|
||||
};
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
polygon.points[i] = path.GetPoint(i).position;
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
public static Spline ToSpline(this IEditablePath path)
|
||||
{
|
||||
var count = path.pointCount * 3;
|
||||
|
||||
if (path.isOpenEnded)
|
||||
count -= 2;
|
||||
|
||||
var spline = new Spline()
|
||||
{
|
||||
isOpenEnded = path.isOpenEnded,
|
||||
points = new Vector3[count]
|
||||
};
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
{
|
||||
var point = path.GetPoint(i);
|
||||
|
||||
spline.points[i*3] = point.position;
|
||||
|
||||
if (i * 3 + 1 < count)
|
||||
{
|
||||
var nextIndex = EditablePathUtility.Mod(i+1, path.pointCount);
|
||||
|
||||
spline.points[i*3 + 1] = path.CalculateRightTangent(i);
|
||||
spline.points[i*3 + 2] = path.CalculateLeftTangent(nextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return spline;
|
||||
}
|
||||
|
||||
public static Vector3 CalculateLocalLeftTangent(this IEditablePath path, int index)
|
||||
{
|
||||
return path.CalculateLeftTangent(index) - path.GetPoint(index).position;
|
||||
}
|
||||
|
||||
public static Vector3 CalculateLeftTangent(this IEditablePath path, int index)
|
||||
{
|
||||
var point = path.GetPoint(index);
|
||||
var isTangentLinear = point.localLeftTangent == Vector3.zero;
|
||||
var isEndpoint = path.isOpenEnded && index == 0;
|
||||
var tangent = point.leftTangent;
|
||||
|
||||
if (isEndpoint)
|
||||
return point.position;
|
||||
|
||||
if (isTangentLinear)
|
||||
{
|
||||
var prevPoint = path.GetPrevPoint(index);
|
||||
var v = prevPoint.position - point.position;
|
||||
tangent = point.position + v.normalized * (v.magnitude / 3f);
|
||||
}
|
||||
|
||||
return tangent;
|
||||
}
|
||||
|
||||
public static Vector3 CalculateLocalRightTangent(this IEditablePath path, int index)
|
||||
{
|
||||
return path.CalculateRightTangent(index) - path.GetPoint(index).position;
|
||||
}
|
||||
|
||||
public static Vector3 CalculateRightTangent(this IEditablePath path, int index)
|
||||
{
|
||||
var point = path.GetPoint(index);
|
||||
var isTangentLinear = point.localRightTangent == Vector3.zero;
|
||||
var isEndpoint = path.isOpenEnded && index == path.pointCount - 1;
|
||||
var tangent = point.rightTangent;
|
||||
|
||||
if (isEndpoint)
|
||||
return point.position;
|
||||
|
||||
if (isTangentLinear)
|
||||
{
|
||||
var nextPoint = path.GetNextPoint(index);
|
||||
var v = nextPoint.position - point.position;
|
||||
tangent = point.position + v.normalized * (v.magnitude / 3f);
|
||||
}
|
||||
|
||||
return tangent;
|
||||
}
|
||||
|
||||
public static ControlPoint GetPrevPoint(this IEditablePath path, int index)
|
||||
{
|
||||
return path.GetPoint(EditablePathUtility.Mod(index - 1, path.pointCount));
|
||||
}
|
||||
|
||||
public static ControlPoint GetNextPoint(this IEditablePath path, int index)
|
||||
{
|
||||
return path.GetPoint(EditablePathUtility.Mod(index + 1, path.pointCount));
|
||||
}
|
||||
|
||||
public static void UpdateTangentMode(this IEditablePath path, int index)
|
||||
{
|
||||
var localToWorldMatrix = path.localToWorldMatrix;
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
var controlPoint = path.GetPoint(index);
|
||||
var isLeftTangentLinear = controlPoint.localLeftTangent == Vector3.zero;
|
||||
var isRightTangentLinear = controlPoint.localRightTangent == Vector3.zero;
|
||||
|
||||
if (isLeftTangentLinear && isRightTangentLinear)
|
||||
controlPoint.tangentMode = TangentMode.Linear;
|
||||
else if (isLeftTangentLinear || isRightTangentLinear)
|
||||
controlPoint.tangentMode = TangentMode.Broken;
|
||||
else if (controlPoint.tangentMode != TangentMode.Continuous)
|
||||
controlPoint.tangentMode = TangentMode.Broken;
|
||||
|
||||
controlPoint.StoreTangents();
|
||||
path.SetPoint(index, controlPoint);
|
||||
path.localToWorldMatrix = localToWorldMatrix;
|
||||
}
|
||||
|
||||
public static void UpdateTangentsFromMode(this IEditablePath path)
|
||||
{
|
||||
const float kEpsilon = 0.001f;
|
||||
|
||||
var localToWorldMatrix = path.localToWorldMatrix;
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
{
|
||||
var controlPoint = path.GetPoint(i);
|
||||
|
||||
if (controlPoint.tangentMode == TangentMode.Linear)
|
||||
{
|
||||
controlPoint.localLeftTangent = Vector3.zero;
|
||||
controlPoint.localRightTangent = Vector3.zero;
|
||||
}
|
||||
else if (controlPoint.tangentMode == TangentMode.Broken)
|
||||
{
|
||||
var isLeftEndpoint = path.isOpenEnded && i == 0;
|
||||
var prevPoint = path.GetPrevPoint(i);
|
||||
var nextPoint = path.GetNextPoint(i);
|
||||
|
||||
var liniarLeftPosition = (prevPoint.position - controlPoint.position) / 3f;
|
||||
var isLeftTangentLinear = isLeftEndpoint || (controlPoint.localLeftTangent - liniarLeftPosition).sqrMagnitude < kEpsilon;
|
||||
|
||||
if (isLeftTangentLinear)
|
||||
controlPoint.localLeftTangent = Vector3.zero;
|
||||
|
||||
var isRightEndpoint = path.isOpenEnded && i == path.pointCount-1;
|
||||
var liniarRightPosition = (nextPoint.position - controlPoint.position) / 3f;
|
||||
var isRightTangentLinear = isRightEndpoint || (controlPoint.localRightTangent - liniarRightPosition).sqrMagnitude < kEpsilon;
|
||||
|
||||
if (isRightTangentLinear)
|
||||
controlPoint.localRightTangent = Vector3.zero;
|
||||
|
||||
if (isLeftTangentLinear && isRightTangentLinear)
|
||||
controlPoint.tangentMode = TangentMode.Linear;
|
||||
}
|
||||
else if (controlPoint.tangentMode == TangentMode.Continuous)
|
||||
{
|
||||
//TODO: ensure tangent continuity
|
||||
}
|
||||
|
||||
controlPoint.StoreTangents();
|
||||
path.SetPoint(i, controlPoint);
|
||||
}
|
||||
|
||||
path.localToWorldMatrix = localToWorldMatrix;
|
||||
}
|
||||
|
||||
public static void SetTangentMode(this IEditablePath path, int index, TangentMode tangentMode)
|
||||
{
|
||||
var localToWorldMatrix = path.localToWorldMatrix;
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
var controlPoint = path.GetPoint(index);
|
||||
var isEndpoint = path.isOpenEnded && (index == 0 || index == path.pointCount - 1);
|
||||
var oldTangentMode = controlPoint.tangentMode;
|
||||
|
||||
controlPoint.tangentMode = tangentMode;
|
||||
controlPoint.RestoreTangents();
|
||||
|
||||
if (tangentMode == TangentMode.Linear)
|
||||
{
|
||||
controlPoint.localLeftTangent = Vector3.zero;
|
||||
controlPoint.localRightTangent = Vector3.zero;
|
||||
}
|
||||
else if (tangentMode == TangentMode.Continuous && !isEndpoint)
|
||||
{
|
||||
var isLeftLinear = controlPoint.localLeftTangent == Vector3.zero;
|
||||
var isRightLinear = controlPoint.localRightTangent == Vector3.zero;
|
||||
var tangentDotProduct = Vector3.Dot(controlPoint.localLeftTangent.normalized, controlPoint.localRightTangent.normalized);
|
||||
var isContinous = tangentDotProduct < 0f && (tangentDotProduct + 1) < 0.001f;
|
||||
var isLinear = isLeftLinear && isRightLinear;
|
||||
|
||||
if ((isLinear || oldTangentMode == TangentMode.Broken) && !isContinous)
|
||||
{
|
||||
var prevPoint = path.GetPrevPoint(index);
|
||||
var nextPoint = path.GetNextPoint(index);
|
||||
var vLeft = prevPoint.position - controlPoint.position;
|
||||
var vRight = nextPoint.position - controlPoint.position;
|
||||
var rightDirection = Vector3.Cross(Vector3.Cross(vLeft, vRight), vLeft.normalized + vRight.normalized).normalized;
|
||||
var scale = 1f / 3f;
|
||||
|
||||
if (isLeftLinear)
|
||||
controlPoint.localLeftTangent = vLeft.magnitude * scale * -rightDirection;
|
||||
else
|
||||
controlPoint.localLeftTangent = controlPoint.localLeftTangent.magnitude * -rightDirection;
|
||||
|
||||
if (isRightLinear)
|
||||
controlPoint.localRightTangent = vRight.magnitude * scale * rightDirection;
|
||||
else
|
||||
controlPoint.localRightTangent = controlPoint.localRightTangent.magnitude * rightDirection;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var isLeftLinear = controlPoint.localLeftTangent == Vector3.zero;
|
||||
var isRightLinear = controlPoint.localRightTangent == Vector3.zero;
|
||||
|
||||
if (isLeftLinear || isRightLinear)
|
||||
{
|
||||
if (isLeftLinear)
|
||||
controlPoint.localLeftTangent = path.CalculateLocalLeftTangent(index);
|
||||
|
||||
if (isRightLinear)
|
||||
controlPoint.localRightTangent = path.CalculateLocalRightTangent(index);
|
||||
}
|
||||
}
|
||||
|
||||
controlPoint.StoreTangents();
|
||||
path.SetPoint(index, controlPoint);
|
||||
path.localToWorldMatrix = localToWorldMatrix;
|
||||
}
|
||||
|
||||
public static void MirrorTangent(this IEditablePath path, int index)
|
||||
{
|
||||
var localToWorldMatrix = path.localToWorldMatrix;
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
var controlPoint = path.GetPoint(index);
|
||||
|
||||
if (controlPoint.tangentMode == TangentMode.Linear)
|
||||
return;
|
||||
|
||||
if (!Mathf.Approximately((controlPoint.localLeftTangent + controlPoint.localRightTangent).sqrMagnitude, 0f))
|
||||
{
|
||||
if (controlPoint.mirrorLeft)
|
||||
controlPoint.localLeftTangent = -controlPoint.localRightTangent;
|
||||
else
|
||||
controlPoint.localRightTangent = -controlPoint.localLeftTangent;
|
||||
|
||||
controlPoint.StoreTangents();
|
||||
path.SetPoint(index, controlPoint);
|
||||
}
|
||||
|
||||
path.localToWorldMatrix = localToWorldMatrix;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class EditablePathUtility
|
||||
{
|
||||
public static int Mod(int x, int m)
|
||||
{
|
||||
int r = x % m;
|
||||
return r < 0 ? r + m : r;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface IEditablePath : ISelectable<Vector3>
|
||||
{
|
||||
ShapeType shapeType { get; set; }
|
||||
IUndoObject undoObject { get; set; }
|
||||
ISelection<int> selection { get; }
|
||||
Matrix4x4 localToWorldMatrix { get; set; }
|
||||
Vector3 forward { get; set; }
|
||||
Vector3 up { get; set; }
|
||||
Vector3 right { get; set; }
|
||||
bool isOpenEnded { get; set; }
|
||||
int pointCount { get; }
|
||||
ControlPoint GetPoint(int index);
|
||||
void SetPoint(int index, ControlPoint controlPoint);
|
||||
void AddPoint(ControlPoint controlPoint);
|
||||
void InsertPoint(int index, ControlPoint controlPoint);
|
||||
void RemovePoint(int index);
|
||||
void Clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface IEditablePathController
|
||||
{
|
||||
IEditablePath editablePath { get; set; }
|
||||
IEditablePath closestEditablePath { get; }
|
||||
ISnapping<Vector3> snapping { get; set; }
|
||||
bool enableSnapping { get; }
|
||||
void RegisterUndo(string name);
|
||||
void ClearSelection();
|
||||
void SelectPoint(int index, bool select);
|
||||
void CreatePoint(int index, Vector3 position);
|
||||
void RemoveSelectedPoints();
|
||||
void MoveSelectedPoints(Vector3 delta);
|
||||
void MoveEdge(int index, Vector3 delta);
|
||||
void SetLeftTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedRightTangent, TangentMode cachedTangentMode);
|
||||
void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent, TangentMode cachedTangentMode);
|
||||
void ClearClosestPath();
|
||||
void AddClosestPath(float distance);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface ISnapping<T>
|
||||
{
|
||||
T Snap(T value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface IUndoObject
|
||||
{
|
||||
void RegisterUndo(string name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class MultipleEditablePathController : IEditablePathController
|
||||
{
|
||||
private IEditablePathController m_Controller = new EditablePathController();
|
||||
private List<IEditablePath> m_Paths = new List<IEditablePath>();
|
||||
private float m_ClosestDistance = float.MaxValue;
|
||||
private IEditablePath m_ClosestPath;
|
||||
|
||||
public IEditablePath editablePath
|
||||
{
|
||||
get { return m_Controller.editablePath; }
|
||||
set { m_Controller.editablePath = value; }
|
||||
}
|
||||
|
||||
public IEditablePath closestEditablePath { get; private set; }
|
||||
|
||||
public ISnapping<Vector3> snapping
|
||||
{
|
||||
get { return m_Controller.snapping; }
|
||||
set { m_Controller.snapping = value; }
|
||||
}
|
||||
|
||||
public bool enableSnapping
|
||||
{
|
||||
get { return EditorSnapSettings.gridSnapEnabled; }
|
||||
}
|
||||
|
||||
public void ClearPaths()
|
||||
{
|
||||
m_Paths.Clear();
|
||||
}
|
||||
|
||||
public void AddPath(IEditablePath path)
|
||||
{
|
||||
if (!m_Paths.Contains(path))
|
||||
m_Paths.Add(path);
|
||||
}
|
||||
|
||||
public void RemovePath(IEditablePath path)
|
||||
{
|
||||
m_Paths.Remove(path);
|
||||
}
|
||||
|
||||
public void RegisterUndo(string name)
|
||||
{
|
||||
var current = editablePath;
|
||||
|
||||
ForEach((s) =>
|
||||
{
|
||||
editablePath = s;
|
||||
m_Controller.RegisterUndo(name);
|
||||
});
|
||||
|
||||
editablePath = current;
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
var current = editablePath;
|
||||
|
||||
ForEach((s) =>
|
||||
{
|
||||
editablePath = s;
|
||||
m_Controller.ClearSelection();
|
||||
});
|
||||
|
||||
editablePath = current;
|
||||
}
|
||||
|
||||
public void SelectPoint(int index, bool select)
|
||||
{
|
||||
m_Controller.SelectPoint(index, select);
|
||||
}
|
||||
|
||||
public void CreatePoint(int index, Vector3 position)
|
||||
{
|
||||
m_Controller.CreatePoint(index, position);
|
||||
}
|
||||
|
||||
public void RemoveSelectedPoints()
|
||||
{
|
||||
var current = editablePath;
|
||||
|
||||
ForEach((s) =>
|
||||
{
|
||||
editablePath = s;
|
||||
m_Controller.RemoveSelectedPoints();
|
||||
});
|
||||
|
||||
editablePath = current;
|
||||
}
|
||||
|
||||
public void MoveSelectedPoints(Vector3 delta)
|
||||
{
|
||||
var current = editablePath;
|
||||
|
||||
ForEach((s) =>
|
||||
{
|
||||
editablePath = s;
|
||||
m_Controller.MoveSelectedPoints(delta);
|
||||
});
|
||||
|
||||
editablePath = current;
|
||||
}
|
||||
|
||||
public void MoveEdge(int index, Vector3 delta)
|
||||
{
|
||||
m_Controller.MoveEdge(index, delta);
|
||||
}
|
||||
|
||||
public void SetLeftTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedRightTangent, TangentMode cachedTangentMode)
|
||||
{
|
||||
m_Controller.SetLeftTangent(index, position, setToLinear, mirror, cachedRightTangent, cachedTangentMode);
|
||||
}
|
||||
|
||||
public void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent, TangentMode cachedTangentMode)
|
||||
{
|
||||
m_Controller.SetRightTangent(index, position, setToLinear, mirror, cachedLeftTangent, cachedTangentMode);
|
||||
}
|
||||
|
||||
public void ClearClosestPath()
|
||||
{
|
||||
m_ClosestDistance = float.MaxValue;
|
||||
closestEditablePath = null;
|
||||
}
|
||||
|
||||
public void AddClosestPath(float distance)
|
||||
{
|
||||
if (distance <= m_ClosestDistance)
|
||||
{
|
||||
m_ClosestDistance = distance;
|
||||
closestEditablePath = editablePath;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForEach(Action<IEditablePath> action)
|
||||
{
|
||||
foreach(var path in m_Paths)
|
||||
{
|
||||
if (path == null)
|
||||
continue;
|
||||
|
||||
action(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class Snapping : ISnapping<Vector3>
|
||||
{
|
||||
public Vector3 Snap(Vector3 position)
|
||||
{
|
||||
return new Vector3(
|
||||
Snap(position.x, EditorSnapSettings.move.x),
|
||||
Snap(position.y, EditorSnapSettings.move.y),
|
||||
position.z);
|
||||
}
|
||||
|
||||
private float Snap(float value, float snap)
|
||||
{
|
||||
return Mathf.Round(value / snap) * snap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class GenericScriptablePath<T> : ScriptablePath
|
||||
{
|
||||
[SerializeField]
|
||||
private List<T> m_Data = new List<T>();
|
||||
|
||||
public T[] data
|
||||
{
|
||||
get { return m_Data.ToArray(); }
|
||||
set
|
||||
{
|
||||
if (value.Length != pointCount)
|
||||
throw new Exception("Custom data count does not match control point count");
|
||||
|
||||
m_Data.Clear();
|
||||
m_Data.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
|
||||
m_Data.Clear();
|
||||
}
|
||||
|
||||
public override void AddPoint(ControlPoint controlPoint)
|
||||
{
|
||||
base.AddPoint(controlPoint);
|
||||
|
||||
m_Data.Add(Create());
|
||||
}
|
||||
|
||||
public override void InsertPoint(int index, ControlPoint controlPoint)
|
||||
{
|
||||
base.InsertPoint(index, controlPoint);
|
||||
|
||||
m_Data.Insert(index, Create());
|
||||
}
|
||||
|
||||
public override void RemovePoint(int index)
|
||||
{
|
||||
base.RemovePoint(index);
|
||||
|
||||
Destroy(m_Data[index]);
|
||||
|
||||
m_Data.RemoveAt(index);
|
||||
}
|
||||
|
||||
public T GetData(int index)
|
||||
{
|
||||
return m_Data[index];
|
||||
}
|
||||
|
||||
public void SetData(int index, T data)
|
||||
{
|
||||
m_Data[index] = data;
|
||||
}
|
||||
|
||||
protected virtual T Create()
|
||||
{
|
||||
return Activator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
protected virtual void Destroy(T data) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class GenericScriptablePathInspector<U,T> : ScriptablePathInspector where U : ScriptableData<T>
|
||||
{
|
||||
private List<U> m_DataObjects = new List<U>();
|
||||
private List<U> m_SelectedDataObjects = new List<U>();
|
||||
private Editor m_CachedEditor = null;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
PrepareDataObjects();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DestroyDataObjects();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
DoCustomDataInspector();
|
||||
}
|
||||
|
||||
protected void DoCustomDataInspector()
|
||||
{
|
||||
PrepareDataObjects();
|
||||
|
||||
if (m_SelectedDataObjects.Count > 0)
|
||||
{
|
||||
CreateCachedEditor(m_SelectedDataObjects.ToArray(), null, ref m_CachedEditor);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
m_CachedEditor.OnInspectorGUI();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetDataObjects();
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareDataObjects()
|
||||
{
|
||||
var elementCount = 0;
|
||||
|
||||
m_SelectedDataObjects.Clear();
|
||||
|
||||
foreach(var path in paths)
|
||||
elementCount += path.pointCount;
|
||||
|
||||
while (m_DataObjects.Count < elementCount)
|
||||
CreateDataObject();
|
||||
|
||||
var index = 0;
|
||||
foreach(var path in paths)
|
||||
{
|
||||
var genericPath = path as GenericScriptablePath<T>;
|
||||
var customDataArray = genericPath.data;
|
||||
var length = customDataArray.Length;
|
||||
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
var dataObject = m_DataObjects[index + i];
|
||||
dataObject.data = customDataArray[i];
|
||||
|
||||
if (path.selection.Contains(i))
|
||||
{
|
||||
dataObject.owner = path.owner;
|
||||
dataObject.index = i;
|
||||
m_SelectedDataObjects.Add(dataObject);
|
||||
}
|
||||
}
|
||||
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDataObjects()
|
||||
{
|
||||
var index = 0;
|
||||
foreach(var path in paths)
|
||||
{
|
||||
var genericPath = path as GenericScriptablePath<T>;
|
||||
var customDataArray = genericPath.data;
|
||||
var length = customDataArray.Length;
|
||||
|
||||
for (var i = 0; i < length; ++i)
|
||||
customDataArray[i] = m_DataObjects[index + i].data;
|
||||
|
||||
genericPath.data = customDataArray;
|
||||
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
|
||||
private U CreateDataObject()
|
||||
{
|
||||
var dataObject = ScriptableObject.CreateInstance<U>();
|
||||
dataObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
m_DataObjects.Add(dataObject);
|
||||
return dataObject;
|
||||
}
|
||||
|
||||
private void DestroyDataObjects()
|
||||
{
|
||||
foreach (var customDataObject in m_DataObjects)
|
||||
DestroyImmediate(customDataObject);
|
||||
|
||||
m_DataObjects.Clear();
|
||||
m_SelectedDataObjects.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal abstract class PathComponentEditor<T> : Editor where T : ScriptablePath
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent snappingLabel = new GUIContent("Snapping", "Snap points using the snap settings");
|
||||
}
|
||||
|
||||
private Editor m_CachedEditor = null;
|
||||
|
||||
// Returns true on Changed.
|
||||
protected bool DoEditButton<U>(GUIContent icon, string label) where U : PathEditorTool<T>
|
||||
{
|
||||
const float kButtonWidth = 33;
|
||||
const float kButtonHeight = 23;
|
||||
const float k_SpaceBetweenLabelAndButton = 5;
|
||||
var buttonStyle = new GUIStyle("EditModeSingleButton");
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(true, kButtonHeight, buttonStyle);
|
||||
var buttonRect = new Rect(rect.xMin + EditorGUIUtility.labelWidth, rect.yMin, kButtonWidth, kButtonHeight);
|
||||
|
||||
var labelContent = new GUIContent(label);
|
||||
var labelSize = GUI.skin.label.CalcSize(labelContent);
|
||||
|
||||
var labelRect = new Rect(
|
||||
buttonRect.xMax + k_SpaceBetweenLabelAndButton,
|
||||
rect.yMin + (rect.height - labelSize.y) * .5f,
|
||||
labelSize.x,
|
||||
rect.height);
|
||||
|
||||
bool hasChanged = false;
|
||||
using (new EditorGUI.DisabledGroupScope(!EditorToolManager.IsAvailable<U>()))
|
||||
{
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
var isActive = GUI.Toggle(buttonRect, EditorToolManager.IsActiveTool<U>(), icon, buttonStyle);
|
||||
|
||||
GUI.Label(labelRect, label);
|
||||
|
||||
if (check.changed)
|
||||
{
|
||||
if (isActive)
|
||||
ToolManager.SetActiveTool<U>();
|
||||
else
|
||||
ToolManager.RestorePreviousTool();
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
protected void DoPathInspector<U>() where U : PathEditorTool<T>
|
||||
{
|
||||
if (EditorToolManager.IsActiveTool<U>() && EditorToolManager.IsAvailable<U>())
|
||||
{
|
||||
var paths = EditorToolManager.GetEditorTool<U>().paths;
|
||||
|
||||
CreateCachedEditor(paths, null, ref m_CachedEditor);
|
||||
|
||||
if (m_CachedEditor == null) //Needed to avoid a nullref on exiting playmode
|
||||
return;
|
||||
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
m_CachedEditor.OnInspectorGUI();
|
||||
|
||||
if (check.changed)
|
||||
EditorToolManager.GetEditorTool<U>().SetShapes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DoOpenEndedInspector<U>(SerializedProperty isOpenEndedProperty) where U : PathEditorTool<T>
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
EditorGUILayout.PropertyField(isOpenEndedProperty);
|
||||
|
||||
if (check.changed)
|
||||
{
|
||||
if (EditorToolManager.IsActiveTool<U>() && EditorToolManager.IsAvailable<U>())
|
||||
{
|
||||
var paths = EditorToolManager.GetEditorTool<U>().paths;
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
path.undoObject.RegisterUndo("Set Open Ended");
|
||||
path.isOpenEnded = isOpenEndedProperty.boolValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,491 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal static class PathEditorToolContents
|
||||
{
|
||||
internal static readonly GUIContent shapeToolIcon = IconContent("ShapeTool", "Start editing the Shape in the Scene View.");
|
||||
internal static readonly GUIContent shapeToolPro = IconContent("ShapeToolPro", "Start editing the Shape in the Scene View.");
|
||||
|
||||
internal static GUIContent IconContent(string name, string tooltip = null)
|
||||
{
|
||||
return new GUIContent(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.common/Path/Editor/Handles/" + name + ".png"), tooltip);
|
||||
}
|
||||
|
||||
public static GUIContent icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (EditorGUIUtility.isProSkin)
|
||||
return shapeToolPro;
|
||||
|
||||
return shapeToolIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IDuringSceneGuiTool
|
||||
{
|
||||
void DuringSceneGui(SceneView sceneView);
|
||||
bool IsAvailable();
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
internal class EditorToolManager
|
||||
{
|
||||
private static List<IDuringSceneGuiTool> m_Tools = new List<IDuringSceneGuiTool>();
|
||||
|
||||
static EditorToolManager()
|
||||
{
|
||||
SceneView.duringSceneGui += DuringSceneGui;
|
||||
}
|
||||
|
||||
internal static void Add(IDuringSceneGuiTool tool)
|
||||
{
|
||||
if (!m_Tools.Contains(tool) && tool is EditorTool)
|
||||
m_Tools.Add(tool);
|
||||
}
|
||||
|
||||
internal static void Remove(IDuringSceneGuiTool tool)
|
||||
{
|
||||
if (m_Tools.Contains(tool))
|
||||
m_Tools.Remove(tool);
|
||||
}
|
||||
|
||||
internal static bool IsActiveTool<T>() where T : EditorTool
|
||||
{
|
||||
return ToolManager.activeToolType.Equals(typeof(T));
|
||||
}
|
||||
|
||||
internal static bool IsAvailable<T>() where T : EditorTool
|
||||
{
|
||||
var tool = GetEditorTool<T>();
|
||||
|
||||
if (tool != null)
|
||||
return tool.IsAvailable();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static T GetEditorTool<T>() where T : EditorTool
|
||||
{
|
||||
foreach(var tool in m_Tools)
|
||||
{
|
||||
if (tool.GetType().Equals(typeof(T)))
|
||||
return tool as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void DuringSceneGui(SceneView sceneView)
|
||||
{
|
||||
foreach (var tool in m_Tools)
|
||||
{
|
||||
if (tool.IsAvailable() && ToolManager.IsActiveTool(tool as EditorTool))
|
||||
tool.DuringSceneGui(sceneView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class PathEditorTool<T> : EditorTool, IDuringSceneGuiTool where T : ScriptablePath
|
||||
{
|
||||
private Dictionary<UnityObject, T> m_Paths = new Dictionary<UnityObject, T>();
|
||||
private IGUIState m_GUIState = new GUIState();
|
||||
private Dictionary<UnityObject, PathEditor> m_PathEditors = new Dictionary<UnityObject, PathEditor>();
|
||||
private Dictionary<UnityObject, SerializedObject> m_SerializedObjects = new Dictionary<UnityObject, SerializedObject>();
|
||||
private MultipleEditablePathController m_Controller = new MultipleEditablePathController();
|
||||
private PointRectSelector m_RectSelector = new PointRectSelector();
|
||||
private bool m_IsActive = false;
|
||||
|
||||
public override bool gridSnapEnabled => true;
|
||||
|
||||
internal T[] paths
|
||||
{
|
||||
get { return m_Paths.Values.ToArray(); }
|
||||
}
|
||||
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return PathEditorToolContents.icon; }
|
||||
}
|
||||
|
||||
public override bool IsAvailable()
|
||||
{
|
||||
return targets.Count() > 0;
|
||||
}
|
||||
|
||||
public T GetPath(UnityObject targetObject)
|
||||
{
|
||||
var path = default(T);
|
||||
m_Paths.TryGetValue(targetObject, out path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public void SetPath(UnityObject target)
|
||||
{
|
||||
var path = GetPath(target);
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
var undoName = Undo.GetCurrentGroupName();
|
||||
var serializedObject = GetSerializedObject(target);
|
||||
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
|
||||
SetShape(path, serializedObject);
|
||||
|
||||
Undo.SetCurrentGroupName(undoName);
|
||||
}
|
||||
|
||||
private void RepaintInspectors()
|
||||
{
|
||||
var editorWindows = Resources.FindObjectsOfTypeAll<EditorWindow>();
|
||||
|
||||
foreach (var editorWindow in editorWindows)
|
||||
{
|
||||
if (editorWindow.titleContent.text == "Inspector")
|
||||
editorWindow.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_IsActive = false;
|
||||
EditorToolManager.Add(this);
|
||||
|
||||
SetupRectSelector();
|
||||
HandleActivation();
|
||||
|
||||
ToolManager.activeToolChanged += HandleActivation;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EditorToolManager.Remove(this);
|
||||
|
||||
ToolManager.activeToolChanged -= HandleActivation;
|
||||
UnregisterCallbacks();
|
||||
}
|
||||
|
||||
private void HandleActivation()
|
||||
{
|
||||
if (m_IsActive == false && ToolManager.IsActiveTool(this))
|
||||
Activate();
|
||||
else if (m_IsActive)
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
m_IsActive = true;
|
||||
RegisterCallbacks();
|
||||
InitializeCache();
|
||||
OnActivate();
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
{
|
||||
OnDeactivate();
|
||||
DestroyCache();
|
||||
UnregisterCallbacks();
|
||||
m_IsActive = false;
|
||||
}
|
||||
|
||||
private void RegisterCallbacks()
|
||||
{
|
||||
UnregisterCallbacks();
|
||||
Selection.selectionChanged += SelectionChanged;
|
||||
EditorApplication.playModeStateChanged += PlayModeStateChanged;
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
}
|
||||
|
||||
private void UnregisterCallbacks()
|
||||
{
|
||||
Selection.selectionChanged -= SelectionChanged;
|
||||
EditorApplication.playModeStateChanged -= PlayModeStateChanged;
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
}
|
||||
|
||||
private void DestroyCache()
|
||||
{
|
||||
foreach (var pair in m_Paths)
|
||||
{
|
||||
var path = pair.Value;
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
Undo.ClearUndo(path);
|
||||
UnityObject.DestroyImmediate(path);
|
||||
}
|
||||
}
|
||||
m_Paths.Clear();
|
||||
m_Controller.ClearPaths();
|
||||
m_PathEditors.Clear();
|
||||
m_SerializedObjects.Clear();
|
||||
}
|
||||
|
||||
private void UndoRedoPerformed()
|
||||
{
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetPath(target);
|
||||
|
||||
if (!path.modified)
|
||||
InitializePath(target);
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectionChanged()
|
||||
{
|
||||
InitializeCache();
|
||||
}
|
||||
|
||||
private void PlayModeStateChanged(PlayModeStateChange stateChange)
|
||||
{
|
||||
if (stateChange == PlayModeStateChange.EnteredEditMode)
|
||||
EditorApplication.delayCall += () => { InitializeCache(); }; //HACK: At this point target is null. Let's wait to next frame to refresh.
|
||||
}
|
||||
|
||||
private void SetupRectSelector()
|
||||
{
|
||||
m_RectSelector.onSelectionBegin = BeginSelection;
|
||||
m_RectSelector.onSelectionChanged = UpdateSelection;
|
||||
m_RectSelector.onSelectionEnd = EndSelection;
|
||||
}
|
||||
|
||||
private void ForEachTarget(Action<UnityObject> action)
|
||||
{
|
||||
foreach(var target in targets)
|
||||
{
|
||||
if (target == null)
|
||||
continue;
|
||||
|
||||
action(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCache()
|
||||
{
|
||||
m_Controller.ClearPaths();
|
||||
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetOrCreatePath(target);
|
||||
var pointCount = path.pointCount;
|
||||
|
||||
InitializePath(target);
|
||||
|
||||
if (pointCount != path.pointCount)
|
||||
path.selection.Clear();
|
||||
|
||||
CreatePathEditor(target);
|
||||
|
||||
m_Controller.AddPath(path);
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializePath(UnityObject target)
|
||||
{
|
||||
IShape shape = null;
|
||||
ControlPoint[] controlPoints = null;
|
||||
|
||||
try
|
||||
{
|
||||
shape = GetShape(target);
|
||||
controlPoints = shape.ToControlPoints();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e.Message);
|
||||
}
|
||||
|
||||
var path = GetPath(target);
|
||||
path.Clear();
|
||||
|
||||
if (shape != null && controlPoints != null)
|
||||
{
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
path.shapeType = shape.type;
|
||||
path.isOpenEnded = shape.isOpenEnded;
|
||||
|
||||
foreach (var controlPoint in controlPoints)
|
||||
path.AddPoint(controlPoint);
|
||||
}
|
||||
|
||||
Initialize(path, GetSerializedObject(target));
|
||||
}
|
||||
|
||||
private T GetOrCreatePath(UnityObject targetObject)
|
||||
{
|
||||
var path = GetPath(targetObject);
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
path = ScriptableObject.CreateInstance<T>();
|
||||
path.hideFlags = HideFlags.HideAndDontSave;
|
||||
path.owner = targetObject;
|
||||
m_Paths[targetObject] = path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private PathEditor GetPathEditor(UnityObject target)
|
||||
{
|
||||
PathEditor pathEditor;
|
||||
m_PathEditors.TryGetValue(target, out pathEditor);
|
||||
return pathEditor;
|
||||
}
|
||||
|
||||
private void CreatePathEditor(UnityObject target)
|
||||
{
|
||||
var pathEditor = new PathEditor();
|
||||
pathEditor.controller = m_Controller;
|
||||
pathEditor.drawerOverride = GetCustomDrawer(target);
|
||||
m_PathEditors[target] = pathEditor;
|
||||
}
|
||||
|
||||
private SerializedObject GetSerializedObject(UnityObject target)
|
||||
{
|
||||
var serializedObject = default(SerializedObject);
|
||||
|
||||
if (!m_SerializedObjects.TryGetValue(target, out serializedObject))
|
||||
{
|
||||
serializedObject = new SerializedObject(target);
|
||||
m_SerializedObjects[target] = serializedObject;
|
||||
}
|
||||
|
||||
return serializedObject;
|
||||
}
|
||||
|
||||
void IDuringSceneGuiTool.DuringSceneGui(SceneView sceneView)
|
||||
{
|
||||
if (m_GUIState.eventType == EventType.Layout)
|
||||
m_Controller.ClearClosestPath();
|
||||
|
||||
m_RectSelector.OnGUI();
|
||||
|
||||
bool changed = false;
|
||||
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetPath(target);
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
path.localToWorldMatrix = GetLocalToWorldMatrix(target);
|
||||
path.forward = GetForward(target);
|
||||
path.up = GetUp(target);
|
||||
path.right = GetRight(target);
|
||||
m_Controller.editablePath = path;
|
||||
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
var pathEditor = GetPathEditor(target);
|
||||
pathEditor.linearTangentIsZero = GetLinearTangentIsZero(target);
|
||||
pathEditor.OnGUI();
|
||||
OnCustomGUI(path);
|
||||
changed |= check.changed;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (changed)
|
||||
{
|
||||
SetShapes();
|
||||
RepaintInspectors();
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginSelection(ISelector<Vector3> selector, bool isAdditive)
|
||||
{
|
||||
m_Controller.RegisterUndo("Selection");
|
||||
|
||||
if (isAdditive)
|
||||
{
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetPath(target);
|
||||
path.selection.BeginSelection();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSelection(selector);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelection(ISelector<Vector3> selector)
|
||||
{
|
||||
var repaintInspectors = false;
|
||||
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetPath(target);
|
||||
|
||||
repaintInspectors |= path.Select(selector);
|
||||
});
|
||||
|
||||
if (repaintInspectors)
|
||||
RepaintInspectors();
|
||||
}
|
||||
|
||||
private void EndSelection(ISelector<Vector3> selector)
|
||||
{
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
var path = GetPath(target);
|
||||
path.selection.EndSelection(true);
|
||||
});
|
||||
}
|
||||
|
||||
internal void SetShapes()
|
||||
{
|
||||
ForEachTarget((target) =>
|
||||
{
|
||||
SetPath(target);
|
||||
});
|
||||
}
|
||||
|
||||
private Transform GetTransform(UnityObject target)
|
||||
{
|
||||
return (target as Component).transform;
|
||||
}
|
||||
|
||||
private Matrix4x4 GetLocalToWorldMatrix(UnityObject target)
|
||||
{
|
||||
return GetTransform(target).localToWorldMatrix;
|
||||
}
|
||||
|
||||
private Vector3 GetForward(UnityObject target)
|
||||
{
|
||||
return GetTransform(target).forward;
|
||||
}
|
||||
|
||||
private Vector3 GetUp(UnityObject target)
|
||||
{
|
||||
return GetTransform(target).up;
|
||||
}
|
||||
|
||||
private Vector3 GetRight(UnityObject target)
|
||||
{
|
||||
return GetTransform(target).right;
|
||||
}
|
||||
|
||||
protected abstract IShape GetShape(UnityObject target);
|
||||
protected virtual void Initialize(T path, SerializedObject serializedObject) { }
|
||||
protected abstract void SetShape(T path, SerializedObject serializedObject);
|
||||
protected virtual void OnActivate() { }
|
||||
protected virtual void OnDeactivate() { }
|
||||
protected virtual void OnCustomGUI(T path) { }
|
||||
protected virtual bool GetLinearTangentIsZero(UnityObject target) { return false; }
|
||||
protected virtual IDrawer GetCustomDrawer(UnityObject target) { return null; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal static class PathEditorToolExtensions
|
||||
{
|
||||
public static void CycleTangentMode<T>(this PathEditorTool<T> pathEditorTool) where T : ScriptablePath
|
||||
{
|
||||
var first = true;
|
||||
var mixed = false;
|
||||
var tangentMode = TangentMode.Linear;
|
||||
var targets = pathEditorTool.targets;
|
||||
|
||||
foreach(var target in targets)
|
||||
{
|
||||
var path = pathEditorTool.GetPath(target);
|
||||
|
||||
if (path.selection.Count == 0)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
{
|
||||
if (!path.selection.Contains(i))
|
||||
continue;
|
||||
|
||||
var point = path.GetPoint(i);
|
||||
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
tangentMode = point.tangentMode;
|
||||
}
|
||||
else if (point.tangentMode != tangentMode)
|
||||
{
|
||||
mixed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mixed)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mixed)
|
||||
tangentMode = TangentMode.Linear;
|
||||
else
|
||||
tangentMode = GetNextTangentMode(tangentMode);
|
||||
|
||||
foreach(var target in targets)
|
||||
{
|
||||
var path = pathEditorTool.GetPath(target);
|
||||
|
||||
if (path.selection.Count == 0)
|
||||
continue;
|
||||
|
||||
path.undoObject.RegisterUndo("Cycle Tangent Mode");
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
{
|
||||
if (!path.selection.Contains(i))
|
||||
continue;
|
||||
|
||||
path.SetTangentMode(i, tangentMode);
|
||||
}
|
||||
|
||||
pathEditorTool.SetPath(target);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MirrorTangent<T>(this PathEditorTool<T> pathEditorTool) where T : ScriptablePath
|
||||
{
|
||||
var targets = pathEditorTool.targets;
|
||||
|
||||
foreach(var target in targets)
|
||||
{
|
||||
var path = pathEditorTool.GetPath(target);
|
||||
|
||||
if (path.selection.Count == 0)
|
||||
continue;
|
||||
|
||||
path.undoObject.RegisterUndo("Mirror Tangents");
|
||||
|
||||
for (var i = 0; i < path.pointCount; ++i)
|
||||
{
|
||||
if (!path.selection.Contains(i))
|
||||
continue;
|
||||
|
||||
path.MirrorTangent(i);
|
||||
}
|
||||
|
||||
pathEditorTool.SetPath(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static TangentMode GetNextTangentMode(TangentMode tangentMode)
|
||||
{
|
||||
return (TangentMode)((((int)tangentMode) + 1) % Enum.GetValues(typeof(TangentMode)).Length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class ScriptableData<T> : ScriptableObject
|
||||
{
|
||||
[SerializeField]
|
||||
private T m_Data;
|
||||
public UnityObject owner { get; set; }
|
||||
public int index { get; set; }
|
||||
|
||||
public T data
|
||||
{
|
||||
get { return m_Data; }
|
||||
set { m_Data = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class ScriptablePath : ScriptableObject, IEditablePath, IUndoObject
|
||||
{
|
||||
[SerializeField]
|
||||
private EditablePath m_EditablePath = new EditablePath();
|
||||
[SerializeField]
|
||||
private bool m_Modified = false;
|
||||
|
||||
internal bool modified
|
||||
{
|
||||
get { return m_Modified; }
|
||||
}
|
||||
|
||||
internal UnityObject owner { get; set; }
|
||||
|
||||
public ShapeType shapeType
|
||||
{
|
||||
get { return m_EditablePath.shapeType; }
|
||||
set { m_EditablePath.shapeType = value; }
|
||||
}
|
||||
|
||||
public IUndoObject undoObject
|
||||
{
|
||||
get { return this; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public ISelection<int> selection
|
||||
{
|
||||
get { return m_EditablePath.selection; }
|
||||
}
|
||||
|
||||
public Matrix4x4 localToWorldMatrix
|
||||
{
|
||||
get { return m_EditablePath.localToWorldMatrix; }
|
||||
set { m_EditablePath.localToWorldMatrix = value; }
|
||||
}
|
||||
|
||||
public Vector3 forward
|
||||
{
|
||||
get { return m_EditablePath.forward; }
|
||||
set { m_EditablePath.forward = value; }
|
||||
}
|
||||
|
||||
public Vector3 up
|
||||
{
|
||||
get { return m_EditablePath.up; }
|
||||
set { m_EditablePath.up = value; }
|
||||
}
|
||||
|
||||
public Vector3 right
|
||||
{
|
||||
get { return m_EditablePath.right; }
|
||||
set { m_EditablePath.right = value; }
|
||||
}
|
||||
|
||||
public bool isOpenEnded
|
||||
{
|
||||
get { return m_EditablePath.isOpenEnded; }
|
||||
set { m_EditablePath.isOpenEnded = value; }
|
||||
}
|
||||
|
||||
public int pointCount
|
||||
{
|
||||
get { return m_EditablePath.pointCount; }
|
||||
}
|
||||
|
||||
public bool Select(ISelector<Vector3> selector)
|
||||
{
|
||||
return m_EditablePath.Select(selector);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
m_EditablePath.Clear();
|
||||
}
|
||||
|
||||
public virtual ControlPoint GetPointLocal(int index)
|
||||
{
|
||||
return m_EditablePath.GetPointLocal(index);
|
||||
}
|
||||
|
||||
public virtual ControlPoint GetPoint(int index)
|
||||
{
|
||||
return m_EditablePath.GetPoint(index);
|
||||
}
|
||||
|
||||
public virtual void SetPoint(int index, ControlPoint controlPoint)
|
||||
{
|
||||
m_EditablePath.SetPoint(index, controlPoint);
|
||||
}
|
||||
|
||||
public virtual void AddPoint(ControlPoint controlPoint)
|
||||
{
|
||||
m_EditablePath.AddPoint(controlPoint);
|
||||
}
|
||||
|
||||
public virtual void InsertPoint(int index, ControlPoint controlPoint)
|
||||
{
|
||||
m_EditablePath.InsertPoint(index, controlPoint);
|
||||
}
|
||||
|
||||
public virtual void RemovePoint(int index)
|
||||
{
|
||||
m_EditablePath.RemovePoint(index);
|
||||
}
|
||||
|
||||
void IUndoObject.RegisterUndo(string name)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(this, name);
|
||||
m_Modified = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(ScriptablePath), true)]
|
||||
internal class ScriptablePathInspector : Editor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent linearIcon = IconContent("TangentLinear", "TangentLinearPro", "Linear");
|
||||
public static readonly GUIContent continuousIcon = IconContent("TangentContinuous", "TangentContinuousPro", "Continuous");
|
||||
public static readonly GUIContent brokenIcon = IconContent("TangentBroken", "TangentBrokenPro", "Broken");
|
||||
public static readonly GUIContent positionLabel = new GUIContent("Position", "Position of the Control Point");
|
||||
public static readonly GUIContent enableSnapLabel = new GUIContent("Snapping", "Snap points using the snap settings");
|
||||
public static readonly GUIContent tangentModeLabel = new GUIContent("Tangent Mode");
|
||||
public static readonly GUIContent pointLabel = new GUIContent("Point");
|
||||
|
||||
|
||||
private static GUIContent IconContent(string name, string tooltip = null)
|
||||
{
|
||||
return new GUIContent(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.common/Path/Editor/Handles/" + name + ".png"), tooltip);
|
||||
}
|
||||
|
||||
private static GUIContent IconContent(string personal, string pro, string tooltip)
|
||||
{
|
||||
if (EditorGUIUtility.isProSkin)
|
||||
return IconContent(pro, tooltip);
|
||||
|
||||
return IconContent(personal, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ScriptablePath> m_Paths = null;
|
||||
private bool m_Dragged = false;
|
||||
|
||||
protected List<ScriptablePath> paths
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Paths == null)
|
||||
m_Paths = targets.Select( t => t as ScriptablePath).ToList();
|
||||
|
||||
return m_Paths;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DoTangentModeInspector();
|
||||
DoPositionInspector();
|
||||
}
|
||||
|
||||
protected void DoTangentModeInspector()
|
||||
{
|
||||
if (!IsAnyShapeType(ShapeType.Spline))
|
||||
return;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(Contents.tangentModeLabel);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(!IsAnyPointSelected()))
|
||||
{
|
||||
if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Linear), Contents.linearIcon))
|
||||
SetMixedTangentMode(TangentMode.Linear);
|
||||
|
||||
if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Continuous), Contents.continuousIcon))
|
||||
SetMixedTangentMode(TangentMode.Continuous);
|
||||
|
||||
if (DoToggle(GetToggleStateFromTangentMode(TangentMode.Broken), Contents.brokenIcon))
|
||||
SetMixedTangentMode(TangentMode.Broken);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
protected void DoPositionInspector()
|
||||
{
|
||||
var showMixedValue = EditorGUI.showMixedValue;
|
||||
var wideMode = EditorGUIUtility.wideMode;
|
||||
|
||||
var position = Vector3.zero;
|
||||
var isMixed = GetMixedPosition(out position);
|
||||
|
||||
EditorGUI.showMixedValue = isMixed;
|
||||
EditorGUIUtility.wideMode = true;
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(!IsAnyPointSelected()))
|
||||
{
|
||||
if (GUIUtility.hotControl == 0)
|
||||
m_Dragged = false;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var delta = EditorGUILayout.Vector2Field(Contents.positionLabel, position) - (Vector2)position;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (m_Dragged == false)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
path.undoObject.RegisterUndo("Point Position");
|
||||
|
||||
m_Dragged = true;
|
||||
}
|
||||
|
||||
SetMixedDeltaPosition(delta);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = showMixedValue;
|
||||
EditorGUIUtility.wideMode = wideMode;
|
||||
}
|
||||
|
||||
private bool DoToggle(bool value, GUIContent icon)
|
||||
{
|
||||
const float kButtonWidth = 33f;
|
||||
const float kButtonHeight = 23f;
|
||||
var buttonStyle = new GUIStyle("EditModeSingleButton");
|
||||
|
||||
var changed = false;
|
||||
using (var check = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
value = GUILayout.Toggle(value, icon, buttonStyle, GUILayout.Width(kButtonWidth), GUILayout.Height(kButtonHeight));
|
||||
changed = check.changed;
|
||||
}
|
||||
|
||||
return value && changed;
|
||||
}
|
||||
|
||||
private bool GetToggleStateFromTangentMode(TangentMode mode)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
{
|
||||
var selection = path.selection;
|
||||
|
||||
foreach (var index in selection.elements)
|
||||
if (path.GetPoint(index).tangentMode != mode)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetMixedTangentMode(TangentMode tangentMode)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
{
|
||||
path.undoObject.RegisterUndo("Tangent Mode");
|
||||
|
||||
foreach (var index in path.selection.elements)
|
||||
path.SetTangentMode(index, tangentMode);
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
private bool GetMixedPosition(out Vector3 position)
|
||||
{
|
||||
var first = true;
|
||||
position = Vector3.zero;
|
||||
|
||||
var activeObject = Selection.activeObject as GameObject;
|
||||
if (Selection.count > 1 || !activeObject)
|
||||
return true;
|
||||
|
||||
foreach(var path in paths)
|
||||
{
|
||||
MonoBehaviour behavior = path.owner as MonoBehaviour;
|
||||
if (!behavior || activeObject != behavior.gameObject)
|
||||
continue;
|
||||
var selection = path.selection;
|
||||
|
||||
foreach (var index in selection.elements)
|
||||
{
|
||||
var controlPoint = path.GetPointLocal(index);
|
||||
|
||||
if (first)
|
||||
{
|
||||
position = controlPoint.position;
|
||||
first = false;
|
||||
}
|
||||
else if (position != controlPoint.position)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetMixedDeltaPosition(Vector3 delta)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
{
|
||||
var selection = path.selection;
|
||||
var matrix = path.localToWorldMatrix;
|
||||
|
||||
path.localToWorldMatrix = Matrix4x4.identity;
|
||||
|
||||
foreach (var index in selection.elements)
|
||||
{
|
||||
var controlPoint = path.GetPoint(index);
|
||||
controlPoint.position += delta;
|
||||
path.SetPoint(index, controlPoint);
|
||||
}
|
||||
|
||||
path.localToWorldMatrix = matrix;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAnyShapeType(ShapeType shapeType)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
if (path.shapeType == shapeType)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool IsAnyPointSelected()
|
||||
{
|
||||
foreach(var path in paths)
|
||||
if (path.selection.Count > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 363 B |
After Width: | Height: | Size: 373 B |
After Width: | Height: | Size: 366 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 381 B |
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class CreatePointAction : ClickAction
|
||||
{
|
||||
private Control m_PointControl;
|
||||
|
||||
public Func<IGUIState, Vector2, Vector3> guiToWorld;
|
||||
public Action<int, Vector3> onCreatePoint;
|
||||
public CreatePointAction(Control pointControl, Control edgeControl) : base(edgeControl, 0, false)
|
||||
{
|
||||
m_PointControl = pointControl;
|
||||
}
|
||||
|
||||
protected override void OnTrigger(IGUIState guiState)
|
||||
{
|
||||
base.OnTrigger(guiState);
|
||||
|
||||
var index = hoveredControl.layoutData.index;
|
||||
var position = GetMousePositionWorld(guiState);
|
||||
|
||||
if (onCreatePoint != null)
|
||||
onCreatePoint(index, position);
|
||||
|
||||
guiState.nearestControl = m_PointControl.ID;
|
||||
|
||||
var data = m_PointControl.layoutData;
|
||||
data.index = index + 1;
|
||||
data.position = position;
|
||||
data.distance = 0f;
|
||||
|
||||
m_PointControl.layoutData = data;
|
||||
|
||||
guiState.changed = true;
|
||||
}
|
||||
|
||||
private Vector3 GetMousePositionWorld(IGUIState guiState)
|
||||
{
|
||||
if (guiToWorld != null)
|
||||
return guiToWorld(guiState, guiState.mousePosition);
|
||||
|
||||
return guiState.GUIToWorld(guiState.mousePosition, hoveredControl.layoutData.forward, hoveredControl.layoutData.position);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class Drawer : IDrawer
|
||||
{
|
||||
internal class Styles
|
||||
{
|
||||
public readonly GUIStyle pointNormalStyle;
|
||||
public readonly GUIStyle tangentNormalStyle;
|
||||
public readonly GUIStyle tangentHoveredStyle;
|
||||
|
||||
public Styles()
|
||||
{
|
||||
var pointNormal = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.common/Path/Editor/Handles/Path/pointNormal.png");
|
||||
pointNormalStyle = CreateStyle(pointNormal, Vector2.one * 12f);
|
||||
tangentNormalStyle = CreateStyle(pointNormal, Vector2.one * 8f);
|
||||
tangentHoveredStyle = CreateStyle(pointNormal, Vector2.one * 10f);
|
||||
}
|
||||
|
||||
private GUIStyle CreateStyle(Texture2D texture, Vector2 size)
|
||||
{
|
||||
var guiStyle = new GUIStyle();
|
||||
guiStyle.normal.background = texture;
|
||||
guiStyle.fixedWidth = size.x;
|
||||
guiStyle.fixedHeight = size.y;
|
||||
|
||||
return guiStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private IGUIState m_GUIState = new GUIState();
|
||||
private Styles m_Styles;
|
||||
private Styles styles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Styles == null)
|
||||
m_Styles = new Styles();
|
||||
|
||||
return m_Styles;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawCreatePointPreview(Vector3 position, Color color)
|
||||
{
|
||||
Color saved = GUI.color;
|
||||
GUI.color = color;
|
||||
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
|
||||
GUI.color = saved;
|
||||
}
|
||||
|
||||
public void DrawPoint(Vector3 position, Color color)
|
||||
{
|
||||
Color saved = GUI.color;
|
||||
GUI.color = color;
|
||||
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
|
||||
GUI.color = saved;
|
||||
}
|
||||
|
||||
public void DrawPointHovered(Vector3 position, Color color)
|
||||
{
|
||||
Color saved = GUI.color;
|
||||
GUI.color = color;
|
||||
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
|
||||
GUI.color = saved;
|
||||
}
|
||||
|
||||
public void DrawPointSelected(Vector3 position, Color color)
|
||||
{
|
||||
Color saved = GUI.color;
|
||||
GUI.color = color;
|
||||
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
|
||||
GUI.color = saved;
|
||||
}
|
||||
|
||||
public void DrawLine(Vector3 p1, Vector3 p2, float width, Color color)
|
||||
{
|
||||
Handles.color = color;
|
||||
Handles.DrawAAPolyLine(width, new Vector3[] { p1, p2 });
|
||||
}
|
||||
|
||||
public void DrawBezier(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float width, Color color)
|
||||
{
|
||||
Handles.color = color;
|
||||
Handles.DrawBezier(p1, p4, p2, p3, color, null, width);
|
||||
}
|
||||
|
||||
public void DrawTangent(Vector3 position, Vector3 tangent, Color color)
|
||||
{
|
||||
DrawLine(position, tangent, 3f, color);
|
||||
Color saved = GUI.color;
|
||||
GUI.color = color;
|
||||
DrawGUIStyleCap(0, tangent, Quaternion.identity, m_GUIState.GetHandleSize(tangent), styles.tangentNormalStyle);
|
||||
GUI.color = saved;
|
||||
}
|
||||
|
||||
|
||||
private void DrawGUIStyleCap(int controlID, Vector3 position, Quaternion rotation, float size, GUIStyle guiStyle)
|
||||
{
|
||||
if (Camera.current && Vector3.Dot(position - Camera.current.transform.position, Camera.current.transform.forward) < 0f)
|
||||
return;
|
||||
|
||||
Handles.BeginGUI();
|
||||
guiStyle.Draw(GetGUIStyleRect(guiStyle, position), GUIContent.none, controlID);
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
private Rect GetGUIStyleRect(GUIStyle style, Vector3 position)
|
||||
{
|
||||
Vector2 vector = HandleUtility.WorldToGUIPoint(position);
|
||||
|
||||
float fixedWidth = style.fixedWidth;
|
||||
float fixedHeight = style.fixedHeight;
|
||||
|
||||
return new Rect(vector.x - fixedWidth / 2f, vector.y - fixedHeight / 2f, fixedWidth, fixedHeight);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Action to process when the user clicks a particular mouse button a certain number of times.
|
||||
/// </summary>
|
||||
internal class ClickAction : HoveredControlAction
|
||||
{
|
||||
private int m_Button;
|
||||
private bool m_UseEvent;
|
||||
/// <summary>
|
||||
/// The number of button clicks required to satisfy the trigger condition
|
||||
/// </summary>
|
||||
public int clickCount = 1;
|
||||
/// <summary>
|
||||
/// The Action to execute when the user satisfies the trigger condition.
|
||||
/// </summary>
|
||||
public Action<IGUIState, Control> onClick;
|
||||
private int m_ClickCounter = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of ClickAction
|
||||
/// </summary>
|
||||
/// <param name="control">Current control</param>
|
||||
/// <param name="button">The mouse button to check for.</param>
|
||||
/// <param name="useEvent">Whether to Use the current event after the trigger condition has been met.</param>
|
||||
public ClickAction(Control control, int button, bool useEvent = true) : base(control)
|
||||
{
|
||||
m_Button = button;
|
||||
m_UseEvent = useEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the trigger condition has been met or not.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the trigger condition has been met. Otherwise, returns false.</returns>
|
||||
protected override bool GetTriggerContidtion(IGUIState guiState)
|
||||
{
|
||||
if (guiState.mouseButton == m_Button && guiState.eventType == EventType.MouseDown)
|
||||
{
|
||||
if (guiState.clickCount == 1)
|
||||
m_ClickCounter = 0;
|
||||
|
||||
++m_ClickCounter;
|
||||
|
||||
if (m_ClickCounter == clickCount)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when the trigger conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnTrigger(IGUIState guiState)
|
||||
{
|
||||
base.OnTrigger(guiState);
|
||||
|
||||
if (onClick != null)
|
||||
onClick(guiState, hoveredControl);
|
||||
|
||||
if (m_UseEvent)
|
||||
guiState.UseEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the finish condition has been met or not. For a ClickAction, this is always `true`.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true`.</returns>
|
||||
protected override bool GetFinishContidtion(IGUIState guiState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Action to process when the custom editor validates a command.
|
||||
/// </summary>
|
||||
internal class CommandAction : GUIAction
|
||||
{
|
||||
private string m_CommandName;
|
||||
|
||||
/// <summary>
|
||||
/// The Action to execute.
|
||||
/// </summary>
|
||||
public Action<IGUIState> onCommand;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of CommandAction
|
||||
/// </summary>
|
||||
/// <param name="commandName">The name of the command. When the custom editor validates a command with this name, it triggers the action.</param>
|
||||
public CommandAction(string commandName)
|
||||
{
|
||||
m_CommandName = commandName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the trigger condition has been met or not.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the trigger condition has been met. Otherwise, returns `false`.</returns>
|
||||
protected override bool GetTriggerContidtion(IGUIState guiState)
|
||||
{
|
||||
if (guiState.eventType == EventType.ValidateCommand && guiState.commandName == m_CommandName)
|
||||
{
|
||||
guiState.UseEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the finish condition has been met or not.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the trigger condition is finished. Otherwise, returns `false`.</returns>
|
||||
protected override bool GetFinishContidtion(IGUIState guiState)
|
||||
{
|
||||
if (guiState.eventType == EventType.ExecuteCommand && guiState.commandName == m_CommandName)
|
||||
{
|
||||
guiState.UseEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when the finish condition is met.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnFinish(IGUIState guiState)
|
||||
{
|
||||
if (onCommand != null)
|
||||
onCommand(guiState);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a UI control in a custom editor.
|
||||
/// </summary>
|
||||
internal abstract class Control
|
||||
{
|
||||
private string m_Name;
|
||||
private int m_NameHashCode;
|
||||
private int m_ID;
|
||||
private LayoutData m_LayoutData;
|
||||
private int m_ActionID = -1;
|
||||
private LayoutData m_HotLayoutData;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the control.
|
||||
/// </summary>
|
||||
public string name
|
||||
{
|
||||
get { return m_Name; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The control ID. The GUI uses this to identify the control.
|
||||
/// </summary>
|
||||
public int ID
|
||||
{
|
||||
get { return m_ID; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action ID.
|
||||
/// </summary>
|
||||
public int actionID
|
||||
{
|
||||
get { return m_ActionID; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The control's layout data. This contains information about the control's position and orientation.
|
||||
/// </summary>
|
||||
public LayoutData layoutData
|
||||
{
|
||||
get { return m_LayoutData; }
|
||||
set { m_LayoutData = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The control's hot layout data
|
||||
/// </summary>
|
||||
public LayoutData hotLayoutData
|
||||
{
|
||||
get { return m_HotLayoutData; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of Control
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the control</param>
|
||||
public Control(string name)
|
||||
{
|
||||
m_Name = name;
|
||||
m_NameHashCode = name.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control from the guiState.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void GetControl(IGUIState guiState)
|
||||
{
|
||||
m_ID = guiState.GetControlID(m_NameHashCode, FocusType.Passive);
|
||||
}
|
||||
|
||||
internal void SetActionID(int actionID)
|
||||
{
|
||||
m_ActionID = actionID;
|
||||
m_HotLayoutData = m_LayoutData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins the layout for this control. A call to EndLayout must always follow a call to this function.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void BeginLayout(IGUIState guiState)
|
||||
{
|
||||
Debug.Assert(guiState.eventType == EventType.Layout);
|
||||
|
||||
m_LayoutData = OnBeginLayout(LayoutData.zero, guiState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control's layout data from the guiState.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void Layout(IGUIState guiState)
|
||||
{
|
||||
Debug.Assert(guiState.eventType == EventType.Layout);
|
||||
|
||||
for (var i = 0; i < GetCount(); ++i)
|
||||
{
|
||||
if (guiState.hotControl == actionID && hotLayoutData.index == i)
|
||||
continue;
|
||||
|
||||
var layoutData = new LayoutData()
|
||||
{
|
||||
index = i,
|
||||
position = GetPosition(guiState, i),
|
||||
distance = GetDistance(guiState, i),
|
||||
forward = GetForward(guiState, i),
|
||||
up = GetUp(guiState, i),
|
||||
right = GetRight(guiState, i),
|
||||
userData = GetUserData(guiState, i)
|
||||
};
|
||||
|
||||
m_LayoutData = LayoutData.Nearest(m_LayoutData, layoutData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the layout for this control. This function must always follow a call to BeginLayout().
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void EndLayout(IGUIState guiState)
|
||||
{
|
||||
Debug.Assert(guiState.eventType == EventType.Layout);
|
||||
|
||||
OnEndLayout(guiState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repaints the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void Repaint(IGUIState guiState)
|
||||
{
|
||||
for (var i = 0; i < GetCount(); ++i)
|
||||
OnRepaint(guiState, i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control begins its layout.
|
||||
/// </summary>
|
||||
/// <param name="data">The layout data.</param>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns the layout data to use.</returns>
|
||||
protected virtual LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control ends its layout.
|
||||
/// /// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected virtual void OnEndLayout(IGUIState guiState)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control repaints its contents.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
protected virtual void OnRepaint(IGUIState guiState, int index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sub-controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, this is `1`. If you implement your own controller and want to use multiple sub-controllers within it, you can override this function to declare how to count the sub-controllers.
|
||||
/// </remarks>
|
||||
/// <returns>Returns the number of sub-controllers. If you do not override this function, this returns 1.</returns>
|
||||
protected virtual int GetCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns Vector3.zero.</returns>
|
||||
protected virtual Vector3 GetPosition(IGUIState guiState, int index)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the forward vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns Vector3.forward.</returns>
|
||||
protected virtual Vector3 GetForward(IGUIState guiState, int index)
|
||||
{
|
||||
return Vector3.forward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the up vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns Vector3.up,</returns>
|
||||
protected virtual Vector3 GetUp(IGUIState guiState, int index)
|
||||
{
|
||||
return Vector3.up;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns Vector3.right.</returns>
|
||||
protected virtual Vector3 GetRight(IGUIState guiState, int index)
|
||||
{
|
||||
return Vector3.right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance from the Scene view camera to the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns layoutData.distance.</returns>
|
||||
protected virtual float GetDistance(IGUIState guiState, int index)
|
||||
{
|
||||
return layoutData.distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control's user data.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>Returns `null`.</returns>
|
||||
protected virtual object GetUserData(IGUIState guiState, int index)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the default implementation of a control.
|
||||
/// </summary>
|
||||
internal abstract class DefaultControl : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// Default kPickDistance == 5.0f
|
||||
/// </summary>
|
||||
public static readonly float kPickDistance = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of DefaultControl
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the default control.</param>
|
||||
public DefaultControl(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the Control.OnBeginLayout function.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sets the LayoutData.distance to DefaultControl.kPickDistance.
|
||||
/// </remarks>
|
||||
/// <param name="data">The layout data.</param>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns the modified layout data.</returns>
|
||||
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
|
||||
{
|
||||
data.distance = kPickDistance;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an action that is tied to a GUI element.
|
||||
/// </summary>
|
||||
internal abstract class GUIAction
|
||||
{
|
||||
private int m_ID = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Func for GetEnable
|
||||
/// </summary>
|
||||
public Func<IGUIState, GUIAction, bool> enable;
|
||||
/// <summary>
|
||||
/// Func for EnabledRepaint
|
||||
/// </summary>
|
||||
public Func<IGUIState, GUIAction, bool> enableRepaint;
|
||||
/// <summary>
|
||||
/// Func for repaintOnMouseMove
|
||||
/// </summary>
|
||||
public Func<IGUIState, GUIAction, bool> repaintOnMouseMove;
|
||||
/// <summary>
|
||||
/// Action for OnPreRepaint
|
||||
/// </summary>
|
||||
public Action<IGUIState, GUIAction> onPreRepaint;
|
||||
/// <summary>
|
||||
/// Func for OnRepaint
|
||||
/// </summary>
|
||||
public Action<IGUIState, GUIAction> onRepaint;
|
||||
|
||||
/// <summary>
|
||||
/// The action ID.
|
||||
/// </summary>
|
||||
public int ID
|
||||
{
|
||||
get { return m_ID; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when Unity draws this GUIAction's GUI.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void OnGUI(IGUIState guiState)
|
||||
{
|
||||
m_ID = guiState.GetControlID(GetType().GetHashCode(), FocusType.Passive);
|
||||
|
||||
if (guiState.hotControl == 0 && IsEnabled(guiState) && CanTrigger(guiState) && GetTriggerContidtion(guiState))
|
||||
{
|
||||
guiState.hotControl = ID;
|
||||
OnTrigger(guiState);
|
||||
}
|
||||
|
||||
if (guiState.hotControl == ID)
|
||||
{
|
||||
if (GetFinishContidtion(guiState))
|
||||
{
|
||||
OnFinish(guiState);
|
||||
guiState.hotControl = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPerform(guiState);
|
||||
}
|
||||
}
|
||||
|
||||
if (guiState.eventType == EventType.Repaint && IsRepaintEnabled(guiState))
|
||||
Repaint(guiState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the GUIAction is enabled.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the GUIAction is enabled in the custom editor. Otherwise, returns `false`.</returns>
|
||||
public bool IsEnabled(IGUIState guiState)
|
||||
{
|
||||
if (enable != null)
|
||||
return enable(guiState, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the GUIAction should repaint.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the GUIAction should repaint. Otherwise, returns `false`.</returns>
|
||||
public bool IsRepaintEnabled(IGUIState guiState)
|
||||
{
|
||||
if (!IsEnabled(guiState))
|
||||
return false;
|
||||
|
||||
if (enableRepaint != null)
|
||||
return enableRepaint(guiState, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preprocessing that occurs before the GUI repaints.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public void PreRepaint(IGUIState guiState)
|
||||
{
|
||||
Debug.Assert(guiState.eventType == EventType.Repaint);
|
||||
|
||||
if (IsEnabled(guiState) && onPreRepaint != null)
|
||||
onPreRepaint(guiState, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when repainting the GUI.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
private void Repaint(IGUIState guiState)
|
||||
{
|
||||
Debug.Assert(guiState.eventType == EventType.Repaint);
|
||||
|
||||
if (onRepaint != null)
|
||||
onRepaint(guiState, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the GUI should repaint if the mouse moves over it.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the GUI should repaint if the moves moves over it. Otherwise, returns `false`.</returns>
|
||||
internal bool IsRepaintOnMouseMoveEnabled(IGUIState guiState)
|
||||
{
|
||||
if (!IsEnabled(guiState) || !IsRepaintEnabled(guiState))
|
||||
return false;
|
||||
|
||||
if (repaintOnMouseMove != null)
|
||||
return repaintOnMouseMove(guiState, this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the finish condition has been met.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if finish condition has been met. Otherwise, returns `false`.</returns>
|
||||
protected abstract bool GetFinishContidtion(IGUIState guiState);
|
||||
/// <summary>
|
||||
/// Determines whether the trigger condition has been met.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if finish condition has been met. Otherwise, returns `false`.</returns>
|
||||
protected abstract bool GetTriggerContidtion(IGUIState guiState);
|
||||
/// <summary>
|
||||
/// Determines whether the GUIAction can trigger.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Always returns `true`.</returns>
|
||||
protected virtual bool CanTrigger(IGUIState guiState) { return true; }
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when triggered.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected virtual void OnTrigger(IGUIState guiState)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when performed.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected virtual void OnPerform(IGUIState guiState)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when finished.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected virtual void OnFinish(IGUIState guiState)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of an IGUIState that represents a generic GUI state.
|
||||
/// </summary>
|
||||
internal class GUIState : IGUIState
|
||||
{
|
||||
private Handles.CapFunction nullCap = (int c, Vector3 p , Quaternion r, float s, EventType ev) => {};
|
||||
|
||||
/// <summary>
|
||||
/// The current mouse position.
|
||||
/// </summary>
|
||||
public Vector2 mousePosition
|
||||
{
|
||||
get { return Event.current.mousePosition; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently pressed button.
|
||||
/// </summary>
|
||||
public int mouseButton
|
||||
{
|
||||
get { return Event.current.button; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current number of mouse clicks.
|
||||
/// </summary>
|
||||
public int clickCount
|
||||
{
|
||||
get { return Event.current.clickCount; }
|
||||
set { Event.current.clickCount = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the shift key is pressed.
|
||||
/// </summary>
|
||||
public bool isShiftDown
|
||||
{
|
||||
get { return Event.current.shift; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the alt key is pressed.
|
||||
/// </summary>
|
||||
public bool isAltDown
|
||||
{
|
||||
get { return Event.current.alt; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicates whether the action key is pressed.
|
||||
/// </summary>
|
||||
public bool isActionKeyDown
|
||||
{
|
||||
get { return EditorGUI.actionKey; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The KeyCode of the currently pressed key.
|
||||
/// </summary>
|
||||
public KeyCode keyCode
|
||||
{
|
||||
get { return Event.current.keyCode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the current event.
|
||||
/// </summary>
|
||||
public EventType eventType
|
||||
{
|
||||
get { return Event.current.type; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the current event's command.
|
||||
/// </summary>
|
||||
public string commandName
|
||||
{
|
||||
get { return Event.current.commandName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The closest control to the event.
|
||||
/// </summary>
|
||||
public int nearestControl
|
||||
{
|
||||
get { return HandleUtility.nearestControl; }
|
||||
set { HandleUtility.nearestControl = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hot Control
|
||||
/// </summary>
|
||||
public int hotControl
|
||||
{
|
||||
get { return GUIUtility.hotControl; }
|
||||
set { GUIUtility.hotControl = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the GUI has changed.
|
||||
/// </summary>
|
||||
public bool changed
|
||||
{
|
||||
get { return GUI.changed; }
|
||||
set { GUI.changed = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of a nested control by a hint and focus type.
|
||||
/// </summary>
|
||||
/// <param name="hint">The hint this function uses to identify the control ID.</param>
|
||||
/// <param name="focusType">The focus Type</param>
|
||||
/// <returns>Returns the ID of the control that matches the hint and focus type.</returns>
|
||||
public int GetControlID(int hint, FocusType focusType)
|
||||
{
|
||||
return GUIUtility.GetControlID(hint, focusType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a control to the GUIState.
|
||||
/// </summary>
|
||||
/// <param name="controlID">The ID of the control to add.</param>
|
||||
/// <param name="distance">The distance from the camera to the control.</param>
|
||||
public void AddControl(int controlID, float distance)
|
||||
{
|
||||
HandleUtility.AddControl(controlID, distance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a slider value has changed.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the slider to check.</param>
|
||||
/// <param name="sliderData">The slider's data.</param>
|
||||
/// <param name="newPosition">The new position of the slider.</param>
|
||||
/// <returns>Returns `true` if the slider has changed. Otherwise, returns `false`.</returns>
|
||||
public bool Slider(int id, SliderData sliderData, out Vector3 newPosition)
|
||||
{
|
||||
if (mouseButton == 0 && eventType == EventType.MouseDown)
|
||||
{
|
||||
hotControl = 0;
|
||||
nearestControl = id;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
newPosition = Handles.Slider2D(id, sliderData.position, sliderData.forward, sliderData.right, sliderData.up, 1f, nullCap, Vector2.zero);
|
||||
return EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the current event.
|
||||
/// </summary>
|
||||
public void UseEvent()
|
||||
{
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repaints the GUI.
|
||||
/// </summary>
|
||||
public void Repaint()
|
||||
{
|
||||
HandleUtility.Repaint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current camera is valid.
|
||||
/// </summary>
|
||||
/// <returns>Returns `true` if the current camera is not null. Otherwise, returns `false`.</returns>
|
||||
public bool HasCurrentCamera()
|
||||
{
|
||||
return Camera.current != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the handle.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the handle.</param>
|
||||
/// <returns>Returns the size of the handle.</returns>
|
||||
public float GetHandleSize(Vector3 position)
|
||||
{
|
||||
var scale = HasCurrentCamera() ? 0.01f : 0.05f;
|
||||
return HandleUtility.GetHandleSize(position) * scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures the GUI-space distance between two points of a segment.
|
||||
/// </summary>
|
||||
/// <param name="p1">The first point.</param>
|
||||
/// <param name="p2">The seconde point.</param>
|
||||
/// <returns>Returns the GUI-space distance between p1 and p2.</returns>
|
||||
public float DistanceToSegment(Vector3 p1, Vector3 p2)
|
||||
{
|
||||
p1 = HandleUtility.WorldToGUIPoint(p1);
|
||||
p2 = HandleUtility.WorldToGUIPoint(p2);
|
||||
|
||||
return HandleUtility.DistancePointToLineSegment(Event.current.mousePosition, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures the distance to a circle.
|
||||
/// </summary>
|
||||
/// <param name="center">The center of the circle.</param>
|
||||
/// <param name="radius">The radius of the circle.</param>
|
||||
/// <returns>Returns the distance to a circle with the specified center and radius.</returns>
|
||||
public float DistanceToCircle(Vector3 center, float radius)
|
||||
{
|
||||
return HandleUtility.DistanceToCircle(center, radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a GUI-space position into world space.
|
||||
/// </summary>
|
||||
/// <param name="guiPosition">The GUI position</param>
|
||||
/// <param name="planeNormal">The plane normal.</param>
|
||||
/// <param name="planePos">The plane position.</param>
|
||||
/// <returns>Returns the world-space position of `guiPosition`.</returns>
|
||||
public Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePos)
|
||||
{
|
||||
Vector3 worldPos = Handles.inverseMatrix.MultiplyPoint(guiPosition);
|
||||
|
||||
if (Camera.current)
|
||||
{
|
||||
Ray ray = HandleUtility.GUIPointToWorldRay(guiPosition);
|
||||
|
||||
planeNormal = Handles.matrix.MultiplyVector(planeNormal);
|
||||
|
||||
planePos = Handles.matrix.MultiplyPoint(planePos);
|
||||
|
||||
Plane plane = new Plane(planeNormal, planePos);
|
||||
|
||||
float distance = 0f;
|
||||
if (plane.Raycast(ray, out distance))
|
||||
{
|
||||
worldPos = Handles.inverseMatrix.MultiplyPoint(ray.GetPoint(distance));
|
||||
}
|
||||
}
|
||||
|
||||
return worldPos;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a system of GUI elements and controls.
|
||||
/// </summary>
|
||||
internal class GUISystem
|
||||
{
|
||||
private readonly int kControlIDCheckHashCode = "ControlIDCheckHashCode".GetHashCode();
|
||||
|
||||
private List<Control> m_Controls = new List<Control>();
|
||||
private List<GUIAction> m_Actions = new List<GUIAction>();
|
||||
private IGUIState m_GUIState;
|
||||
private int m_PrevNearestControl = -1;
|
||||
private LayoutData m_PrevNearestLayoutData = LayoutData.zero;
|
||||
private int m_ControlIDCheck = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of GUISystem
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
public GUISystem(IGUIState guiState)
|
||||
{
|
||||
m_GUIState = guiState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a control to the internal list of controls.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to add.</param>
|
||||
public void AddControl(Control control)
|
||||
{
|
||||
if (control == null)
|
||||
throw new NullReferenceException("Control is null");
|
||||
|
||||
m_Controls.Add(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a control from the internal list of controls.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to remove.</param>
|
||||
public void RemoveControl(Control control)
|
||||
{
|
||||
m_Controls.Remove(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an action to the internal list of actions.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to add.</param>
|
||||
public void AddAction(GUIAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new NullReferenceException("Action is null");
|
||||
|
||||
m_Actions.Add(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an action from the internal list of actions.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to remove.</param>
|
||||
public void RemoveAction(GUIAction action)
|
||||
{
|
||||
m_Actions.Remove(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when Unity draws this GUISystems's GUI.
|
||||
/// </summary>
|
||||
public void OnGUI()
|
||||
{
|
||||
var controlIDCheck = m_GUIState.GetControlID(kControlIDCheckHashCode, FocusType.Passive);
|
||||
|
||||
if (m_GUIState.eventType == EventType.Layout)
|
||||
m_ControlIDCheck = controlIDCheck;
|
||||
else if (m_GUIState.eventType != EventType.Used && m_ControlIDCheck != controlIDCheck)
|
||||
Debug.LogWarning("GetControlID at event " + m_GUIState.eventType + " returns a controlID different from the one in Layout event");
|
||||
|
||||
var nearestLayoutData = LayoutData.zero;
|
||||
|
||||
foreach (var control in m_Controls)
|
||||
control.GetControl(m_GUIState);
|
||||
|
||||
if (m_GUIState.eventType == EventType.Layout)
|
||||
{
|
||||
foreach (var control in m_Controls)
|
||||
control.BeginLayout(m_GUIState);
|
||||
|
||||
foreach (var control in m_Controls)
|
||||
{
|
||||
control.Layout(m_GUIState);
|
||||
nearestLayoutData = LayoutData.Nearest(nearestLayoutData, control.layoutData);
|
||||
}
|
||||
|
||||
foreach (var control in m_Controls)
|
||||
m_GUIState.AddControl(control.ID, control.layoutData.distance);
|
||||
|
||||
foreach (var control in m_Controls)
|
||||
control.EndLayout(m_GUIState);
|
||||
|
||||
if (m_PrevNearestControl == m_GUIState.nearestControl)
|
||||
{
|
||||
if (nearestLayoutData.index != m_PrevNearestLayoutData.index)
|
||||
m_GUIState.Repaint();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PrevNearestControl = m_GUIState.nearestControl;
|
||||
m_GUIState.Repaint();
|
||||
}
|
||||
|
||||
m_PrevNearestLayoutData = nearestLayoutData;
|
||||
}
|
||||
|
||||
if (m_GUIState.eventType == EventType.Repaint)
|
||||
{
|
||||
foreach (var action in m_Actions)
|
||||
if (action.IsRepaintEnabled(m_GUIState))
|
||||
action.PreRepaint(m_GUIState);
|
||||
|
||||
foreach (var control in m_Controls)
|
||||
control.Repaint(m_GUIState);
|
||||
}
|
||||
|
||||
var repaintOnMouseMove = false;
|
||||
|
||||
foreach (var action in m_Actions)
|
||||
{
|
||||
if (IsMouseMoveEvent())
|
||||
repaintOnMouseMove |= action.IsRepaintOnMouseMoveEnabled(m_GUIState);
|
||||
|
||||
action.OnGUI(m_GUIState);
|
||||
}
|
||||
|
||||
if (repaintOnMouseMove)
|
||||
m_GUIState.Repaint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when the mouse moves.
|
||||
/// </summary>
|
||||
/// <returns>Returns `true` if the mouse moved. Otherwise, returns `false`.</returns>
|
||||
private bool IsMouseMoveEvent()
|
||||
{
|
||||
return m_GUIState.eventType == EventType.MouseMove || m_GUIState.eventType == EventType.MouseDrag;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic UI control.
|
||||
/// </summary>
|
||||
internal class GenericControl : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// Func for OnBeginLayout
|
||||
/// </summary>
|
||||
public Func<IGUIState, LayoutData> onBeginLayout;
|
||||
/// <summary>
|
||||
/// Action for OnEndLayout
|
||||
/// </summary>
|
||||
public Action<IGUIState> onEndLayout;
|
||||
/// <summary>
|
||||
/// Action for OnRepaint
|
||||
/// </summary>
|
||||
public Action<IGUIState, Control, int> onRepaint;
|
||||
/// <summary>
|
||||
/// Func for GetCount
|
||||
/// </summary>
|
||||
public Func<int> count;
|
||||
/// <summary>
|
||||
/// Func for GetPosition
|
||||
/// </summary>
|
||||
public Func<int, Vector3> position;
|
||||
/// <summary>
|
||||
/// Func for GetDistance
|
||||
/// </summary>
|
||||
public Func<IGUIState, int, float> distance;
|
||||
/// <summary>
|
||||
/// Func for GetForward
|
||||
/// </summary>
|
||||
public Func<int, Vector3> forward;
|
||||
/// <summary>
|
||||
/// Func for GetUp
|
||||
/// </summary>
|
||||
public Func<int, Vector3> up;
|
||||
/// <summary>
|
||||
/// Func for GetRight
|
||||
/// </summary>
|
||||
public Func<int, Vector3> right;
|
||||
/// <summary>
|
||||
/// Func for GetUserData
|
||||
/// </summary>
|
||||
public Func<int, object> userData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of GenericControl
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the generic control.</param>
|
||||
public GenericControl(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sub-controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, this is `1`. If you implement your own controller and want to use multiple sub-controllers within it, you can assign getCount to a function that returns the number of sub-controllers.
|
||||
/// </remarks>
|
||||
/// <returns>Returns the number of sub-controllers. If you do not assign getCount, this returns 1.</returns>
|
||||
protected override int GetCount()
|
||||
{
|
||||
if (count != null)
|
||||
return count();
|
||||
|
||||
return base.GetCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control ends its layout.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnEndLayout(IGUIState guiState)
|
||||
{
|
||||
if (onEndLayout != null)
|
||||
onEndLayout(guiState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control repaints its contents.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">Current Index</param>
|
||||
protected override void OnRepaint(IGUIState guiState, int index)
|
||||
{
|
||||
if (onRepaint != null)
|
||||
onRepaint(guiState, this, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the control begins its layout.
|
||||
/// </summary>
|
||||
/// <param name="data">The layout data.</param>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>The LayoutData</returns>
|
||||
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
|
||||
{
|
||||
if (onBeginLayout != null)
|
||||
return onBeginLayout(guiState);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>The position</returns>
|
||||
protected override Vector3 GetPosition(IGUIState guiState, int index)
|
||||
{
|
||||
if (position != null)
|
||||
return position(index);
|
||||
|
||||
return base.GetPosition(guiState,index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance from the Scene view camera to the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the distance from the Scene view camera to the control.</returns>
|
||||
protected override float GetDistance(IGUIState guiState, int index)
|
||||
{
|
||||
if (distance != null)
|
||||
return distance(guiState, index);
|
||||
|
||||
return base.GetDistance(guiState, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the forward vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's forward vector.</returns>
|
||||
protected override Vector3 GetForward(IGUIState guiState, int index)
|
||||
{
|
||||
if (forward != null)
|
||||
return forward(index);
|
||||
|
||||
return base.GetForward(guiState,index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the up vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's up vector.</returns>
|
||||
protected override Vector3 GetUp(IGUIState guiState, int index)
|
||||
{
|
||||
if (up != null)
|
||||
return up(index);
|
||||
|
||||
return base.GetUp(guiState,index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's right vector.</returns>
|
||||
protected override Vector3 GetRight(IGUIState guiState, int index)
|
||||
{
|
||||
if (right != null)
|
||||
return right(index);
|
||||
|
||||
return base.GetRight(guiState,index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override for GetUserData
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Return the user data</returns>
|
||||
protected override object GetUserData(IGUIState guiState, int index)
|
||||
{
|
||||
if (userData != null)
|
||||
return userData(index);
|
||||
|
||||
return base.GetUserData(guiState,index);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a default generic UI control.
|
||||
/// </summary>
|
||||
internal class GenericDefaultControl : DefaultControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Func for GetPosition
|
||||
/// </summary>
|
||||
public Func<IGUIState, Vector3> position;
|
||||
/// <summary>
|
||||
/// Func for GetForward
|
||||
/// </summary>
|
||||
public Func<IGUIState, Vector3> forward;
|
||||
/// <summary>
|
||||
/// Func for GetUp
|
||||
/// </summary>
|
||||
public Func<IGUIState, Vector3> up;
|
||||
/// <summary>
|
||||
/// Func for GetRight
|
||||
/// </summary>
|
||||
public Func<IGUIState, Vector3> right;
|
||||
/// <summary>
|
||||
/// Func for GetUserData
|
||||
/// </summary>
|
||||
public Func<IGUIState, object> userData;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of GenericDefaultControl
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the generic default control.</param>
|
||||
public GenericDefaultControl(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance from the Scene view camera to the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>The distance from the Scene view camera to the control.</returns>
|
||||
protected override Vector3 GetPosition(IGUIState guiState, int index)
|
||||
{
|
||||
if (position != null)
|
||||
return position(guiState);
|
||||
|
||||
return base.GetPosition(guiState, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the forward vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's forward vector.</returns>
|
||||
protected override Vector3 GetForward(IGUIState guiState, int index)
|
||||
{
|
||||
if (forward != null)
|
||||
return forward(guiState);
|
||||
|
||||
return base.GetForward(guiState, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the up vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's up vector.</returns>
|
||||
protected override Vector3 GetUp(IGUIState guiState, int index)
|
||||
{
|
||||
if (up != null)
|
||||
return up(guiState);
|
||||
|
||||
return base.GetUp(guiState, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right vector of the control.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the generic control's right vector.</returns>
|
||||
protected override Vector3 GetRight(IGUIState guiState, int index)
|
||||
{
|
||||
if (right != null)
|
||||
return right(guiState);
|
||||
|
||||
return base.GetRight(guiState, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control's user data.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <param name="index">The Index</param>
|
||||
/// <returns>Returns the user data</returns>
|
||||
protected override object GetUserData(IGUIState guiState, int index)
|
||||
{
|
||||
if (userData != null)
|
||||
return userData(guiState);
|
||||
|
||||
return base.GetUserData(guiState, index);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an action that is tied to a Control.
|
||||
/// </summary>
|
||||
internal abstract class HoveredControlAction : GUIAction
|
||||
{
|
||||
private Control m_HoveredControl;
|
||||
|
||||
/// <summary>
|
||||
/// The hovered control.
|
||||
/// </summary>
|
||||
public Control hoveredControl
|
||||
{
|
||||
get { return m_HoveredControl; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of HoverControlAction.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to execcute an action for on hover.</param>
|
||||
public HoveredControlAction(Control control)
|
||||
{
|
||||
m_HoveredControl = control;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the HoveredControlAction can trigger.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the HoveredControlAction can trigger. Otherwise, returns `false`.</returns>
|
||||
protected override bool CanTrigger(IGUIState guiState)
|
||||
{
|
||||
return guiState.nearestControl == hoveredControl.ID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the methods in its invocation list when triggered.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnTrigger(IGUIState guiState)
|
||||
{
|
||||
m_HoveredControl.SetActionID(ID);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents transform data for a slider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unity uses this data to position and orient the slider in the custom editor.
|
||||
/// </remarks>
|
||||
internal struct SliderData
|
||||
{
|
||||
/// <summary>
|
||||
/// The slider's position.
|
||||
/// </summary>
|
||||
public Vector3 position;
|
||||
/// <summary>
|
||||
/// The slider's forward vector.
|
||||
/// </summary>
|
||||
public Vector3 forward;
|
||||
/// <summary>
|
||||
/// The slider's up vector.
|
||||
/// </summary>
|
||||
public Vector3 up;
|
||||
/// <summary>
|
||||
/// The slider's right vector.
|
||||
/// </summary>
|
||||
public Vector3 right;
|
||||
|
||||
/// <summary>
|
||||
/// zero definition for SliderData
|
||||
/// </summary>
|
||||
public static readonly SliderData zero = new SliderData() { position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for GUIStates
|
||||
/// </summary>
|
||||
internal interface IGUIState
|
||||
{
|
||||
/// <summary>
|
||||
/// The mouse position.
|
||||
/// </summary>
|
||||
Vector2 mousePosition { get; }
|
||||
/// <summary>
|
||||
/// The mouse button pressed.
|
||||
/// </summary>
|
||||
int mouseButton { get; }
|
||||
/// <summary>
|
||||
/// The number of mouse clicks.
|
||||
/// </summary>
|
||||
int clickCount { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates whether the shift key is pressed.
|
||||
/// </summary>
|
||||
bool isShiftDown { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether the alt key is pressed.
|
||||
/// </summary>
|
||||
bool isAltDown { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether the action key is pressed.
|
||||
/// </summary>
|
||||
bool isActionKeyDown { get; }
|
||||
/// <summary>
|
||||
/// The KeyCode of the currently pressed key.
|
||||
/// </summary>
|
||||
KeyCode keyCode { get; }
|
||||
/// <summary>
|
||||
/// The type of the event.
|
||||
/// </summary>
|
||||
EventType eventType { get; }
|
||||
/// <summary>
|
||||
/// The name of the event's command.
|
||||
/// </summary>
|
||||
string commandName { get; }
|
||||
/// <summary>
|
||||
/// The closest control to the event.
|
||||
/// </summary>
|
||||
int nearestControl { get; set; }
|
||||
/// <summary>
|
||||
/// Hot Control
|
||||
/// </summary>
|
||||
int hotControl { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates whether the GUI has changed.
|
||||
/// </summary>
|
||||
bool changed { get; set; }
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of a nested control by a hint and focus type.
|
||||
/// </summary>
|
||||
/// <param name="hint">The hint this function uses to identify the control ID.</param>
|
||||
/// <param name="focusType">The focus Type</param>
|
||||
/// <returns>Returns the ID of the control that matches the hint and focus type.</returns>
|
||||
int GetControlID(int hint, FocusType focusType);
|
||||
/// <summary>
|
||||
/// Adds a control to the GUIState.
|
||||
/// </summary>
|
||||
/// <param name="controlID">The ID of the control to add.</param>
|
||||
/// <param name="distance">The distance from the camera to the control.</param>
|
||||
void AddControl(int controlID, float distance);
|
||||
/// <summary>
|
||||
/// Checks whether a slider value has changed.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the slider to check.</param>
|
||||
/// <param name="sliderData">The slider's data.</param>
|
||||
/// <param name="newPosition">The new position of the slider.</param>
|
||||
/// <returns>Returns `true` if the slider has changed. Otherwise, returns `false`.</returns>
|
||||
bool Slider(int id, SliderData sliderData, out Vector3 newPosition);
|
||||
/// <summary>
|
||||
/// Uses the event.
|
||||
/// </summary>
|
||||
void UseEvent();
|
||||
/// <summary>
|
||||
/// Repaints the GUI.
|
||||
/// </summary>
|
||||
void Repaint();
|
||||
/// <summary>
|
||||
/// Checks if the current camera is valid.
|
||||
/// </summary>
|
||||
/// <returns>Returns `true` if the current camera is not null. Otherwise, returns `false`.</returns>
|
||||
bool HasCurrentCamera();
|
||||
/// <summary>
|
||||
/// Gets the size of the handle.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the handle.</param>
|
||||
/// <returns>Returns the size of the handle.</returns>
|
||||
float GetHandleSize(Vector3 position);
|
||||
/// <summary>
|
||||
/// Measures the GUI-space distance between two points of a segment.
|
||||
/// </summary>
|
||||
/// <param name="p1">The first point.</param>
|
||||
/// <param name="p2">The second point.</param>
|
||||
/// <returns>Returns the GUI-space distance between p1 and p2.</returns>
|
||||
float DistanceToSegment(Vector3 p1, Vector3 p2);
|
||||
/// <summary>
|
||||
/// Measures the distance to a circle.
|
||||
/// </summary>
|
||||
/// <param name="center">The center of the circle</param>
|
||||
/// <param name="radius">The radius of the circle</param>
|
||||
/// <returns>Returns the distance to a circle with the specified center and radius.</returns>
|
||||
float DistanceToCircle(Vector3 center, float radius);
|
||||
/// <summary>
|
||||
/// Transforms a GUI-space position into world space.
|
||||
/// </summary>
|
||||
/// <param name="guiPosition">The GUI Position.</param>
|
||||
/// <param name="planeNormal">The plane normal.</param>
|
||||
/// <param name="planePos">The plane position.</param>
|
||||
/// <returns>Returns the world-space position of `guiPosition`.</returns>
|
||||
Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePos);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the layout of a GUI element in a custom editor.
|
||||
/// </summary>
|
||||
internal struct LayoutData
|
||||
{
|
||||
/// <summary>
|
||||
/// The layout's index.
|
||||
/// </summary>
|
||||
public int index;
|
||||
/// <summary>
|
||||
/// The distance from the layout to the camera.
|
||||
/// </summary>
|
||||
public float distance;
|
||||
/// <summary>
|
||||
/// The layout's world-space position.
|
||||
/// </summary>
|
||||
public Vector3 position;
|
||||
/// <summary>
|
||||
/// The layout's world-space forward vector.
|
||||
/// </summary>
|
||||
public Vector3 forward;
|
||||
/// <summary>
|
||||
/// The layout's world-space up vector.
|
||||
/// </summary>
|
||||
public Vector3 up;
|
||||
/// <summary>
|
||||
/// The layout's world-space right vector.
|
||||
/// </summary>
|
||||
public Vector3 right;
|
||||
/// <summary>
|
||||
/// The layout's user data.
|
||||
/// </summary>
|
||||
public object userData;
|
||||
|
||||
/// <summary>
|
||||
/// Zero definition of LayoutData.
|
||||
/// </summary>
|
||||
public static readonly LayoutData zero = new LayoutData() { index = 0, distance = float.MaxValue, position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout that is closest to the camera,
|
||||
/// </summary>
|
||||
/// <param name="currentData">The current layout.</param>
|
||||
/// <param name="newData">The new layout to compare with.</param>
|
||||
/// <returns>Returns the closest layout to the camera. If `currentData` is closest to the camera, returns `currentData`. Otherwise, if `newData` is closest to the camera, returns `newData`.</returns>
|
||||
public static LayoutData Nearest(LayoutData currentData, LayoutData newData)
|
||||
{
|
||||
if (newData.distance <= currentData.distance)
|
||||
return newData;
|
||||
|
||||
return currentData;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path.GUIFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// SliderAction implementation of a ClickAction
|
||||
/// </summary>
|
||||
internal class SliderAction : ClickAction
|
||||
{
|
||||
private SliderData m_SliderData;
|
||||
|
||||
/// <summary>
|
||||
/// Action for OnSliderBegin
|
||||
/// </summary>
|
||||
public Action<IGUIState, Control, Vector3> onSliderBegin;
|
||||
/// <summary>
|
||||
/// Action for OnSliderChanged
|
||||
/// </summary>
|
||||
public Action<IGUIState, Control, Vector3> onSliderChanged;
|
||||
/// <summary>
|
||||
/// Action for OnSliderEnd
|
||||
/// </summary>
|
||||
public Action<IGUIState, Control, Vector3> onSliderEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of SliderAction
|
||||
/// </summary>
|
||||
/// <param name="control">The control to execcute an action for on slide.</param>
|
||||
public SliderAction(Control control) : base(control, 0, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the finish condition has been met or not.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
/// <returns>Returns `true` if the finish condition has been met. Otherwise, returns `false`.</returns>
|
||||
protected override bool GetFinishContidtion(IGUIState guiState)
|
||||
{
|
||||
return guiState.eventType == EventType.MouseUp && guiState.mouseButton == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when there is interaction with the slider. It updates the stored slider data with data post-interaction.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnTrigger(IGUIState guiState)
|
||||
{
|
||||
base.OnTrigger(guiState);
|
||||
|
||||
m_SliderData.position = hoveredControl.hotLayoutData.position;
|
||||
m_SliderData.forward = hoveredControl.hotLayoutData.forward;
|
||||
m_SliderData.right = hoveredControl.hotLayoutData.right;
|
||||
m_SliderData.up = hoveredControl.hotLayoutData.up;
|
||||
|
||||
if (onSliderBegin != null)
|
||||
onSliderBegin(guiState, hoveredControl, m_SliderData.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post-processing for when the slider interaction finishes.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor.</param>
|
||||
protected override void OnFinish(IGUIState guiState)
|
||||
{
|
||||
if (onSliderEnd != null)
|
||||
onSliderEnd(guiState, hoveredControl, m_SliderData.position);
|
||||
|
||||
guiState.UseEvent();
|
||||
guiState.Repaint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the slider to the new permission and executes `onSliderChanged` using the new position.
|
||||
/// </summary>
|
||||
/// <param name="guiState">The current state of the custom editor</param>
|
||||
protected override void OnPerform(IGUIState guiState)
|
||||
{
|
||||
Vector3 newPosition;
|
||||
var changed = guiState.Slider(ID, m_SliderData, out newPosition);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
m_SliderData.position = newPosition;
|
||||
|
||||
if (onSliderChanged != null)
|
||||
onSliderChanged(guiState, hoveredControl, newPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface IDrawer
|
||||
{
|
||||
void DrawCreatePointPreview(Vector3 position, Color color);
|
||||
void DrawPoint(Vector3 position, Color color);
|
||||
void DrawPointHovered(Vector3 position, Color color);
|
||||
void DrawPointSelected(Vector3 position, Color color);
|
||||
void DrawLine(Vector3 p1, Vector3 p2, float width, Color color);
|
||||
void DrawBezier(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float width, Color color);
|
||||
void DrawTangent(Vector3 position, Vector3 tangent, Color color);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,537 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class PathEditor
|
||||
{
|
||||
const float kSnappingDistance = 15f;
|
||||
const string kDeleteCommandName = "Delete";
|
||||
const string kSoftDeleteCommandName = "SoftDelete";
|
||||
public IEditablePathController controller { get; set; }
|
||||
public bool linearTangentIsZero { get; set; }
|
||||
private IDrawer m_Drawer = new Drawer();
|
||||
private IDrawer m_DrawerOverride;
|
||||
private GUISystem m_GUISystem;
|
||||
|
||||
public IDrawer drawerOverride { get; set; }
|
||||
|
||||
private IDrawer drawer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (drawerOverride != null)
|
||||
return drawerOverride;
|
||||
|
||||
return m_Drawer;
|
||||
}
|
||||
}
|
||||
|
||||
public PathEditor() : this(new GUISystem(new GUIState())) { }
|
||||
|
||||
public PathEditor(GUISystem guiSystem)
|
||||
{
|
||||
m_GUISystem = guiSystem;
|
||||
|
||||
var m_PointControl = new GenericControl("Point")
|
||||
{
|
||||
count = GetPointCount,
|
||||
distance = (guiState, i) =>
|
||||
{
|
||||
var position = GetPoint(i).position;
|
||||
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
|
||||
},
|
||||
position = (i) => { return GetPoint(i).position; },
|
||||
forward = (i) => { return GetForward(); },
|
||||
up = (i) => { return GetUp(); },
|
||||
right = (i) => { return GetRight(); },
|
||||
onRepaint = DrawPoint
|
||||
};
|
||||
|
||||
var m_EdgeControl = new GenericControl("Edge")
|
||||
{
|
||||
count = GetEdgeCount,
|
||||
distance = DistanceToEdge,
|
||||
position = (i) => { return GetPoint(i).position; },
|
||||
forward = (i) => { return GetForward(); },
|
||||
up = (i) => { return GetUp(); },
|
||||
right = (i) => { return GetRight(); },
|
||||
onRepaint = DrawEdge
|
||||
};
|
||||
m_EdgeControl.onEndLayout = (guiState) => { controller.AddClosestPath(m_EdgeControl.layoutData.distance); };
|
||||
|
||||
var m_LeftTangentControl = new GenericControl("LeftTangent")
|
||||
{
|
||||
count = () =>
|
||||
{
|
||||
if (GetShapeType() != ShapeType.Spline)
|
||||
return 0;
|
||||
|
||||
return GetPointCount();
|
||||
},
|
||||
distance = (guiState, i) =>
|
||||
{
|
||||
if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
|
||||
return float.MaxValue;
|
||||
|
||||
if (!IsSelected(i) || IsOpenEnded() && i == 0)
|
||||
return float.MaxValue;
|
||||
|
||||
var position = GetLeftTangent(i);
|
||||
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
|
||||
},
|
||||
position = (i) => { return GetLeftTangent(i); },
|
||||
forward = (i) => { return GetForward(); },
|
||||
up = (i) => { return GetUp(); },
|
||||
right = (i) => { return GetRight(); },
|
||||
onRepaint = (guiState, control, i) =>
|
||||
{
|
||||
if (!IsSelected(i) || IsOpenEnded() && i == 0)
|
||||
return;
|
||||
|
||||
var point = GetPoint(i);
|
||||
|
||||
if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
|
||||
return;
|
||||
|
||||
var position = point.position;
|
||||
var leftTangent = GetLeftTangent(i);
|
||||
|
||||
drawer.DrawTangent(position, leftTangent, HandleSettings.tangentColor);
|
||||
}
|
||||
};
|
||||
|
||||
var m_RightTangentControl = new GenericControl("RightTangent")
|
||||
{
|
||||
count = () =>
|
||||
{
|
||||
if (GetShapeType() != ShapeType.Spline)
|
||||
return 0;
|
||||
|
||||
return GetPointCount();
|
||||
},
|
||||
distance = (guiState, i) =>
|
||||
{
|
||||
if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
|
||||
return float.MaxValue;
|
||||
|
||||
if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
|
||||
return float.MaxValue;
|
||||
|
||||
var position = GetRightTangent(i);
|
||||
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
|
||||
},
|
||||
position = (i) => { return GetRightTangent(i); },
|
||||
forward = (i) => { return GetForward(); },
|
||||
up = (i) => { return GetUp(); },
|
||||
right = (i) => { return GetRight(); },
|
||||
onRepaint = (guiState, control, i) =>
|
||||
{
|
||||
if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
|
||||
return;
|
||||
|
||||
var point = GetPoint(i);
|
||||
|
||||
if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
|
||||
return;
|
||||
|
||||
var position = point.position;
|
||||
var rightTangent = GetRightTangent(i);
|
||||
|
||||
drawer.DrawTangent(position, rightTangent, HandleSettings.tangentColor);
|
||||
}
|
||||
};
|
||||
|
||||
var m_CreatePointAction = new CreatePointAction(m_PointControl, m_EdgeControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState) && !guiState.isActionKeyDown && controller.closestEditablePath == controller.editablePath,
|
||||
enableRepaint = (guiState, action) => EnableCreatePointRepaint(guiState, m_PointControl, m_LeftTangentControl, m_RightTangentControl),
|
||||
repaintOnMouseMove = (guiState, action) => true,
|
||||
guiToWorld = GUIToWorld,
|
||||
onCreatePoint = (index, position) =>
|
||||
{
|
||||
controller.RegisterUndo("Create Point");
|
||||
controller.CreatePoint(index, position);
|
||||
},
|
||||
onPreRepaint = (guiState, action) =>
|
||||
{
|
||||
if (GetPointCount() > 0)
|
||||
{
|
||||
var position = ClosestPointInEdge(guiState, guiState.mousePosition, m_EdgeControl.layoutData.index);
|
||||
drawer.DrawCreatePointPreview(position, ControlPointSettings.controlPointColor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action<IGUIState> removePoints = (guiState) =>
|
||||
{
|
||||
controller.RegisterUndo("Remove Point");
|
||||
controller.RemoveSelectedPoints();
|
||||
guiState.changed = true;
|
||||
};
|
||||
|
||||
var m_RemovePointAction1 = new CommandAction(kDeleteCommandName)
|
||||
{
|
||||
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
|
||||
onCommand = removePoints
|
||||
};
|
||||
|
||||
var m_RemovePointAction2 = new CommandAction(kSoftDeleteCommandName)
|
||||
{
|
||||
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
|
||||
onCommand = removePoints
|
||||
};
|
||||
|
||||
var dragged = false;
|
||||
var m_MovePointAction = new SliderAction(m_PointControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState),
|
||||
onClick = (guiState, control) =>
|
||||
{
|
||||
dragged = false;
|
||||
var index = control.layoutData.index;
|
||||
|
||||
if (!IsSelected(index))
|
||||
{
|
||||
controller.RegisterUndo("Selection");
|
||||
|
||||
if (!guiState.isActionKeyDown)
|
||||
controller.ClearSelection();
|
||||
|
||||
controller.SelectPoint(index, true);
|
||||
guiState.changed = true;
|
||||
}
|
||||
},
|
||||
onSliderChanged = (guiState, control, position) =>
|
||||
{
|
||||
var index = control.hotLayoutData.index;
|
||||
var delta = SnapIfNeeded(position) - GetPoint(index).position;
|
||||
|
||||
if (!dragged)
|
||||
{
|
||||
controller.RegisterUndo("Move Point");
|
||||
dragged = true;
|
||||
}
|
||||
|
||||
controller.MoveSelectedPoints(delta);
|
||||
}
|
||||
};
|
||||
|
||||
var m_MoveEdgeAction = new SliderAction(m_EdgeControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState) && guiState.isActionKeyDown,
|
||||
onSliderBegin = (guiState, control, position) =>
|
||||
{
|
||||
dragged = false;
|
||||
|
||||
},
|
||||
onSliderChanged = (guiState, control, position) =>
|
||||
{
|
||||
var index = control.hotLayoutData.index;
|
||||
var delta = position - GetPoint(index).position;
|
||||
|
||||
if (!dragged)
|
||||
{
|
||||
controller.RegisterUndo("Move Edge");
|
||||
dragged = true;
|
||||
}
|
||||
|
||||
controller.MoveEdge(index, delta);
|
||||
}
|
||||
};
|
||||
|
||||
var cachedRightTangent = Vector3.zero;
|
||||
var cachedLeftTangent = Vector3.zero;
|
||||
var cachedTangentMode = TangentMode.Linear;
|
||||
|
||||
var m_MoveLeftTangentAction = new SliderAction(m_LeftTangentControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState),
|
||||
onSliderBegin = (guiState, control, position) =>
|
||||
{
|
||||
dragged = false;
|
||||
var point = GetPoint(control.hotLayoutData.index);
|
||||
cachedRightTangent = point.rightTangent;
|
||||
cachedTangentMode = point.tangentMode;
|
||||
},
|
||||
onSliderChanged = (guiState, control, position) =>
|
||||
{
|
||||
var index = control.hotLayoutData.index;
|
||||
var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
|
||||
|
||||
if (!dragged)
|
||||
{
|
||||
controller.RegisterUndo("Move Tangent");
|
||||
dragged = true;
|
||||
}
|
||||
|
||||
position = SnapIfNeeded(position);
|
||||
controller.SetLeftTangent(index, position, setToLinear, guiState.isShiftDown, cachedRightTangent, cachedTangentMode);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
var m_MoveRightTangentAction = new SliderAction(m_RightTangentControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState),
|
||||
onSliderBegin = (guiState, control, position) =>
|
||||
{
|
||||
dragged = false;
|
||||
var point = GetPoint(control.hotLayoutData.index);
|
||||
cachedLeftTangent = point.leftTangent;
|
||||
cachedTangentMode = point.tangentMode;
|
||||
},
|
||||
onSliderChanged = (guiState, control, position) =>
|
||||
{
|
||||
var index = control.hotLayoutData.index;
|
||||
var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
|
||||
|
||||
if (!dragged)
|
||||
{
|
||||
controller.RegisterUndo("Move Tangent");
|
||||
dragged = true;
|
||||
}
|
||||
|
||||
position = SnapIfNeeded(position);
|
||||
controller.SetRightTangent(index, position, setToLinear, guiState.isShiftDown, cachedLeftTangent, cachedTangentMode);
|
||||
}
|
||||
};
|
||||
|
||||
m_GUISystem.AddControl(m_EdgeControl);
|
||||
m_GUISystem.AddControl(m_PointControl);
|
||||
m_GUISystem.AddControl(m_LeftTangentControl);
|
||||
m_GUISystem.AddControl(m_RightTangentControl);
|
||||
m_GUISystem.AddAction(m_CreatePointAction);
|
||||
m_GUISystem.AddAction(m_RemovePointAction1);
|
||||
m_GUISystem.AddAction(m_RemovePointAction2);
|
||||
m_GUISystem.AddAction(m_MovePointAction);
|
||||
m_GUISystem.AddAction(m_MoveEdgeAction);
|
||||
m_GUISystem.AddAction(m_MoveLeftTangentAction);
|
||||
m_GUISystem.AddAction(m_MoveRightTangentAction);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
m_GUISystem.OnGUI();
|
||||
}
|
||||
|
||||
private bool IsAltDown(IGUIState guiState)
|
||||
{
|
||||
return guiState.hotControl == 0 && guiState.isAltDown;
|
||||
}
|
||||
|
||||
private ControlPoint GetPoint(int index)
|
||||
{
|
||||
return controller.editablePath.GetPoint(index);
|
||||
}
|
||||
|
||||
private int GetPointCount()
|
||||
{
|
||||
return controller.editablePath.pointCount;
|
||||
}
|
||||
|
||||
private int GetEdgeCount()
|
||||
{
|
||||
if (controller.editablePath.isOpenEnded)
|
||||
return controller.editablePath.pointCount - 1;
|
||||
|
||||
return controller.editablePath.pointCount;
|
||||
}
|
||||
|
||||
private int GetSelectedPointCount()
|
||||
{
|
||||
return controller.editablePath.selection.Count;
|
||||
}
|
||||
|
||||
private bool IsSelected(int index)
|
||||
{
|
||||
return controller.editablePath.selection.Contains(index);
|
||||
}
|
||||
|
||||
private Vector3 GetForward()
|
||||
{
|
||||
return controller.editablePath.forward;
|
||||
}
|
||||
|
||||
private Vector3 GetUp()
|
||||
{
|
||||
return controller.editablePath.up;
|
||||
}
|
||||
|
||||
private Vector3 GetRight()
|
||||
{
|
||||
return controller.editablePath.right;
|
||||
}
|
||||
|
||||
private Matrix4x4 GetLocalToWorldMatrix()
|
||||
{
|
||||
return controller.editablePath.localToWorldMatrix;
|
||||
}
|
||||
|
||||
private ShapeType GetShapeType()
|
||||
{
|
||||
return controller.editablePath.shapeType;
|
||||
}
|
||||
|
||||
private bool IsOpenEnded()
|
||||
{
|
||||
return controller.editablePath.isOpenEnded;
|
||||
}
|
||||
|
||||
private Vector3 GetLeftTangent(int index)
|
||||
{
|
||||
if (linearTangentIsZero)
|
||||
return GetPoint(index).leftTangent;
|
||||
|
||||
return controller.editablePath.CalculateLeftTangent(index);
|
||||
}
|
||||
|
||||
private Vector3 GetRightTangent(int index)
|
||||
{
|
||||
if (linearTangentIsZero)
|
||||
return GetPoint(index).rightTangent;
|
||||
|
||||
return controller.editablePath.CalculateRightTangent(index);
|
||||
}
|
||||
|
||||
private int NextIndex(int index)
|
||||
{
|
||||
return EditablePathUtility.Mod(index + 1, GetPointCount());
|
||||
}
|
||||
|
||||
private ControlPoint NextControlPoint(int index)
|
||||
{
|
||||
return GetPoint(NextIndex(index));
|
||||
}
|
||||
|
||||
private int PrevIndex(int index)
|
||||
{
|
||||
return EditablePathUtility.Mod(index - 1, GetPointCount());
|
||||
}
|
||||
|
||||
private ControlPoint PrevControlPoint(int index)
|
||||
{
|
||||
return GetPoint(PrevIndex(index));
|
||||
}
|
||||
|
||||
private Vector3 ClosestPointInEdge(IGUIState guiState, Vector2 mousePosition, int index)
|
||||
{
|
||||
if (GetShapeType() == ShapeType.Polygon)
|
||||
{
|
||||
var p0 = GetPoint(index).position;
|
||||
var p1 = NextControlPoint(index).position;
|
||||
var mouseWorldPosition = GUIToWorld(guiState, mousePosition);
|
||||
|
||||
var dir1 = (mouseWorldPosition - p0);
|
||||
var dir2 = (p1 - p0);
|
||||
|
||||
return Mathf.Clamp01(Vector3.Dot(dir1, dir2.normalized) / dir2.magnitude) * dir2 + p0;
|
||||
}
|
||||
else if (GetShapeType() == ShapeType.Spline)
|
||||
{
|
||||
var nextIndex = NextIndex(index);
|
||||
float t;
|
||||
return BezierUtility.ClosestPointOnCurve(
|
||||
GUIToWorld(guiState, mousePosition),
|
||||
GetPoint(index).position,
|
||||
GetPoint(nextIndex).position,
|
||||
GetRightTangent(index),
|
||||
GetLeftTangent(nextIndex),
|
||||
out t);
|
||||
}
|
||||
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
private float DistanceToEdge(IGUIState guiState, int index)
|
||||
{
|
||||
if (GetShapeType() == ShapeType.Polygon)
|
||||
{
|
||||
return guiState.DistanceToSegment(GetPoint(index).position, NextControlPoint(index).position);
|
||||
}
|
||||
else if (GetShapeType() == ShapeType.Spline)
|
||||
{
|
||||
var closestPoint = ClosestPointInEdge(guiState, guiState.mousePosition, index);
|
||||
var closestPoint2 = HandleUtility.WorldToGUIPoint(closestPoint);
|
||||
|
||||
return (closestPoint2 - guiState.mousePosition).magnitude;
|
||||
}
|
||||
|
||||
return float.MaxValue;
|
||||
}
|
||||
|
||||
private Vector3 GUIToWorld(IGUIState guiState, Vector2 position)
|
||||
{
|
||||
return guiState.GUIToWorld(position, GetForward(), GetLocalToWorldMatrix().MultiplyPoint3x4(Vector3.zero));
|
||||
}
|
||||
|
||||
private void DrawPoint(IGUIState guiState, Control control, int index)
|
||||
{
|
||||
var position = GetPoint(index).position;
|
||||
var isTangent = (control.name == "LeftTangent" || control.name == "RightTangent");
|
||||
if (guiState.hotControl == control.actionID && control.hotLayoutData.index == index || IsSelected(index))
|
||||
drawer.DrawPointSelected(position, ControlPointSettings.controlPointSelectedColor);
|
||||
else if (guiState.hotControl == 0 && guiState.nearestControl == control.ID && !IsAltDown(guiState) && control.layoutData.index == index)
|
||||
drawer.DrawPointHovered(position, ControlPointSettings.controlPointSelectedColor);
|
||||
else
|
||||
drawer.DrawPoint(position, isTangent ? HandleSettings.tangentColor : ControlPointSettings.controlPointColor);
|
||||
}
|
||||
|
||||
private void DrawEdge(IGUIState guiState, Control control, int index)
|
||||
{
|
||||
if (GetShapeType() == ShapeType.Polygon)
|
||||
{
|
||||
var nextIndex = NextIndex(index);
|
||||
var color = HandleSettings.splineColor;
|
||||
|
||||
if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
|
||||
color = HandleSettings.splineHoveredColor;
|
||||
|
||||
drawer.DrawLine(GetPoint(index).position, GetPoint(nextIndex).position, 5f, color);
|
||||
}
|
||||
else if (GetShapeType() == ShapeType.Spline)
|
||||
{
|
||||
var nextIndex = NextIndex(index);
|
||||
var color = HandleSettings.splineColor;
|
||||
|
||||
if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
|
||||
color = HandleSettings.splineHoveredColor;
|
||||
|
||||
drawer.DrawBezier(
|
||||
GetPoint(index).position,
|
||||
GetRightTangent(index),
|
||||
GetLeftTangent(nextIndex),
|
||||
GetPoint(nextIndex).position,
|
||||
5f,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnableCreatePointRepaint(IGUIState guiState, Control pointControl, Control leftTangentControl, Control rightTangentControl)
|
||||
{
|
||||
return guiState.nearestControl != pointControl.ID &&
|
||||
guiState.hotControl == 0 &&
|
||||
(guiState.nearestControl != leftTangentControl.ID) &&
|
||||
(guiState.nearestControl != rightTangentControl.ID);
|
||||
}
|
||||
|
||||
private Vector3 SnapIfNeeded(Vector3 position)
|
||||
{
|
||||
if (!controller.enableSnapping || controller.snapping == null)
|
||||
return position;
|
||||
|
||||
var guiPosition = HandleUtility.WorldToGUIPoint(position);
|
||||
var snappedGuiPosition = HandleUtility.WorldToGUIPoint(controller.snapping.Snap(position));
|
||||
var sqrDistance = (guiPosition - snappedGuiPosition).sqrMagnitude;
|
||||
|
||||
if (sqrDistance < kSnappingDistance * kSnappingDistance)
|
||||
position = controller.snapping.Snap(position);
|
||||
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.U2D.Common.Path.GUIFramework;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal abstract class RectSelector<T> : ISelector<T>
|
||||
{
|
||||
internal class Styles
|
||||
{
|
||||
public readonly GUIStyle selectionRectStyle;
|
||||
|
||||
public Styles()
|
||||
{
|
||||
selectionRectStyle = GUI.skin.FindStyle("selectionRect");
|
||||
}
|
||||
}
|
||||
|
||||
public Action<ISelector<T>, bool> onSelectionBegin;
|
||||
public Action<ISelector<T>> onSelectionChanged;
|
||||
public Action<ISelector<T>> onSelectionEnd;
|
||||
|
||||
private GUISystem m_GUISystem;
|
||||
private Control m_RectSelectorControl;
|
||||
private GUIAction m_RectSelectAction;
|
||||
private Rect m_GUIRect;
|
||||
private Styles m_Styles;
|
||||
|
||||
public Rect guiRect
|
||||
{
|
||||
get { return m_GUIRect; }
|
||||
}
|
||||
|
||||
private Styles styles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Styles == null)
|
||||
m_Styles = new Styles();
|
||||
|
||||
return m_Styles;
|
||||
}
|
||||
}
|
||||
|
||||
public RectSelector() : this(new GUISystem(new GUIState())) { }
|
||||
|
||||
public RectSelector(GUISystem guiSystem)
|
||||
{
|
||||
m_GUISystem = guiSystem;
|
||||
|
||||
m_RectSelectorControl = new GenericDefaultControl("RectSelector");
|
||||
|
||||
var start = Vector2.zero;
|
||||
var rectEnd = Vector2.zero;
|
||||
m_RectSelectAction = new SliderAction(m_RectSelectorControl)
|
||||
{
|
||||
enable = (guiState, action) => !IsAltDown(guiState),
|
||||
enableRepaint = (guiState, action) =>
|
||||
{
|
||||
var size = start - rectEnd;
|
||||
return size != Vector2.zero && guiState.hotControl == action.ID;
|
||||
},
|
||||
onSliderBegin = (guiState, control, position) =>
|
||||
{
|
||||
start = guiState.mousePosition;
|
||||
rectEnd = guiState.mousePosition;
|
||||
m_GUIRect = FromToRect(start, rectEnd);
|
||||
|
||||
if (onSelectionBegin != null)
|
||||
onSelectionBegin(this, guiState.isShiftDown);
|
||||
},
|
||||
onSliderChanged = (guiState, control, position) =>
|
||||
{
|
||||
rectEnd = guiState.mousePosition;
|
||||
m_GUIRect = FromToRect(start, rectEnd);
|
||||
|
||||
if (onSelectionChanged != null)
|
||||
onSelectionChanged(this);
|
||||
},
|
||||
onSliderEnd = (guiState, control, position) =>
|
||||
{
|
||||
if (onSelectionEnd != null)
|
||||
onSelectionEnd(this);
|
||||
},
|
||||
onRepaint = (guiState, action) =>
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
styles.selectionRectStyle.Draw(m_GUIRect, GUIContent.none, false, false, false, false);
|
||||
Handles.EndGUI();
|
||||
}
|
||||
};
|
||||
|
||||
m_GUISystem.AddControl(m_RectSelectorControl);
|
||||
m_GUISystem.AddAction(m_RectSelectAction);
|
||||
}
|
||||
|
||||
private bool IsAltDown(IGUIState guiState)
|
||||
{
|
||||
return guiState.hotControl == 0 && guiState.isAltDown;
|
||||
}
|
||||
|
||||
private Rect FromToRect(Vector2 start, Vector2 end)
|
||||
{
|
||||
Rect r = new Rect(start.x, start.y, end.x - start.x, end.y - start.y);
|
||||
if (r.width < 0)
|
||||
{
|
||||
r.x += r.width;
|
||||
r.width = -r.width;
|
||||
}
|
||||
if (r.height < 0)
|
||||
{
|
||||
r.y += r.height;
|
||||
r.height = -r.height;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
m_GUISystem.OnGUI();
|
||||
}
|
||||
|
||||
bool ISelector<T>.Select(T element)
|
||||
{
|
||||
return Select(element);
|
||||
}
|
||||
|
||||
protected abstract bool Select(T element);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface ISelectable<T>
|
||||
{
|
||||
bool Select(ISelector<T> selector);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface ISelection<T>
|
||||
{
|
||||
int Count { get; }
|
||||
T activeElement { get; set; }
|
||||
T[] elements { get; set; }
|
||||
void Clear();
|
||||
void BeginSelection();
|
||||
void EndSelection(bool select);
|
||||
bool Select(T element, bool select);
|
||||
bool Contains(T element);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal interface ISelector<T>
|
||||
{
|
||||
bool Select(T element);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
[Serializable]
|
||||
internal class IndexedSelection : SerializableSelection<int>
|
||||
{
|
||||
protected override int GetInvalidElement() { return -1; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class PointRectSelector : RectSelector<Vector3>
|
||||
{
|
||||
protected override bool Select(Vector3 element)
|
||||
{
|
||||
return guiRect.Contains(HandleUtility.WorldToGUIPoint(element), true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
[Serializable]
|
||||
internal abstract class SerializableSelection<T> : ISelection<T>, ISerializationCallbackReceiver
|
||||
{
|
||||
internal readonly static int kInvalidID = -1;
|
||||
|
||||
[SerializeField]
|
||||
private T[] m_Keys = new T[0];
|
||||
|
||||
private HashSet<T> m_Selection = new HashSet<T>();
|
||||
private HashSet<T> m_TemporalSelection = new HashSet<T>();
|
||||
private bool m_SelectionInProgress = false;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return m_Selection.Count + m_TemporalSelection.Count; }
|
||||
}
|
||||
|
||||
public T activeElement
|
||||
{
|
||||
get { return First(); }
|
||||
set
|
||||
{
|
||||
Clear();
|
||||
Select(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
public T[] elements
|
||||
{
|
||||
get
|
||||
{
|
||||
var set = m_Selection;
|
||||
|
||||
if (m_SelectionInProgress)
|
||||
{
|
||||
var union = new HashSet<T>(m_Selection);
|
||||
union.UnionWith(m_TemporalSelection);
|
||||
set = union;
|
||||
}
|
||||
|
||||
return new List<T>(set).ToArray();
|
||||
}
|
||||
set
|
||||
{
|
||||
Clear();
|
||||
foreach(var element in value)
|
||||
Select(element, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract T GetInvalidElement();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
GetSelection().Clear();
|
||||
}
|
||||
|
||||
public void BeginSelection()
|
||||
{
|
||||
m_SelectionInProgress = true;
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void EndSelection(bool select)
|
||||
{
|
||||
m_SelectionInProgress = false;
|
||||
|
||||
if (select)
|
||||
m_Selection.UnionWith(m_TemporalSelection);
|
||||
else
|
||||
m_Selection.ExceptWith(m_TemporalSelection);
|
||||
|
||||
m_TemporalSelection.Clear();
|
||||
}
|
||||
|
||||
public bool Select(T element, bool select)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
if(EqualityComparer<T>.Default.Equals(element, GetInvalidElement()))
|
||||
return changed;
|
||||
|
||||
if (select)
|
||||
changed = GetSelection().Add(element);
|
||||
else if (Contains(element))
|
||||
changed = GetSelection().Remove(element);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public bool Contains(T element)
|
||||
{
|
||||
return m_Selection.Contains(element) || m_TemporalSelection.Contains(element);
|
||||
}
|
||||
|
||||
private HashSet<T> GetSelection()
|
||||
{
|
||||
if (m_SelectionInProgress)
|
||||
return m_TemporalSelection;
|
||||
|
||||
return m_Selection;
|
||||
}
|
||||
|
||||
private T First()
|
||||
{
|
||||
T element = First(m_Selection);
|
||||
|
||||
if(EqualityComparer<T>.Default.Equals(element, GetInvalidElement()))
|
||||
element = First(m_TemporalSelection);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private T First(HashSet<T> set)
|
||||
{
|
||||
if(set.Count == 0)
|
||||
return GetInvalidElement();
|
||||
|
||||
using (var enumerator = set.GetEnumerator())
|
||||
{
|
||||
Debug.Assert(enumerator.MoveNext());
|
||||
return enumerator.Current;
|
||||
}
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Keys = new List<T>(m_Selection).ToArray();
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
elements = m_Keys;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal enum ShapeType
|
||||
{
|
||||
Polygon,
|
||||
Spline
|
||||
}
|
||||
|
||||
internal interface IShape
|
||||
{
|
||||
ShapeType type { get; }
|
||||
bool isOpenEnded { get; }
|
||||
ControlPoint[] ToControlPoints();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal struct Polygon : IShape
|
||||
{
|
||||
public bool isOpenEnded;
|
||||
|
||||
public Vector3[] points;
|
||||
|
||||
ShapeType IShape.type => ShapeType.Polygon;
|
||||
|
||||
bool IShape.isOpenEnded => isOpenEnded;
|
||||
|
||||
ControlPoint[] IShape.ToControlPoints()
|
||||
{
|
||||
if (points == null)
|
||||
throw new NullReferenceException("Points array is null");
|
||||
|
||||
var controlPoints = new List<ControlPoint>();
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
controlPoints.Add(new ControlPoint() { position = point });
|
||||
}
|
||||
|
||||
return controlPoints.ToArray();
|
||||
}
|
||||
|
||||
public static Polygon empty = new Polygon() { isOpenEnded = true, points = new Vector3[0] };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal static class ShapeExtensions
|
||||
{
|
||||
public static Polygon ToPolygon(this Vector3[] points, bool isOpenEnded)
|
||||
{
|
||||
return new Polygon()
|
||||
{
|
||||
isOpenEnded = isOpenEnded,
|
||||
points = points
|
||||
};
|
||||
}
|
||||
|
||||
public static Spline ToSpline(this Vector3[] points, bool isOpenEnded)
|
||||
{
|
||||
if (!points.IsSpline(isOpenEnded) && points.IsSpline(!isOpenEnded))
|
||||
{
|
||||
var pointList = new List<Vector3>(points);
|
||||
|
||||
if (isOpenEnded)
|
||||
{
|
||||
while (pointList.Count % 3 != 1)
|
||||
pointList.RemoveAt(pointList.Count-1);
|
||||
|
||||
points = pointList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var last = pointList[pointList.Count-1];
|
||||
var first = pointList[0];
|
||||
var v = first - last;
|
||||
|
||||
pointList.Add(last + v.normalized * (v.magnitude / 3f));
|
||||
pointList.Add(first - v.normalized * (v.magnitude / 3f));
|
||||
|
||||
points = pointList.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (!points.IsSpline(isOpenEnded))
|
||||
throw new Exception("The provided control point array can't conform a Spline.");
|
||||
|
||||
return new Spline()
|
||||
{
|
||||
isOpenEnded = isOpenEnded,
|
||||
points = points
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsSpline(this Vector3[] points, bool isOpenEnded)
|
||||
{
|
||||
if (points.Length < 4)
|
||||
return false;
|
||||
|
||||
if (isOpenEnded && points.Length % 3 != 1)
|
||||
return false;
|
||||
|
||||
if (!isOpenEnded && points.Length % 3 != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Spline ToSpline(this Polygon polygon)
|
||||
{
|
||||
var newPointCount = polygon.points.Length * 3;
|
||||
|
||||
if (polygon.isOpenEnded)
|
||||
newPointCount = (polygon.points.Length - 1) * 3 + 1;
|
||||
|
||||
var newPoints = new Vector3[newPointCount];
|
||||
var controlPoints = polygon.points;
|
||||
var pointCount = controlPoints.Length;
|
||||
|
||||
for (var i = 0; i < pointCount; ++i)
|
||||
{
|
||||
var nextIndex = (i + 1) % pointCount;
|
||||
var point = controlPoints[i];
|
||||
var v = controlPoints[nextIndex] - point;
|
||||
|
||||
newPoints[i * 3] = point;
|
||||
|
||||
if (i * 3 + 2 < newPointCount)
|
||||
{
|
||||
newPoints[i * 3 + 1] = point + v / 3f;
|
||||
newPoints[i * 3 + 2] = point + v * 2f / 3f;
|
||||
}
|
||||
}
|
||||
|
||||
return new Spline()
|
||||
{
|
||||
isOpenEnded = polygon.isOpenEnded,
|
||||
points = newPoints
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal struct Spline : IShape
|
||||
{
|
||||
public bool isOpenEnded;
|
||||
|
||||
public Vector3[] points;
|
||||
|
||||
ShapeType IShape.type => ShapeType.Spline;
|
||||
|
||||
bool IShape.isOpenEnded => isOpenEnded;
|
||||
|
||||
ControlPoint[] IShape.ToControlPoints()
|
||||
{
|
||||
if (points == null)
|
||||
throw new NullReferenceException("Points array is null");
|
||||
|
||||
if (!points.IsSpline(isOpenEnded))
|
||||
throw new Exception("The provided control point array can't conform a Spline.");
|
||||
|
||||
var controlPoints = new List<ControlPoint>();
|
||||
var leftTangent = Vector3.zero;
|
||||
var rightTangent = Vector3.zero;
|
||||
var pointCount = points.Length;
|
||||
|
||||
for (var i = 0; i < pointCount; i += 3)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
if (isOpenEnded)
|
||||
leftTangent = points[0];
|
||||
else
|
||||
leftTangent = points[EditablePathUtility.Mod(-1, pointCount)];
|
||||
}
|
||||
|
||||
if (i == pointCount - 1 && isOpenEnded)
|
||||
rightTangent = points[i];
|
||||
else
|
||||
rightTangent = points[i+1];
|
||||
|
||||
|
||||
controlPoints.Add(
|
||||
new ControlPoint()
|
||||
{
|
||||
position = points[i],
|
||||
leftTangent = leftTangent,
|
||||
rightTangent = rightTangent,
|
||||
tangentMode = TangentMode.Broken
|
||||
});
|
||||
|
||||
if (i == pointCount - 1 && isOpenEnded)
|
||||
leftTangent = Vector3.zero;
|
||||
else
|
||||
leftTangent = points[i+2];
|
||||
}
|
||||
|
||||
pointCount = controlPoints.Count;
|
||||
|
||||
for (var i = 0; i < pointCount; ++i)
|
||||
{
|
||||
var prevIndex = EditablePathUtility.Mod(i-1, pointCount);
|
||||
var nextIndex = EditablePathUtility.Mod(i+1, pointCount);
|
||||
var controlPoint = controlPoints[i];
|
||||
var prevControlPoint = controlPoints[prevIndex];
|
||||
var nextControlPoint = controlPoints[nextIndex];
|
||||
|
||||
var liniarLeftPosition = (prevControlPoint.position - controlPoint.position) / 3f;
|
||||
var isLeftTangentLinear = (controlPoint.localLeftTangent - liniarLeftPosition).sqrMagnitude < 0.001f;
|
||||
|
||||
if (isLeftTangentLinear)
|
||||
controlPoint.localLeftTangent = Vector3.zero;
|
||||
|
||||
var liniarRightPosition = (nextControlPoint.position - controlPoint.position) / 3f;
|
||||
var isRightTangentLinear = (controlPoint.localRightTangent - liniarRightPosition).sqrMagnitude < 0.001f;
|
||||
|
||||
if (isRightTangentLinear)
|
||||
controlPoint.localRightTangent = Vector3.zero;
|
||||
|
||||
var tangentDotProduct = Vector3.Dot(controlPoint.localLeftTangent.normalized, controlPoint.localRightTangent.normalized);
|
||||
var isContinous = tangentDotProduct < 0f && (tangentDotProduct + 1) * (tangentDotProduct + 1) < 0.001f;
|
||||
|
||||
if (isLeftTangentLinear && isRightTangentLinear)
|
||||
controlPoint.tangentMode = TangentMode.Linear;
|
||||
else if (isLeftTangentLinear || isRightTangentLinear)
|
||||
controlPoint.tangentMode = TangentMode.Broken;
|
||||
else if (isContinous)
|
||||
controlPoint.tangentMode = TangentMode.Continuous;
|
||||
|
||||
controlPoints[i] = controlPoint;
|
||||
}
|
||||
|
||||
return controlPoints.ToArray();
|
||||
}
|
||||
|
||||
public static Spline empty = new Spline() { isOpenEnded = true, points = new Vector3[0] };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "Unity.2D.Common.Path.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.Common.Path
|
||||
{
|
||||
internal class ControlPointSettings
|
||||
{
|
||||
const string kControlPointRKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointR";
|
||||
const string kControlPointGKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointG";
|
||||
const string kControlPointBKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointB";
|
||||
const string kControlPointAKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointA";
|
||||
static readonly GUIContent kControlPointKeyContent = EditorGUIUtility.TrTextContent("ControlPoint Color");
|
||||
|
||||
const string kControlPointSRKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointSR";
|
||||
const string kControlPointSGKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointSG";
|
||||
const string kControlPointSBKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointSB";
|
||||
const string kControlPointSAKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.ControlPointSA";
|
||||
static readonly GUIContent kControlPointSKeyContent = EditorGUIUtility.TrTextContent("ControlPoint (Selected)");
|
||||
|
||||
static bool showHandle = true;
|
||||
|
||||
public static Color controlPointColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Color()
|
||||
{
|
||||
r = EditorPrefs.GetFloat(kControlPointRKey, 1),
|
||||
g = EditorPrefs.GetFloat(kControlPointGKey, 1),
|
||||
b = EditorPrefs.GetFloat(kControlPointBKey, 1),
|
||||
a = EditorPrefs.GetFloat(kControlPointAKey, 1)
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(kControlPointRKey, value.r);
|
||||
EditorPrefs.SetFloat(kControlPointGKey, value.g);
|
||||
EditorPrefs.SetFloat(kControlPointBKey, value.b);
|
||||
EditorPrefs.SetFloat(kControlPointAKey, value.a);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color controlPointSelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Color()
|
||||
{
|
||||
r = EditorPrefs.GetFloat(kControlPointSRKey, 1),
|
||||
g = EditorPrefs.GetFloat(kControlPointSGKey, 235.0f / 255.0f),
|
||||
b = EditorPrefs.GetFloat(kControlPointSBKey, 4F / 255F),
|
||||
a = EditorPrefs.GetFloat(kControlPointSAKey, 1)
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(kControlPointSRKey, value.r);
|
||||
EditorPrefs.SetFloat(kControlPointSGKey, value.g);
|
||||
EditorPrefs.SetFloat(kControlPointSBKey, value.b);
|
||||
EditorPrefs.SetFloat(kControlPointSAKey, value.a);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetDefault()
|
||||
{
|
||||
controlPointColor = Color.white;
|
||||
controlPointSelectedColor = new Color(1.0f, 235.0f / 255.0f, 4.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space(8);
|
||||
showHandle = EditorGUILayout.BeginFoldoutHeaderGroup(showHandle, "Control Points");
|
||||
if (showHandle)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var sc = EditorGUILayout.ColorField(kControlPointKeyContent, controlPointColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
controlPointColor = sc;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var sh = EditorGUILayout.ColorField(kControlPointSKeyContent, controlPointSelectedColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
controlPointSelectedColor = sh;
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
}
|
||||
internal class HandleSettings
|
||||
{
|
||||
const string kSplineRKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineR";
|
||||
const string kSplineGKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineG";
|
||||
const string kSplineBKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineB";
|
||||
const string kSplineAKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineA";
|
||||
static readonly GUIContent kSplineKeyContent = EditorGUIUtility.TrTextContent("Spline Color");
|
||||
|
||||
const string kSplineHRKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineHR";
|
||||
const string kSplineHGKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineHG";
|
||||
const string kSplineHBKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineHB";
|
||||
const string kSplineHAKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.SplineHA";
|
||||
static readonly GUIContent kSplineHKeyContent = EditorGUIUtility.TrTextContent("Spline Color (Hovered)");
|
||||
|
||||
const string kTangentRKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.TangentR";
|
||||
const string kTangentGKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.TangentG";
|
||||
const string kTangentBKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.TangentB";
|
||||
const string kTangentAKey = UserSettings.kSettingsUniqueKey + "PathEditorSetting.TangentA";
|
||||
static readonly GUIContent kTangentKeyContent = EditorGUIUtility.TrTextContent("Tangent Color");
|
||||
|
||||
static bool showHandle = true;
|
||||
|
||||
public static Color splineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Color()
|
||||
{
|
||||
r = EditorPrefs.GetFloat(kSplineRKey, 1),
|
||||
g = EditorPrefs.GetFloat(kSplineGKey, 1),
|
||||
b = EditorPrefs.GetFloat(kSplineBKey, 1),
|
||||
a = EditorPrefs.GetFloat(kSplineAKey, 1)
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(kSplineRKey, value.r);
|
||||
EditorPrefs.SetFloat(kSplineGKey, value.g);
|
||||
EditorPrefs.SetFloat(kSplineBKey, value.b);
|
||||
EditorPrefs.SetFloat(kSplineAKey, value.a);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color splineHoveredColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Color()
|
||||
{
|
||||
r = EditorPrefs.GetFloat(kSplineHRKey, 1),
|
||||
g = EditorPrefs.GetFloat(kSplineHGKey, 235.0f / 255.0f),
|
||||
b = EditorPrefs.GetFloat(kSplineHBKey, 4F / 255F),
|
||||
a = EditorPrefs.GetFloat(kSplineHAKey, 1)
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(kSplineHRKey, value.r);
|
||||
EditorPrefs.SetFloat(kSplineHGKey, value.g);
|
||||
EditorPrefs.SetFloat(kSplineHBKey, value.b);
|
||||
EditorPrefs.SetFloat(kSplineHAKey, value.a);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color tangentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Color()
|
||||
{
|
||||
r = EditorPrefs.GetFloat(kTangentRKey, 1),
|
||||
g = EditorPrefs.GetFloat(kTangentGKey, 235.0f / 255.0f),
|
||||
b = EditorPrefs.GetFloat(kTangentBKey, 4F / 255F),
|
||||
a = EditorPrefs.GetFloat(kTangentAKey, 1)
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(kTangentRKey, value.r);
|
||||
EditorPrefs.SetFloat(kTangentGKey, value.g);
|
||||
EditorPrefs.SetFloat(kTangentBKey, value.b);
|
||||
EditorPrefs.SetFloat(kTangentAKey, value.a);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetDefault()
|
||||
{
|
||||
splineColor = Color.white;
|
||||
splineHoveredColor = new Color(1.0f, 235.0f / 255.0f, 4.0f / 255.0f, 1.0f);
|
||||
tangentColor = new Color(1.0f, 235.0f / 255.0f, 4.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space(8);
|
||||
showHandle = EditorGUILayout.BeginFoldoutHeaderGroup(showHandle, "Splines and Tangent");
|
||||
if (showHandle)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var sc = EditorGUILayout.ColorField(kSplineKeyContent, splineColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
splineColor = sc;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var sh = EditorGUILayout.ColorField(kSplineHKeyContent, splineHoveredColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
splineHoveredColor = sh;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var tc = EditorGUILayout.ColorField(kTangentKeyContent, tangentColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
tangentColor = tc;
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserSettings : SettingsProvider
|
||||
{
|
||||
public const string kSettingsUniqueKey = "UnityEditor.U2D.SpriteShape/";
|
||||
private static ControlPointSettings s_ControlPointSettings = new ControlPointSettings();
|
||||
private static HandleSettings s_HandleSettings = new HandleSettings();
|
||||
|
||||
public UserSettings() : base("Preferences/2D/SpriteShape", SettingsScope.User)
|
||||
{
|
||||
guiHandler = OnGUI;
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
private static SettingsProvider CreateSettingsProvider()
|
||||
{
|
||||
return new UserSettings()
|
||||
{
|
||||
guiHandler = SettingsGUI
|
||||
};
|
||||
}
|
||||
|
||||
private static void SettingsGUI(string searchContext)
|
||||
{
|
||||
s_ControlPointSettings.OnGUI();
|
||||
s_HandleSettings.OnGUI();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button("Use Defaults", new GUILayoutOption[] { GUILayout.Width(100)}))
|
||||
{
|
||||
s_ControlPointSettings.SetDefault();
|
||||
s_HandleSettings.SetDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
2D Shared Code
|
||||
|
||||
- UTess - a 2D geometry generation toolkit.
|
||||
- ImagePacker - fits a list of textures or rects into a bigger rect.
|
|
@ -0,0 +1,5 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Common.Tests.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Runtime")]
|
|
@ -0,0 +1,8 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Animation.Tests.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.IK.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.IK.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.PSDImporter.Editor")]
|
|
@ -0,0 +1,6 @@
|
|||
using UnityEngine.Animations;
|
||||
|
||||
namespace UnityEngine.U2D.Common
|
||||
{
|
||||
internal interface IPreviewable : IAnimationPreviewable { }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Unity.InternalAPIEngineBridge.001",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using UnityEngine.UIElements;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.U2D.Common
|
||||
{
|
||||
internal static class InternalEngineBridge
|
||||
{
|
||||
public static void SetLocalAABB(SpriteRenderer spriteRenderer, Bounds aabb)
|
||||
{
|
||||
spriteRenderer.SetLocalAABB(aabb);
|
||||
}
|
||||
|
||||
public static void SetDeformableBuffer(SpriteRenderer spriteRenderer, NativeArray<byte> src)
|
||||
{
|
||||
spriteRenderer.SetDeformableBuffer(src);
|
||||
}
|
||||
|
||||
public static bool IsUsingDeformableBuffer(SpriteRenderer spriteRenderer, IntPtr buffer)
|
||||
{
|
||||
return spriteRenderer.IsUsingDeformableBuffer(buffer);
|
||||
}
|
||||
|
||||
public static Vector2 GUIUnclip(Vector2 v)
|
||||
{
|
||||
return GUIClip.Unclip(v);
|
||||
}
|
||||
|
||||
public static Rect GetGUIClipTopMostRect()
|
||||
{
|
||||
return GUIClip.topmostRect;
|
||||
}
|
||||
|
||||
public static Rect GetGUIClipTopRect()
|
||||
{
|
||||
return GUIClip.GetTopRect();
|
||||
}
|
||||
|
||||
public static Rect GetGUIClipVisibleRect()
|
||||
{
|
||||
return GUIClip.visibleRect;
|
||||
}
|
||||
|
||||
|
||||
public static void SetBatchDeformableBufferAndLocalAABBArray(SpriteRenderer[] spriteRenderers, NativeArray<IntPtr> buffers, NativeArray<int> bufferSizes, NativeArray<Bounds> bounds)
|
||||
{
|
||||
SpriteRendererDataAccessExtensions.SetBatchDeformableBufferAndLocalAABBArray(spriteRenderers, buffers, bufferSizes, bounds);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static void SetLocalEulerHint(Transform t)
|
||||
{
|
||||
t.SetLocalEulerHint(t.GetLocalEulerAngles(t.rotationOrder));
|
||||
}
|
||||
|
||||
public static bool IsChecked(this VisualElement element)
|
||||
{
|
||||
return (element.pseudoStates & PseudoStates.Checked) == PseudoStates.Checked;
|
||||
}
|
||||
|
||||
public static bool IsHovered(this VisualElement element)
|
||||
{
|
||||
return (element.pseudoStates & PseudoStates.Hover) == PseudoStates.Hover;
|
||||
}
|
||||
|
||||
public static void SetChecked(this VisualElement element, bool isChecked)
|
||||
{
|
||||
if (isChecked)
|
||||
{
|
||||
element.pseudoStates |= PseudoStates.Checked;
|
||||
}
|
||||
else
|
||||
{
|
||||
element.pseudoStates &= ~PseudoStates.Checked;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public static int ConvertFloatToInt(float f)
|
||||
{
|
||||
return Animations.DiscreteEvaluationAttributeUtilities.ConvertFloatToDiscreteInt(f);
|
||||
}
|
||||
|
||||
public static float ConvertIntToFloat(int i)
|
||||
{
|
||||
return Animations.DiscreteEvaluationAttributeUtilities.ConvertDiscreteIntToFloat(i);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using Unity.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Burst;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Array. Used within UTess and constrained to
|
||||
/// 1. Auto-resizes upto the Max count with a smaller initial count.
|
||||
/// 2. Only be used within the created thread. Read 1.
|
||||
/// 3. Read/Write access are all fast-paths.
|
||||
/// 4. Mostly used with Temp Alloc within UTess ontext.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[DebuggerDisplay("Length = {Length}")]
|
||||
[DebuggerTypeProxy(typeof(ArrayDebugView<>))]
|
||||
internal unsafe struct Array<T> : IDisposable where T : struct
|
||||
{
|
||||
internal NativeArray<T> m_Array;
|
||||
internal int m_MaxSize;
|
||||
internal Allocator m_AllocLabel;
|
||||
internal NativeArrayOptions m_Options;
|
||||
|
||||
public Array(int length, int maxSize, Allocator allocMode, NativeArrayOptions options)
|
||||
{
|
||||
m_Array = new NativeArray<T>(length, allocMode, options);
|
||||
m_AllocLabel = allocMode;
|
||||
m_Options = options;
|
||||
m_MaxSize = maxSize;
|
||||
}
|
||||
|
||||
|
||||
private void ResizeIfRequired(int index)
|
||||
{
|
||||
if (index >= m_MaxSize || index < 0)
|
||||
throw new IndexOutOfRangeException(
|
||||
$"Trying to access beyond allowed size. {index} is out of range of '{m_MaxSize}' MaxSize.");
|
||||
if (index < m_Array.Length)
|
||||
return;
|
||||
|
||||
int requiredSize = Length;
|
||||
while (requiredSize <= index)
|
||||
requiredSize = requiredSize * 2;
|
||||
|
||||
requiredSize = requiredSize > m_MaxSize ? m_MaxSize : requiredSize;
|
||||
var copyArray = new NativeArray<T>(requiredSize, m_AllocLabel, m_Options);
|
||||
|
||||
NativeArray<T>.Copy(m_Array, copyArray, Length);
|
||||
m_Array.Dispose();
|
||||
m_Array = copyArray;
|
||||
}
|
||||
|
||||
public unsafe T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Array[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ResizeIfRequired(index);
|
||||
m_Array[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCreated => m_Array.IsCreated;
|
||||
|
||||
public int Length => (m_MaxSize != 0) ? m_Array.Length : 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Array.Dispose();
|
||||
m_MaxSize = 0;
|
||||
}
|
||||
|
||||
public void* UnsafePtr
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Array.GetUnsafePtr();
|
||||
}
|
||||
}
|
||||
|
||||
public void* UnsafeReadOnlyPtr
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Array.GetUnsafeReadOnlyPtr();
|
||||
}
|
||||
}
|
||||
|
||||
// Should only ever be used for Debugging.
|
||||
public void CopyTo(T[] array)
|
||||
{
|
||||
m_Array.CopyTo(array);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DebuggerTypeProxy for <see cref="Array{T}"/>
|
||||
/// </summary>
|
||||
internal sealed class ArrayDebugView<T> where T : struct
|
||||
{
|
||||
private Array<T> array;
|
||||
|
||||
public ArrayDebugView(Array<T> array)
|
||||
{
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public T[] Items
|
||||
{
|
||||
get
|
||||
{
|
||||
var ret = new T[array.Length];
|
||||
array.CopyTo(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using Unity.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[DebuggerDisplay("Length = {Length}")]
|
||||
[DebuggerTypeProxy(typeof(ArraySliceDebugView<>))]
|
||||
internal unsafe struct ArraySlice<T> : System.IEquatable<ArraySlice<T>> where T : struct
|
||||
{
|
||||
[NativeDisableUnsafePtrRestriction] internal byte* m_Buffer;
|
||||
internal int m_Stride;
|
||||
internal int m_Length;
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
internal int m_MinIndex;
|
||||
internal int m_MaxIndex;
|
||||
internal AtomicSafetyHandle m_Safety;
|
||||
#endif
|
||||
|
||||
public ArraySlice(NativeArray<T> array, int start, int length)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start), $"Slice start {start} < 0.");
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(length), $"Slice length {length} < 0.");
|
||||
if (start + length > array.Length)
|
||||
throw new ArgumentException(
|
||||
$"Slice start + length ({start + length}) range must be <= array.Length ({array.Length})");
|
||||
m_MinIndex = 0;
|
||||
m_MaxIndex = length - 1;
|
||||
m_Safety = Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array);
|
||||
#endif
|
||||
|
||||
m_Stride = UnsafeUtility.SizeOf<T>();
|
||||
var ptr = (byte*)array.GetUnsafePtr() + m_Stride * start;
|
||||
m_Buffer = ptr;
|
||||
m_Length = length;
|
||||
}
|
||||
|
||||
public ArraySlice(Array<T> array, int start, int length)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start), $"Slice start {start} < 0.");
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(length), $"Slice length {length} < 0.");
|
||||
if (start + length > array.Length)
|
||||
throw new ArgumentException(
|
||||
$"Slice start + length ({start + length}) range must be <= array.Length ({array.Length})");
|
||||
m_MinIndex = 0;
|
||||
m_MaxIndex = length - 1;
|
||||
m_Safety = Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array.m_Array);
|
||||
#endif
|
||||
|
||||
m_Stride = UnsafeUtility.SizeOf<T>();
|
||||
var ptr = (byte*)array.UnsafePtr + m_Stride * start;
|
||||
m_Buffer = ptr;
|
||||
m_Length = length;
|
||||
}
|
||||
|
||||
public bool Equals(ArraySlice<T> other)
|
||||
{
|
||||
return m_Buffer == other.m_Buffer && m_Stride == other.m_Stride && m_Length == other.m_Length;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is ArraySlice<T> && Equals((ArraySlice<T>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int)m_Buffer;
|
||||
hashCode = (hashCode * 397) ^ m_Stride;
|
||||
hashCode = (hashCode * 397) ^ m_Length;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(ArraySlice<T> left, ArraySlice<T> right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ArraySlice<T> left, ArraySlice<T> right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
// These are double-whammy excluded to we can elide bounds checks in the Burst disassembly view
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
void CheckReadIndex(int index)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (index < m_MinIndex || index > m_MaxIndex)
|
||||
FailOutOfRangeError(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
void CheckWriteIndex(int index)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (index < m_MinIndex || index > m_MaxIndex)
|
||||
FailOutOfRangeError(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
private void FailOutOfRangeError(int index)
|
||||
{
|
||||
if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1))
|
||||
throw new System.IndexOutOfRangeException(
|
||||
$"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\n" +
|
||||
"ReadWriteBuffers are restricted to only read & write the element at the job index. " +
|
||||
"You can use double buffering strategies to avoid race conditions due to " +
|
||||
"reading & writing in parallel to the same elements from a job.");
|
||||
|
||||
throw new System.IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length.");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public static unsafe ArraySlice<T> ConvertExistingDataToArraySlice(void* dataPointer, int stride, int length)
|
||||
{
|
||||
if (length < 0)
|
||||
throw new System.ArgumentException($"Invalid length of '{length}'. It must be greater than 0.",
|
||||
nameof(length));
|
||||
if (stride < 0)
|
||||
throw new System.ArgumentException($"Invalid stride '{stride}'. It must be greater than 0.",
|
||||
nameof(stride));
|
||||
|
||||
var newSlice = new ArraySlice<T>
|
||||
{
|
||||
m_Stride = stride,
|
||||
m_Buffer = (byte*)dataPointer,
|
||||
m_Length = length,
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
m_MinIndex = 0,
|
||||
m_MaxIndex = length - 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
return newSlice;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
CheckReadIndex(index);
|
||||
#endif
|
||||
return UnsafeUtility.ReadArrayElementWithStride<T>(m_Buffer, index, m_Stride);
|
||||
}
|
||||
|
||||
[WriteAccessRequired]
|
||||
set
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
CheckWriteIndex(index);
|
||||
#endif
|
||||
UnsafeUtility.WriteArrayElementWithStride(m_Buffer, index, m_Stride, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe void* GetUnsafeReadOnlyPtr()
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
|
||||
#endif
|
||||
return m_Buffer;
|
||||
}
|
||||
|
||||
internal void CopyTo(T[] array)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (Length != array.Length)
|
||||
throw new ArgumentException($"array.Length ({array.Length}) does not match the Length of this instance ({Length}).", nameof(array));
|
||||
#endif
|
||||
unsafe
|
||||
{
|
||||
GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
|
||||
IntPtr addr = handle.AddrOfPinnedObject();
|
||||
|
||||
var sizeOf = UnsafeUtility.SizeOf<T>();
|
||||
UnsafeUtility.MemCpyStride((byte*)addr, sizeOf, this.GetUnsafeReadOnlyPtr(), Stride, sizeOf, m_Length);
|
||||
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
internal T[] ToArray()
|
||||
{
|
||||
var array = new T[Length];
|
||||
CopyTo(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int Stride => m_Stride;
|
||||
public int Length => m_Length;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DebuggerTypeProxy for <see cref="ArraySlice{T}"/>
|
||||
/// </summary>
|
||||
internal sealed class ArraySliceDebugView<T> where T : struct
|
||||
{
|
||||
ArraySlice<T> m_Slice;
|
||||
|
||||
public ArraySliceDebugView(ArraySlice<T> slice)
|
||||
{
|
||||
m_Slice = slice;
|
||||
}
|
||||
|
||||
public T[] Items
|
||||
{
|
||||
get { return m_Slice.ToArray(); }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
// Ensures that there are no duplicate Points, Overlapping Edges.
|
||||
struct PlanarGraph
|
||||
{
|
||||
|
||||
private static readonly double kEpsilon = 0.00001;
|
||||
private static readonly int kMaxIntersectionTolerance = 4; // Maximum Intersection Tolerance per Intersection Loop Check.
|
||||
|
||||
internal static void RemoveDuplicateEdges(ref Array<int2> edges, ref int edgeCount, Array<int> duplicates, int duplicateCount)
|
||||
{
|
||||
|
||||
if (duplicateCount == 0)
|
||||
{
|
||||
for (var i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
var e = edges[i];
|
||||
e.x = math.min(edges[i].x, edges[i].y);
|
||||
e.y = math.max(edges[i].x, edges[i].y);
|
||||
edges[i] = e;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
var e = edges[i];
|
||||
var a = duplicates[e.x];
|
||||
var b = duplicates[e.y];
|
||||
e.x = math.min(a, b);
|
||||
e.y = math.max(a, b);
|
||||
edges[i] = e;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int2, TessEdgeCompare>(edges.UnsafePtr, 0, edgeCount - 1,new TessEdgeCompare());
|
||||
}
|
||||
|
||||
var n = 1;
|
||||
for (var i = 1; i < edgeCount; ++i)
|
||||
{
|
||||
var prev = edges[i - 1];
|
||||
var next = edges[i];
|
||||
if (next.x == prev.x && next.y == prev.y)
|
||||
continue;
|
||||
if (next.x == next.y)
|
||||
continue;
|
||||
edges[n++] = next;
|
||||
}
|
||||
edgeCount = n;
|
||||
}
|
||||
|
||||
internal static bool CheckCollinear(double2 a0, double2 a1, double2 b0, double2 b1)
|
||||
{
|
||||
double2 a = a0;
|
||||
double2 b = a1;
|
||||
double2 c = b0;
|
||||
double2 d = b1;
|
||||
|
||||
double x = (b.y - a.y) / (b.x - a.x);
|
||||
double y = (c.y - a.y) / (c.x - a.x);
|
||||
double z = (d.y - a.y) / (d.x - a.x);
|
||||
return ((!math.isinf(x) || !math.isinf(y) || !math.isinf(z)) && math.abs(x - y) > kEpsilon && math.abs(x - z) > kEpsilon);
|
||||
}
|
||||
|
||||
internal static bool LineLineIntersection(double2 a0, double2 a1, double2 b0, double2 b1)
|
||||
{
|
||||
var x0 = ModuleHandle.OrientFastDouble(a0, b0, b1);
|
||||
var y0 = ModuleHandle.OrientFastDouble(a1, b0, b1);
|
||||
if ((x0 > kEpsilon && y0 > kEpsilon) || (x0 < -kEpsilon && y0 < -kEpsilon))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var x1 = ModuleHandle.OrientFastDouble(b0, a0, a1);
|
||||
var y1 = ModuleHandle.OrientFastDouble(b1, a0, a1);
|
||||
if ((x1 > kEpsilon && y1 > kEpsilon) || (x1 < -kEpsilon && y1 < -kEpsilon))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for degenerate collinear case
|
||||
if (math.abs(x0) < kEpsilon && math.abs(y0) < kEpsilon && math.abs(x1) < kEpsilon && math.abs(y1) < kEpsilon)
|
||||
{
|
||||
return CheckCollinear(a0, a1, b0, b1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool LineLineIntersection(double2 p1, double2 p2, double2 p3, double2 p4, ref double2 result)
|
||||
{
|
||||
double bx = p2.x - p1.x;
|
||||
double by = p2.y - p1.y;
|
||||
double dx = p4.x - p3.x;
|
||||
double dy = p4.y - p3.y;
|
||||
double bDotDPerp = bx * dy - by * dx;
|
||||
if (math.abs(bDotDPerp) < kEpsilon)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double cx = p3.x - p1.x;
|
||||
double cy = p3.y - p1.y;
|
||||
double t = (cx * dy - cy * dx) / bDotDPerp;
|
||||
if ((t >= -kEpsilon) && (t <= 1.0f + kEpsilon))
|
||||
{
|
||||
result.x = p1.x + t * bx;
|
||||
result.y = p1.y + t * by;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool CalculateEdgeIntersections(Array<int2> edges, int edgeCount, Array<double2> points, int pointCount, ref Array<int2> results, ref Array<double2> intersects, ref int resultCount)
|
||||
{
|
||||
resultCount = 0;
|
||||
|
||||
for (int i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < edgeCount; ++j)
|
||||
{
|
||||
var e = edges[i];
|
||||
var f = edges[j];
|
||||
if (e.x == f.x || e.x == f.y || e.y == f.x || e.y == f.y)
|
||||
continue;
|
||||
|
||||
var a = points[e.x];
|
||||
var b = points[e.y];
|
||||
var c = points[f.x];
|
||||
var d = points[f.y];
|
||||
var g = double2.zero;
|
||||
if (LineLineIntersection(a, b, c, d))
|
||||
{
|
||||
if (LineLineIntersection(a, b, c, d, ref g))
|
||||
{
|
||||
// Until we ensure Outline is generated properly, we need this useless Check every correction.
|
||||
if (resultCount >= intersects.Length)
|
||||
return false;
|
||||
|
||||
intersects[resultCount] = g;
|
||||
results[resultCount++] = new int2(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basically we have self intersections all over (eg. gobo_tree_04). Better don't generate any Mesh as even though this will eventually succeed, error correction will take long time.
|
||||
if (resultCount > (edgeCount * kMaxIntersectionTolerance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var tjc = new IntersectionCompare();
|
||||
tjc.edges = edges;
|
||||
tjc.points = points;
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int2, IntersectionCompare>(results.UnsafePtr, 0, resultCount - 1, tjc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool CalculateTJunctions(Array<int2> edges, int edgeCount, Array<double2> points, int pointCount, Array<int2> results, ref int resultCount)
|
||||
{
|
||||
resultCount = 0;
|
||||
|
||||
for (int i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
for (int j = 0; j < pointCount; ++j)
|
||||
{
|
||||
var e = edges[i];
|
||||
if (e.x == j || e.y == j)
|
||||
continue;
|
||||
|
||||
var a = points[e.x];
|
||||
var b = points[e.y];
|
||||
var c = points[j];
|
||||
var d = points[j];
|
||||
if (LineLineIntersection(a, b, c, d))
|
||||
{
|
||||
// Until we ensure Outline is generated properly, we need this useless Check every correction.
|
||||
if (resultCount >= results.Length)
|
||||
return false;
|
||||
results[resultCount++] = new int2(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool CutEdges(ref Array<double2> points, ref int pointCount, ref Array<int2> edges, ref int edgeCount, ref Array<int2> tJunctions, ref int tJunctionCount, Array<int2> intersections, Array<double2> intersects, int intersectionCount)
|
||||
{
|
||||
for (int i = 0; i < intersectionCount; ++i)
|
||||
{
|
||||
var intr = intersections[i];
|
||||
var e = intr.x;
|
||||
var f = intr.y;
|
||||
|
||||
int2 j1 = int2.zero;
|
||||
j1.x = e;
|
||||
j1.y = pointCount;
|
||||
tJunctions[tJunctionCount++] = j1;
|
||||
int2 j2 = int2.zero;
|
||||
j2.x = f;
|
||||
j2.y = pointCount;
|
||||
tJunctions[tJunctionCount++] = j2;
|
||||
|
||||
// Until we ensure Outline is generated properly, we need this useless Check every correction.
|
||||
if (pointCount >= points.Length)
|
||||
return false;
|
||||
|
||||
points[pointCount++] = intersects[i];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int2, TessJunctionCompare>( tJunctions.UnsafePtr, 0, tJunctionCount - 1, new TessJunctionCompare());
|
||||
}
|
||||
|
||||
// Split edges along junctions
|
||||
for (int i = tJunctionCount - 1; i >= 0; --i)
|
||||
{
|
||||
var tJunction = tJunctions[i];
|
||||
var e = tJunction.x;
|
||||
var edge = edges[e];
|
||||
var s = edge.x;
|
||||
var t = edge.y;
|
||||
|
||||
// Check if edge is not lexicographically sorted
|
||||
var a = points[s];
|
||||
var b = points[t];
|
||||
if (((a.x - b.x) < 0) || (a.x == b.x && (a.y - b.y) < 0))
|
||||
{
|
||||
var tmp = s;
|
||||
s = t;
|
||||
t = tmp;
|
||||
}
|
||||
|
||||
// Split leading edge
|
||||
edge.x = s;
|
||||
var last = edge.y = tJunction.y;
|
||||
edges[e] = edge;
|
||||
|
||||
// If we are grouping edges by color, remember to track data
|
||||
// Split other edges
|
||||
while (i > 0 && tJunctions[i - 1].x == e)
|
||||
{
|
||||
var next = tJunctions[--i].y;
|
||||
int2 te = new int2();
|
||||
te.x = last;
|
||||
te.y = next;
|
||||
edges[edgeCount++] = te;
|
||||
last = next;
|
||||
}
|
||||
|
||||
int2 le = new int2();
|
||||
le.x = last;
|
||||
le.y = t;
|
||||
edges[edgeCount++] = le;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void RemoveDuplicatePoints(ref Array<double2> points, ref int pointCount, ref Array<int> duplicates, ref int duplicateCount, Allocator allocator)
|
||||
{
|
||||
TessLink link = TessLink.CreateLink(pointCount, allocator);
|
||||
|
||||
for (int i = 0; i < pointCount; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < pointCount; ++j)
|
||||
{
|
||||
if (math.distance(points[i], points[j]) < kEpsilon)
|
||||
{
|
||||
link.Link(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
duplicateCount = 0;
|
||||
for (var i = 0; i < pointCount; ++i)
|
||||
{
|
||||
var j = link.Find(i);
|
||||
if (j != i)
|
||||
{
|
||||
duplicateCount++;
|
||||
points[j] = math.min(points[i], points[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Find Duplicates.
|
||||
if (duplicateCount != 0)
|
||||
{
|
||||
|
||||
var prevPointCount = pointCount;
|
||||
pointCount = 0;
|
||||
for (var i = 0; i < prevPointCount; ++i)
|
||||
{
|
||||
var j = link.Find(i);
|
||||
if (j == i)
|
||||
{
|
||||
duplicates[i] = pointCount;
|
||||
points[pointCount++] = points[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
duplicates[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Duplicates.
|
||||
for (int i = 0; i < prevPointCount; ++i)
|
||||
{
|
||||
if (duplicates[i] < 0)
|
||||
{
|
||||
duplicates[i] = duplicates[link.Find(i)];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TessLink.DestroyLink(link);
|
||||
}
|
||||
|
||||
// Validate the Input Points ane Edges.
|
||||
internal static bool Validate(Allocator allocator, in NativeArray<float2> inputPoints, int pointCount, in NativeArray<int2> inputEdges, int edgeCount, ref NativeArray<float2> outputPoints, out int outputPointCount, ref NativeArray<int2> outputEdges, out int outputEdgeCount)
|
||||
{
|
||||
outputPointCount = 0;
|
||||
outputEdgeCount = 0;
|
||||
|
||||
// Outline generated inputs can have differences in the range of 0.00001f.. See TwoLayers.psb sample.
|
||||
// Since PlanarGraph operates on double, scaling up and down does not result in loss of data.
|
||||
var precisionFudge = 10000.0f;
|
||||
var protectLoop = edgeCount;
|
||||
var requiresFix = true;
|
||||
var validGraph = false;
|
||||
|
||||
// Processing Arrays.
|
||||
int startEdgeCount = edgeCount;
|
||||
Array<int> duplicates = new Array<int>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
Array<int2> edges = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
Array<int2> tJunctions = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
Array<int2> edgeIntersections = new Array<int2>(startEdgeCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
Array<double2> points = new Array<double2>(pointCount * 2, pointCount * 8, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
Array<double2> intersects = new Array<double2>(pointCount * 2, pointCount * 8, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
// Initialize.
|
||||
for (int i = 0; i < pointCount; ++i)
|
||||
points[i] = inputPoints[i] * precisionFudge;
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemCpy(edges.UnsafeReadOnlyPtr, inputEdges.GetUnsafePtr(), edgeCount * sizeof(int2));
|
||||
}
|
||||
|
||||
// Pre-clear duplicates, otherwise the following will simply fail.
|
||||
RemoveDuplicateEdges(ref edges, ref edgeCount, duplicates, 0);
|
||||
|
||||
// While PSG is clean.
|
||||
while (requiresFix && --protectLoop > 0)
|
||||
{
|
||||
// Edge Edge Intersection.
|
||||
int intersectionCount = 0;
|
||||
validGraph = CalculateEdgeIntersections(edges, edgeCount, points, pointCount, ref edgeIntersections, ref intersects, ref intersectionCount);
|
||||
if (!validGraph)
|
||||
break;
|
||||
|
||||
// Edge Point Intersection. T-Junction.
|
||||
int tJunctionCount = 0;
|
||||
validGraph = CalculateTJunctions(edges, edgeCount, points, pointCount, tJunctions, ref tJunctionCount);
|
||||
if (!validGraph)
|
||||
break;
|
||||
|
||||
// Cut Overlapping Edges.
|
||||
validGraph = CutEdges(ref points, ref pointCount, ref edges, ref edgeCount, ref tJunctions, ref tJunctionCount, edgeIntersections, intersects, intersectionCount);
|
||||
if (!validGraph)
|
||||
break;
|
||||
|
||||
// Remove Duplicate Points.
|
||||
int duplicateCount = 0;
|
||||
RemoveDuplicatePoints(ref points, ref pointCount, ref duplicates, ref duplicateCount, allocator);
|
||||
RemoveDuplicateEdges(ref edges, ref edgeCount, duplicates, duplicateCount);
|
||||
|
||||
requiresFix = intersectionCount != 0 || tJunctionCount != 0;
|
||||
}
|
||||
|
||||
if (validGraph)
|
||||
{
|
||||
// Finalize Output.
|
||||
outputEdgeCount = edgeCount;
|
||||
outputPointCount = pointCount;
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemCpy(outputEdges.GetUnsafePtr(), edges.UnsafeReadOnlyPtr, edgeCount * sizeof(int2));
|
||||
}
|
||||
for (int i = 0; i < pointCount; ++i)
|
||||
outputPoints[i] = new float2((float)(points[i].x / precisionFudge), (float)(points[i].y / precisionFudge));
|
||||
}
|
||||
|
||||
edges.Dispose();
|
||||
points.Dispose();
|
||||
intersects.Dispose();
|
||||
duplicates.Dispose();
|
||||
tJunctions.Dispose();
|
||||
edgeIntersections.Dispose();
|
||||
|
||||
return (validGraph && protectLoop > 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
struct Refinery
|
||||
{
|
||||
|
||||
// Old min and max are 0.5 and 0.05. We pretty much do the same with some relaxing on both ends.
|
||||
private static readonly float kMinAreaFactor = 0.0482f;
|
||||
private static readonly float kMaxAreaFactor = 0.4820f;
|
||||
// After doing more tests with a number of Sprites, this is area to which we can reduce to considering quality and CPU cost.
|
||||
private static readonly int kMaxSteinerCount = 4084;
|
||||
|
||||
// Check if Triangle is Ok.
|
||||
static bool RequiresRefining(UTriangle tri, float maxArea)
|
||||
{
|
||||
// Add any further criteria later on.
|
||||
return (tri.area > maxArea);
|
||||
}
|
||||
|
||||
static void FetchEncroachedSegments(NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, ref Array<UEncroachingSegment> encroach, ref int encroachCount, UCircle c)
|
||||
{
|
||||
for (int i = 0; i < pgEdgeCount; ++i)
|
||||
{
|
||||
var edge = pgEdges[i];
|
||||
var edgeA = pgPoints[edge.x];
|
||||
var edgeB = pgPoints[edge.y];
|
||||
|
||||
// Check if center is along the Edge.
|
||||
if (!math.any(c.center - edgeA) || !math.any(c.center - edgeB))
|
||||
continue;
|
||||
|
||||
// Get Radius
|
||||
var edgeD = edgeA - edgeB;
|
||||
var edgeM = (edgeA + edgeB) * 0.5f;
|
||||
var edgeR = math.length(edgeD) * 0.5f;
|
||||
if (math.length(edgeM - c.center) > edgeR)
|
||||
continue;
|
||||
|
||||
UEncroachingSegment es = new UEncroachingSegment();
|
||||
es.a = edgeA;
|
||||
es.b = edgeB;
|
||||
es.index = i;
|
||||
encroach[encroachCount++] = es;
|
||||
}
|
||||
}
|
||||
|
||||
static void InsertVertex(ref NativeArray<float2> pgPoints, ref int pgPointCount, float2 newVertex, ref int nid)
|
||||
{
|
||||
nid = pgPointCount;
|
||||
pgPoints[nid] = newVertex;
|
||||
pgPointCount++;
|
||||
}
|
||||
|
||||
static void SplitSegments(ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> pgEdges, ref int pgEdgeCount, UEncroachingSegment es)
|
||||
{
|
||||
var sid = es.index;
|
||||
var edge = pgEdges[sid];
|
||||
var edgeA = pgPoints[edge.x];
|
||||
var edgeB = pgPoints[edge.y];
|
||||
var split = (edgeA + edgeB) * 0.5f;
|
||||
var neid = 0;
|
||||
if (math.abs(edge.x - edge.y) == 1)
|
||||
{
|
||||
neid = (edge.x > edge.y) ? edge.x : edge.y;
|
||||
InsertVertex(ref pgPoints, ref pgPointCount, split, ref neid);
|
||||
|
||||
// Add the split segments.
|
||||
var rep = pgEdges[sid];
|
||||
pgEdges[sid] = new int2(rep.x, neid);
|
||||
for (int i = pgEdgeCount; i > (sid + 1); --i)
|
||||
pgEdges[i] = pgEdges[i - 1];
|
||||
pgEdges[sid + 1] = new int2(neid, rep.y);
|
||||
pgEdgeCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
neid = pgPointCount;
|
||||
pgPoints[pgPointCount++] = split;
|
||||
pgEdges[sid] = new int2(math.max(edge.x, edge.y), neid);
|
||||
pgEdges[pgEdgeCount++] = new int2(math.min(edge.x, edge.y), neid);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool Condition(Allocator allocator, float factorArea, float targetArea, ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> pgEdges, ref int pgEdgeCount, ref NativeArray<float2> vertices, ref int vertexCount, ref NativeArray<int> indices, ref int indexCount, ref float maxArea)
|
||||
{
|
||||
|
||||
// Process Triangles.
|
||||
maxArea = 0.0f;
|
||||
var minArea = 0.0f;
|
||||
var avgArea = 0.0f;
|
||||
var refined = false;
|
||||
var validGraph = true;
|
||||
|
||||
// Temporary Stuffs.
|
||||
int triangleCount = 0, invalidTriangle = -1, inputPointCount = pgPointCount;
|
||||
var encroach = new Array<UEncroachingSegment>(inputPointCount, ModuleHandle.kMaxEdgeCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
var triangles = new Array<UTriangle>(inputPointCount * 4, ModuleHandle.kMaxTriangleCount, allocator, NativeArrayOptions.UninitializedMemory);
|
||||
ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref maxArea, ref avgArea, ref minArea);
|
||||
factorArea = factorArea != 0 ? math.clamp(factorArea, kMinAreaFactor, kMaxAreaFactor) : factorArea;
|
||||
var criArea = maxArea * factorArea;
|
||||
criArea = math.max(criArea, targetArea);
|
||||
|
||||
// Refine
|
||||
while (!refined && validGraph)
|
||||
{
|
||||
|
||||
// Check if any of the Triangle is Invalid or Segment is invalid. If yes, Refine.
|
||||
for (int i = 0; i < triangleCount; ++i)
|
||||
{
|
||||
if (RequiresRefining(triangles[i], criArea))
|
||||
{
|
||||
invalidTriangle = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find any Segment that can be Split based on the Input Length.
|
||||
// todo.
|
||||
|
||||
if (invalidTriangle != -1)
|
||||
{
|
||||
|
||||
// Get all Segments that are encroached.
|
||||
var t = triangles[invalidTriangle];
|
||||
var encroachCount = 0;
|
||||
FetchEncroachedSegments(pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref encroach, ref encroachCount, t.c);
|
||||
|
||||
// Split each Encroached Segments. If no segments are encroached. Split the Triangle.
|
||||
if (encroachCount != 0)
|
||||
{
|
||||
for (int i = 0; i < encroachCount; ++i)
|
||||
{
|
||||
SplitSegments(ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, encroach[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update Triangulation.
|
||||
var split = t.c.center;
|
||||
pgPoints[pgPointCount++] = split;
|
||||
}
|
||||
|
||||
// Tessellate again.
|
||||
indexCount = 0; vertexCount = 0;
|
||||
validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref vertices, ref vertexCount, ref indices, ref indexCount);
|
||||
|
||||
// Build Internal Triangles.
|
||||
encroachCount = 0; triangleCount = 0; invalidTriangle = -1;
|
||||
if (validGraph)
|
||||
ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref maxArea, ref avgArea, ref minArea);
|
||||
|
||||
// More than enough Steiner points inserted. This handles all sort of weird input sprites very well (even random point cloud).
|
||||
if (pgPointCount - inputPointCount > kMaxSteinerCount)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
refined = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dispose off
|
||||
triangles.Dispose();
|
||||
encroach.Dispose();
|
||||
return refined;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
using Unity.Mathematics;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
struct Smoothen
|
||||
{
|
||||
|
||||
// This is an arbitrary value less than 2 to ensure points are not moved too far away from the source polygon.
|
||||
private static readonly float kMaxAreaTolerance = 1.842f;
|
||||
private static readonly float kMaxEdgeTolerance = 2.482f;
|
||||
|
||||
// Trim Edges
|
||||
static void RefineEdges(ref NativeArray<int4> refinedEdges, ref NativeArray<int4> delaEdges, ref int delaEdgeCount, ref NativeArray<int4> voronoiEdges)
|
||||
{
|
||||
int origEdgeCount = delaEdgeCount;
|
||||
delaEdgeCount = 0;
|
||||
|
||||
// Check Neighbour Triangles.
|
||||
for (int i = 0; i < origEdgeCount - 1; ++i)
|
||||
{
|
||||
var edge = delaEdges[i];
|
||||
var neighbor = delaEdges[i + 1];
|
||||
if (edge.x == neighbor.x && edge.y == neighbor.y)
|
||||
{
|
||||
// Found the Opposite Edge. i.e Nearby Triangle.
|
||||
edge.w = neighbor.z;
|
||||
++i;
|
||||
}
|
||||
// Update new list.
|
||||
refinedEdges[delaEdgeCount++] = edge;
|
||||
}
|
||||
|
||||
// Generate Voronoi Edges.
|
||||
for (int i = 0; i < delaEdgeCount; ++i)
|
||||
{
|
||||
var ti1 = refinedEdges[i].z;
|
||||
var ti2 = refinedEdges[i].w;
|
||||
|
||||
// We only really care about Bounded Edges. This is simplification. Hoping this garbage works.
|
||||
if (ti1 != -1 && ti2 != -1)
|
||||
{
|
||||
// Get Triangles
|
||||
int4 e = new int4(ti2, ti1, i, 0);
|
||||
voronoiEdges[i] = e;
|
||||
}
|
||||
}
|
||||
|
||||
ModuleHandle.Copy(refinedEdges, delaEdges, delaEdgeCount);
|
||||
}
|
||||
|
||||
// Get all the Edges that has this Point.
|
||||
static void GetAffectingEdges(int pointIndex, NativeArray<int4> edges, int edgeCount, ref NativeArray<int> resultSet, ref NativeArray<int> checkSet, ref int resultCount)
|
||||
{
|
||||
resultCount = 0;
|
||||
for (int i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
if (pointIndex == edges[i].x || pointIndex == edges[i].y)
|
||||
resultSet[resultCount++] = i;
|
||||
checkSet[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to Centroids Triangles.
|
||||
static void CentroidByPoints(int triIndex, NativeArray<UTriangle> triangles, ref NativeArray<int> centroidTris, ref int centroidCount, ref float2 aggregate, ref float2 point)
|
||||
{
|
||||
for (int i = 0; i < centroidCount; ++i)
|
||||
if (triIndex == centroidTris[i])
|
||||
return;
|
||||
centroidTris[centroidCount++] = triIndex;
|
||||
aggregate += triangles[triIndex].c.center;
|
||||
point = aggregate / centroidCount;
|
||||
}
|
||||
|
||||
static void CentroidByPolygon(int4 e, NativeArray<UTriangle> triangles, ref float2 centroid, ref float area, ref float distance)
|
||||
{
|
||||
var es = triangles[e.x].c.center;
|
||||
var ee = triangles[e.y].c.center;
|
||||
var d = es.x * ee.y - ee.x * es.y;
|
||||
distance = distance + math.distance(es, ee);
|
||||
area = area + d;
|
||||
centroid.x += (ee.x + es.x) * d;
|
||||
centroid.y += (ee.y + es.y) * d;
|
||||
}
|
||||
|
||||
// Connect Triangles
|
||||
static bool ConnectTriangles(ref NativeArray<int4> connectedTri, ref NativeArray<int> affectEdges, ref NativeArray<int> checkSet, NativeArray<int4> voronoiEdges, int triangleCount)
|
||||
{
|
||||
var ei = affectEdges[0];
|
||||
var ni = affectEdges[0];
|
||||
connectedTri[0] = new int4(voronoiEdges[ei].x, voronoiEdges[ei].y, 0, 0);
|
||||
checkSet[ni] = 1;
|
||||
|
||||
for (int i = 1; i < triangleCount; ++i)
|
||||
{
|
||||
ni = affectEdges[i];
|
||||
if (checkSet[ni] == 0)
|
||||
{
|
||||
if (voronoiEdges[ni].x == connectedTri[i - 1].y)
|
||||
{
|
||||
connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0);
|
||||
checkSet[ni] = 1;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (voronoiEdges[ni].y == connectedTri[i - 1].y)
|
||||
{
|
||||
connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0);
|
||||
checkSet[ni] = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var connected = false;
|
||||
for (int j = 0; j < triangleCount; ++j)
|
||||
{
|
||||
ni = affectEdges[j];
|
||||
if (checkSet[ni] == 1)
|
||||
continue;
|
||||
if (voronoiEdges[ni].x == connectedTri[i - 1].y)
|
||||
{
|
||||
connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0);
|
||||
checkSet[ni] = 1;
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
else if (voronoiEdges[ni].y == connectedTri[i - 1].y)
|
||||
{
|
||||
connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0);
|
||||
checkSet[ni] = 1;
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!connected)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Perform Voronoi based Smoothing. Does not add/remove points but merely relocates internal vertices so they are uniform distributed.
|
||||
internal static bool Condition(Allocator allocator, ref NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, ref NativeArray<float2> vertices, ref int vertexCount, ref NativeArray<int> indices, ref int indexCount)
|
||||
{
|
||||
|
||||
// Build Triangles and Edges.
|
||||
float maxArea = 0, cmxArea = 0, minArea = 0, cmnArea = 0, avgArea = 0, minEdge = 0, maxEdge = 0, avgEdge = 0;
|
||||
bool polygonCentroid = true, validGraph = true;
|
||||
int triangleCount = 0, delaEdgeCount = 0, affectingEdgeCount = 0;
|
||||
var triangles = new NativeArray<UTriangle>(indexCount, allocator); // Intentionally added more room than actual Triangles needed here.
|
||||
var delaEdges = new NativeArray<int4>(indexCount, allocator);
|
||||
var voronoiEdges = new NativeArray<int4>(indexCount, allocator);
|
||||
var connectedTri = new NativeArray<int4>(vertexCount, allocator);
|
||||
var voronoiCheck = new NativeArray<int>(indexCount, allocator);
|
||||
var affectsEdges = new NativeArray<int>(indexCount, allocator);
|
||||
var triCentroids = new NativeArray<int>(vertexCount, allocator);
|
||||
ModuleHandle.BuildTrianglesAndEdges(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref delaEdges, ref delaEdgeCount, ref maxArea, ref avgArea, ref minArea);
|
||||
var refinedEdges = new NativeArray<int4>(delaEdgeCount, allocator);
|
||||
|
||||
// Sort the Delaunay Edges.
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int4, DelaEdgeCompare>(
|
||||
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(delaEdges), 0, delaEdgeCount - 1,
|
||||
new DelaEdgeCompare());
|
||||
}
|
||||
|
||||
// TrimEdges. Update Triangle Info for Shared Edges and remove Duplicates.
|
||||
RefineEdges(ref refinedEdges, ref delaEdges, ref delaEdgeCount, ref voronoiEdges);
|
||||
|
||||
// Now for each point, generate Voronoi diagram.
|
||||
for (int i = 0; i < vertexCount; ++i)
|
||||
{
|
||||
|
||||
// Try moving this to Centroid of the Voronoi Polygon.
|
||||
GetAffectingEdges(i, delaEdges, delaEdgeCount, ref affectsEdges, ref voronoiCheck, ref affectingEdgeCount);
|
||||
var bounded = affectingEdgeCount != 0;
|
||||
|
||||
// Check for Boundedness
|
||||
for (int j = 0; j < affectingEdgeCount; ++j)
|
||||
{
|
||||
// Edge Index.
|
||||
var ei = affectsEdges[j];
|
||||
if (delaEdges[ei].z == -1 || delaEdges[ei].w == -1)
|
||||
{
|
||||
bounded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is bounded point, relocate to Voronoi Diagram's Centroid
|
||||
if (bounded)
|
||||
{
|
||||
polygonCentroid = ConnectTriangles(ref connectedTri, ref affectsEdges, ref voronoiCheck, voronoiEdges, affectingEdgeCount);
|
||||
if (!polygonCentroid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
float2 point = float2.zero;
|
||||
float area = 0, distance = 0;
|
||||
for (int k = 0; k < affectingEdgeCount; ++k)
|
||||
{
|
||||
CentroidByPolygon(connectedTri[k], triangles, ref point, ref area, ref distance);
|
||||
}
|
||||
point /= (3 * area);
|
||||
pgPoints[i] = point;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Do Delaunay Again.
|
||||
int srcIndexCount = indexCount, srcVertexCount = vertexCount;
|
||||
indexCount = 0; vertexCount = 0; triangleCount = 0;
|
||||
if (polygonCentroid)
|
||||
{
|
||||
validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref vertices, ref vertexCount, ref indices, ref indexCount);
|
||||
if (validGraph)
|
||||
ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref cmxArea, ref avgArea, ref cmnArea, ref maxEdge, ref avgEdge, ref minEdge);
|
||||
// This Edge validation prevents artifacts by forcing a fallback. todo: Fix the actual bug in Outline generation.
|
||||
validGraph = validGraph && (cmxArea < maxArea * kMaxAreaTolerance) && (maxEdge < avgEdge * kMaxEdgeTolerance);
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
triangles.Dispose();
|
||||
delaEdges.Dispose();
|
||||
refinedEdges.Dispose();
|
||||
voronoiCheck.Dispose();
|
||||
voronoiEdges.Dispose();
|
||||
affectsEdges.Dispose();
|
||||
triCentroids.Dispose();
|
||||
connectedTri.Dispose();
|
||||
return (validGraph && srcIndexCount == indexCount && srcVertexCount == vertexCount);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,911 @@
|
|||
using System;
|
||||
using Unity.Profiling;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
|
||||
// Constrained Delaunay Triangulation.
|
||||
struct Tessellator
|
||||
{
|
||||
|
||||
// For Processing.
|
||||
NativeArray<int2> m_Edges;
|
||||
NativeArray<UStar> m_Stars;
|
||||
Array<int3> m_Cells;
|
||||
int m_CellCount;
|
||||
|
||||
// For Storage.
|
||||
NativeArray<int> m_ILArray;
|
||||
NativeArray<int> m_IUArray;
|
||||
NativeArray<int> m_SPArray;
|
||||
int m_NumEdges;
|
||||
int m_NumHulls;
|
||||
int m_NumPoints;
|
||||
int m_StarCount;
|
||||
|
||||
// Intermediates.
|
||||
NativeArray<int> m_Flags;
|
||||
NativeArray<int> m_Neighbors;
|
||||
NativeArray<int> m_Constraints;
|
||||
Allocator m_Allocator;
|
||||
|
||||
struct TestHullPointL : ICondition2<UHull, float2>
|
||||
{
|
||||
public bool Test(UHull h, float2 p, ref float t)
|
||||
{
|
||||
t = ModuleHandle.OrientFast(h.a, h.b, p);
|
||||
return t < 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct TestHullPointU : ICondition2<UHull, float2>
|
||||
{
|
||||
public bool Test(UHull h, float2 p, ref float t)
|
||||
{
|
||||
t = ModuleHandle.OrientFast(h.a, h.b, p);
|
||||
return t > 0;
|
||||
}
|
||||
}
|
||||
|
||||
static float FindSplit(UHull hull, UEvent edge)
|
||||
{
|
||||
float d = 0;
|
||||
if (hull.a.x < edge.a.x)
|
||||
{
|
||||
d = ModuleHandle.OrientFast(hull.a, hull.b, edge.a);
|
||||
}
|
||||
else
|
||||
{
|
||||
d = ModuleHandle.OrientFast(edge.b, edge.a, hull.a);
|
||||
}
|
||||
|
||||
if (0 != d)
|
||||
{
|
||||
return d;
|
||||
}
|
||||
|
||||
if (edge.b.x < hull.b.x)
|
||||
{
|
||||
d = ModuleHandle.OrientFast(hull.a, hull.b, edge.b);
|
||||
}
|
||||
else
|
||||
{
|
||||
d = ModuleHandle.OrientFast(edge.b, edge.a, hull.b);
|
||||
}
|
||||
|
||||
if (0 != d)
|
||||
{
|
||||
return d;
|
||||
}
|
||||
return hull.idx - edge.idx;
|
||||
}
|
||||
|
||||
struct TestHullEventLe : ICondition2<UHull, UEvent>
|
||||
{
|
||||
public bool Test(UHull h, UEvent p, ref float t)
|
||||
{
|
||||
t = FindSplit(h, p);
|
||||
return t <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct TestHullEventE : ICondition2<UHull, UEvent>
|
||||
{
|
||||
public bool Test(UHull h, UEvent p, ref float t)
|
||||
{
|
||||
t = FindSplit(h, p);
|
||||
return t == 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SetAllocator(Allocator allocator)
|
||||
{
|
||||
m_Allocator = allocator;
|
||||
}
|
||||
|
||||
bool AddPoint(NativeArray<UHull> hulls, int hullCount, NativeArray<float2> points, float2 p, int idx)
|
||||
{
|
||||
int l = ModuleHandle.GetLower(hulls, hullCount, p, new TestHullPointL());
|
||||
int u = ModuleHandle.GetUpper(hulls, hullCount, p, new TestHullPointU());
|
||||
if (l < 0 || u < 0)
|
||||
return false;
|
||||
for (int i = l; i < u; ++i)
|
||||
{
|
||||
UHull hull = hulls[i];
|
||||
|
||||
int m = hull.ilcount;
|
||||
while (m > 1 && ModuleHandle.OrientFast(points[hull.ilarray[m - 2]], points[hull.ilarray[m - 1]], p) > 0)
|
||||
{
|
||||
int3 c = new int3();
|
||||
c.x = hull.ilarray[m - 1];
|
||||
c.y = hull.ilarray[m - 2];
|
||||
c.z = idx;
|
||||
m_Cells[m_CellCount++] = c;
|
||||
m -= 1;
|
||||
}
|
||||
|
||||
hull.ilcount = m + 1;
|
||||
if (hull.ilcount > hull.ilarray.Length)
|
||||
return false;
|
||||
hull.ilarray[m] = idx;
|
||||
|
||||
m = hull.iucount;
|
||||
while (m > 1 && ModuleHandle.OrientFast(points[hull.iuarray[m - 2]], points[hull.iuarray[m - 1]], p) < 0)
|
||||
{
|
||||
int3 c = new int3();
|
||||
c.x = hull.iuarray[m - 2];
|
||||
c.y = hull.iuarray[m - 1];
|
||||
c.z = idx;
|
||||
m_Cells[m_CellCount++] = c;
|
||||
m -= 1;
|
||||
}
|
||||
|
||||
hull.iucount = m + 1;
|
||||
if (hull.iucount > hull.iuarray.Length)
|
||||
return false;
|
||||
hull.iuarray[m] = idx;
|
||||
|
||||
hulls[i] = hull;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InsertHull(NativeArray<UHull> Hulls, int Pos, ref int Count, UHull Value)
|
||||
{
|
||||
if (Count < Hulls.Length - 1)
|
||||
{
|
||||
for (int i = Count; i > Pos; --i)
|
||||
Hulls[i] = Hulls[i - 1];
|
||||
Hulls[Pos] = Value;
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
|
||||
static void EraseHull(NativeArray<UHull> Hulls, int Pos, ref int Count)
|
||||
{
|
||||
if (Count < Hulls.Length)
|
||||
{
|
||||
for (int i = Pos; i < Count - 1; ++i)
|
||||
Hulls[i] = Hulls[i + 1];
|
||||
Count--;
|
||||
}
|
||||
}
|
||||
|
||||
bool SplitHulls(NativeArray<UHull> hulls, ref int hullCount, NativeArray<float2> points, UEvent evt)
|
||||
{
|
||||
int index = ModuleHandle.GetLower(hulls, hullCount, evt, new TestHullEventLe());
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
UHull hull = hulls[index];
|
||||
|
||||
UHull newHull;
|
||||
newHull.a = evt.a;
|
||||
newHull.b = evt.b;
|
||||
newHull.idx = evt.idx;
|
||||
|
||||
int y = hull.iuarray[hull.iucount - 1];
|
||||
newHull.iuarray = new ArraySlice<int>(m_IUArray, newHull.idx * m_NumHulls, m_NumHulls);
|
||||
newHull.iucount = hull.iucount;
|
||||
for (int i = 0; i < newHull.iucount; ++i)
|
||||
newHull.iuarray[i] = hull.iuarray[i];
|
||||
hull.iuarray[0] = y;
|
||||
hull.iucount = 1;
|
||||
hulls[index] = hull;
|
||||
|
||||
newHull.ilarray = new ArraySlice<int>(m_ILArray, newHull.idx * m_NumHulls, m_NumHulls);
|
||||
newHull.ilarray[0] = y;
|
||||
newHull.ilcount = 1;
|
||||
|
||||
InsertHull(hulls, index + 1, ref hullCount, newHull);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MergeHulls(NativeArray<UHull> hulls, ref int hullCount, NativeArray<float2> points, UEvent evt)
|
||||
{
|
||||
float2 temp = evt.a;
|
||||
evt.a = evt.b;
|
||||
evt.b = temp;
|
||||
int index = ModuleHandle.GetEqual(hulls, hullCount, evt, new TestHullEventE());
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
UHull upper = hulls[index];
|
||||
UHull lower = hulls[index - 1];
|
||||
|
||||
lower.iucount = upper.iucount;
|
||||
for (int i = 0; i < lower.iucount; ++i)
|
||||
lower.iuarray[i] = upper.iuarray[i];
|
||||
|
||||
hulls[index - 1] = lower;
|
||||
EraseHull(hulls, index, ref hullCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InsertUniqueEdge(NativeArray<int2> edges, int2 e, ref int edgeCount)
|
||||
{
|
||||
TessEdgeCompare edgeComparer = new TessEdgeCompare();
|
||||
var validEdge = true;
|
||||
for (int j = 0; validEdge && j < edgeCount; ++j)
|
||||
if (edgeComparer.Compare(e, edges[j]) == 0)
|
||||
validEdge = false;
|
||||
if (validEdge)
|
||||
edges[edgeCount++] = e;
|
||||
}
|
||||
|
||||
void PrepareDelaunay(NativeArray<int2> edges, int edgeCount)
|
||||
{
|
||||
m_StarCount = m_CellCount * 3;
|
||||
m_Stars = new NativeArray<UStar>(m_StarCount, m_Allocator);
|
||||
m_SPArray = new NativeArray<int>(m_StarCount * m_StarCount, m_Allocator, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
var UEdgeCount = 0;
|
||||
var UEdges = new NativeArray<int2>(m_StarCount, m_Allocator);
|
||||
|
||||
// Input Edges.
|
||||
for (int i = 0; i < edgeCount; ++i)
|
||||
{
|
||||
int2 e = edges[i];
|
||||
e.x = (edges[i].x < edges[i].y) ? edges[i].x : edges[i].y;
|
||||
e.y = (edges[i].x > edges[i].y) ? edges[i].x : edges[i].y;
|
||||
edges[i] = e;
|
||||
InsertUniqueEdge(UEdges, e, ref UEdgeCount);
|
||||
}
|
||||
|
||||
m_Edges = new NativeArray<int2>(UEdgeCount, m_Allocator);
|
||||
for (int i = 0; i < UEdgeCount; ++i)
|
||||
m_Edges[i] = UEdges[i];
|
||||
UEdges.Dispose();
|
||||
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int2, TessEdgeCompare>(
|
||||
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_Edges), 0, m_Edges.Length - 1,
|
||||
new TessEdgeCompare());
|
||||
}
|
||||
|
||||
// Init Stars.
|
||||
for (int i = 0; i < m_StarCount; ++i)
|
||||
{
|
||||
UStar s = m_Stars[i];
|
||||
s.points = new ArraySlice<int>(m_SPArray, i * m_StarCount, m_StarCount);
|
||||
s.pointCount = 0;
|
||||
m_Stars[i] = s;
|
||||
}
|
||||
|
||||
// Fill stars.
|
||||
for (int i = 0; i < m_CellCount; ++i)
|
||||
{
|
||||
int a = m_Cells[i].x;
|
||||
int b = m_Cells[i].y;
|
||||
int c = m_Cells[i].z;
|
||||
UStar sa = m_Stars[a];
|
||||
UStar sb = m_Stars[b];
|
||||
UStar sc = m_Stars[c];
|
||||
sa.points[sa.pointCount++] = b;
|
||||
sa.points[sa.pointCount++] = c;
|
||||
sb.points[sb.pointCount++] = c;
|
||||
sb.points[sb.pointCount++] = a;
|
||||
sc.points[sc.pointCount++] = a;
|
||||
sc.points[sc.pointCount++] = b;
|
||||
m_Stars[a] = sa;
|
||||
m_Stars[b] = sb;
|
||||
m_Stars[c] = sc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int OppositeOf(int a, int b)
|
||||
{
|
||||
ArraySlice<int> points = m_Stars[b].points;
|
||||
for (int k = 1, n = m_Stars[b].pointCount; k < n; k += 2)
|
||||
if (points[k] == a)
|
||||
return points[k - 1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct TestEdgePointE : ICondition2<int2, int2>
|
||||
{
|
||||
public bool Test(int2 h, int2 p, ref float t)
|
||||
{
|
||||
TessEdgeCompare tc = new TessEdgeCompare();
|
||||
t = tc.Compare(h, p);
|
||||
return t == 0;
|
||||
}
|
||||
}
|
||||
|
||||
int FindConstraint(int a, int b)
|
||||
{
|
||||
int2 e;
|
||||
e.x = a < b ? a : b;
|
||||
e.y = a > b ? a : b;
|
||||
return ModuleHandle.GetEqual(m_Edges, m_Edges.Length, e, new TestEdgePointE());
|
||||
}
|
||||
|
||||
void AddTriangle(int i, int j, int k)
|
||||
{
|
||||
UStar si = m_Stars[i];
|
||||
UStar sj = m_Stars[j];
|
||||
UStar sk = m_Stars[k];
|
||||
si.points[si.pointCount++] = j;
|
||||
si.points[si.pointCount++] = k;
|
||||
sj.points[sj.pointCount++] = k;
|
||||
sj.points[sj.pointCount++] = i;
|
||||
sk.points[sk.pointCount++] = i;
|
||||
sk.points[sk.pointCount++] = j;
|
||||
m_Stars[i] = si;
|
||||
m_Stars[j] = sj;
|
||||
m_Stars[k] = sk;
|
||||
}
|
||||
|
||||
void RemovePair(int r, int j, int k)
|
||||
{
|
||||
UStar s = m_Stars[r];
|
||||
ArraySlice<int> points = s.points;
|
||||
for (int i = 1, n = s.pointCount; i < n; i += 2)
|
||||
{
|
||||
if (points[i - 1] == j && points[i] == k)
|
||||
{
|
||||
points[i - 1] = points[n - 2];
|
||||
points[i] = points[n - 1];
|
||||
s.points = points;
|
||||
s.pointCount = s.pointCount - 2;
|
||||
m_Stars[r] = s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveTriangle(int i, int j, int k)
|
||||
{
|
||||
RemovePair(i, j, k);
|
||||
RemovePair(j, k, i);
|
||||
RemovePair(k, i, j);
|
||||
}
|
||||
|
||||
void EdgeFlip(int i, int j)
|
||||
{
|
||||
int a = OppositeOf(i, j);
|
||||
int b = OppositeOf(j, i);
|
||||
RemoveTriangle(i, j, a);
|
||||
RemoveTriangle(j, i, b);
|
||||
AddTriangle(i, b, a);
|
||||
AddTriangle(j, a, b);
|
||||
}
|
||||
|
||||
bool Flip(NativeArray<float2> points, ref Array<int> stack, ref int stackCount, int a, int b, int x)
|
||||
{
|
||||
int y = OppositeOf(a, b);
|
||||
|
||||
if (y < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b < a)
|
||||
{
|
||||
int tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
tmp = x;
|
||||
x = y;
|
||||
y = tmp;
|
||||
}
|
||||
|
||||
if (FindConstraint(a, b) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y]))
|
||||
{
|
||||
if ((2 + stackCount) >= stack.Length)
|
||||
return false;
|
||||
stack[stackCount++] = a;
|
||||
stack[stackCount++] = b;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Array<int3> GetCells(ref int count)
|
||||
{
|
||||
var cellsOut = new Array<int3>(m_NumPoints * 4, m_NumPoints * (m_NumPoints + 1), m_Allocator, NativeArrayOptions.UninitializedMemory);
|
||||
count = 0;
|
||||
for (int i = 0, n = m_Stars.Length; i < n; ++i)
|
||||
{
|
||||
ArraySlice<int> points = m_Stars[i].points;
|
||||
for (int j = 0, m = m_Stars[i].pointCount; j < m; j += 2)
|
||||
{
|
||||
int s = points[j];
|
||||
int t = points[j + 1];
|
||||
if (i < math.min(s, t))
|
||||
{
|
||||
int3 c = new int3();
|
||||
c.x = i;
|
||||
c.y = s;
|
||||
c.z = t;
|
||||
cellsOut[count++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cellsOut;
|
||||
}
|
||||
|
||||
internal bool ApplyDelaunay(NativeArray<float2> points, NativeArray<int2> edges)
|
||||
{
|
||||
|
||||
// Early out if cannot find any valid cells.
|
||||
if (0 == m_CellCount)
|
||||
return false;
|
||||
|
||||
var stack = new Array<int>(m_NumPoints * 4, m_NumPoints * (m_NumPoints + 1), m_Allocator, NativeArrayOptions.UninitializedMemory);
|
||||
int stackCount = 0;
|
||||
var valid = true;
|
||||
|
||||
PrepareDelaunay(edges, m_NumEdges);
|
||||
for (int a = 0; valid && (a < m_NumPoints); ++a)
|
||||
{
|
||||
UStar star = m_Stars[a];
|
||||
for (int j = 1; j < star.pointCount; j += 2)
|
||||
{
|
||||
int b = star.points[j];
|
||||
|
||||
if (b < a)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FindConstraint(a, b) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = star.points[j - 1], y = -1;
|
||||
for (int k = 1; k < star.pointCount; k += 2)
|
||||
{
|
||||
if (star.points[k - 1] == b)
|
||||
{
|
||||
y = star.points[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (y < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y]))
|
||||
{
|
||||
if ((2 + stackCount) >= stack.Length)
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
stack[stackCount++] = a;
|
||||
stack[stackCount++] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flipFlops = m_NumPoints * m_NumPoints;
|
||||
while (stackCount > 0 && valid)
|
||||
{
|
||||
int b = stack[stackCount - 1];
|
||||
stackCount--;
|
||||
int a = stack[stackCount - 1];
|
||||
stackCount--;
|
||||
|
||||
int x = -1, y = -1;
|
||||
UStar star = m_Stars[a];
|
||||
for (int i = 1; i < star.pointCount; i += 2)
|
||||
{
|
||||
int s = star.points[i - 1];
|
||||
int t = star.points[i];
|
||||
if (s == b)
|
||||
{
|
||||
y = t;
|
||||
}
|
||||
else if (t == b)
|
||||
{
|
||||
x = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (x < 0 || y < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ModuleHandle.IsInsideCircle(points[a], points[b], points[x], points[y]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EdgeFlip(a, b);
|
||||
|
||||
valid = Flip(points, ref stack, ref stackCount, x, a, y);
|
||||
valid = valid && Flip(points, ref stack, ref stackCount, a, y, x);
|
||||
valid = valid && Flip(points, ref stack, ref stackCount, y, b, x);
|
||||
valid = valid && Flip(points, ref stack, ref stackCount, b, x, y);
|
||||
valid = valid && (--flipFlops > 0);
|
||||
}
|
||||
|
||||
stack.Dispose();
|
||||
return valid;
|
||||
}
|
||||
|
||||
struct TestCellE : ICondition2<int3, int3>
|
||||
{
|
||||
public bool Test(int3 h, int3 p, ref float t)
|
||||
{
|
||||
TessCellCompare tc = new TessCellCompare();
|
||||
t = tc.Compare(h, p);
|
||||
return t == 0;
|
||||
}
|
||||
}
|
||||
|
||||
int FindNeighbor(Array<int3> cells, int count, int a, int b, int c)
|
||||
{
|
||||
int x = a, y = b, z = c;
|
||||
if (b < c)
|
||||
{
|
||||
if (b < a)
|
||||
{
|
||||
x = b;
|
||||
y = c;
|
||||
z = a;
|
||||
}
|
||||
}
|
||||
else if (c < a)
|
||||
{
|
||||
x = c;
|
||||
y = a;
|
||||
z = b;
|
||||
}
|
||||
|
||||
if (x < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int3 key;
|
||||
key.x = x;
|
||||
key.y = y;
|
||||
key.z = z;
|
||||
return ModuleHandle.GetEqual(cells, count, key, new TestCellE());
|
||||
}
|
||||
|
||||
Array<int3> Constrain(ref int count)
|
||||
{
|
||||
var cells = GetCells(ref count);
|
||||
int nc = count;
|
||||
for (int i = 0; i < nc; ++i)
|
||||
{
|
||||
int3 c = cells[i];
|
||||
int x = c.x, y = c.y, z = c.z;
|
||||
if (y < z)
|
||||
{
|
||||
if (y < x)
|
||||
{
|
||||
c.x = y;
|
||||
c.y = z;
|
||||
c.z = x;
|
||||
}
|
||||
}
|
||||
else if (z < x)
|
||||
{
|
||||
c.x = z;
|
||||
c.y = x;
|
||||
c.z = y;
|
||||
}
|
||||
|
||||
cells[i] = c;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<int3, TessCellCompare>(
|
||||
cells.UnsafePtr, 0, m_CellCount - 1,
|
||||
new TessCellCompare());
|
||||
}
|
||||
|
||||
// Out
|
||||
m_Flags = new NativeArray<int>(nc, m_Allocator);
|
||||
m_Neighbors = new NativeArray<int>(nc * 3, m_Allocator);
|
||||
m_Constraints = new NativeArray<int>(nc * 3, m_Allocator);
|
||||
var next = new NativeArray<int>(nc * 3, m_Allocator);
|
||||
var active = new NativeArray<int>(nc * 3, m_Allocator);
|
||||
|
||||
int side = 1, nextCount = 0, activeCount = 0;
|
||||
|
||||
for (int i = 0; i < nc; ++i)
|
||||
{
|
||||
int3 c = cells[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
int x = j, y = (j + 1) % 3;
|
||||
x = (x == 0) ? c.x : (j == 1) ? c.y : c.z;
|
||||
y = (y == 0) ? c.x : (y == 1) ? c.y : c.z;
|
||||
|
||||
int o = OppositeOf(y, x);
|
||||
int a = m_Neighbors[3 * i + j] = FindNeighbor(cells, count, y, x, o);
|
||||
int b = m_Constraints[3 * i + j] = (-1 != FindConstraint(x, y)) ? 1 : 0;
|
||||
if (a < 0)
|
||||
{
|
||||
if (0 != b)
|
||||
{
|
||||
next[nextCount++] = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
active[activeCount++] = i;
|
||||
m_Flags[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (activeCount > 0 || nextCount > 0)
|
||||
{
|
||||
while (activeCount > 0)
|
||||
{
|
||||
int t = active[activeCount - 1];
|
||||
activeCount--;
|
||||
if (m_Flags[t] == -side)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Flags[t] = side;
|
||||
int3 c = cells[t];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
int f = m_Neighbors[3 * t + j];
|
||||
if (f >= 0 && m_Flags[f] == 0)
|
||||
{
|
||||
if (0 != m_Constraints[3 * t + j])
|
||||
{
|
||||
next[nextCount++] = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
active[activeCount++] = f;
|
||||
m_Flags[f] = side;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int e = 0; e < nextCount; e++)
|
||||
active[e] = next[e];
|
||||
activeCount = nextCount;
|
||||
nextCount = 0;
|
||||
side = -side;
|
||||
}
|
||||
|
||||
active.Dispose();
|
||||
next.Dispose();
|
||||
return cells;
|
||||
}
|
||||
|
||||
internal NativeArray<int3> RemoveExterior(ref int cellCount)
|
||||
{
|
||||
int constrainedCount = 0;
|
||||
var constrained = Constrain(ref constrainedCount);
|
||||
var cellsOut = new NativeArray<int3>(constrainedCount, m_Allocator);
|
||||
cellCount = 0;
|
||||
for (int i = 0; i < constrainedCount; ++i)
|
||||
{
|
||||
if (m_Flags[i] == -1)
|
||||
{
|
||||
cellsOut[cellCount++] = constrained[i];
|
||||
}
|
||||
}
|
||||
|
||||
constrained.Dispose();
|
||||
return cellsOut;
|
||||
}
|
||||
|
||||
internal NativeArray<int3> RemoveInterior(int cellCount)
|
||||
{
|
||||
int constrainedCount = 0;
|
||||
var constrained = Constrain(ref constrainedCount);
|
||||
var cellsOut = new NativeArray<int3>(constrainedCount, m_Allocator);
|
||||
cellCount = 0;
|
||||
for (int i = 0; i < constrainedCount; ++i)
|
||||
{
|
||||
if (m_Flags[i] == 1)
|
||||
{
|
||||
cellsOut[cellCount++] = constrained[i];
|
||||
}
|
||||
}
|
||||
|
||||
constrained.Dispose();
|
||||
return cellsOut;
|
||||
}
|
||||
|
||||
internal bool Triangulate(NativeArray<float2> points, int pointCount, NativeArray<int2> edges, int edgeCount)
|
||||
{
|
||||
m_NumEdges = edgeCount;
|
||||
m_NumHulls = edgeCount * 2;
|
||||
m_NumPoints = pointCount;
|
||||
m_CellCount = 0;
|
||||
int allocSize = m_NumHulls * (m_NumHulls + 1);
|
||||
m_Cells = new Array<int3>(allocSize, ModuleHandle.kMaxTriangleCount, m_Allocator, NativeArrayOptions.UninitializedMemory);
|
||||
m_ILArray = new NativeArray<int>(allocSize, m_Allocator); // Make room for -1 node.
|
||||
m_IUArray = new NativeArray<int>(allocSize, m_Allocator); // Make room for -1 node.
|
||||
|
||||
NativeArray<UHull> hulls = new NativeArray<UHull>(m_NumPoints * 8, m_Allocator);
|
||||
int hullCount = 0;
|
||||
|
||||
NativeArray<UEvent> events = new NativeArray<UEvent>(m_NumPoints + (m_NumEdges * 2), m_Allocator);
|
||||
int eventCount = 0;
|
||||
|
||||
for (int i = 0; i < m_NumPoints; ++i)
|
||||
{
|
||||
UEvent evt = new UEvent();
|
||||
evt.a = points[i];
|
||||
evt.b = new float2();
|
||||
evt.idx = i;
|
||||
evt.type = (int)UEventType.EVENT_POINT;
|
||||
events[eventCount++] = evt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_NumEdges; ++i)
|
||||
{
|
||||
int2 e = edges[i];
|
||||
float2 a = points[e.x];
|
||||
float2 b = points[e.y];
|
||||
if (a.x < b.x)
|
||||
{
|
||||
UEvent _s = new UEvent();
|
||||
_s.a = a;
|
||||
_s.b = b;
|
||||
_s.idx = i;
|
||||
_s.type = (int)UEventType.EVENT_START;
|
||||
|
||||
UEvent _e = new UEvent();
|
||||
_e.a = b;
|
||||
_e.b = a;
|
||||
_e.idx = i;
|
||||
_e.type = (int)UEventType.EVENT_END;
|
||||
|
||||
events[eventCount++] = _s;
|
||||
events[eventCount++] = _e;
|
||||
}
|
||||
else if (a.x > b.x)
|
||||
{
|
||||
UEvent _s = new UEvent();
|
||||
_s.a = b;
|
||||
_s.b = a;
|
||||
_s.idx = i;
|
||||
_s.type = (int)UEventType.EVENT_START;
|
||||
|
||||
UEvent _e = new UEvent();
|
||||
_e.a = a;
|
||||
_e.b = b;
|
||||
_e.idx = i;
|
||||
_e.type = (int)UEventType.EVENT_END;
|
||||
|
||||
events[eventCount++] = _s;
|
||||
events[eventCount++] = _e;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
ModuleHandle.InsertionSort<UEvent, TessEventCompare>(
|
||||
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(events), 0, eventCount - 1,
|
||||
new TessEventCompare());
|
||||
;
|
||||
}
|
||||
|
||||
var hullOp = true;
|
||||
float minX = events[0].a.x - (1 + math.abs(events[0].a.x)) * math.pow(2.0f, -16.0f);
|
||||
UHull hull;
|
||||
hull.a.x = minX;
|
||||
hull.a.y = 1;
|
||||
hull.b.x = minX;
|
||||
hull.b.y = 0;
|
||||
hull.idx = -1;
|
||||
hull.ilarray = new ArraySlice<int>(m_ILArray, m_NumHulls * m_NumHulls, m_NumHulls); // Last element
|
||||
hull.iuarray = new ArraySlice<int>(m_IUArray, m_NumHulls * m_NumHulls, m_NumHulls);
|
||||
hull.ilcount = 0;
|
||||
hull.iucount = 0;
|
||||
hulls[hullCount++] = hull;
|
||||
|
||||
|
||||
for (int i = 0, numEvents = eventCount; i < numEvents; ++i)
|
||||
{
|
||||
|
||||
switch (events[i].type)
|
||||
{
|
||||
case (int) UEventType.EVENT_POINT:
|
||||
{
|
||||
hullOp = AddPoint(hulls, hullCount, points, events[i].a, events[i].idx);
|
||||
}
|
||||
break;
|
||||
|
||||
case (int) UEventType.EVENT_START:
|
||||
{
|
||||
hullOp = SplitHulls(hulls, ref hullCount, points, events[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
hullOp = MergeHulls(hulls, ref hullCount, points, events[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hullOp)
|
||||
break;
|
||||
}
|
||||
|
||||
events.Dispose();
|
||||
hulls.Dispose();
|
||||
return hullOp;
|
||||
}
|
||||
|
||||
internal static bool Tessellate(Allocator allocator, NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, ref NativeArray<float2> outputVertices, ref int vertexCount, ref NativeArray<int> outputIndices, ref int indexCount)
|
||||
{
|
||||
// Process.
|
||||
Tessellator tess = new Tessellator();
|
||||
tess.SetAllocator(allocator);
|
||||
int maxCount = 0, triCount = 0;
|
||||
var valid = true;
|
||||
|
||||
valid = tess.Triangulate(pgPoints, pgPointCount, pgEdges, pgEdgeCount);
|
||||
valid = valid && tess.ApplyDelaunay(pgPoints, pgEdges);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// Output.
|
||||
NativeArray<int3> cells = tess.RemoveExterior(ref triCount);
|
||||
for (var i = 0; i < triCount; ++i)
|
||||
{
|
||||
var a = (UInt16)cells[i].x;
|
||||
var b = (UInt16)cells[i].y;
|
||||
var c = (UInt16)cells[i].z;
|
||||
if (a != b && b != c && a != c)
|
||||
{
|
||||
outputIndices[indexCount++] = a;
|
||||
outputIndices[indexCount++] = c;
|
||||
outputIndices[indexCount++] = b;
|
||||
}
|
||||
maxCount = math.max(math.max(math.max(cells[i].x, cells[i].y), cells[i].z), maxCount);
|
||||
}
|
||||
maxCount = (maxCount != 0) ? (maxCount + 1) : 0;
|
||||
for (var i = 0; i < maxCount; ++i)
|
||||
outputVertices[vertexCount++] = pgPoints[i];
|
||||
cells.Dispose();
|
||||
}
|
||||
|
||||
tess.Cleanup();
|
||||
return valid;
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
if (m_Edges.IsCreated) m_Edges.Dispose();
|
||||
if (m_Stars.IsCreated) m_Stars.Dispose();
|
||||
if (m_SPArray.IsCreated) m_SPArray.Dispose();
|
||||
if (m_Cells.IsCreated) m_Cells.Dispose();
|
||||
if (m_ILArray.IsCreated) m_ILArray.Dispose();
|
||||
if (m_IUArray.IsCreated) m_IUArray.Dispose();
|
||||
if (m_Flags.IsCreated) m_Flags.Dispose();
|
||||
if (m_Neighbors.IsCreated) m_Neighbors.Dispose();
|
||||
if (m_Constraints.IsCreated) m_Constraints.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,949 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Profiling;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D.Common.UTess
|
||||
{
|
||||
enum UEventType
|
||||
{
|
||||
EVENT_POINT = 0,
|
||||
EVENT_END = 1,
|
||||
EVENT_START = 2,
|
||||
};
|
||||
|
||||
struct UEvent
|
||||
{
|
||||
public float2 a;
|
||||
public float2 b;
|
||||
public int idx;
|
||||
public int type;
|
||||
};
|
||||
|
||||
struct UHull
|
||||
{
|
||||
public float2 a;
|
||||
public float2 b;
|
||||
public int idx;
|
||||
|
||||
public ArraySlice<int> ilarray;
|
||||
public int ilcount;
|
||||
public ArraySlice<int> iuarray;
|
||||
public int iucount;
|
||||
};
|
||||
|
||||
struct UStar
|
||||
{
|
||||
public ArraySlice<int> points;
|
||||
public int pointCount;
|
||||
};
|
||||
|
||||
struct UBounds
|
||||
{
|
||||
public double2 min;
|
||||
public double2 max;
|
||||
};
|
||||
|
||||
struct UCircle
|
||||
{
|
||||
public float2 center;
|
||||
public float radius;
|
||||
};
|
||||
|
||||
struct UTriangle
|
||||
{
|
||||
public float2 va;
|
||||
public float2 vb;
|
||||
public float2 vc;
|
||||
public UCircle c;
|
||||
public float area;
|
||||
public int3 indices;
|
||||
};
|
||||
|
||||
struct UEncroachingSegment
|
||||
{
|
||||
public float2 a;
|
||||
public float2 b;
|
||||
public int index;
|
||||
}
|
||||
|
||||
internal interface ICondition2<in T, in U>
|
||||
{
|
||||
bool Test(T x, U y, ref float t);
|
||||
}
|
||||
|
||||
struct XCompare : IComparer<double>
|
||||
{
|
||||
public int Compare(double a, double b)
|
||||
{
|
||||
return (a < b) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe struct IntersectionCompare : IComparer<int2>
|
||||
{
|
||||
public Array<double2> points;
|
||||
public Array<int2> edges;
|
||||
|
||||
public fixed double xvasort[4];
|
||||
public fixed double xvbsort[4];
|
||||
|
||||
public int Compare(int2 a, int2 b)
|
||||
{
|
||||
var e1a = edges[a.x];
|
||||
var e1b = edges[a.y];
|
||||
var e2a = edges[b.x];
|
||||
var e2b = edges[b.y];
|
||||
|
||||
xvasort[0] = points[e1a.x].x;
|
||||
xvasort[1] = points[e1a.y].x;
|
||||
xvasort[2] = points[e1b.x].x;
|
||||
xvasort[3] = points[e1b.y].x;
|
||||
|
||||
xvbsort[0] = points[e2a.x].x;
|
||||
xvbsort[1] = points[e2a.y].x;
|
||||
xvbsort[2] = points[e2b.x].x;
|
||||
xvbsort[3] = points[e2b.y].x;
|
||||
|
||||
fixed (double* xvasortPtr = xvasort)
|
||||
{
|
||||
ModuleHandle.InsertionSort<double, XCompare>(xvasortPtr, 0, 3, new XCompare());
|
||||
}
|
||||
|
||||
fixed (double* xvbsortPtr = xvbsort)
|
||||
{
|
||||
ModuleHandle.InsertionSort<double, XCompare>(xvbsortPtr, 0, 3, new XCompare());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (xvasort[i] - xvbsort[i] != 0)
|
||||
return xvasort[i] < xvbsort[i] ? -1 : 1;
|
||||
return points[e1a.x].y < points[e1a.x].y ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct TessEventCompare : IComparer<UEvent>
|
||||
{
|
||||
public int Compare(UEvent a, UEvent b)
|
||||
{
|
||||
float f = (a.a.x - b.a.x);
|
||||
if (0 != f)
|
||||
return (f > 0) ? 1 : -1;
|
||||
|
||||
f = (a.a.y - b.a.y);
|
||||
if (0 != f)
|
||||
return (f > 0) ? 1 : -1;
|
||||
|
||||
int i = a.type - b.type;
|
||||
if (0 != i)
|
||||
return i;
|
||||
|
||||
if (a.type != (int)UEventType.EVENT_POINT)
|
||||
{
|
||||
float o = ModuleHandle.OrientFast(a.a, a.b, b.b);
|
||||
if (0 != o)
|
||||
{
|
||||
return (o > 0) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return a.idx - b.idx;
|
||||
}
|
||||
}
|
||||
|
||||
struct TessEdgeCompare : IComparer<int2>
|
||||
{
|
||||
public int Compare(int2 a, int2 b)
|
||||
{
|
||||
int i = a.x - b.x;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.y - b.y;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
struct TessCellCompare : IComparer<int3>
|
||||
{
|
||||
public int Compare(int3 a, int3 b)
|
||||
{
|
||||
int i = a.x - b.x;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.y - b.y;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.z - b.z;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
struct TessJunctionCompare : IComparer<int2>
|
||||
{
|
||||
public int Compare(int2 a, int2 b)
|
||||
{
|
||||
int i = a.x - b.x;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.y - b.y;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
struct DelaEdgeCompare : IComparer<int4>
|
||||
{
|
||||
public int Compare(int4 a, int4 b)
|
||||
{
|
||||
int i = a.x - b.x;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.y - b.y;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.z - b.z;
|
||||
if (0 != i)
|
||||
return i;
|
||||
i = a.w - b.w;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
struct TessLink
|
||||
{
|
||||
|
||||
internal NativeArray<int> roots;
|
||||
internal NativeArray<int> ranks;
|
||||
|
||||
internal static TessLink CreateLink(int count, Allocator allocator)
|
||||
{
|
||||
TessLink link = new TessLink();
|
||||
link.roots = new NativeArray<int>(count, allocator);
|
||||
link.ranks = new NativeArray<int>(count, allocator);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
link.roots[i] = i;
|
||||
link.ranks[i] = 0;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
internal static void DestroyLink(TessLink link)
|
||||
{
|
||||
link.ranks.Dispose();
|
||||
link.roots.Dispose();
|
||||
}
|
||||
|
||||
internal int Find(int x)
|
||||
{
|
||||
var x0 = x;
|
||||
while (roots[x] != x)
|
||||
{
|
||||
x = roots[x];
|
||||
}
|
||||
while (roots[x0] != x)
|
||||
{
|
||||
var y = roots[x0];
|
||||
roots[x0] = x;
|
||||
x0 = y;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
internal void Link(int x, int y)
|
||||
{
|
||||
var xr = Find(x);
|
||||
var yr = Find(y);
|
||||
if (xr == yr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var xd = ranks[xr];
|
||||
var yd = ranks[yr];
|
||||
if (xd < yd)
|
||||
{
|
||||
roots[xr] = yr;
|
||||
}
|
||||
else if (yd < xd)
|
||||
{
|
||||
roots[yr] = xr;
|
||||
}
|
||||
else
|
||||
{
|
||||
roots[yr] = xr;
|
||||
++ranks[xr];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
internal struct ModuleHandle
|
||||
{
|
||||
|
||||
// Max Edge Count with Subdivision allowed. This is already a very relaxed limit
|
||||
// and anything beyond are basically littered with numerous paths.
|
||||
internal static readonly int kMaxArea = 65536;
|
||||
internal static readonly int kMaxEdgeCount = 65536;
|
||||
internal static readonly int kMaxIndexCount = 65536;
|
||||
internal static readonly int kMaxVertexCount = 65536;
|
||||
internal static readonly int kMaxTriangleCount = kMaxIndexCount / 3;
|
||||
internal static readonly int kMaxRefineIterations = 48;
|
||||
internal static readonly int kMaxSmoothenIterations = 256;
|
||||
internal static readonly float kIncrementAreaFactor = 1.2f;
|
||||
|
||||
internal static void Copy<T>(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
|
||||
where T : struct
|
||||
{
|
||||
NativeArray<T>.Copy(src, srcIndex, dst, dstIndex, length);
|
||||
}
|
||||
|
||||
internal static void Copy<T>(NativeArray<T> src, NativeArray<T> dst, int length)
|
||||
where T : struct
|
||||
{
|
||||
Copy(src, 0, dst, 0, length);
|
||||
}
|
||||
|
||||
internal static unsafe void InsertionSort<T, U>(void* array, int lo, int hi, U comp)
|
||||
where T : struct where U : IComparer<T>
|
||||
{
|
||||
int i, j;
|
||||
T t;
|
||||
for (i = lo; i < hi; i++)
|
||||
{
|
||||
j = i;
|
||||
t = UnsafeUtility.ReadArrayElement<T>(array, i + 1);
|
||||
while (j >= lo && comp.Compare(t, UnsafeUtility.ReadArrayElement<T>(array, j)) < 0)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement<T>(array, j + 1, UnsafeUtility.ReadArrayElement<T>(array, j));
|
||||
j--;
|
||||
}
|
||||
UnsafeUtility.WriteArrayElement<T>(array, j + 1, t);
|
||||
}
|
||||
}
|
||||
|
||||
// Search Lower Bounds
|
||||
internal static int GetLower<T, U, X>(NativeArray<T> values, int count, U check, X condition)
|
||||
where T : struct where U : struct where X : ICondition2<T, U>
|
||||
{
|
||||
int l = 0;
|
||||
int h = count - 1;
|
||||
int i = l - 1;
|
||||
while (l <= h)
|
||||
{
|
||||
int m = ((int)(l + h)) >> 1;
|
||||
float t = 0;
|
||||
if (condition.Test(values[m], check, ref t))
|
||||
{
|
||||
i = m;
|
||||
l = m + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = m - 1;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// Search Upper Bounds
|
||||
internal static int GetUpper<T, U, X>(NativeArray<T> values, int count, U check, X condition)
|
||||
where T : struct where U : struct where X : ICondition2<T, U>
|
||||
{
|
||||
int l = 0;
|
||||
int h = count - 1;
|
||||
int i = h + 1;
|
||||
while (l <= h)
|
||||
{
|
||||
int m = ((int)(l + h)) >> 1;
|
||||
float t = 0;
|
||||
if (condition.Test(values[m], check, ref t))
|
||||
{
|
||||
i = m;
|
||||
h = m - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// Search for Equal
|
||||
internal static int GetEqual<T, U, X>(Array<T> values, int count, U check, X condition)
|
||||
where T : struct where U : struct where X : ICondition2<T, U>
|
||||
{
|
||||
int l = 0;
|
||||
int h = count - 1;
|
||||
while (l <= h)
|
||||
{
|
||||
int m = ((int)(l + h)) >> 1;
|
||||
float t = 0;
|
||||
condition.Test(values[m], check, ref t);
|
||||
if (t == 0)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
else if (t <= 0)
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = m - 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Search for Equal
|
||||
internal static int GetEqual<T, U, X>(NativeArray<T> values, int count, U check, X condition)
|
||||
where T : struct where U : struct where X : ICondition2<T, U>
|
||||
{
|
||||
int l = 0;
|
||||
int h = count - 1;
|
||||
while (l <= h)
|
||||
{
|
||||
int m = ((int)(l + h)) >> 1;
|
||||
float t = 0;
|
||||
condition.Test(values[m], check, ref t);
|
||||
if (t == 0)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
else if (t <= 0)
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = m - 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Simple Orientation test.
|
||||
internal static float OrientFast(float2 a, float2 b, float2 c)
|
||||
{
|
||||
float epsilon = 1.1102230246251565e-16f;
|
||||
float det = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y);
|
||||
if (math.abs(det) < epsilon) return 0;
|
||||
return det;
|
||||
}
|
||||
|
||||
// This is needed when doing PlanarGraph as it requires high precision separation of points.
|
||||
internal static double OrientFastDouble(double2 a, double2 b, double2 c)
|
||||
{
|
||||
double epsilon = 1.1102230246251565e-16f;
|
||||
double det = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y);
|
||||
if (math.abs(det) < epsilon) return 0;
|
||||
return det;
|
||||
}
|
||||
|
||||
internal static UCircle CircumCircle(UTriangle tri)
|
||||
{
|
||||
float xa = tri.va.x * tri.va.x;
|
||||
float xb = tri.vb.x * tri.vb.x;
|
||||
float xc = tri.vc.x * tri.vc.x;
|
||||
float ya = tri.va.y * tri.va.y;
|
||||
float yb = tri.vb.y * tri.vb.y;
|
||||
float yc = tri.vc.y * tri.vc.y;
|
||||
float c = 2f * ((tri.vb.x - tri.va.x) * (tri.vc.y - tri.va.y) - (tri.vb.y - tri.va.y) * (tri.vc.x - tri.va.x));
|
||||
float x = ((tri.vc.y - tri.va.y) * (xb - xa + yb - ya) + (tri.va.y - tri.vb.y) * (xc - xa + yc - ya)) / c;
|
||||
float y = ((tri.va.x - tri.vc.x) * (xb - xa + yb - ya) + (tri.vb.x - tri.va.x) * (xc - xa + yc - ya)) / c;
|
||||
float vx = (tri.va.x - x);
|
||||
float vy = (tri.va.y - y);
|
||||
return new UCircle { center = new float2(x, y), radius = math.sqrt((vx * vx) + (vy * vy)) };
|
||||
}
|
||||
|
||||
internal static bool IsInsideCircle(UCircle c, float2 v)
|
||||
{
|
||||
return math.distance(v, c.center) < c.radius;
|
||||
}
|
||||
|
||||
internal static float TriangleArea(float2 va, float2 vb, float2 vc)
|
||||
{
|
||||
float3 a = new float3(va.x, va.y, 0);
|
||||
float3 b = new float3(vb.x, vb.y, 0);
|
||||
float3 c = new float3(vc.x, vc.y, 0);
|
||||
float3 v = math.cross(a - b, a - c);
|
||||
return math.abs(v.z) * 0.5f;
|
||||
}
|
||||
|
||||
internal static float Sign(float2 p1, float2 p2, float2 p3)
|
||||
{
|
||||
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
|
||||
}
|
||||
|
||||
internal static bool IsInsideTriangle(float2 pt, float2 v1, float2 v2, float2 v3)
|
||||
{
|
||||
float d1, d2, d3;
|
||||
bool has_neg, has_pos;
|
||||
|
||||
d1 = Sign(pt, v1, v2);
|
||||
d2 = Sign(pt, v2, v3);
|
||||
d3 = Sign(pt, v3, v1);
|
||||
|
||||
has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
|
||||
has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
|
||||
|
||||
return !(has_neg && has_pos);
|
||||
}
|
||||
|
||||
internal static bool IsInsideTriangleApproximate(float2 pt, float2 v1, float2 v2, float2 v3)
|
||||
{
|
||||
float d0, d1, d2, d3;
|
||||
d0 = TriangleArea(v1, v2, v3);
|
||||
d1 = TriangleArea(pt, v1, v2);
|
||||
d2 = TriangleArea(pt, v2, v3);
|
||||
d3 = TriangleArea(pt, v3, v1);
|
||||
float epsilon = 1.1102230246251565e-16f;
|
||||
return Mathf.Abs(d0 - (d1 + d2 + d3)) < epsilon;
|
||||
}
|
||||
|
||||
internal static bool IsInsideCircle(float2 a, float2 b, float2 c, float2 p)
|
||||
{
|
||||
float ab = math.dot(a, a);
|
||||
float cd = math.dot(b, b);
|
||||
float ef = math.dot(c, c);
|
||||
|
||||
float ax = a.x;
|
||||
float ay = a.y;
|
||||
float bx = b.x;
|
||||
float by = b.y;
|
||||
float cx = c.x;
|
||||
float cy = c.y;
|
||||
|
||||
float circum_x = (ab * (cy - by) + cd * (ay - cy) + ef * (by - ay)) /
|
||||
(ax * (cy - by) + bx * (ay - cy) + cx * (by - ay));
|
||||
float circum_y = (ab * (cx - bx) + cd * (ax - cx) + ef * (bx - ax)) /
|
||||
(ay * (cx - bx) + by * (ax - cx) + cy * (bx - ax));
|
||||
|
||||
float2 circum = new float2();
|
||||
circum.x = circum_x / 2;
|
||||
circum.y = circum_y / 2;
|
||||
float circum_radius = math.distance(a, circum);
|
||||
float dist = math.distance(p, circum);
|
||||
return circum_radius - dist > 0.00001f;
|
||||
}
|
||||
|
||||
internal static void BuildTriangles(NativeArray<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> triangles, ref int triangleCount, ref float maxArea, ref float avgArea, ref float minArea)
|
||||
{
|
||||
// Check if there are invalid triangles or segments.
|
||||
for (int i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
UTriangle tri = new UTriangle();
|
||||
var i0 = indices[i + 0];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
tri.va = vertices[i0];
|
||||
tri.vb = vertices[i1];
|
||||
tri.vc = vertices[i2];
|
||||
tri.c = CircumCircle(tri);
|
||||
tri.area = TriangleArea(tri.va, tri.vb, tri.vc);
|
||||
maxArea = math.max(tri.area, maxArea);
|
||||
minArea = math.min(tri.area, minArea);
|
||||
avgArea = avgArea + tri.area;
|
||||
triangles[triangleCount++] = tri;
|
||||
}
|
||||
avgArea = avgArea / triangleCount;
|
||||
}
|
||||
|
||||
internal static void BuildTriangles(NativeArray<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref Array<UTriangle> triangles, ref int triangleCount, ref float maxArea, ref float avgArea, ref float minArea)
|
||||
{
|
||||
// Check if there are invalid triangles or segments.
|
||||
for (int i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
UTriangle tri = new UTriangle();
|
||||
var i0 = indices[i + 0];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
tri.va = vertices[i0];
|
||||
tri.vb = vertices[i1];
|
||||
tri.vc = vertices[i2];
|
||||
tri.c = CircumCircle(tri);
|
||||
tri.area = TriangleArea(tri.va, tri.vb, tri.vc);
|
||||
maxArea = math.max(tri.area, maxArea);
|
||||
minArea = math.min(tri.area, minArea);
|
||||
avgArea = avgArea + tri.area;
|
||||
triangles[triangleCount++] = tri;
|
||||
}
|
||||
avgArea = avgArea / triangleCount;
|
||||
}
|
||||
|
||||
internal static void BuildTriangles(NativeArray<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> triangles, ref int triangleCount, ref float maxArea, ref float avgArea, ref float minArea, ref float maxEdge, ref float avgEdge, ref float minEdge)
|
||||
{
|
||||
// Check if there are invalid triangles or segments.
|
||||
for (int i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
UTriangle tri = new UTriangle();
|
||||
var i0 = indices[i + 0];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
tri.va = vertices[i0];
|
||||
tri.vb = vertices[i1];
|
||||
tri.vc = vertices[i2];
|
||||
tri.c = CircumCircle(tri);
|
||||
|
||||
tri.area = TriangleArea(tri.va, tri.vb, tri.vc);
|
||||
maxArea = math.max(tri.area, maxArea);
|
||||
minArea = math.min(tri.area, minArea);
|
||||
avgArea = avgArea + tri.area;
|
||||
|
||||
var e1 = math.distance(tri.va, tri.vb);
|
||||
var e2 = math.distance(tri.vb, tri.vc);
|
||||
var e3 = math.distance(tri.vc, tri.va);
|
||||
maxEdge = math.max(e1, maxEdge);
|
||||
maxEdge = math.max(e2, maxEdge);
|
||||
maxEdge = math.max(e3, maxEdge);
|
||||
minEdge = math.min(e1, minEdge);
|
||||
minEdge = math.min(e2, minEdge);
|
||||
minEdge = math.min(e3, minEdge);
|
||||
|
||||
avgEdge = avgEdge + e1;
|
||||
avgEdge = avgEdge + e2;
|
||||
avgEdge = avgEdge + e3;
|
||||
triangles[triangleCount++] = tri;
|
||||
}
|
||||
avgArea = avgArea / triangleCount;
|
||||
avgEdge = avgEdge / indexCount;
|
||||
}
|
||||
|
||||
internal static void BuildTrianglesAndEdges(NativeArray<float2> vertices, int vertexCount, NativeArray<int> indices, int indexCount, ref NativeArray<UTriangle> triangles, ref int triangleCount, ref NativeArray<int4> delaEdges, ref int delaEdgeCount, ref float maxArea, ref float avgArea, ref float minArea)
|
||||
{
|
||||
// Check if there are invalid triangles or segments.
|
||||
for (int i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
UTriangle tri = new UTriangle();
|
||||
var i0 = indices[i + 0];
|
||||
var i1 = indices[i + 1];
|
||||
var i2 = indices[i + 2];
|
||||
tri.va = vertices[i0];
|
||||
tri.vb = vertices[i1];
|
||||
tri.vc = vertices[i2];
|
||||
tri.c = CircumCircle(tri);
|
||||
tri.area = TriangleArea(tri.va, tri.vb, tri.vc);
|
||||
maxArea = math.max(tri.area, maxArea);
|
||||
minArea = math.min(tri.area, minArea);
|
||||
avgArea = avgArea + tri.area;
|
||||
tri.indices = new int3(i0, i1, i2);
|
||||
|
||||
// Outputs.
|
||||
delaEdges[delaEdgeCount++] = new int4(math.min(i0, i1), math.max(i0, i1), triangleCount, -1);
|
||||
delaEdges[delaEdgeCount++] = new int4(math.min(i1, i2), math.max(i1, i2), triangleCount, -1);
|
||||
delaEdges[delaEdgeCount++] = new int4(math.min(i2, i0), math.max(i2, i0), triangleCount, -1);
|
||||
triangles[triangleCount++] = tri;
|
||||
}
|
||||
avgArea = avgArea / triangleCount;
|
||||
}
|
||||
|
||||
static void CopyGraph(NativeArray<float2> srcPoints, int srcPointCount, ref NativeArray<float2> dstPoints, ref int dstPointCount, NativeArray<int2> srcEdges, int srcEdgeCount, ref NativeArray<int2> dstEdges, ref int dstEdgeCount)
|
||||
{
|
||||
dstEdgeCount = srcEdgeCount;
|
||||
dstPointCount = srcPointCount;
|
||||
Copy(srcEdges, dstEdges, srcEdgeCount);
|
||||
Copy(srcPoints, dstPoints, srcPointCount);
|
||||
}
|
||||
|
||||
static void CopyGeometry(NativeArray<int> srcIndices, int srcIndexCount, ref NativeArray<int> dstIndices, ref int dstIndexCount, NativeArray<float2> srcVertices, int srcVertexCount, ref NativeArray<float2> dstVertices, ref int dstVertexCount)
|
||||
{
|
||||
dstIndexCount = srcIndexCount;
|
||||
dstVertexCount = srcVertexCount;
|
||||
Copy(srcIndices, dstIndices, srcIndexCount);
|
||||
Copy(srcVertices, dstVertices, srcVertexCount);
|
||||
}
|
||||
|
||||
static void TransferOutput(NativeArray<int2> srcEdges, int srcEdgeCount, ref NativeArray<int2> dstEdges, ref int dstEdgeCount, NativeArray<int> srcIndices, int srcIndexCount, ref NativeArray<int> dstIndices, ref int dstIndexCount, NativeArray<float2> srcVertices, int srcVertexCount, ref NativeArray<float2> dstVertices, ref int dstVertexCount)
|
||||
{
|
||||
dstEdgeCount = srcEdgeCount;
|
||||
dstIndexCount = srcIndexCount;
|
||||
dstVertexCount = srcVertexCount;
|
||||
Copy(srcEdges, dstEdges, srcEdgeCount);
|
||||
Copy(srcIndices, dstIndices, srcIndexCount);
|
||||
Copy(srcVertices, dstVertices, srcVertexCount);
|
||||
}
|
||||
|
||||
static void GraphConditioner(NativeArray<float2> points, ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> pgEdges, ref int pgEdgeCount, bool resetTopology)
|
||||
{
|
||||
var min = new float2(math.INFINITY, math.INFINITY);
|
||||
var max = float2.zero;
|
||||
for (int i = 0; i < points.Length; ++i)
|
||||
{
|
||||
min = math.min(points[i], min);
|
||||
max = math.max(points[i], max);
|
||||
}
|
||||
|
||||
var ext = (max - min);
|
||||
var mid = ext * 0.5f;
|
||||
var kNonRect = 0.0001f;
|
||||
|
||||
// Construct a simple convex hull rect!.
|
||||
pgPointCount = resetTopology ? 0 : pgPointCount;
|
||||
var pc = pgPointCount;
|
||||
pgPoints[pgPointCount++] = new float2(min.x, min.y); pgPoints[pgPointCount++] = new float2(min.x - kNonRect, min.y + mid.y); pgPoints[pgPointCount++] = new float2(min.x, max.y); pgPoints[pgPointCount++] = new float2(min.x + mid.x, max.y + kNonRect);
|
||||
pgPoints[pgPointCount++] = new float2(max.x, max.y); pgPoints[pgPointCount++] = new float2(max.x + kNonRect, min.y + mid.y); pgPoints[pgPointCount++] = new float2(max.x, min.y); pgPoints[pgPointCount++] = new float2(min.x + mid.x, min.y - kNonRect);
|
||||
|
||||
pgEdgeCount = 8;
|
||||
pgEdges[0] = new int2(pc + 0, pc + 1); pgEdges[1] = new int2(pc + 1, pc + 2); pgEdges[2] = new int2(pc + 2, pc + 3); pgEdges[3] = new int2(pc + 3, pc + 4);
|
||||
pgEdges[4] = new int2(pc + 4, pc + 5); pgEdges[5] = new int2(pc + 5, pc + 6); pgEdges[6] = new int2(pc + 6, pc + 7); pgEdges[7] = new int2(pc + 7, pc + 0);
|
||||
}
|
||||
|
||||
// Reorder vertices.
|
||||
static void Reorder(int startVertexCount, int index, ref NativeArray<int> indices, ref int indexCount, ref NativeArray<float2> vertices, ref int vertexCount)
|
||||
{
|
||||
|
||||
var found = false;
|
||||
|
||||
for (var i = 0; i < indexCount; ++i)
|
||||
{
|
||||
if (indices[i] != index) continue;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
vertexCount--;
|
||||
vertices[index] = vertices[vertexCount];
|
||||
for (var i = 0; i < indexCount; ++i)
|
||||
if (indices[i] == vertexCount)
|
||||
indices[i] = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Perform Sanitization.
|
||||
internal static void VertexCleanupConditioner(int startVertexCount, ref NativeArray<int> indices, ref int indexCount, ref NativeArray<float2> vertices, ref int vertexCount)
|
||||
{
|
||||
|
||||
for (int i = startVertexCount; i < vertexCount; ++i)
|
||||
{
|
||||
Reorder(startVertexCount,i, ref indices, ref indexCount, ref vertices, ref vertexCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static float4 ConvexQuad(Allocator allocator, NativeArray<float2> points, NativeArray<int2> edges, ref NativeArray<float2> outVertices, ref int outVertexCount, ref NativeArray<int> outIndices, ref int outIndexCount, ref NativeArray<int2> outEdges, ref int outEdgeCount)
|
||||
{
|
||||
// Inputs are garbage, just early out.
|
||||
float4 ret = float4.zero;
|
||||
outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0;
|
||||
if (points.Length < 3 || points.Length >= kMaxVertexCount)
|
||||
return ret;
|
||||
|
||||
// Ensure inputs form a proper PlanarGraph.
|
||||
int pgEdgeCount = 0, pgPointCount = 0;
|
||||
NativeArray<int2> pgEdges = new NativeArray<int2>(kMaxEdgeCount, allocator);
|
||||
NativeArray<float2> pgPoints = new NativeArray<float2>(kMaxVertexCount, allocator);
|
||||
|
||||
// Valid Edges and Paths, correct the Planar Graph. If invalid create a simple convex hull rect.
|
||||
GraphConditioner(points, ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, true);
|
||||
Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref outVertices, ref outVertexCount, ref outIndices, ref outIndexCount);
|
||||
|
||||
// Dispose Temp Memory.
|
||||
pgPoints.Dispose();
|
||||
pgEdges.Dispose();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static float4 Tessellate(Allocator allocator, in NativeArray<float2> points, in NativeArray<int2> edges, ref NativeArray<float2> outVertices, out int outVertexCount, ref NativeArray<int> outIndices, out int outIndexCount, ref NativeArray<int2> outEdges, out int outEdgeCount)
|
||||
{
|
||||
// Inputs are garbage, just early out.
|
||||
float4 ret = float4.zero;
|
||||
outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0;
|
||||
if (points.Length < 3 || points.Length >= kMaxVertexCount)
|
||||
return ret;
|
||||
|
||||
// Ensure inputs form a proper PlanarGraph.
|
||||
bool validGraph = false, handleEdgeCase = false;
|
||||
int pgEdgeCount = 0, pgPointCount = 0;
|
||||
NativeArray<int2> pgEdges = new NativeArray<int2>(edges.Length * 8, allocator);
|
||||
NativeArray<float2> pgPoints = new NativeArray<float2>(points.Length * 4, allocator);
|
||||
|
||||
// Valid Edges and Paths, correct the Planar Graph. If invalid create a simple convex hull rect.
|
||||
if (0 != edges.Length)
|
||||
{
|
||||
validGraph = PlanarGraph.Validate(allocator, in points, points.Length, in edges, edges.Length, ref pgPoints,out pgPointCount, ref pgEdges, out pgEdgeCount);
|
||||
}
|
||||
|
||||
// Fallbacks are now handled by the Higher level packages. Enable if UTess needs to handle it.
|
||||
// #if UTESS_QUAD_FALLBACK
|
||||
// if (!validGraph)
|
||||
// {
|
||||
// pgPointCount = 0;
|
||||
// handleEdgeCase = true;
|
||||
// ModuleHandle.Copy(points, pgPoints, points.Length);
|
||||
// GraphConditioner(points, ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, false);
|
||||
// }
|
||||
// #else
|
||||
|
||||
// If its not a valid Graph simply return back input Data without triangulation instead of going through UTess (pointless wasted cpu cycles).
|
||||
if (!validGraph)
|
||||
{
|
||||
outEdgeCount = edges.Length;
|
||||
outVertexCount = points.Length;
|
||||
ModuleHandle.Copy(edges, outEdges, edges.Length);
|
||||
ModuleHandle.Copy(points, outVertices, points.Length);
|
||||
}
|
||||
|
||||
// Do a proper Delaunay Triangulation if Inputs are valid.
|
||||
if (pgPointCount > 2 && pgEdgeCount > 2)
|
||||
{
|
||||
// Tessellate does not add new points, only PG and SD does. Assuming each point creates a degenerate triangle, * 4 is more than enough.
|
||||
NativeArray<int> tsIndices = new NativeArray<int>(pgPointCount * 8, allocator);
|
||||
NativeArray<float2> tsVertices = new NativeArray<float2>(pgPointCount * 4, allocator);
|
||||
int tsIndexCount = 0, tsVertexCount = 0;
|
||||
validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref tsVertices, ref tsVertexCount, ref tsIndices, ref tsIndexCount);
|
||||
if (validGraph)
|
||||
{
|
||||
// Copy Out
|
||||
TransferOutput(pgEdges, pgEdgeCount, ref outEdges, ref outEdgeCount, tsIndices, tsIndexCount, ref outIndices, ref outIndexCount, tsVertices, tsVertexCount, ref outVertices, ref outVertexCount);
|
||||
if (handleEdgeCase == true)
|
||||
outEdgeCount = 0;
|
||||
}
|
||||
tsVertices.Dispose();
|
||||
tsIndices.Dispose();
|
||||
}
|
||||
|
||||
// Dispose Temp Memory.
|
||||
pgPoints.Dispose();
|
||||
pgEdges.Dispose();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static float4 Subdivide(Allocator allocator, NativeArray<float2> points, NativeArray<int2> edges, ref NativeArray<float2> outVertices, ref int outVertexCount, ref NativeArray<int> outIndices, ref int outIndexCount, ref NativeArray<int2> outEdges, ref int outEdgeCount, float areaFactor, float targetArea, int refineIterations, int smoothenIterations)
|
||||
{
|
||||
// Inputs are garbage, just early out.
|
||||
float4 ret = float4.zero;
|
||||
outEdgeCount = 0; outIndexCount = 0; outVertexCount = 0;
|
||||
if (points.Length < 3 || points.Length >= kMaxVertexCount || 0 == edges.Length)
|
||||
return ret;
|
||||
|
||||
// Do a proper Delaunay Triangulation.
|
||||
int tsIndexCount = 0, tsVertexCount = 0;
|
||||
NativeArray<int> tsIndices = new NativeArray<int>(kMaxIndexCount, allocator);
|
||||
NativeArray<float2> tsVertices = new NativeArray<float2>(kMaxVertexCount, allocator);
|
||||
var validGraph = Tessellator.Tessellate(allocator, points, points.Length, edges, edges.Length, ref tsVertices, ref tsVertexCount, ref tsIndices, ref tsIndexCount);
|
||||
|
||||
// Refinement and Smoothing.
|
||||
bool refined = false;
|
||||
bool refinementRequired = (targetArea != 0 || areaFactor != 0);
|
||||
if (validGraph && refinementRequired)
|
||||
{
|
||||
// Do Refinement until success.
|
||||
float maxArea = 0;
|
||||
float incArea = 0;
|
||||
int rfEdgeCount = 0, rfPointCount = 0, rfIndexCount = 0, rfVertexCount = 0;
|
||||
NativeArray<int2> rfEdges = new NativeArray<int2>(kMaxEdgeCount, allocator);
|
||||
NativeArray<float2> rfPoints = new NativeArray<float2>(kMaxVertexCount, allocator);
|
||||
NativeArray<int> rfIndices = new NativeArray<int>(kMaxIndexCount, allocator);
|
||||
NativeArray<float2> rfVertices = new NativeArray<float2>(kMaxVertexCount, allocator);
|
||||
ret.x = 0;
|
||||
refineIterations = Math.Min(refineIterations, kMaxRefineIterations);
|
||||
|
||||
if (targetArea != 0)
|
||||
{
|
||||
// Increment for Iterations.
|
||||
incArea = (targetArea / 10);
|
||||
|
||||
while (targetArea < kMaxArea && refineIterations > 0)
|
||||
{
|
||||
// Do Mesh Refinement.
|
||||
CopyGraph(points, points.Length, ref rfPoints, ref rfPointCount, edges, edges.Length, ref rfEdges, ref rfEdgeCount);
|
||||
CopyGeometry(tsIndices, tsIndexCount, ref rfIndices, ref rfIndexCount, tsVertices, tsVertexCount, ref rfVertices, ref rfVertexCount);
|
||||
refined = Refinery.Condition(allocator, areaFactor, targetArea, ref rfPoints, ref rfPointCount, ref rfEdges, ref rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount, ref maxArea);
|
||||
|
||||
if (refined && rfIndexCount > rfPointCount)
|
||||
{
|
||||
// Copy Out
|
||||
ret.x = areaFactor;
|
||||
TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount);
|
||||
break;
|
||||
}
|
||||
|
||||
refined = false;
|
||||
targetArea = targetArea + incArea;
|
||||
refineIterations--;
|
||||
}
|
||||
|
||||
}
|
||||
else if (areaFactor != 0)
|
||||
{
|
||||
// Increment for Iterations.
|
||||
areaFactor = math.lerp(0.1f, 0.54f, (areaFactor - 0.05f) / 0.45f); // Specific to Animation.
|
||||
incArea = (areaFactor / 10);
|
||||
|
||||
while (areaFactor < 0.8f && refineIterations > 0)
|
||||
{
|
||||
// Do Mesh Refinement.
|
||||
CopyGraph(points, points.Length, ref rfPoints, ref rfPointCount, edges, edges.Length, ref rfEdges, ref rfEdgeCount);
|
||||
CopyGeometry(tsIndices, tsIndexCount, ref rfIndices, ref rfIndexCount, tsVertices, tsVertexCount, ref rfVertices, ref rfVertexCount);
|
||||
refined = Refinery.Condition(allocator, areaFactor, targetArea, ref rfPoints, ref rfPointCount, ref rfEdges, ref rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount, ref maxArea);
|
||||
|
||||
if (refined && rfIndexCount > rfPointCount)
|
||||
{
|
||||
// Copy Out
|
||||
ret.x = areaFactor;
|
||||
TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount);
|
||||
break;
|
||||
}
|
||||
|
||||
refined = false;
|
||||
areaFactor = areaFactor + incArea;
|
||||
refineIterations--;
|
||||
}
|
||||
}
|
||||
|
||||
if (refined)
|
||||
{
|
||||
// Sanitize generated geometry data.
|
||||
var preSmoothen = outVertexCount;
|
||||
if (ret.x != 0)
|
||||
VertexCleanupConditioner(tsVertexCount, ref rfIndices, ref rfIndexCount, ref rfVertices, ref rfVertexCount);
|
||||
|
||||
// Smoothen. At this point only vertex relocation is allowed, not vertex addition/removal.
|
||||
// Note: Only refined mesh contains Steiner points and we only smoothen these points.
|
||||
ret.y = 0;
|
||||
smoothenIterations = math.clamp(smoothenIterations, 0, kMaxSmoothenIterations);
|
||||
while (smoothenIterations > 0)
|
||||
{
|
||||
var smoothen = Smoothen.Condition(allocator, ref rfPoints, rfPointCount, rfEdges, rfEdgeCount, ref rfVertices, ref rfVertexCount, ref rfIndices, ref rfIndexCount);
|
||||
if (!smoothen)
|
||||
break;
|
||||
// Copy Out
|
||||
ret.y = (float)(smoothenIterations);
|
||||
TransferOutput(rfEdges, rfEdgeCount, ref outEdges, ref outEdgeCount, rfIndices, rfIndexCount, ref outIndices, ref outIndexCount, rfVertices, rfVertexCount, ref outVertices, ref outVertexCount);
|
||||
smoothenIterations--;
|
||||
}
|
||||
|
||||
// Sanitize generated geometry data.
|
||||
var postSmoothen = outVertexCount;
|
||||
if (ret.y != 0)
|
||||
VertexCleanupConditioner(tsVertexCount, ref outIndices, ref outIndexCount, ref outVertices, ref outVertexCount);
|
||||
}
|
||||
|
||||
rfVertices.Dispose();
|
||||
rfIndices.Dispose();
|
||||
rfPoints.Dispose();
|
||||
rfEdges.Dispose();
|
||||
}
|
||||
|
||||
// Refinement failed but Graph succeeded.
|
||||
if (validGraph && !refined)
|
||||
{
|
||||
// Copy Out
|
||||
TransferOutput(edges, edges.Length, ref outEdges, ref outEdgeCount, tsIndices, tsIndexCount, ref outIndices, ref outIndexCount, tsVertices, tsVertexCount, ref outVertices, ref outVertexCount);
|
||||
}
|
||||
|
||||
// Dispose Temp Memory.
|
||||
tsVertices.Dispose();
|
||||
tsIndices.Dispose();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "Unity.2D.Common.Runtime",
|
||||
"references": [
|
||||
"Unity.Mathematics"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using NUnit.Framework;
|
||||
|
||||
|
||||
namespace UnityEditor.Experimental.U2D.Commons.Tests
|
||||
{
|
||||
internal class Placeholder
|
||||
{
|
||||
[Test]
|
||||
public void PlaceHolderTest()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
Assert.Pass("This is a placeholder to ensure we have at least one test.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "Unity.2D.Common.EditorTests",
|
||||
"references": [
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
This package contains third-party software components governed by the license(s) indicated below:
|
||||
|
||||
---------
|
||||
Component Name: cdt2d
|
||||
|
||||
License Type: The MIT License
|
||||
|
||||
Copyright (c) 2015 Mikola Lysenko
|
||||
|
||||
https://github.com/mikolalysenko/cdt2d
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "com.unity.2d.common",
|
||||
"displayName": "2D Common",
|
||||
"version": "8.0.1",
|
||||
"unity": "2022.2",
|
||||
"description": "2D Common is a package that contains shared functionalities that are used by most of the other 2D packages.",
|
||||
"keywords": [
|
||||
"2d"
|
||||
],
|
||||
"category": "2D",
|
||||
"dependencies": {
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.modules.uielements": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.burst": "1.7.3"
|
||||
},
|
||||
"relatedPackages": {
|
||||
"com.unity.2d.common.tests": "8.0.1"
|
||||
},
|
||||
"upmCi": {
|
||||
"footprint": "e46c475e7e07e020559ecf33c81990a04364425f"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.cds.internal.unity3d.com/unity/2d.git",
|
||||
"type": "git",
|
||||
"revision": "6fc635d83d4bb0b2640893b8d4a0f3934044aeb5"
|
||||
}
|
||||
}
|