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

View File

@@ -0,0 +1,223 @@
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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;
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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 ControlPoint(Vector3 pos)
{
position = pos;
localLeftTangent = Vector3.zero;
localRightTangent = Vector3.zero;
tangentMode = TangentMode.Linear;
continuousCache = default(TangentCache);
brokenCache = default(TangentCache);
mirrorLeft = false;
}
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;
}
}
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
[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 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;
}
public virtual void SetDefaultShape()
{
}
}
}

View File

@@ -0,0 +1,264 @@
using System;
using System.Linq;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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; set; }
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;
int pointsCountToRemove = editablePath.selection.Count;
if (editablePath.pointCount != pointsCountToRemove)
{
var indices = editablePath.selection.elements.OrderByDescending(i => i);
foreach (var index in indices)
{
if (editablePath.pointCount > minPointCount)
{
editablePath.RemovePoint(index);
}
}
ClearSelection();
}
else
{
editablePath.SetDefaultShape();
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)
{
var controlPoint = editablePath.GetPoint(index);
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);
}
public void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent)
{
var controlPoint = editablePath.GetPoint(index);
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);
}
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));
}
}
}

View File

@@ -0,0 +1,275 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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;
}
}
}

View File

@@ -0,0 +1,14 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal class EditablePathUtility
{
public static int Mod(int x, int m)
{
int r = x % m;
return r < 0 ? r + m : r;
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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();
void SetDefaultShape();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface IEditablePathController
{
IEditablePath editablePath { get; set; }
IEditablePath closestEditablePath { get; }
ISnapping<Vector3> snapping { get; set; }
bool enableSnapping { get; set; }
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);
void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent);
void ClearClosestPath();
void AddClosestPath(float distance);
}
}

View File

@@ -0,0 +1,10 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface ISnapping<T>
{
T Snap(T value);
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface IUndoObject
{
void RegisterUndo(string name);
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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 m_Controller.enableSnapping; }
set { m_Controller.enableSnapping = value; }
}
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)
{
m_Controller.SetLeftTangent(index, position, setToLinear, mirror, cachedRightTangent);
}
public void SetRightTangent(int index, Vector3 position, bool setToLinear, bool mirror, Vector3 cachedLeftTangent)
{
m_Controller.SetRightTangent(index, position, setToLinear, mirror, cachedLeftTangent);
}
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);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal class Snapping : ISnapping<Vector3>
{
public Vector3 Snap(Vector3 position)
{
return new Vector3(
Snap(position.x, EditorPrefs.GetFloat("MoveSnapX", 1f)),
Snap(position.y, EditorPrefs.GetFloat("MoveSnapY", 1f)),
position.z);
}
private float Snap(float value, float snap)
{
return Mathf.Round(value / snap) * snap;
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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) {}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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>();
m_DataObjects.Add(dataObject);
return dataObject;
}
private void DestroyDataObjects()
{
foreach (var customDataObject in m_DataObjects)
DestroyImmediate(customDataObject);
m_DataObjects.Clear();
m_SelectedDataObjects.Clear();
}
}
}

View File

@@ -0,0 +1,121 @@
#pragma warning disable 0618
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Experimental.Rendering.Universal.Path2D;
#if !UNITY_2020_2_OR_NEWER
using ToolManager = UnityEditor.EditorTools.EditorTools;
#endif
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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;
protected void 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);
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();
}
}
}
}
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 DoSnappingInspector<U>() where U : PathEditorTool<T>
{
if (EditorToolManager.IsActiveTool<U>() && EditorToolManager.IsAvailable<U>())
{
var tool = EditorToolManager.GetEditorTool<U>();
tool.enableSnapping = EditorGUILayout.Toggle(Contents.snappingLabel, tool.enableSnapping);
}
}
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();
}
}
}
#pragma warning restore 0618

View File

