diff --git a/TLM/TLM/Manager/Impl/LaneArrowManager.cs b/TLM/TLM/Manager/Impl/LaneArrowManager.cs
index ca28d8e43..ea907387e 100644
--- a/TLM/TLM/Manager/Impl/LaneArrowManager.cs
+++ b/TLM/TLM/Manager/Impl/LaneArrowManager.cs
@@ -139,6 +139,18 @@ public void ResetLaneArrows(uint laneId) {
}
}
+ ///
+ /// Resets lane arrows to their default value for the given node
+ ///
+ public void ResetNodeLaneArrows(ushort nodeId) {
+ ref NetNode node = ref nodeId.ToNode();
+ for (int i = 0; i < 8; ++i) {
+ ushort segmentId = node.GetSegment(i);
+ bool startNode = segmentId.ToSegment().IsStartNode(nodeId);
+ LaneArrowManager.Instance.ResetLaneArrows(segmentId, startNode);
+ }
+ }
+
///
/// Updates all road relevant segments so that the dedicated turning lane policy would take effect.
///
diff --git a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs
index 71f4eab70..b0ac79f98 100644
--- a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs
+++ b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs
@@ -37,6 +37,8 @@ static LaneConnectionManager() {
public static LaneConnectionManager Instance { get; }
+ public LaneConnectionSubManager SubManager(bool track) => track ? Track : Road;
+
public override void OnBeforeLoadData() {
base.OnBeforeLoadData();
Road.OnBeforeLoadData();
diff --git a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs
index 2a3581a61..b6567e213 100644
--- a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs
+++ b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs
@@ -249,6 +249,7 @@ internal bool RemoveLaneConnection(uint sourceLaneId, uint targetLaneId, bool so
ushort targetSegmentId = targetLaneId.ToLane().m_segment;
ushort nodeId = sourceSegmentId.ToSegment().GetNodeId(sourceStartNode);
var result = connectionDataBase_.Disconnect(sourceLaneId, targetLaneId, nodeId);
+ AssertLane(sourceLaneId, sourceStartNode);
if (verbose_) {
Log._Debug($"LaneConnectionSubManager({Group}).RemoveLaneConnection({sourceLaneId}, {targetLaneId}, " +
@@ -298,6 +299,10 @@ internal void RemoveLaneConnectionsFromNode(ushort nodeId) {
}
}
+ if (Supports(LaneEndTransitionGroup.Road)) {
+ LaneArrowManager.Instance.ResetNodeLaneArrows(nodeId);
+ }
+
for (int i = 0; i < 8; ++i) {
ushort segmentId = node.GetSegment(i);
RoutingManager.Instance.RequestRecalculation(segmentId);
@@ -341,9 +346,6 @@ internal void RemoveLaneConnections(uint laneId,
/// The affected node
/// true if any connection was added
internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) {
- if (sourceLaneId == targetLaneId) {
- return false;
- }
bool valid = ValidateLane(sourceLaneId) & ValidateLane(targetLaneId);
if (!valid) {
@@ -376,14 +378,17 @@ static bool IsDirectionValid(ref NetLane lane, NetInfo.Lane laneInfo, ushort nod
return dir.IsFlagSet(NetInfo.Direction.Backward);
}
}
- canConnect =
- IsDirectionValid(ref sourceNetLane, sourceLaneInfo, nodeId, true) &&
- IsDirectionValid(ref targetNetLane, targetLaneInfo, nodeId, false);
+ canConnect = IsDirectionValid(ref sourceNetLane, sourceLaneInfo, nodeId, true);
+ bool deadEnd = sourceLaneId == targetLaneId;
+ if (!deadEnd) {
+ canConnect &= IsDirectionValid(ref targetNetLane, targetLaneInfo, nodeId, false);
+ }
+
if (!canConnect) {
return false;
}
- if (Group == LaneEndTransitionGroup.Track) {
+ if (!deadEnd && Group == LaneEndTransitionGroup.Track) {
bool targetStartnode = targetSegmentId.ToSegment().IsStartNode(nodeId);
canConnect = LaneConnectionManager.CheckSegmentsTurningAngle(
sourceSegmentId, sourceStartNode, targetSegmentId, targetStartnode);
@@ -392,8 +397,36 @@ static bool IsDirectionValid(ref NetLane lane, NetInfo.Lane laneInfo, ushort nod
}
}
+ var connections = GetLaneConnections(sourceLaneId, sourceStartNode);
+ if (verbose_) {
+ Log._Debug($"AddLaneConnection: {sourceLaneId}->{targetLaneId} at {nodeId} connections={connections.ToSTR()}");
+ }
+ if (connections != null) {
+ foreach (uint laneId in connections) {
+ LaneEnd key = new(laneId, nodeId);
+ if (deadEnd) {
+ if (laneId != sourceLaneId) {
+ // dead end lane connection cannot have other lane connections.
+ if (verbose_) {
+ Log._Debug($"making a dead end connection disconnecting {sourceLaneId}->{laneId} at {nodeId}");
+ }
+ connectionDataBase_.Disconnect(sourceLaneId, laneId, nodeId);
+ }
+ } else {
+ if (laneId == sourceLaneId) {
+ // if adding a new connection then remove the dead end connection.
+ if (verbose_) {
+ Log._Debug($"removing dead end connection for lane:{sourceLaneId} at node:{nodeId}");
+ }
+ connectionDataBase_.Disconnect(sourceLaneId, sourceLaneId, nodeId);
+ }
+ }
+ }
+ }
+
connectionDataBase_.ConnectTo(sourceLaneId, targetLaneId, nodeId);
Assert(AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode), $"AreLanesConnected({sourceLaneId}, {targetLaneId}, {sourceStartNode})");
+ AssertLane(sourceLaneId, sourceStartNode);
if (verbose_) {
Log._Debug($"LaneConnectionSubManager({Group}).AddLaneConnection({sourceLaneId}, " +
@@ -423,6 +456,16 @@ static bool IsDirectionValid(ref NetLane lane, NetInfo.Lane laneInfo, ushort nod
return true;
}
+ private void AssertLane(uint laneId, bool startNode) {
+ Assert(laneId.ToLane().IsValidWithSegment(), $"IsValidWithSegment() faild for laneId:{laneId}");
+ var connections = GetLaneConnections(laneId, startNode);
+ if (connections != null && connections.Contains(laneId)) {
+ // dead end should only have one connection to itself.
+ ushort nodeId = laneId.ToLane().GetNodeId(startNode);
+ Assert(connections.Length == 1, $"connections for lane:{laneId} at node:{nodeId} = " + connections.ToSTR());
+ }
+ }
+
private void ReleasingSegment(ushort segmentId, ref NetSegment segment) {
if (verbose_) {
Log._Debug($"LaneConnectionSubManager({Group}).ReleasingSegment({segmentId}, isValid={segment.IsValid()}): " +
@@ -462,15 +505,6 @@ private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) {
return;
}
- if (!HasOutgoingConnections(laneId, startNode)) {
- if (verbose_) {
- Log._Debug($"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"lane {laneId} does not have outgoing connections");
- }
-
- return;
- }
-
if (nodeId == 0) {
if (verbose_) {
Log._Debug($"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
@@ -480,7 +514,6 @@ private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) {
return;
}
- var arrows = LaneArrows.None;
ushort segmentId = laneId.ToLane().m_segment;
if (segmentId == 0) {
@@ -508,153 +541,43 @@ private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) {
return;
}
- IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager;
- ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)];
-
- for (int i = 0; i < 8; ++i) {
- ushort otherSegmentId = netNode.GetSegment(i);
- if (otherSegmentId != 0) {
- //TODO move the following into a function
- ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId);
-
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}. dir={dir}");
- }
-
- // check if arrow has already been set for this direction
- switch (dir) {
- case ArrowDirection.Turn: {
- if (LHT) {
- if ((arrows & LaneArrows.Right) != LaneArrows.None) {
- continue;
- }
- } else {
- if ((arrows & LaneArrows.Left) != LaneArrows.None) {
- continue;
- }
- }
-
- break;
- }
-
- case ArrowDirection.Forward: {
- if ((arrows & LaneArrows.Forward) != LaneArrows.None) {
- continue;
- }
-
- break;
- }
-
- case ArrowDirection.Left: {
- if ((arrows & LaneArrows.Left) != LaneArrows.None) {
- continue;
- }
-
- break;
- }
-
- case ArrowDirection.Right: {
- if ((arrows & LaneArrows.Right) != LaneArrows.None) {
- continue;
- }
-
- break;
- }
-
- default: {
- continue;
- }
- }
-
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}: need to determine arrows");
- }
-
- bool addArrow = false;
- uint curLaneId = otherSegmentId.ToSegment().m_lanes;
-
- while (curLaneId != 0) {
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}: checking lane {curLaneId}");
- }
-
- if (AreLanesConnected(laneId, curLaneId, startNode)) {
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}: checking lane " +
- $"{curLaneId}: lanes are connected");
- }
-
- addArrow = true;
- break;
- }
-
- curLaneId = curLaneId.ToLane().m_nextLane;
- }
-
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}: finished processing " +
- $"lanes. addArrow={addArrow} arrows (before)={arrows}");
- }
-
- if (!addArrow) {
- continue;
- }
-
- switch (dir) {
- case ArrowDirection.Turn: {
- if (LHT) {
- arrows |= LaneArrows.Right;
- } else {
- arrows |= LaneArrows.Left;
- }
-
- break;
- }
-
- case ArrowDirection.Forward: {
- arrows |= LaneArrows.Forward;
- break;
- }
-
- case ArrowDirection.Left: {
- arrows |= LaneArrows.Left;
- break;
- }
+ var targetLaneIds = this.GetLaneConnections(laneId, startNode);
+ if (targetLaneIds.IsNullOrEmpty()) {
+ LaneArrowManager.Instance.ResetLaneArrows(laneId);
+ return;
+ }
- case ArrowDirection.Right: {
- arrows |= LaneArrows.Right;
- break;
- }
+ ref ExtSegmentEnd segEnd = ref ExtSegmentEndManager.Instance.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)];
- default: {
- continue;
- }
- }
-
- if (verbose_) {
- Log._Debug(
- $"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"processing connected segment {otherSegmentId}: arrows={arrows}");
- }
+ var arrows = LaneArrows.None;
+ foreach (uint targetLaneId in targetLaneIds) {
+ if (targetLaneId != laneId) {
+ ArrowDirection dir = segEndMan.GetDirection(ref segEnd, targetLaneId.ToLane().m_segment);
+ arrows |= ToLaneArrows(dir);
}
}
if (verbose_) {
Log._Debug($"LaneConnectionSubManager({Group}).RecalculateLaneArrows({laneId}, {nodeId}): " +
- $"setting lane arrows to {arrows}");
+ $"setting lane arrows to {arrows}");
}
LaneArrowManager.Instance.SetLaneArrows(laneId, arrows, true);
+
+ static LaneArrows ToLaneArrows(ArrowDirection dir) {
+ switch (dir) {
+ case ArrowDirection.Forward:
+ return LaneArrows.Forward;
+ case ArrowDirection.Left:
+ return LaneArrows.Left;
+ case ArrowDirection.Right:
+ return LaneArrows.Right;
+ case ArrowDirection.Turn:
+ return LaneArrows_Far;
+ default:
+ return LaneArrows.None;
+ }
+ }
}
internal void ResetLaneConnections() {
@@ -682,10 +605,6 @@ public bool LoadData(List data) {
continue;
}
- if (conn.sourceLaneId == conn.targetLaneId) {
- continue;
- }
-
ushort nodeId = sourceLane.GetNodeId(conn.sourceStartNode);
#if DEBUGLOAD
Log._Debug($"Loading lane connection: lane {conn.sourceLaneId} -> {conn.targetLaneId} @ node: {nodeId}");
diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs
index 7a7402103..aa316991c 100644
--- a/TLM/TLM/Manager/Impl/RoutingManager.cs
+++ b/TLM/TLM/Manager/Impl/RoutingManager.cs
@@ -647,7 +647,9 @@ void _ExtendedLogImpl(params object[] lines) => DetailLogger.LogDebug(
extendedLog?.Invoke(new { prevSegmentId, _ = "Exploring", nextSegmentId, nextLaneId, nextLaneIndex });
// next is compatible lane
- if (IsSupported(nextLaneInfo) && (prevLaneInfo.m_vehicleType & nextLaneInfo.m_vehicleType) != 0) {
+ if (nextLaneId != prevLaneId &&
+ IsSupported(nextLaneInfo) &&
+ (prevLaneInfo.m_vehicleType & nextLaneInfo.m_vehicleType) != 0) {
extendedLog?.Invoke(new { _ = "vehicle type check passed for", nextLaneId, nextLaneIndex });
// next is incoming lane
@@ -920,7 +922,7 @@ void _ExtendedLogImpl(params object[] lines) => DetailLogger.LogDebug(
nextLaneId = nextLaneId.ToLane().m_nextLane;
++nextLaneIndex;
- } // foreach lane
+ }
extendedLog?.Invoke(new { isNextSegmentValid, nextCompatibleTransitionDatas = nextCompatibleTransitionDataIndices?.ArrayToString() });
diff --git a/TLM/TLM/Resources/LaneConnectionManager/dead_end.png b/TLM/TLM/Resources/LaneConnectionManager/dead_end.png
new file mode 100644
index 000000000..eaaf01124
Binary files /dev/null and b/TLM/TLM/Resources/LaneConnectionManager/dead_end.png differ
diff --git a/TLM/TLM/Resources/LaneConnectionManager/dead_end.svg b/TLM/TLM/Resources/LaneConnectionManager/dead_end.svg
new file mode 100644
index 000000000..a6e008909
--- /dev/null
+++ b/TLM/TLM/Resources/LaneConnectionManager/dead_end.svg
@@ -0,0 +1,73 @@
+
+
diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj
index 3340c4f4c..da6347ca9 100644
--- a/TLM/TLM/TLM.csproj
+++ b/TLM/TLM/TLM.csproj
@@ -1100,9 +1100,8 @@
-
-
+
diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
index f2d1a22b0..8c9e36557 100644
--- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
+++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
@@ -36,6 +36,7 @@ public LaneConnectorTool(TrafficManagerTool mainTool)
addCursor_ = CursorUtil.LoadCursorFromResource("LaneConnectionManager.add_cursor.png");
removeCursor_ = CursorUtil.LoadCursorFromResource("LaneConnectionManager.remove_cursor.png");
directionArrow_ = TextureResources.LoadDllResource("LaneConnectionManager.direction_arrow.png", new IntVector2(256, 256));
+ deadEnd_ = TextureResources.LoadDllResource("LaneConnectionManager.dead_end.png", new IntVector2(512, 512));
}
/// State of the tool UI.
@@ -86,6 +87,8 @@ public enum StayInLaneMode {
private Texture2D directionArrow_;
+ private Texture2D deadEnd_;
+
private LaneEndTransitionGroup selectedNodeTransitionGroups_;
private LaneEndTransitionGroup selectedLaneTransitionGroup_;
@@ -141,6 +144,7 @@ private class LaneEnd {
internal readonly HashSet ConnectedCarLaneEnds = new();
internal readonly HashSet ConnectedTrackLaneEnds = new();
internal HashSet ConnectedLaneEnds(bool track) => track ? ConnectedTrackLaneEnds : ConnectedCarLaneEnds;
+ internal bool IsDeadEnd(bool track) => ConnectedLaneEnds(track).Contains(this);
internal Color Color;
internal SegmentLaneMarker SegmentMarker;
@@ -187,6 +191,28 @@ internal void RenderOverlay(
NodeMarker.RenderOverlay(cameraInfo, color, shape: shape, enlarge: highlight, renderLimits: renderLimits);
}
+
+ internal void RenderDeadEndSign(RenderManager.CameraInfo cameraInfo, Texture2D deadEnd, bool enlarge, bool overDraw) {
+ Vector3 pos = NodeMarker.Position;
+ pos -= NodeMarker.Direction * (enlarge ? 1.7f : 1.4f);
+ const float scale = .7f;
+
+ overDraw |= TrafficManagerTool.IsUndergroundMode;
+ float overdrawHeight = overDraw ? 0f : 0.5f;
+ float minY = pos.y - overdrawHeight;
+ float maxY = pos.y + overdrawHeight;
+
+ Highlight.DrawTextureAt(
+ cameraInfo: cameraInfo,
+ pos: pos,
+ dir: NodeMarker.Direction,
+ color: Color.white,
+ texture: deadEnd,
+ size: scale,
+ minY: minY,
+ maxY: maxY,
+ renderLimits: overDraw);
+ }
}
private float TransparentAlpha => ENABLE_TRANSPARENCY ? TrafficManagerTool.OverlayAlpha : 1f;
@@ -284,9 +310,11 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) {
RenderLaneCurves(cameraInfo);
if (!viewOnly) {
- RenderLaneOverlays(cameraInfo);
+ RenderLaneOverlays(cameraInfo); // render lane ends + dead end signs
RenderFloatingLaneCurve(cameraInfo);
- }
+ }
+
+ RenderDeadEnds(cameraInfo);
}
private void RenderNodeCircles(RenderManager.CameraInfo cameraInfo) {
@@ -350,6 +378,10 @@ private void RenderLaneCurves(RenderManager.CameraInfo cameraInfo) {
}
bool track = group == LaneEndTransitionGroup.Track;
+ if (laneEnd.IsDeadEnd(track)) {
+ continue;
+ }
+
foreach (LaneEnd targetLaneEnd in laneEnd.ConnectedLaneEnds(track)) {
ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane();
if (!targetLane.IsValidWithSegment()) {
@@ -379,11 +411,22 @@ private void RenderLaneCurves(RenderManager.CameraInfo cameraInfo) {
if (this.selectedLaneEnd != null) {
// lane curves for selectedMarker will be drawn last to be on the top of other lane markers.
+ if (hoveredLaneEnd == null) {
+ if (selectedLaneEnd.IntersectRay()) {
+ hoveredLaneEnd = selectedLaneEnd;
+ }
+ }
+
foreach (var group in ALL_GROUPS) {
if ((group & group_) == 0) {
continue;
}
bool track = group == LaneEndTransitionGroup.Track;
+ bool deadEnd = selectedLaneEnd.IsDeadEnd(track) || selectedLaneEnd == hoveredLaneEnd;
+ if (deadEnd) {
+ continue;
+ }
+
foreach (LaneEnd targetLaneEnd in this.selectedLaneEnd.ConnectedLaneEnds(track)) {
ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane();
if (!targetLane.IsValidWithSegment()) {
@@ -415,7 +458,7 @@ private void RenderFloatingLaneCurve(RenderManager.CameraInfo cameraInfo) {
Vector3 selNodePos = SelectedNodeId.ToNode().m_position;
// Draw a currently dragged curve
- if (hoveredLaneEnd == null || hoveredLaneEnd.LaneId == selectedLaneEnd.LaneId) {
+ if (hoveredLaneEnd == null) {
// get accurate position on a plane positioned at node height
Plane plane = new(Vector3.up, Vector3.zero.ChangeY(selNodePos.y));
Ray ray = InGameUtil.Instance.CachedMainCamera.ScreenPointToRay(Input.mousePosition);
@@ -434,41 +477,56 @@ private void RenderFloatingLaneCurve(RenderManager.CameraInfo cameraInfo) {
overDraw: true);
} else {
// snap to hovered, render accurate connection bezier
- Bezier3 bezier = CalculateBezierConnection(selectedLaneEnd, hoveredLaneEnd);
bool connected = LaneConnectionManager.Instance.AreLanesConnected(
selectedLaneEnd.LaneId, hoveredLaneEnd.LaneId, selectedLaneEnd.StartNode, group_);
Color fillColor = connected ?
Color.Lerp(a: selectedLaneEnd.Color, b: Color.white, t: 0.33f) : // show underneath color if there is connection.
default; // hollow if there isn't connection
- bool track = (group_ & LaneEndTransitionGroup.Track) != 0;
- bool showArrow = !connected && track && ShouldShowDirectionOfConnection(selectedLaneEnd, hoveredLaneEnd);
- DrawLaneCurve(
- cameraInfo: cameraInfo,
- bezier: ref bezier,
- color: fillColor,
- outlineColor: Color.white,
- arrowColor: default,
- arrowOutlineColor: showArrow ? Color.white : default,
- size: 0.18f, // Embolden
- overDraw: true);
- if (!connected && MultiMode && selectedLaneEnd.IsBidirectional && hoveredLaneEnd.IsBidirectional) {
- Bezier3 bezier2 = CalculateBezierConnection(hoveredLaneEnd, selectedLaneEnd);
- // draw backward arrow only:
- bool connected2 = LaneConnectionManager.Instance.AreLanesConnected(
- hoveredLaneEnd.LaneId, selectedLaneEnd.LaneId, selectedLaneEnd.StartNode, group_);
+ if (selectedLaneEnd == hoveredLaneEnd) {
+ Bezier3 bezier = CalculateDeadEndBezier(selectedLaneEnd);
DrawLaneCurve(
cameraInfo: cameraInfo,
- bezier: ref bezier2,
- color: default,
+ bezier: ref bezier,
+ color: fillColor,
outlineColor: Color.white,
arrowColor: default,
- arrowOutlineColor: connected2 ? default : Color.white,
+ arrowOutlineColor: default,
+ size: 0.18f, // Embolden
+ overDraw: true,
+ subDivide: true);
+ } else {
+ bool track = (group_ & LaneEndTransitionGroup.Track) != 0;
+ bool showArrow = !connected && track && ShouldShowDirectionOfConnection(selectedLaneEnd, hoveredLaneEnd);
+ Bezier3 bezier = CalculateBezierConnection(selectedLaneEnd, hoveredLaneEnd);
+
+ DrawLaneCurve(
+ cameraInfo: cameraInfo,
+ bezier: ref bezier,
+ color: fillColor,
+ outlineColor: Color.white,
+ arrowColor: default,
+ arrowOutlineColor: showArrow ? Color.white : default,
size: 0.18f, // Embolden
overDraw: true);
- }
+ if (!connected && MultiMode && selectedLaneEnd.IsBidirectional && hoveredLaneEnd.IsBidirectional) {
+ Bezier3 bezier2 = CalculateBezierConnection(hoveredLaneEnd, selectedLaneEnd);
+ // draw backward arrow only:
+ bool connected2 = LaneConnectionManager.Instance.AreLanesConnected(
+ hoveredLaneEnd.LaneId, selectedLaneEnd.LaneId, selectedLaneEnd.StartNode, group_);
+ DrawLaneCurve(
+ cameraInfo: cameraInfo,
+ bezier: ref bezier2,
+ color: default,
+ outlineColor: Color.white,
+ arrowColor: default,
+ arrowOutlineColor: connected2 ? default : Color.white,
+ size: 0.18f, // Embolden
+ overDraw: true);
+ }
- OverrideCursor = connected ? removeCursor_ : addCursor_;
+ OverrideCursor = connected ? removeCursor_ : addCursor_;
+ }
}
}
@@ -485,7 +543,7 @@ private void RenderLaneOverlays(RenderManager.CameraInfo cameraInfo) {
bool targetMode = GetSelectionMode() == SelectionMode.SelectTarget;
if (sourceMode & laneEnd.IsSource) {
// draw source marker in source selection mode,
- // make exception for markers that have no target:
+ // make exception for markers that can have no target:
foreach (var targetLaneEnd in laneEnds) {
if (CanConnect(laneEnd, targetLaneEnd, group_, out bool acute2)) {
drawMarker = true;
@@ -529,6 +587,41 @@ private void RenderLaneOverlays(RenderManager.CameraInfo cameraInfo) {
}
}
+ private void RenderDeadEnds(RenderManager.CameraInfo cameraInfo) {
+ for (int cacheIndex = CachedVisibleNodeIds.Size - 1; cacheIndex >= 0; cacheIndex--) {
+ ushort nodeId = CachedVisibleNodeIds.Values[cacheIndex];
+ bool isVisible = MainTool.IsNodeVisible(nodeId);
+ bool hasMarkers = currentLaneEnds_.TryGetValue(nodeId, out List laneEnds);
+
+ if (!isVisible || !hasMarkers) {
+ continue;
+ }
+
+ LaneEndTransitionGroup groupAtNode = group_;
+ if (nodeId != SelectedNodeId) {
+ if (AltIsPressed) {
+ groupAtNode = LaneEndTransitionGroup.Track;
+ } else {
+ groupAtNode = LaneEndTransitionGroup.Vehicle;
+ }
+ }
+
+ foreach (LaneEnd laneEnd in laneEnds) {
+ foreach (var group in ALL_GROUPS) {
+ if ((group & groupAtNode) != 0) {
+ bool track = group == LaneEndTransitionGroup.Track;
+ bool deadEnd = laneEnd.IsDeadEnd(track);
+ if (deadEnd) {
+ bool enlarge = laneEnd == hoveredLaneEnd || laneEnd == selectedLaneEnd;
+ bool overDraw = nodeId == SelectedNodeId;
+ laneEnd.RenderDeadEndSign(cameraInfo, deadEnd_, enlarge: enlarge, overDraw: overDraw);
+ }
+ }
+ }
+ }
+ }
+ }
+
// TODO: use the new StateMachine after migrating from LegacySubTool to TrafficManagerSubTool
private void HandleStateMachine() {
if ((frameClearPressed > 0) && ((Time.frameCount - frameClearPressed) < 20)) {
@@ -536,6 +629,7 @@ private void HandleStateMachine() {
frameClearPressed = 0; // consumed
// remove all connections at selected node
LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(SelectedNodeId);
+ selectedLaneEnd = null;
RefreshCurrentNodeMarkers(SelectedNodeId);
}
@@ -1104,15 +1198,16 @@ public override void OnPrimaryClickOverlay() {
} else if (GetSelectionMode() == SelectionMode.SelectTarget) {
// toggle lane connection
bool canBeBidirectional = selectedLaneEnd.IsBidirectional && hoveredLaneEnd.IsBidirectional;
+ bool deadEnd = selectedLaneEnd == hoveredLaneEnd; // we are toggling dead end.
if (LaneConnectionManager.Instance.AreLanesConnected(
selectedLaneEnd.LaneId, hoveredLaneEnd.LaneId, selectedLaneEnd.StartNode, group_)) {
RemoveLaneConnection(selectedLaneEnd, hoveredLaneEnd, group_);
- if (canBeBidirectional && ShiftIsPressed) {
+ if (!deadEnd && canBeBidirectional && ShiftIsPressed) {
RemoveLaneConnection(hoveredLaneEnd, selectedLaneEnd, group_);
}
} else {
AddLaneConnection(selectedLaneEnd, hoveredLaneEnd, group_);
- if (canBeBidirectional && ShiftIsPressed) {
+ if (!deadEnd && canBeBidirectional && ShiftIsPressed) {
AddLaneConnection(hoveredLaneEnd, selectedLaneEnd, group_);
}
}
@@ -1122,29 +1217,37 @@ public override void OnPrimaryClickOverlay() {
MainTool.RequestOnscreenDisplayUpdate();
}
}
+
private static void UpdateConnectionTwoway(LaneEnd laneEnd1, LaneEnd laneEnd2) {
UpdateConnection(laneEnd1, laneEnd2);
- UpdateConnection(laneEnd2, laneEnd1);
+ if (laneEnd1 != laneEnd2) {
+ UpdateConnection(laneEnd2, laneEnd1);
+ }
}
private static void UpdateConnection(LaneEnd source, LaneEnd target) {
Log._Debug($"LaneConnectorTool.UpdateConnection({source.LaneId}, {target.LaneId}) called at node{source.NodeId})");
- if (LaneConnectionManager.Instance.Road.AreLanesConnected(
- source.LaneId, target.LaneId, source.StartNode)) {
- source.ConnectedCarLaneEnds.Add(target);
- Log._Debug("there is car connection");
- } else {
- source.ConnectedCarLaneEnds.Remove(target);
- Log._Debug("there is no car connection");
- }
-
- if (LaneConnectionManager.Instance.Track.AreLanesConnected(
- source.LaneId, target.LaneId, source.StartNode)) {
- source.ConnectedTrackLaneEnds.Add(target);
- Log._Debug("there is track connection");
- } else {
- source.ConnectedTrackLaneEnds.Remove(target);
- Log._Debug("there is no track connection");
+ bool deadEnd = source == target;
+ foreach (var group in ALL_GROUPS) {
+ bool track = group == LaneEndTransitionGroup.Track;
+ if (deadEnd) {
+ // when dead end connection is made, remove all other connections.
+ source.ConnectedLaneEnds(track).Clear();
+ Log._Debug($"cleared cached {group} connections");
+ } else if (!LaneConnectionManager.Instance.SubManager(track).AreLanesConnected(
+ source.LaneId, source.LaneId, source.StartNode)) {
+ // when new connection is made, remove previous dead end connection.
+ source.ConnectedLaneEnds(track).Remove(source);
+ Log._Debug($"removed cached {group} dead end");
+ }
+ if (LaneConnectionManager.Instance.SubManager(track).AreLanesConnected(
+ source.LaneId, target.LaneId, source.StartNode)) {
+ source.ConnectedLaneEnds(track).Add(target);
+ Log._Debug($"there is {group} connection");
+ } else {
+ source.ConnectedLaneEnds(track).Remove(target);
+ Log._Debug($"there is no {group} connection");
+ }
}
}
@@ -1232,6 +1335,7 @@ private void RefreshCurrentNodeMarkers(ushort forceNodeId = 0) {
currentLaneEnds_.Clear();
} else {
currentLaneEnds_.Remove(forceNodeId);
+
}
for (ushort nodeId = forceNodeId == 0 ? (ushort)1 : forceNodeId;
@@ -1412,22 +1516,15 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node, out La
continue;
}
- uint[] carConnections = LaneConnectionManager.Instance.Road.GetLaneConnections(
- sourceLaneEnd.LaneId, sourceLaneEnd.StartNode);
- if (!carConnections.IsNullOrEmpty()) {
- foreach (LaneEnd targetLaneEnd in laneEnds) {
- if (targetLaneEnd.IsTarget && carConnections.Contains(targetLaneEnd.LaneId)) {
- sourceLaneEnd.ConnectedCarLaneEnds.Add(targetLaneEnd);
- }
- }
- }
-
- uint[] trackConnections = LaneConnectionManager.Instance.Track.GetLaneConnections(
- sourceLaneEnd.LaneId, sourceLaneEnd.StartNode);
- if (!trackConnections.IsNullOrEmpty()) {
- foreach (LaneEnd targetLaneEnd in laneEnds) {
- if (targetLaneEnd.IsTarget && trackConnections.Contains(targetLaneEnd.LaneId)) {
- sourceLaneEnd.ConnectedTrackLaneEnds.Add(targetLaneEnd);
+ foreach(var group in ALL_GROUPS) {
+ bool track = group == LaneEndTransitionGroup.Track;
+ uint[] connections = LaneConnectionManager.Instance.SubManager(track)
+ .GetLaneConnections(sourceLaneEnd.LaneId, sourceLaneEnd.StartNode);
+ if (!connections.IsNullOrEmpty()) {
+ foreach (LaneEnd targetLaneEnd in laneEnds) {
+ if ((targetLaneEnd.IsTarget || targetLaneEnd == sourceLaneEnd) && connections.Contains(targetLaneEnd.LaneId)) {
+ sourceLaneEnd.ConnectedLaneEnds(track).Add(targetLaneEnd);
+ }
}
}
}
@@ -1565,6 +1662,7 @@ private void DrawLaneCurve(RenderManager.CameraInfo cameraInfo,
alphaBlend: arrowColor.a == 0f, // avoid strange shape.
renderLimits: overDraw);
}
+
if (outlineColor.a != 0) {
Highlight.DrawBezier(
cameraInfo: cameraInfo,
@@ -1620,24 +1718,30 @@ private Bezier3 CalculateBezierConnection(LaneEnd sourceLaneEnd, LaneEnd targetL
Bezier3 bezier3 = default;
bezier3.a = sourceLaneEnd.NodeMarker.Position;
bezier3.d = targetLaneEnd.NodeMarker.Position;
+ Vector3 dira = -sourceLaneEnd.NodeMarker.Direction;
+ Vector3 dird = -targetLaneEnd.NodeMarker.Direction;
- Vector3 sourceLaneDirection =
- (sourceLaneEnd.LaneId.ToLane().m_bezier
- .Tangent(sourceLaneEnd.StartNode ? 0f : 1f) *
- (sourceLaneEnd.StartNode ? -1 : 1)).normalized;
- Vector3 targetLaneDirection =
- (targetLaneEnd.LaneId.ToLane().m_bezier
- .Tangent(targetLaneEnd.StartNode ? 0f : 1f) *
- (targetLaneEnd.StartNode ? -1 : 1)).normalized;
NetSegment.CalculateMiddlePoints(
- bezier3.a,
- sourceLaneDirection,
- bezier3.d,
- targetLaneDirection,
- false,
- false,
- out bezier3.b,
- out bezier3.c);
+ bezier3.a,
+ dira,
+ bezier3.d,
+ dird,
+ false,
+ false,
+ out bezier3.b,
+ out bezier3.c);
+ return bezier3;
+ }
+
+ private Bezier3 CalculateDeadEndBezier(LaneEnd laneEnd) {
+ Bezier3 bezier3 = default;
+ Vector3 dir = -laneEnd.NodeMarker.Direction * 10;
+ bezier3.d = bezier3.a = laneEnd.NodeMarker.Position + dir * .1f; // move forward a bit to avoid rendering over the dead End Icon.
+
+ const float angle = Mathf.PI / 4;
+ bezier3.b = bezier3.a + dir.RotateXZ(angle);
+ bezier3.c = bezier3.d + dir.RotateXZ(-angle);
+
return bezier3;
}
diff --git a/TLM/TLM/Util/Extensions/NetSegmentExtensions.cs b/TLM/TLM/Util/Extensions/NetSegmentExtensions.cs
index 229be46af..c2258c1d4 100644
--- a/TLM/TLM/Util/Extensions/NetSegmentExtensions.cs
+++ b/TLM/TLM/Util/Extensions/NetSegmentExtensions.cs
@@ -169,8 +169,10 @@ public static bool AnyApplicableLane(
NetInfo.LaneType laneType,
VehicleInfo.VehicleType vehicleType) {
+#if DEBUG
AssertNotNone(laneType, nameof(laneType));
AssertNotNone(vehicleType, nameof(vehicleType));
+#endif
NetInfo segmentInfo = netSegment.Info;
diff --git a/TLM/TLM/Util/Shortcuts.cs b/TLM/TLM/Util/Shortcuts.cs
index bf60f6749..b8e022237 100644
--- a/TLM/TLM/Util/Shortcuts.cs
+++ b/TLM/TLM/Util/Shortcuts.cs
@@ -143,7 +143,6 @@ internal static void AssertNotNull(object obj, string m = "") {
///
/// Thrown if is not some kind of .
///
- [Conditional("DEBUG")]
internal static void AssertNotNone(T value, string m = "") {
if (!typeof(T).IsEnum)
throw new ArgumentException($"Type '{typeof(T).FullName}' is not an enum");
diff --git a/TLM/TLM/Util/TMPEVectorUtil.cs b/TLM/TLM/Util/TMPEVectorUtil.cs
index 264e71ec2..fc8a6ca5c 100644
--- a/TLM/TLM/Util/TMPEVectorUtil.cs
+++ b/TLM/TLM/Util/TMPEVectorUtil.cs
@@ -7,5 +7,18 @@ internal static Vector3 RotateXZ90CW(this Vector3 v) =>
internal static Vector3 RotateXZ90CCW(this Vector3 v) =>
new Vector3(-v.z, v.y, v.x);
+ ///
+ /// rotate counter clockwise
+ ///
+ /// in radians
+ internal static Vector3 RotateXZ(this Vector3 v, float angle) {
+ var cos = Mathf.Cos(angle);
+ var sin = Mathf.Sin(angle);
+ return new Vector3 {
+ x = v.x * cos - v.z * sin,
+ y = v.y,
+ z = v.x * sin + v.z * cos,
+ };
+ }
}
}