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,