diff --git a/TLM/TLM/Manager/Impl/ExtNodeManager.cs b/TLM/TLM/Manager/Impl/ExtNodeManager.cs index 4a935c1db..7a75b416c 100644 --- a/TLM/TLM/Manager/Impl/ExtNodeManager.cs +++ b/TLM/TLM/Manager/Impl/ExtNodeManager.cs @@ -38,7 +38,7 @@ private ExtNodeManager() { /// NodeId of the node to test. /// public static bool JunctionHasHighwayRules(ushort nodeId) { - return JunctionHasOnlyHighwayRoads(nodeId) && !LaneConnection.LaneConnectionManager.Instance.Sub.HasNodeConnections(nodeId); + return JunctionHasOnlyHighwayRoads(nodeId) && !LaneConnection.LaneConnectionManager.Instance.Road.HasNodeConnections(nodeId); } public GetNodeSegmentIdsEnumerable GetNodeSegmentIds(ushort nodeId, ClockDirection clockDirection) { diff --git a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs index 52806d2b7..bf0a3ffe1 100644 --- a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs +++ b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionManager.cs @@ -4,6 +4,7 @@ namespace TrafficManager.Manager.Impl.LaneConnection { using TrafficManager.API.Manager; using TrafficManager.API.Traffic.Data; using TrafficManager.API.Traffic.Enums; + using TrafficManager.Util; using TrafficManager.Util.Extensions; using UnityEngine; #if DEBUG @@ -23,8 +24,8 @@ public class LaneConnectionManager | VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Trolleybus; - public LaneConnectionSubManager Sub = // TODO #354 divide into Road/Track - new LaneConnectionSubManager(LaneEndTransitionGroup.Vehicle); + public LaneConnectionSubManager Road = new LaneConnectionSubManager(LaneEndTransitionGroup.Road); + public LaneConnectionSubManager Track = new LaneConnectionSubManager(LaneEndTransitionGroup.Track); public NetInfo.LaneType LaneTypes => LANE_TYPES; @@ -38,16 +39,19 @@ static LaneConnectionManager() { public override void OnBeforeLoadData() { base.OnBeforeLoadData(); - Sub.OnBeforeLoadData(); + Road.OnBeforeLoadData(); + Track.OnBeforeLoadData(); } public override void OnLevelUnloading() { base.OnLevelUnloading(); - Sub.OnLevelUnloading(); + Road.OnLevelUnloading(); + Track.OnLevelUnloading(); } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); - Sub.PrintDebugInfo(); + Road.PrintDebugInfo(); + Track.PrintDebugInfo(); } /// @@ -55,7 +59,60 @@ protected override void InternalPrintDebugInfo() { /// /// check at start node of source lane? public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { - return Sub.AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode); + return Road.AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode) || + Track.AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode); + } + + public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceStartNode, LaneEndTransitionGroup group) { + bool ret = Road.Supports(group) && + Road.AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode); + if (!ret) { + ret = Track.Supports(group) && + Track.AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode); + } + return ret; + } + + + /// + /// Adds a lane connection between two lanes. + /// pass in LaneEndTransitionGroup.All to add lane connections in every sub manager that supports both lanes. + /// + /// From lane id + /// To lane id + /// The affected node + /// lane or track + /// true if any connection was added, falsse otherwise + public bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode, LaneEndTransitionGroup group) { + bool success = true; + if (Road.Supports(group)) { + success = Road.AddLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } + if (Track.Supports(group)) { + success &= Track.AddLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } + return success; + } + + public bool RemoveLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode, LaneEndTransitionGroup group) { + bool success = true; + var sourceLaneInfo = ExtLaneManager.Instance.GetLaneInfo(sourceLaneId); + var targetLaneInfo = ExtLaneManager.Instance.GetLaneInfo(targetLaneId); + if (Road.Supports(group)) { + success = Road.RemoveLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } else { + bool canConnect = Road.Supports(sourceLaneInfo) && Road.Supports(targetLaneInfo); + if (!canConnect) + Road.RemoveLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } + if (Track.Supports(group)) { + success |= Track.RemoveLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } else { + bool canConnect = Track.Supports(sourceLaneInfo) && Track.Supports(targetLaneInfo); + if (!canConnect) + Track.RemoveLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); + } + return success; } /// @@ -63,23 +120,60 @@ public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceS /// Performance note: This act as HasOutgoingConnections for uni-directional lanes but faster /// public bool HasConnections(uint laneId, bool startNode) => - Sub.HasConnections(laneId, startNode); + Road.HasConnections(laneId, startNode) || Track.HasConnections(laneId, startNode); /// /// Determines if there exist custom lane connections at the specified node /// - public bool HasNodeConnections(ushort nodeId) => Sub.HasNodeConnections(nodeId); + public bool HasNodeConnections(ushort nodeId) => + Road.HasNodeConnections(nodeId) || Track.HasNodeConnections(nodeId); // Note: Not performance critical public bool HasUturnConnections(ushort segmentId, bool startNode) => - Sub.HasUturnConnections(segmentId, startNode); + Road.HasUturnConnections(segmentId, startNode); /// /// Removes all lane connections at the specified node /// /// Affected node internal void RemoveLaneConnectionsFromNode(ushort nodeId) { - Sub.RemoveLaneConnectionsFromNode(nodeId); + Road.RemoveLaneConnectionsFromNode(nodeId); + Track.RemoveLaneConnectionsFromNode(nodeId); + } + + + /// + /// Checks if the turning angle between two segments at the given node is within bounds. + /// + public static bool CheckSegmentsTurningAngle(ushort sourceSegmentId, + bool sourceStartNode, + ushort targetSegmentId, + bool targetStartNode) { + if (sourceSegmentId == targetSegmentId) { + return false; + } + + ref NetSegment sourceSegment = ref sourceSegmentId.ToSegment(); + ref NetSegment targetSegment = ref targetSegmentId.ToSegment(); + float turningAngle = 0.01f - Mathf.Min( + sourceSegment.Info.m_maxTurnAngleCos, + targetSegment.Info.m_maxTurnAngleCos); + + if (turningAngle < 1f) { + Vector3 sourceDirection = sourceStartNode + ? sourceSegment.m_startDirection + : sourceSegment.m_endDirection; + + Vector3 targetDirection = targetStartNode + ? targetSegment.m_startDirection + : targetSegment.m_endDirection; + + float dirDotProd = (sourceDirection.x * targetDirection.x) + + (sourceDirection.z * targetDirection.z); + return dirDotProd < turningAngle; + } + + return true; } internal bool GetLaneEndPoint(ushort segmentId, @@ -170,14 +264,17 @@ internal bool GetLaneEndPoint(ushort segmentId, } public bool LoadData(List data) { - bool success = true; + bool success; Log.Info($"Loading {data.Count} lane connections"); - success = Sub.LoadData(data); + success = Road.LoadData(data); + success &= Track.LoadData(data); return success; } public List SaveData(ref bool success) { - return Sub.SaveData(ref success); + var ret = Road.SaveData(ref success); + ret.AddRange(Track.SaveData(ref success)); + return ret; } } } \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs index fb68c7144..bf34aa2fe 100644 --- a/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs +++ b/TLM/TLM/Manager/Impl/LaneConnection/LaneConnectionSubManager.cs @@ -9,11 +9,8 @@ namespace TrafficManager.Manager.Impl.LaneConnection { using TrafficManager.Lifecycle; using TrafficManager.State; using TrafficManager.Util.Extensions; - using UnityEngine; using static TrafficManager.Util.Shortcuts; using TrafficManager.Util; - using TrafficManager.Util.Extensions; - using TrafficManager.Lifecycle; using TrafficManager.Patch; #if DEBUG using TrafficManager.State.ConfigData; @@ -24,13 +21,36 @@ public class LaneConnectionSubManager : ICustomDataManager>, ILaneConnectionManager { private ConnectionDataBase connectionDataBase_; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "RAS0002:Readonly field for a non-readonly struct", Justification = "False alarm")] +#pragma warning disable RAS0002 // Readonly field for a non-readonly struct public readonly LaneEndTransitionGroup Group; + public readonly NetInfo.LaneType laneTypes_; + public readonly VehicleInfo.VehicleType vehicleTypes_; +#pragma warning restore RAS0002 // Readonly field for a non-readonly struct + + internal LaneConnectionSubManager(LaneEndTransitionGroup group) { + Group = group; + laneTypes_ = default; + vehicleTypes_ = default; + if (Group == LaneEndTransitionGroup.Road) { + laneTypes_ |= TrackUtils.ROAD_LANE_TYPES; + vehicleTypes_ |= TrackUtils.ROAD_VEHICLE_TYPES; + } + if (Group == LaneEndTransitionGroup.Track) { + laneTypes_ |= TrackUtils.TRACK_LANE_TYPES; + vehicleTypes_ |= TrackUtils.TRACK_VEHICLE_TYPES; + } + } - internal LaneConnectionSubManager(LaneEndTransitionGroup group) => Group = group; + public NetInfo.LaneType LaneTypes => laneTypes_; + + public VehicleInfo.VehicleType VehicleTypes => vehicleTypes_; + + /// + /// tests if the input group is supported by this sub-manager. + /// + public bool Supports(LaneEndTransitionGroup group) => (group & Group) != 0; - public NetInfo.LaneType LaneTypes => LaneConnectionManager.LANE_TYPES; - public VehicleInfo.VehicleType VehicleTypes => LaneConnectionManager.VEHICLE_TYPES; + public bool Supports(NetInfo.Lane laneInfo) => laneInfo.Matches(laneTypes_, vehicleTypes_); private LaneConnectionSubManager() { NetManagerEvents.Instance.ReleasingSegment += ReleasingSegment; @@ -242,7 +262,7 @@ internal bool RemoveLaneConnection(uint sourceLaneId, uint targetLaneId, bool so return false; } - if ((Group & LaneEndTransitionGroup.Road) != 0) { + if (Supports(LaneEndTransitionGroup.Road)) { RecalculateLaneArrows(sourceLaneId, nodeId, sourceStartNode); } @@ -340,9 +360,48 @@ internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourc return false; } + var sourceLaneInfo = ExtLaneManager.Instance.GetLaneInfo(sourceLaneId); + var targetLaneInfo = ExtLaneManager.Instance.GetLaneInfo(targetLaneId); + ref NetLane sourceNetLane = ref sourceLaneId.ToLane(); + ref NetLane targetNetLane = ref targetLaneId.ToLane(); + bool canConnect = Supports(sourceLaneInfo) && Supports(targetLaneInfo); + if (!canConnect) { + return false; + } + ushort sourceSegmentId = sourceLaneId.ToLane().m_segment; ushort targetSegmentId = targetLaneId.ToLane().m_segment; ushort nodeId = sourceSegmentId.ToSegment().GetNodeId(sourceStartNode); + ref NetNode netNode = ref nodeId.ToNode(); + + // check if source lane goes toward the node + // and target lane goes away from the node. + static bool IsDirectionValid(ref NetLane lane, NetInfo.Lane laneInfo, ushort nodeId, bool source) { + bool invert = lane.m_segment.ToSegment().m_flags.IsFlagSet(NetSegment.Flags.Invert); + bool startNode = lane.IsStartNode(nodeId); + var dir = laneInfo.m_finalDirection; + if (source ^ startNode ^ invert) { + return dir.IsFlagSet(NetInfo.Direction.Forward); + } else { + return dir.IsFlagSet(NetInfo.Direction.Backward); + } + } + canConnect = + IsDirectionValid(ref sourceNetLane, sourceLaneInfo, nodeId, true) && + IsDirectionValid(ref targetNetLane, targetLaneInfo, nodeId, false); + if (!canConnect) { + return false; + } + + if (Group == LaneEndTransitionGroup.Track) { + bool targetStartnode = targetSegmentId.ToSegment().IsStartNode(nodeId); + canConnect = LaneConnectionManager.CheckSegmentsTurningAngle( + sourceSegmentId, sourceStartNode, targetSegmentId, targetStartnode); + if (!canConnect) { + return false; + } + } + connectionDataBase_.ConnectTo(sourceLaneId, targetLaneId, nodeId); Assert(AreLanesConnected(sourceLaneId, targetLaneId, sourceStartNode), $"AreLanesConnected({sourceLaneId}, {targetLaneId}, {sourceStartNode})"); @@ -357,9 +416,9 @@ internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourc $"{targetLaneId}, {sourceStartNode})"); } - - - RecalculateLaneArrows(sourceLaneId, nodeId, sourceStartNode); + if (Supports(LaneEndTransitionGroup.Road)) { + RecalculateLaneArrows(sourceLaneId, nodeId, sourceStartNode); + } if (sourceSegmentId == targetSegmentId) { JunctionRestrictionsManager.Instance.SetUturnAllowed( @@ -368,8 +427,7 @@ internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourc true); } - RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); - RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); + RoutingManager.Instance.RequestNodeRecalculation(ref netNode); if (TMPELifecycle.Instance.MayPublishSegmentChanges()) { ExtSegmentManager extSegmentManager = ExtSegmentManager.Instance; @@ -388,7 +446,7 @@ private void ReleasingSegment(ushort segmentId, ref NetSegment segment) { const bool logLaneConnections = false; #endif if (logLaneConnections) { - Log._Debug($"LaneConnectionManager.ReleasingSegment({segmentId}, isValid={segment.IsValid()}): " + + Log._Debug($"LaneConnectionSubManager({Group}).ReleasingSegment({segmentId}, isValid={segment.IsValid()}): " + "Segment is about to become invalid. Removing lane connections."); } @@ -402,76 +460,6 @@ private void ReleasingSegment(ushort segmentId, ref NetSegment segment) { } } - internal bool GetLaneEndPoint(ushort segmentId, - bool startNode, - byte laneIndex, - uint? laneId, - NetInfo.Lane laneInfo, - out bool outgoing, - out bool incoming, - out Vector3? pos) { - ref NetSegment netSegment = ref segmentId.ToSegment(); - - pos = null; - outgoing = false; - incoming = false; - - if ((netSegment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { - return false; - } - - if (laneId == null) { - laneId = ExtSegmentManager.Instance.GetLaneId(segmentId, laneIndex); - if (laneId == 0) { - return false; - } - } - - ref NetLane netLane = ref ((uint)laneId).ToLane(); - - if ((netLane.m_flags & - ((ushort)NetLane.Flags.Created | (ushort)NetLane.Flags.Deleted)) != - (ushort)NetLane.Flags.Created) { - return false; - } - - if (laneInfo == null) { - if (laneIndex < netSegment.Info.m_lanes.Length) { - laneInfo = netSegment.Info.m_lanes[laneIndex]; - } else { - return false; - } - } - - NetInfo.Direction laneDir = ((netSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) - ? laneInfo.m_finalDirection - : NetInfo.InvertDirection(laneInfo.m_finalDirection); - - if (startNode) { - if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) { - outgoing = true; - } - - if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { - incoming = true; - } - - pos = netLane.m_bezier.a; - } else { - if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { - outgoing = true; - } - - if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) { - incoming = true; - } - - pos = netLane.m_bezier.d; - } - - return true; - } - /// /// Recalculates lane arrows based on present lane connections. /// @@ -701,6 +689,10 @@ public bool LoadData(List data) { foreach (Configuration.LaneConnection conn in data) { try { + if(!Supports(conn.group)) { + continue; + } + ref NetLane lowerLane = ref conn.sourceLaneId.ToLane(); if (!lowerLane.IsValidWithSegment()) { continue; @@ -719,7 +711,7 @@ public bool LoadData(List data) { Log._Debug($"Loading lane connection: lane {conn.sourceLaneId} -> {conn.targetLaneId}"); #endif AddLaneConnection(conn.sourceLaneId, conn.targetLaneId, conn.sourceStartNode); - if (conn.Legacy) { + if (conn.LegacyBidirectional) { ushort segmentId = conn.sourceLaneId.ToLane().m_segment; ushort nodeId = segmentId.ToSegment().GetNodeId(conn.sourceStartNode); bool targetStartNode = conn.targetLaneId.ToLane().IsStartNode(nodeId); @@ -753,7 +745,8 @@ public bool LoadData(List data) { new Configuration.LaneConnection( source.LaneId, target.LaneId, - source.StartNode)); + source.StartNode, + Group)); } } catch (Exception e) { Log.Error($"Exception occurred while saving lane data @ {source.LaneId}: {e.ToString()}"); diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index b52f2775c..c8f280003 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -676,11 +676,11 @@ void _ExtendedLogImpl(params object[] lines) => DetailLogger.LogDebug( // routing tracked vehicles (trains, trams, metros, monorails) // lane may be mixed car+tram bool nextHasConnections = - LaneConnectionManager.Instance.Sub.HasConnections( + LaneConnectionManager.Instance.Track.HasConnections( nextLaneId, isNodeStartNodeOfNextSegment); if (nextHasConnections) { - bool connected = LaneConnectionManager.Instance.Sub.AreLanesConnected( + bool connected = LaneConnectionManager.Instance.Track.AreLanesConnected( nextLaneId, prevLaneId, isNodeStartNodeOfNextSegment); @@ -763,11 +763,11 @@ void _ExtendedLogImpl(params object[] lines) => DetailLogger.LogDebug( bool connected = true; bool nextHasConnections = - LaneConnectionManager.Instance.Sub.HasConnections( + LaneConnectionManager.Instance.Road.HasConnections( nextLaneId, isNodeStartNodeOfNextSegment); if (nextHasConnections) { - connected = LaneConnectionManager.Instance.Sub.AreLanesConnected( + connected = LaneConnectionManager.Instance.Road.AreLanesConnected( nextLaneId, prevLaneId, isNodeStartNodeOfNextSegment); @@ -1082,7 +1082,7 @@ bool laneChangesAllowed // skip lanes having lane connections // in highway-rules HasConnections() gives the same result as HasOutgoingConnections but faster. - if (LaneConnectionManager.Instance.Sub.HasConnections( + if (LaneConnectionManager.Instance.Road.HasConnections( nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNodeStartNodeOfNextSegment)) { int laneConnectionTransIndex = @@ -1404,7 +1404,8 @@ bool laneChangesAllowed } // skip lanes having lane connections - if (LaneConnectionManager.Instance.Sub.HasOutgoingConnections( + // nextCompatibleTransitionDatas is only for roads. + if (LaneConnectionManager.Instance.Road.HasOutgoingConnections( nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNodeStartNodeOfNextSegment)) { int laneConnectionTransIndex = diff --git a/TLM/TLM/Resources/LaneConnectionManager/direction_arrow.png b/TLM/TLM/Resources/LaneConnectionManager/direction_arrow.png index 4b18f7b53..0d21786a3 100644 Binary files a/TLM/TLM/Resources/LaneConnectionManager/direction_arrow.png and b/TLM/TLM/Resources/LaneConnectionManager/direction_arrow.png differ diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index 9a8d13876..126517e22 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -9,6 +9,7 @@ namespace TrafficManager { using System.Runtime.Serialization; using TrafficManager.Lifecycle; using Util; + using LaneEndTransitionGroup = TrafficManager.API.Traffic.Enums.LaneEndTransitionGroup; [Serializable] public class Configuration { @@ -186,12 +187,15 @@ public class LaneConnection : ISerializable { public bool sourceStartNode; - public bool Legacy => SerializableDataExtension.Version < 2; + public LaneEndTransitionGroup group = LaneEndTransitionGroup.Vehicle; - public LaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { + public bool LegacyBidirectional => SerializableDataExtension.Version < 2; + + public LaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode, LaneEndTransitionGroup group) { this.sourceLaneId = sourceLaneId; this.targetLaneId = targetLaneId; this.sourceStartNode = sourceStartNode; + this.group = group; } //serialization @@ -215,6 +219,9 @@ public LaneConnection(SerializationInfo info, StreamingContext context) { case nameof(sourceStartNode): sourceStartNode = (bool)item.Value; break; + case nameof(group): + group = (LaneEndTransitionGroup)item.Value; + break; } } } diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index f291727df..78b8a0049 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -315,7 +315,7 @@ public static bool ResetLaneArrowFlags(uint laneId) { #if DEBUGFLAGS Log._Debug($"Flags.resetLaneArrowFlags: Resetting lane arrows of lane {laneId}."); #endif - if (LaneConnectionManager.Instance.Sub.HasOutgoingConnections(laneId)) { + if (LaneConnectionManager.Instance.Road.HasOutgoingConnections(laneId)) { return false; } @@ -392,7 +392,7 @@ public static bool ToggleLaneArrowFlags(uint laneId, return false; // disallow custom lane arrows in highway rule mode } - if (LaneConnectionManager.Instance.Sub.HasOutgoingConnections(laneId, startNode)) { + if (LaneConnectionManager.Instance.Road.HasOutgoingConnections(laneId, startNode)) { // TODO refactor res = SetLaneArrow_Result.LaneConnection; return false; // custom lane connection present diff --git a/TLM/TLM/UI/Helpers/Highlight.cs b/TLM/TLM/UI/Helpers/Highlight.cs index 3b3d947ea..14b8e6768 100644 --- a/TLM/TLM/UI/Helpers/Highlight.cs +++ b/TLM/TLM/UI/Helpers/Highlight.cs @@ -11,6 +11,12 @@ namespace TrafficManager.UI.Helpers { /// Must be called from GUI callbacks only, will not work from other code. /// public static class Highlight { + public enum Shape { + Circle, + Square, + Diamond, + } + /// /// Create this to describe a grid for rendering multiple icons. /// Icons are positioned in the XZ plane in the world around the GridOrigin, but rendered @@ -117,6 +123,9 @@ public bool DrawGenericOverlayGridTexture(Texture2D texture, } } + public static Texture2D SquareTexture; + public static Texture2D TriangleTexture; + public static void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, bool warning = false, @@ -153,7 +162,7 @@ public static bool IsNodeVisible(ushort node) { } /// - /// draw triangular arrow head at the given of the + /// draw triangular (sides = 2, 2.24, 2.24) arrow head at the given of the /// public static void DrawArrowHead( RenderManager.CameraInfo cameraInfo, @@ -190,7 +199,7 @@ public static void DrawArrowHead( } /// - /// draw triangular arrow head at the given of the + /// draw triangular (sides = 2, 2.24, 2.24) arrow head at the given of the /// public static void DrawArrowHead( RenderManager.CameraInfo cameraInfo, @@ -224,39 +233,57 @@ public static void DrawArrowHead( alphaBlend: alphaBlend); } - /// - /// draw '>' shaped arrow head at the given of the - /// - public static void DrawArrowHead2( + + + public static void DrawShape( RenderManager.CameraInfo cameraInfo, - ref Bezier3 bezier, - float t, + Shape shape, Color color, + Vector3 center, + Vector3 tangent, float size, - float length, float minY, float maxY, bool renderLimits, - bool alphaBlend = false) { - Vector3 center = bezier.Position(t); - Vector3 dir = bezier.Tangent(t).normalized * length; - Vector3 dir90 = dir.RotateXZ90CW(); - - Segment3 line1 = new(center + dir, center + dir90); - Segment3 line2 = new(center + dir, center - dir90); - + bool alphaBlend) { Singleton.instance.m_drawCallData.m_overlayCalls++; - RenderManager.instance.OverlayEffect.DrawSegment( - cameraInfo, - color, - segment1: line1, - segment2: line2, - size: size, - dashLen: 0, - minY: minY, - maxY: maxY, - renderLimits: renderLimits, - alphaBlend: alphaBlend); + if (shape is Shape.Circle) { + RenderManager.instance.OverlayEffect.DrawCircle( + cameraInfo: cameraInfo, + color: color, + center: center, + size: size, + minY: minY, + maxY: maxY, + renderLimits: renderLimits, + alphaBlend: alphaBlend); + } else { + size *= 0.5f; + Vector3 dir = tangent * size; + Vector3 dir90 = dir.RotateXZ90CW(); + Quad3 quad = shape switch { + Shape.Square => new Quad3 { + a = center - dir + dir90, + b = center + dir + dir90, + c = center + dir - dir90, + d = center - dir - dir90, + }, + Shape.Diamond => new Quad3 { + a = center - dir, + b = center + dir90, + c = center + dir, + d = center - dir90, + }, + }; + RenderManager.instance.OverlayEffect.DrawQuad( + cameraInfo, + color, + quad, + minY, + maxY, + renderLimits: renderLimits, + alphaBlend: alphaBlend); + } } public static void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, diff --git a/TLM/TLM/UI/Helpers/NodeLaneMarker.cs b/TLM/TLM/UI/Helpers/NodeLaneMarker.cs index 17f5dbc4b..510a9cd79 100644 --- a/TLM/TLM/UI/Helpers/NodeLaneMarker.cs +++ b/TLM/TLM/UI/Helpers/NodeLaneMarker.cs @@ -1,13 +1,14 @@ -using ColossalFramework.Math; -using UnityEngine; - namespace TrafficManager.UI.Helpers { - using Util; + using System; + using TrafficManager.Util; + using UnityEngine; internal class NodeLaneMarker { + internal const float RADIUS = 1f; + internal Vector3 TerrainPosition; // projected on terrain internal Vector3 Position; // original height. - static internal float Radius = 1f; + internal Vector3 Direction; // pointing toward the lane. /// /// Intersects mouse ray with marker bounds. @@ -15,31 +16,42 @@ internal class NodeLaneMarker { /// trueif mouse ray intersects with marker false otherwise internal bool IntersectRay() { Ray mouseRay = InGameUtil.Instance.CachedMainCamera.ScreenPointToRay(Input.mousePosition); - float hitH = TrafficManagerTool.GetAccurateHitHeight(); - - Bounds bounds = new Bounds(Vector3.zero, Vector3.one * Radius) { - center = Position + Bounds bounds = new(Vector3.zero, Vector3.one * RADIUS) { + center = Position, }; return bounds.IntersectRay(mouseRay); } - internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bool enlarge = false, bool renderLimits = false) { - float magnification = enlarge ? 2f : 1f; + internal void RenderOverlay( + RenderManager.CameraInfo cameraInfo, + Color color, + Highlight.Shape shape = Highlight.Shape.Circle, + bool enlarge = false, + bool renderLimits = false) { float overdrawHeight = renderLimits ? 0f : 5f; - RenderManager.instance.OverlayEffect.DrawCircle( + float magnification = enlarge ? 2f : 1f; + float size = RADIUS * magnification; + float outlineScale = shape is Highlight.Shape.Circle ? 0.75f : 0.9f; + float outlineSize = RADIUS * outlineScale * magnification; + + Highlight.DrawShape( cameraInfo, + shape, color, Position, - Radius * magnification, + Direction, + size, Position.y - overdrawHeight, Position.y + overdrawHeight, renderLimits, true); - RenderManager.instance.OverlayEffect.DrawCircle( + Highlight.DrawShape( cameraInfo, + shape, Color.black, Position, - Radius * 0.75f * magnification, // inner black + Direction, + outlineSize, // black outline Position.y - overdrawHeight, Position.y + overdrawHeight, renderLimits, diff --git a/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs b/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs index 7ae16acc8..969009a42 100644 --- a/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs +++ b/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs @@ -66,9 +66,17 @@ private void CalculateBounds() { } /// Renders lane overlay. - internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bool enlarge = false, bool renderLimits = false) { + internal void RenderOverlay( + RenderManager.CameraInfo cameraInfo, + Color color, + bool enlarge = false, + bool renderLimits = false, + bool alphaBlend = false, + bool cutStart = false, + bool cutEnd = false) { float minH = Mathf.Min(Bezier.a.y, Bezier.d.y); float maxH = Mathf.Max(Bezier.a.y, Bezier.d.y); + float size = enlarge ? Size * 1.41f : Size; float overdrawHeight = IsUnderground || renderLimits ? 0f : 5f; ColossalFramework.Singleton.instance.m_drawCallData.m_overlayCalls++; @@ -76,13 +84,13 @@ internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bo cameraInfo: cameraInfo, color: color, bezier: Bezier, - size: enlarge ? Size * 1.41f : Size, - cutStart: 0, - cutEnd: 0, + size: size, + cutStart: cutStart ? size * 0.50f : 0, + cutEnd: cutEnd ? size * 0.50f : 0, minY: minH - overdrawHeight, maxY: maxH + overdrawHeight, renderLimits: IsUnderground || renderLimits, - alphaBlend: false); + alphaBlend: alphaBlend); } } } diff --git a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs index d7233241d..e723eaccd 100644 --- a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs +++ b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs @@ -269,7 +269,7 @@ private static bool CanReset(ushort segmentId, bool startNode) { sort: false); foreach (var lane in lanes) { - if (!LaneConnectionManager.Instance.Sub.HasOutgoingConnections(lane.laneId)) + if (!LaneConnectionManager.Instance.Road.HasOutgoingConnections(lane.laneId)) return true; } diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 9ea279db5..434cdd371 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -20,11 +20,11 @@ namespace TrafficManager.UI.SubTools { using TrafficManager.Util.Extensions; using TrafficManager.U; using TrafficManager.UI.Textures; + using TrafficManager.API.Traffic.Enums; public class LaneConnectorTool : LegacySubTool, - UI.MainMenu.IOnscreenDisplayProvider - { + UI.MainMenu.IOnscreenDisplayProvider { public LaneConnectorTool(TrafficManagerTool mainTool) : base(mainTool) { // Log._Debug($"LaneConnectorTool: Constructor called"); @@ -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)); + Highlight.TriangleTexture = directionArrow_; } /// State of the tool UI. @@ -86,6 +87,16 @@ public enum StayInLaneMode { private Texture2D directionArrow_; + private Texture2D square_; + + private LaneEndTransitionGroup selectedNodeTransitionGroups_; + + private LaneEndTransitionGroup selectedLaneTransitionGroup_; + + private LaneEndTransitionGroup group_; + + private static LaneEndTransitionGroup[] ALL_GROUPS = new[] {LaneEndTransitionGroup.Road, LaneEndTransitionGroup.Track }; + /// /// Stores potentially visible ids for nodes while the camera did not move /// @@ -96,30 +107,50 @@ public enum StayInLaneMode { /// private CameraTransformValue LastCachedCamera { get; set; } + /// - /// create all lane connections (bidirectional, cars+tram). + /// all lane connections (bidirectional, cars+tram). /// private bool MultiMode => ShiftIsPressed; + private void UpdateGroup() { + if (selectedLaneTransitionGroup_ != LaneEndTransitionGroup.None) { + group_ = selectedLaneTransitionGroup_; + } else if (selectedNodeTransitionGroups_ == LaneEndTransitionGroup.Vehicle) { + // No node is selected or selected node has road+track + if (MultiMode) { + group_ = LaneEndTransitionGroup.Vehicle; + } else if (AltIsPressed) { + group_ = LaneEndTransitionGroup.Track; + } else { + group_ = LaneEndTransitionGroup.Road; + } + } else { + group_ = selectedNodeTransitionGroups_; + } + } + private class LaneEnd { - internal readonly List ConnectedLaneEnds = new List(); - internal Color Color; - internal int InnerSimilarLaneIndex; // used for stay in lane. - internal bool IsBidirectional; // can be source AND/OR target of a lane connection. + internal ushort SegmentId; + internal ushort NodeId; + internal bool StartNode; + internal uint LaneId; internal bool IsSource; internal bool IsTarget; - internal uint LaneId; - - internal NetInfo.LaneType LaneType; - internal ushort NodeId; - internal NodeLaneMarker NodeMarker; internal int OuterSimilarLaneIndex; - internal ushort SegmentId; + internal int InnerSimilarLaneIndex; // used for stay in lane. internal int SegmentIndex; // index accesable by NetNode.GetSegment(SegmentIndex); + internal bool IsBidirectional; // can be source AND/OR target of a lane connection. + internal readonly HashSet ConnectedCarLaneEnds = new (); + internal readonly HashSet ConnectedTrackLaneEnds = new (); + internal HashSet ConnectedLaneEnds(bool track) => track ? ConnectedTrackLaneEnds : ConnectedCarLaneEnds; + internal Color Color; internal SegmentLaneMarker SegmentMarker; - internal bool StartNode; - internal VehicleInfo.VehicleType VehicleType; + internal NodeLaneMarker NodeMarker; + + internal NetInfo.Lane LaneInfo; + internal LaneEndTransitionGroup TransitionGroup; /// /// Intersects mouse ray with marker bounds. @@ -131,11 +162,33 @@ private class LaneEnd { /// renders lane overlay. If highlighted, renders englarged sheath(lane+circle) overlay. Otherwise /// renders circle at lane end. /// - internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bool highlight = false, bool renderLimits = false) { + internal void RenderOverlay( + RenderManager.CameraInfo cameraInfo, + Color color, + bool highlight = false, + bool renderLimits = false, + LaneEndTransitionGroup groupFilter = LaneEndTransitionGroup.Vehicle) { + + var groups = TransitionGroup & groupFilter; + + Highlight.Shape shape; + bool cutEnd; + if (this.IsBidirectional) { + shape = Highlight.Shape.Diamond; + cutEnd = true; + } else if((groups & LaneEndTransitionGroup.Track) != 0) { + shape = Highlight.Shape.Square; + cutEnd = true; + } else { + shape = Highlight.Shape.Circle; + cutEnd = false; + } + if (highlight) { - SegmentMarker.RenderOverlay(cameraInfo, color, enlarge: true, renderLimits); + SegmentMarker.RenderOverlay(cameraInfo, color, cutEnd: cutEnd, enlarge: true, renderLimits: renderLimits); } - NodeMarker.RenderOverlay(cameraInfo, color, enlarge: highlight, renderLimits); + + NodeMarker.RenderOverlay(cameraInfo, color, shape: shape, enlarge: highlight, renderLimits: renderLimits); } } @@ -175,6 +228,7 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { MassEditOverlay.IsActive)) { return; } + UpdateGroup(); NetManager netManager = Singleton.instance; @@ -248,6 +302,15 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { float intersectionY = Singleton.instance.SampleDetailHeightSmooth(nodeId.ToNode().m_position); + LaneEndTransitionGroup groupAtNode = group_; + if (nodeId != SelectedNodeId) { + if (AltIsPressed) { + groupAtNode = LaneEndTransitionGroup.Track; + } else { + groupAtNode = LaneEndTransitionGroup.Vehicle; + } + } + foreach (LaneEnd laneEnd in laneEnds) { ref NetLane sourceLane = ref laneEnd.LaneId.ToLane(); if (!sourceLane.IsValidWithSegment()) { @@ -255,28 +318,34 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { } if (laneEnd != selectedLaneEnd) { - foreach (LaneEnd targetLaneEnd in laneEnd.ConnectedLaneEnds) { - ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane(); - if (!targetLane.IsValidWithSegment()) { + foreach (var group in ALL_GROUPS) { + if((group & groupAtNode) == 0) { continue; } + bool track = group == LaneEndTransitionGroup.Track; + foreach (LaneEnd targetLaneEnd in laneEnd.ConnectedLaneEnds(track)) { + ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane(); + if (!targetLane.IsValidWithSegment()) { + continue; + } - // render lane connection from laneEnd to targetLaneEnd - Bezier3 bezier = CalculateBezierConnection(laneEnd, targetLaneEnd); - Vector3 height = bezier.Max(); - bool underground = (height.y + 1f) < intersectionY || laneEnd.NodeId == SelectedNodeId; - - Color fillColor = laneEnd.Color.WithAlpha(TransparentAlpha); - Color outlineColor = Color.black.WithAlpha(TransparentAlpha); - bool showArrow = ShouldShowDirectionOfConnection(laneEnd, targetLaneEnd); - DrawLaneCurve( - cameraInfo: cameraInfo, - bezier: ref bezier, - color: fillColor, - outlineColor: outlineColor, - arrowColor: showArrow ? fillColor : default, - arrowOutlineColor: showArrow ? outlineColor : default, - underground: underground); + // render lane connection from laneEnd to targetLaneEnd + Bezier3 bezier = CalculateBezierConnection(laneEnd, targetLaneEnd); + Vector3 height = bezier.Max(); + bool underground = (height.y + 1f) < intersectionY || laneEnd.NodeId == SelectedNodeId; + + Color fillColor = laneEnd.Color.WithAlpha(TransparentAlpha); + Color outlineColor = Color.black.WithAlpha(TransparentAlpha); + bool showArrow = track && ShouldShowDirectionOfConnection(laneEnd, targetLaneEnd); + DrawLaneCurve( + cameraInfo: cameraInfo, + bezier: ref bezier, + color: fillColor, + outlineColor: outlineColor, + arrowColor: showArrow ? fillColor : default, + arrowOutlineColor: showArrow ? outlineColor : default, + underground: underground); + } } } @@ -285,22 +354,29 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { } bool drawMarker = false; + bool acute = true; bool sourceMode = GetSelectionMode() == SelectionMode.SelectSource; bool targetMode = GetSelectionMode() == SelectionMode.SelectTarget; if ( sourceMode & laneEnd.IsSource) { // draw source marker in source selection mode, // make exception for markers that have no target: foreach(var targetLaneEnd in laneEnds) { - if (CanConnect(laneEnd, targetLaneEnd)) { + if (CanConnect(laneEnd, targetLaneEnd, group_, out bool acute2)) { drawMarker = true; - break; + if (!acute2) { + acute = false; + break; + } } } } else if (targetMode) { // selected source marker in target selection mode - drawMarker = - selectedLaneEnd == laneEnd || - CanConnect(selectedLaneEnd, laneEnd); + if(selectedLaneEnd == laneEnd) { + drawMarker = true; + acute = false; + } else { + drawMarker = CanConnect(selectedLaneEnd, laneEnd, group_, out acute); + } } // highlight hovered marker and selected marker @@ -316,10 +392,16 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { } } - bool isTarget = selectedLaneEnd != null && laneEnd != selectedLaneEnd; - var color = isTarget ? Color.white : laneEnd.Color; - bool highlightMarker = laneEnd == selectedLaneEnd || markerIsHovered; - laneEnd.RenderOverlay(cameraInfo, color, highlightMarker, true); + var group = laneEnd.TransitionGroup & group_; + if (acute) { + group &= ~LaneEndTransitionGroup.Track; + } + if (group != 0) { + bool isTarget = selectedLaneEnd != null && laneEnd != selectedLaneEnd; + var color = isTarget ? Color.white : laneEnd.Color; + bool highlightMarker = laneEnd == selectedLaneEnd || markerIsHovered; + laneEnd.RenderOverlay(cameraInfo, color, highlightMarker, true, group); + } } // if drawMarker } // end foreach lanemarker in node markers } // end for node in all nodes @@ -327,24 +409,30 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { if (this.selectedLaneEnd != null) { // lane curves for selectedMarker will be drawn last to // be on the top of other lane markers. - foreach (LaneEnd targetLaneEnd in this.selectedLaneEnd.ConnectedLaneEnds) { - ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane(); - if (!targetLane.IsValidWithSegment()) { + foreach (var group in ALL_GROUPS) { + if ((group & group_) == 0) { continue; } + bool track = group == LaneEndTransitionGroup.Track; + foreach (LaneEnd targetLaneEnd in this.selectedLaneEnd.ConnectedLaneEnds(track)) { + ref NetLane targetLane = ref targetLaneEnd.LaneId.ToLane(); + if (!targetLane.IsValidWithSegment()) { + continue; + } - Bezier3 bezier = CalculateBezierConnection(selectedLaneEnd, targetLaneEnd); - bool showArrow = ShouldShowDirectionOfConnection(selectedLaneEnd, targetLaneEnd); - DrawLaneCurve( - cameraInfo: cameraInfo, - bezier: ref bezier, - color: this.selectedLaneEnd.Color, - outlineColor: Color.black, - arrowColor: showArrow ? this.selectedLaneEnd.Color : default, - arrowOutlineColor: showArrow ? Color.black : default, - size: 0.18f, // Embolden - underground: true); - } // end foreach selectedMarker.ConnectedMarkers + Bezier3 bezier = CalculateBezierConnection(selectedLaneEnd, targetLaneEnd); + bool showArrow = track & ShouldShowDirectionOfConnection(selectedLaneEnd, targetLaneEnd); + DrawLaneCurve( + cameraInfo: cameraInfo, + bezier: ref bezier, + color: this.selectedLaneEnd.Color, + outlineColor: Color.black, + arrowColor: showArrow ? this.selectedLaneEnd.Color : default, + arrowOutlineColor: showArrow ? Color.black : default, + size: 0.18f, // Embolden + underground: true); + } // end foreach selectedMarker.ConnectedMarkers + } } // end if selectedMarker != null } @@ -388,38 +476,40 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } else { // snap to hovered, render accurate connection bezier Bezier3 bezier = CalculateBezierConnection(selectedLaneEnd, hoveredLaneEnd); - bool connected = LaneConnectionManager.Instance.Sub.AreLanesConnected( - selectedLaneEnd.LaneId, hoveredLaneEnd.LaneId, selectedLaneEnd.StartNode); + 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 showArrow = ShouldShowDirectionOfConnection(selectedLaneEnd, hoveredLaneEnd); + 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: connected ? default : Color.white, + arrowColor: default, + arrowOutlineColor: showArrow ? Color.white : default, size: 0.18f, // Embolden underground: true); if(!connected && MultiMode && selectedLaneEnd.IsBidirectional && hoveredLaneEnd.IsBidirectional) { Bezier3 bezier2 = CalculateBezierConnection(hoveredLaneEnd, selectedLaneEnd); // draw backward arrow only: - bool connected2 = LaneConnectionManager.Instance.Sub.AreLanesConnected( - hoveredLaneEnd.LaneId, selectedLaneEnd.LaneId, selectedLaneEnd.StartNode); + bool connected2 = LaneConnectionManager.Instance.AreLanesConnected( + hoveredLaneEnd.LaneId, selectedLaneEnd.LaneId, selectedLaneEnd.StartNode, group_); DrawLaneCurve( cameraInfo: cameraInfo, bezier: ref bezier2, color: default, - outlineColor: default, + outlineColor: Color.white, arrowColor: default, arrowOutlineColor: connected2 ? default : Color.white, + size: 0.18f, // Embolden underground: true); } - OverrideCursor = connected ? removeCursor_ : addCursor_; + OverrideCursor = connected ? removeCursor_ : addCursor_; } } @@ -438,6 +528,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { frameStayInLanePressed = 0; // not pressed anymore (consumed) frameClearPressed = 0; // consumed selectedLaneEnd = null; + selectedLaneTransitionGroup_ = 0; ref NetNode node = ref SelectedNodeId.ToNode(); bool stayInLane = GetSortedSegments(SelectedNodeId, out List segList); @@ -807,7 +898,7 @@ bool ConnectToMinor2(int sourceIdx, int targetIdx) { targetIdx >= LowerBound(totalSource - laneCountMinor2Source); } - List laneEnds = GetLaneEnds(nodeId, ref node); + List laneEnds = GetLaneEnds(nodeId, ref node, out _); foreach (LaneEnd sourceLaneEnd in laneEnds) { if (!sourceLaneEnd.IsSource || sourceLaneEnd.SegmentId == mainSegmentTargetId) { @@ -817,8 +908,7 @@ bool ConnectToMinor2(int sourceIdx, int targetIdx) { if (!targetLaneEnd.IsTarget || targetLaneEnd.SegmentId == sourceLaneEnd.SegmentId || targetLaneEnd.SegmentId == mainSegmentSourceId || - !CanConnect(sourceLaneEnd, targetLaneEnd) - ) { + !CanConnect(sourceLaneEnd, targetLaneEnd, LaneEndTransitionGroup.Vehicle, out _)) { continue; } bool connect = false; @@ -858,10 +948,11 @@ bool ConnectToMinor2(int sourceIdx, int targetIdx) { } if (connect) { - LaneConnectionManager.Instance.Sub.AddLaneConnection( + LaneConnectionManager.Instance.AddLaneConnection( sourceLaneEnd.LaneId, targetLaneEnd.LaneId, - sourceLaneEnd.StartNode); + sourceLaneEnd.StartNode, + LaneEndTransitionGroup.Vehicle); } } // foreach } // foreach @@ -921,6 +1012,8 @@ public override void OnPrimaryClickOverlay() { SelectedNodeId = 0; selectedLaneEnd = null; + selectedNodeTransitionGroups_ = 0; + selectedLaneTransitionGroup_ = 0; stayInLaneMode = StayInLaneMode.None; MainTool.RequestOnscreenDisplayUpdate(); return; @@ -932,11 +1025,12 @@ public override void OnPrimaryClickOverlay() { () => $"Node {HoveredNodeId} has been selected. Creating markers."); // selected node has changed. create markers - List laneEnds = GetLaneEnds(HoveredNodeId, ref hoveredNode); + List laneEnds = GetLaneEnds(HoveredNodeId, ref hoveredNode, out selectedNodeTransitionGroups_); if (laneEnds != null) { SelectedNodeId = HoveredNodeId; selectedLaneEnd = null; + selectedLaneTransitionGroup_ = 0; stayInLaneMode = StayInLaneMode.None; currentLaneEnds[SelectedNodeId] = laneEnds; @@ -953,6 +1047,7 @@ public override void OnPrimaryClickOverlay() { // click on free spot. deselect node SelectedNodeId = 0; selectedLaneEnd = null; + selectedLaneTransitionGroup_ = 0; stayInLaneMode = StayInLaneMode.None; MainTool.RequestOnscreenDisplayUpdate(); return; @@ -976,6 +1071,7 @@ public override void OnPrimaryClickOverlay() { if (GetSelectionMode() == SelectionMode.SelectSource) { // select source marker selectedLaneEnd = hoveredLaneEnd; + selectedLaneTransitionGroup_ = group_ & selectedLaneEnd.TransitionGroup; Log._DebugIf( logLaneConn, () => "LaneConnectorTool: set selected marker"); @@ -983,53 +1079,57 @@ public override void OnPrimaryClickOverlay() { } else if (GetSelectionMode() == SelectionMode.SelectTarget) { // toggle lane connection bool canBeBidirectional = selectedLaneEnd.IsBidirectional && hoveredLaneEnd.IsBidirectional; - if (LaneConnectionManager.Instance.Sub.AreLanesConnected( - selectedLaneEnd.LaneId, - hoveredLaneEnd.LaneId, - selectedLaneEnd.StartNode)) { - RemoveLaneConnection(selectedLaneEnd, hoveredLaneEnd); + if (LaneConnectionManager.Instance.AreLanesConnected( + selectedLaneEnd.LaneId, hoveredLaneEnd.LaneId, selectedLaneEnd.StartNode, group_)) { + RemoveLaneConnection(selectedLaneEnd, hoveredLaneEnd, group_); if (canBeBidirectional && ShiftIsPressed) { - RemoveLaneConnection(hoveredLaneEnd, selectedLaneEnd); + RemoveLaneConnection(hoveredLaneEnd, selectedLaneEnd, group_); } } else { - AddLaneConnection(selectedLaneEnd, hoveredLaneEnd); + AddLaneConnection(selectedLaneEnd, hoveredLaneEnd, group_); if (canBeBidirectional && ShiftIsPressed) { - AddLaneConnection(hoveredLaneEnd, selectedLaneEnd); + AddLaneConnection(hoveredLaneEnd, selectedLaneEnd, group_); } } + UpdateConnectionTwoway(selectedLaneEnd, hoveredLaneEnd); + MainTool.RequestOnscreenDisplayUpdate(); } } + private static void UpdateConnectionTwoway(LaneEnd laneEnd1, LaneEnd laneEnd2) { + UpdateConnection(laneEnd1, laneEnd2); + UpdateConnection(laneEnd2, laneEnd1); + } - private void RemoveLaneConnection(LaneEnd source, LaneEnd target) { - if (LaneConnectionManager.Instance.Sub.RemoveLaneConnection( - source.LaneId, - target.LaneId, - source.StartNode)) { - - // try to remove connection - source.ConnectedLaneEnds.Remove(target); - Log._DebugIf( - verbose_, - () => $"LaneConnectorTool: removed lane connection: {source.LaneId}, " + - $"{target.LaneId}"); + 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"); } } - private void AddLaneConnection(LaneEnd source, LaneEnd target) { - if (LaneConnectionManager.Instance.Sub.AddLaneConnection( - source.LaneId, - target.LaneId, - source.StartNode)) { - // try to add connection - source.ConnectedLaneEnds.Add(target); - Log._DebugIf( - verbose_, - () => $"LaneConnectorTool: added lane connection: {source.LaneId}, " + - $"{target.LaneId}"); - } + private static void RemoveLaneConnection(LaneEnd source, LaneEnd target, LaneEndTransitionGroup group) { + LaneConnectionManager.Instance.RemoveLaneConnection( + source.LaneId, target.LaneId, source.StartNode, group); + } + private static void AddLaneConnection(LaneEnd source, LaneEnd target, LaneEndTransitionGroup group) { + LaneConnectionManager.Instance.AddLaneConnection( + source.LaneId, target.LaneId, source.StartNode, group); } public override void OnSecondaryClickOverlay() { @@ -1062,6 +1162,7 @@ public override void OnSecondaryClickOverlay() { logLaneConn, () => "LaneConnectorTool: OnSecondaryClickOverlay: selected node id = 0"); SelectedNodeId = 0; + selectedNodeTransitionGroups_= 0; MainTool.RequestOnscreenDisplayUpdate(); break; } @@ -1072,6 +1173,7 @@ public override void OnSecondaryClickOverlay() { logLaneConn, () => "LaneConnectorTool: OnSecondaryClickOverlay: switch to selected source mode"); selectedLaneEnd = null; + selectedLaneTransitionGroup_ = 0; MainTool.RequestOnscreenDisplayUpdate(); break; } @@ -1092,6 +1194,8 @@ public override void OnActivate() { #endif SelectedNodeId = 0; selectedLaneEnd = null; + selectedNodeTransitionGroups_ = 0; + selectedLaneTransitionGroup_ = 0; hoveredLaneEnd = null; stayInLaneMode = StayInLaneMode.None; RefreshCurrentNodeMarkers(); @@ -1115,11 +1219,11 @@ private void RefreshCurrentNodeMarkers(ushort forceNodeId = 0) { } if (nodeId != SelectedNodeId && - !LaneConnectionManager.Instance.Sub.HasNodeConnections(nodeId)) { + !LaneConnectionManager.Instance.HasNodeConnections(nodeId)) { continue; } - List laneEnds = GetLaneEnds(nodeId, ref netNode); + List laneEnds = GetLaneEnds(nodeId, ref netNode, out _); if (laneEnds == null) { continue; @@ -1156,7 +1260,8 @@ public override void Initialize() { /// Node id. /// Ref to the node struct. /// List of lane end structs. - private static List GetLaneEnds(ushort nodeId, ref NetNode node) { + private static List GetLaneEnds(ushort nodeId, ref NetNode node, out LaneEndTransitionGroup groups) { + groups = 0; if (nodeId == 0) { return null; } @@ -1189,6 +1294,7 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { float offsetT = FloatUtil.IsZero(netSegment.m_averageLength) ? 0.1f : offset / netSegment.m_averageLength; for (byte laneIndex = 0; (laneIndex < lanes.Length) && (laneId != 0); laneIndex++) { + ref NetLane netLane = ref laneId.ToLane(); NetInfo.Lane laneInfo = lanes[laneIndex]; if (((laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != NetInfo.LaneType.None) @@ -1204,15 +1310,16 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { incoming: out bool isTarget, pos: out _)) { - Vector3 pos; - Bezier3 bezier = laneId.ToLane().m_bezier; + groups |= laneInfo.GetLaneEndTransitionGroup(); + Bezier3 bezier = netLane.m_bezier; if (startNode) { - bezier = bezier.Cut(offsetT, 1f); - pos = bezier.a; - } else { - bezier = bezier.Cut(0, 1f - offsetT); - pos = bezier.d; + // reverse bezier. + bezier = new Bezier3(bezier.d, bezier.c, bezier.b, bezier.a); } + bezier = bezier.Cut(0, 1f - offsetT); + Vector3 pos = bezier.d; + Vector3 dir = VectorUtils.NormalizeXZ(bezier.c - bezier.d); + dir.y = 0; float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); var terrainPos = new Vector3(pos.x, terrainY, pos.z); @@ -1224,7 +1331,8 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { } NodeLaneMarker nodeMarker = new NodeLaneMarker { TerrainPosition = terrainPos, - Position = (Vector3)pos, + Position = pos, + Direction = dir, }; Color32 nodeMarkerColor = isSource @@ -1250,8 +1358,8 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { Color = nodeMarkerColor, IsSource = isSource, IsTarget = isTarget, - LaneType = laneInfo.m_laneType, - VehicleType = laneInfo.m_vehicleType, + LaneInfo = laneInfo, + TransitionGroup = laneInfo.GetLaneEndTransitionGroup(), InnerSimilarLaneIndex = innerSimilarLaneIndex, OuterSimilarLaneIndex = outerSimilarLaneIndex, SegmentIndex = segmentIndex, @@ -1266,7 +1374,7 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { } } - laneId = laneId.ToLane().m_nextLane; + laneId = netLane.m_nextLane; } } @@ -1274,27 +1382,28 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { return null; } - foreach (LaneEnd laneEnd1 in laneEnds) { - if (!laneEnd1.IsSource) { - continue; - } - - uint[] connections = - LaneConnectionManager.Instance.Sub.GetLaneConnections( - laneEnd1.LaneId, - laneEnd1.StartNode); - - if ((connections == null) || (connections.Length == 0)) { + foreach (LaneEnd sourceLaneEnd in laneEnds) { + if (!sourceLaneEnd.IsSource) { continue; } - foreach (LaneEnd laneEnd2 in laneEnds) { - if (!laneEnd2.IsTarget) { - 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); + } } + } - if (connections.Contains(laneEnd2.LaneId)) { - laneEnd1.ConnectedLaneEnds.Add(laneEnd2); + 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); + } } } } @@ -1302,66 +1411,30 @@ private static List GetLaneEnds(ushort nodeId, ref NetNode node) { return laneEnds; } - private static bool CanConnect(LaneEnd source, LaneEnd target) { - bool ret = source != target && source.IsSource && target.IsTarget; - ret &= (target.VehicleType & source.VehicleType) != 0; - - bool IsRoad(LaneEnd laneEnd) => - (laneEnd.LaneType & LaneArrowManager.LANE_TYPES) != 0 && - (laneEnd.VehicleType & LaneArrowManager.VEHICLE_TYPES) != 0; + private static bool CanConnect(LaneEnd source, LaneEnd target, LaneEndTransitionGroup groups, out bool acute) { + acute = true; + bool canConnect = (source.LaneInfo.m_vehicleType & target.LaneInfo.m_vehicleType) != 0; + if (!canConnect) { + return false; + } + canConnect = source != target && source.IsSource && target.IsTarget; + if (!canConnect) { + return false; + } // turning angle does not apply to roads. - bool isRoad = IsRoad(source) && IsRoad(target); + bool road = groups != LaneEndTransitionGroup.Track && + source.LaneInfo.MatchesRoad() && + target.LaneInfo.MatchesRoad(); // check track turning angles are within bounds - ret &= isRoad || CheckSegmentsTurningAngle( + acute = !LaneConnectionManager.CheckSegmentsTurningAngle( sourceSegmentId: source.SegmentId, sourceStartNode: source.StartNode, targetSegmentId: target.SegmentId, targetStartNode: target.StartNode); - return ret; - } - - /// - /// Checks if the turning angle between two segments at the given node is within bounds. - /// - /// - /// - /// - /// - /// - /// - /// - private static bool CheckSegmentsTurningAngle(ushort sourceSegmentId, - bool sourceStartNode, - ushort targetSegmentId, - bool targetStartNode) { - if(sourceSegmentId == targetSegmentId) { - return false; - } - - ref NetSegment sourceSegment = ref sourceSegmentId.ToSegment(); - ref NetSegment targetSegment = ref targetSegmentId.ToSegment(); - float turningAngle = 0.01f - Mathf.Min( - sourceSegment.Info.m_maxTurnAngleCos, - targetSegment.Info.m_maxTurnAngleCos); - - if (turningAngle < 1f) { - Vector3 sourceDirection = sourceStartNode - ? sourceSegment.m_startDirection - : sourceSegment.m_endDirection; - - Vector3 targetDirection = targetStartNode - ? targetSegment.m_startDirection - : targetSegment.m_endDirection; - - float dirDotProd = (sourceDirection.x * targetDirection.x) + - (sourceDirection.z * targetDirection.z); - return dirDotProd < turningAngle; - } - - return true; + return road || !acute; } /// @@ -1446,20 +1519,19 @@ private void DrawLaneCurve(RenderManager.CameraInfo cameraInfo, Bounds bounds = bezier.GetBounds(); float minY = bounds.min.y - 0.5f; float maxY = bounds.max.y + 0.5f; + if (arrowOutlineColor.a != 0) { + Highlight.DrawArrowHead( + cameraInfo: cameraInfo, + bezier: ref bezier, + t: 2f / 3f, + color: arrowOutlineColor, + size: size + 0.5f, + minY: minY, + maxY: maxY, + alphaBlend: arrowColor.a == 0f, // avoid strange shape. + renderLimits: underground); + } if (outlineColor.a != 0) { - if (arrowOutlineColor.a != 0) { - Highlight.DrawArrowHead( - cameraInfo: cameraInfo, - bezier: ref bezier, - t: 2f / 3f, - color: arrowOutlineColor, - size: size + 0.5f, - minY: minY, - maxY: maxY, - alphaBlend: arrowColor.a == 0f, // avoid strange shape. - renderLimits: underground); - } - RenderManager.instance.OverlayEffect.DrawBezier( cameraInfo: cameraInfo, color: outlineColor, @@ -1618,9 +1690,11 @@ public void UpdateOnscreenDisplayPanel() { if(selectedLaneEnd != null) { bool bidirectional = selectedLaneEnd.IsBidirectional; if (bidirectional) { - string key = "UI.Key:Shift bidirectional"; - items.Add(new HoldModifier(shift: true, localizedText: T(key))); + items.Add(new HoldModifier(shift: true, localizedText: T("UI.Key:Shift bidirectional"))); } + } else if(selectedNodeTransitionGroups_ == LaneEndTransitionGroup.Vehicle) { + items.Add(new HoldModifier(alt: true, localizedText: T("UI.Key:alt track mode"))); + items.Add(new HoldModifier(shift: true, localizedText: T("UI.Key:Shift car+track mode"))); } OnscreenDisplay.Display(items); diff --git a/TLM/TLM/UI/SubTools/RoutingDetector/LaneEnd.cs b/TLM/TLM/UI/SubTools/RoutingDetector/LaneEnd.cs index bafb5c794..151d32378 100644 --- a/TLM/TLM/UI/SubTools/RoutingDetector/LaneEnd.cs +++ b/TLM/TLM/UI/SubTools/RoutingDetector/LaneEnd.cs @@ -25,9 +25,9 @@ internal class LaneEnd { /// internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bool highlight = false, bool renderLimits = false) { if (highlight) { - SegmentMarker.RenderOverlay(cameraInfo, color, enlarge: true, renderLimits); + SegmentMarker.RenderOverlay(cameraInfo, color, enlarge: true, renderLimits: renderLimits); } - NodeMarker.RenderOverlay(cameraInfo, color, enlarge: highlight, renderLimits); + NodeMarker.RenderOverlay(cameraInfo, color, enlarge: highlight, renderLimits: renderLimits); } } } diff --git a/TLM/TLM/Util/Record/LaneConnectionRecord.cs b/TLM/TLM/Util/Record/LaneConnectionRecord.cs index 8d1b259f4..c993b1dfb 100644 --- a/TLM/TLM/Util/Record/LaneConnectionRecord.cs +++ b/TLM/TLM/Util/Record/LaneConnectionRecord.cs @@ -15,42 +15,54 @@ public class LaneConnectionRecord : IRecordable { public byte LaneIndex; public bool StartNode; - private uint[] connections_; + private uint[] connections_; // legacy + private uint[] roadConnections_; + private uint[] trackConnections_; private static LaneConnectionManager connMan => LaneConnectionManager.Instance; - private uint[] GetCurrentConnections() => connMan.Sub.GetLaneConnections(LaneId, StartNode); - public void Record() { - connections_ = GetCurrentConnections(); - //Log._Debug($"LaneConnectionRecord.Record: connections_=" + connections_.ToSTR()); - - if (connections_ != null) - connections_ = (uint[])connections_.Clone(); + connections_ = null; + roadConnections_ = connMan.Road.GetLaneConnections(LaneId, StartNode)?.Clone() as uint[]; + trackConnections_ = connMan.Track.GetLaneConnections(LaneId, StartNode)?.Clone() as uint[]; } - public void Restore() { - if (connections_ == null) { - connMan.Sub.RemoveLaneConnections(LaneId, StartNode); + private void RestoreImpl(LaneConnectionSubManager man, uint[] connections) { + if (connections == null) { + man.RemoveLaneConnections(LaneId, StartNode); return; } - var currentConnections = GetCurrentConnections(); + var currentConnections = man.GetLaneConnections(LaneId, StartNode) ?? new uint[0]; //Log._Debug($"currentConnections=" + currentConnections.ToSTR()); - //Log._Debug($"connections_=" + connections_.ToSTR()); + //Log._Debug($"connections=" + connections.ToSTR()); - foreach (uint targetLaneId in connections_) { + foreach (uint targetLaneId in connections) { if (currentConnections == null || !currentConnections.Contains(targetLaneId)) { - connMan.Sub.AddLaneConnection(LaneId, targetLaneId, StartNode); + man.AddLaneConnection(LaneId, targetLaneId, StartNode); } } - foreach (uint targetLaneId in currentConnections ?? Enumerable.Empty()) { - if (!connections_.Contains(targetLaneId)) { - connMan.Sub.RemoveLaneConnection(LaneId, targetLaneId, StartNode); + foreach (uint targetLaneId in currentConnections) { + if (!connections.Contains(targetLaneId)) { + man.RemoveLaneConnection(LaneId, targetLaneId, StartNode); } } } - public void Transfer(Dictionary map) { + public void Restore() { + if (connections_ != null) { + // legacy + RestoreImpl(connMan.Road, connections_); + RestoreImpl(connMan.Track, connections_); + } else { + RestoreImpl(connMan.Road, roadConnections_); + RestoreImpl(connMan.Track, trackConnections_); + } + } + + private void TransferImpl( + Dictionary map, + LaneConnectionSubManager man, + uint[] connections) { uint MappedLaneId(uint originalLaneID) { var originalLaneInstanceID = new InstanceID { NetLane = originalLaneID }; if (map.TryGetValue(originalLaneInstanceID, out var ret)) @@ -60,21 +72,32 @@ uint MappedLaneId(uint originalLaneID) { } var mappedLaneId = MappedLaneId(LaneId); - if (connections_ == null) { - connMan.Sub.RemoveLaneConnections(mappedLaneId, StartNode); + if (connections == null) { + man.RemoveLaneConnections(mappedLaneId, StartNode); return; } if (mappedLaneId == 0) return; - //Log._Debug($"connections_=" + connections_.ToSTR()); - foreach (uint targetLaneId in connections_) { + //Log._Debug($"connections=" + connections.ToSTR()); + foreach (uint targetLaneId in connections) { var mappedTargetLaneId = MappedLaneId(targetLaneId); if (mappedTargetLaneId == 0) continue; //Log._Debug($"connecting lanes: {mappedLaneId}->{mappedTargetLaneId}"); - connMan.Sub.AddLaneConnection(mappedLaneId, mappedTargetLaneId, StartNode); + man.AddLaneConnection(mappedLaneId, mappedTargetLaneId, StartNode); + } + } + + public void Transfer(Dictionary map) { + if (connections_ != null) { + // legacy + TransferImpl(map, connMan.Road, connections_); + TransferImpl(map, connMan.Track, connections_); + } else { + TransferImpl(map, connMan.Road, roadConnections_); + TransferImpl(map, connMan.Track, trackConnections_); } } @@ -93,7 +116,7 @@ public static List GetLanes(ushort nodeId) { continue; } - foreach (LaneIdAndIndex laneIdAndIndex in extSegmentManager.GetSegmentLaneIdsAndLaneIndexes(segmentId)) { + foreach (LaneIdAndIndex laneIdAndIndex in netSegment.GetSegmentLaneIdsAndLaneIndexes()) { NetInfo.Lane laneInfo = netInfo.m_lanes[laneIdAndIndex.laneIndex]; bool match = (laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != 0 && (laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != 0; diff --git a/TLM/TLM/Util/SeparateTurningLanesUtil.cs b/TLM/TLM/Util/SeparateTurningLanesUtil.cs index f3ff09bbb..7f69b9ad6 100644 --- a/TLM/TLM/Util/SeparateTurningLanesUtil.cs +++ b/TLM/TLM/Util/SeparateTurningLanesUtil.cs @@ -80,7 +80,7 @@ public static void SeparateNode(ushort nodeId, out SetLaneArrow_Result res, bool return; } - if (LaneConnectionManager.Instance.Sub.HasNodeConnections(nodeId)) { + if (LaneConnectionManager.Instance.Road.HasNodeConnections(nodeId)) { res = SetLaneArrow_Result.LaneConnection; return; } @@ -491,7 +491,7 @@ public static SetLaneArrow_Result CanChangeLanes(ushort segmentId, ushort nodeId int srcLaneCount = laneList.Count(); for (int i = 0; i < srcLaneCount; ++i) { - if (LaneConnectionManager.Instance.Sub.HasOutgoingConnections(laneList[i].laneId, startNode)) { + if (LaneConnectionManager.Instance.Road.HasOutgoingConnections(laneList[i].laneId, startNode)) { return SetLaneArrow_Result.LaneConnection; } }