#define USE_WILLRENDEROBJECT_WORKAROUND using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using UltimateGameTools.MeshSimplifier; public class AutomaticLOD : MonoBehaviour { [Serializable] public enum EvalMode { CameraDistance, ScreenCoverage } [Serializable] public enum LevelsToGenerate { _1 = 1, _2, _3, _4, _5, _6 } [Serializable] public enum SwitchMode { SwitchMesh, SwitchGameObject, UnityLODGroup } [Serializable] public class LODLevelData { // Common public float m_fScreenCoverage; public float m_fMaxCameraDistance; public float m_fMeshVerticesAmount; // 0.0 - 1.0 public int m_nColorEditorBarIndex; public Mesh m_mesh; public bool m_bUsesOriginalMesh; // SwitchMode = SwitchGameObject / LODGroup public GameObject m_gameObject; public LODLevelData GetCopy() { LODLevelData data = new LODLevelData(); data.m_fScreenCoverage = m_fScreenCoverage; data.m_fMaxCameraDistance = m_fMaxCameraDistance; data.m_fMeshVerticesAmount = m_fMeshVerticesAmount; data.m_nColorEditorBarIndex = m_nColorEditorBarIndex; data.m_mesh = m_mesh; data.m_bUsesOriginalMesh = m_bUsesOriginalMesh; return data; } } public static Camera UserDefinedLODCamera { get { return s_userDefinedCamera; } set { s_userDefinedCamera = value; } } public SwitchMode LODSwitchMode { get { if(m_LODObjectRootPersist != null) { return m_LODObjectRootPersist.m_switchMode; } else if(m_LODObjectRoot != null) { return m_LODObjectRoot.m_switchMode; } return m_switchMode; } } [HideInInspector] public Mesh m_originalMesh = null; [HideInInspector] public EvalMode m_evalMode = EvalMode.ScreenCoverage; [HideInInspector] public bool m_bEnablePrefabUsage = false; [HideInInspector] public string m_strAssetPath = null; [HideInInspector] public float m_fMaxCameraDistance = 1000.0f; [HideInInspector] public int m_nColorEditorBarNewIndex = 0; [HideInInspector] public List m_listLODLevels; [HideInInspector] public AutomaticLOD m_LODObjectRoot; [HideInInspector] public List m_listDependentChildren = new List(); public bool m_bExpandRelevanceSpheres = true; public RelevanceSphere[] m_aRelevanceSpheres = null; [SerializeField] private Simplifier m_meshSimplifier = null; [SerializeField] private bool m_bGenerateIncludeChildren = true; [SerializeField] private LevelsToGenerate m_levelsToGenerate = LevelsToGenerate._3; [SerializeField] private SwitchMode m_switchMode = SwitchMode.UnityLODGroup; [SerializeField] private bool m_bOverrideRootSettings = false; [SerializeField, HideInInspector] private bool m_bLODDataDirty = true; [SerializeField, HideInInspector] private AutomaticLOD m_LODObjectRootPersist = null; // Will persist even if you exclude it from the LOD tree [SerializeField, HideInInspector] private LODGroup m_LODGroup = null; // For SwitchMode == LODGroup private bool m_bUseAutomaticCameraLODSwitch = true; void Awake() { if (m_originalMesh) { MeshFilter meshFilter = GetComponent(); if(meshFilter != null) { meshFilter.sharedMesh = m_originalMesh; } else { SkinnedMeshRenderer skin = GetComponent(); if (skin != null) { skin.sharedMesh = m_originalMesh; } } } m_localCenter = transform.InverseTransformPoint(ComputeWorldCenter()); m_cachedFrameLODLevel = new Dictionary(); // Compute which frames the LOD check takes place, to evenly distribute checks over time and not take much CPU // if we have tens or hundreds of LOD objects b_performCheck = false; m_frameToCheck = -1; if(IsDependent() == false) { // Only do this for root LOD objects m_frameToCheck = s_currentFrameCheckIndex; s_currentCheckIndex = s_currentCheckIndex + 1; if(s_currentCheckIndex >= k_MaxLODChecksPerFrame) { s_currentCheckIndex = 0; s_currentFrameCheckIndex++; if(s_currentFrameCheckIndex >= k_MaxFrameCheckLoop) { s_currentFrameCheckIndex = 0; } s_checkLoopLength = Mathf.Min(s_checkLoopLength + 1, k_MaxFrameCheckLoop); } } s_lastFrameComputedModulus = 0; s_currentFrameInLoop = 0; } void Update() { // See if this frame we need to do the LOD check if(m_bUseAutomaticCameraLODSwitch == false || LODSwitchMode == SwitchMode.UnityLODGroup) { return; } if (Time.frameCount != s_lastFrameComputedModulus && s_checkLoopLength > 0) { s_currentFrameInLoop = Time.frameCount % s_checkLoopLength; s_lastFrameComputedModulus = Time.frameCount; } if(IsDependent() == false) { if(s_currentFrameInLoop == m_frameToCheck) { b_performCheck = true; m_cachedFrameLODLevel.Clear(); } else { b_performCheck = false; } } #if USE_WILLRENDEROBJECT_WORKAROUND Camera renderCamera = s_userDefinedCamera != null ? s_userDefinedCamera : m_renderCamera != null ? m_renderCamera : Camera.main; if (IsDependent()) { bool bCheck = m_LODObjectRootPersist != null ? m_LODObjectRootPersist.b_performCheck : m_LODObjectRoot.b_performCheck; if (bCheck) { // Dependent object renderCamera = s_userDefinedCamera != null ? s_userDefinedCamera : (m_LODObjectRootPersist != null && m_LODObjectRootPersist.m_renderCamera != null) ? m_LODObjectRootPersist.m_renderCamera : (m_LODObjectRoot != null && m_LODObjectRoot.m_renderCamera != null) ? m_LODObjectRoot.m_renderCamera : (m_renderCamera != null ? m_renderCamera : Camera.main); if (bCheck && renderCamera != null) { int nLODLevel = m_LODObjectRootPersist != null ? m_LODObjectRootPersist.GetLODLevelUsingCamera(renderCamera) : m_LODObjectRoot.GetLODLevelUsingCamera(renderCamera); if (nLODLevel != -1) { SwitchToLOD(nLODLevel, false); } } } } else { if (m_originalMesh && b_performCheck && renderCamera) { // Root object with mesh data int nLODLevel = GetLODLevelUsingCamera(renderCamera); if (nLODLevel != -1) { SwitchToLOD(nLODLevel, false); } } } #endif } #if !USE_WILLRENDEROBJECT_WORKAROUND // Needs Unity bugfix: changing meshes during OnWillRenderObject void OnWillRenderObject() { if(m_bUseAutomaticCameraLODSwitch == false || LODSwitchMode == SwitchMode.UnityLODGroup) { return; } #if UNITY_EDITOR for(int i = 0; i < UnityEditor.SceneView.sceneViews.Count; ++i) { UnityEditor.SceneView sceneView = UnityEditor.SceneView.sceneViews[i] as UnityEditor.SceneView; if (Camera.current == sceneView.camera) { return; } } #endif if (IsDependent()) { // Dependent object bool bCheck = m_LODObjectRootPersist != null ? m_LODObjectRootPersist.b_performCheck : m_LODObjectRoot.b_performCheck; if(bCheck) { int nLODLevel = m_LODObjectRootPersist != null ? m_LODObjectRootPersist.GetLODLevelUsingCamera(Camera.current) : m_LODObjectRoot.GetLODLevelUsingCamera(Camera.current); if (nLODLevel != -1) { SwitchToLOD(nLODLevel, false); } } } else { if (m_originalMesh && b_performCheck) { // Root object with mesh data int nLODLevel = GetLODLevelUsingCamera(Camera.current); if (nLODLevel != -1) { SwitchToLOD(nLODLevel, false); } } } } #else void OnWillRenderObject() { #if UNITY_EDITOR // Is it a scene view camera? for (int i = 0; i < UnityEditor.SceneView.sceneViews.Count; ++i) { UnityEditor.SceneView sceneView = UnityEditor.SceneView.sceneViews[i] as UnityEditor.SceneView; if (Camera.current == sceneView.camera) { return; } } if(Camera.current.enabled == false || m_originalMesh == null) { return; } #endif m_renderCamera = Camera.current; } #endif #if UNITY_EDITOR void OnDrawGizmos() { if (m_LODObjectRoot != null) { if (m_LODObjectRoot.m_bExpandRelevanceSpheres == false) { return; } } else { if (m_bExpandRelevanceSpheres == false) { return; } } Gizmos.color = Color.red; RelevanceSphere[] aRelevanceSpheres = m_LODObjectRoot != null ? m_LODObjectRoot.m_aRelevanceSpheres : m_aRelevanceSpheres; if (aRelevanceSpheres == null) { return; } if(aRelevanceSpheres.Length == 0) { return; } bool bDrawVertices = false; for (int i = 0; i < UnityEditor.Selection.gameObjects.Length; i++) { if (((UnityEditor.Selection.gameObjects[i] == this.gameObject) && m_LODObjectRoot == null) || ((m_LODObjectRoot != null) && (UnityEditor.Selection.gameObjects[i] == m_LODObjectRoot.gameObject))) { bDrawVertices = true; } } if (bDrawVertices == false) { return; } Vector3[] aVerticesWorld = Simplifier.GetWorldVertices(this.gameObject); if(aVerticesWorld == null) { return; } Matrix4x4[] aSphereMatrices = new Matrix4x4[aRelevanceSpheres.Length]; for (int nSphere = 0; nSphere < aRelevanceSpheres.Length; nSphere++) { aSphereMatrices[nSphere] = Matrix4x4.TRS(aRelevanceSpheres[nSphere].m_v3Position, Quaternion.Euler(aRelevanceSpheres[nSphere].m_v3Rotation), aRelevanceSpheres[nSphere].m_v3Scale).inverse; } for (int nVertex = 0; nVertex < aVerticesWorld.Length; nVertex++) { for (int nSphere = 0; nSphere < aRelevanceSpheres.Length; nSphere++) { if (aRelevanceSpheres[nSphere].m_bExpanded) { Vector3 v3VertexSphereLocal = aSphereMatrices[nSphere].MultiplyPoint(aVerticesWorld[nVertex]); if (v3VertexSphereLocal.magnitude <= 0.5) { Gizmos.DrawCube(aVerticesWorld[nVertex], Vector3.one * UnityEditor.HandleUtility.GetHandleSize(aVerticesWorld[nVertex]) * 0.05f); break; } } } } } #endif public static bool HasValidMeshData(GameObject go) { MeshFilter meshFilter = go.GetComponent(); if (meshFilter != null) { return true; } else { SkinnedMeshRenderer skin = go.GetComponent(); if (skin != null) { return true; } } return false; } public static bool IsRootOrBelongsToLODTree(AutomaticLOD automaticLOD, AutomaticLOD root) { if(automaticLOD == null) { return false; } return (automaticLOD.m_LODObjectRoot == null) || (automaticLOD.m_LODObjectRoot == root) || (automaticLOD == root) || (automaticLOD.m_LODObjectRoot == root.m_LODObjectRoot); } public int GetNumberOfLevelsToGenerate() { return (int)m_levelsToGenerate; } public bool IsGenerateIncludeChildrenActive() { return m_bGenerateIncludeChildren; } public bool IsRootAutomaticLOD() { return m_LODObjectRoot == null; } public bool HasDependentChildren() { return m_listDependentChildren != null && m_listDependentChildren.Count > 0; } public bool HasLODDataDirty() { return m_bLODDataDirty; } public bool SetLODDataDirty(bool bDirty) { return m_bLODDataDirty = bDirty; } public int GetLODLevelCount() { return m_listLODLevels != null ? m_listLODLevels.Count : 0; } public float ComputeScreenCoverage(Camera camera) { float fMinX = float.MaxValue; float fMinY = float.MaxValue; float fMaxX = float.MinValue; float fMaxY = float.MinValue; if(m_originalMesh) { if(_corners == null) { _corners = new Vector3[8]; } if (_corners.Length != 8) { _corners = new Vector3[8]; } BuildCornerData(ref _corners, m_originalMesh.bounds); for(int i = 0; i < _corners.Length; i++) { Vector3 v3Viewport = camera.WorldToViewportPoint(transform.TransformPoint(_corners[i])); if(v3Viewport.x < fMinX) fMinX = v3Viewport.x; if(v3Viewport.y < fMinY) fMinY = v3Viewport.y; if(v3Viewport.x > fMaxX) fMaxX = v3Viewport.x; if(v3Viewport.y > fMaxY) fMaxY = v3Viewport.y; } } for(int nObject = 0; nObject < m_listDependentChildren.Count; nObject++) { if(m_listDependentChildren[nObject] != null && m_listDependentChildren[nObject].m_originalMesh) { if(m_listDependentChildren[nObject]._corners == null) { m_listDependentChildren[nObject]._corners = new Vector3[8]; } if (m_listDependentChildren[nObject]._corners.Length != 8) { m_listDependentChildren[nObject]._corners = new Vector3[8]; } BuildCornerData(ref m_listDependentChildren[nObject]._corners, m_listDependentChildren[nObject].m_originalMesh.bounds); for(int i = 0; i < m_listDependentChildren[nObject]._corners.Length; i++) { Vector3 v3Viewport = camera.WorldToViewportPoint(m_listDependentChildren[nObject].transform.TransformPoint(m_listDependentChildren[nObject]._corners[i])); if(v3Viewport.x < fMinX) fMinX = v3Viewport.x; if(v3Viewport.y < fMinY) fMinY = v3Viewport.y; if(v3Viewport.x > fMaxX) fMaxX = v3Viewport.x; if(v3Viewport.y > fMaxY) fMaxY = v3Viewport.y; } } } float fScreenCoveragePixels = (fMaxX - fMinX) * (fMaxY - fMinY); return fScreenCoveragePixels; } public Vector3 ComputeWorldCenter() { float fMinX = float.MaxValue; float fMinY = float.MaxValue; float fMinZ = float.MaxValue; float fMaxX = float.MinValue; float fMaxY = float.MinValue; float fMaxZ = float.MinValue; if (m_originalMesh) { for (int i = 0; i < 2; i++) { Vector3 v3World = i == 0 ? GetComponent().bounds.min : GetComponent().bounds.max; if (v3World.x < fMinX) fMinX = v3World.x; if (v3World.y < fMinY) fMinY = v3World.y; if (v3World.z < fMinZ) fMinZ = v3World.z; if (v3World.x > fMaxX) fMaxX = v3World.x; if (v3World.y > fMaxY) fMaxY = v3World.y; if (v3World.z > fMaxZ) fMaxZ = v3World.z; } } for (int nObject = 0; nObject < m_listDependentChildren.Count; nObject++) { if (m_listDependentChildren[nObject] != null && m_listDependentChildren[nObject].m_originalMesh) { for (int i = 0; i < 2; i++) { Vector3 v3World = i == 0 ? m_listDependentChildren[nObject].GetComponent().bounds.min : m_listDependentChildren[nObject].GetComponent().bounds.max; if (v3World.x < fMinX) fMinX = v3World.x; if (v3World.y < fMinY) fMinY = v3World.y; if (v3World.z < fMinZ) fMinZ = v3World.z; if (v3World.x > fMaxX) fMaxX = v3World.x; if (v3World.y > fMaxY) fMaxY = v3World.y; if (v3World.z > fMaxZ) fMaxZ = v3World.z; } } } Vector3 v3Min = new Vector3(fMinX, fMinY, fMinZ); Vector3 v3Max = new Vector3(fMaxX, fMaxY, fMaxZ); return (v3Max + v3Min) * 0.5f; } public float ComputeViewSpaceBounds(Vector3 v3CameraPos, Vector3 v3CameraDir, Vector3 v3CameraUp, out Vector3 v3Min, out Vector3 v3Max, out Vector3 v3Center) { Matrix4x4 mtxView = Matrix4x4.TRS(v3CameraPos, Quaternion.LookRotation(v3CameraDir, v3CameraUp), Vector3.one); float fMinX = float.MaxValue; float fMinY = float.MaxValue; float fMinZ = float.MaxValue; float fMaxX = float.MinValue; float fMaxY = float.MinValue; float fMaxZ = float.MinValue; v3Center = mtxView.inverse.MultiplyPoint(transform.TransformPoint(Vector3.zero)); if (m_originalMesh) { for (int i = 0; i < 2; i++) { Vector3 v3World = i == 0 ? GetComponent().bounds.min : GetComponent().bounds.max; Vector3 v3View = mtxView.inverse.MultiplyPoint(v3World); if (v3View.x < fMinX) fMinX = v3View.x; if (v3View.y < fMinY) fMinY = v3View.y; if (v3View.z < fMinZ) fMinZ = v3View.z; if (v3View.x > fMaxX) fMaxX = v3View.x; if (v3View.y > fMaxY) fMaxY = v3View.y; if (v3View.z > fMaxZ) fMaxZ = v3View.z; } } for (int nObject = 0; nObject < m_listDependentChildren.Count; nObject++) { if (m_listDependentChildren[nObject] != null && m_listDependentChildren[nObject].m_originalMesh) { for (int i = 0; i < 2; i++) { Vector3 v3World = i == 0 ? m_listDependentChildren[nObject].GetComponent().bounds.min : m_listDependentChildren[nObject].GetComponent().bounds.max; Vector3 v3View = mtxView.inverse.MultiplyPoint(v3World); if (v3View.x < fMinX) fMinX = v3View.x; if (v3View.y < fMinY) fMinY = v3View.y; if (v3View.z < fMinZ) fMinZ = v3View.z; if (v3View.x > fMaxX) fMaxX = v3View.x; if (v3View.y > fMaxY) fMaxY = v3View.y; if (v3View.z > fMaxZ) fMaxZ = v3View.z; } } } v3Min = new Vector3(fMinX, fMinY, fMinZ); v3Max = new Vector3(fMaxX, fMaxY, fMaxZ); float fViewSurfaceArea = (fMaxX - fMinX) * (fMaxY - fMinY); return fViewSurfaceArea; } public void SetAutomaticCameraLODSwitch(bool bEnabled) { SetAutomaticCameraLODSwitchRecursive(this, this.gameObject, bEnabled); } private static void SetAutomaticCameraLODSwitchRecursive(AutomaticLOD root, GameObject gameObject, bool bEnabled) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if(automaticLOD != null && IsRootOrBelongsToLODTree(automaticLOD, root)) { automaticLOD.m_bUseAutomaticCameraLODSwitch = bEnabled; } for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { SetAutomaticCameraLODSwitchRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bEnabled); } } public void SetLODLevels(List listLODLevelData, EvalMode evalMode, float fMaxCameraDistance, bool bRecurseIntoChildren) { m_listLODLevels = listLODLevelData; m_fMaxCameraDistance = fMaxCameraDistance; m_nColorEditorBarNewIndex = listLODLevelData.Count; m_evalMode = evalMode; m_LODObjectRoot = null; m_LODObjectRootPersist = null; m_listDependentChildren = new List(); if (bRecurseIntoChildren) { for (int nChild = 0; nChild < transform.childCount; nChild++) { SetLODLevelsRecursive(this, transform.GetChild(nChild).gameObject); } } } private static void SetLODLevelsRecursive(AutomaticLOD root, GameObject gameObject) { AutomaticLOD automaticLOD = gameObject.GetComponent(); bool bProcess = false; if(automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { bProcess = true; } } else { if(HasValidMeshData(gameObject)) { automaticLOD = gameObject.AddComponent(); bProcess = true; } } if(bProcess && automaticLOD) { automaticLOD.m_fMaxCameraDistance = root.m_fMaxCameraDistance; automaticLOD.m_nColorEditorBarNewIndex = root.m_nColorEditorBarNewIndex; automaticLOD.m_evalMode = root.m_evalMode; automaticLOD.m_listLODLevels = new List(); automaticLOD.m_LODObjectRoot = root; automaticLOD.m_LODObjectRootPersist = root; root.m_listDependentChildren.Add(automaticLOD); for (int i = 0; i < root.m_listLODLevels.Count; i++) { automaticLOD.m_listLODLevels.Add(root.m_listLODLevels[i].GetCopy()); automaticLOD.m_listLODLevels[i].m_mesh = CreateNewEmptyMesh(automaticLOD); } } for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { SetLODLevelsRecursive(root, gameObject.transform.GetChild(nChild).gameObject); } if(bProcess && automaticLOD) { for (int i = 0; i < root.m_listLODLevels.Count; i++) { CheckForAdditionalLODSetup(root, automaticLOD, automaticLOD.m_listLODLevels[i], i); } } } public void AddLODLevel(int nLevel, bool bBefore, bool bCreateMesh, bool bRecurseIntoChildren) { AddLODLevelRecursive(this, this.gameObject, nLevel, bBefore, bCreateMesh, bRecurseIntoChildren); } public static void AddLODLevelRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, bool bBefore, bool bCreateMesh, bool bRecurseIntoChildren) { if (Simplifier.Cancelled) { return; } AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { bool bProcess = true; if (automaticLOD.m_listLODLevels == null) { bProcess = false; } else { if (nLevel < 0 || nLevel >= automaticLOD.m_listLODLevels.Count) { bProcess = false; } } if(bProcess) { LODLevelData data = new LODLevelData(); data.m_bUsesOriginalMesh = false; data.m_gameObject = null; if (bBefore) { if (nLevel == 0) { data.m_fScreenCoverage = automaticLOD.m_listLODLevels[0].m_fScreenCoverage; data.m_fMaxCameraDistance = automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance; data.m_fMeshVerticesAmount = 1.0f; data.m_nColorEditorBarIndex = automaticLOD.m_nColorEditorBarNewIndex++; if (automaticLOD.m_listLODLevels.Count > 1) { automaticLOD.m_listLODLevels[0].m_fScreenCoverage = (automaticLOD.m_listLODLevels[0].m_fScreenCoverage + automaticLOD.m_listLODLevels[1].m_fScreenCoverage) / 2.0f; automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance = (automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance + automaticLOD.m_listLODLevels[1].m_fMaxCameraDistance) / 2.0f; } else { automaticLOD.m_listLODLevels[0].m_fScreenCoverage *= 0.5f; automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance *= 2.0f; if(Mathf.Approximately(automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance, 0.0f)) { automaticLOD.m_listLODLevels[0].m_fMaxCameraDistance = automaticLOD.m_fMaxCameraDistance * 0.5f; } } } else { data.m_fScreenCoverage = (automaticLOD.m_listLODLevels[nLevel - 1].m_fScreenCoverage + automaticLOD.m_listLODLevels[nLevel].m_fScreenCoverage) / 2.0f; data.m_fMaxCameraDistance = (automaticLOD.m_listLODLevels[nLevel - 1].m_fMaxCameraDistance + automaticLOD.m_listLODLevels[nLevel].m_fMaxCameraDistance) / 2.0f; data.m_fMeshVerticesAmount = (automaticLOD.m_listLODLevels[nLevel - 1].m_fMeshVerticesAmount + automaticLOD.m_listLODLevels[nLevel].m_fMeshVerticesAmount) / 2.0f; data.m_nColorEditorBarIndex = automaticLOD.m_nColorEditorBarNewIndex++; } if(bCreateMesh) { if (data.m_mesh == null) { data.m_mesh = CreateNewEmptyMesh(automaticLOD); } } automaticLOD.m_listLODLevels.Insert(nLevel, data); if(bCreateMesh) { CheckForAdditionalLODSetup(root, automaticLOD, data, nLevel == 0 ? 0 : nLevel - 1); } } else { int nLastLevel = automaticLOD.m_listLODLevels.Count - 1; if (nLevel == nLastLevel) { data.m_fScreenCoverage = automaticLOD.m_listLODLevels[nLastLevel].m_fScreenCoverage * 0.5f; data.m_fMaxCameraDistance = (automaticLOD.m_listLODLevels[nLastLevel].m_fMaxCameraDistance + automaticLOD.m_fMaxCameraDistance) * 0.5f; data.m_fMeshVerticesAmount = automaticLOD.m_listLODLevels[nLastLevel].m_fMeshVerticesAmount * 0.5f; data.m_nColorEditorBarIndex = automaticLOD.m_nColorEditorBarNewIndex++; } else { data.m_fScreenCoverage = (automaticLOD.m_listLODLevels[nLevel + 1].m_fScreenCoverage + automaticLOD.m_listLODLevels[nLevel].m_fScreenCoverage) / 2.0f; data.m_fMaxCameraDistance = (automaticLOD.m_listLODLevels[nLevel + 1].m_fMaxCameraDistance + automaticLOD.m_listLODLevels[nLevel].m_fMaxCameraDistance) / 2.0f; data.m_fMeshVerticesAmount = (automaticLOD.m_listLODLevels[nLevel + 1].m_fMeshVerticesAmount + automaticLOD.m_listLODLevels[nLevel].m_fMeshVerticesAmount) / 2.0f; data.m_nColorEditorBarIndex = automaticLOD.m_nColorEditorBarNewIndex++; } if (bCreateMesh) { if (data.m_mesh == null) { data.m_mesh = CreateNewEmptyMesh(automaticLOD); } } if (nLevel == nLastLevel) { automaticLOD.m_listLODLevels.Add(data); } else { automaticLOD.m_listLODLevels.Insert(nLevel + 1, data); } if(bCreateMesh) { CheckForAdditionalLODSetup(root, automaticLOD, data, nLevel == nLastLevel ? nLastLevel : nLevel + 1); } } } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { AddLODLevelRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, bBefore, bCreateMesh, bRecurseIntoChildren); if (Simplifier.Cancelled) { return; } } } } public void RemoveLODLevel(int nLevel, bool bDeleteMesh, bool bRecurseIntoChildren) { RemoveLODLevelRecursive(this, this.gameObject, nLevel, bDeleteMesh, bRecurseIntoChildren); } public static void RemoveLODLevelRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, bool bDeleteMesh, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { bool bProcess = true; if (automaticLOD.m_listLODLevels == null) { bProcess = false; } else { if (nLevel < 0 || nLevel >= automaticLOD.m_listLODLevels.Count || automaticLOD.m_listLODLevels.Count == 1) { bProcess = false; } } if (bProcess) { if (bDeleteMesh) { if (automaticLOD.m_listLODLevels[nLevel].m_mesh != null) { automaticLOD.m_listLODLevels[nLevel].m_mesh.Clear(); } if(automaticLOD.m_listLODLevels[nLevel].m_gameObject != null) { if(Application.isEditor && Application.isPlaying == false) { DestroyImmediate(automaticLOD.m_listLODLevels[nLevel].m_gameObject); } else { Destroy(automaticLOD.m_listLODLevels[nLevel].m_gameObject); } } } if (nLevel == 0) { if(automaticLOD.m_listLODLevels.Count > 1) { automaticLOD.m_listLODLevels[1].m_fMaxCameraDistance = 0.0f; automaticLOD.m_listLODLevels[1].m_fScreenCoverage = 1.0f; } } automaticLOD.m_listLODLevels.RemoveAt(nLevel); } for(int i = 0; i < automaticLOD.m_listLODLevels.Count; i++) { if(automaticLOD.m_listLODLevels[i].m_gameObject != null) { automaticLOD.m_listLODLevels[i].m_gameObject.name = "LOD" + i; } } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { RemoveLODLevelRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, bDeleteMesh, bRecurseIntoChildren); } } } public Simplifier GetMeshSimplifier() { return m_meshSimplifier; } public void ComputeLODData(bool bRecurseIntoChildren, Simplifier.ProgressDelegate progress = null) { ComputeLODDataRecursive(this, this.gameObject, bRecurseIntoChildren, progress); } private void ComputeLODDataRecursive(AutomaticLOD root, GameObject gameObject, bool bRecurseIntoChildren, Simplifier.ProgressDelegate progress = null) { if (Simplifier.Cancelled) { return; } AutomaticLOD automaticLOD = gameObject.GetComponent(); if(automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { automaticLOD.FreeLODData(false); MeshFilter meshFilter = automaticLOD.GetComponent(); if (meshFilter != null) { if (automaticLOD.m_originalMesh == null) { automaticLOD.m_originalMesh = meshFilter.sharedMesh; } Simplifier[] simplifiers = automaticLOD.GetComponents(); for (int c = 0; c < simplifiers.Length; c++) { if (Application.isEditor && Application.isPlaying == false) { DestroyImmediate(simplifiers[c]); } else { Destroy(simplifiers[c]); } } automaticLOD.m_meshSimplifier = automaticLOD.gameObject.AddComponent(); automaticLOD.m_meshSimplifier.hideFlags = HideFlags.HideInInspector; IEnumerator enumerator = automaticLOD.m_meshSimplifier.ProgressiveMesh(gameObject, automaticLOD.m_originalMesh, root.m_aRelevanceSpheres, automaticLOD.name, progress); while (enumerator.MoveNext()) { if (Simplifier.Cancelled) { return; } } if (Simplifier.Cancelled) { return; } } else { SkinnedMeshRenderer skin = automaticLOD.GetComponent(); if (skin != null) { if (automaticLOD.m_originalMesh == null) { automaticLOD.m_originalMesh = skin.sharedMesh; } Simplifier[] simplifiers = automaticLOD.GetComponents(); for (int c = 0; c < simplifiers.Length; c++) { if (Application.isEditor && Application.isPlaying == false) { DestroyImmediate(simplifiers[c]); } else { Destroy(simplifiers[c]); } } automaticLOD.m_meshSimplifier = automaticLOD.gameObject.AddComponent(); automaticLOD.m_meshSimplifier.hideFlags = HideFlags.HideInInspector; IEnumerator enumerator = automaticLOD.m_meshSimplifier.ProgressiveMesh(gameObject, automaticLOD.m_originalMesh, root.m_aRelevanceSpheres, automaticLOD.name, progress); while(enumerator.MoveNext()) { if(Simplifier.Cancelled) { return; } } if (Simplifier.Cancelled) { return; } } } } for (int i = 0; i < automaticLOD.m_listLODLevels.Count; i++) { automaticLOD.m_listLODLevels[i].m_mesh = null; } automaticLOD.m_bLODDataDirty = false; } if(bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount && Simplifier.Cancelled == false; nChild++) { ComputeLODDataRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bRecurseIntoChildren, progress); if (Simplifier.Cancelled) { return; } } } } public bool HasLODData() { return m_meshSimplifier != null && m_listLODLevels != null && m_listLODLevels.Count > 0; } public int GetLODLevelUsingCamera(Camera currentCamera) { // Only compute it once per camera and frame if (m_cachedFrameLODLevel.ContainsKey(currentCamera)) { return m_cachedFrameLODLevel[currentCamera]; } if (m_listLODLevels == null || m_listLODLevels.Count == 0) { return -1; } float fDistanceToCamera = 0.0f; float fScreenCoverage = 0.0f; if (m_evalMode == EvalMode.CameraDistance) { Vector3 v3WorldCenter = transform.TransformPoint(m_localCenter.x, m_localCenter.y, m_localCenter.z); fDistanceToCamera = Vector3.Distance(v3WorldCenter, currentCamera.transform.position); } else if(m_evalMode == EvalMode.ScreenCoverage) { fScreenCoverage = ComputeScreenCoverage(currentCamera); } int nLODLevel = 0; for (nLODLevel = 0; nLODLevel < m_listLODLevels.Count; nLODLevel++) { if (nLODLevel == m_listLODLevels.Count - 1) { break; } if (m_evalMode == EvalMode.CameraDistance) { if (fDistanceToCamera < m_listLODLevels[nLODLevel + 1].m_fMaxCameraDistance) { break; } } else if (m_evalMode == EvalMode.ScreenCoverage) { if (fScreenCoverage > m_listLODLevels[nLODLevel + 1].m_fScreenCoverage) { break; } } } // Set the cached value for this frame m_cachedFrameLODLevel.Add(currentCamera, nLODLevel); return nLODLevel; } public int GetCurrentLODLevel() { return m_nCurrentLOD; } public void SwitchToLOD(int nLevel, bool bRecurseIntoChildren) { if(m_LODGroup != null) { m_LODGroup.ForceLOD(nLevel); } else { SwitchToLODRecursive(this, this.gameObject, nLevel, bRecurseIntoChildren); } } private static void SwitchToLODRecursive(AutomaticLOD root, GameObject gameObject, int nLODLevel, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (nLODLevel >= 0 && nLODLevel < automaticLOD.m_listLODLevels.Count && automaticLOD.m_nCurrentLOD != nLODLevel) { if (automaticLOD.LODSwitchMode == SwitchMode.SwitchMesh) { MeshFilter meshFilter = gameObject.GetComponent(); if (meshFilter != null) { Mesh newMesh = automaticLOD.m_listLODLevels[nLODLevel].m_bUsesOriginalMesh ? automaticLOD.m_originalMesh : automaticLOD.m_listLODLevels[nLODLevel].m_mesh; if (meshFilter.sharedMesh != newMesh) { meshFilter.sharedMesh = newMesh; } } else { SkinnedMeshRenderer skin = gameObject.GetComponent(); if (skin != null) { Mesh newMesh = automaticLOD.m_listLODLevels[nLODLevel].m_bUsesOriginalMesh ? automaticLOD.m_originalMesh : automaticLOD.m_listLODLevels[nLODLevel].m_mesh; if (skin.sharedMesh != newMesh) { if (newMesh != null && newMesh.vertexCount == 0) { // Avoid editor warning of mesh not having skinning data if (skin.sharedMesh != null) { skin.sharedMesh = null; } } else { skin.sharedMesh = newMesh; } } } } } else if(automaticLOD.LODSwitchMode == SwitchMode.SwitchGameObject) { if (gameObject.GetComponent() != null) { gameObject.GetComponent().enabled = automaticLOD.m_listLODLevels[nLODLevel].m_bUsesOriginalMesh; } for (int i = 0; i < automaticLOD.m_listLODLevels.Count; i++) { if(automaticLOD.m_listLODLevels[i].m_gameObject != null) { automaticLOD.m_listLODLevels[i].m_gameObject.SetActive(automaticLOD.m_listLODLevels[nLODLevel].m_bUsesOriginalMesh == false && i == nLODLevel && automaticLOD.gameObject.activeSelf); } } } else if(automaticLOD.LODSwitchMode == SwitchMode.UnityLODGroup) { return; } automaticLOD.m_nCurrentLOD = nLODLevel; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { SwitchToLODRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLODLevel, true); } } } public void ComputeAllLODMeshes(bool bRecurseIntoChildren, Simplifier.ProgressDelegate progress = null) { if (m_listLODLevels != null) { for(int i = 0; i < m_listLODLevels.Count; i++) { ComputeLODMeshRecursive(this, this.gameObject, i, bRecurseIntoChildren, progress); if (Simplifier.Cancelled) { return; } } SetupLODGroup(); } } public void ComputeLODMesh(int nLevel, bool bRecurseIntoChildren, Simplifier.ProgressDelegate progress = null) { ComputeLODMeshRecursive(this, this.gameObject, nLevel, bRecurseIntoChildren, progress); SetupLODGroup(); } private static void ComputeLODMeshRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, bool bRecurseIntoChildren, Simplifier.ProgressDelegate progress = null) { if (Simplifier.Cancelled) { return; } AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_meshSimplifier != null) { if (automaticLOD.m_listLODLevels[nLevel].m_mesh) { automaticLOD.m_listLODLevels[nLevel].m_mesh.Clear(); } float fAmount = automaticLOD.m_listLODLevels[nLevel].m_fMeshVerticesAmount; if (automaticLOD.m_bOverrideRootSettings == false && automaticLOD.m_LODObjectRoot != null) { fAmount = automaticLOD.m_LODObjectRoot.m_listLODLevels[nLevel].m_fMeshVerticesAmount; } if (automaticLOD.m_listLODLevels[nLevel].m_mesh == null) { automaticLOD.m_listLODLevels[nLevel].m_mesh = CreateNewEmptyMesh(automaticLOD); } CheckForAdditionalLODSetup(root, automaticLOD, automaticLOD.m_listLODLevels[nLevel], nLevel); int nVertexCount = Mathf.RoundToInt(fAmount * automaticLOD.m_meshSimplifier.GetOriginalMeshUniqueVertexCount()); if(nVertexCount < automaticLOD.m_meshSimplifier.GetOriginalMeshUniqueVertexCount()) { automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh = false; IEnumerator enumerator = automaticLOD.m_meshSimplifier.ComputeMeshWithVertexCount(gameObject, automaticLOD.m_listLODLevels[nLevel].m_mesh, nVertexCount, automaticLOD.name + " LOD " + nLevel, progress); while (enumerator.MoveNext()) { if (Simplifier.Cancelled) { return; } } if (Simplifier.Cancelled) { return; } } else { automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh = true; if (automaticLOD.m_listLODLevels[nLevel].m_gameObject != null && automaticLOD.m_listLODLevels[nLevel].m_gameObject.GetComponent() != null) { automaticLOD.m_listLODLevels[nLevel].m_gameObject.GetComponent().enabled = !automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh; } } } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { ComputeLODMeshRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, bRecurseIntoChildren, progress); if (Simplifier.Cancelled) { return; } } } } public void RestoreOriginalMesh(bool bDeleteLODData, bool bRecurseIntoChildren) { if(m_LODGroup != null) { m_LODGroup.ForceLOD(-1); } RestoreOriginalMeshRecursive(this, this.gameObject, bDeleteLODData, bRecurseIntoChildren); } private static void RestoreOriginalMeshRecursive(AutomaticLOD root, GameObject gameObject, bool bDeleteLODData, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.LODSwitchMode != SwitchMode.SwitchGameObject) { if (automaticLOD.m_originalMesh != null) { MeshFilter meshFilter = automaticLOD.GetComponent(); if (meshFilter != null) { meshFilter.sharedMesh = automaticLOD.m_originalMesh; } else { SkinnedMeshRenderer skin = automaticLOD.GetComponent(); if (skin != null) { skin.sharedMesh = automaticLOD.m_originalMesh; } } } } automaticLOD.m_nCurrentLOD = -1; if(automaticLOD.LODSwitchMode == SwitchMode.SwitchGameObject) { if (gameObject.GetComponent() != null) { gameObject.GetComponent().enabled = true; } for(int i = 0; i < automaticLOD.m_listLODLevels.Count; i++) { if(automaticLOD.m_listLODLevels[i].m_gameObject != null) { automaticLOD.m_listLODLevels[i].m_gameObject.SetActive(false); } } } if(bDeleteLODData) { automaticLOD.FreeLODData(false); automaticLOD.m_listLODLevels.Clear(); automaticLOD.m_listLODLevels = null; automaticLOD.m_listDependentChildren.Clear(); } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { RestoreOriginalMeshRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bDeleteLODData, bRecurseIntoChildren); } } } public bool HasOriginalMeshActive(bool bRecurseIntoChildren) { return HasOriginalMeshActiveRecursive(this, this.gameObject, bRecurseIntoChildren); } private static bool HasOriginalMeshActiveRecursive(AutomaticLOD root, GameObject gameObject, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); bool bHasOriginalMeshActive = false; if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_originalMesh != null) { MeshFilter meshFilter = automaticLOD.GetComponent(); if (meshFilter != null) { if(meshFilter.sharedMesh == automaticLOD.m_originalMesh) { bHasOriginalMeshActive = true; } } else { SkinnedMeshRenderer skin = automaticLOD.GetComponent(); if (skin != null) { if(skin.sharedMesh == automaticLOD.m_originalMesh) { bHasOriginalMeshActive = true; } } } } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { bHasOriginalMeshActive = bHasOriginalMeshActive || HasOriginalMeshActiveRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bRecurseIntoChildren); } } return bHasOriginalMeshActive; } public bool HasVertexData(int nLevel, bool bRecurseIntoChildren) { return HasVertexDataRecursive(this, this.gameObject, nLevel, bRecurseIntoChildren); } private static bool HasVertexDataRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh) { if(automaticLOD.m_originalMesh && automaticLOD.m_originalMesh.vertexCount > 0) { return true; } } else if (automaticLOD.m_listLODLevels[nLevel].m_mesh && automaticLOD.m_listLODLevels[nLevel].m_mesh.vertexCount > 0) { return true; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { if (HasVertexDataRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, bRecurseIntoChildren)) { return true; } } } return false; } public int GetOriginalVertexCount(bool bRecurseIntoChildren) { int nVertexCount = 0; GetOriginalVertexCountRecursive(this, this.gameObject, ref nVertexCount, bRecurseIntoChildren); return nVertexCount; } private static void GetOriginalVertexCountRecursive(AutomaticLOD root, GameObject gameObject, ref int nVertexCount, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_originalMesh != null) { nVertexCount += automaticLOD.m_originalMesh.vertexCount; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { GetOriginalVertexCountRecursive(root, gameObject.transform.GetChild(nChild).gameObject, ref nVertexCount, bRecurseIntoChildren); } } } public int GetOriginalTriangleCount(bool bRecurseIntoChildren) { int nTriangleCount = 0; GetOriginalTriangleCountRecursive(this, this.gameObject, ref nTriangleCount, bRecurseIntoChildren); return nTriangleCount; } private static void GetOriginalTriangleCountRecursive(AutomaticLOD root, GameObject gameObject, ref int nTriangleCount, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_originalMesh != null) { nTriangleCount += automaticLOD.m_originalMesh.triangles.Length / 3; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { GetOriginalTriangleCountRecursive(root, gameObject.transform.GetChild(nChild).gameObject, ref nTriangleCount, bRecurseIntoChildren); } } } public int GetCurrentVertexCount(bool bRecurseIntoChildren) { int nVertexCount = 0; GetCurrentVertexCountRecursive(this, this.gameObject, ref nVertexCount, bRecurseIntoChildren); return nVertexCount; } private static void GetCurrentVertexCountRecursive(AutomaticLOD root, GameObject gameObject, ref int nVertexCount, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { MeshFilter meshFilter = gameObject.GetComponent(); if(meshFilter != null && meshFilter.sharedMesh != null) { nVertexCount += meshFilter.sharedMesh.vertexCount; } else { SkinnedMeshRenderer skin = gameObject.GetComponent(); if (skin != null && skin.sharedMesh != null) { nVertexCount += skin.sharedMesh.vertexCount; } } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { GetCurrentVertexCountRecursive(root, gameObject.transform.GetChild(nChild).gameObject, ref nVertexCount, bRecurseIntoChildren); } } } public int GetLODVertexCount(int nLevel, bool bRecurseIntoChildren) { int nVertexCount = 0; GetLODVertexCountRecursive(this, this.gameObject, nLevel, ref nVertexCount, bRecurseIntoChildren); return nVertexCount; } private static void GetLODVertexCountRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, ref int nVertexCount, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if(automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh && automaticLOD.m_originalMesh != null) { nVertexCount += automaticLOD.m_originalMesh.vertexCount; } else if (automaticLOD.m_listLODLevels[nLevel].m_mesh != null) { nVertexCount += automaticLOD.m_listLODLevels[nLevel].m_mesh.vertexCount; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { GetLODVertexCountRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, ref nVertexCount, bRecurseIntoChildren); } } } public int GetLODTriangleCount(int nLevel, bool bRecurseIntoChildren) { int nTriangleCount = 0; GetLODTriangleCountRecursive(this, this.gameObject, nLevel, ref nTriangleCount, bRecurseIntoChildren); return nTriangleCount; } private static void GetLODTriangleCountRecursive(AutomaticLOD root, GameObject gameObject, int nLevel, ref int nTriangleCount, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_listLODLevels[nLevel].m_bUsesOriginalMesh && automaticLOD.m_originalMesh != null) { nTriangleCount += automaticLOD.m_originalMesh.triangles.Length / 3; } else if (automaticLOD.m_listLODLevels[nLevel].m_mesh != null) { nTriangleCount += automaticLOD.m_listLODLevels[nLevel].m_mesh.triangles.Length / 3; } } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { GetLODTriangleCountRecursive(root, gameObject.transform.GetChild(nChild).gameObject, nLevel, ref nTriangleCount, bRecurseIntoChildren); } } } public void RemoveFromLODTree() { if (m_LODObjectRoot != null) { m_LODObjectRoot.m_listDependentChildren.Remove(this); } RestoreOriginalMesh(true, false); } public void FreeLODData(bool bRecurseIntoChildren) { FreeLODDataRecursive(this, this.gameObject, bRecurseIntoChildren); } private static void FreeLODDataRecursive(AutomaticLOD root, GameObject gameObject, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_listLODLevels != null) { foreach (LODLevelData data in automaticLOD.m_listLODLevels) { if (data.m_mesh) { data.m_mesh.Clear(); } if(data.m_gameObject != null) { if(Application.isEditor && Application.isPlaying == false) { DestroyImmediate(data.m_gameObject); } else { Destroy(data.m_gameObject); } } data.m_bUsesOriginalMesh = false; } } Simplifier[] simplifiers = automaticLOD.GetComponents(); for (int c = 0; c < simplifiers.Length; c++) { if (Application.isEditor && Application.isPlaying == false) { DestroyImmediate(simplifiers[c]); } else { Destroy(simplifiers[c]); } } if (automaticLOD.m_meshSimplifier != null) { automaticLOD.m_meshSimplifier = null; } if(automaticLOD.m_LODGroup != null) { if (Application.isEditor && Application.isPlaying == false) { DestroyImmediate(automaticLOD.m_LODGroup); } else { Destroy(automaticLOD.m_LODGroup); } } automaticLOD.m_bLODDataDirty = true; } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { FreeLODDataRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bRecurseIntoChildren); } } } private static Mesh CreateNewEmptyMesh(AutomaticLOD automaticLOD) { if(automaticLOD.m_originalMesh == null) { return new Mesh(); } Mesh meshOut = Mesh.Instantiate(automaticLOD.m_originalMesh); meshOut.Clear(); return meshOut; } private static GameObject CreateBasicObjectCopy(GameObject gameObject, Mesh mesh, Transform parent) { GameObject newGameObject = new GameObject(); newGameObject.layer = gameObject.layer; newGameObject.isStatic = gameObject.isStatic; newGameObject.tag = gameObject.tag; newGameObject.transform.parent = parent; newGameObject.transform.localPosition = Vector3.zero; newGameObject.transform.localRotation = Quaternion.identity; newGameObject.transform.localScale = Vector3.one; Component[] aComponents = gameObject.GetComponents(); for(int i = 0; i < aComponents.Length; i++) { Component c = aComponents[i]; if(c.GetType() == typeof(MeshRenderer) || c.GetType() == typeof(MeshFilter) || c.GetType() == typeof(SkinnedMeshRenderer)) { CopyComponent(c, newGameObject); } } MeshFilter meshFilter = newGameObject.GetComponent(); if(meshFilter != null) { meshFilter.sharedMesh = mesh; } else { SkinnedMeshRenderer skin = newGameObject.GetComponent(); if(skin != null) { skin.sharedMesh = mesh; } } if(newGameObject.GetComponent() != null) { newGameObject.GetComponent().enabled = true; } return newGameObject; } private static void CheckForAdditionalLODSetup(AutomaticLOD root, AutomaticLOD automaticLOD, LODLevelData levelData, int level) { if(automaticLOD.LODSwitchMode == SwitchMode.SwitchGameObject || automaticLOD.LODSwitchMode == SwitchMode.UnityLODGroup) { if(levelData.m_gameObject != null) { if(Application.isEditor && Application.isPlaying == false) { DestroyImmediate(levelData.m_gameObject); } else { Destroy(levelData.m_gameObject); } } levelData.m_gameObject = CreateBasicObjectCopy(automaticLOD.gameObject, levelData.m_mesh, automaticLOD.gameObject.transform); levelData.m_gameObject.SetActive(automaticLOD.LODSwitchMode == SwitchMode.UnityLODGroup); for(int i = 0; i < automaticLOD.m_listLODLevels.Count; i++) { if(automaticLOD.m_listLODLevels[i].m_gameObject != null) { automaticLOD.m_listLODLevels[i].m_gameObject.name = "LOD" + i; automaticLOD.m_listLODLevels[i].m_gameObject.transform.SetSiblingIndex(i); } } } else { levelData.m_gameObject = null; } } public void SetupLODGroup() { AutomaticLOD rootAutomaticLOD = m_LODObjectRoot == null ? this : m_LODObjectRoot; if (rootAutomaticLOD != null && rootAutomaticLOD.m_switchMode == SwitchMode.UnityLODGroup) { LOD[] lods; if (rootAutomaticLOD.m_LODGroup == null) { rootAutomaticLOD.m_LODGroup = rootAutomaticLOD.gameObject.GetComponent(); if (rootAutomaticLOD.m_LODGroup == null) { rootAutomaticLOD.m_LODGroup = rootAutomaticLOD.gameObject.AddComponent(); } } lods = rootAutomaticLOD.m_LODGroup.GetLODs(); List> renderers = new List>(); for(int i = 0; i < rootAutomaticLOD.GetNumberOfLevelsToGenerate(); ++i) { renderers.Add(new List()); } SetupLODGroupRecursive(rootAutomaticLOD, rootAutomaticLOD.gameObject, ref renderers); for(int i = 0; i < renderers.Count; ++i) { lods[i].renderers = renderers[i].ToArray(); } for (int i = renderers.Count; i < lods.Length; ++i) { lods[i].renderers = null; } rootAutomaticLOD.m_LODGroup.SetLODs(lods); rootAutomaticLOD.m_LODGroup.RecalculateBounds(); } } private static void SetupLODGroupRecursive(AutomaticLOD root, GameObject gameObject, ref List> renderers) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_listLODLevels != null) { bool usesOriginalMesh = false; for(int i = 0; i < automaticLOD.m_listLODLevels.Count; ++i) { LODLevelData data = automaticLOD.m_listLODLevels[i]; Renderer renderer = data.m_bUsesOriginalMesh ? gameObject.GetComponent() : (data.m_gameObject != null ? data.m_gameObject.GetComponent() : null); if (renderer != null && renderers[i].Contains(renderer) == false) { renderers[i].Add(renderer); } if(data.m_bUsesOriginalMesh) { usesOriginalMesh = true; } } Renderer originalRenderer = gameObject.GetComponent(); if (originalRenderer != null) { originalRenderer.enabled = usesOriginalMesh; } } } } for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { SetupLODGroupRecursive(root, gameObject.transform.GetChild(nChild).gameObject, ref renderers); } } static private Component CopyComponent(Component original, GameObject destination) { #if UNITY_EDITOR System.Type type = original.GetType(); var dst = destination.GetComponent(type); if(!dst) { dst = destination.AddComponent(type); } var fields = type.GetFields(); foreach(var field in fields) { if(field.IsStatic) { continue; } field.SetValue(dst, field.GetValue(original)); } var props = type.GetProperties(); foreach(var prop in props) { if(!prop.CanWrite || !prop.CanWrite || prop.Name == "mesh" || prop.Name == "sharedMesh" || prop.Name == "material" || prop.Name == "materials") { continue; } prop.SetValue(dst, prop.GetValue(original, null), null); } return dst; #else return null; #endif } #if UNITY_EDITOR public void DisablePrefabUsage(bool bRecurseIntoChildren) { DisablePrefabUsageRecursive(this, this.gameObject, bRecurseIntoChildren); } private static void DisablePrefabUsageRecursive(AutomaticLOD root, GameObject gameObject, bool bRecurseIntoChildren) { AutomaticLOD automaticLOD = gameObject.GetComponent(); if (automaticLOD != null) { if (IsRootOrBelongsToLODTree(automaticLOD, root)) { if (automaticLOD.m_listLODLevels != null) { foreach (LODLevelData data in automaticLOD.m_listLODLevels) { if (data.m_mesh) { if(UnityEditor.AssetDatabase.IsMainAsset(data.m_mesh) || UnityEditor.AssetDatabase.IsSubAsset(data.m_mesh)) { Mesh newMesh = Instantiate(data.m_mesh) as Mesh; data.m_mesh = newMesh; } } } } automaticLOD.m_strAssetPath = null; } } if (bRecurseIntoChildren) { for (int nChild = 0; nChild < gameObject.transform.childCount; nChild++) { DisablePrefabUsageRecursive(root, gameObject.transform.GetChild(nChild).gameObject, bRecurseIntoChildren); } } } #endif private void BuildCornerData(ref Vector3[] av3Corners, Bounds bounds) { av3Corners[0].x = bounds.min.x; av3Corners[0].y = bounds.min.y; av3Corners[0].z = bounds.min.z; av3Corners[1].x = bounds.min.x; av3Corners[1].y = bounds.min.y; av3Corners[1].z = bounds.max.z; av3Corners[2].x = bounds.min.x; av3Corners[2].y = bounds.max.y; av3Corners[2].z = bounds.min.z; av3Corners[3].x = bounds.min.x; av3Corners[3].y = bounds.max.y; av3Corners[3].z = bounds.max.z; av3Corners[4].x = bounds.max.x; av3Corners[4].y = bounds.min.y; av3Corners[4].z = bounds.min.z; av3Corners[5].x = bounds.max.x; av3Corners[5].y = bounds.min.y; av3Corners[5].z = bounds.max.z; av3Corners[6].x = bounds.max.x; av3Corners[6].y = bounds.max.y; av3Corners[6].z = bounds.min.z; av3Corners[7].x = bounds.max.x; av3Corners[7].y = bounds.max.y; av3Corners[7].z = bounds.max.z; } private bool IsDependent() { return m_LODObjectRoot != null || m_LODObjectRootPersist != null; } // Optimization to distribute LOD checks over time if we have tens or hundreds of LOD objects private const int k_MaxLODChecksPerFrame = 4; private const int k_MaxFrameCheckLoop = 100; private static int s_currentCheckIndex = 0; private static int s_currentFrameCheckIndex = 0; private static int s_checkLoopLength = 0; private static int s_lastFrameComputedModulus = -1; private static int s_currentFrameInLoop = -1; // User defined camera to override the one we use for LOD activation private static Camera s_userDefinedCamera = null; // Instance variables private Camera m_renderCamera = null; private int m_nCurrentLOD = -1; private Dictionary m_cachedFrameLODLevel; private Vector3 m_localCenter; private Vector3[] _corners = null; private int m_frameToCheck = 0; private bool b_performCheck = false; }