@@ -0,0 +1,499 @@
#pragma warning disable 0618
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
using UnityObject = UnityEngine.Object;
#if !UNITY_2020_2_OR_NEWER
using ToolManager = UnityEditor.EditorTools.EditorTools;
#endif
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal static class PathEditorToolContents
{
internal static readonly GUIContent shapeToolIcon = IconContent("ShapeTool", "Unlocks the shape to allow editing in the Scene View.");
internal static readonly GUIContent shapeToolPro = IconContent("ShapeToolPro", "Unlocks the shape to allow editing in the Scene View.");
internal static GUIContent IconContent(string name, string tooltip = null)
{
return new GUIContent(Resources.Load<Texture>(name), 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, GUISystem> m_GUISystems = new Dictionary<UnityObject, GUISystem>();
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;
internal T[] paths
{
get { return m_Paths.Values.ToArray(); }
}
public bool enableSnapping
{
get { return m_Controller.enableSnapping; }
set { m_Controller.enableSnapping = value; }
}
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_GUISystems.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();
CreateGUISystem(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.owner = targetObject;
m_Paths[targetObject] = path;
}
return path;
}
private GUISystem GetGUISystem(UnityObject target)
{
GUISystem guiSystem;
m_GUISystems.TryGetValue(target, out guiSystem);
return guiSystem;
}
private void CreateGUISystem(UnityObject target)
{
var guiSystem = new GUISystem(m_GUIState);
var view = new EditablePathView();
view.controller = m_Controller;
view.Install(guiSystem);
m_GUISystems[target] = guiSystem;
}
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())
{
GetGUISystem(target).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) {}
}
}
#pragma warning restore 0618

View File

@@ -0,0 +1,107 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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; }
}
}
}

View File

@@ -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.Experimental.Rendering.Universal.Path2D
{
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 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;
}
public virtual void SetDefaultShape()
{
m_Modified = true;
}
}
}

View File

@@ -0,0 +1,233 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
[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(Resources.Load<Texture>(name), 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;
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);
if (first)
{
position = controlPoint.position;
first = false;
}
else if (position != controlPoint.position)
{
return true;
}
}
path.localToWorldMatrix = matrix;
}
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;
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class ClickAction : HoveredControlAction
{
private int m_Button;
private bool m_UseEvent;
public Action<IGUIState, Control> onClick;
public ClickAction(Control control, int button, bool useEvent = true) : base(control)
{
m_Button = button;
m_UseEvent = useEvent;
}
protected override bool GetTriggerContidtion(IGUIState guiState)
{
return guiState.mouseButton == m_Button && guiState.eventType == EventType.MouseDown;
}
protected override void OnTrigger(IGUIState guiState)
{
base.OnTrigger(guiState);
if (onClick != null)
onClick(guiState, hoveredControl);
if (m_UseEvent)
guiState.UseCurrentEvent();
}
protected override bool GetFinishContidtion(IGUIState guiState)
{
return true;
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class CommandAction : GUIAction
{
private string m_CommandName;
public Action<IGUIState> onCommand;
public CommandAction(string commandName)
{
m_CommandName = commandName;
}
protected override bool GetTriggerContidtion(IGUIState guiState)
{
if (guiState.eventType == EventType.ValidateCommand && guiState.commandName == m_CommandName)
{
guiState.UseCurrentEvent();
return true;
}
return false;
}
protected override bool GetFinishContidtion(IGUIState guiState)
{
if (guiState.eventType == EventType.ExecuteCommand && guiState.commandName == m_CommandName)
{
guiState.UseCurrentEvent();
return true;
}
return false;
}
protected override void OnFinish(IGUIState guiState)
{
if (onCommand != null)
onCommand(guiState);
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
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;
public string name
{
get { return m_Name; }
}
public int ID
{
get { return m_ID; }
}
public int actionID
{
get { return m_ActionID; }
}
public LayoutData layoutData
{
get { return m_LayoutData; }
set { m_LayoutData = value; }
}
public LayoutData hotLayoutData
{
get { return m_HotLayoutData; }
}
public Control(string name)
{
m_Name = name;
m_NameHashCode = name.GetHashCode();
}
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;
}
public void BeginLayout(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Layout);
m_LayoutData = OnBeginLayout(LayoutData.zero, guiState);
}
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);
}
}
public void EndLayout(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Layout);
OnEndLayout(guiState);
}
public void Repaint(IGUIState guiState)
{
for (var i = 0; i < GetCount(); ++i)
OnRepaint(guiState, i);
}
protected virtual LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
return data;
}
protected virtual void OnEndLayout(IGUIState guiState)
{
}
protected virtual void OnRepaint(IGUIState guiState, int index)
{
}
protected virtual int GetCount()
{
return 1;
}
protected virtual Vector3 GetPosition(IGUIState guiState, int index)
{
return Vector3.zero;
}
protected virtual Vector3 GetForward(IGUIState guiState, int index)
{
return Vector3.forward;
}
protected virtual Vector3 GetUp(IGUIState guiState, int index)
{
return Vector3.up;
}
protected virtual Vector3 GetRight(IGUIState guiState, int index)
{
return Vector3.right;
}
protected virtual float GetDistance(IGUIState guiState, int index)
{
return layoutData.distance;
}
protected virtual object GetUserData(IGUIState guiState, int index)
{
return null;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal abstract class DefaultControl : Control
{
public static readonly float kPickDistance = 5f;
public DefaultControl(string name) : base(name)
{
}
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
data.distance = kPickDistance;
return data;
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal abstract class GUIAction
{
private int m_ID = -1;
public Func<IGUIState, GUIAction, bool> enable;
public Func<IGUIState, GUIAction, bool> enableRepaint;
public Func<IGUIState, GUIAction, bool> repaintOnMouseMove;
public Action<IGUIState, GUIAction> onPreRepaint;
public Action<IGUIState, GUIAction> onRepaint;
public int ID
{
get { return m_ID; }
}
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);
}
public bool IsEnabled(IGUIState guiState)
{
if (enable != null)
return enable(guiState, this);
return true;
}
public bool IsRepaintEnabled(IGUIState guiState)
{
if (!IsEnabled(guiState))
return false;
if (enableRepaint != null)
return enableRepaint(guiState, this);
return true;
}
public void PreRepaint(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Repaint);
if (IsEnabled(guiState) && onPreRepaint != null)
onPreRepaint(guiState, this);
}
private void Repaint(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Repaint);
if (onRepaint != null)
onRepaint(guiState, this);
}
internal bool IsRepaintOnMouseMoveEnabled(IGUIState guiState)
{
if (!IsEnabled(guiState) || !IsRepaintEnabled(guiState))
return false;
if (repaintOnMouseMove != null)
return repaintOnMouseMove(guiState, this);
return false;
}
protected abstract bool GetFinishContidtion(IGUIState guiState);
protected abstract bool GetTriggerContidtion(IGUIState guiState);
protected virtual bool CanTrigger(IGUIState guiState) { return true; }
protected virtual void OnTrigger(IGUIState guiState)
{
}
protected virtual void OnPerform(IGUIState guiState)
{
}
protected virtual void OnFinish(IGUIState guiState)
{
}
}
}

