using System.Collections.Generic; using Model; using UnityEngine; namespace RailroaderPhysicsOverhaul; [UnityEngine.DefaultExecutionOrder(10000)] // run after all game LateUpdates so our MPB is last to write public class CarDebugVisualizer : MonoBehaviour { public static CarDebugVisualizer Instance { get; private set; } public int TintedCount => _curr.Count; // A 1x1 white texture fed via MPB overrides _MainTex so the shader renders // texture*color = white*color = solid color, regardless of the car's original texture. Texture2D _whiteTex; readonly MaterialPropertyBlock _mpb = new(); readonly Dictionary _rendCache = new(); readonly Dictionary _curr = new(); readonly Dictionary _prev = new(); static readonly Color ColFrozen = new(1.00f, 0.90f, 0.00f); // yellow static readonly Color ColFastPath = new(0.00f, 1.00f, 1.00f); // cyan static readonly Color ColFullPath = new(1.00f, 0.00f, 1.00f); // magenta void Awake() { Instance = this; _whiteTex = new Texture2D(1, 1, TextureFormat.RGBA32, false) { hideFlags = HideFlags.HideAndDontSave }; _whiteTex.SetPixel(0, 0, Color.white); _whiteTex.Apply(); } void OnDestroy() { ClearAll(); if (_whiteTex) Destroy(_whiteTex); Instance = null; } void LateUpdate() { _prev.Clear(); foreach (var kv in _curr) _prev[kv.Key] = kv.Value; _curr.Clear(); if (Main.Settings.DebugHighlightFrozen) Collect(ConsistFreezer.FrozenCars, ColFrozen); if (Main.Settings.DebugHighlightFastPath) Collect(ConsistLOD.FastPathCars, ColFastPath); if (Main.Settings.DebugHighlightFullPath) Collect(ConsistLOD.FullPathCars, ColFullPath); foreach (var (car, color) in _curr) ApplyTint(car, color); foreach (var car in _prev.Keys) if (!_curr.ContainsKey(car)) ClearTint(car); if (Time.frameCount % 300 == 0) PurgeCache(); } void Collect(HashSet source, Color color) { foreach (var car in source) if (car != null && !_curr.ContainsKey(car)) _curr[car] = color; } void ApplyTint(Car car, Color color) { foreach (var r in GetRenderers(car)) { if (r == null) continue; _mpb.Clear(); _mpb.SetColor("_Color", color); _mpb.SetColor("_BaseColor", color); _mpb.SetTexture("_MainTex", _whiteTex); // built-in RP _mpb.SetTexture("_BaseMap", _whiteTex); // URP _mpb.SetTexture("_BaseColorMap", _whiteTex); // HDRP r.SetPropertyBlock(_mpb); } } void ClearTint(Car car) { foreach (var r in GetRenderers(car)) if (r != null) r.SetPropertyBlock(null); } void ClearAll() { foreach (var car in _curr.Keys) ClearTint(car); foreach (var car in _prev.Keys) ClearTint(car); _curr.Clear(); _prev.Clear(); _rendCache.Clear(); } Renderer[] GetRenderers(Car car) { if (!_rendCache.TryGetValue(car, out var renderers) || renderers.Length == 0) { renderers = car.BodyTransform != null ? car.BodyTransform.GetComponentsInChildren() : System.Array.Empty(); // Don't cache empty results — retry next frame until the mesh loads if (renderers.Length > 0) _rendCache[car] = renderers; } return renderers; } void PurgeCache() { var dead = new List(); foreach (var car in _rendCache.Keys) if (car == null) dead.Add(car); foreach (var car in dead) _rendCache.Remove(car); } }