485 lines
12 KiB
C#
485 lines
12 KiB
C#
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<Vector3> _vertices = new List<Vector3>();
|
|
|
|
private readonly List<Vector3> _normals = new List<Vector3>();
|
|
|
|
private readonly List<Vector4> _tangents = new List<Vector4>();
|
|
|
|
private readonly List<Color> _colors = new List<Color>();
|
|
|
|
private readonly List<Vector4> _uv = new List<Vector4>();
|
|
|
|
private readonly List<Vector4> _uv3 = new List<Vector4>();
|
|
|
|
private readonly List<int> _indices = new List<int>();
|
|
|
|
private Bounds _meshBounds;
|
|
|
|
private int[] _indicesBase;
|
|
|
|
private int _verticesCount;
|
|
|
|
private int _indicesCount;
|
|
|
|
private readonly Dictionary<SVector2Int, int> _newVertices = new Dictionary<SVector2Int, int>(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<MeshRenderer>();
|
|
_objectBounds = _sourceMeshRenderer.bounds;
|
|
ClearData();
|
|
_targetMesh = DuplicateMesh(_baseMesh);
|
|
_targetMesh.indexFormat = IndexFormat.UInt32;
|
|
_targetMesh.MarkDynamic();
|
|
GetComponent<MeshFilter>().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<Vector4> 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);
|
|
}
|
|
}
|
|
}
|