private static void ShowHandles(Path2D aPathRaw, int aIndex, Matrix4x4 aTransform, Matrix4x4 aInvTransform, SerializedProperty aPath, bool aShowMeta, bool aUnlock, Path2D.Plane aPlane, Plane aEditPlane, PathEditorVisuals aVisuals) { PointControl ctrl = aPathRaw.GetControls(aIndex); bool locked = ctrl.type == PointType.Locked; Vector3 at = aTransform.MultiplyPoint3x4(Plane(aPathRaw[aIndex], aPlane)); if (ctrl.type == PointType.Auto || ctrl.type == PointType.AutoSymmetrical || ctrl.type == PointType.Sharp) { } else if (ctrl.type == PointType.CircleCorner) { if (aPathRaw.Closed || (aIndex != 0 && aIndex != aPathRaw.Count - 1)) { float size = HandleUtility.GetHandleSize(at); float radiusOffset = .2f; Vector3 end1 = PathUtil.GetRoundedCornerEnd(aIndex, aPathRaw.GetPathRaw(), aPathRaw.GetControls(), aPathRaw.Closed, ctrl.radius, true); Vector3 end2 = PathUtil.GetRoundedCornerEnd(aIndex, aPathRaw.GetPathRaw(), aPathRaw.GetControls(), aPathRaw.Closed, ctrl.radius, false); Vector3 normal = Vector2.Lerp(end1.xy() - aPathRaw[aIndex], end2.xy() - aPathRaw[aIndex], 0.5f).normalized; Vector3 v = aTransform.MultiplyVector(Plane(normal * (ctrl.radius + radiusOffset), aPlane)) + at; Vector3 nV = Handles.FreeMoveHandle(v, SceneView.lastActiveSceneView.camera.transform.rotation, size * aVisuals.sizeControlHandle, Vector3.zero, aVisuals.capControlHandle); if (nV != v) { SerializedProperty radius = aPath.FindPropertyRelative("_pointControls").GetArrayElementAtIndex(aIndex).FindPropertyRelative("radius"); nV = EditorTools.ProjectPoint(nV, aEditPlane); nV -= at; nV = aInvTransform.MultiplyVector(nV); Vector2 newPos = Deplane(nV, aPlane); radius.floatValue = SnapScale(radius.floatValue, Mathf.Max(0, newPos.magnitude - radiusOffset), PathSnap.World); _recentInteract = aIndex; GUI.changed = true; } if (aShowMeta) { string txt = Math.Round(ctrl.radius, 2).ToString(); Handles.Label(v, txt, ShadowStyle); Handles.Label(v, txt, LabelStyle); } } } else { Vector2 newPos; Vector3 v = aTransform.MultiplyVector(Plane(ctrl.controlPrev, aPlane)) + at; Vector3 nV = Handles.FreeMoveHandle(v, SceneView.lastActiveSceneView.camera.transform.rotation, HandleUtility.GetHandleSize(v) * aVisuals.sizeControlHandle, Vector3.zero, aVisuals.capControlHandle); if (nV != v) { SerializedProperty controlProp = aPath.FindPropertyRelative("_pointControls").GetArrayElementAtIndex(aIndex); SerializedProperty type = controlProp.FindPropertyRelative("type"); SerializedProperty prev = controlProp.FindPropertyRelative("controlPrev"); nV = EditorTools.ProjectPoint(nV, aEditPlane); nV -= at; nV = aInvTransform.MultiplyVector(nV); newPos = Deplane(nV, aPlane); prev.vector2Value = SnapRadial(v, prev.vector2Value, newPos, aTransform, aInvTransform, PathSnap.World); if (aUnlock) { ctrl.type = PointType.Free; type.enumValueIndex = (int)ctrl.type; } _recentInteract = aIndex; GUI.changed = true; } if (ctrl.type == PointType.Locked) { v = -aTransform.MultiplyVector(Plane(ctrl.controlPrev, aPlane)) + at; } else { v = aTransform.MultiplyVector(Plane(ctrl.controlNext, aPlane)) + at; } nV = Handles.FreeMoveHandle(v, SceneView.lastActiveSceneView.camera.transform.rotation, HandleUtility.GetHandleSize(v) * aVisuals.sizeControlHandle, Vector3.zero, aVisuals.capControlHandle); if (nV != v) { SerializedProperty controlProp = aPath.FindPropertyRelative("_pointControls").GetArrayElementAtIndex(aIndex); SerializedProperty type = controlProp.FindPropertyRelative("type"); SerializedProperty prev = controlProp.FindPropertyRelative("controlPrev"); SerializedProperty next = controlProp.FindPropertyRelative("controlNext"); nV = EditorTools.ProjectPoint(nV, aEditPlane); nV -= at; nV = aInvTransform.MultiplyVector(nV); if (aUnlock) { ctrl.type = PointType.Free; type.enumValueIndex = (int)ctrl.type; } if (ctrl.type == PointType.Locked) { newPos = Deplane(-nV, aPlane); prev.vector2Value = SnapRadial(v, prev.vector2Value, newPos, aTransform, aInvTransform, PathSnap.World); } else { newPos = Deplane(nV, aPlane); next.vector2Value = SnapRadial(v, next.vector2Value, newPos, aTransform, aInvTransform, PathSnap.World); } _recentInteract = aIndex; GUI.changed = true; } if (aShowMeta) { bool xz = aPlane == Path2D.Plane.XZ; Vector2 prop2 = (locked?ctrl.controlPrev:ctrl.controlNext); Vector3 handle1 = aTransform.MultiplyVector(Plane(ctrl.controlPrev, aPlane)) + at; Vector3 handle2 = (locked?-1:1) * aTransform.MultiplyVector(Plane((locked?ctrl.controlPrev:ctrl.controlNext), aPlane)) + at; float ang1 = PathUtil.ClockwiseAngle(ctrl.controlPrev, Vector2.right); //Vector2.Angle(prev.vector2Value, Vector2.right); float ang2 = PathUtil.ClockwiseAngle((locked?-1:1) * prop2, Vector2.right); //Vector2.Angle(prop2.vector2Value, Vector2.right); float mag1 = ctrl.controlPrev.magnitude; float mag2 = prop2.magnitude; Vector3 pos = Vector3.Lerp(handle1, at, 0.5f); string txt = Math.Round(mag1, 2).ToString(); Handles.Label(pos, txt, ShadowStyle); Handles.Label(pos, txt, LabelStyle); pos = Vector3.Lerp(handle2, at, 0.5f); txt = Math.Round(mag2, 2).ToString(); Handles.Label(pos, txt, ShadowStyle); Handles.Label(pos, txt, LabelStyle); pos = handle1; txt = "\u00B0" + Mathf.Round(ang1); Handles.Label(pos, txt, ShadowStyle); Handles.Label(pos, txt, LabelStyle); pos = handle2; txt = "\u00B0" + Mathf.Round(ang2); Handles.Label(pos, txt, ShadowStyle); Handles.Label(pos, txt, LabelStyle); if (ctrl.type == PointType.Free) { if (ang2 < ang1) { ang2 += 360; } float ang = ang2 - ang1; float halfAng = ang1 + ang / 2; float arcRadius = HandleUtility.GetHandleSize(at) * aVisuals.sizeVertex * 1.75f; Vector3 centerArc = new Vector3(Mathf.Cos(halfAng * Mathf.Deg2Rad), Mathf.Sin(halfAng * Mathf.Deg2Rad), 0); if (xz) { centerArc = new Vector3(centerArc.x, 0, centerArc.y); } centerArc = aTransform.MultiplyVector(centerArc); var centeredStyle = new GUIStyle(GUI.skin.label); centeredStyle.contentOffset = new Vector2(-9, -9); centeredStyle.normal.textColor = new Color(0, 0, 0, 0.5f); Handles.Label(at + centerArc * arcRadius * 2f, "\u00B0" + Mathf.Round(ang), centeredStyle); centeredStyle.contentOffset = new Vector2(-10, -10); centeredStyle.normal.textColor = Color.white; Handles.Label(at + centerArc * arcRadius * 2f, "\u00B0" + Mathf.Round(ang), centeredStyle); Handles.DrawWireArc(at, aPlane == Path2D.Plane.XY?Vector3.forward:Vector3.up, ((xz?handle2:handle1) - at).normalized, ang, arcRadius); } } } }
public static List <Vector3> GetAllHandleLocations(Matrix4x4 aTransform, Path2D aPathRaw, bool aShowShiftAdd = true, Path2D.Plane aPlane = Path2D.Plane.XY) { List <Vector3> result = new List <Vector3>(); List <Vector2> points = aPathRaw.GetPathRaw(); List <PointControl> controls = aPathRaw.GetControls(); bool showControls = !Event.current.shift && !Event.current.alt; // add all points List <Vector3> worldPoints = PathUtil.To3D(points, aTransform, aPlane == Path2D.Plane.XY ? PathUtil.ConvertOptions.XY : PathUtil.ConvertOptions.XZ); result.AddRange(worldPoints); // add the control points if (showControls) { for (int i = 0; i < points.Count; i++) { PointControl ctrl = controls[i]; PointType t = ctrl.type; Vector3 at = worldPoints[i]; if (t == PointType.CircleCorner) { Vector3 end1 = PathUtil.GetRoundedCornerEnd(i, points, controls, aPathRaw.Closed, ctrl.radius, true); Vector3 end2 = PathUtil.GetRoundedCornerEnd(i, points, controls, aPathRaw.Closed, ctrl.radius, false); Vector3 normal = Vector2.Lerp(end1.xy() - points[i], end2.xy() - points[i], 0.5f).normalized; result.Add(at + aTransform.MultiplyVector(Plane(normal * (ctrl.radius + 0.2f), aPlane))); } else if (t == PointType.Free || t == PointType.Locked) { Vector3 v = aTransform.MultiplyVector(Plane(ctrl.controlPrev, aPlane)) + at; result.Add(v); if (t == PointType.Locked) { v = -aTransform.MultiplyVector(Plane(ctrl.controlPrev, aPlane)) + at; } else { v = aTransform.MultiplyVector(Plane(ctrl.controlNext, aPlane)) + at; } result.Add(v); } } } // create the plane on which all edits occur Plane editPlane; if (aPlane == Path2D.Plane.XY) { editPlane = new Plane(aTransform.MultiplyPoint3x4(Vector3.zero), aTransform.MultiplyPoint3x4(Vector3.right), aTransform.MultiplyPoint3x4(Vector3.up)); } else { editPlane = new Plane(aTransform.MultiplyPoint3x4(Vector3.zero), aTransform.MultiplyPoint3x4(Vector3.right), aTransform.MultiplyPoint3x4(Vector3.forward)); } // add shift-add handle if (Event.current.shift && aShowShiftAdd) { Ray r = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); float dist = 0; if (editPlane.Raycast(r, out dist)) { result.Add(r.GetPoint(dist)); } } return(result); }
private static void ShowShiftAdd(Path2D aPathRaw, Matrix4x4 aTransform, Matrix4x4 aInvTransform, SerializedProperty aPath, Action <SerializedProperty, int> aOnAddPoint, Path2D.Plane aPlane, Plane aEditPlane, PathEditorVisuals aVisuals) { if (Event.current.type == EventType.MouseMove) { SceneView.RepaintAll(); } Ray r = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); float dist = 0; if (aEditPlane.Raycast(r, out dist)) { Vector3 worldPt = r.GetPoint(dist); Vector3 localPt = aInvTransform.MultiplyPoint3x4(worldPt); List <Vector2> points = aPathRaw.GetPathRaw(); int segment = PathUtil.GetClosestSegment(points, Deplane(localPt, aPlane), aPathRaw.Closed); Vector3 p1 = points.Count == 0 ? worldPt : aTransform.MultiplyPoint3x4(Plane(points[segment], aPlane)); Vector3 p2 = points.Count == 0 ? worldPt : aTransform.MultiplyPoint3x4(Plane(points[PathUtil.WrapIndex(segment + 1, points.Count, aPathRaw.Closed)], aPlane)); float d1 = Vector3.Distance(p1, worldPt); float d2 = Vector3.Distance(p2, worldPt); int insertIndex = 0; EditorGUIUtility.AddCursorRect(new Rect(0, 0, Screen.width, Screen.height), MouseCursor.ArrowPlus); // draw dotted lines, and figure out what index we'll be adding to if (!aPathRaw.Closed && segment == 0 && d1 < d2) { Handles.DrawDottedLine(p1, worldPt, 4); insertIndex = 0; } else if (!aPathRaw.Closed && segment == points.Count - 2 && d2 < d1) { Handles.DrawDottedLine(p2, worldPt, 4); insertIndex = points.Count; } else { Handles.DrawDottedLine(p1, worldPt, 4); Handles.DrawDottedLine(p2, worldPt, 4); insertIndex = segment + 1; } // do the add point button if (Handles.Button(worldPt, SceneView.lastActiveSceneView.camera.transform.rotation, HandleUtility.GetHandleSize(worldPt) * aVisuals.sizeVertexAdd, HandleUtility.GetHandleSize(worldPt) * aVisuals.sizeVertexAdd, aVisuals.capVertexAdd)) { AddPoint(aPath, Deplane(localPt, aPlane), insertIndex); if (aOnAddPoint != null) { aOnAddPoint(aPath, insertIndex); } _recentInteract = insertIndex; GUI.changed = true; } } }
public static void DoDragSelect(Matrix4x4 aTransform, Path2D aPath, Rect aValidDragArea, PathEditorVisuals aVisuals) { if (aVisuals == null) { aVisuals = _defaultVisuals; } int id = GUIUtility.GetControlID("PathEditorDragSelect".GetHashCode(), FocusType.Keyboard); Event curr = Event.current; // get the current selection list Selection selection = GetSelection(aPath); Vector2 mouse = curr.mousePosition; EventType type = curr.GetTypeForControl(id); switch (type) { case EventType.MouseDown: if (curr.button == 0 && !curr.alt && !curr.control && aValidDragArea.Contains(mouse)) { GUIUtility.hotControl = id; _isMouseDown = true; _mouseDownPos = curr.mousePosition; curr.Use(); } break; case EventType.MouseUp: if (curr.button == 0) { _isMouseDown = false; } if (curr.button == 0 && GUIUtility.hotControl == id && curr.mousePosition != _mouseDownPos) { selection.ids.Clear(); for (int i = 0; i < aPath.Count; i += 1) { float left = Mathf.Min(_mouseDownPos.x, _mouseDownPos.x + (mouse.x - _mouseDownPos.x)); float right = Mathf.Max(_mouseDownPos.x, _mouseDownPos.x + (mouse.x - _mouseDownPos.x)); float top = Mathf.Min(_mouseDownPos.y, _mouseDownPos.y + (mouse.y - _mouseDownPos.y)); float bottom = Mathf.Max(_mouseDownPos.y, _mouseDownPos.y + (mouse.y - _mouseDownPos.y)); Rect r = new Rect(left, top, right - left, bottom - top); if (r.Contains(HandleUtility.WorldToGUIPoint(aTransform.MultiplyPoint(aPath[i])))) { selection.ids.Add(i); } } } if (curr.button == 0 && GUIUtility.hotControl == id) { GUIUtility.hotControl = 0; curr.Use(); } // if the mouse hasn't moved, emulate Unity's default mouse behaviour if (curr.mousePosition == _mouseDownPos) { if (selection.Count > 0) { selection.ids.Clear(); SceneView.RepaintAll(); } else { GameObject go = HandleUtility.PickGameObject(curr.mousePosition, false); UnityEditor.Selection.activeGameObject = go; } } break; case EventType.MouseDrag: if (_isMouseDown && GUIUtility.hotControl == id) { SceneView.RepaintAll(); GUI.changed = true; curr.Use(); } break; case EventType.Repaint: if (curr.button == 0 && !curr.alt && !curr.control && _isMouseDown) { Vector3 pt1 = HandleUtility.GUIPointToWorldRay(_mouseDownPos).GetPoint(0.2f); Vector3 pt2 = HandleUtility.GUIPointToWorldRay(mouse).GetPoint(0.2f); Vector3 pt3 = HandleUtility.GUIPointToWorldRay(new Vector2(_mouseDownPos.x, mouse.y)).GetPoint(0.2f); Vector3 pt4 = HandleUtility.GUIPointToWorldRay(new Vector2(mouse.x, _mouseDownPos.y)).GetPoint(0.2f); Handles.DrawSolidRectangleWithOutline(new Vector3[] { pt1, pt3, pt2, pt4 }, aVisuals.colorDragSelectInner, aVisuals.colorDragSelectOuter); } break; } }
public static void OnSceneGUI(Matrix4x4 aTransform, Matrix4x4 aInvTransform, SerializedProperty aPath, Path2D aPathRaw, bool aShowShiftAdd = true, Action <SerializedProperty, int> aOnAddPoint = null, Action <SerializedProperty, int> aOnRemovePoint = null, Path2D.Plane aPlane = Path2D.Plane.XY, PathSnap aSnapMode = PathSnap.Unity, float aSmartSnapDist = 0, KeyCode aVertModeKey = KeyCode.C, PathEditorVisuals aVisuals = null) { if (aVisuals == null) { aVisuals = _defaultVisuals; } // get the current selection list Selection selection = GetSelection(aPathRaw); bool showControls = !Event.current.shift && !Event.current.alt; // check for point type switching mode if (Event.current.type == EventType.KeyDown && Event.current.keyCode == aVertModeKey) { _vMode = true; Event.current.Use(); } if (Event.current.type == EventType.KeyUp && Event.current.keyCode == aVertModeKey) { _vMode = false; Event.current.Use(); } bool deleteMode = Event.current.alt; // draw all the curve and handle lines if (Event.current.type == EventType.Repaint) { ShowPathLines(aPathRaw, aTransform, showControls, aPlane, aVisuals); } // create the plane on which all edits occur Plane editPlane; if (aPlane == Path2D.Plane.XY) { editPlane = new Plane(aTransform.MultiplyPoint3x4(Vector3.zero), aTransform.MultiplyPoint3x4(Vector3.right), aTransform.MultiplyPoint3x4(Vector3.up)); } else { editPlane = new Plane(aTransform.MultiplyPoint3x4(Vector3.zero), aTransform.MultiplyPoint3x4(Vector3.right), aTransform.MultiplyPoint3x4(Vector3.forward)); } // check for shift-add point to path if (Event.current.shift && aShowShiftAdd && Event.current.button != 1 && !Event.current.control) { ShowShiftAdd(aPathRaw, aTransform, aInvTransform, aPath, aOnAddPoint, aPlane, editPlane, aVisuals); } // draw the handles for each point for (int i = 0; i < aPathRaw.Count; i++) { if (selection.Count > 0) { _activeHandleTint = selection.IsSelected(i) ? aVisuals.colorSelectionTint : aVisuals.colorUnselectedTint; } else { _activeHandleTint = aVisuals.colorNoSelectionTint; } if (deleteMode) { ShowPointDeleteMode(aPathRaw, i, selection, aTransform, aPath, aOnRemovePoint, aPlane, aVisuals); } else if (_vMode) { ShowPointTypeMode(aPathRaw, i, selection, aTransform, aInvTransform, aPath, aPlane, aVisuals); } else { ShowPoint(aPathRaw, i, selection, aTransform, aInvTransform, aPath, aPlane, editPlane, aSnapMode, aSmartSnapDist, aVisuals); } if (showControls) { ShowHandles(aPathRaw, i, aTransform, aInvTransform, aPath, i == _recentInteract, _vMode, aPlane, editPlane, aVisuals); } } if (GUI.changed) { SetDirty(aPath); } Handles.color = Color.white; }
public Path2D BoolAdd(Path2D aOther) { if (!(Closed && aOther.Closed) || SelfIntersecting() || aOther.SelfIntersecting()) { if (!Closed || !aOther.Closed) { Debug.LogWarning("Paths must be closed for boolean operations!"); } if (SelfIntersecting()) { Debug.LogWarning("This path is self-intersecting!"); } if (aOther.SelfIntersecting()) { Debug.LogWarning("Other path is self-intersecting!"); } Path2D r = new Path2D(); r.Closed = true; return(r); } if (!IsClockwise()) { ReverseSelf(); } if (!aOther.IsClockwise()) { aOther.ReverseSelf(); } // find a point that is not within the other path int id = -1; for (int i = 0; i < _points.Count; i++) { if (!aOther.Contains(_points[i])) { id = i; break; } } // if it's entirely enclosed, then the other path is the result if (id == -1) { return(new Path2D(aOther)); } int currId = id; List <Vector2> currPath = _points; List <Vector2> otherPath = aOther._points; Vector2 start = currPath[currId]; Vector2 curr = start; int ignoreSegment = -1; Path2D result = new Path2D(); result.Closed = true; result.Add(start); int escape = (currPath.Count + otherPath.Count) * 2; while (!(curr == start && result.Count > 1)) { int nextId = (currId + 1) % currPath.Count; Vector2 next = currPath[nextId]; float dist = float.MaxValue; Vector2 nextPt = next; bool swapPaths = false; for (int i = 0; i < otherPath.Count; i++) { if (i == ignoreSegment) { continue; } Vector2 o1 = otherPath[i]; Vector2 o2 = otherPath[(i + 1) % otherPath.Count]; if (PathUtil.LineSegmentIntersection(curr, next, o1, o2)) { Vector2 pt = PathUtil.LineIntersectionPoint(curr, next, o1, o2); float mag = (curr - pt).sqrMagnitude; if (mag < dist) { dist = mag; nextPt = pt; swapPaths = true; nextId = i; } } } ignoreSegment = -1; result.Add(nextPt); if (swapPaths) { List <Vector2> t = currPath; currPath = otherPath; otherPath = t; ignoreSegment = currId; } currId = nextId; curr = nextPt; escape--; if (escape <= 0) { break; } } return(result); }
public void CopySettings(Path2D aCopyPath) { _closed = aCopyPath.Closed; _splitDistance = aCopyPath.SmoothSplitDistance; SetDirty(); }