diff --git a/TLM/TLM/Manager/Impl/LaneArrowManager.cs b/TLM/TLM/Manager/Impl/LaneArrowManager.cs index 01166fa4b..d7720e186 100644 --- a/TLM/TLM/Manager/Impl/LaneArrowManager.cs +++ b/TLM/TLM/Manager/Impl/LaneArrowManager.cs @@ -319,15 +319,20 @@ public static void SeparateSegmentLanes(ushort segmentId, ushort nodeId, out Set if (numdirs < 2) { return; // no junction } + bool lht = LaneArrowManager.Instance.Services.SimulationService.TrafficDrivesOnLeft; if (srcLaneCount == 2 && numdirs == 3) { - LaneArrowManager.Instance.SetLaneArrows(laneList[0].laneId, LaneArrows.LeftForward); - LaneArrowManager.Instance.SetLaneArrows(laneList[1].laneId, LaneArrows.Right); + if (!lht) { + LaneArrowManager.Instance.SetLaneArrows(laneList[0].laneId, LaneArrows.LeftForward); + LaneArrowManager.Instance.SetLaneArrows(laneList[1].laneId, LaneArrows.Right); + }else { + LaneArrowManager.Instance.SetLaneArrows(laneList[1].laneId, LaneArrows.ForwardRight); + LaneArrowManager.Instance.SetLaneArrows(laneList[0].laneId, LaneArrows.Left); + } return; } int l = 0, f = 0, r = 0; - bool lht = LaneArrowManager.Instance.Services.SimulationService.TrafficDrivesOnLeft; if (numdirs == 2) { if (!lht) { //if traffic drives on right, then favour the more difficult left turns. diff --git a/TLM/TLM/UI/SubTool.cs b/TLM/TLM/UI/SubTool.cs index 8d450429c..6d953e3e7 100644 --- a/TLM/TLM/UI/SubTool.cs +++ b/TLM/TLM/UI/SubTool.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.UI { +namespace TrafficManager.UI { using ColossalFramework.UI; using JetBrains.Annotations; using Textures; @@ -78,12 +78,12 @@ private Texture2D BorderlessTexture { private GUIStyle borderlessStyle_; - protected ushort HoveredNodeId { + protected virtual ushort HoveredNodeId { get => TrafficManagerTool.HoveredNodeId; set => TrafficManagerTool.HoveredNodeId = value; } - protected ushort HoveredSegmentId { + protected virtual ushort HoveredSegmentId { get => TrafficManagerTool.HoveredSegmentId; set => TrafficManagerTool.HoveredSegmentId = value; } diff --git a/TLM/TLM/UI/SubTools/LaneArrowTool.cs b/TLM/TLM/UI/SubTools/LaneArrowTool.cs index 078469dca..5ca05148a 100644 --- a/TLM/TLM/UI/SubTools/LaneArrowTool.cs +++ b/TLM/TLM/UI/SubTools/LaneArrowTool.cs @@ -6,6 +6,7 @@ namespace TrafficManager.UI.SubTools { using GenericGameBridge.Service; using Manager.Impl; using State; + using TrafficManager.API.Traffic.Data; using UnityEngine; public class LaneArrowTool : SubTool { @@ -41,7 +42,7 @@ public override void OnPrimaryClickOverlay() { LaneArrowManager.SeparateTurningLanes.SeparateSegmentLanes(HoveredSegmentId, HoveredNodeId, out res); } else if (ctrlDown) { LaneArrowManager.SeparateTurningLanes.SeparateNode(HoveredNodeId, out res); - } else { + } else if (HasHoverLaneArrows()) { SelectedSegmentId = HoveredSegmentId; SelectedNodeId = HoveredNodeId; } @@ -108,13 +109,81 @@ public override void OnToolGUI(Event e) { cursorInSecondaryPanel_ = windowRect3.Contains(Event.current.mousePosition); } + /// + /// Determines whether or not the hovered segment end has lane arrows. + /// + private bool HasHoverLaneArrows() => HasSegmentEndLaneArrows(HoveredSegmentId, HoveredNodeId); + + /// + /// determines whether or not the given segment end has lane arrows. + /// + private bool HasSegmentEndLaneArrows(ushort segmentId, ushort nodeId) { + if(nodeId == 0 || segmentId == 0) { + return false; + } +#if DEBUG + if(!Constants.ServiceFactory.NetService.IsNodeValid(nodeId) || + !Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + Debug.LogError("Invalid node or segment ID"); + } +#endif + ExtSegmentEndManager segEndMan = ExtSegmentEndManager.Instance; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, nodeId)]; + NetNode[] nodesBuffer = Singleton.instance.m_nodes.m_buffer; + bool bJunction = (nodesBuffer[nodeId].m_flags & NetNode.Flags.Junction) != 0; + + // Outgoing lanes toward the node is incomming lanes to the segment end. + return bJunction && segEnd.incoming; + } + + protected override ushort HoveredNodeId { + get { + // if the current segment end does not have lane arrows + // and the other end of the segment does have lane arrows, then + // assume the user intends to hover over that one. + // This code makes it easier to hover over small segments. + if (!HasSegmentEndLaneArrows(HoveredSegmentId, base.HoveredNodeId)) + { + ref NetSegment segment = ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId]; + ushort otherNodeId = segment.GetOtherNode(base.HoveredNodeId); + if (HasSegmentEndLaneArrows(HoveredSegmentId, otherNodeId)) { + return otherNodeId; + } + } + return base.HoveredNodeId; + } + set => base.HoveredNodeId = value; + } + + /// + /// Draws a half sausage to highlight the segment end. + /// + private void DrawSegmentEnd( + RenderManager.CameraInfo cameraInfo, + ushort segmentId, + bool bStartNode, + Color color, + bool alpha = false) { + ref NetSegment segment = ref Singleton.instance.m_segments.m_buffer[segmentId]; + + // if only one side of the segment has lane arrows then the length of the + // is 1. but the highlight still looks like a sausage which is cut at one end. + // this is important to give user visual feedback which area is hoverable. + bool con = + HasSegmentEndLaneArrows(segmentId, segment.m_startNode) ^ + HasSegmentEndLaneArrows(segmentId, segment.m_endNode); + float cut = con ? 1f : 0.5f; + + MainTool.DrawCutSegmentEnd(cameraInfo, segmentId, cut, bStartNode, color, alpha); + } + public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { NetManager netManager = Singleton.instance; bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); bool altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); - - if (ctrlDown) { + bool PrimaryDown = Input.GetMouseButton(0); + if (ctrlDown && !cursorInSecondaryPanel_ && (HoveredNodeId != 0)) { // draw hovered node MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); return; @@ -122,6 +191,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { // Log._Debug($"LaneArrow Overlay: {HoveredNodeId} {HoveredSegmentId} {SelectedNodeId} {SelectedSegmentId}"); if (!cursorInSecondaryPanel_ + && HasHoverLaneArrows() && (HoveredSegmentId != 0) && (HoveredNodeId != 0) && ((HoveredSegmentId != SelectedSegmentId) @@ -131,28 +201,20 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { if (((netManager.m_segments.m_buffer[HoveredSegmentId].m_startNode == HoveredNodeId) || (netManager.m_segments.m_buffer[HoveredSegmentId].m_endNode == HoveredNodeId)) - && ((nodeFlags & NetNode.Flags.Junction) != NetNode.Flags.None)) - { - NetTool.RenderOverlay( - cameraInfo, - ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId], - MainTool.GetToolColor(altDown, false), - MainTool.GetToolColor(altDown, false)); + && ((nodeFlags & NetNode.Flags.Junction) != NetNode.Flags.None)) { + bool bStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(HoveredSegmentId, HoveredNodeId); + Color color = MainTool.GetToolColor(PrimaryDown, false); + bool alpha = !altDown; + DrawSegmentEnd(cameraInfo, HoveredSegmentId, bStartNode, color, alpha); } } - if (SelectedSegmentId == 0) return; - - Color color; - if (altDown && HoveredSegmentId == SelectedSegmentId) - color = MainTool.GetToolColor(true, true); - else - color = MainTool.GetToolColor(true, false); - NetTool.RenderOverlay( - cameraInfo, - ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], - color, - color); + if (SelectedSegmentId != 0) { + Color color = MainTool.GetToolColor(true, false); + bool bStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(SelectedSegmentId, SelectedNodeId); + bool alpha = !altDown && HoveredSegmentId == SelectedSegmentId; + DrawSegmentEnd(cameraInfo, SelectedSegmentId, bStartNode, color, alpha); + } } private void GuiLaneChangeWindow(int num) { diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 6d9042d83..b9e8d7e82 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.UI { +namespace TrafficManager.UI { using System; using System.Collections.Generic; using System.Linq; @@ -410,6 +410,7 @@ protected override void OnToolGUI(Event e) { } } + public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, bool warning = false, @@ -417,50 +418,92 @@ public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, DrawNodeCircle(cameraInfo, nodeId, GetToolColor(warning, false), alpha); } + /// + /// Gets the coordinates of the given node. + /// + private static Vector3 GetNodePos(ushort nodeId) { + NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer; + Vector3 pos = nodeBuffer[nodeId].m_position; + float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); + if (terrainY > pos.y) { + pos.y = terrainY; + } + return pos; + } + + /// the average half width of all connected segments + private static float CalculateNodeRadius(ushort nodeId) { + + float sum_half_width = 0; + int count = 0; + Constants.ServiceFactory.NetService.IterateNodeSegments( + nodeId, + (ushort segmentId, ref NetSegment segment) => { + sum_half_width += segment.Info.m_halfWidth; + count++; + return true; + }); + return sum_half_width / count; + } + public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, Color color, bool alpha = false) { - NetNode[] nodesBuffer = Singleton.instance.m_nodes.m_buffer; - NetSegment segment = - Singleton.instance.m_segments.m_buffer[ nodesBuffer[nodeId].m_segment0]; + float r = CalculateNodeRadius(nodeId); + Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; + DrawOverlayCircle(cameraInfo, color, pos, r * 2, alpha); + } - Vector3 pos = nodesBuffer[nodeId].m_position; - float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); - if (terrainY > pos.y) { - pos.y = terrainY; + /// + /// Draws a half sausage at segment end. + /// + /// + /// The lenght of the highlight [0~1] + /// Determines the direction of the half sausage. + public void DrawCutSegmentEnd(RenderManager.CameraInfo cameraInfo, + ushort segmentId, + float cut, + bool bStartNode, + Color color, + bool alpha = false) { + if( segmentId == 0) { + return; } + ref NetSegment segment = ref Singleton.instance.m_segments.m_buffer[segmentId]; + float width = segment.Info.m_halfWidth; + + NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer; + bool IsMiddle(ushort nodeId) => (nodeBuffer[nodeId].m_flags & NetNode.Flags.Middle) != 0; Bezier3 bezier; - bezier.a = pos; - bezier.d = pos; + bezier.a = GetNodePos(segment.m_startNode); + bezier.d = GetNodePos(segment.m_endNode); NetSegment.CalculateMiddlePoints( bezier.a, segment.m_startDirection, bezier.d, segment.m_endDirection, - false, - false, + IsMiddle(segment.m_startNode), + IsMiddle(segment.m_endNode), out bezier.b, out bezier.c); - DrawOverlayBezier(cameraInfo, bezier, color, alpha); - } + if (bStartNode) { + bezier = bezier.Cut(0, cut); + } else { + bezier = bezier.Cut(1 - cut, 1); + } - private void DrawOverlayBezier(RenderManager.CameraInfo cameraInfo, - Bezier3 bezier, - Color color, - bool alpha = false) { - const float width = 8f; // 8 - small roads; 16 - big roads Singleton.instance.m_drawCallData.m_overlayCalls++; Singleton.instance.OverlayEffect.DrawBezier( cameraInfo, color, bezier, width * 2f, - width, - width, + bStartNode ? 0 : width, + bStartNode ? width : 0, -1f, 1280f, false,