示例#1
0
        internal void housekeeping(bool mayDelete, RoadBaseAI.TrafficLightState mainState = RoadBaseAI.TrafficLightState.Red, RoadBaseAI.TrafficLightState leftState = RoadBaseAI.TrafficLightState.Red, RoadBaseAI.TrafficLightState rightState = RoadBaseAI.TrafficLightState.Red, RoadBaseAI.TrafficLightState pedState = RoadBaseAI.TrafficLightState.Red)
        {
            // we intentionally never delete vehicle types (because we may want to retain traffic light states if a segment is upgraded or replaced)

            HashSet <ExtVehicleType> setupLights     = new HashSet <ExtVehicleType>();
            HashSet <ExtVehicleType> allAllowedTypes = VehicleRestrictionsManager.GetAllowedVehicleTypesAsSet(segmentId, nodeId);
            ExtVehicleType           allAllowedMask  = VehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, nodeId);

            SeparateVehicleTypes = ExtVehicleType.None;
#if DEBUGHK
            Log._Debug($"CustomSegmentLights: housekeeping @ seg. {segmentId}, node {nodeId}, allAllowedTypes={string.Join(", ", allAllowedTypes.Select(x => x.ToString()).ToArray())}");
#endif
            bool addPedestrianLight = false;
            uint numLights          = 0;
            foreach (ExtVehicleType allowedTypes in allAllowedTypes)
            {
                foreach (ExtVehicleType mask in singleLaneVehicleTypes)
                {
                    if (setupLights.Contains(mask))
                    {
                        continue;
                    }

                    if ((allowedTypes & mask) != ExtVehicleType.None && (allowedTypes & ~(mask | ExtVehicleType.Emergency)) == ExtVehicleType.None)
                    {
#if DEBUGHK
                        Log._Debug($"CustomSegmentLights: housekeeping @ seg. {segmentId}, node {nodeId}: adding {mask} light");
#endif

                        if (!CustomLights.ContainsKey(mask))
                        {
                            CustomLights.Add(mask, new TrafficLight.CustomSegmentLight(this, nodeId, segmentId, mainState, leftState, rightState));
                            VehicleTypes.AddFirst(mask);
                        }
                        ++numLights;
                        addPedestrianLight        = true;
                        autoPedestrianVehicleType = mask;
                        mainSegmentLight          = CustomLights[mask];
                        setupLights.Add(mask);
                        SeparateVehicleTypes |= mask;
                        break;
                    }
                }
            }

            if (allAllowedTypes.Count > numLights)
            {
#if DEBUGHK
                Log._Debug($"CustomSegmentLights: housekeeping @ seg. {segmentId}, node {nodeId}: adding main vehicle light: {mainVehicleType}");
#endif

                // traffic lights for cars
                if (!CustomLights.ContainsKey(mainVehicleType))
                {
                    CustomLights.Add(mainVehicleType, new TrafficLight.CustomSegmentLight(this, nodeId, segmentId, mainState, leftState, rightState));
                    VehicleTypes.AddFirst(mainVehicleType);
                }
                autoPedestrianVehicleType = mainVehicleType;
                mainSegmentLight          = CustomLights[mainVehicleType];
                addPedestrianLight        = allAllowedMask == ExtVehicleType.None || (allAllowedMask & ~ExtVehicleType.RailVehicle) != ExtVehicleType.None;
            }
            else
            {
                addPedestrianLight = true;
            }

#if DEBUGHK
            if (addPedestrianLight)
            {
                Log._Debug($"CustomSegmentLights: housekeeping @ seg. {segmentId}, node {nodeId}: adding ped. light");
            }
