Files
2025-06-09 23:23:13 +08:00

221 lines
9.0 KiB
C#

using Unity.Mathematics;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Splines;
using UnityEngine;
using UnityEngine.Splines;
namespace JBooth.MicroVerseCore
{
[EditorTool("Shape Tool", typeof(Road))]
public class RoadShapeTool : EditorTool, IDrawSelectedHandles
{
public override bool IsAvailable()
{
var splineDataTarget = target as Road;
if (splineDataTarget == null || splineDataTarget.splineContainer == null)
return false;
RoadSystem rs = splineDataTarget.GetComponentInParent<RoadSystem>();
if (rs == null || rs.systemConfig == null || rs.systemConfig.allowShaping == false)
return false;
return base.IsAvailable();
}
GUIContent m_IconContent;
public override GUIContent toolbarIcon => m_IconContent;
bool m_DisableHandles = false;
void OnEnable()
{
m_IconContent = new GUIContent()
{
image = Resources.Load<Texture2D>("Icons/WidthTool"),
text = "Shape Tool",
tooltip = "Adjust the shape of the created path."
};
}
protected const float k_HandleSize = 0.15f;
protected bool DrawDataPoints(ISpline spline, SplineData<float2> splineData)
{
var inUse = false;
for (int dataFrameIndex = 0; dataFrameIndex < splineData.Count; dataFrameIndex++)
{
var dataPoint = splineData[dataFrameIndex];
var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPoint.Index, splineData.PathIndexUnit);
spline.Evaluate(normalizedT, out var position, out var tangent, out var up);
tangent.y = 0;
if (DrawDataPoint(position, tangent, Vector3.up, dataPoint.Value, out var result))
{
dataPoint.Value = result;
splineData[dataFrameIndex] = dataPoint;
inUse = true;
}
}
return inUse;
}
public override void OnToolGUI(EditorWindow window)
{
var splineDataTarget = target as Road;
if (splineDataTarget == null || splineDataTarget.splineContainer == null)
return;
Undo.RecordObject(splineDataTarget, "Modifying Shape SplineData");
for (int i = 0; i < splineDataTarget.splineShapes.Count; ++i)
{
if (i < splineDataTarget.splineContainer.Splines.Count)
{
var spline = splineDataTarget.splineContainer.Splines[i];
var nativeSpline = new NativeSpline(spline, splineDataTarget.splineContainer.transform.localToWorldMatrix);
Handles.color = Color.blue;
m_DisableHandles = false;
//User defined handles to manipulate width
DrawDataPoints(nativeSpline, splineDataTarget.splineShapes[i].shapeData);
//Using the out-of the box behaviour to manipulate indexes
nativeSpline.DataPointHandles(splineDataTarget.splineShapes[i].shapeData);
}
}
}
public void OnDrawHandles()
{
var splineDataTarget = target as Road;
if (ToolManager.IsActiveTool(this) || splineDataTarget == null || splineDataTarget.splineContainer == null)
return;
while (splineDataTarget.splineShapes.Count > splineDataTarget.splineContainer.Splines.Count)
{
splineDataTarget.splineShapes.RemoveAt(splineDataTarget.splineShapes.Count - 1);
}
for (int i = 0; i < splineDataTarget.splineContainer.Splines.Count; ++i)
{
if (i >= splineDataTarget.splineShapes.Count)
{
var sw = new Road.SplineShapeData();
sw.shapeData = new SplineData<float2>();
sw.shapeData.PathIndexUnit = PathIndexUnit.Normalized;
splineDataTarget.splineShapes.Add(sw);
}
var nativeSpline = new NativeSpline(splineDataTarget.splineContainer.Splines[i], splineDataTarget.splineContainer.transform.localToWorldMatrix);
Color color = Color.blue;
color.a = 0.5f;
Handles.color = color;
m_DisableHandles = true;
DrawDataPoints(nativeSpline, splineDataTarget.splineShapes[i].shapeData);
}
}
protected bool DrawDataPoint(
Vector3 position,
Vector3 tangent,
Vector3 up,
float2 inValue,
out float2 outValue)
{
// width handles
int id1 = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive);
int id2 = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive);
// height handles
int id3 = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive);
int id4 = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive);
outValue = inValue;
if (tangent == Vector3.zero)
return false;
if (Event.current.type == EventType.MouseUp
&& Event.current.button != 0
&& (GUIUtility.hotControl == id1 || GUIUtility.hotControl == id2 || GUIUtility.hotControl == id3 || GUIUtility.hotControl == id4))
{
Event.current.Use();
return false;
}
var handleColor = Color.cyan;
if (GUIUtility.hotControl == id1 || GUIUtility.hotControl == id2 || GUIUtility.hotControl == id3 || GUIUtility.hotControl == id4)
handleColor = Handles.selectedColor;
else if (GUIUtility.hotControl == 0 && (HandleUtility.nearestControl == id1 || HandleUtility.nearestControl == id2 || HandleUtility.nearestControl == id3 || HandleUtility.nearestControl == id4))
handleColor = Handles.preselectionColor;
RoadSystem rs = (target as Road).GetComponentInParent<RoadSystem>();
float handleOffset = 0;
if (rs != null && rs.systemConfig != null)
{
handleOffset = rs.systemConfig.shapingSizeHandleStart;
}
var normalDirection = math.normalize(math.cross(tangent, up));
inValue++;
var extremity1 = position - inValue.x * (Vector3)normalDirection - ((Vector3)normalDirection * handleOffset);
var extremity2 = position + inValue.x * (Vector3)normalDirection + ((Vector3)normalDirection * handleOffset);
var extremity3 = position - inValue.y * (Vector3)up - ((Vector3)up * handleOffset);
var extremity4 = position + inValue.y * (Vector3)up + ((Vector3)up * handleOffset);
Vector3 val1, val2, val3, val4;
using (new Handles.DrawingScope(handleColor))
{
Handles.DrawLine(extremity1, extremity2);
Handles.DrawLine(extremity3, extremity4);
val1 = Handles.Slider(id1, extremity1, normalDirection,
k_HandleSize * .5f * HandleUtility.GetHandleSize(position), CustomHandleCap, 0);
val2 = Handles.Slider(id2, extremity2, normalDirection,
k_HandleSize * .5f * HandleUtility.GetHandleSize(position), CustomHandleCap, 0);
val3 = Handles.Slider(id3, extremity3, up,
k_HandleSize * .5f * HandleUtility.GetHandleSize(position), CustomHandleCap, 0);
val4 = Handles.Slider(id4, extremity4, up,
k_HandleSize * .5f * HandleUtility.GetHandleSize(position), CustomHandleCap, 0);
}
if (GUIUtility.hotControl == id1 && math.abs((val1 - extremity1).magnitude) > 0)
{
outValue.x = math.max(0, math.abs((val1 - position).magnitude) - 1 - handleOffset);
return true;
}
if (GUIUtility.hotControl == id2 && math.abs((val2 - extremity2).magnitude) > 0)
{
outValue.x = math.max(0, math.abs((val2 - position).magnitude) - 1 - handleOffset);
return true;
}
if (GUIUtility.hotControl == id3 && math.abs((val3 - extremity3).magnitude) > 0)
{
outValue.y = math.max(0, math.abs((val3 - position).magnitude) - 1 - handleOffset);
return true;
}
if (GUIUtility.hotControl == id4 && math.abs((val4 - extremity4).magnitude) > 0)
{
outValue.y = math.max(0, math.abs((val4 - position).magnitude) - 1 - handleOffset);
return true;
}
return false;
}
public void CustomHandleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
{
Handles.CubeHandleCap(controlID, position, rotation, size, m_DisableHandles ? EventType.Repaint : eventType);
}
}
}