railroader-physics-optimizer/CarDebugVisualizer.cs
Seton Carmichael 766822d15a release: 0.1.2 - debug visualization, loco exclusions, tuned defaults
New in 0.1.2:
- Debug color overlay: tint cars by physics state (yellow=frozen,
  cyan=fast-path, magenta=full-accuracy), individual toggles per state
- Exclude locomotives from Physics Optimizer and Auto Freeze
- Debug stats line in overlay showing live counts per state
- Resync quality 1/16 option added

Defaults tuned for new installs:
- Resync quality: 1/4 -> 1/8
- Speed threshold: ~0.7 mph -> 0.2 mph
- Overlay opacity: 100% -> 75%

Rename: Physics Overhaul -> Physics Optimizer throughout
2026-06-16 22:14:34 -04:00

124 lines
3.9 KiB
C#

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<Car, Renderer[]> _rendCache = new();
readonly Dictionary<Car, Color> _curr = new();
readonly Dictionary<Car, Color> _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<Car> 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<Renderer>()
: System.Array.Empty<Renderer>();
// 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<Car>();
foreach (var car in _rendCache.Keys)
if (car == null) dead.Add(car);
foreach (var car in dead) _rendCache.Remove(car);
}
}