#endif

            if (mayDelete)
            {
                // delete traffic lights for non-existing configurations
                HashSet <ExtVehicleType> vehicleTypesToDelete = new HashSet <ExtVehicleType>();
                foreach (KeyValuePair <ExtVehicleType, CustomSegmentLight> e in CustomLights)
                {
                    if (e.Key == mainVehicleType)
                    {
                        continue;
                    }
                    if (!setupLights.Contains(e.Key))
                    {
                        vehicleTypesToDelete.Add(e.Key);
                    }
                }

                foreach (ExtVehicleType vehicleType in vehicleTypesToDelete)
                {
#if DEBUGHK
                    Log._Debug($"Deleting traffic light for {vehicleType} at segment {segmentId}, node {nodeId}");
#endif
                    CustomLights.Remove(vehicleType);
                    VehicleTypes.Remove(vehicleType);
                }
            }

            if (CustomLights.ContainsKey(mainVehicleType) && VehicleTypes.First.Value != mainVehicleType)
            {
                VehicleTypes.Remove(mainVehicleType);
                VehicleTypes.AddFirst(mainVehicleType);
            }

            if (addPedestrianLight)
            {
#if DEBUGHK
                Log._Debug($"CustomSegmentLights: housekeeping @ seg. {segmentId}, node {nodeId}: adding pedestrian light");
#endif
                if (pedestrianLightState == null)
                {
                    pedestrianLightState = pedState;
                }
            }
            else
            {
                pedestrianLightState = null;
            }
        }
        /// <summary>
        /// Calculates the current metrics for flowing and waiting vehicles
        /// </summary>
        /// <param name="wait"></param>
        /// <param name="flow"></param>
        /// <returns>true if the values could be calculated, false otherwise</returns>
        public bool calcWaitFlow(out float wait, out float flow)
        {
#if TRACE
            Singleton <CodeProfiler> .instance.Start("TimedTrafficLightsStep.calcWaitFlow");
#endif

#if DEBUGMETRIC
            bool debug = timedNode.NodeId == 3201;
#else
            bool debug = false;
#endif

#if DEBUGMETRIC
            if (debug)
            {
                Log.Warning($"TimedTrafficLightsStep.calcWaitFlow: ***START*** @ node {timedNode.NodeId}");
            }
#endif

            uint numFlows    = 0;
            uint numWaits    = 0;
            uint curMeanFlow = 0;
            uint curMeanWait = 0;

            // we are the master node. calculate traffic data
            foreach (ushort timedNodeId in timedNode.NodeGroup)
            {
                TrafficLightSimulation sim = TrafficLightSimulation.GetNodeSimulation(timedNodeId);
                if (sim == null || !sim.IsTimedLight())
                {
                    continue;
                }
                TimedTrafficLights slaveTimedNode = sim.TimedLight;
                if (slaveTimedNode.NumSteps() <= timedNode.CurrentStep)
                {
                    for (int i = 0; i < slaveTimedNode.NumSteps(); ++i)
                    {
                        slaveTimedNode.GetStep(i).invalid = true;
                    }
                    continue;
                }
                TimedTrafficLightsStep slaveStep = slaveTimedNode.Steps[timedNode.CurrentStep];

                //List<int> segmentIdsToDelete = new List<int>();

                // minimum time reached. check traffic!
                foreach (KeyValuePair <ushort, CustomSegmentLights> e in slaveStep.segmentLights)
                {
                    var fromSegmentId = e.Key;
                    var segLights     = e.Value;

                    // one of the traffic lights at this segment is green: count minimum traffic flowing through
                    SegmentEnd fromSeg = TrafficPriority.GetPrioritySegment(timedNodeId, fromSegmentId);
                    if (fromSeg == null)
                    {
#if DEBUGMETRIC
                        if (debug)
                        {
                            Log.Warning($"TimedTrafficLightsStep.calcWaitFlow: No priority segment @ seg. {fromSegmentId} found!");
                        }
#endif
                        //Log.Warning("stepDone(): prioSeg is null");
                        //segmentIdsToDelete.Add(fromSegmentId);
                        continue;                         // skip invalid segment
                    }

                    //bool startPhase = getCurrentFrame() <= startFrame + minTime + 2; // during start phase all vehicles on "green" segments are counted as flowing
                    ExtVehicleType validVehicleTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(fromSegmentId, timedNode.NodeId);

                    foreach (KeyValuePair <byte, ExtVehicleType> e2 in segLights.VehicleTypeByLaneIndex)
                    {
                        byte           laneIndex   = e2.Key;
                        ExtVehicleType vehicleType = e2.Value;
                        if (vehicleType != ExtVehicleType.None && (validVehicleTypes & vehicleType) == ExtVehicleType.None)
                        {
                            continue;
                        }
                        CustomSegmentLight segLight = segLights.GetCustomLight(laneIndex);
                        if (segLight == null)
                        {
                            Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {fromSegmentId}, node {timedNode.NodeId}!");
                            continue;
                        }

#if DEBUGMETRIC
                        if (debug)
                        {
                            Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {fromSegmentId}. Vehicle types: {vehicleType}");
                        }
#endif

                        Dictionary <ushort, uint> carsFlowingToSegmentMetric = null;
                        Dictionary <ushort, uint> allCarsToSegmentMetric     = null;
                        try {
                            carsFlowingToSegmentMetric = fromSeg.GetVehicleMetricGoingToSegment(false, laneIndex, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow (1): " + ex.ToString());
                        }

                        try {
                            allCarsToSegmentMetric = fromSeg.GetVehicleMetricGoingToSegment(true, laneIndex, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow (2): " + ex.ToString());
                        }

                        if (carsFlowingToSegmentMetric == null)
                        {
                            continue;
                        }

                        // build directions from toSegment to fromSegment
                        Dictionary <ushort, Direction> directions = new Dictionary <ushort, Direction>();
                        foreach (KeyValuePair <ushort, uint> f in allCarsToSegmentMetric)
                        {
                            var             toSegmentId = f.Key;
                            SegmentGeometry geometry    = SegmentGeometry.Get(fromSegmentId);
                            Direction       dir         = geometry.GetDirection(toSegmentId, timedNodeId == geometry.StartNodeId());
                            directions[toSegmentId] = dir;
#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Calculated direction for seg. {fromSegmentId} -> seg. {toSegmentId}: {dir}");
                            }
#endif
                        }

                        // calculate waiting/flowing traffic
                        foreach (KeyValuePair <ushort, uint> f in allCarsToSegmentMetric)
                        {
                            ushort toSegmentId               = f.Key;
                            uint   totalNormCarLength        = f.Value;
                            uint   totalFlowingNormCarLength = carsFlowingToSegmentMetric[f.Key];

#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Total norm. car length of vehicles on lane {laneIndex} going to seg. {toSegmentId}: {totalNormCarLength}");
                            }
#endif

                            bool addToFlow = false;
                            switch (directions[toSegmentId])
                            {
                            case Direction.Turn:
                                addToFlow = TrafficPriority.IsLeftHandDrive() ? segLight.isRightGreen() : segLight.isLeftGreen();
                                break;

                            case Direction.Left:
                                addToFlow = segLight.isLeftGreen();
                                break;

                            case Direction.Right:
                                addToFlow = segLight.isRightGreen();
                                break;

                            case Direction.Forward:
                            default:
                                addToFlow = segLight.isForwardGreen();
                                break;
                            }

                            if (addToFlow)
                            {
                                ++numFlows;
                                curMeanFlow += totalFlowingNormCarLength;
                            }
                            else
                            {
                                ++numWaits;
                                curMeanWait += totalNormCarLength;
                            }

#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Vehicles on lane {laneIndex} on seg. {fromSegmentId} going to seg. {toSegmentId} flowing? {addToFlow} curMeanFlow={curMeanFlow}, curMeanWait={curMeanWait}");
                            }
#endif
                        }
                    }
                }

                // delete invalid segments from step

                /*foreach (int segmentId in segmentIdsToDelete) {
                 *      slaveStep.segmentLightStates.Remove(segmentId);
                 * }*/

                if (slaveStep.segmentLights.Count <= 0)
                {
                    invalid = true;
                    flow    = 0f;
                    wait    = 0f;
#if TRACE
                    Singleton <CodeProfiler> .instance.Stop("TimedTrafficLightsStep.calcWaitFlow");
#endif
                    return(false);
                }
            }

