Editor/RoadToolWindow/RoadToolWindow.Gizmo.cs
using Sandbox;
namespace RedSnail.RoadTool.Editor;
public partial class RoadToolWindow
{
private const float GIZMO_BOX_SIZE = 2.0f;
private const float LINE_THICKNESS = 2.0f;
private const float TANGENT_LINE_THICKNESS = 0.8f;
private void DrawGizmos()
{
using (Gizmo.Scope("road_editor", _targetComponent.WorldTransform))
{
DrawSplineSegments();
DrawPositionGizmo();
DrawPointControls();
}
}
private void DrawSplineSegments()
{
_targetComponent.Spline.ConvertToPolyline(ref _polyLine);
for (var i = 0; i < _polyLine.Count - 1; i++)
{
DrawSegment(i, _polyLine[i], _polyLine[i + 1]);
}
}
private void DrawSegment(int index, Vector3 start, Vector3 end)
{
using (Gizmo.Scope("segment" + index))
using (Gizmo.Hitbox.LineScope())
{
Gizmo.Draw.LineThickness = LINE_THICKNESS;
Gizmo.Hitbox.AddPotentialLine(start, end, LINE_THICKNESS * 2f);
Gizmo.Draw.Line(start, end);
if (Gizmo.IsHovered && Gizmo.HasMouseFocus)
{
HandleSegmentHover(start, end);
}
}
}
private void HandleSegmentHover(Vector3 start, Vector3 end)
{
Gizmo.Draw.Color = Color.Cyan;
if (!new Line(start, end).ClosestPoint(Gizmo.CurrentRay.ToLocal(Gizmo.Transform),
out Vector3 pointOnLine, out _))
return;
var hoverSample = _targetComponent.Spline.SampleAtClosestPosition(pointOnLine);
DrawHoverHandle(pointOnLine, hoverSample.Tangent);
if (Gizmo.HasClicked && Gizmo.Pressed.This)
{
InsertPointAtHover(hoverSample.Distance);
}
}
private void DrawHoverHandle(Vector3 position, Vector3 tangent)
{
using (Gizmo.Scope("hover_handle", new Transform(position, Rotation.LookAt(tangent))))
using (Gizmo.GizmoControls.PushFixedScale())
{
Gizmo.Draw.SolidBox(BBox.FromPositionAndSize(Vector3.Zero, GIZMO_BOX_SIZE));
}
}
private void InsertPointAtHover(float distance)
{
using (CreateUndoScope("Added spline point"))
{
var newPointIndex = _targetComponent.Spline.AddPointAtDistance(distance, true);
SelectedPointIndex = newPointIndex;
_inTangentSelected = false;
_outTangentSelected = false;
}
}
private void DrawPositionGizmo()
{
var gizmoPosition = CalculateGizmoPosition();
if (!Gizmo.IsShiftPressed)
{
_draggingOutNewPoint = false;
}
using (Gizmo.Scope("position", new Transform(gizmoPosition)))
{
HandlePositionControl();
}
}
private Vector3 CalculateGizmoPosition()
{
var position = _selectedPoint.Position;
if (_inTangentSelected)
position += _selectedPoint.In;
else if (_outTangentSelected)
position += _selectedPoint.Out;
return position;
}
private void HandlePositionControl()
{
_moveInProgress = false;
if (Gizmo.Control.Position("spline_control_", Vector3.Zero, out var delta))
{
_moveInProgress = true;
_movementUndoScope ??= CreateUndoScope("Moved spline point");
if (_inTangentSelected)
MoveSelectedPointInTangent(delta);
else if (_outTangentSelected)
MoveSelectedPointOutTangent(delta);
else
HandlePointMove(delta);
}
if (!_moveInProgress && Gizmo.WasLeftMouseReleased)
{
_movementUndoScope?.Dispose();
_movementUndoScope = null;
}
}
private void HandlePointMove(Vector3 delta)
{
if (Gizmo.IsShiftPressed && !_draggingOutNewPoint)
{
_draggingOutNewPoint = true;
var currentPoint = _targetComponent.Spline.GetPoint(SelectedPointIndex);
_targetComponent.Spline.InsertPoint(SelectedPointIndex + 1, currentPoint);
SelectedPointIndex++;
}
else
{
MoveSelectedPoint(delta);
}
}
private void DrawPointControls()
{
var spline = _targetComponent.Spline;
for (var i = 0; i < spline.PointCount; i++)
{
if (spline.IsLoop && i == spline.SegmentCount)
continue;
var point = spline.GetPoint(i);
DrawPointControl(i, point);
}
}
private void DrawPointControl(int index, Spline.Point point)
{
using (Gizmo.Scope("point_controls" + index, new Transform(point.Position)))
{
Gizmo.Draw.IgnoreDepth = true;
DrawPointPositionHandle(index);
if (SelectedPointIndex == index)
{
DrawTangentHandles(point);
}
}
}
private void DrawPointPositionHandle(int index)
{
using (Gizmo.Scope("position"))
using (Gizmo.GizmoControls.PushFixedScale())
{
Gizmo.Hitbox.DepthBias = 0.1f;
Gizmo.Hitbox.BBox(BBox.FromPositionAndSize(Vector3.Zero, GIZMO_BOX_SIZE));
bool isSelected = index == SelectedPointIndex && !_inTangentSelected && !_outTangentSelected;
if (Gizmo.IsHovered || isSelected)
{
Gizmo.Draw.Color = Color.Cyan;
}
Gizmo.Draw.SolidBox(BBox.FromPositionAndSize(Vector3.Zero, GIZMO_BOX_SIZE));
if (Gizmo.HasClicked && Gizmo.Pressed.This)
{
SelectPoint(index);
}
}
}
private void DrawTangentHandles(Spline.Point point)
{
Gizmo.Draw.Color = Color.White;
Gizmo.Draw.LineThickness = TANGENT_LINE_THICKNESS;
DrawTangentHandle("in_tangent", point.In, -point.In, ref _inTangentSelected, ref _outTangentSelected);
DrawTangentHandle("out_tangent", point.Out, -point.Out, ref _outTangentSelected, ref _inTangentSelected);
}
private void DrawTangentHandle(string name, Vector3 offset, Vector3 lineStart, ref bool thisSelected, ref bool otherSelected)
{
using (Gizmo.Scope(name, new Transform(offset)))
{
bool isMirroredOrAuto = _selectedPointTangentMode is HandleModeTemp.Mirrored or HandleModeTemp.Auto;
if (isMirroredOrAuto && (thisSelected || otherSelected))
{
Gizmo.Draw.Color = Color.Cyan;
}
Gizmo.Draw.Line(lineStart, Vector3.Zero);
if (_selectedPointTangentMode != HandleModeTemp.Linear)
{
DrawTangentBox(ref thisSelected, ref otherSelected);
}
}
}
private void DrawTangentBox(ref bool thisSelected, ref bool otherSelected)
{
using (Gizmo.GizmoControls.PushFixedScale())
{
Gizmo.Hitbox.DepthBias = 0.1f;
Gizmo.Hitbox.BBox(BBox.FromPositionAndSize(Vector3.Zero, GIZMO_BOX_SIZE));
if (Gizmo.IsHovered || thisSelected)
{
Gizmo.Draw.Color = Color.Cyan;
}
Gizmo.Draw.SolidBox(BBox.FromPositionAndSize(Vector3.Zero, GIZMO_BOX_SIZE));
if (Gizmo.HasClicked && Gizmo.Pressed.This)
{
thisSelected = true;
otherSelected = false;
}
}
}
}