View File

@@ -0,0 +1,165 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class GUIState : IGUIState
{
private Handles.CapFunction nullCap = (int c, Vector3 p , Quaternion r, float s, EventType ev) => {};
public Vector2 mousePosition
{
get { return Event.current.mousePosition; }
}
public int mouseButton
{
get { return Event.current.button; }
}
public int clickCount
{
get { return Event.current.clickCount; }
}
public bool isShiftDown
{
get { return Event.current.shift; }
}
public bool isAltDown
{
get { return Event.current.alt; }
}
public bool isActionKeyDown
{
get { return EditorGUI.actionKey; }
}
public KeyCode keyCode
{
get { return Event.current.keyCode; }
}
public EventType eventType
{
get { return Event.current.type; }
}
public string commandName
{
get { return Event.current.commandName; }
}
public int nearestControl
{
get { return HandleUtility.nearestControl; }
set { HandleUtility.nearestControl = value; }
}
public int hotControl
{
get { return GUIUtility.hotControl; }
set { GUIUtility.hotControl = value; }
}
public bool changed
{
get { return GUI.changed; }
set { GUI.changed = value; }
}
public int GetControlID(int hint, FocusType focusType)
{
return GUIUtility.GetControlID(hint, focusType);
}
public void AddControl(int controlID, float distance)
{
HandleUtility.AddControl(controlID, distance);
}
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();
}
public void UseCurrentEvent()
{
Event.current.Use();
}
public void Repaint()
{
HandleUtility.Repaint();
}
public bool IsEventOutsideWindow()
{
return Event.current.type == EventType.Ignore;
}
public bool IsViewToolActive()
{
return UnityEditor.Tools.current == Tool.View || isAltDown || mouseButton == 1 || mouseButton == 2;
}
public bool HasCurrentCamera()
{
return Camera.current != null;
}
public float GetHandleSize(Vector3 position)
{
var scale = HasCurrentCamera() ? 0.01f : 0.05f;
return HandleUtility.GetHandleSize(position) * scale;
}
public float DistanceToSegment(Vector3 p1, Vector3 p2)
{
p1 = HandleUtility.WorldToGUIPoint(p1);
p2 = HandleUtility.WorldToGUIPoint(p2);
return HandleUtility.DistancePointToLineSegment(Event.current.mousePosition, p1, p2);
}
public float DistanceToCircle(Vector3 center, float radius)
{
return HandleUtility.DistanceToCircle(center, radius);
}
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;
}
}
}