#if DEBUGMETRIC
            if (debug)
            {
                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ### Calculation completed. numFlows={numFlows}, numWaits={numWaits}, curMeanFlow={curMeanFlow}, curMeanWait={curMeanWait}");
            }

            wait = curMeanWait;
            flow = curMeanFlow;
#else
            if (numFlows > 0)
            {
                curMeanFlow /= numFlows;
            }
            if (numWaits > 0)
            {
                curMeanWait /= numWaits;
            }

            float fCurMeanFlow = curMeanFlow;
            fCurMeanFlow /= waitFlowBalance;             // a value smaller than 1 rewards steady traffic currents

            wait = (float)curMeanWait;
            flow = fCurMeanFlow;
#endif
#if TRACE
            Singleton <CodeProfiler> .instance.Stop("TimedTrafficLightsStep.calcWaitFlow");
#endif
            return(true);
        }
        /// <summary>
        /// Calculates the current metrics for flowing and waiting vehicles
        /// </summary>
        /// <param name="wait"></param>
        /// <param name="flow"></param>
        /// <returns>true if the values could be calculated, false otherwise</returns>
        public bool calcWaitFlow(out float wait, out float flow)
        {
#if DEBUG
            bool debug = timedNode.NodeId == 17857;
#else
            bool debug = false;
#endif

            uint numFlows    = 0;
            uint numWaits    = 0;
            uint curMeanFlow = 0;
            uint curMeanWait = 0;

            // we are the master node. calculate traffic data
            foreach (ushort timedNodeId in groupNodeIds)
            {
                TrafficLightSimulation sim = TrafficLightSimulation.GetNodeSimulation(timedNodeId);
                if (sim == null || !sim.IsTimedLight())
                {
                    continue;
                }
                TimedTrafficLights slaveTimedNode = sim.TimedLight;
                if (slaveTimedNode.NumSteps() <= timedNode.CurrentStep)
                {
                    for (int i = 0; i < slaveTimedNode.NumSteps(); ++i)
                    {
                        slaveTimedNode.GetStep(i).invalid = true;
                    }
                    continue;
                }
                TimedTrafficLightsStep slaveStep = slaveTimedNode.Steps[timedNode.CurrentStep];

                //List<int> segmentIdsToDelete = new List<int>();

                // minimum time reached. check traffic!
                foreach (KeyValuePair <ushort, CustomSegmentLights> e in slaveStep.segmentLights)
                {
                    var fromSegmentId = e.Key;
                    var segLights     = e.Value;

                    // one of the traffic lights at this segment is green: count minimum traffic flowing through
                    SegmentEnd fromSeg = TrafficPriority.GetPrioritySegment(timedNodeId, fromSegmentId);
                    if (fromSeg == null)
                    {
                        //Log.Warning("stepDone(): prioSeg is null");
                        //segmentIdsToDelete.Add(fromSegmentId);
                        continue;                         // skip invalid segment
                    }

                    bool           startPhase        = getCurrentFrame() <= startFrame + minTime + 2;    // during start phase all vehicles on "green" segments are counted as flowing
                    ExtVehicleType validVehicleTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(fromSegmentId, timedNode.NodeId);

                    foreach (ExtVehicleType vehicleType in segLights.VehicleTypes)
                    {
                        if (vehicleType != ExtVehicleType.None && (validVehicleTypes & vehicleType) == ExtVehicleType.None)
                        {
                            continue;
                        }
                        CustomSegmentLight segLight = segLights.GetCustomLight(vehicleType);
                        if (segLight == null)
                        {
                            Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {fromSegmentId}, node {timedNode.NodeId}!");
                            continue;
                        }

                        Dictionary <ushort, uint>[] carsToSegmentMetrics = new Dictionary <ushort, uint> [startPhase ? 1: 2];
                        try {
                            carsToSegmentMetrics[0] = fromSeg.GetVehicleMetricGoingToSegment(null, vehicleType, segLights.SeparateVehicleTypes, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow: " + ex.ToString());
                        }
                        if (!startPhase)
                        {
                            try {
                                carsToSegmentMetrics[1] = fromSeg.GetVehicleMetricGoingToSegment(0.1f, vehicleType, segLights.SeparateVehicleTypes, debug);
                            } catch (Exception ex) {
                                Log.Warning("calcWaitFlow: " + ex.ToString());
                            }
                        }

                        if (carsToSegmentMetrics[0] == null)
                        {
                            continue;
                        }

                        // build directions from toSegment to fromSegment
                        Dictionary <ushort, Direction> directions = new Dictionary <ushort, Direction>();
                        foreach (KeyValuePair <ushort, uint> f in carsToSegmentMetrics[0])
                        {
                            var             toSegmentId = f.Key;
                            SegmentGeometry geometry    = CustomRoadAI.GetSegmentGeometry(fromSegmentId);
                            Direction       dir         = geometry.GetDirection(toSegmentId, timedNodeId);
                            directions[toSegmentId] = dir;
                        }

                        // calculate waiting/flowing traffic
                        for (int i = 0; i < carsToSegmentMetrics.Length; ++i)
                        {
                            if (carsToSegmentMetrics[i] == null)
                            {
                                continue;
                            }

                            foreach (KeyValuePair <ushort, uint> f in carsToSegmentMetrics[i])
                            {
                                ushort toSegmentId        = f.Key;
                                uint   totalNormCarLength = f.Value;

                                bool addToFlow = false;
                                switch (directions[toSegmentId])
                                {
                                case Direction.Left:
                                    if (segLight.isLeftGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;

                                case Direction.Right:
                                    if (segLight.isRightGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;

                                case Direction.Forward:
                                default:
                                    if (segLight.isForwardGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;
                                }

                                if (addToFlow)
                                {
                                    if (i > 0 || startPhase)
                                    {
                                        ++numFlows;
                                        curMeanFlow += totalNormCarLength;
                                    }
                                }
                                else if (i == 0)
                                {
                                    ++numWaits;
                                    curMeanWait += totalNormCarLength;
                                }
                            }
                        }
                    }
                }

                // delete invalid segments from step

                /*foreach (int segmentId in segmentIdsToDelete) {
                 *      slaveStep.segmentLightStates.Remove(segmentId);
                 * }*/

                if (slaveStep.segmentLights.Count <= 0)
                {
                    invalid = true;
                    flow    = 0f;
                    wait    = 0f;
                    return(false);
                }
            }

            if (numFlows > 0)
            {
                curMeanFlow /= numFlows;
            }
            if (numWaits > 0)
            {
                curMeanWait /= numWaits;
            }

            float fCurMeanFlow = curMeanFlow;
            fCurMeanFlow /= 100f * waitFlowBalance;             // a value smaller than 1 rewards steady traffic currents

            wait = (float)curMeanWait / 100f;
            flow = fCurMeanFlow;
            return(true);
        }
        private bool drawVehicleRestrictionHandles(ushort segmentId, bool viewOnly)
        {
            if (!LoadingExtension.IsPathManagerCompatible)
            {
                return(false);
            }

            if (viewOnly && !Options.vehicleRestrictionsOverlay && TrafficManagerTool.GetToolMode() != ToolMode.VehicleRestrictions)
            {
                return(false);
            }

            Vector3 center = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_bounds.center;

            var screenPos = Camera.main.WorldToScreenPoint(center);

            screenPos.y = Screen.height - screenPos.y;
            if (screenPos.z < 0)
            {
                return(false);
            }
            var camPos = Singleton <SimulationManager> .instance.m_simulationView.m_position;
            var diff   = center - camPos;

            if (diff.magnitude > TrafficManagerTool.PriorityCloseLod)
            {
                return(false);                // do not draw if too distant
            }
            int numDirections;
            int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections);

            // draw vehicle restrictions over each lane
            NetInfo segmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].Info;
            Vector3 yu          = (Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endDirection - Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startDirection).normalized;

            if ((Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None)
            {
                yu = -yu;
            }
            Vector3   xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized;
            float     f  = viewOnly ? 4f : 7f;
            ItemClass connectionClass = segmentInfo.GetConnectionClass();
            int       maxNumSigns     = 0;

            if (connectionClass.m_service == ItemClass.Service.Road)
            {
                maxNumSigns = roadVehicleTypes.Length;
            }
            else if (connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain)
            {
                maxNumSigns = railVehicleTypes.Length;
            }
            //Vector3 zero = center - 0.5f * (float)(numLanes + numDirections - 1) * f * (xu + yu); // "bottom left"
            Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu - 0.5f * (float)maxNumSigns * f * yu;             // "bottom left"

            /*if (!viewOnly)
             *      Log._Debug($"xu: {xu.ToString()} yu: {yu.ToString()} center: {center.ToString()} zero: {zero.ToString()} numLanes: {numLanes} numDirections: {numDirections}");*/

            uint                        x           = 0;
            var                         guiColor    = GUI.color;
            List <object[]>             sortedLanes = TrafficManagerTool.GetSortedVehicleLanes(segmentId, segmentInfo, null);
            bool                        hovered     = false;
            HashSet <NetInfo.Direction> directions  = new HashSet <NetInfo.Direction>();

            foreach (object[] laneData in sortedLanes)
            {
                uint laneId    = (uint)laneData[0];
                uint laneIndex = (uint)laneData[2];

                NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
                if (!directions.Contains(laneInfo.m_direction))
                {
                    if (directions.Count > 0)
                    {
                        ++x;                         // space between different directions
                    }
                    directions.Add(laneInfo.m_direction);
                }

                ExtVehicleType[] possibleVehicleTypes = null;
                if (VehicleRestrictionsManager.IsRoadLane(laneInfo))
                {
                    possibleVehicleTypes = roadVehicleTypes;
                }
                else if (VehicleRestrictionsManager.IsRailLane(laneInfo))
                {
                    possibleVehicleTypes = railVehicleTypes;
                }
                else
                {
                    ++x;
                    continue;
                }

                ExtVehicleType allowedTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo);

                uint y = 0;
#if DEBUGx
                Vector3 labelCenter = zero + f * (float)x * xu + f * (float)y * yu;                 // in game coordinates

                var labelScreenPos = Camera.main.WorldToScreenPoint(labelCenter);
                labelScreenPos.y = Screen.height - labelScreenPos.y;
                diff             = labelCenter - camPos;

                var labelZoom = 1.0f / diff.magnitude * 100f;
                _counterStyle.fontSize         = (int)(11f * labelZoom);
                _counterStyle.normal.textColor = new Color(1f, 1f, 0f);

                string  labelStr  = $"Idx {laneIndex}";
                Vector2 dim       = _counterStyle.CalcSize(new GUIContent(labelStr));
                Rect    labelRect = new Rect(labelScreenPos.x - dim.x / 2f, labelScreenPos.y, dim.x, dim.y);
                GUI.Label(labelRect, labelStr, _counterStyle);

                ++y;
#endif
                foreach (ExtVehicleType vehicleType in possibleVehicleTypes)
                {
                    bool allowed = VehicleRestrictionsManager.IsAllowed(allowedTypes, vehicleType);
                    if (allowed && viewOnly)
                    {
                        continue;                         // do not draw allowed vehicles in view-only mode
                    }
                    bool hoveredHandle;
                    DrawRestrictionsSign(viewOnly, camPos, out diff, xu, yu, f, zero, x, y, ref guiColor, TrafficLightToolTextureResources.VehicleRestrictionTextures[vehicleType][allowed], out hoveredHandle);
                    if (hoveredHandle)
                    {
                        hovered = true;
                    }

                    if (hoveredHandle && MainTool.CheckClicked())
                    {
                        // toggle vehicle restrictions
                        //Log._Debug($"Setting vehicle restrictions of segment {segmentId}, lane idx {laneIndex}, {vehicleType.ToString()} to {!allowed}");
                        VehicleRestrictionsManager.ToggleAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType, !allowed);
                    }

                    ++y;
                }

                ++x;
            }

            guiColor.a = 1f;
            GUI.color  = guiColor;

            return(hovered);
        }
        private void _guiVehicleRestrictionsWindow(int num)
        {
            if (GUILayout.Button(Translation.GetString("Invert")))
            {
                // invert pattern

                NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes         = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);        // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = selectedSegmentInfo.m_lanes[laneIndex];

                    ExtVehicleType baseMask = ExtVehicleType.None;
                    if (VehicleRestrictionsManager.IsRoadLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RoadVehicle;
                    }
                    else if (VehicleRestrictionsManager.IsRailLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RailVehicle;
                    }

                    if (baseMask == ExtVehicleType.None)
                    {
                        continue;
                    }

                    ExtVehicleType allowedTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo);
                    allowedTypes = ~allowedTypes & baseMask;
                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, laneIndex, laneId, allowedTypes);
                }
            }

            GUILayout.BeginHorizontal();
            if (GUILayout.Button(Translation.GetString("Allow_all_vehicles")))
            {
                // allow all vehicle types

                NetInfo         segmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, segmentInfo, null);                // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = segmentInfo.m_lanes[laneIndex];

                    ExtVehicleType baseMask = ExtVehicleType.None;
                    if (VehicleRestrictionsManager.IsRoadLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RoadVehicle;
                    }
                    else if (VehicleRestrictionsManager.IsRailLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RailVehicle;
                    }

                    if (baseMask == ExtVehicleType.None)
                    {
                        continue;
                    }

                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, laneIndex, laneId, baseMask);
                }
            }

            if (GUILayout.Button(Translation.GetString("Ban_all_vehicles")))
            {
                // ban all vehicle types

                NetInfo         segmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, segmentInfo, null);                // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = segmentInfo.m_lanes[laneIndex];

                    ExtVehicleType baseMask = ExtVehicleType.None;
                    if (VehicleRestrictionsManager.IsRoadLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RoadVehicle;
                    }
                    else if (VehicleRestrictionsManager.IsRailLane(laneInfo))
                    {
                        baseMask = ExtVehicleType.RailVehicle;
                    }

                    if (baseMask == ExtVehicleType.None)
                    {
                        continue;
                    }

                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, laneIndex, laneId, ~baseMask);
                }
            }
            GUILayout.EndHorizontal();

            if (GUILayout.Button(Translation.GetString("Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions")))
            {
                NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> selectedSortedLanes = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);

                LinkedList <ushort> nodesToProcess    = new LinkedList <ushort>();
                HashSet <ushort>    processedNodes    = new HashSet <ushort>();
                HashSet <ushort>    processedSegments = new HashSet <ushort>();
                processedSegments.Add(SelectedSegmentId);

                ushort selectedStartNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].m_startNode;
                ushort selectedEndNodeId   = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].m_endNode;

                if (selectedStartNodeId != 0)
                {
                    nodesToProcess.AddFirst(selectedStartNodeId);
                }
                if (selectedEndNodeId != 0)
                {
                    nodesToProcess.AddFirst(selectedEndNodeId);
                }

                while (nodesToProcess.First != null)
                {
                    ushort nodeId = nodesToProcess.First.Value;
                    nodesToProcess.RemoveFirst();
                    processedNodes.Add(nodeId);

                    if (Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].CountSegments() > 2)
                    {
                        continue;                         // junction. stop.
                    }
                    // explore segments at node
                    for (var s = 0; s < 8; s++)
                    {
                        var segmentId = Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].GetSegment(s);

                        if (segmentId <= 0 || processedSegments.Contains(segmentId))
                        {
                            continue;
                        }
                        processedSegments.Add(segmentId);

                        NetInfo         segmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].Info;
                        List <object[]> sortedLanes = TrafficManagerTool.GetSortedVehicleLanes(segmentId, segmentInfo, null);

                        if (sortedLanes.Count == selectedSortedLanes.Count)
                        {
                            // number of lanes matches selected segment
                            for (int i = 0; i < sortedLanes.Count; ++i)
                            {
                                object[] selectedLaneData = selectedSortedLanes[i];
                                object[] laneData         = sortedLanes[i];

                                uint         selectedLaneId    = (uint)selectedLaneData[0];
                                uint         selectedLaneIndex = (uint)selectedLaneData[2];
                                NetInfo.Lane selectedLaneInfo  = segmentInfo.m_lanes[selectedLaneIndex];

                                uint         laneId    = (uint)laneData[0];
                                uint         laneIndex = (uint)laneData[2];
                                NetInfo.Lane laneInfo  = segmentInfo.m_lanes[laneIndex];

                                // apply restrictions of selected segment & lane
                                VehicleRestrictionsManager.SetAllowedVehicleTypes(segmentId, laneIndex, laneId, VehicleRestrictionsManager.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, selectedLaneIndex, selectedLaneInfo));
                            }

                            // add nodes to explore
                            ushort startNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startNode;
                            ushort endNodeId   = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endNode;

                            if (startNodeId != 0 && !processedNodes.Contains(startNodeId))
                            {
                                nodesToProcess.AddFirst(startNodeId);
                            }
                            if (endNodeId != 0 && !processedNodes.Contains(endNodeId))
                            {
                                nodesToProcess.AddFirst(endNodeId);
                            }
                        }
                    }
                }
            }
        }
        private void ApplyRestrictionsToAllSegments(int?sortedLaneIndex = null)
        {
            NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
            List <object[]> selectedSortedLanes = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);

            LinkedList <ushort> nodesToProcess    = new LinkedList <ushort>();
            HashSet <ushort>    processedNodes    = new HashSet <ushort>();
            HashSet <ushort>    processedSegments = new HashSet <ushort>();

            processedSegments.Add(SelectedSegmentId);

            ushort selectedStartNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].m_startNode;
            ushort selectedEndNodeId   = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].m_endNode;

            if (selectedStartNodeId != 0)
            {
                nodesToProcess.AddFirst(selectedStartNodeId);
            }
            if (selectedEndNodeId != 0)
            {
                nodesToProcess.AddFirst(selectedEndNodeId);
            }

            while (nodesToProcess.First != null)
            {
                ushort nodeId = nodesToProcess.First.Value;
                nodesToProcess.RemoveFirst();
                processedNodes.Add(nodeId);

                if (Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].CountSegments() > 2)
                {
                    continue;                     // junction. stop.
                }
                // explore segments at node
                for (var s = 0; s < 8; s++)
                {
                    var segmentId = Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].GetSegment(s);

                    if (segmentId <= 0 || processedSegments.Contains(segmentId))
                    {
                        continue;
                    }
                    processedSegments.Add(segmentId);

                    NetInfo         segmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].Info;
                    List <object[]> sortedLanes = TrafficManagerTool.GetSortedVehicleLanes(segmentId, segmentInfo, null);

                    if (sortedLanes.Count == selectedSortedLanes.Count)
                    {
                        // number of lanes matches selected segment
                        int sli = -1;
                        for (int i = 0; i < sortedLanes.Count; ++i)
                        {
                            ++sli;

                            if (sortedLaneIndex != null && sli != sortedLaneIndex)
                            {
                                continue;
                            }

                            object[] selectedLaneData = selectedSortedLanes[i];
                            object[] laneData         = sortedLanes[i];

                            uint         selectedLaneId    = (uint)selectedLaneData[0];
                            uint         selectedLaneIndex = (uint)selectedLaneData[2];
                            NetInfo.Lane selectedLaneInfo  = segmentInfo.m_lanes[selectedLaneIndex];

                            uint         laneId    = (uint)laneData[0];
                            uint         laneIndex = (uint)laneData[2];
                            NetInfo.Lane laneInfo  = segmentInfo.m_lanes[laneIndex];

                            // apply restrictions of selected segment & lane
                            VehicleRestrictionsManager.SetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, laneId, VehicleRestrictionsManager.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, selectedLaneIndex, selectedLaneInfo));
                        }

                        // add nodes to explore
                        ushort startNodeId = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startNode;
                        ushort endNodeId   = Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endNode;

                        if (startNodeId != 0 && !processedNodes.Contains(startNodeId))
                        {
                            nodesToProcess.AddFirst(startNodeId);
                        }
                        if (endNodeId != 0 && !processedNodes.Contains(endNodeId))
                        {
                            nodesToProcess.AddFirst(endNodeId);
                        }
                    }
                }
            }
        }
        private void _guiVehicleRestrictionsWindow(int num)
        {
            if (GUILayout.Button(Translation.GetString("Invert")))
            {
                // invert pattern

                NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes         = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);        // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = selectedSegmentInfo.m_lanes[laneIndex];

                    ExtVehicleType baseMask = VehicleRestrictionsManager.GetBaseMask(laneInfo);

                    if (baseMask == ExtVehicleType.None)
                    {
                        continue;
                    }

                    ExtVehicleType allowedTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo);
                    allowedTypes = ~allowedTypes & baseMask;
                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, allowedTypes);
                }
            }

            GUILayout.BeginHorizontal();
            if (GUILayout.Button(Translation.GetString("Allow_all_vehicles")))
            {
                // allow all vehicle types

                NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes         = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);        // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = selectedSegmentInfo.m_lanes[laneIndex];

                    ExtVehicleType baseMask = VehicleRestrictionsManager.GetBaseMask(laneInfo);

                    if (baseMask == ExtVehicleType.None)
                    {
                        continue;
                    }

                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask);
                }
            }

            if (GUILayout.Button(Translation.GetString("Ban_all_vehicles")))
            {
                // ban all vehicle types

                NetInfo         selectedSegmentInfo = Singleton <NetManager> .instance.m_segments.m_buffer[SelectedSegmentId].Info;
                List <object[]> sortedLanes         = TrafficManagerTool.GetSortedVehicleLanes(SelectedSegmentId, selectedSegmentInfo, null);        // TODO does not need to be sorted, but every lane should be a vehicle lane
                foreach (object[] laneData in sortedLanes)
                {
                    uint         laneId    = (uint)laneData[0];
                    uint         laneIndex = (uint)laneData[2];
                    NetInfo.Lane laneInfo  = selectedSegmentInfo.m_lanes[laneIndex];

                    VehicleRestrictionsManager.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, ExtVehicleType.None);
                }
            }
            GUILayout.EndHorizontal();

            if (GUILayout.Button(Translation.GetString("Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions")))
            {
                ApplyRestrictionsToAllSegments();
            }
        }