422 lines
17 KiB
C#
422 lines
17 KiB
C#
|
namespace Dreamteck.Splines.Editor
|
||
|
{
|
||
|
using UnityEngine;
|
||
|
using UnityEditor;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
public class CreatePointModule : PointModule
|
||
|
{
|
||
|
public enum AppendMode { Beginning = 0, End = 1}
|
||
|
public enum PlacementMode { YPlane, XPlane, ZPlane, CameraPlane, Surface, Insert }
|
||
|
public enum NormalMode { Default, LookAtCamera, AlignWithCamera, Calculate, Left, Right, Up, Down, Forward, Back }
|
||
|
protected PlacementMode placementMode = PlacementMode.YPlane;
|
||
|
public AppendMode appendMode = AppendMode.End;
|
||
|
public float offset = 0f;
|
||
|
public NormalMode normalMode = NormalMode.Default;
|
||
|
public LayerMask surfaceLayerMask = new LayerMask();
|
||
|
public float createPointSize = 1f;
|
||
|
public Color createPointColor = Color.white;
|
||
|
protected Spline visualizer;
|
||
|
protected Camera editorCamera;
|
||
|
protected Vector3 createPoint = Vector3.zero, createNormal = Vector3.up;
|
||
|
protected SplineSample evalResult = new SplineSample();
|
||
|
protected int lastCreated = -1;
|
||
|
|
||
|
public CreatePointModule(SplineEditor editor) : base(editor)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
public override GUIContent GetIconOff()
|
||
|
{
|
||
|
return IconContent("+", "add", "Add Points");
|
||
|
}
|
||
|
|
||
|
public override GUIContent GetIconOn()
|
||
|
{
|
||
|
return IconContent("+", "add_on", "Add Points");
|
||
|
}
|
||
|
|
||
|
public override void LoadState()
|
||
|
{
|
||
|
base.LoadState();
|
||
|
normalMode = (NormalMode)LoadInt("normalMode");
|
||
|
placementMode = (PlacementMode)LoadInt("placementMode");
|
||
|
appendMode = (AppendMode)LoadInt("appendMode", 1);
|
||
|
offset = LoadFloat("offset");
|
||
|
surfaceLayerMask = LoadInt("surfaceLayerMask", ~0);
|
||
|
}
|
||
|
|
||
|
public override void SaveState()
|
||
|
{
|
||
|
base.SaveState();
|
||
|
SaveInt("normalMode", (int)normalMode);
|
||
|
SaveInt("placementMode", (int)placementMode);
|
||
|
SaveInt("appendMode", (int)appendMode);
|
||
|
SaveFloat("offset", offset);
|
||
|
SaveInt("surfaceLayerMask", surfaceLayerMask);
|
||
|
}
|
||
|
|
||
|
public override void Deselect()
|
||
|
{
|
||
|
base.Deselect();
|
||
|
GUIUtility.hotControl = -1;
|
||
|
if (Event.current != null)
|
||
|
{
|
||
|
Event.current.Use();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnDrawInspector()
|
||
|
{
|
||
|
placementMode = (PlacementMode)EditorGUILayout.EnumPopup("Placement Mode", placementMode);
|
||
|
if (placementMode != PlacementMode.Insert)
|
||
|
{
|
||
|
normalMode = (NormalMode)EditorGUILayout.EnumPopup("Normal Mode", normalMode);
|
||
|
appendMode = (AppendMode)EditorGUILayout.EnumPopup("Append To", appendMode);
|
||
|
}
|
||
|
string offsetLabel = "Grid Offset";
|
||
|
if (placementMode == PlacementMode.CameraPlane) offsetLabel = "Far Plane";
|
||
|
if (placementMode == PlacementMode.Surface) offsetLabel = "Surface Offset";
|
||
|
offset = EditorGUILayout.FloatField(offsetLabel, offset);
|
||
|
if (placementMode == PlacementMode.Surface)
|
||
|
{
|
||
|
surfaceLayerMask = DreamteckEditorGUI.LayermaskField("Surface Mask", surfaceLayerMask);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnDrawScene()
|
||
|
{
|
||
|
editorCamera = SceneView.currentDrawingSceneView.camera;
|
||
|
bool canCreate = false;
|
||
|
if (placementMode == PlacementMode.CameraPlane)
|
||
|
{
|
||
|
GetCreatePointOnPlane(-editorCamera.transform.forward, editorCamera.transform.position + editorCamera.transform.forward * offset, out createPoint);
|
||
|
Handles.color = new Color(1f, 0.78f, 0.12f);
|
||
|
DrawGrid(createPoint, editorCamera.transform.forward, Vector2.one * 10, 2.5f);
|
||
|
Handles.color = Color.white;
|
||
|
canCreate = true;
|
||
|
createNormal = -editorCamera.transform.forward;
|
||
|
}
|
||
|
|
||
|
if (placementMode == PlacementMode.Surface)
|
||
|
{
|
||
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||
|
RaycastHit hit;
|
||
|
if (Physics.Raycast(ray, out hit, Mathf.Infinity, surfaceLayerMask))
|
||
|
{
|
||
|
canCreate = true;
|
||
|
createPoint = hit.point + hit.normal * offset;
|
||
|
Handles.color = Color.blue;
|
||
|
Handles.DrawLine(hit.point, createPoint);
|
||
|
SplineEditorHandles.DrawRectangle(createPoint, Quaternion.LookRotation(-editorCamera.transform.forward, editorCamera.transform.up), HandleUtility.GetHandleSize(createPoint) * 0.1f);
|
||
|
Handles.color = Color.white;
|
||
|
createNormal = hit.normal;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (placementMode == PlacementMode.XPlane)
|
||
|
{
|
||
|
canCreate = AxisGrid(Vector3.right, new Color(0.85f, 0.24f, 0.11f, 0.92f), out createPoint);
|
||
|
createNormal = Vector3.right;
|
||
|
}
|
||
|
|
||
|
if (placementMode == PlacementMode.YPlane)
|
||
|
{
|
||
|
canCreate = AxisGrid(Vector3.up, new Color(0.6f, 0.95f, 0.28f, 0.92f), out createPoint);
|
||
|
createNormal = Vector3.up;
|
||
|
}
|
||
|
|
||
|
if (placementMode == PlacementMode.ZPlane)
|
||
|
{
|
||
|
canCreate = AxisGrid(Vector3.forward, new Color(0.22f, 0.47f, 0.97f, 0.92f), out createPoint);
|
||
|
createNormal = Vector3.back;
|
||
|
}
|
||
|
|
||
|
if (placementMode == PlacementMode.Insert)
|
||
|
{
|
||
|
canCreate = true;
|
||
|
if (points.Length < 2)
|
||
|
{
|
||
|
placementMode = PlacementMode.YPlane;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
InsertMode(Event.current.mousePosition);
|
||
|
}
|
||
|
}
|
||
|
else if (eventModule.mouseLeftDown && canCreate && !eventModule.mouseRight && !eventModule.alt)
|
||
|
{
|
||
|
CreateSplinePoint(createPoint, createNormal);
|
||
|
}
|
||
|
|
||
|
if (lastCreated >= 0 && lastCreated < points.Length && editor.eventModule.mouseLeft)
|
||
|
{
|
||
|
Vector3 tangent = points[lastCreated].position - createPoint;
|
||
|
if (appendMode == AppendMode.End)
|
||
|
{
|
||
|
tangent = createPoint - points[lastCreated].position;
|
||
|
}
|
||
|
points[lastCreated].SetTangent2Position(points[lastCreated].position + tangent);
|
||
|
RegisterChange();
|
||
|
}
|
||
|
else if (!editor.eventModule.mouseLeft)
|
||
|
{
|
||
|
lastCreated = -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!canCreate) DrawMouseCross();
|
||
|
UpdateVisualizer();
|
||
|
SplineDrawer.DrawSpline(visualizer, color);
|
||
|
Repaint();
|
||
|
}
|
||
|
|
||
|
protected virtual void CreateSplinePoint(Vector3 position, Vector3 normal)
|
||
|
{
|
||
|
GUIUtility.hotControl = -1;
|
||
|
AddPoint();
|
||
|
}
|
||
|
|
||
|
protected void AddPoint()
|
||
|
{
|
||
|
SplinePoint newPoint = new SplinePoint(createPoint, createPoint);
|
||
|
newPoint.size = createPointSize;
|
||
|
newPoint.color = createPointColor;
|
||
|
SplinePoint[] newPoints = editor.GetPointsArray();
|
||
|
if (appendMode == AppendMode.End)
|
||
|
{
|
||
|
Dreamteck.ArrayUtility.Add(ref newPoints, newPoint);
|
||
|
lastCreated = newPoints.Length - 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Dreamteck.ArrayUtility.Insert(ref newPoints, 0, newPoint);
|
||
|
lastCreated = 0;
|
||
|
}
|
||
|
|
||
|
editor.SetPointsArray(newPoints);
|
||
|
SetPointNormal(lastCreated, createNormal);
|
||
|
SelectPoint(lastCreated);
|
||
|
RegisterChange();
|
||
|
}
|
||
|
|
||
|
protected void SetPointNormal(int index, Vector3 defaultNormal)
|
||
|
{
|
||
|
if (editor.is2D)
|
||
|
{
|
||
|
points[index].normal = Vector3.back;
|
||
|
return;
|
||
|
}
|
||
|
if (normalMode == NormalMode.Default) points[index].normal = defaultNormal;
|
||
|
else
|
||
|
{
|
||
|
Camera editorCamera = SceneView.lastActiveSceneView.camera;
|
||
|
switch (normalMode)
|
||
|
{
|
||
|
case NormalMode.AlignWithCamera: points[index].normal = editorCamera.transform.forward; break;
|
||
|
case NormalMode.LookAtCamera: points[index].normal = Vector3.Normalize(editorCamera.transform.position - points[index].position); break;
|
||
|
case NormalMode.Calculate: PointNormalModule.CalculatePointNormal(points, index, isClosed); break;
|
||
|
case NormalMode.Left: points[index].normal = Vector3.left; break;
|
||
|
case NormalMode.Right: points[index].normal = Vector3.right; break;
|
||
|
case NormalMode.Up: points[index].normal = Vector3.up; break;
|
||
|
case NormalMode.Down: points[index].normal = Vector3.down; break;
|
||
|
case NormalMode.Forward: points[index].normal = Vector3.forward; break;
|
||
|
case NormalMode.Back: points[index].normal = Vector3.back; break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void InsertMode(Vector3 screenCoordinates)
|
||
|
{
|
||
|
|
||
|
double percent = ProjectScreenSpace(screenCoordinates);
|
||
|
editor.evaluate(percent, ref evalResult);
|
||
|
if (editor.eventModule.mouseRight)
|
||
|
{
|
||
|
SplineEditorHandles.DrawCircle(evalResult.position, Quaternion.LookRotation(editorCamera.transform.position - evalResult.position), HandleUtility.GetHandleSize(evalResult.position) * 0.2f);
|
||
|
return;
|
||
|
}
|
||
|
if (SplineEditorHandles.CircleButton(evalResult.position, Quaternion.LookRotation(editorCamera.transform.position - evalResult.position), HandleUtility.GetHandleSize(evalResult.position) * 0.2f, 1.5f, color))
|
||
|
{
|
||
|
SplinePoint newPoint = new SplinePoint(evalResult.position, evalResult.position);
|
||
|
newPoint.size = evalResult.size;
|
||
|
newPoint.color = evalResult.color;
|
||
|
newPoint.normal = evalResult.up;
|
||
|
double floatIndex = (points.Length - 1) * percent;
|
||
|
int pointIndex = Mathf.Clamp(DMath.FloorInt(floatIndex), 0, points.Length - 2);
|
||
|
editor.AddPointAt(pointIndex + 1);
|
||
|
points[pointIndex + 1].SetPoint(newPoint);
|
||
|
SelectPoint(pointIndex);
|
||
|
RegisterChange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected double ProjectScreenSpace(Vector2 screenPoint)
|
||
|
{
|
||
|
float closestDistance = (screenPoint - HandleUtility.WorldToGUIPoint(points[0].position)).sqrMagnitude;
|
||
|
double closestPercent = 0.0;
|
||
|
double moveStep = 1.0 / ((editor.points.Length - 1) * sampleRate);
|
||
|
double add = moveStep;
|
||
|
if (splineType == Spline.Type.Linear) add /= 2.0;
|
||
|
int count = 0;
|
||
|
for (double i = add; i < 1.0; i += add)
|
||
|
{
|
||
|
editor.evaluate(i, ref evalResult);
|
||
|
Vector2 point = HandleUtility.WorldToGUIPoint(evalResult.position);
|
||
|
float dist = (point - screenPoint).sqrMagnitude;
|
||
|
if (dist < closestDistance)
|
||
|
{
|
||
|
closestDistance = dist;
|
||
|
closestPercent = i;
|
||
|
}
|
||
|
count++;
|
||
|
}
|
||
|
return closestPercent;
|
||
|
}
|
||
|
|
||
|
bool GetCreatePointOnPlane(Vector3 normal, Vector3 origin, out Vector3 result)
|
||
|
{
|
||
|
Plane plane = new Plane(normal, origin);
|
||
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||
|
float rayDistance;
|
||
|
if (plane.Raycast(ray, out rayDistance))
|
||
|
{
|
||
|
result = ray.GetPoint(rayDistance);
|
||
|
return true;
|
||
|
}
|
||
|
else if (normal == Vector3.zero)
|
||
|
{
|
||
|
result = origin;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = ray.GetPoint(0f);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool AxisGrid(Vector3 axis, Color color, out Vector3 origin)
|
||
|
{
|
||
|
float dot = Vector3.Dot(editorCamera.transform.position.normalized, axis);
|
||
|
if (dot < 0f) axis = -axis;
|
||
|
Plane plane = new Plane(axis, Vector3.zero);
|
||
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||
|
float rayDistance;
|
||
|
if (plane.Raycast(ray, out rayDistance))
|
||
|
{
|
||
|
origin = ray.GetPoint(rayDistance) + axis * offset;
|
||
|
Handles.color = color;
|
||
|
float distance = 1f;
|
||
|
ray = new Ray(editorCamera.transform.position, -axis);
|
||
|
if (!editorCamera.orthographic && plane.Raycast(ray, out rayDistance)) distance = Vector3.Distance(editorCamera.transform.position + axis * offset, origin);
|
||
|
else if (editorCamera.orthographic) distance = 2f * editorCamera.orthographicSize;
|
||
|
DrawGrid(origin, axis, Vector2.one * distance * 0.3f, distance * 2.5f * 0.03f);
|
||
|
Handles.DrawLine(origin, origin - axis * offset);
|
||
|
Handles.color = Color.white;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
origin = Vector3.zero;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DrawGrid(Vector3 center, Vector3 normal, Vector2 size, float scale)
|
||
|
{
|
||
|
Vector3 right = Vector3.Cross(Vector3.up, normal).normalized;
|
||
|
if (Mathf.Abs(Vector3.Dot(Vector3.up, normal)) >= 0.9999f) right = Vector3.Cross(Vector3.forward, normal).normalized;
|
||
|
Vector3 up = Vector3.Cross(normal, right).normalized;
|
||
|
Vector3 startPoint = center - right * size.x * 0.5f + up * size.y * 0.5f;
|
||
|
float i = 0f;
|
||
|
float add = scale;
|
||
|
while (i <= size.x)
|
||
|
{
|
||
|
Vector3 point = startPoint + right * i;
|
||
|
Handles.DrawLine(point, point - up * size.y);
|
||
|
i += add;
|
||
|
}
|
||
|
|
||
|
i = 0f;
|
||
|
add = scale;
|
||
|
while (i <= size.x)
|
||
|
{
|
||
|
Vector3 point = startPoint - up * i;
|
||
|
Handles.DrawLine(point, point + right * size.x);
|
||
|
i += add;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DrawMouseCross()
|
||
|
{
|
||
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||
|
Vector3 origin = ray.GetPoint(1f);
|
||
|
float size = 0.4f * HandleUtility.GetHandleSize(origin);
|
||
|
Vector3 a = origin + editorCamera.transform.up * size - editorCamera.transform.right * size;
|
||
|
Vector3 b = origin - editorCamera.transform.up * size + editorCamera.transform.right * size;
|
||
|
Handles.color = Color.red;
|
||
|
Handles.DrawLine(a, b);
|
||
|
a = origin - editorCamera.transform.up * size - editorCamera.transform.right * size;
|
||
|
b = origin + editorCamera.transform.up * size + editorCamera.transform.right * size;
|
||
|
Handles.DrawLine(a, b);
|
||
|
Handles.color = Color.white;
|
||
|
}
|
||
|
|
||
|
private void UpdateVisualizer()
|
||
|
{
|
||
|
if(visualizer == null) visualizer = new Spline(splineType);
|
||
|
visualizer.type = splineType;
|
||
|
visualizer.sampleRate = sampleRate;
|
||
|
if(placementMode == PlacementMode.Insert)
|
||
|
{
|
||
|
visualizer.points = editor.GetPointsArray();
|
||
|
if (isClosed) visualizer.Close();
|
||
|
else if (visualizer.isClosed) visualizer.Break();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (visualizer.points.Length != points.Length + 1)
|
||
|
{
|
||
|
visualizer.points = new SplinePoint[points.Length + 1];
|
||
|
}
|
||
|
|
||
|
SplinePoint newPoint = new SplinePoint(createPoint, createPoint, createNormal, 1f, Color.white);
|
||
|
if (appendMode == AppendMode.End)
|
||
|
{
|
||
|
for (int i = 0; i < points.Length; i++)
|
||
|
{
|
||
|
visualizer.points[i] = points[i].CreateSplinePoint();
|
||
|
}
|
||
|
visualizer.points[visualizer.points.Length - 1] = newPoint;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int i = 1; i < visualizer.points.Length; i++)
|
||
|
{
|
||
|
visualizer.points[i] = points[i - 1].CreateSplinePoint();
|
||
|
}
|
||
|
visualizer.points[0] = newPoint;
|
||
|
}
|
||
|
|
||
|
if (isClosed && !visualizer.isClosed)
|
||
|
{
|
||
|
if(visualizer.points.Length >= 3)
|
||
|
{
|
||
|
visualizer.Close();
|
||
|
} else
|
||
|
{
|
||
|
visualizer.Break();
|
||
|
}
|
||
|
}
|
||
|
else if (!isClosed && visualizer.isClosed)
|
||
|
{
|
||
|
visualizer.Break();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|