1/8 caused intermittent physics instability on busy saves. 1/4 is a better balance between update frequency and stability.
261 lines
11 KiB
C#
261 lines
11 KiB
C#
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace S3.Modules.PhysicsOptimizer;
|
|
|
|
// Renders the Physics Optimizer settings as the body of its foldout in the S³
|
|
// settings panel. Writes both the persisted settings and the runtime statics on
|
|
// every change, then persists via PhysicsOptimizerModule.Persist().
|
|
static class PhysicsSettingsUI
|
|
{
|
|
static readonly int[] ResyncOptions = { 1, 2, 4, 8, 16 };
|
|
static readonly string[] ResyncLabels = { "1/1 (max quality)", "1/2", "1/4 (default)", "1/8", "1/16" };
|
|
|
|
static string _addInput = "";
|
|
static string _toRemove = null; // deferred to avoid mutating list during enumeration
|
|
|
|
public static void Draw()
|
|
{
|
|
PhysicsSettings s = PhysicsOptimizerModule.Settings;
|
|
bool changed = false;
|
|
GUILayout.BeginVertical();
|
|
|
|
// ── LOD Fast-Path ─────────────────────────────────────────────────────────
|
|
GUILayout.Label("<b>LOD Fast-Path</b>", GUILayout.ExpandWidth(false));
|
|
GUILayout.Space(4f);
|
|
|
|
bool newEnabled = GUILayout.Toggle(s.OptimizerEnabled,
|
|
" Enabled (skips expensive 3D updates for cars far from camera)");
|
|
if (newEnabled != s.OptimizerEnabled)
|
|
{
|
|
s.OptimizerEnabled = newEnabled;
|
|
ConsistLOD.Enabled = newEnabled;
|
|
changed = true;
|
|
}
|
|
|
|
GUILayout.Space(6f);
|
|
|
|
// Distance threshold slider
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label($"Distance threshold: {s.DistanceThreshold:F0} m", GUILayout.Width(220f));
|
|
float newDist = GUILayout.HorizontalSlider(s.DistanceThreshold, 5f, 200f, GUILayout.Width(200f));
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Label(" Cars farther than this from the camera use dead-reckoning.", GUI.skin.label);
|
|
|
|
if (Mathf.Abs(newDist - s.DistanceThreshold) > 0.5f)
|
|
{
|
|
s.DistanceThreshold = Mathf.Round(newDist);
|
|
ConsistLOD.DistanceThreshold = s.DistanceThreshold;
|
|
changed = true;
|
|
}
|
|
|
|
GUILayout.Space(6f);
|
|
|
|
// Resync quality radio buttons
|
|
GUILayout.Label("Resync quality (fraction of far cars fully updated per tick):");
|
|
GUILayout.BeginHorizontal();
|
|
for (int i = 0; i < ResyncOptions.Length; i++)
|
|
{
|
|
bool selected = s.ResyncInterval == ResyncOptions[i];
|
|
if (GUILayout.Toggle(selected, ResyncLabels[i], GUI.skin.button, GUILayout.Width(130f)) && !selected)
|
|
{
|
|
s.ResyncInterval = ResyncOptions[i];
|
|
ConsistLOD.ResyncInterval = s.ResyncInterval;
|
|
changed = true;
|
|
}
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Label(" Lower = smoother visuals; higher = cheaper. Cost spread evenly via stagger.", GUI.skin.label);
|
|
|
|
GUILayout.Space(12f);
|
|
|
|
// ── Auto Freeze ───────────────────────────────────────────────────────────
|
|
GUILayout.Label("<b>Auto Freeze</b> (replaces stock optimizer)", GUILayout.ExpandWidth(false));
|
|
GUILayout.Space(4f);
|
|
|
|
bool newAF = GUILayout.Toggle(s.AutoFreezeEnabled,
|
|
" Skip Verlet tick for consists that are far away and nearly stopped");
|
|
if (newAF != s.AutoFreezeEnabled)
|
|
{
|
|
s.AutoFreezeEnabled = newAF;
|
|
ConsistFreezer.AutoFreezeEnabled = newAF;
|
|
changed = true;
|
|
}
|
|
|
|
if (s.AutoFreezeEnabled)
|
|
{
|
|
GUILayout.Space(4f);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label($"Freeze distance: {s.AutoFreezeDistance:F0} m", GUILayout.Width(200f));
|
|
float newFDist = GUILayout.HorizontalSlider(s.AutoFreezeDistance, 50f, 1000f, GUILayout.Width(200f));
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Label(" Consists with no car closer than this are eligible to freeze.", GUI.skin.label);
|
|
if (Mathf.Abs(newFDist - s.AutoFreezeDistance) > 1f)
|
|
{
|
|
s.AutoFreezeDistance = Mathf.Round(newFDist);
|
|
ConsistFreezer.AutoFreezeDistance = s.AutoFreezeDistance;
|
|
changed = true;
|
|
}
|
|
|
|
GUILayout.Space(4f);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label($"Speed threshold: {s.AutoFreezeSpeedThreshold * 2.23694f:F1} mph", GUILayout.Width(200f));
|
|
float newSpd = GUILayout.HorizontalSlider(s.AutoFreezeSpeedThreshold * 2.23694f, 0f, 10f, GUILayout.Width(200f));
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Label(" Consist must be slower than this to be eligible.", GUI.skin.label);
|
|
float newSpdMs = newSpd / 2.23694f;
|
|
if (Mathf.Abs(newSpdMs - s.AutoFreezeSpeedThreshold) > 0.01f)
|
|
{
|
|
s.AutoFreezeSpeedThreshold = newSpdMs;
|
|
ConsistFreezer.AutoFreezeSpeedThreshold = s.AutoFreezeSpeedThreshold;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
GUILayout.Space(12f);
|
|
|
|
// ── Locomotive Exclusions ─────────────────────────────────────────────────
|
|
GUILayout.Label("<b>Locomotive Exclusions</b>", GUILayout.ExpandWidth(false));
|
|
GUILayout.Space(4f);
|
|
|
|
bool newExLOD = GUILayout.Toggle(s.ExcludeLocosFromLOD, " Exclude locomotives from LOD fast-path");
|
|
if (newExLOD != s.ExcludeLocosFromLOD)
|
|
{
|
|
s.ExcludeLocosFromLOD = newExLOD;
|
|
ConsistLOD.ExcludeLocomotives = newExLOD;
|
|
changed = true;
|
|
}
|
|
|
|
bool newExFreeze = GUILayout.Toggle(s.ExcludeLocosFromFreeze, " Exclude locomotives from Auto Freeze");
|
|
if (newExFreeze != s.ExcludeLocosFromFreeze)
|
|
{
|
|
s.ExcludeLocosFromFreeze = newExFreeze;
|
|
ConsistFreezer.ExcludeLocomotives = newExFreeze;
|
|
changed = true;
|
|
}
|
|
|
|
GUILayout.Space(12f);
|
|
|
|
// ── Road Number Filter ────────────────────────────────────────────────────
|
|
GUILayout.Label("<b>Road Number Filter</b>", GUILayout.ExpandWidth(false));
|
|
GUILayout.Space(4f);
|
|
|
|
bool newBlEnabled = GUILayout.Toggle(s.BlacklistEnabled, " Enable road number filter");
|
|
if (newBlEnabled != s.BlacklistEnabled)
|
|
{
|
|
s.BlacklistEnabled = newBlEnabled;
|
|
ConsistLOD.BlacklistEnabled = newBlEnabled;
|
|
ConsistLOD.InvalidateConsistCache();
|
|
changed = true;
|
|
}
|
|
|
|
if (s.BlacklistEnabled)
|
|
{
|
|
GUILayout.Space(4f);
|
|
|
|
// Mode toggle
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("Mode:", GUILayout.Width(45f));
|
|
if (GUILayout.Toggle(s.IsBlacklist, " Blacklist (listed consists skip optimizer)",
|
|
GUILayout.ExpandWidth(false)) && !s.IsBlacklist)
|
|
{
|
|
s.IsBlacklist = true;
|
|
ConsistLOD.IsBlacklist = true;
|
|
ConsistLOD.InvalidateConsistCache();
|
|
changed = true;
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Space(45f);
|
|
if (GUILayout.Toggle(!s.IsBlacklist, " Whitelist (only listed consists use optimizer)",
|
|
GUILayout.ExpandWidth(false)) && s.IsBlacklist)
|
|
{
|
|
s.IsBlacklist = false;
|
|
ConsistLOD.IsBlacklist = false;
|
|
ConsistLOD.InvalidateConsistCache();
|
|
changed = true;
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.Space(6f);
|
|
|
|
// Add entry
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("Road number:", GUILayout.Width(110f));
|
|
_addInput = GUILayout.TextField(_addInput, GUILayout.Width(160f));
|
|
if (GUILayout.Button("Add", GUILayout.Width(50f)))
|
|
{
|
|
string trimmed = _addInput.Trim();
|
|
if (trimmed.Length > 0 && !s.RoadNumberList.Contains(trimmed))
|
|
{
|
|
s.RoadNumberList = s.RoadNumberList.Append(trimmed).ToArray();
|
|
SyncList(s);
|
|
changed = true;
|
|
}
|
|
_addInput = "";
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Label(" Enter the full display name as shown in-game (e.g. \"UP 1234\").", GUI.skin.label);
|
|
|
|
GUILayout.Space(4f);
|
|
|
|
// Current entries
|
|
if (s.RoadNumberList.Length == 0)
|
|
{
|
|
GUILayout.Label(" (no entries)", GUI.skin.label);
|
|
}
|
|
else
|
|
{
|
|
foreach (string name in s.RoadNumberList)
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label(name, GUILayout.Width(180f));
|
|
if (GUILayout.Button("Remove", GUILayout.Width(70f)))
|
|
_toRemove = name;
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
// Apply deferred removal (can't mutate inside foreach)
|
|
if (_toRemove != null)
|
|
{
|
|
s.RoadNumberList = s.RoadNumberList.Where(n => n != _toRemove).ToArray();
|
|
SyncList(s);
|
|
_toRemove = null;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
GUILayout.Space(12f);
|
|
|
|
// ── Debug Visualization ───────────────────────────────────────────────────
|
|
GUILayout.Label("<b>Debug Visualization</b>", GUILayout.ExpandWidth(false));
|
|
GUILayout.Space(4f);
|
|
GUILayout.Label(" Tints car models by physics state. Useful for verifying LOD and freeze behaviour.", GUI.skin.label);
|
|
GUILayout.Space(4f);
|
|
|
|
bool newHF = GUILayout.Toggle(s.DebugHighlightFrozen, " Highlight frozen cars (yellow)");
|
|
if (newHF != s.DebugHighlightFrozen) { s.DebugHighlightFrozen = newHF; changed = true; }
|
|
|
|
bool newHFP = GUILayout.Toggle(s.DebugHighlightFastPath, " Highlight fast-path cars (cyan)");
|
|
if (newHFP != s.DebugHighlightFastPath) { s.DebugHighlightFastPath = newHFP; changed = true; }
|
|
|
|
bool newHFU = GUILayout.Toggle(s.DebugHighlightFullPath, " Highlight full-accuracy cars (magenta)");
|
|
if (newHFU != s.DebugHighlightFullPath) { s.DebugHighlightFullPath = newHFU; changed = true; }
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
if (changed)
|
|
PhysicsOptimizerModule.Persist();
|
|
}
|
|
|
|
static void SyncList(PhysicsSettings s)
|
|
{
|
|
ConsistLOD.RoadNumberList.Clear();
|
|
foreach (string n in s.RoadNumberList)
|
|
ConsistLOD.RoadNumberList.Add(n);
|
|
ConsistLOD.InvalidateConsistCache();
|
|
}
|
|
}
|