diff --git a/TLM/CSUtil.Commons/Log.cs b/TLM/CSUtil.Commons/Log.cs index 234c9a88f..3ac9e77e0 100644 --- a/TLM/CSUtil.Commons/Log.cs +++ b/TLM/CSUtil.Commons/Log.cs @@ -1,67 +1,65 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using UnityEngine; +namespace CSUtil.Commons { + using System; + using System.Diagnostics; + using System.IO; + using System.Threading; + using UnityEngine; -namespace CSUtil.Commons { + public static class Log { + private enum LogLevel { + Debug, + Info, + Warning, + Error + } - public static class Log { - private enum LogLevel { - Debug, - Info, - Warning, - Error - } + private static object logLock = new object(); - private static object logLock = new object(); + private static string logFilename = Path.Combine(Application.dataPath, "TMPE.log"); // TODO refactor log filename to configuration + private static Stopwatch sw = Stopwatch.StartNew(); - private static string logFilename = Path.Combine(Application.dataPath, "TMPE.log"); // TODO refactor log filename to configuration - private static Stopwatch sw = Stopwatch.StartNew(); + static Log() { + try { + if (File.Exists(logFilename)) { + File.Delete(logFilename); + } + } catch (Exception) { - static Log() { - try { - if (File.Exists(logFilename)) { - File.Delete(logFilename); - } - } catch (Exception) { - - } - } + } + } - [Conditional("DEBUG")] - public static void _Debug(string s) { - LogToFile(s, LogLevel.Debug); - } + [Conditional("DEBUG")] + public static void _Debug(string s) { + LogToFile(s, LogLevel.Debug); + } - public static void Info(string s) { - LogToFile(s, LogLevel.Info); - } + public static void Info(string s) { + LogToFile(s, LogLevel.Info); + } - public static void Warning(string s) { - LogToFile(s, LogLevel.Warning); - } + public static void Warning(string s) { + LogToFile(s, LogLevel.Warning); + } - public static void Error(string s) { - LogToFile(s, LogLevel.Error); - } + public static void Error(string s) { + LogToFile(s, LogLevel.Error); + } - private static void LogToFile(string log, LogLevel level) { - try { - Monitor.Enter(logLock); - - using (StreamWriter w = File.AppendText(logFilename)) { - w.WriteLine($"[{level.ToString()}] @ {sw.ElapsedTicks} {log}"); - if (level == LogLevel.Warning || level == LogLevel.Error) { - w.WriteLine((new System.Diagnostics.StackTrace()).ToString()); - w.WriteLine(); - } - } - } finally { - Monitor.Exit(logLock); - } - } - } + private static void LogToFile(string log, LogLevel level) { + try { + Monitor.Enter(logLock); -} + using (StreamWriter w = File.AppendText(logFilename)) { + w.WriteLine($"[{level.ToString()}] @ {sw.ElapsedTicks} {log}"); + if (level == LogLevel.Warning || level == LogLevel.Error) { + w.WriteLine((new System.Diagnostics.StackTrace()).ToString()); + w.WriteLine(); + } + } + } finally { + Monitor.Exit(logLock); + } + } + } + +} \ No newline at end of file diff --git a/TLM/TLM/Geometry/ISegmentEndId.cs b/TLM/TLM/Geometry/ISegmentEndId.cs index b8ea1c889..b096c5c3e 100644 --- a/TLM/TLM/Geometry/ISegmentEndId.cs +++ b/TLM/TLM/Geometry/ISegmentEndId.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.Geometry { + using System; -namespace TrafficManager.Geometry { - public interface ISegmentEndId : IEquatable { - // TODO documentation - ushort SegmentId { get; } - bool StartNode { get; } + public interface ISegmentEndId : IEquatable { + // TODO documentation + ushort SegmentId { get; } + bool StartNode { get; } - bool Relocate(ushort segmentId, bool startNode); - } -} + bool Relocate(ushort segmentId, bool startNode); + } +} \ No newline at end of file diff --git a/TLM/TLM/LoadingExtension.cs b/TLM/TLM/LoadingExtension.cs index 3d2f5fd1c..43a0a0b71 100644 --- a/TLM/TLM/LoadingExtension.cs +++ b/TLM/TLM/LoadingExtension.cs @@ -2539,6 +2539,8 @@ public override void OnReleased() { public override void OnLevelUnloading() { Log.Info("OnLevelUnloading"); + World.TearDown(); + base.OnLevelUnloading(); if (IsPathManagerReplaced) { CustomPathManager._instance.WaitForAllPaths(); @@ -2698,7 +2700,7 @@ public override void OnLevelLoaded(LoadMode mode) { // add "remove citizen instance" button UIView.GetAView().gameObject.AddComponent(); - + initDetours(); //Log.Info("Fixing non-created nodes with problems..."); @@ -2713,6 +2715,8 @@ public override void OnLevelLoaded(LoadMode mode) { //InitTool(); //Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); + World.Setup(); + Log.Info("OnLevelLoaded complete."); } diff --git a/TLM/TLM/Manager/Impl/LaneArrowManager.cs b/TLM/TLM/Manager/Impl/LaneArrowManager.cs index 8ea6f0786..6244e812c 100644 --- a/TLM/TLM/Manager/Impl/LaneArrowManager.cs +++ b/TLM/TLM/Manager/Impl/LaneArrowManager.cs @@ -12,157 +12,160 @@ using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { - public class LaneArrowManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager, ILaneArrowManager { - public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; - public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; - public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle &~ ExtVehicleType.Emergency; - - public static readonly LaneArrowManager Instance = new LaneArrowManager(); - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"- Not implemented -"); - // TODO implement - } - - public LaneArrows GetFinalLaneArrows(uint laneId) { - return Flags.getFinalLaneArrowFlags(laneId, true); - } - - public bool SetLaneArrows(uint laneId, LaneArrows flags, bool overrideHighwayArrows = false) { - if (Flags.setLaneArrowFlags(laneId, flags, overrideHighwayArrows)) { - OnLaneChange(laneId); - return true; - } - return false; - } - - public bool ToggleLaneArrows(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { - if (Flags.toggleLaneArrowFlags(laneId, startNode, flags, out res)) { - OnLaneChange(laneId); - return true; - } - return false; - } - - protected void OnLaneChange(uint laneId) { - Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - RoutingManager.Instance.RequestRecalculation(lane.m_segment); - - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(lane.m_segment); - } - return true; - }); - } - - protected override void HandleInvalidSegment(SegmentGeometry geometry) { - Flags.resetSegmentArrowFlags(geometry.SegmentId); - } - - protected override void HandleValidSegment(SegmentGeometry geometry) { - - } - - public void ApplyFlags() { - for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { - Flags.applyLaneArrowFlags(laneId); - } - } - - public override void OnBeforeSaveData() { - base.OnBeforeSaveData(); - ApplyFlags(); - } - - public override void OnAfterLoadData() { - base.OnAfterLoadData(); - Flags.clearHighwayLaneArrows(); - ApplyFlags(); - } - - [Obsolete] - public bool LoadData(string data) { - bool success = true; - Log.Info($"Loading lane arrow data (old method)"); + public class LaneArrowManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager, ILaneArrowManager { + public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; + public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; + public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle &~ ExtVehicleType.Emergency; + + public static readonly LaneArrowManager Instance = new LaneArrowManager(); + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + + public LaneArrows GetFinalLaneArrows(uint laneId) { + return Flags.getFinalLaneArrowFlags(laneId, true); + } + + public bool SetLaneArrows(uint laneId, LaneArrows flags, bool overrideHighwayArrows = false) { + if (Flags.setLaneArrowFlags(laneId, flags, overrideHighwayArrows)) { + OnLaneChange(laneId); + return true; + } + return false; + } + + public bool ToggleLaneArrows(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { + if (Flags.toggleLaneArrowFlags(laneId, startNode, flags, out res)) { + OnLaneChange(laneId); + return true; + } + return false; + } + + protected void OnLaneChange(uint laneId) { + Services.NetService.ProcessLane( + laneId, + delegate(uint lId, ref NetLane lane) { + RoutingManager.Instance.RequestRecalculation(lane.m_segment); + + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(lane.m_segment); + } + + return true; + }); + } + + protected override void HandleInvalidSegment(SegmentGeometry geometry) { + Flags.resetSegmentArrowFlags(geometry.SegmentId); + } + + protected override void HandleValidSegment(SegmentGeometry geometry) { + + } + + public void ApplyFlags() { + for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { + Flags.applyLaneArrowFlags(laneId); + } + } + + public override void OnBeforeSaveData() { + base.OnBeforeSaveData(); + ApplyFlags(); + } + + public override void OnAfterLoadData() { + base.OnAfterLoadData(); + Flags.clearHighwayLaneArrows(); + ApplyFlags(); + } + + [Obsolete] + public bool LoadData(string data) { + bool success = true; + Log.Info($"Loading lane arrow data (old method)"); #if DEBUG - Log._Debug($"LaneFlags: {data}"); + Log._Debug($"LaneFlags: {data}"); #endif - var lanes = data.Split(','); - - if (lanes.Length > 1) { - foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { - try { - Log._Debug($"Split Data: {split[0]} , {split[1]}"); - var laneId = Convert.ToUInt32(split[0]); - uint flags = Convert.ToUInt32(split[1]); - - if (!Services.NetService.IsLaneValid(laneId)) - continue; - - if (flags > ushort.MaxValue) - continue; - - uint laneArrowFlags = flags & Flags.lfr; - uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); + var lanes = data.Split(','); + + if (lanes.Length > 1) { + foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { + try { + Log._Debug($"Split Data: {split[0]} , {split[1]}"); + var laneId = Convert.ToUInt32(split[0]); + uint flags = Convert.ToUInt32(split[1]); + + if (!Services.NetService.IsLaneValid(laneId)) + continue; + + if (flags > ushort.MaxValue) + continue; + + uint laneArrowFlags = flags & Flags.lfr; + uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); #if DEBUG - Log._Debug("Setting flags for lane " + laneId + " to " + flags + " (" + ((Flags.LaneArrows)(laneArrowFlags)).ToString() + ")"); - if ((origFlags | laneArrowFlags) == origFlags) { // only load if setting differs from default - Log._Debug("Flags for lane " + laneId + " are original (" + ((NetLane.Flags)(origFlags)).ToString() + ")"); - } + Log._Debug("Setting flags for lane " + laneId + " to " + flags + " (" + ((Flags.LaneArrows)(laneArrowFlags)).ToString() + ")"); + if ((origFlags | laneArrowFlags) == origFlags) { // only load if setting differs from default + Log._Debug("Flags for lane " + laneId + " are original (" + ((NetLane.Flags)(origFlags)).ToString() + ")"); + } #endif - SetLaneArrows(laneId, (Flags.LaneArrows)laneArrowFlags); - } catch (Exception e) { - Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.ToString()}"); - success = false; - } - } - } - return success; - } - - [Obsolete] - string ICustomDataManager.SaveData(ref bool success) { - return null; - } - - public bool LoadData(List data) { - bool success = true; - Log.Info($"Loading lane arrow data (new method)"); - - foreach (var laneArrowData in data) { - try { - if (!Services.NetService.IsLaneValid(laneArrowData.laneId)) - continue; - - uint laneArrowFlags = laneArrowData.arrows & Flags.lfr; - SetLaneArrows(laneArrowData.laneId, (Flags.LaneArrows)laneArrowFlags); - } catch (Exception e) { - Log.Error($"Error loading lane arrow data for lane {laneArrowData.laneId}, arrows={laneArrowData.arrows}: {e.ToString()}"); - success = false; - } - } - return success; - } - - public List SaveData(ref bool success) { - List ret = new List(); - for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { - try { - Flags.LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); - - if (laneArrows == null) - continue; - - uint laneArrowInt = (uint)laneArrows; - Log._Debug($"Saving lane arrows for lane {i}, setting to {laneArrows.ToString()} ({laneArrowInt})"); - ret.Add(new Configuration.LaneArrowData(i, laneArrowInt)); - } catch (Exception e) { - Log.Error($"Exception occurred while saving lane arrows @ {i}: {e.ToString()}"); - success = false; - } - } - return ret; - } - } -} + SetLaneArrows(laneId, (Flags.LaneArrows)laneArrowFlags); + } catch (Exception e) { + Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.ToString()}"); + success = false; + } + } + } + return success; + } + + [Obsolete] + string ICustomDataManager.SaveData(ref bool success) { + return null; + } + + public bool LoadData(List data) { + bool success = true; + Log.Info($"Loading lane arrow data (new method)"); + + foreach (var laneArrowData in data) { + try { + if (!Services.NetService.IsLaneValid(laneArrowData.laneId)) + continue; + + uint laneArrowFlags = laneArrowData.arrows & Flags.lfr; + SetLaneArrows(laneArrowData.laneId, (Flags.LaneArrows)laneArrowFlags); + } catch (Exception e) { + Log.Error($"Error loading lane arrow data for lane {laneArrowData.laneId}, arrows={laneArrowData.arrows}: {e.ToString()}"); + success = false; + } + } + return success; + } + + public List SaveData(ref bool success) { + List ret = new List(); + for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { + try { + Flags.LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); + + if (laneArrows == null) + continue; + + uint laneArrowInt = (uint)laneArrows; + Log._Debug($"Saving lane arrows for lane {i}, setting to {laneArrows.ToString()} ({laneArrowInt})"); + ret.Add(new Configuration.LaneArrowData(i, laneArrowInt)); + } catch (Exception e) { + Log.Error($"Exception occurred while saving lane arrows @ {i}: {e.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Resources/LaneTool/DirectionBlocked.png b/TLM/TLM/Resources/LaneTool/DirectionBlocked.png new file mode 100644 index 000000000..c8e6c83bc Binary files /dev/null and b/TLM/TLM/Resources/LaneTool/DirectionBlocked.png differ diff --git a/TLM/TLM/Resources/WorldSpaceGUI/SeeThroughZ.shader b/TLM/TLM/Resources/WorldSpaceGUI/SeeThroughZ.shader new file mode 100644 index 000000000..724b3a2a0 --- /dev/null +++ b/TLM/TLM/Resources/WorldSpaceGUI/SeeThroughZ.shader @@ -0,0 +1,92 @@ +// This shader renders UI through the world geometry +// +Shader "Custom/UI Overlay SH" +{ + Properties + { + [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} + _Color("Tint", Color) = (1,1,1,1) + + _StencilComp("Stencil Comparison", Float) = 8 + _Stencil("Stencil ID", Float) = 0 + _StencilOp("Stencil Operation", Float) = 0 + _StencilWriteMask("Stencil Write Mask", Float) = 255 + _StencilReadMask("Stencil Read Mask", Float) = 255 + + _ColorMask("Color Mask", Float) = 15 + } + + SubShader + { + Tags + { + "Queue" = "Overlay" + "IgnoreProjector" = "True" + "RenderType" = "Transparent" + "PreviewType" = "Plane" + "CanUseSpriteAtlas" = "True" + } + + Stencil + { + Ref[_Stencil] + Comp[_StencilComp] + Pass[_StencilOp] + ReadMask[_StencilReadMask] + WriteMask[_StencilWriteMask] + } + + Cull Off + Lighting Off + ZWrite Off + ZTest Off + Blend SrcAlpha OneMinusSrcAlpha + ColorMask[_ColorMask] + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + + struct appdata_t + { + float4 vertex : POSITION; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + fixed4 color : COLOR; + half2 texcoord : TEXCOORD0; + }; + + fixed4 _Color; + + v2f vert(appdata_t IN) + { + v2f OUT; + OUT.vertex = UnityObjectToClipPos(IN.vertex); + OUT.texcoord = IN.texcoord; +#ifdef UNITY_HALF_TEXEL_OFFSET + OUT.vertex.xy += (_ScreenParams.zw - 1.0)*float2(-1,1); +#endif + OUT.color = IN.color * _Color; + return OUT; + } + + sampler2D _MainTex; + + fixed4 frag(v2f IN) : SV_Target + { + half4 color = tex2D(_MainTex, IN.texcoord) * IN.color; + clip(color.a - 0.01); + return color; + } + ENDCG + } + } +} diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index dfe83f7fb..623715516 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -1,999 +1,999 @@ #define DEBUGFLAGSx -using ColossalFramework; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Geometry; -using TrafficManager.Manager; -using TrafficManager.Manager.Impl; -using TrafficManager.Traffic; -using TrafficManager.Util; - namespace TrafficManager.State { - [Obsolete] - public class Flags { - [Flags] - public enum LaneArrows { // compatible with NetLane.Flags - None = 0, - Forward = 16, - Left = 32, - Right = 64, - LeftForward = 48, - LeftRight = 96, - ForwardRight = 80, - LeftForwardRight = 112 - } - - public enum LaneArrowChangeResult { - Invalid, - HighwayArrows, - LaneConnection, - Success - } - - public static readonly uint lfr = (uint)NetLane.Flags.LeftForwardRight; - - /// - /// For each lane: Defines the lane arrows which are set - /// - private static LaneArrows?[] laneArrowFlags = null; - - /// - /// For each lane (by id): list of lanes that are connected with this lane by the T++ lane connector - /// key 1: source lane id - /// key 2: at start node? - /// values: target lane id - /// - internal static uint[][][] laneConnections = null; - - /// - /// For each lane: Defines the currently set speed limit - /// - private static Dictionary laneSpeedLimit = null; // TODO remove - - internal static float?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index - - /// - /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) - /// - private static LaneArrows?[] highwayLaneArrowFlags = null; - - /// - /// For each lane: Defines the allowed vehicle types - /// - internal static ExtVehicleType?[][] laneAllowedVehicleTypesArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index - - private static object laneSpeedLimitLock = new object(); - - internal static void PrintDebugInfo() { - Log.Info("------------------------"); - Log.Info("--- LANE ARROW FLAGS ---"); - Log.Info("------------------------"); - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - if (highwayLaneArrowFlags[i] != null || laneArrowFlags[i] != null) { - Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}"); - } - - if (highwayLaneArrowFlags[i] != null) { - Log.Info($"\thighway arrows: {highwayLaneArrowFlags[i]}"); - } - - if (laneArrowFlags[i] != null) { - Log.Info($"\tcustom arrows: {laneArrowFlags[i]}"); - } - } - - Log.Info("------------------------"); - Log.Info("--- LANE CONNECTIONS ---"); - Log.Info("------------------------"); - for (uint i = 0; i < laneConnections.Length; ++i) { - if (laneConnections[i] == null) - continue; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[i].m_segment; - Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}, seg. valid? {Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)}"); - for (int x = 0; x < 2; ++x) { - if (laneConnections[i][x] == null) - continue; - - ushort nodeId = x == 0 ? Singleton.instance.m_segments.m_buffer[segmentId].m_startNode : Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; - Log.Info($"\tNode idx {x} ({nodeId}, seg. {segmentId}): valid? {Constants.ServiceFactory.NetService.IsNodeValid(nodeId)}"); - - for (int y = 0; y < laneConnections[i][x].Length; ++y) { - if (laneConnections[i][x][y] == 0) - continue; - - Log.Info($"\t\tEntry {y}: {laneConnections[i][x][y]} (valid? {Constants.ServiceFactory.NetService.IsLaneValid(laneConnections[i][x][y])})"); - } - } - } - - Log.Info("-------------------------"); - Log.Info("--- LANE SPEED LIMITS ---"); - Log.Info("-------------------------"); - for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { - if (laneSpeedLimitArray[i] == null) - continue; - Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); - for (int x = 0; x < laneSpeedLimitArray[i].Length; ++x) { - if (laneSpeedLimitArray[i][x] == null) - continue; - Log.Info($"\tLane idx {x}: {laneSpeedLimitArray[i][x]}"); - } - } - - Log.Info("---------------------------------"); - Log.Info("--- LANE VEHICLE RESTRICTIONS ---"); - Log.Info("---------------------------------"); - for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { - if (laneAllowedVehicleTypesArray[i] == null) - continue; - Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); - for (int x = 0; x < laneAllowedVehicleTypesArray[i].Length; ++x) { - if (laneAllowedVehicleTypesArray[i][x] == null) - continue; - Log.Info($"\tLane idx {x}: {laneAllowedVehicleTypesArray[i][x]}"); - } - } - } - - [Obsolete] - public static bool mayHaveTrafficLight(ushort nodeId) { - if (nodeId <= 0) { - return false; - } - - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (not created). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - ItemClass connectionClass = Singleton.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) == NetNode.Flags.None && - connectionClass.m_service != ItemClass.Service.PublicTransport - ) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no junction or not public transport). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags} connectionClass={connectionClass?.m_service}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - if (connectionClass == null || - (connectionClass.m_service != ItemClass.Service.Road && - connectionClass.m_service != ItemClass.Service.PublicTransport)) { - //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no connection class). connectionClass={connectionClass?.m_service}"); - Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; - return false; - } - - return true; - } - - [Obsolete] - public static bool setNodeTrafficLight(ushort nodeId, bool flag) { - if (nodeId <= 0) - return false; + using System; + using System.Collections.Generic; + using System.Threading; + using ColossalFramework; + using CSUtil.Commons; + using JetBrains.Annotations; + using Manager.Impl; + using Traffic; + + [Obsolete] + public class Flags { + [Flags] + public enum LaneArrows { // compatible with NetLane.Flags + None = 0, + Forward = 16, + Left = 32, + Right = 64, + [UsedImplicitly] + LeftForward = Forward + Left, + [UsedImplicitly] + LeftRight = Left + Right, + [UsedImplicitly] + ForwardRight = Forward + Right, + LeftForwardRight = Left + Forward + Right + } + + public enum LaneArrowChangeResult { + Invalid, + HighwayArrows, + LaneConnection, + Success + } + + public static readonly uint lfr = (uint)NetLane.Flags.LeftForwardRight; + + /// + /// For each lane: Defines the lane arrows which are set + /// + private static LaneArrows?[] laneArrowFlags = null; + + /// + /// For each lane (by id): list of lanes that are connected with this lane by the T++ lane connector + /// key 1: source lane id + /// key 2: at start node? + /// values: target lane id + /// + internal static uint[][][] laneConnections = null; + + /// + /// For each lane: Defines the currently set speed limit + /// + private static Dictionary laneSpeedLimit = null; // TODO remove + + internal static float?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index + + /// + /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) + /// + private static LaneArrows?[] highwayLaneArrowFlags = null; + + /// + /// For each lane: Defines the allowed vehicle types + /// + internal static ExtVehicleType?[][] laneAllowedVehicleTypesArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index + + private static object laneSpeedLimitLock = new object(); + + internal static void PrintDebugInfo() { + Log.Info("------------------------"); + Log.Info("--- LANE ARROW FLAGS ---"); + Log.Info("------------------------"); + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + if (highwayLaneArrowFlags[i] != null || laneArrowFlags[i] != null) { + Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}"); + } + + if (highwayLaneArrowFlags[i] != null) { + Log.Info($"\thighway arrows: {highwayLaneArrowFlags[i]}"); + } + + if (laneArrowFlags[i] != null) { + Log.Info($"\tcustom arrows: {laneArrowFlags[i]}"); + } + } + + Log.Info("------------------------"); + Log.Info("--- LANE CONNECTIONS ---"); + Log.Info("------------------------"); + for (uint i = 0; i < laneConnections.Length; ++i) { + if (laneConnections[i] == null) + continue; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[i].m_segment; + Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}, seg. valid? {Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)}"); + for (int x = 0; x < 2; ++x) { + if (laneConnections[i][x] == null) + continue; + + ushort nodeId = x == 0 ? Singleton.instance.m_segments.m_buffer[segmentId].m_startNode : Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; + Log.Info($"\tNode idx {x} ({nodeId}, seg. {segmentId}): valid? {Constants.ServiceFactory.NetService.IsNodeValid(nodeId)}"); + + for (int y = 0; y < laneConnections[i][x].Length; ++y) { + if (laneConnections[i][x][y] == 0) + continue; + + Log.Info($"\t\tEntry {y}: {laneConnections[i][x][y]} (valid? {Constants.ServiceFactory.NetService.IsLaneValid(laneConnections[i][x][y])})"); + } + } + } + + Log.Info("-------------------------"); + Log.Info("--- LANE SPEED LIMITS ---"); + Log.Info("-------------------------"); + for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { + if (laneSpeedLimitArray[i] == null) + continue; + Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); + for (int x = 0; x < laneSpeedLimitArray[i].Length; ++x) { + if (laneSpeedLimitArray[i][x] == null) + continue; + Log.Info($"\tLane idx {x}: {laneSpeedLimitArray[i][x]}"); + } + } + + Log.Info("---------------------------------"); + Log.Info("--- LANE VEHICLE RESTRICTIONS ---"); + Log.Info("---------------------------------"); + for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { + if (laneAllowedVehicleTypesArray[i] == null) + continue; + Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); + for (int x = 0; x < laneAllowedVehicleTypesArray[i].Length; ++x) { + if (laneAllowedVehicleTypesArray[i][x] == null) + continue; + Log.Info($"\tLane idx {x}: {laneAllowedVehicleTypesArray[i][x]}"); + } + } + } + + [Obsolete] + public static bool mayHaveTrafficLight(ushort nodeId) { + if (nodeId <= 0) { + return false; + } + + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (not created). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + ItemClass connectionClass = Singleton.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) == NetNode.Flags.None && + connectionClass.m_service != ItemClass.Service.PublicTransport + ) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no junction or not public transport). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags} connectionClass={connectionClass?.m_service}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + if (connectionClass == null || + (connectionClass.m_service != ItemClass.Service.Road && + connectionClass.m_service != ItemClass.Service.PublicTransport)) { + //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no connection class). connectionClass={connectionClass?.m_service}"); + Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; + return false; + } + + return true; + } + + [Obsolete] + public static bool setNodeTrafficLight(ushort nodeId, bool flag) { + if (nodeId <= 0) + return false; #if DEBUGFLAGS Log._Debug($"Flags: Set node traffic light: {nodeId}={flag}"); #endif - if (!mayHaveTrafficLight(nodeId)) { - //Log.Warning($"Flags: Refusing to add/delete traffic light to/from node: {nodeId} {flag}"); - return false; - } + if (!mayHaveTrafficLight(nodeId)) { + //Log.Warning($"Flags: Refusing to add/delete traffic light to/from node: {nodeId} {flag}"); + return false; + } - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; - if ((bool)flag) { + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; + if ((bool)flag) { #if DEBUGFLAGS Log._Debug($"Adding traffic light @ node {nId}"); #endif - flags |= NetNode.Flags.TrafficLights; - } else { + flags |= NetNode.Flags.TrafficLights; + } else { #if DEBUGFLAGS Log._Debug($"Removing traffic light @ node {nId}"); #endif - flags &= ~NetNode.Flags.TrafficLights; - } - node.m_flags = flags; - return true; - }); - return true; - } - - [Obsolete] - internal static bool isNodeTrafficLight(ushort nodeId) { - if (nodeId <= 0) - return false; - - if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) - return false; - - return (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; - } - - /// - /// Removes lane connections that point from lane to lane at node . - /// - /// - /// - /// - /// - private static bool RemoveSingleLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { + flags &= ~NetNode.Flags.TrafficLights; + } + node.m_flags = flags; + return true; + }); + return true; + } + + [Obsolete] + internal static bool isNodeTrafficLight(ushort nodeId) { + if (nodeId <= 0) + return false; + + if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) + return false; + + return (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; + } + + /// + /// Removes lane connections that point from lane to lane at node . + /// + /// + /// + /// + /// + private static bool RemoveSingleLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { #if DEBUGFLAGS Log._Debug($"Flags.CleanupLaneConnections({sourceLaneId}, {targetLaneId}, {startNode}) called."); #endif - int nodeArrayIndex = startNode ? 0 : 1; - - if (laneConnections[sourceLaneId] == null || laneConnections[sourceLaneId][nodeArrayIndex] == null) - return false; - - uint[] srcLaneConnections = laneConnections[sourceLaneId][nodeArrayIndex]; - - bool ret = false; - int remainingConnections = 0; - for (int i = 0; i < srcLaneConnections.Length; ++i) { - if (srcLaneConnections[i] != targetLaneId) { - ++remainingConnections; - } else { - ret = true; - srcLaneConnections[i] = 0; - } - } - - if (remainingConnections <= 0) { - laneConnections[sourceLaneId][nodeArrayIndex] = null; - if (laneConnections[sourceLaneId][1 - nodeArrayIndex] == null) - laneConnections[sourceLaneId] = null; // total cleanup - return ret; - } - - if (remainingConnections != srcLaneConnections.Length) { - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[remainingConnections]; - int k = 0; - for (int i = 0; i < srcLaneConnections.Length; ++i) { - if (srcLaneConnections[i] == 0) - continue; - laneConnections[sourceLaneId][nodeArrayIndex][k++] = srcLaneConnections[i]; - } - } - return ret; - } - - /// - /// Removes any lane connections that exist between two given lanes - /// - /// - /// - /// - /// - internal static bool RemoveLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { + int nodeArrayIndex = startNode ? 0 : 1; + + if (laneConnections[sourceLaneId] == null || laneConnections[sourceLaneId][nodeArrayIndex] == null) + return false; + + uint[] srcLaneConnections = laneConnections[sourceLaneId][nodeArrayIndex]; + + bool ret = false; + int remainingConnections = 0; + for (int i = 0; i < srcLaneConnections.Length; ++i) { + if (srcLaneConnections[i] != targetLaneId) { + ++remainingConnections; + } else { + ret = true; + srcLaneConnections[i] = 0; + } + } + + if (remainingConnections <= 0) { + laneConnections[sourceLaneId][nodeArrayIndex] = null; + if (laneConnections[sourceLaneId][1 - nodeArrayIndex] == null) + laneConnections[sourceLaneId] = null; // total cleanup + return ret; + } + + if (remainingConnections != srcLaneConnections.Length) { + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[remainingConnections]; + int k = 0; + for (int i = 0; i < srcLaneConnections.Length; ++i) { + if (srcLaneConnections[i] == 0) + continue; + laneConnections[sourceLaneId][nodeArrayIndex][k++] = srcLaneConnections[i]; + } + } + return ret; + } + + /// + /// Removes any lane connections that exist between two given lanes + /// + /// + /// + /// + /// + internal static bool RemoveLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}) called."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}) called."); #endif - bool lane1Valid = CheckLane(lane1Id); - bool lane2Valid = CheckLane(lane2Id); - - bool ret = false; - - if (! lane1Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane1Id); - ret = true; - } - - if (! lane2Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane2Id); - ret = true; - } - - if (lane1Valid || lane2Valid) { - ushort commonNodeId; - bool startNode2; - - LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor - if (commonNodeId == 0) { - Log.Warning($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}): Could not identify common node between lanes {lane1Id} and {lane2Id}"); - } - - if (RemoveSingleLaneConnection(lane1Id, lane2Id, startNode1)) - ret = true; - if (RemoveSingleLaneConnection(lane2Id, lane1Id, startNode2)) - ret = true; - } + bool lane1Valid = CheckLane(lane1Id); + bool lane2Valid = CheckLane(lane2Id); + + bool ret = false; + + if (! lane1Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane1Id); + ret = true; + } + + if (! lane2Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane2Id); + ret = true; + } + + if (lane1Valid || lane2Valid) { + ushort commonNodeId; + bool startNode2; + + LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor + if (commonNodeId == 0) { + Log.Warning($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}): Could not identify common node between lanes {lane1Id} and {lane2Id}"); + } + + if (RemoveSingleLaneConnection(lane1Id, lane2Id, startNode1)) + ret = true; + if (RemoveSingleLaneConnection(lane2Id, lane1Id, startNode2)) + ret = true; + } #if DEBUGCONN - if (debug) - Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}). ret={ret}"); + if (debug) + Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}). ret={ret}"); #endif - return ret; - } - - /// - /// Removes all incoming/outgoing lane connections of the given lane - /// - /// - /// - internal static void RemoveLaneConnections(uint laneId, bool? startNode=null) { + return ret; + } + + /// + /// Removes all incoming/outgoing lane connections of the given lane + /// + /// + /// + internal static void RemoveLaneConnections(uint laneId, bool? startNode=null) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}) called. laneConnections[{laneId}]={laneConnections[laneId]}"); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}) called. laneConnections[{laneId}]={laneConnections[laneId]}"); #endif - if (laneConnections[laneId] == null) - return; + if (laneConnections[laneId] == null) + return; - bool laneValid = CheckLane(laneId); - bool clearBothSides = startNode == null || !laneValid; + bool laneValid = CheckLane(laneId); + bool clearBothSides = startNode == null || !laneValid; #if DEBUGCONN - if (debug) - Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); + if (debug) + Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); #endif - int? nodeArrayIndex = null; - if (!clearBothSides) { - nodeArrayIndex = (bool)startNode ? 0 : 1; - } - - for (int k = 0; k <= 1; ++k) { - if (nodeArrayIndex != null && k != (int)nodeArrayIndex) - continue; - - bool startNode1 = k == 0; - - if (laneConnections[laneId][k] == null) - continue; - - for (int i = 0; i < laneConnections[laneId][k].Length; ++i) { - uint otherLaneId = laneConnections[laneId][k][i]; - ushort commonNodeId; - bool startNode2; - LaneConnectionManager.Instance.GetCommonNodeId(laneId, otherLaneId, startNode1, out commonNodeId, out startNode2); // TODO refactor - if (commonNodeId == 0) { - Log.Warning($"Flags.RemoveLaneConnections({laneId}, {startNode}): Could not identify common node between lanes {laneId} and {otherLaneId}"); - } - - RemoveSingleLaneConnection(otherLaneId, laneId, startNode2); - } - - laneConnections[laneId][k] = null; - } - - if (clearBothSides) - laneConnections[laneId] = null; - } - - /// - /// adds lane connections between two given lanes - /// - /// - /// - /// - /// - internal static bool AddLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { - bool lane1Valid = CheckLane(lane1Id); - bool lane2Valid = CheckLane(lane2Id); - - if (!lane1Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane1Id); - } - - if (!lane2Valid) { - // remove all incoming/outgoing lane connections - RemoveLaneConnections(lane2Id); - } - - if (!lane1Valid || !lane2Valid) - return false; - - ushort commonNodeId; - bool startNode2; - LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor - - if (commonNodeId != 0) { - CreateLaneConnection(lane1Id, lane2Id, startNode1); - CreateLaneConnection(lane2Id, lane1Id, startNode2); - - return true; - } else - return false; - } - - /// - /// Adds a lane connection from lane to lane at node - /// Assumes that both lanes are valid. - /// - /// - /// - /// - private static void CreateLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { - if (laneConnections[sourceLaneId] == null) { - laneConnections[sourceLaneId] = new uint[2][]; - } - - int nodeArrayIndex = startNode ? 0 : 1; - - if (laneConnections[sourceLaneId][nodeArrayIndex] == null) { - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[] { targetLaneId }; - return; - } - - uint[] oldConnections = laneConnections[sourceLaneId][nodeArrayIndex]; - laneConnections[sourceLaneId][nodeArrayIndex] = new uint[oldConnections.Length + 1]; - Array.Copy(oldConnections, laneConnections[sourceLaneId][nodeArrayIndex], oldConnections.Length); - laneConnections[sourceLaneId][nodeArrayIndex][oldConnections.Length] = targetLaneId; - } - - internal static bool CheckLane(uint laneId) { // TODO refactor - if (laneId <= 0) - return false; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return false; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if (segmentId <= 0) - return false; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return false; - return true; - } - - public static void setLaneSpeedLimit(uint laneId, float? speedLimit) { - if (!CheckLane(laneId)) { - return; - } - - var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - uint laneIndex = 0; - while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - if (curLaneId == laneId) { - setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); - return; - } - laneIndex++; - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - } - } - - public static void removeLaneSpeedLimit(uint laneId) { - setLaneSpeedLimit(laneId, null); - } - - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, float speedLimit) { - if (segmentId <= 0 || laneIndex < 0) { - return; - } - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & - (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { - return; - } - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } - - // find the lane id - var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - for (var i = 0; i < laneIndex; ++i) { - if (laneId == 0) { - return; // no valid lane found - } - laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; - } - - setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); - } - - public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, float? speedLimit) { - if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) { - return; - } - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { - return; - } - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) { - return; - } - var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } - - try { - Monitor.Enter(laneSpeedLimitLock); + int? nodeArrayIndex = null; + if (!clearBothSides) { + nodeArrayIndex = (bool)startNode ? 0 : 1; + } + + for (int k = 0; k <= 1; ++k) { + if (nodeArrayIndex != null && k != (int)nodeArrayIndex) + continue; + + bool startNode1 = k == 0; + + if (laneConnections[laneId][k] == null) + continue; + + for (int i = 0; i < laneConnections[laneId][k].Length; ++i) { + uint otherLaneId = laneConnections[laneId][k][i]; + ushort commonNodeId; + bool startNode2; + LaneConnectionManager.Instance.GetCommonNodeId(laneId, otherLaneId, startNode1, out commonNodeId, out startNode2); // TODO refactor + if (commonNodeId == 0) { + Log.Warning($"Flags.RemoveLaneConnections({laneId}, {startNode}): Could not identify common node between lanes {laneId} and {otherLaneId}"); + } + + RemoveSingleLaneConnection(otherLaneId, laneId, startNode2); + } + + laneConnections[laneId][k] = null; + } + + if (clearBothSides) + laneConnections[laneId] = null; + } + + /// + /// adds lane connections between two given lanes + /// + /// + /// + /// + /// + internal static bool AddLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { + bool lane1Valid = CheckLane(lane1Id); + bool lane2Valid = CheckLane(lane2Id); + + if (!lane1Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane1Id); + } + + if (!lane2Valid) { + // remove all incoming/outgoing lane connections + RemoveLaneConnections(lane2Id); + } + + if (!lane1Valid || !lane2Valid) + return false; + + ushort commonNodeId; + bool startNode2; + LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor + + if (commonNodeId != 0) { + CreateLaneConnection(lane1Id, lane2Id, startNode1); + CreateLaneConnection(lane2Id, lane1Id, startNode2); + + return true; + } else + return false; + } + + /// + /// Adds a lane connection from lane to lane at node + /// Assumes that both lanes are valid. + /// + /// + /// + /// + private static void CreateLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { + if (laneConnections[sourceLaneId] == null) { + laneConnections[sourceLaneId] = new uint[2][]; + } + + int nodeArrayIndex = startNode ? 0 : 1; + + if (laneConnections[sourceLaneId][nodeArrayIndex] == null) { + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[] { targetLaneId }; + return; + } + + uint[] oldConnections = laneConnections[sourceLaneId][nodeArrayIndex]; + laneConnections[sourceLaneId][nodeArrayIndex] = new uint[oldConnections.Length + 1]; + Array.Copy(oldConnections, laneConnections[sourceLaneId][nodeArrayIndex], oldConnections.Length); + laneConnections[sourceLaneId][nodeArrayIndex][oldConnections.Length] = targetLaneId; + } + + internal static bool CheckLane(uint laneId) { // TODO refactor + if (laneId <= 0) + return false; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return false; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if (segmentId <= 0) + return false; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return false; + return true; + } + + public static void setLaneSpeedLimit(uint laneId, float? speedLimit) { + if (!CheckLane(laneId)) { + return; + } + + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + uint laneIndex = 0; + while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { + if (curLaneId == laneId) { + setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); + return; + } + laneIndex++; + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + } + } + + public static void removeLaneSpeedLimit(uint laneId) { + setLaneSpeedLimit(laneId, null); + } + + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, float speedLimit) { + if (segmentId <= 0 || laneIndex < 0) { + return; + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & + (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { + return; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } + + // find the lane id + var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + for (var i = 0; i < laneIndex; ++i) { + if (laneId == 0) { + return; // no valid lane found + } + laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; + } + + setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); + } + + public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, float? speedLimit) { + if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) { + return; + } + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) { + return; + } + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) { + return; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } + + try { + Monitor.Enter(laneSpeedLimitLock); #if DEBUGFLAGS Log._Debug($"Flags.setLaneSpeedLimit: setting speed limit of lane index {laneIndex} @ seg. {segmentId} to {speedLimit}"); #endif - if (speedLimit == null) { - laneSpeedLimit.Remove(laneId); - - if (laneSpeedLimitArray[segmentId] == null) { - return; - } - if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { - return; - } - laneSpeedLimitArray[segmentId][laneIndex] = null; - } else { - laneSpeedLimit[laneId] = speedLimit.Value; - - // save speed limit into the fast-access array. - // (1) ensure that the array is defined and large enough - if (laneSpeedLimitArray[segmentId] == null) { - laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; - } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { - var oldArray = laneSpeedLimitArray[segmentId]; - laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; - Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); - } - // (2) insert the custom speed limit - laneSpeedLimitArray[segmentId][laneIndex] = speedLimit; - } - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - public static void setLaneAllowedVehicleTypes(uint laneId, ExtVehicleType vehicleTypes) { - if (laneId <= 0) - return; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return; - - ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; - if (segmentId <= 0) - return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - uint laneIndex = 0; - while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { - if (curLaneId == laneId) { - setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, vehicleTypes); - return; - } - laneIndex++; - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - } - } - - public static void setLaneAllowedVehicleTypes(ushort segmentId, uint laneIndex, uint laneId, ExtVehicleType vehicleTypes) { - if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) - return; - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return; - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; - if (laneIndex >= segmentInfo.m_lanes.Length) { - return; - } + if (speedLimit == null) { + laneSpeedLimit.Remove(laneId); + + if (laneSpeedLimitArray[segmentId] == null) { + return; + } + if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { + return; + } + laneSpeedLimitArray[segmentId][laneIndex] = null; + } else { + laneSpeedLimit[laneId] = speedLimit.Value; + + // save speed limit into the fast-access array. + // (1) ensure that the array is defined and large enough + if (laneSpeedLimitArray[segmentId] == null) { + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; + } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { + var oldArray = laneSpeedLimitArray[segmentId]; + laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length]; + Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); + } + // (2) insert the custom speed limit + laneSpeedLimitArray[segmentId][laneIndex] = speedLimit; + } + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + public static void setLaneAllowedVehicleTypes(uint laneId, ExtVehicleType vehicleTypes) { + if (laneId <= 0) + return; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return; + + ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + if (segmentId <= 0) + return; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + uint laneIndex = 0; + while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { + if (curLaneId == laneId) { + setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, vehicleTypes); + return; + } + laneIndex++; + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + } + } + + public static void setLaneAllowedVehicleTypes(ushort segmentId, uint laneIndex, uint laneId, ExtVehicleType vehicleTypes) { + if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) + return; + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return; + NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } #if DEBUGFLAGS Log._Debug($"Flags.setLaneAllowedVehicleTypes: setting allowed vehicles of lane index {laneIndex} @ seg. {segmentId} to {vehicleTypes.ToString()}"); #endif - // save allowed vehicle types into the fast-access array. - // (1) ensure that the array is defined and large enough - if (laneAllowedVehicleTypesArray[segmentId] == null) { - laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - } else if (laneAllowedVehicleTypesArray[segmentId].Length < segmentInfo.m_lanes.Length) { - var oldArray = laneAllowedVehicleTypesArray[segmentId]; - laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; - Array.Copy(oldArray, laneAllowedVehicleTypesArray[segmentId], oldArray.Length); - } - // (2) insert the custom speed limit - laneAllowedVehicleTypesArray[segmentId][laneIndex] = vehicleTypes; - } - - public static void resetSegmentVehicleRestrictions(ushort segmentId) { - if (segmentId <= 0) - return; + // save allowed vehicle types into the fast-access array. + // (1) ensure that the array is defined and large enough + if (laneAllowedVehicleTypesArray[segmentId] == null) { + laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + } else if (laneAllowedVehicleTypesArray[segmentId].Length < segmentInfo.m_lanes.Length) { + var oldArray = laneAllowedVehicleTypesArray[segmentId]; + laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; + Array.Copy(oldArray, laneAllowedVehicleTypesArray[segmentId], oldArray.Length); + } + // (2) insert the custom speed limit + laneAllowedVehicleTypesArray[segmentId][laneIndex] = vehicleTypes; + } + + public static void resetSegmentVehicleRestrictions(ushort segmentId) { + if (segmentId <= 0) + return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentVehicleRestrictions: Resetting vehicle restrictions of segment {segmentId}."); #endif - laneAllowedVehicleTypesArray[segmentId] = null; - } + laneAllowedVehicleTypesArray[segmentId] = null; + } - public static void resetSegmentArrowFlags(ushort segmentId) { - if (segmentId <= 0) - return; + public static void resetSegmentArrowFlags(ushort segmentId) { + if (segmentId <= 0) + return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}."); #endif - NetManager netManager = Singleton.instance; - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + NetManager netManager = Singleton.instance; + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - int laneIndex = 0; - while (laneIndex < numLanes && curLaneId != 0u) { + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + int laneIndex = 0; + while (laneIndex < numLanes && curLaneId != 0u) { #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}: Resetting lane {curLaneId}."); #endif - laneArrowFlags[curLaneId] = null; + laneArrowFlags[curLaneId] = null; - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + } - public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { + public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}) called"); #endif - if (!mayHaveLaneArrows(laneId)) { + if (!mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): lane must not have lane arrows"); #endif - removeLaneArrowFlags(laneId); - return false; - } + removeLaneArrowFlags(laneId); + return false; + } - if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { + if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): highway arrows may not be overridden"); #endif - return false; // disallow custom lane arrows in highway rule mode - } + return false; // disallow custom lane arrows in highway rule mode + } - if (overrideHighwayArrows) { + if (overrideHighwayArrows) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): overriding highway arrows"); #endif - highwayLaneArrowFlags[laneId] = null; - } + highwayLaneArrowFlags[laneId] = null; + } #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): setting flags"); #endif - laneArrowFlags[laneId] = flags; - return applyLaneArrowFlags(laneId, false); - } - - public static void setHighwayLaneArrowFlags(uint laneId, LaneArrows flags, bool check=true) { - if (check && !mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - return; - } - - highwayLaneArrowFlags[laneId] = flags; + laneArrowFlags[laneId] = flags; + return applyLaneArrowFlags(laneId, false); + } + + public static void setHighwayLaneArrowFlags(uint laneId, LaneArrows flags, bool check=true) { + if (check && !mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + return; + } + + highwayLaneArrowFlags[laneId] = flags; #if DEBUGFLAGS Log._Debug($"Flags.setHighwayLaneArrowFlags: Setting highway arrows of lane {laneId} to {flags}"); #endif - applyLaneArrowFlags(laneId, false); - } - - public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { - if (!mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - res = LaneArrowChangeResult.Invalid; - return false; - } - - if (highwayLaneArrowFlags[laneId] != null) { - res = LaneArrowChangeResult.HighwayArrows; - return false; // disallow custom lane arrows in highway rule mode - } - - if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor - res = LaneArrowChangeResult.LaneConnection; - return false; // custom lane connection present - } - - LaneArrows? arrows = laneArrowFlags[laneId]; - if (arrows == null) { - // read currently defined arrows - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - laneFlags &= lfr; // filter arrows - arrows = (LaneArrows)laneFlags; - } - - arrows ^= flags; - laneArrowFlags[laneId] = arrows; - if (applyLaneArrowFlags(laneId, false)) { - res = LaneArrowChangeResult.Success; - return true; - } else { - res = LaneArrowChangeResult.Invalid; - return false; - } - } - - internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { - if (laneId <= 0) - return false; - NetManager netManager = Singleton.instance; - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) - return false; - - ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; - - var dir = NetInfo.Direction.Forward; - var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); - - NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; - uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; - int numLanes = segmentInfo.m_lanes.Length; - int laneIndex = 0; - int wIter = 0; - while (laneIndex < numLanes && curLaneId != 0u) { - ++wIter; - if (wIter >= 100) { - Log.Error("Too many iterations in Flags.mayHaveLaneArrows!"); - break; - } - - if (curLaneId == laneId) { - NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; - bool isStartNode = (laneInfo.m_finalDirection & dir2) == NetInfo.Direction.None; - if (startNode != null && isStartNode != startNode) - return false; - ushort nodeId = isStartNode ? netManager.m_segments.m_buffer[segmentId].m_startNode : netManager.m_segments.m_buffer[segmentId].m_endNode; - - if ((netManager.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) - return false; - return (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; - } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - return false; - } - - public static float? getLaneSpeedLimit(uint laneId) { - try { - Monitor.Enter(laneSpeedLimitLock); - - float speedLimit; - if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { - return null; - } - - return speedLimit; - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - internal static IDictionary getAllLaneSpeedLimits() { - IDictionary ret = new Dictionary(); - try { - Monitor.Enter(laneSpeedLimitLock); - - ret = new Dictionary(laneSpeedLimit); - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - return ret; - } - - internal static IDictionary getAllLaneAllowedVehicleTypes() { - IDictionary ret = new Dictionary(); - - for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { - Constants.ServiceFactory.NetService.ProcessSegment((ushort)segmentId, delegate (ushort segId, ref NetSegment segment) { - if ((segment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return true; - - ExtVehicleType?[] allowedTypes = laneAllowedVehicleTypesArray[segId]; - if (allowedTypes == null) { - return true; - } - - Constants.ServiceFactory.NetService.IterateSegmentLanes(segId, ref segment, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { - if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { - return true; - } - - if (laneIndex >= allowedTypes.Length) { - return true; - } - - ExtVehicleType? allowedType = allowedTypes[laneIndex]; - - if (allowedType == null) { - return true; - } - - ret.Add(laneId, (ExtVehicleType)allowedType); - return true; - }); - return true; - }); - } - - return ret; - } - - public static LaneArrows? getLaneArrowFlags(uint laneId) { - return laneArrowFlags[laneId]; - } - - public static LaneArrows? getHighwayLaneArrowFlags(uint laneId) { - return highwayLaneArrowFlags[laneId]; - } - - public static void removeHighwayLaneArrowFlags(uint laneId) { + applyLaneArrowFlags(laneId, false); + } + + public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { + if (!mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + res = LaneArrowChangeResult.Invalid; + return false; + } + + if (highwayLaneArrowFlags[laneId] != null) { + res = LaneArrowChangeResult.HighwayArrows; + return false; // disallow custom lane arrows in highway rule mode + } + + if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor + res = LaneArrowChangeResult.LaneConnection; + return false; // custom lane connection present + } + + LaneArrows? arrows = laneArrowFlags[laneId]; + if (arrows == null) { + // read currently defined arrows + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + laneFlags &= lfr; // filter arrows + arrows = (LaneArrows)laneFlags; + } + + arrows ^= flags; + laneArrowFlags[laneId] = arrows; + if (applyLaneArrowFlags(laneId, false)) { + res = LaneArrowChangeResult.Success; + return true; + } else { + res = LaneArrowChangeResult.Invalid; + return false; + } + } + + internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { + if (laneId <= 0) + return false; + NetManager netManager = Singleton.instance; + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) + return false; + + ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; + + var dir = NetInfo.Direction.Forward; + var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); + + NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; + uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; + int numLanes = segmentInfo.m_lanes.Length; + int laneIndex = 0; + int wIter = 0; + while (laneIndex < numLanes && curLaneId != 0u) { + ++wIter; + if (wIter >= 100) { + Log.Error("Too many iterations in Flags.mayHaveLaneArrows!"); + break; + } + + if (curLaneId == laneId) { + NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + bool isStartNode = (laneInfo.m_finalDirection & dir2) == NetInfo.Direction.None; + if (startNode != null && isStartNode != startNode) + return false; + ushort nodeId = isStartNode ? netManager.m_segments.m_buffer[segmentId].m_startNode : netManager.m_segments.m_buffer[segmentId].m_endNode; + + if ((netManager.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) + return false; + return (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; + } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + return false; + } + + public static float? getLaneSpeedLimit(uint laneId) { + try { + Monitor.Enter(laneSpeedLimitLock); + + float speedLimit; + if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { + return null; + } + + return speedLimit; + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + internal static IDictionary getAllLaneSpeedLimits() { + IDictionary ret = new Dictionary(); + try { + Monitor.Enter(laneSpeedLimitLock); + + ret = new Dictionary(laneSpeedLimit); + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + return ret; + } + + internal static IDictionary getAllLaneAllowedVehicleTypes() { + IDictionary ret = new Dictionary(); + + for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { + Constants.ServiceFactory.NetService.ProcessSegment((ushort)segmentId, delegate (ushort segId, ref NetSegment segment) { + if ((segment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return true; + + ExtVehicleType?[] allowedTypes = laneAllowedVehicleTypesArray[segId]; + if (allowedTypes == null) { + return true; + } + + Constants.ServiceFactory.NetService.IterateSegmentLanes(segId, ref segment, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { + if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { + return true; + } + + if (laneIndex >= allowedTypes.Length) { + return true; + } + + ExtVehicleType? allowedType = allowedTypes[laneIndex]; + + if (allowedType == null) { + return true; + } + + ret.Add(laneId, (ExtVehicleType)allowedType); + return true; + }); + return true; + }); + } + + return ret; + } + + public static LaneArrows? getLaneArrowFlags(uint laneId) { + return laneArrowFlags[laneId]; + } + + public static LaneArrows? getHighwayLaneArrowFlags(uint laneId) { + return highwayLaneArrowFlags[laneId]; + } + + public static void removeHighwayLaneArrowFlags(uint laneId) { #if DEBUGFLAGS Log._Debug($"Flags.removeHighwayLaneArrowFlags: Removing highway arrows of lane {laneId}"); #endif - if (highwayLaneArrowFlags[laneId] != null) { - highwayLaneArrowFlags[laneId] = null; - applyLaneArrowFlags(laneId, false); - } - } - - public static void applyAllFlags() { - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - applyLaneArrowFlags(i); - } - } - - public static bool applyLaneArrowFlags(uint laneId, bool check=true) { + if (highwayLaneArrowFlags[laneId] != null) { + highwayLaneArrowFlags[laneId] = null; + applyLaneArrowFlags(laneId, false); + } + } + + public static void applyAllFlags() { + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + applyLaneArrowFlags(i); + } + } + + public static bool applyLaneArrowFlags(uint laneId, bool check=true) { #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags({laneId}, {check}) called"); #endif - if (laneId <= 0) - return true; + if (laneId <= 0) + return true; - if (check && !mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - return false; - } + if (check && !mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + return false; + } - LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; - LaneArrows? arrows = laneArrowFlags[laneId]; - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; + LaneArrows? arrows = laneArrowFlags[laneId]; + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - if (hwArrows != null) { - laneFlags &= ~lfr; // remove all arrows - laneFlags |= (uint)hwArrows; // add highway arrows - } else if (arrows != null) { - LaneArrows flags = (LaneArrows)arrows; - laneFlags &= ~lfr; // remove all arrows - laneFlags |= (uint)flags; // add desired arrows - } + if (hwArrows != null) { + laneFlags &= ~lfr; // remove all arrows + laneFlags |= (uint)hwArrows; // add highway arrows + } else if (arrows != null) { + LaneArrows flags = (LaneArrows)arrows; + laneFlags &= ~lfr; // remove all arrows + laneFlags |= (uint)flags; // add desired arrows + } #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags: Setting lane flags of lane {laneId} to {((NetLane.Flags)laneFlags).ToString()}"); #endif - Singleton.instance.m_lanes.m_buffer[laneId].m_flags = Convert.ToUInt16(laneFlags); - return true; - } + Singleton.instance.m_lanes.m_buffer[laneId].m_flags = Convert.ToUInt16(laneFlags); + return true; + } - public static LaneArrows getFinalLaneArrowFlags(uint laneId, bool check=true) { - if (! mayHaveLaneArrows(laneId)) { + public static LaneArrows getFinalLaneArrowFlags(uint laneId, bool check=true) { + if (! mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Lane {laneId} may not have lane arrows"); #endif - return LaneArrows.None; - } - - uint ret = 0; - LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; - LaneArrows? arrows = laneArrowFlags[laneId]; - - if (hwArrows != null) { - ret &= ~lfr; // remove all arrows - ret |= (uint)hwArrows; // add highway arrows - } else if (arrows != null) { - LaneArrows flags = (LaneArrows)arrows; - ret &= ~lfr; // remove all arrows - ret |= (uint)flags; // add desired arrows - } else { - Constants.ServiceFactory.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - ret = lane.m_flags; - ret &= (uint)LaneArrows.LeftForwardRight; - return true; - }); - } - - return (LaneArrows)ret; - } - - public static void removeLaneArrowFlags(uint laneId) { - if (laneId <= 0) - return; - - if (highwayLaneArrowFlags[laneId] != null) - return; // modification of arrows in highway rule mode is forbidden - - laneArrowFlags[laneId] = null; - uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; - - if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) == NetLane.Flags.Created) { - Singleton.instance.m_lanes.m_buffer[laneId].m_flags &= (ushort)~lfr; - } - } - - internal static void removeHighwayLaneArrowFlagsAtSegment(ushort segmentId) { - if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) - return; - - int i = 0; - uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; - - while (i < Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes.Length && curLaneId != 0u) { - Flags.removeHighwayLaneArrowFlags(curLaneId); - curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; - ++i; - } // foreach lane - } - - public static void clearHighwayLaneArrows() { - for (uint i = 0; i < Singleton.instance.m_lanes.m_size; ++i) { - highwayLaneArrowFlags[i] = null; - } - } - - public static void resetSpeedLimits() { - try { - Monitor.Enter(laneSpeedLimitLock); - laneSpeedLimit.Clear(); - for (int i = 0; i < Singleton.instance.m_segments.m_size; ++i) { - laneSpeedLimitArray[i] = null; - } - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - } - - internal static void OnLevelUnloading() { - for (uint i = 0; i < laneConnections.Length; ++i) { - laneConnections[i] = null; - } - - for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { - laneSpeedLimitArray[i] = null; - } - - try { - Monitor.Enter(laneSpeedLimitLock); - laneSpeedLimit.Clear(); - } finally { - Monitor.Exit(laneSpeedLimitLock); - } - - for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { - laneAllowedVehicleTypesArray[i] = null; - } - - for (uint i = 0; i < laneArrowFlags.Length; ++i) { - laneArrowFlags[i] = null; - } - - for (uint i = 0; i < highwayLaneArrowFlags.Length; ++i) { - highwayLaneArrowFlags[i] = null; - } - } - - static Flags() { - laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; - laneSpeedLimitArray = new float?[NetManager.MAX_SEGMENT_COUNT][]; - laneSpeedLimit = new Dictionary(); - laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; - laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; - highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; - } - - public static void OnBeforeLoadData() { - - } - } -} + return LaneArrows.None; + } + + uint ret = 0; + LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; + LaneArrows? arrows = laneArrowFlags[laneId]; + + if (hwArrows != null) { + ret &= ~lfr; // remove all arrows + ret |= (uint)hwArrows; // add highway arrows + } else if (arrows != null) { + LaneArrows flags = (LaneArrows)arrows; + ret &= ~lfr; // remove all arrows + ret |= (uint)flags; // add desired arrows + } else { + Constants.ServiceFactory.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { + ret = lane.m_flags; + ret &= (uint)LaneArrows.LeftForwardRight; + return true; + }); + } + + return (LaneArrows)ret; + } + + public static void removeLaneArrowFlags(uint laneId) { + if (laneId <= 0) + return; + + if (highwayLaneArrowFlags[laneId] != null) + return; // modification of arrows in highway rule mode is forbidden + + laneArrowFlags[laneId] = null; + uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; + + if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) == NetLane.Flags.Created) { + Singleton.instance.m_lanes.m_buffer[laneId].m_flags &= (ushort)~lfr; + } + } + + internal static void removeHighwayLaneArrowFlagsAtSegment(ushort segmentId) { + if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) + return; + + int i = 0; + uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + + while (i < Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes.Length && curLaneId != 0u) { + Flags.removeHighwayLaneArrowFlags(curLaneId); + curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; + ++i; + } // foreach lane + } + + public static void clearHighwayLaneArrows() { + for (uint i = 0; i < Singleton.instance.m_lanes.m_size; ++i) { + highwayLaneArrowFlags[i] = null; + } + } + + public static void resetSpeedLimits() { + try { + Monitor.Enter(laneSpeedLimitLock); + laneSpeedLimit.Clear(); + for (int i = 0; i < Singleton.instance.m_segments.m_size; ++i) { + laneSpeedLimitArray[i] = null; + } + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + } + + internal static void OnLevelUnloading() { + for (uint i = 0; i < laneConnections.Length; ++i) { + laneConnections[i] = null; + } + + for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { + laneSpeedLimitArray[i] = null; + } + + try { + Monitor.Enter(laneSpeedLimitLock); + laneSpeedLimit.Clear(); + } finally { + Monitor.Exit(laneSpeedLimitLock); + } + + for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { + laneAllowedVehicleTypesArray[i] = null; + } + + for (uint i = 0; i < laneArrowFlags.Length; ++i) { + laneArrowFlags[i] = null; + } + + for (uint i = 0; i < highwayLaneArrowFlags.Length; ++i) { + highwayLaneArrowFlags[i] = null; + } + } + + static Flags() { + laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; + laneSpeedLimitArray = new float?[NetManager.MAX_SEGMENT_COUNT][]; + laneSpeedLimit = new Dictionary(); + laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; + laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; + highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; + } + + public static void OnBeforeLoadData() { + + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index f5799538a..9cb2a1cad 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -229,6 +229,7 @@ + @@ -251,14 +252,22 @@ + + + + + + + + + - @@ -271,6 +280,7 @@ + @@ -278,6 +288,7 @@ + @@ -295,7 +306,6 @@ - @@ -421,6 +431,10 @@ {D3ADE06E-F493-4819-865A-3BB44FEEDF01} CSUtil.Commons + + {F4BEABA8-E56B-4201-99C8-5E0115E87D1C} + OptionsFramework + {3f2f7926-5d51-4880-a2b7-4594a10d7e54} TMPE.CitiesGameBridge @@ -454,6 +468,10 @@ + + + PreserveNewest + @@ -489,8 +507,6 @@ - - diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs index 7f72d4c6a..d3e839d05 100644 --- a/TLM/TLM/Traffic/Data/SpeedLimit.cs +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -1,176 +1,175 @@ -using System; -using System.Collections.Generic; -using TrafficManager.State; -using TrafficManager.UI; -using UnityEngine; - -namespace TrafficManager.Traffic.Data { - public enum SpeedUnit { - CurrentlyConfigured, // Currently selected in the options menu - Kmph, - Mph - } - - public enum MphSignStyle { - SquareUS = 0, - RoundUK = 1, - RoundGerman = 2, - } - - /// - /// Defines a speed limit value with default Kmph and display value of Mph - /// for when the option is set to display Mph. The engine still uses kmph. - /// - public struct SpeedLimit { - public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h - private const ushort LOWER_KMPH = 10; - public const ushort UPPER_KMPH = 140; - private const ushort KMPH_STEP = 10; - public const int BREAK_PALETTE_COLUMN_KMPH = 8; // palette shows N in a row, then break and another row - - private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph - private const ushort LOWER_MPH = 5; - public const ushort UPPER_MPH = 90; - private const ushort MPH_STEP = 5; - public const int BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row - - private const float LOWER_SPEED = 0.1f; - private const float UPPER_SPEED = 2 * 10.0f; // 1000 km/h - - /// - /// Produces list of speed limits to offer user in the palette - /// - /// What kind of speed limit list is required - /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list. - /// The values are in-game speeds as float. - public static List EnumerateSpeedLimits(SpeedUnit unit) { - var result = new List(); - switch (unit) { - case SpeedUnit.Kmph: - for (var km = LOWER_KMPH; km <= UPPER_KMPH; km += KMPH_STEP) { - result.Add(km / SPEED_TO_KMPH); - } - break; - case SpeedUnit.Mph: - for (var mi = LOWER_MPH; mi <= UPPER_MPH; mi += MPH_STEP) { - result.Add(mi / SPEED_TO_MPH); - } - break; - case SpeedUnit.CurrentlyConfigured: - // Automatically choose from the config - return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph - ? EnumerateSpeedLimits(SpeedUnit.Mph) - : EnumerateSpeedLimits(SpeedUnit.Kmph); - } - - return result; - } - - public static string ToMphPreciseString(float speed) { - if (IsZero(speed)) { - return Translation.GetString("Speed_limit_unlimited"); - } - - return ToMphPrecise(speed) + " MPH"; - } - - public static string ToKmphPreciseString(float speed) { - if (IsZero(speed)) { - return Translation.GetString("Speed_limit_unlimited"); - } - - return ToKmphPrecise(speed) + " km/h"; - } - - public static bool NearlyEqual(float a, float b) { - return Mathf.Abs(a - b) < 0.001f; - } - - public static bool IsZero(float speed) { - return speed < 0.001f; - } - - public static bool IsValidRange(float speed) { - return IsZero(speed) || (speed >= LOWER_SPEED && speed <= UPPER_SPEED); - } - - /// - /// Convert float game speed to mph and round to nearest STEP - /// - /// - /// - public static ushort ToMphRounded(float speed) { - var mph = speed * SPEED_TO_MPH; - return (ushort)(Mathf.Round(mph / MPH_STEP) * MPH_STEP); - } - - public static ushort ToMphPrecise(float speed) { - return (ushort)Mathf.Round(speed * SPEED_TO_MPH); - } - - /// - /// Convert float game speed to km/h and round to nearest STEP - /// - /// - /// - public static ushort ToKmphRounded(float speed) { - var kmph = speed * SPEED_TO_KMPH; - return (ushort)(Mathf.Round(kmph / KMPH_STEP) * KMPH_STEP); - } - - public static ushort ToKmphPrecise(float speed) { - return (ushort)Mathf.Round(speed * SPEED_TO_KMPH); - } - - /// - /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and - /// then decrease by STEP. - /// - /// Ingame speed - /// Ingame speed decreased by the increment for MPH or KMPH - public static float GetPrevious(float speed) { - if (speed < 0f) { - return -1f; - } - if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { - var rounded = ToMphRounded(speed); - return (rounded > LOWER_MPH ? rounded - MPH_STEP : LOWER_MPH) / SPEED_TO_MPH; - } else { - var rounded = ToKmphRounded(speed); - return (rounded > LOWER_KMPH ? rounded - KMPH_STEP : LOWER_KMPH) / SPEED_TO_KMPH; - } - } - - /// - /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and - /// then increase by STEP. - /// - /// Ingame speed - /// Ingame speed increased by the increment for MPH or KMPH - public static float GetNext(float speed) { - if (speed < 0f) { - return -1f; - } - if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { - var rounded = ToMphRounded(speed); - return (rounded < UPPER_MPH ? rounded + MPH_STEP : UPPER_MPH) / SPEED_TO_MPH; - } - else { - var rounded = ToKmphRounded(speed); - return (rounded < UPPER_KMPH ? rounded + KMPH_STEP : UPPER_KMPH) / SPEED_TO_KMPH; - } - } - - /// - /// For US signs and MPH enabled, scale textures vertically by 1.25f. - /// Other signs are round. - /// - /// Multiplier for horizontal sign size - public static float GetVerticalTextureScale() { - return (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph && - GlobalConfig.Instance.Main.MphRoadSignStyle == MphSignStyle.SquareUS) - ? 1.25f - : 1.0f; - } - } +namespace TrafficManager.Traffic.Data { + using System.Collections.Generic; + using State; + using UI; + using UnityEngine; + + public enum SpeedUnit { + CurrentlyConfigured, // Currently selected in the options menu + Kmph, + Mph + } + + public enum MphSignStyle { + SquareUS = 0, + RoundUK = 1, + RoundGerman = 2, + } + + /// + /// Defines a speed limit value with default Kmph and display value of Mph + /// for when the option is set to display Mph. The engine still uses kmph. + /// + public struct SpeedLimit { + public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h + private const ushort LOWER_KMPH = 10; + public const ushort UPPER_KMPH = 140; + private const ushort KMPH_STEP = 10; + public const int BREAK_PALETTE_COLUMN_KMPH = 8; // palette shows N in a row, then break and another row + + private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph + private const ushort LOWER_MPH = 5; + public const ushort UPPER_MPH = 90; + private const ushort MPH_STEP = 5; + public const int BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row + + private const float LOWER_SPEED = 0.1f; + private const float UPPER_SPEED = 2 * 10.0f; // 1000 km/h + + /// + /// Produces list of speed limits to offer user in the palette + /// + /// What kind of speed limit list is required + /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list. + /// The values are in-game speeds as float. + public static List EnumerateSpeedLimits(SpeedUnit unit) { + var result = new List(); + switch (unit) { + case SpeedUnit.Kmph: + for (var km = LOWER_KMPH; km <= UPPER_KMPH; km += KMPH_STEP) { + result.Add(km / SPEED_TO_KMPH); + } + break; + case SpeedUnit.Mph: + for (var mi = LOWER_MPH; mi <= UPPER_MPH; mi += MPH_STEP) { + result.Add(mi / SPEED_TO_MPH); + } + break; + case SpeedUnit.CurrentlyConfigured: + // Automatically choose from the config + return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? EnumerateSpeedLimits(SpeedUnit.Mph) + : EnumerateSpeedLimits(SpeedUnit.Kmph); + } + + return result; + } + + public static string ToMphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToMphPrecise(speed) + " MPH"; + } + + public static string ToKmphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToKmphPrecise(speed) + " km/h"; + } + + public static bool NearlyEqual(float a, float b) { + return Mathf.Abs(a - b) < 0.001f; + } + + public static bool IsZero(float speed) { + return speed < 0.001f; + } + + public static bool IsValidRange(float speed) { + return IsZero(speed) || (speed >= LOWER_SPEED && speed <= UPPER_SPEED); + } + + /// + /// Convert float game speed to mph and round to nearest STEP + /// + /// + /// + public static ushort ToMphRounded(float speed) { + var mph = speed * SPEED_TO_MPH; + return (ushort)(Mathf.Round(mph / MPH_STEP) * MPH_STEP); + } + + public static ushort ToMphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_MPH); + } + + /// + /// Convert float game speed to km/h and round to nearest STEP + /// + /// + /// + public static ushort ToKmphRounded(float speed) { + var kmph = speed * SPEED_TO_KMPH; + return (ushort)(Mathf.Round(kmph / KMPH_STEP) * KMPH_STEP); + } + + public static ushort ToKmphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_KMPH); + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then decrease by STEP. + /// + /// Ingame speed + /// Ingame speed decreased by the increment for MPH or KMPH + public static float GetPrevious(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded > LOWER_MPH ? rounded - MPH_STEP : LOWER_MPH) / SPEED_TO_MPH; + } else { + var rounded = ToKmphRounded(speed); + return (rounded > LOWER_KMPH ? rounded - KMPH_STEP : LOWER_KMPH) / SPEED_TO_KMPH; + } + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then increase by STEP. + /// + /// Ingame speed + /// Ingame speed increased by the increment for MPH or KMPH + public static float GetNext(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded < UPPER_MPH ? rounded + MPH_STEP : UPPER_MPH) / SPEED_TO_MPH; + } + else { + var rounded = ToKmphRounded(speed); + return (rounded < UPPER_KMPH ? rounded + KMPH_STEP : UPPER_KMPH) / SPEED_TO_KMPH; + } + } + + /// + /// For US signs and MPH enabled, scale textures vertically by 1.25f. + /// Other signs are round. + /// + /// Multiplier for horizontal sign size + public static float GetVerticalTextureScale() { + return (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph && + GlobalConfig.Instance.Main.MphRoadSignStyle == MphSignStyle.SquareUS) + ? 1.25f + : 1.0f; + } + } } \ No newline at end of file diff --git a/TLM/TLM/UI/CanvasGUI/WorldSpaceGUI.cs b/TLM/TLM/UI/CanvasGUI/WorldSpaceGUI.cs new file mode 100644 index 000000000..14b4c5bd0 --- /dev/null +++ b/TLM/TLM/UI/CanvasGUI/WorldSpaceGUI.cs @@ -0,0 +1,233 @@ +namespace TrafficManager.UI.CanvasGUI { + using System.Collections.Generic; + using Texture; + using UnityEngine; + using UnityEngine.EventSystems; + using UnityEngine.UI; + + /// + /// Creates canvas in the world space somewhere on map, and also creates + /// controls for it and assists with styling, etc. + /// + public class WorldSpaceGUI { + private GameObject canvasGameObj_; + private ulong counter_; + + // Raycaster and eventsystem handle the input + private GraphicRaycaster raycaster_; + private EventSystem eventSystem_; + private bool mouse1Held = false; + + /// Creates canvas $"{name} Canvas" in the scene + /// + /// + /// + public WorldSpaceGUI(string name, Vector3 pos, Quaternion rot) { + // seeThroughShader_ = Resources.Load("WorldSpaceGUI.SeeThroughZ"); + var canvasName = $"{name} Canvas"; + DestroyAllWithName(canvasName); + + canvasGameObj_ = new GameObject { name = canvasName }; + + var canvasComponent = canvasGameObj_.AddComponent(); + canvasComponent.renderMode = RenderMode.WorldSpace; + + var rtComponent = canvasGameObj_.GetComponent(); + rtComponent.localScale = new Vector3(1f, 1f, 1f); + rtComponent.localPosition = pos; + rtComponent.localRotation = rot; + + var scalerComponent = canvasGameObj_.AddComponent(); + scalerComponent.dynamicPixelsPerUnit = 1f; // 1f is blurry, 8f gives reasonably readable text + scalerComponent.referencePixelsPerUnit = 1f; + + // Fetch the Event System from the Scene + raycaster_ = canvasGameObj_.AddComponent(); + eventSystem_ = UnityEngine.Object.FindObjectOfType(); + + // Set the camera + // TODO: Although this sets the camera, it does not work correctly with the GUI + // var mainCam = GameObject.FindObjectOfType(); + // Debug.Assert(mainCam != null); + // canvasComponent.worldCamera = mainCam.GetComponent(); + + // canvasComponent.worldCamera = Camera.main; + // Log.Info($"All cameras count {Camera.allCamerasCount}"); + } + + /// + /// For safety: delete all other canvases with this name + /// + /// + private static void DestroyAllWithName(string canvasName) { + do { + var destroy = GameObject.Find(canvasName); + if (destroy == null) { + break; + } + + UnityEngine.Object.Destroy(destroy); + } while (true); + } + + public void DestroyCanvas() { + UnityEngine.Object.Destroy(canvasGameObj_); + canvasGameObj_ = null; + } + + /// + /// Sets up rect transform for the form element. + /// As Unity canvas have Y facing up and it is more natural to have Y facing + /// down like on a computer screen, the input position has its Y negated and + /// shifted accordingly. + /// + /// What are we moving + /// The position with Y facing down, in a natural way + /// The size of the element + private void SetupRectTransform(GameObject gameObject, Vector3 pos, Vector2 size) { + var rectTransform = gameObject.GetComponent(); + + // adjust position from a more natural way to Unity3d Y facing down + // pos.y = -(pos.y + size.y); + + rectTransform.localPosition = pos; + rectTransform.localScale = new Vector3(1f, 1f, 1f); + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size.x); + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size.y); + rectTransform.localRotation = Quaternion.identity; + } + + public GameObject AddText(Vector3 pos, Vector2 size, string str, GameObject parent = null) { + // Text + var textGameObj = new GameObject(); + textGameObj.transform.SetParent(parent == null ? canvasGameObj_.transform : parent.transform); + textGameObj.name = "Text " + (counter_++); + + var textComponent = textGameObj.AddComponent(); + // C:S fonts: OpenSans-Regular, OpenSans-Semibold. NanumGothic and ArchitectsDaughter + // textComponent.font = Resources.GetBuiltinResource("OpenSans-Regular"); + textComponent.font = Resources.GetBuiltinResource("Arial.ttf"); + textComponent.text = str; + textComponent.fontSize = 5; // in metres + textComponent.color = Color.black; + SetupMaterial(textComponent.material); + + // Text position + SetupRectTransform(textGameObj, pos, size); + return textGameObj; + } + + private void SetupMaterial(Material material) { + material.shader = TextureResources.WorldSpaceGUI.SeeThroughZ; + } + + public GameObject AddButton(Vector3 pos, Vector2 size, + string str = "", + GameObject parent = null) { + // Add the button object + var buttonGameObj = new GameObject(); + buttonGameObj.transform.SetParent(parent == null ? canvasGameObj_.transform : parent.transform); + buttonGameObj.name = "Button " + (counter_++); + + //----------------- + + buttonGameObj.AddComponent(); + + buttonGameObj.AddComponent(); + buttonGameObj.AddComponent