diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 93fe9ef..ce7ec9b 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -598,15 +598,10 @@ MonoBehaviour: solver: {fileID: 603012225} initialFirstSegmentLength: 1.2 minFirstSegmentLength: 0.1 - maxFirstSegmentLength: 5 + maxFirstSegmentLength: 50 lineAdjustSpeed: 1 extendKey: 273 retractKey: 274 - rebuildKey: 114 - showGui: 1 - showNodeDetails: 1 - showSegmentDetails: 1 - detailScroll: {x: 0, y: 0} --- !u!1 &832575517 GameObject: m_ObjectHideFlags: 0 @@ -960,7 +955,7 @@ GameObject: - component: {fileID: 1939106084} - component: {fileID: 1939106083} - component: {fileID: 1939106082} - m_Layer: 0 + m_Layer: 6 m_Name: Terrain m_TagString: Untagged m_Icon: {fileID: 0} diff --git a/Assets/Scripts/FishingLine/FishingLineRenderer.cs b/Assets/Scripts/FishingLine/FishingLineRenderer.cs index 4534170..08069d9 100644 --- a/Assets/Scripts/FishingLine/FishingLineRenderer.cs +++ b/Assets/Scripts/FishingLine/FishingLineRenderer.cs @@ -17,12 +17,28 @@ namespace F2RopeLine2.FishingLine [SerializeField] private float damping = 0.98f; [Min(0f)] [SerializeField] private float gravityScale = 1f; + [Min(0.001f)] + [SerializeField] private float simulationStep = 0.0166667f; + [Min(0.001f)] + [SerializeField] private float maxDeltaTime = 0.0333333f; [Header("Pin Follow")] [Min(0f)] [SerializeField] private float pinFollowStrength = 50f; [SerializeField] private bool writeBackVirtualNodeTransforms = true; + [Header("Stability")] + [Min(1)] + [SerializeField] private int maxSubStepsPerFrame = 2; + [Min(0f)] + [SerializeField] private float sleepVelocityThreshold = 0.001f; + [Min(0f)] + [SerializeField] private float sleepDistanceThreshold = 0.002f; + [Min(1)] + [SerializeField] private int stableFramesBeforeSleep = 4; + [Min(0f)] + [SerializeField] private float wakeDistanceThreshold = 0.001f; + [Header("Debug")] [SerializeField] private bool drawDebugSamples = false; [SerializeField] private Color debugSampleColor = new(1f, 0.2f, 0.2f, 1f); @@ -31,7 +47,13 @@ namespace F2RopeLine2.FishingLine private readonly List positions = new(); private readonly List previousPositions = new(); + private readonly List sampledNodes = new(); + private readonly List lastPinnedNodePositions = new(); + private readonly List lastRestLengths = new(); private bool[] pinnedFlags = System.Array.Empty(); + private float accumulatedTime; + private bool isSleeping; + private int stableFrameCounter; public int SampleCount => positions.Count; @@ -83,27 +105,59 @@ namespace F2RopeLine2.FishingLine return; } - EnsureBuffers(nodes, pinnedIndices); + var topologyChanged = EnsureBuffers(nodes, pinnedIndices); + if (topologyChanged) + { + WakeUp(); + } + + if (ShouldWake(nodes, restLengths)) + { + WakeUp(); + } + Simulate(nodes, restLengths, deltaTime); ApplyToRenderer(nodes); + CacheFrameState(nodes, restLengths); } - private void EnsureBuffers( + private bool EnsureBuffers( IReadOnlyList nodes, IReadOnlyList pinnedIndices) { - if (positions.Count == nodes.Count) + var topologyChanged = positions.Count != nodes.Count; + var previousPositionMap = new Dictionary(sampledNodes.Count); + var previousHistoryMap = new Dictionary(sampledNodes.Count); + for (var i = 0; i < sampledNodes.Count; i++) { - return; + var sampledNode = sampledNodes[i]; + if (sampledNode == null) + { + continue; + } + + previousPositionMap[sampledNode] = positions[i]; + previousHistoryMap[sampledNode] = previousPositions[i]; } positions.Clear(); previousPositions.Clear(); + sampledNodes.Clear(); pinnedFlags = new bool[nodes.Count]; for (var i = 0; i < nodes.Count; i++) { - var position = nodes[i].Position; + var node = nodes[i]; + sampledNodes.Add(node); + + if (node != null && previousPositionMap.TryGetValue(node, out var preservedPosition)) + { + positions.Add(preservedPosition); + previousPositions.Add(previousHistoryMap[node]); + continue; + } + + var position = node != null ? node.Position : Vector3.zero; positions.Add(position); previousPositions.Add(position); } @@ -116,12 +170,41 @@ namespace F2RopeLine2.FishingLine pinnedFlags[pinnedIndex] = true; } } + + return topologyChanged; } private void Simulate(IReadOnlyList nodes, IReadOnlyList restLengths, float deltaTime) { - var simulationDelta = Mathf.Max(0.0001f, deltaTime); - var gravity = Physics.gravity * gravityScale * simulationDelta * simulationDelta; + if (isSleeping) + { + PinLogicalNodes(nodes, simulationStep); + return; + } + + var clampedDelta = Mathf.Clamp(deltaTime, 0f, maxDeltaTime); + accumulatedTime = Mathf.Min(accumulatedTime + clampedDelta, simulationStep * maxSubStepsPerFrame); + + var subStepCount = 0; + while (accumulatedTime >= simulationStep && subStepCount < maxSubStepsPerFrame) + { + SimulateStep(nodes, restLengths, simulationStep); + accumulatedTime -= simulationStep; + subStepCount++; + } + + if (subStepCount == 0) + { + PinLogicalNodes(nodes, simulationStep); + ApplySleep(nodes); + } + + EvaluateSleepState(nodes, restLengths); + } + + private void SimulateStep(IReadOnlyList nodes, IReadOnlyList restLengths, float stepDelta) + { + var gravity = Physics.gravity * gravityScale * stepDelta * stepDelta; for (var i = 0; i < nodes.Count; i++) { @@ -141,7 +224,7 @@ namespace F2RopeLine2.FishingLine for (var iteration = 0; iteration < solverIterations; iteration++) { - PinLogicalNodes(nodes, simulationDelta); + PinLogicalNodes(nodes, stepDelta); for (var segmentIndex = 0; segmentIndex < restLengths.Count; segmentIndex++) { @@ -149,7 +232,8 @@ namespace F2RopeLine2.FishingLine } } - PinLogicalNodes(nodes, simulationDelta); + PinLogicalNodes(nodes, stepDelta); + ApplySleep(nodes); } private void PinLogicalNodes(IReadOnlyList nodes, float deltaTime) @@ -211,6 +295,136 @@ namespace F2RopeLine2.FishingLine positions[segmentIndex + 1] -= correction; } + private void ApplySleep(IReadOnlyList nodes) + { + for (var i = 0; i < nodes.Count; i++) + { + if (pinnedFlags[i]) + { + continue; + } + + var current = positions[i]; + var previous = previousPositions[i]; + var velocityMagnitude = (current - previous).magnitude; + var authoredDistance = Vector3.Distance(current, nodes[i].Position); + + if (velocityMagnitude <= sleepVelocityThreshold && authoredDistance <= sleepDistanceThreshold) + { + previousPositions[i] = current; + } + } + } + + private void EvaluateSleepState(IReadOnlyList nodes, IReadOnlyList restLengths) + { + var isStable = true; + for (var i = 0; i < nodes.Count; i++) + { + if (pinnedFlags[i]) + { + continue; + } + + var velocityMagnitude = (positions[i] - previousPositions[i]).magnitude; + if (velocityMagnitude > sleepVelocityThreshold) + { + isStable = false; + break; + } + } + + if (isStable) + { + for (var i = 0; i < restLengths.Count; i++) + { + var error = Mathf.Abs(Vector3.Distance(positions[i], positions[i + 1]) - restLengths[i]); + if (error > sleepDistanceThreshold) + { + isStable = false; + break; + } + } + } + + if (!isStable) + { + stableFrameCounter = 0; + return; + } + + stableFrameCounter++; + if (stableFrameCounter < stableFramesBeforeSleep) + { + return; + } + + isSleeping = true; + accumulatedTime = 0f; + for (var i = 0; i < positions.Count; i++) + { + previousPositions[i] = positions[i]; + } + } + + private bool ShouldWake(IReadOnlyList nodes, IReadOnlyList restLengths) + { + if (!isSleeping) + { + return false; + } + + if (lastPinnedNodePositions.Count != nodes.Count || lastRestLengths.Count != restLengths.Count) + { + return true; + } + + for (var i = 0; i < nodes.Count; i++) + { + if (!pinnedFlags[i]) + { + continue; + } + + if (Vector3.Distance(nodes[i].Position, lastPinnedNodePositions[i]) > wakeDistanceThreshold) + { + return true; + } + } + + for (var i = 0; i < restLengths.Count; i++) + { + if (Mathf.Abs(restLengths[i] - lastRestLengths[i]) > wakeDistanceThreshold) + { + return true; + } + } + + return false; + } + + private void CacheFrameState(IReadOnlyList nodes, IReadOnlyList restLengths) + { + lastPinnedNodePositions.Clear(); + for (var i = 0; i < nodes.Count; i++) + { + lastPinnedNodePositions.Add(nodes[i] != null ? nodes[i].Position : Vector3.zero); + } + + lastRestLengths.Clear(); + for (var i = 0; i < restLengths.Count; i++) + { + lastRestLengths.Add(restLengths[i]); + } + } + + private void WakeUp() + { + isSleeping = false; + stableFrameCounter = 0; + accumulatedTime = 0f; + } + private void ApplyToRenderer(IReadOnlyList nodes) { lineRenderer.positionCount = positions.Count; diff --git a/Assets/Scripts/FishingLine/FishingLineSolver.cs b/Assets/Scripts/FishingLine/FishingLineSolver.cs index 002367b..e7325bf 100644 --- a/Assets/Scripts/FishingLine/FishingLineSolver.cs +++ b/Assets/Scripts/FishingLine/FishingLineSolver.cs @@ -46,7 +46,8 @@ namespace F2RopeLine2.FishingLine private readonly List orderedNodes = new(); private readonly List restLengths = new(); private readonly List pinnedIndices = new(); - private readonly List runtimeVirtualNodes = new(); + private readonly List firstSegmentVirtualPool = new(); + private readonly List otherSegmentVirtualPool = new(); private readonly List runtimeJoints = new(); private Transform virtualNodeRoot; @@ -68,16 +69,24 @@ namespace F2RopeLine2.FishingLine public int LogicalNodeCount => logicalNodes?.Length ?? 0; - public int RuntimeVirtualNodeCount => runtimeVirtualNodes.Count; + public int RuntimeVirtualNodeCount => firstSegmentVirtualPool.Count + otherSegmentVirtualPool.Count; public int ActiveRuntimeVirtualNodeCount { get { var activeCount = 0; - for (var i = 0; i < runtimeVirtualNodes.Count; i++) + for (var i = 0; i < firstSegmentVirtualPool.Count; i++) { - if (runtimeVirtualNodes[i] != null && runtimeVirtualNodes[i].gameObject.activeSelf) + if (firstSegmentVirtualPool[i] != null && firstSegmentVirtualPool[i].gameObject.activeSelf) + { + activeCount++; + } + } + + for (var i = 0; i < otherSegmentVirtualPool.Count; i++) + { + if (otherSegmentVirtualPool[i] != null && otherSegmentVirtualPool[i].gameObject.activeSelf) { activeCount++; } @@ -350,29 +359,39 @@ namespace F2RopeLine2.FishingLine return; } - var expectedVirtualCount = 0; var segmentLayouts = new SegmentLayout[logicalNodes.Length - 1]; + var firstSegmentVirtualCount = 0; + var otherSegmentVirtualCount = 0; for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++) { segmentLayouts[segmentIndex] = BuildSegmentLayout(segmentIndex); - expectedVirtualCount += segmentLayouts[segmentIndex].VirtualNodeCount; + if (segmentIndex == 0) + { + firstSegmentVirtualCount = segmentLayouts[segmentIndex].VirtualNodeCount; + } + else + { + otherSegmentVirtualCount += segmentLayouts[segmentIndex].VirtualNodeCount; + } } - ResizeRuntimeVirtualNodePool(expectedVirtualCount); + EnsureFirstSegmentVirtualPool(firstSegmentVirtualCount); + EnsureOtherSegmentVirtualPool(otherSegmentVirtualCount); orderedNodes.Add(logicalNodes[0]); pinnedIndices.Add(0); - var virtualNodeCursor = 0; + var otherVirtualCursor = 0; for (var segmentIndex = 0; segmentIndex < segmentLayouts.Length; segmentIndex++) { var fromNode = logicalNodes[segmentIndex]; var toNode = logicalNodes[segmentIndex + 1]; var layout = segmentLayouts[segmentIndex]; + var segmentVirtualNodes = GetSegmentVirtualNodes(segmentIndex, layout.VirtualNodeCount, ref otherVirtualCursor); - for (var virtualIndex = 0; virtualIndex < layout.VirtualNodeCount; virtualIndex++) + for (var virtualIndex = 0; virtualIndex < segmentVirtualNodes.Count; virtualIndex++) { - var virtualNode = runtimeVirtualNodes[virtualNodeCursor++]; + var virtualNode = segmentVirtualNodes[virtualIndex]; virtualNode.SetRuntimeVirtual(true, orderedNodes.Count); virtualNode.name = $"VirtualNode_{segmentIndex}_{virtualIndex}"; virtualNode.transform.SetParent(virtualNodeRoot, false); @@ -389,10 +408,11 @@ namespace F2RopeLine2.FishingLine orderedNodes.Add(toNode); pinnedIndices.Add(orderedNodes.Count - 1); - PlaceVirtualNodesBetween(fromNode, toNode, layout, segmentIndex); + PlaceVirtualNodesBetween(fromNode, toNode, layout, segmentVirtualNodes); } - DisableUnusedVirtualNodes(expectedVirtualCount); + DisableUnusedFirstSegmentVirtualNodes(firstSegmentVirtualCount); + DisableUnusedOtherSegmentVirtualNodes(otherSegmentVirtualCount); UpdateJointLimitsFromConfig(); chainDirty = false; } @@ -464,7 +484,7 @@ namespace F2RopeLine2.FishingLine FishingLineNode fromNode, FishingLineNode toNode, SegmentLayout layout, - int segmentIndex) + IReadOnlyList segmentVirtualNodes) { if (layout.VirtualNodeCount == 0) { @@ -476,55 +496,100 @@ namespace F2RopeLine2.FishingLine var normalizedDirection = distance > 0.0001f ? direction / distance : Vector3.down; var accumulatedDistance = 0f; - var runtimeBaseIndex = GetVirtualBaseIndexForSegment(segmentIndex); for (var i = 0; i < layout.VirtualNodeCount; i++) { accumulatedDistance += layout.GapLengths[i]; var position = fromNode.Position + normalizedDirection * accumulatedDistance; - var virtualNode = runtimeVirtualNodes[runtimeBaseIndex + i]; - virtualNode.SetVisualPosition(position); + var virtualNode = segmentVirtualNodes[i]; + var wasActive = virtualNode.gameObject.activeSelf; + if (!Application.isPlaying || !wasActive) + { + virtualNode.SetVisualPosition(position); + } + virtualNode.gameObject.SetActive(true); } } - private int GetVirtualBaseIndexForSegment(int segmentIndex) + private List GetSegmentVirtualNodes(int segmentIndex, int virtualCount, ref int otherVirtualCursor) { - var index = 0; - for (var i = 0; i < segmentIndex; i++) + var result = new List(virtualCount); + if (virtualCount <= 0) { - index += BuildSegmentLayout(i).VirtualNodeCount; + return result; } - return index; + if (segmentIndex == 0) + { + for (var i = 0; i < virtualCount; i++) + { + result.Add(firstSegmentVirtualPool[virtualCount - 1 - i]); + } + + return result; + } + + for (var i = 0; i < virtualCount; i++) + { + result.Add(otherSegmentVirtualPool[otherVirtualCursor + i]); + } + + otherVirtualCursor += virtualCount; + return result; } - private void ResizeRuntimeVirtualNodePool(int targetCount) + private void EnsureFirstSegmentVirtualPool(int targetCount) { EnsureVirtualNodeRoot(); - while (runtimeVirtualNodes.Count < targetCount) + while (firstSegmentVirtualPool.Count < targetCount) { - var node = CreateRuntimeVirtualNode(runtimeVirtualNodes.Count); - runtimeVirtualNodes.Add(node); + var node = CreateRuntimeVirtualNode($"FirstSegmentVirtualNode_{firstSegmentVirtualPool.Count}"); + firstSegmentVirtualPool.Add(node); } } - private FishingLineNode CreateRuntimeVirtualNode(int index) + private void EnsureOtherSegmentVirtualPool(int targetCount) { - var runtimeObject = new GameObject($"VirtualNode_{index}"); + EnsureVirtualNodeRoot(); + + while (otherSegmentVirtualPool.Count < targetCount) + { + var node = CreateRuntimeVirtualNode($"OtherSegmentVirtualNode_{otherSegmentVirtualPool.Count}"); + otherSegmentVirtualPool.Add(node); + } + } + + private FishingLineNode CreateRuntimeVirtualNode(string nodeName) + { + var runtimeObject = new GameObject(nodeName); runtimeObject.transform.SetParent(virtualNodeRoot, false); var node = runtimeObject.AddComponent(); - node.SetRuntimeVirtual(true, index); + node.SetRuntimeVirtual(true, -1); runtimeObject.hideFlags = HideFlags.DontSave; + runtimeObject.SetActive(false); return node; } - private void DisableUnusedVirtualNodes(int usedCount) + private void DisableUnusedFirstSegmentVirtualNodes(int usedCount) { - for (var i = 0; i < runtimeVirtualNodes.Count; i++) + for (var i = 0; i < firstSegmentVirtualPool.Count; i++) { - var node = runtimeVirtualNodes[i]; + var node = firstSegmentVirtualPool[i]; + var active = i < usedCount; + if (node != null) + { + node.gameObject.SetActive(active); + } + } + } + + private void DisableUnusedOtherSegmentVirtualNodes(int usedCount) + { + for (var i = 0; i < otherSegmentVirtualPool.Count; i++) + { + var node = otherSegmentVirtualPool[i]; var active = i < usedCount; if (node != null) { @@ -535,17 +600,24 @@ namespace F2RopeLine2.FishingLine private void DestroyAllRuntimeVirtualNodes() { - for (var i = runtimeVirtualNodes.Count - 1; i >= 0; i--) + for (var i = firstSegmentVirtualPool.Count - 1; i >= 0; i--) { - DestroyNodeObject(runtimeVirtualNodes[i]); + DestroyNodeObject(firstSegmentVirtualPool[i]); } - runtimeVirtualNodes.Clear(); + for (var i = otherSegmentVirtualPool.Count - 1; i >= 0; i--) + { + DestroyNodeObject(otherSegmentVirtualPool[i]); + } + + firstSegmentVirtualPool.Clear(); + otherSegmentVirtualPool.Clear(); } private void CleanupDeadReferences() { - runtimeVirtualNodes.RemoveAll(node => node == null); + firstSegmentVirtualPool.RemoveAll(node => node == null); + otherSegmentVirtualPool.RemoveAll(node => node == null); runtimeJoints.RemoveAll(joint => joint == null); } diff --git a/Assets/Scripts/FishingLine/FishingLineTestController.cs b/Assets/Scripts/FishingLine/FishingLineTestController.cs index 6aa7722..f15e80c 100644 --- a/Assets/Scripts/FishingLine/FishingLineTestController.cs +++ b/Assets/Scripts/FishingLine/FishingLineTestController.cs @@ -20,13 +20,6 @@ namespace F2RopeLine2.FishingLine [Header("Input")] [SerializeField] private KeyCode extendKey = KeyCode.UpArrow; [SerializeField] private KeyCode retractKey = KeyCode.DownArrow; - [SerializeField] private KeyCode rebuildKey = KeyCode.R; - - [Header("Debug")] - [SerializeField] private bool showGui = true; - [SerializeField] private bool showNodeDetails = true; - [SerializeField] private bool showSegmentDetails = true; - [SerializeField] private Vector2 detailScroll = Vector2.zero; private float targetFirstSegmentLength; @@ -75,96 +68,6 @@ namespace F2RopeLine2.FishingLine solver.SetFirstSegmentLength(targetFirstSegmentLength); } - if (Input.GetKeyDown(rebuildKey)) - { - solver.RebuildNow(); - } - } - - private void OnGUI() - { - if (!showGui || solver == null) - { - return; - } - - GUILayout.BeginArea(new Rect(16f, 16f, 280f, 180f), GUI.skin.box); - GUILayout.Label("Fishing Line Test"); - GUILayout.Label($"First Segment Length: {solver.FirstSegmentLength:F2} m"); - GUILayout.Label($"Total Line Length: {solver.TotalLineLength:F2} m"); - GUILayout.Label($"Chain: {(solver.IsChainDirty ? "Dirty" : "Ready")}"); - GUILayout.Label($"Logical/Virtual/Ordered: {solver.LogicalNodeCount}/{solver.ActiveRuntimeVirtualNodeCount}/{solver.OrderedNodeCount}"); - GUILayout.Label($"Keys: {extendKey} / {retractKey} / {rebuildKey}"); - - var nextLength = GUILayout.HorizontalSlider( - targetFirstSegmentLength, - minFirstSegmentLength, - maxFirstSegmentLength); - - if (!Mathf.Approximately(nextLength, targetFirstSegmentLength)) - { - targetFirstSegmentLength = nextLength; - solver.SetFirstSegmentLength(targetFirstSegmentLength); - } - - if (GUILayout.Button("Rebuild Line")) - { - solver.RebuildNow(); - } - - GUILayout.BeginHorizontal(); - if (GUILayout.Button("Retract")) - { - targetFirstSegmentLength = Mathf.Max(minFirstSegmentLength, targetFirstSegmentLength - 0.1f); - solver.SetFirstSegmentLength(targetFirstSegmentLength); - } - - if (GUILayout.Button("Extend")) - { - targetFirstSegmentLength = Mathf.Min(maxFirstSegmentLength, targetFirstSegmentLength + 0.1f); - solver.SetFirstSegmentLength(targetFirstSegmentLength); - } - - GUILayout.EndHorizontal(); - GUILayout.EndArea(); - - GUILayout.BeginArea(new Rect(16f, 204f, 420f, 320f), GUI.skin.box); - GUILayout.Label("Fishing Line Debug"); - if (solver.TryGetComponent(out var renderer)) - { - GUILayout.Label($"Render Samples: {renderer.SampleCount}"); - GUILayout.Label($"Rendered Length: {renderer.CurrentRenderedLength:F2} m"); - } - - detailScroll = GUILayout.BeginScrollView(detailScroll, GUILayout.Width(404f), GUILayout.Height(250f)); - - if (showNodeDetails) - { - GUILayout.Label("Nodes"); - for (var i = 0; i < solver.OrderedNodeCount; i++) - { - if (!solver.TryGetOrderedNode(i, out var node)) - { - continue; - } - - GUILayout.Label($"#{i} {node.GetDebugSummary()}"); - } - } - - if (showSegmentDetails) - { - GUILayout.Label("Segments"); - for (var i = 0; i < solver.SegmentCount; i++) - { - var rest = solver.RestLengths[i]; - var actual = solver.GetActualDistance(i); - GUILayout.Label($"S{i} rest:{rest:F3}m actual:{actual:F3}m diff:{(actual - rest):F3}m"); - } - } - - GUILayout.EndScrollView(); - GUILayout.EndArea(); } } } diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 52adb77..b544c88 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -82,7 +82,7 @@ PlayerSettings: androidApplicationEntry: 2 defaultIsNativeResolution: 1 macRetinaSupport: 1 - runInBackground: 0 + runInBackground: 1 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset index 6413d11..5ef3972 100644 --- a/ProjectSettings/TagManager.asset +++ b/ProjectSettings/TagManager.asset @@ -2,7 +2,7 @@ %TAG !u! tag:unity3d.com,2011: --- !u!78 &1 TagManager: - serializedVersion: 2 + serializedVersion: 3 tags: [] layers: - Default @@ -11,7 +11,7 @@ TagManager: - - Water - UI - - + - Terrain - - - @@ -50,27 +50,4 @@ TagManager: - Light Layer 5 - Light Layer 6 - Light Layer 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + m_MigratedRenderPipelines: []