View File

@@ -0,0 +1,124 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
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;
public GUISystem(IGUIState guiState)
{
m_GUIState = guiState;
}
public void AddControl(Control control)
{
if (control == null)
throw new NullReferenceException("Control is null");
m_Controls.Add(control);
}
public void RemoveControl(Control control)
{
m_Controls.Remove(control);
}
public void AddAction(GUIAction action)
{
if (action == null)
throw new NullReferenceException("Action is null");
m_Actions.Add(action);
}
public void RemoveAction(GUIAction action)
{
m_Actions.Remove(action);
}
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.UseCurrentEvent();
}
private bool IsMouseMoveEvent()
{
return m_GUIState.eventType == EventType.MouseMove || m_GUIState.eventType == EventType.MouseDrag;
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class GenericControl : Control
{
public Func<IGUIState, LayoutData> onBeginLayout = null;
public Action<IGUIState> onEndLayout = null;
public Action<IGUIState, Control, int> onRepaint;
public Func<int> count;
public Func<int, Vector3> position;
public Func<IGUIState, int, float> distance;
public Func<int, Vector3> forward;
public Func<int, Vector3> up;
public Func<int, Vector3> right;
public Func<int, object> userData = null;
public GenericControl(string name) : base(name)
{
}
protected override int GetCount()
{
if (count != null)
return count();
return base.GetCount();
}
protected override void OnEndLayout(IGUIState guiState)
{
if (onEndLayout != null)
onEndLayout(guiState);
}
protected override void OnRepaint(IGUIState guiState, int index)
{
if (onRepaint != null)
onRepaint(guiState, this, index);
}
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
if (onBeginLayout != null)
return onBeginLayout(guiState);
return data;
}
protected override Vector3 GetPosition(IGUIState guiState, int index)
{
if (position != null)
return position(index);
return base.GetPosition(guiState, index);
}
protected override float GetDistance(IGUIState guiState, int index)
{
if (distance != null)
return distance(guiState, index);
return base.GetDistance(guiState, index);
}
protected override Vector3 GetForward(IGUIState guiState, int index)
{
if (forward != null)
return forward(index);
return base.GetForward(guiState, index);
}
protected override Vector3 GetUp(IGUIState guiState, int index)
{
if (up != null)
return up(index);
return base.GetUp(guiState, index);
}
protected override Vector3 GetRight(IGUIState guiState, int index)
{
if (right != null)
return right(index);
return base.GetRight(guiState, index);
}
protected override object GetUserData(IGUIState guiState, int index)
{
if (userData != null)
return userData(index);
return base.GetUserData(guiState, index);
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class GenericDefaultControl : DefaultControl
{
public Func<IGUIState, Vector3> position;
public Func<IGUIState, Vector3> forward;
public Func<IGUIState, Vector3> up;
public Func<IGUIState, Vector3> right;
public Func<IGUIState, object> userData = null;
public GenericDefaultControl(string name) : base(name)
{
}
protected override Vector3 GetPosition(IGUIState guiState, int index)
{
if (position != null)
return position(guiState);
return base.GetPosition(guiState, index);
}
protected override Vector3 GetForward(IGUIState guiState, int index)
{
if (forward != null)
return forward(guiState);
return base.GetForward(guiState, index);
}
protected override Vector3 GetUp(IGUIState guiState, int index)
{
if (up != null)
return up(guiState);
return base.GetUp(guiState, index);
}
protected override Vector3 GetRight(IGUIState guiState, int index)
{
if (right != null)
return right(guiState);
return base.GetRight(guiState, index);
}
protected override object GetUserData(IGUIState guiState, int index)
{
if (userData != null)
return userData(guiState);
return base.GetUserData(guiState, index);
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal abstract class HoveredControlAction : GUIAction
{
private Control m_HoveredControl;
public Control hoveredControl
{
get { return m_HoveredControl; }
}
public HoveredControlAction(Control control)
{
m_HoveredControl = control;
}
protected override bool CanTrigger(IGUIState guiState)
{
return guiState.nearestControl == hoveredControl.ID;
}
protected override void OnTrigger(IGUIState guiState)
{
m_HoveredControl.SetActionID(ID);
}
}
}

View File

@@ -0,0 +1,43 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal struct SliderData
{
public Vector3 position;
public Vector3 forward;
public Vector3 up;
public Vector3 right;
public static readonly SliderData zero = new SliderData() { position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
}
internal interface IGUIState
{
Vector2 mousePosition { get; }
int mouseButton { get; }
int clickCount { get; }
bool isShiftDown { get; }
bool isAltDown { get; }
bool isActionKeyDown { get; }
KeyCode keyCode { get; }
EventType eventType { get; }
string commandName { get; }
int nearestControl { get; set; }
int hotControl { get; set; }
bool changed { get; set; }
int GetControlID(int hint, FocusType focusType);
void AddControl(int controlID, float distance);
bool Slider(int id, SliderData sliderData, out Vector3 newPosition);
void UseCurrentEvent();
void Repaint();
bool IsEventOutsideWindow();
bool IsViewToolActive();
bool HasCurrentCamera();
float GetHandleSize(Vector3 position);
float DistanceToSegment(Vector3 p1, Vector3 p2);
float DistanceToCircle(Vector3 center, float radius);
Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePos);
}
}

View File

@@ -0,0 +1,25 @@
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal struct LayoutData
{
public int index;
public float distance;
public Vector3 position;
public Vector3 forward;
public Vector3 up;
public Vector3 right;
public object userData;
public static readonly LayoutData zero = new LayoutData() { index = 0, distance = float.MaxValue, position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
public static LayoutData Nearest(LayoutData currentData, LayoutData newData)
{
if (newData.distance <= currentData.distance)
return newData;
return currentData;
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework
{
internal class SliderAction : ClickAction
{
private SliderData m_SliderData;
public Action<IGUIState, Control, Vector3> onSliderBegin;
public Action<IGUIState, Control, Vector3> onSliderChanged;
public Action<IGUIState, Control, Vector3> onSliderEnd;
public SliderAction(Control control) : base(control, 0, false)
{
}
protected override bool GetFinishContidtion(IGUIState guiState)
{
return guiState.eventType == EventType.MouseUp && guiState.mouseButton == 0;
}
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);
}
protected override void OnFinish(IGUIState guiState)
{
if (onSliderEnd != null)
onSliderEnd(guiState, hoveredControl, m_SliderData.position);
guiState.UseCurrentEvent();
guiState.Repaint();
}
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);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

View File

@@ -0,0 +1,7 @@
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface ISelectable<T>
{
bool Select(ISelector<T> selector);
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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);
}
}

View File

@@ -0,0 +1,7 @@
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface ISelector<T>
{
bool Select(T element);
}
}

View File

@@ -0,0 +1,13 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
[Serializable]
internal class IndexedSelection : SerializableSelection<int>
{
protected override int GetInvalidElement() { return -1; }
}
}

