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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + 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, + }; + } } }