using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace NatureManufacture.RAM { [ExecuteInEditMode] public class LodManager : MonoBehaviour, ISerializationCallbackReceiver { public struct SVector2Int { public int X; public int Y; } private enum SubdividePhase { Loading = 0, PreSubdivide = 1, InitArrays = 2, DoSubdivide = 3, FinishMesh = 4 } [SerializeField] private MeshFilter sourceMeshFilter; [SerializeField] private float refreshTime = 0.1f; [SerializeField] private Vector4 lodDistance = new Vector4(100f, 80f, 50f, 20f); [SerializeField] private Transform cameraLod; [SerializeField] private float trianglesRefreshRate = 200f; [SerializeField] private float cameraMovementDistanceCheck = 0.1f; [SerializeField] private int levelStart = 4; private int currentTriangleIndex; private float _lastRefresh = -100f; private readonly Queue<(int, int, int, int)> subdivideData = new Queue<(int, int, int, int)>(); private SubdividePhase _currentPhase; private Vector3 _camPosition; private MeshRenderer _sourceMeshRenderer; private Matrix4x4 _localToWorldMatrix; private Bounds _objectBounds; private Vector3 _lastCameraPosition; private Mesh _targetMesh; private Mesh _baseMesh; private readonly List _vertices = new List(); private readonly List _normals = new List(); private readonly List _tangents = new List(); private readonly List _colors = new List(); private readonly List _uv = new List(); private readonly List _uv3 = new List(); private readonly List _indices = new List(); private Bounds _meshBounds; private int[] _indicesBase; private int _verticesCount; private int _indicesCount; private readonly Dictionary _newVertices = new Dictionary(new NmsVector2IntEqualityComparer()); private SVector2Int _test; public MeshFilter SourceMeshFilter { get { return sourceMeshFilter; } set { sourceMeshFilter = value; } } public Vector4 LODDistance { get { return lodDistance; } set { lodDistance = value; } } public float RefreshTime { get { return refreshTime; } set { refreshTime = value; } } public Transform CameraLod { get { if (cameraLod == null && Camera.main != null) { cameraLod = Camera.main.transform; } return cameraLod; } set { cameraLod = value; } } private void OnEnable() { _currentPhase = SubdividePhase.Loading; } private void OnDisable() { StopAllCoroutines(); _currentPhase = SubdividePhase.Loading; } public void OnBeforeSerialize() { StopAllCoroutines(); } public void OnAfterDeserialize() { _currentPhase = SubdividePhase.Loading; } private void PrepareMesh() { if (!CameraLod && Camera.main != null) { CameraLod = Camera.main.transform; } _baseMesh = SourceMeshFilter.sharedMesh; _baseMesh.RecalculateTangents(); _meshBounds = _baseMesh.bounds; _sourceMeshRenderer = SourceMeshFilter.GetComponent(); _objectBounds = _sourceMeshRenderer.bounds; ClearData(); _targetMesh = DuplicateMesh(_baseMesh); _targetMesh.indexFormat = IndexFormat.UInt32; _targetMesh.MarkDynamic(); GetComponent().sharedMesh = _targetMesh; _currentPhase = SubdividePhase.PreSubdivide; _lastRefresh = Time.realtimeSinceStartup; _lastCameraPosition = Vector3.zero; } private void ClearData() { _vertices.Clear(); _normals.Clear(); _tangents.Clear(); _colors.Clear(); _uv.Clear(); _uv3.Clear(); _indices.Clear(); _newVertices.Clear(); } private void Update() { ManageSubdivide(); } private void ManageSubdivide(bool checkDistance = true) { switch (_currentPhase) { case SubdividePhase.Loading: PrepareMesh(); break; case SubdividePhase.PreSubdivide: WaitForSubdivide(checkDistance); break; case SubdividePhase.InitArrays: InitArrays(); break; case SubdividePhase.DoSubdivide: DoSubdivide(); break; case SubdividePhase.FinishMesh: FinishMesh(); break; } } private void WaitForSubdivide(bool checkDistance) { if (!(_lastRefresh + RefreshTime < Time.realtimeSinceStartup)) { return; } _lastRefresh = Time.realtimeSinceStartup; SetPosition(); if (checkDistance && !(Mathf.Sqrt(_objectBounds.SqrDistance(_camPosition)) < lodDistance[0])) { return; } float num = Vector3.Distance(_camPosition, _lastCameraPosition); float num2 = lodDistance[0]; for (int i = 1; i < 4; i++) { if (lodDistance[i] != 0f) { num2 = lodDistance[i]; break; } } if (!checkDistance || !(num < num2 * cameraMovementDistanceCheck)) { _lastCameraPosition = _camPosition; _currentPhase = SubdividePhase.InitArrays; } } private void SetPosition() { if (!Application.isPlaying || !(CameraLod == null)) { _camPosition = CameraLod.position; } } private void InitArrays() { if ((bool)_baseMesh) { _baseMesh = SourceMeshFilter.sharedMesh; } if (!_baseMesh) { Debug.Log(_baseMesh.vertices.Length); Debug.LogError("No base mesh filter with mesh"); return; } _verticesCount = _vertices.Count; _indicesCount = 0; currentTriangleIndex = 0; _localToWorldMatrix = base.transform.localToWorldMatrix; _currentPhase = SubdividePhase.DoSubdivide; } private void FinishMesh() { _targetMesh.SetVertices(_vertices, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); if (_normals.Count > 0) { _targetMesh.SetNormals(_normals, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); } if (_tangents.Count > 0) { _targetMesh.SetTangents(_tangents, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); } if (_colors.Count > 0) { _targetMesh.SetColors(_colors, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); } if (_uv.Count > 0) { _targetMesh.SetUVs(0, _uv, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); } if (_uv3.Count > 0) { _targetMesh.SetUVs(3, _uv3, 0, _verticesCount, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); } _targetMesh.SetTriangles(_indices, 0, _indicesCount, 0, calculateBounds: false); _targetMesh.bounds = _meshBounds; if (_sourceMeshRenderer.enabled) { _sourceMeshRenderer.enabled = false; } _currentPhase = SubdividePhase.PreSubdivide; } private void DoSubdivide() { for (int i = 0; (float)i < trianglesRefreshRate; i++) { if (currentTriangleIndex >= _indicesBase.Length) { currentTriangleIndex = 0; _currentPhase = SubdividePhase.FinishMesh; break; } int i2 = _indicesBase[currentTriangleIndex]; int i3 = _indicesBase[currentTriangleIndex + 1]; int i4 = _indicesBase[currentTriangleIndex + 2]; SubdivideTriangleIterative(_camPosition, levelStart, i2, i3, i4); currentTriangleIndex += 3; } } private void SubdivideTriangleIterative(Vector3 camPosition, int level, int i1, int i2, int i3) { subdivideData.Enqueue((level, i1, i2, i3)); while (subdivideData.Count > 0) { (int, int, int, int) tuple = subdivideData.Dequeue(); int item = tuple.Item1; int item2 = tuple.Item2; int item3 = tuple.Item3; int item4 = tuple.Item4; int num = levelStart - item; if (num > 3) { num = 0; } float num2 = LODDistance[num]; bool flag = Vector3.Distance(_localToWorldMatrix.MultiplyPoint3x4(_vertices[item2]), camPosition) > num2; bool flag2 = Vector3.Distance(_localToWorldMatrix.MultiplyPoint3x4(_vertices[item3]), camPosition) > num2; bool flag3 = Vector3.Distance(_localToWorldMatrix.MultiplyPoint3x4(_vertices[item4]), camPosition) > num2; if (!flag && !flag2 && flag3 && item > 0) { int newVertex = GetNewVertex4(item2, item3); AddOrChangeIndice(item2); AddOrChangeIndice(newVertex); AddOrChangeIndice(item4); AddOrChangeIndice(newVertex); AddOrChangeIndice(item3); AddOrChangeIndice(item4); } else if (flag && !flag2 && !flag3 && item > 0) { int newVertex2 = GetNewVertex4(item3, item4); AddOrChangeIndice(item2); AddOrChangeIndice(newVertex2); AddOrChangeIndice(item4); AddOrChangeIndice(item2); AddOrChangeIndice(item3); AddOrChangeIndice(newVertex2); } else if (!flag && flag2 && !flag3 && item > 0) { int newVertex3 = GetNewVertex4(item2, item4); AddOrChangeIndice(newVertex3); AddOrChangeIndice(item3); AddOrChangeIndice(item4); AddOrChangeIndice(item2); AddOrChangeIndice(item3); AddOrChangeIndice(newVertex3); } else if (flag || flag2 || item == 0) { AddOrChangeIndice(item2); AddOrChangeIndice(item3); AddOrChangeIndice(item4); } else if (item > 0) { int newVertex4 = GetNewVertex4(item2, item3); int newVertex5 = GetNewVertex4(item3, item4); int newVertex6 = GetNewVertex4(item4, item2); subdivideData.Enqueue((item - 1, item2, newVertex4, newVertex6)); subdivideData.Enqueue((item - 1, item3, newVertex5, newVertex4)); subdivideData.Enqueue((item - 1, item4, newVertex6, newVertex5)); subdivideData.Enqueue((item - 1, newVertex4, newVertex5, newVertex6)); } } } private void AddOrChangeIndice(int indice) { if (_indices.Count > _indicesCount) { _indices[_indicesCount] = indice; } else { _indices.Add(indice); } _indicesCount++; } private int GetNewVertex4(int i1, int i2) { if (i1 < i2) { _test.X = i1; _test.Y = i2; } else { _test.X = i2; _test.Y = i1; } if (_newVertices.TryGetValue(_test, out var value)) { return value; } int count = _vertices.Count; _newVertices.Add(_test, count); if (_vertices.Count > _verticesCount) { _vertices[_verticesCount] = (_vertices[i1] + _vertices[i2]) * 0.5f; _uv[_verticesCount] = NewUv(_uv, i1, i2); _normals[_verticesCount] = (_normals[i1] + _normals[i2]) * 0.5f; _tangents[_verticesCount] = (_tangents[i1] + _tangents[i2]) * 0.5f; if (_colors.Count > 0) { _colors[_verticesCount] = Color.Lerp(_colors[i1], _colors[i2], 0.5f); } if (_uv3.Count > 0) { _uv3[_verticesCount] = (_uv3[i1] + _uv3[i2]) * 0.5f; } } else { _vertices.Add((_vertices[i1] + _vertices[i2]) * 0.5f); _normals.Add((_normals[i1] + _normals[i2]) * 0.5f); _tangents.Add((_tangents[i1] + _tangents[i2]) * 0.5f); _uv.Add(NewUv(_uv, i1, i2)); if (_colors.Count > 0) { _colors.Add(Color.Lerp(_colors[i1], _colors[i2], 0.5f)); } if (_uv3.Count > 0) { _uv3.Add((_uv3[i1] + _uv3[i2]) * 0.5f); } } _verticesCount++; return count; } private Vector4 NewUv(List oldUv, int i1, int i2) { return (oldUv[i1] + oldUv[i2]) * 0.5f; } private Mesh DuplicateMesh(Mesh mesh) { _indicesBase = mesh.triangles; _vertices.AddRange(mesh.vertices); _normals.AddRange(mesh.normals); _tangents.AddRange(mesh.tangents); _colors.AddRange(mesh.colors); mesh.GetUVs(0, _uv); mesh.GetUVs(3, _uv3); return Object.Instantiate(mesh); } } }