View File

@@ -0,0 +1,13 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal class PointRectSelector : RectSelector<Vector3>
{
protected override bool Select(Vector3 element)
{
return guiRect.Contains(HandleUtility.WorldToGUIPoint(element), true);
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal abstract class RectSelector<T> : ISelector<T>
{
public Action<ISelector<T>, bool> onSelectionBegin;
public Action<ISelector<T>> onSelectionChanged;
public Action<ISelector<T>> onSelectionEnd = null;
private GUISystem m_GUISystem;
private Control m_RectSelectorControl;
private GUIAction m_RectSelectAction;
private Vector3 m_RectStart;
private Vector3 m_RectEnd;
private Rect m_GUIRect;
private IDrawer m_Drawer = new Drawer();
public Rect guiRect
{
get { return m_GUIRect; }
}
public RectSelector() : this(new GUISystem(new GUIState())) {}
public RectSelector(GUISystem guiSystem)
{
m_GUISystem = guiSystem;
m_RectSelectorControl = new GenericDefaultControl("RectSelector")
{
position = (guiState) =>
{
return GUIToWorld(guiState, guiState.mousePosition);
},
forward = (guiState) =>
{
if (Camera.current)
return Camera.current.transform.forward;
return Vector3.forward;
},
right = (guiState) =>
{
if (Camera.current)
return Camera.current.transform.right;
return Vector3.right;
},
up = (guiState) =>
{
if (Camera.current)
return Camera.current.transform.up;
return Vector3.up;
}
};
m_RectSelectAction = new SliderAction(m_RectSelectorControl)
{
enableRepaint = (guiState, action) =>
{
var size = m_RectStart - m_RectEnd;
return size != Vector3.zero && guiState.hotControl == action.ID;
},
onClick = (guiState, control) =>
{
m_RectStart = GUIToWorld(guiState, guiState.mousePosition);
m_RectEnd = m_RectStart;
m_GUIRect = CalculateGUIRect();
},
onSliderBegin = (guiState, control, position) =>
{
m_RectEnd = position;
m_GUIRect = CalculateGUIRect();
if (onSelectionBegin != null)
onSelectionBegin(this, guiState.isShiftDown);
},
onSliderChanged = (guiState, control, position) =>
{
m_RectEnd = position;
m_GUIRect = CalculateGUIRect();
if (onSelectionChanged != null)
onSelectionChanged(this);
},
onSliderEnd = (guiState, control, position) =>
{
if (onSelectionEnd != null)
onSelectionEnd(this);
},
onRepaint = (guiState, action) =>
{
m_Drawer.DrawSelectionRect(m_GUIRect);
}
};
m_GUISystem.AddControl(m_RectSelectorControl);
m_GUISystem.AddAction(m_RectSelectAction);
}
private Vector3 GUIToWorld(IGUIState guiState, Vector2 guiPosition)
{
var forward = Vector3.forward;
if (guiState.HasCurrentCamera())
forward = Camera.current.transform.forward;
return guiState.GUIToWorld(guiPosition, forward, Vector3.zero);
}
private Rect CalculateGUIRect()
{
return FromToRect(HandleUtility.WorldToGUIPoint(m_RectStart), HandleUtility.WorldToGUIPoint(m_RectEnd));
}
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);
}
}

