diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/WireRail/WireRailComponentTests.cs b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/WireRail/WireRailComponentTests.cs index c7b09ab75..73382e73b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/WireRail/WireRailComponentTests.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/WireRail/WireRailComponentTests.cs @@ -34,6 +34,54 @@ public void TearDown() Undo.ClearAll(); } + [Test] + public void ShouldKeepResolvedRenderMaterialAcrossActivationAndMeshRebuilds() + { + var go = new GameObject("Wire Rail"); go.SetActive(false); + var imported = new Material(Shader.Find("Hidden/InternalErrorShader")); + var resolved = new Material(imported); + try { + var component = go.AddComponent(); + var renderer = component.SplineContainer.GetComponent(); + renderer.sharedMaterial = imported; + component.RebuildRenderGeometry(); + // Package profiles replace glTF placeholders while the table is inactive. + renderer.sharedMaterial = resolved; + go.SetActive(true); + Assert.That(renderer.sharedMaterial, Is.SameAs(resolved)); + component.RebuildRenderGeometry(); + Assert.That(renderer.sharedMaterial, Is.SameAs(resolved)); + Object.DestroyImmediate(renderer); + component.RebuildRenderGeometry(); + Assert.That(component.SplineContainer.GetComponent().sharedMaterial, Is.SameAs(resolved)); + } finally { + Object.DestroyImmediate(go); Object.DestroyImmediate(imported); Object.DestroyImmediate(resolved); + } + } + [TestCase(false)] + [TestCase(true)] + public void ShouldApplyAnExplicitRenderMaterialChangeAfterAssigningTheRenderer(bool afterDomainReload) + { + var go = new GameObject("Wire Rail"); + var original = new Material(Shader.Find("Hidden/InternalErrorShader")); + var chosen = new Material(original); + try { + var component = go.AddComponent(); + var renderer = component.SplineContainer.GetComponent(); + renderer.sharedMaterial = original; + component.RebuildRenderGeometry(); + // The inspector edits this serialized choice, rather than the generated renderer. + typeof(WireRailComponent).GetField("_renderMaterial", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(component, chosen); + if (afterDomainReload) { + // Unity retains the serialized choice but clears assignment tracking on reload. + typeof(WireRailComponent).GetField("_lastAssignedRenderMaterial", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(component, null); + } + component.RebuildRenderGeometry(); + Assert.That(renderer.sharedMaterial, Is.SameAs(chosen)); + } finally { + Object.DestroyImmediate(go); Object.DestroyImmediate(original); Object.DestroyImmediate(chosen); + } + } [Test] public void ShouldCreateAThreeDimensionalVpxSpline() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/WireRail/WireRailComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/WireRail/WireRailComponent.cs index acc219b21..15891b50d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/WireRail/WireRailComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/WireRail/WireRailComponent.cs @@ -1763,6 +1763,7 @@ public class WireRailComponent : MonoBehaviour, ICollidableComponent, IPackable [NonSerialized] private Vector3[] _colliderEdgeVertices = Array.Empty(); [NonSerialized] private int _colliderTopologyRetryCount; [NonSerialized] private bool _rebuildingGeneratedMeshes; + [NonSerialized] private Material _lastAssignedRenderMaterial; [NonSerialized] private bool _collidersDirty = true; [NonSerialized] private bool _colliderGeometryDirty = true; [NonSerialized] private int _renderGeometryVersion; @@ -3626,16 +3627,19 @@ private void AssignRenderMaterial(MeshRenderer renderer, bool adoptExisting) { var pipeline = GraphicsSettings.currentRenderPipeline; var pipelineDefault = pipeline ? pipeline.defaultMaterial : GetBuiltinDefaultMaterial(); - if (adoptExisting && !_renderMaterial && renderer.sharedMaterial - && renderer.sharedMaterial != pipelineDefault) { + if (adoptExisting && renderer.sharedMaterial + && renderer.sharedMaterial != pipelineDefault + && (!_renderMaterial || _lastAssignedRenderMaterial && renderer.sharedMaterial != _lastAssignedRenderMaterial)) { // The renderer arrived with a material of its own, e.g. restored from a - // package; that is the author's choice, not something to overwrite. + // package, or a material resolver replaced our last assignment. Keep that + // resolved material when activation or a spline edit rebuilds the mesh. _renderMaterial = renderer.sharedMaterial; } var material = _renderMaterial ? _renderMaterial : pipelineDefault; if (renderer.sharedMaterial != material) { renderer.sharedMaterial = material; } + _lastAssignedRenderMaterial = material; } private static Material GetBuiltinDefaultMaterial()