View File

@@ -0,0 +1,143 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
[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;
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal enum ShapeType
{
Polygon,
Spline
}
internal interface IShape
{
ShapeType type { get; }
bool isOpenEnded { get; }
ControlPoint[] ToControlPoints();
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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] };
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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
};
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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] };
}
}

View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
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);
}
}
}

View File

@@ -0,0 +1,141 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal class DefaultStyles
{
public readonly GUIStyle pointNormalStyle;
public readonly GUIStyle pointHoveredStyle;
public readonly GUIStyle pointSelectedStyle;
public readonly GUIStyle pointPreviewStyle;
public readonly GUIStyle pointRemovePreviewStyle;
public readonly GUIStyle tangentNormalStyle;
public readonly GUIStyle tangentHoveredStyle;
public readonly GUIStyle selectionRectStyle;
public DefaultStyles()
{
var pointNormal = Resources.Load<Texture2D>("Path/pointNormal");
var pointHovered = Resources.Load<Texture2D>("Path/pointHovered");
var pointSelected = Resources.Load<Texture2D>("Path/pointSelected");
var pointPreview = Resources.Load<Texture2D>("Path/pointPreview");
var pointRemovePreview = Resources.Load<Texture2D>("Path/pointRemovePreview");
var tangentNormal = Resources.Load<Texture2D>("Path/tangentNormal");
pointNormalStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointNormal"), Vector2.one * 12f);
pointHoveredStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointHovered"), Vector2.one * 12f);
pointSelectedStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointSelected"), Vector2.one * 12f);
pointPreviewStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointPreview"), Vector2.one * 12f);
pointRemovePreviewStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointRemovePreview"), Vector2.one * 12f);
tangentNormalStyle = CreateStyle(Resources.Load<Texture2D>("Path/tangentNormal"), Vector2.one * 8f);
tangentHoveredStyle = CreateStyle(Resources.Load<Texture2D>("Path/pointHovered"), Vector2.one * 10f);
selectionRectStyle = GUI.skin.FindStyle("selectionRect");
}
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;
}
}
internal class Drawer : IDrawer
{
private IGUIState m_GUIState = new GUIState();
private DefaultStyles m_Styles;
protected DefaultStyles styles
{
get
{
if (m_Styles == null)
m_Styles = new DefaultStyles();
return m_Styles;
}
}
public void DrawSelectionRect(Rect rect)
{
Handles.BeginGUI();
styles.selectionRectStyle.Draw(rect, GUIContent.none, false, false, false, false);
Handles.EndGUI();
}
public void DrawCreatePointPreview(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointPreviewStyle);
}
public void DrawRemovePointPreview(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointRemovePreviewStyle);
}
public void DrawPoint(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
}
public void DrawPointHovered(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointHoveredStyle);
}
public void DrawPointSelected(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointSelectedStyle);
}
public void DrawLine(Vector3 p1, Vector3 p2, float width, Color color)
{
Handles.color = color;
Handles.DrawAAPolyLine(width, new Vector3[] { p1, p2 });
}
public void DrawDottedLine(Vector3 p1, Vector3 p2, float width, Color color)
{
Handles.color = color;
Handles.DrawDottedLine(p1, p2, width);
}
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)
{
DrawLine(position, tangent, 3f, Color.yellow);
DrawGUIStyleCap(0, tangent, Quaternion.identity, m_GUIState.GetHandleSize(tangent), styles.tangentNormalStyle);
}
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);
}
}
}

View File

@@ -0,0 +1,489 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal class EditablePathView : IEditablePathView
{
const float kSnappingDistance = 15f;
const string kDeleteCommandName = "Delete";
const string kSoftDeleteCommandName = "SoftDelete";
public IEditablePathController controller { get; set; }
private Control m_PointControl;
private Control m_EdgeControl;
private Control m_LeftTangentControl;
private Control m_RightTangentControl;
private GUIAction m_MovePointAction;
private GUIAction m_MoveEdgeAction;
private GUIAction m_CreatePointAction;
private GUIAction m_RemovePointAction1;
private GUIAction m_RemovePointAction2;
private GUIAction m_MoveLeftTangentAction;
private GUIAction m_MoveRightTangentAction;
private IDrawer m_Drawer;
public EditablePathView() : this(new Drawer()) {}
public EditablePathView(IDrawer drawer)
{
m_Drawer = drawer;
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
};
m_EdgeControl = new GenericControl("Edge")
{
onEndLayout = (guiState) => { controller.AddClosestPath(m_EdgeControl.layoutData.distance); },
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_LeftTangentControl = new GenericControl("LeftTangent")
{
count = () =>
{
if (GetShapeType() != ShapeType.Spline)
return 0;
return GetPointCount();
},
distance = (guiState, i) =>
{
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 position = GetPoint(i).position;
var leftTangent = GetLeftTangent(i);
m_Drawer.DrawTangent(position, leftTangent);
}
};
m_RightTangentControl = new GenericControl("RightTangent")
{
count = () =>
{
if (GetShapeType() != ShapeType.Spline)
return 0;
return GetPointCount();
},
distance = (guiState, i) =>
{
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 position = GetPoint(i).position;
var rightTangent = GetRightTangent(i);
m_Drawer.DrawTangent(position, rightTangent);
}
};
m_CreatePointAction = new CreatePointAction(m_PointControl, m_EdgeControl)
{
enable = (guiState, action) => { return !guiState.isShiftDown && controller.closestEditablePath == controller.editablePath; },
enableRepaint = EnableCreatePointRepaint,
repaintOnMouseMove = (guiState, action) => { return 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);
m_Drawer.DrawCreatePointPreview(position);
}
}
};
Action<IGUIState> removePoints = (guiState) =>
{
controller.RegisterUndo("Remove Point");
controller.RemoveSelectedPoints();
guiState.changed = true;
};
m_RemovePointAction1 = new CommandAction(kDeleteCommandName)
{
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
onCommand = removePoints
};
m_RemovePointAction2 = new CommandAction(kSoftDeleteCommandName)
{
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
onCommand = removePoints
};
m_MovePointAction = new SliderAction(m_PointControl)
{
onClick = (guiState, control) =>
{
var index = control.layoutData.index;
if (!guiState.isActionKeyDown && !IsSelected(index))
controller.ClearSelection();
controller.SelectPoint(index, true);
guiState.changed = true;
},
onSliderBegin = (guiState, control, position) =>
{
controller.RegisterUndo("Move Point");
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var delta = SnapIfNeeded(position) - GetPoint(index).position;
controller.MoveSelectedPoints(delta);
}
};
m_MoveEdgeAction = new SliderAction(m_EdgeControl)
{
enable = (guiState, action) => { return guiState.isShiftDown; },
onSliderBegin = (guiState, control, position) =>
{
controller.RegisterUndo("Move Edge");
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var delta = position - GetPoint(index).position;
controller.MoveEdge(index, delta);
}
};
var cachedRightTangent = Vector3.zero;
var cachedLeftTangent = Vector3.zero;
m_MoveLeftTangentAction = new SliderAction(m_LeftTangentControl)
{
onSliderBegin = (guiState, control, position) =>
{
controller.RegisterUndo("Move Tangent");
cachedRightTangent = GetPoint(control.hotLayoutData.index).rightTangent;
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var setToLinear = guiState.nearestControl == m_PointControl.ID && m_PointControl.layoutData.index == index;
controller.SetLeftTangent(index, position, setToLinear, guiState.isShiftDown, cachedRightTangent);
},
onSliderEnd = (guiState, control, position) =>
{
controller.editablePath.UpdateTangentMode(control.hotLayoutData.index);
guiState.changed = true;
}
};
m_MoveRightTangentAction = new SliderAction(m_RightTangentControl)
{
onSliderBegin = (guiState, control, position) =>
{
controller.RegisterUndo("Move Tangent");
cachedLeftTangent = GetPoint(control.hotLayoutData.index).leftTangent;
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var setToLinear = guiState.nearestControl == m_PointControl.ID && m_PointControl.layoutData.index == index;
controller.SetRightTangent(index, position, setToLinear, guiState.isShiftDown, cachedLeftTangent);
},
onSliderEnd = (guiState, control, position) =>
{
controller.editablePath.UpdateTangentMode(control.hotLayoutData.index);
guiState.changed = true;
}
};
}
public void Install(GUISystem guiSystem)
{
guiSystem.AddControl(m_EdgeControl);
guiSystem.AddControl(m_PointControl);
guiSystem.AddControl(m_LeftTangentControl);
guiSystem.AddControl(m_RightTangentControl);
guiSystem.AddAction(m_CreatePointAction);
guiSystem.AddAction(m_RemovePointAction1);
guiSystem.AddAction(m_RemovePointAction2);
guiSystem.AddAction(m_MovePointAction);
guiSystem.AddAction(m_MoveEdgeAction);
guiSystem.AddAction(m_MoveLeftTangentAction);
guiSystem.AddAction(m_MoveRightTangentAction);
}
public void Uninstall(GUISystem guiSystem)
{
guiSystem.RemoveControl(m_EdgeControl);
guiSystem.RemoveControl(m_PointControl);
guiSystem.RemoveControl(m_LeftTangentControl);
guiSystem.RemoveControl(m_RightTangentControl);
guiSystem.RemoveAction(m_CreatePointAction);
guiSystem.RemoveAction(m_RemovePointAction1);
guiSystem.RemoveAction(m_RemovePointAction2);
guiSystem.RemoveAction(m_MovePointAction);
guiSystem.RemoveAction(m_MoveEdgeAction);
guiSystem.RemoveAction(m_MoveLeftTangentAction);
guiSystem.RemoveAction(m_MoveRightTangentAction);
}
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)
{
return controller.editablePath.CalculateLeftTangent(index);
}
private Vector3 GetRightTangent(int index)
{
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;
if (guiState.hotControl == control.actionID && control.hotLayoutData.index == index || IsSelected(index))
m_Drawer.DrawPointSelected(position);
else if (guiState.hotControl == 0 && guiState.nearestControl == control.ID && control.layoutData.index == index)
m_Drawer.DrawPointHovered(position);
else
m_Drawer.DrawPoint(position);
}
private void DrawEdge(IGUIState guiState, Control control, int index)
{
if (GetShapeType() == ShapeType.Polygon)
{
var nextIndex = NextIndex(index);
var color = Color.white;
if (guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0)
color = Color.yellow;
m_Drawer.DrawLine(GetPoint(index).position, GetPoint(nextIndex).position, 5f, color);
}
else if (GetShapeType() == ShapeType.Spline)
{
var nextIndex = NextIndex(index);
var color = Color.white;
if (guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0)
color = Color.yellow;
m_Drawer.DrawBezier(
GetPoint(index).position,
GetRightTangent(index),
GetLeftTangent(nextIndex),
GetPoint(nextIndex).position,
5f,
color);
}
}
private bool EnableCreatePointRepaint(IGUIState guiState, GUIAction action)
{
return guiState.nearestControl != m_PointControl.ID &&
guiState.hotControl == 0 &&
(guiState.nearestControl != m_LeftTangentControl.ID) &&
(guiState.nearestControl != m_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;
}
}
}

View File

@@ -0,0 +1,18 @@
using UnityEngine;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface IDrawer
{
void DrawSelectionRect(Rect rect);
void DrawCreatePointPreview(Vector3 position);
void DrawRemovePointPreview(Vector3 position);
void DrawPoint(Vector3 position);
void DrawPointHovered(Vector3 position);
void DrawPointSelected(Vector3 position);
void DrawLine(Vector3 p1, Vector3 p2, float width, Color color);
void DrawDottedLine(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);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
using UnityEditor.Experimental.Rendering.Universal.Path2D.GUIFramework;
namespace UnityEditor.Experimental.Rendering.Universal.Path2D
{
internal interface IEditablePathView
{
IEditablePathController controller { get; set; }
void Install(GUISystem guiSystem);
void Uninstall(GUISystem guiSystem);
}
}