├── .gitignore ├── Runtime ├── AssemblyInfo.cs ├── Extensions.meta ├── Helpers.meta ├── Interfaces.meta ├── Shaders.meta ├── Unity.AutoLOD.asmdef.meta ├── Utilities.meta ├── Shaders │ ├── SimpleBatcher.shader.meta │ └── SimpleBatcher.shader ├── Helpers │ ├── MeshLOD.cs.meta │ ├── MonoBehaviourHelper.cs.meta │ ├── RequiresLayerAttribute.cs │ ├── WorkingMesh.cs.meta │ ├── TimedEnumerator.cs.meta │ ├── RequiresLayerAttribute.cs.meta │ ├── RequiresTagAttribute.cs.meta │ ├── OptionalDependencyAttribute.cs.meta │ ├── RequiresTagAttribute.cs │ ├── OptionalDependencyAttribute.cs │ ├── MeshLOD.cs │ ├── TimedEnumerator.cs │ ├── MonoBehaviourHelper.cs │ └── WorkingMesh.cs ├── LODVolume.cs.meta ├── Extensions │ ├── IEnumeratorExtensions.cs │ ├── IEnumeratorExtensions.cs.meta │ ├── LODGroupExtensions.cs.meta │ └── LODGroupExtensions.cs ├── Interfaces │ ├── IBatcher.cs.meta │ ├── IPreferences.cs.meta │ ├── IMeshSimplifier.cs.meta │ ├── IPreferences.cs │ ├── IBatcher.cs │ └── IMeshSimplifier.cs ├── AssemblyInfo.cs.meta ├── Utilities │ ├── ObjectUtils.cs.meta │ └── ObjectUtils.cs ├── Unity.AutoLOD.asmdef └── LODVolume.cs ├── CHANGELOG.md.meta ├── package.json.meta ├── Runtime.meta ├── Editor ├── Helpers.meta ├── MeshSimplifiers │ ├── Simplygon.meta │ ├── Simplygon │ │ ├── Unity.AutoLOD.Editor.Simplygon.asmdef.meta │ │ ├── SimplygonMeshSimplifier.cs.meta │ │ ├── Unity.AutoLOD.Editor.Simplygon.asmdef │ │ └── SimplygonMeshSimplifier.cs │ ├── QuadricMeshSimplifier.cs.meta │ ├── SimulatedMeshSimplifier.cs.meta │ ├── InstaLODMeshSimplifier.cs.meta │ ├── InstaLODMeshSimplifier.cs │ ├── SimulatedMeshSimplifier.cs │ └── QuadricMeshSimplifier.cs ├── Batchers.meta ├── MeshSimplifiers.meta ├── Unity.AutoLOD.Editor.asmdef.meta ├── AutoLOD.cs.meta ├── GridPlacementUtility.cs.meta ├── LODData.cs.meta ├── SceneLOD.cs.meta ├── LODDataEditor.cs.meta ├── TagManager.cs.meta ├── TextureAtlas.cs.meta ├── LODImportSettings.cs.meta ├── TextureAtlas.cs ├── TextureAtlasModule.cs.meta ├── Batchers │ ├── SimpleBatcher.cs.meta │ ├── MaterialPreservingBatcher.cs.meta │ ├── MaterialPreservingBatcher.cs │ └── SimpleBatcher.cs ├── LODImportSettingsDrawer.cs.meta ├── Helpers │ ├── ProfileAnimation.cs.meta │ └── ProfileAnimation.cs ├── LODImportSettings.cs ├── ModelImporterLODGenerator.cs.meta ├── Unity.AutoLOD.Editor.asmdef ├── LODData.cs ├── LODImportSettingsDrawer.cs ├── TextureAtlasModule.cs ├── LODDataEditor.cs ├── TagManager.cs ├── GridPlacementUtility.cs ├── ModelImporterLODGenerator.cs └── SceneLOD.cs ├── Editor.meta ├── LICENSE.meta ├── README.md.meta ├── LICENSE ├── package.json ├── CHANGELOG.md ├── .gitattributes └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /Generated* 2 | -------------------------------------------------------------------------------- /Runtime/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Unity.AutoLOD.Editor")] -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5af2bc35b5b4ca4e94f428655f2447e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07638e4f8d5370d4cb709121dd1daa1a 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd116cf9a374a6245b0ca348f8002c58 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Helpers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45d5d1fca3645ec468dbb5de17ddd796 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/Simplygon.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 101213ffbe5a97d4ca5e7ecb05df1983 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 88d7e24f96e246e429af69ae050eea91 3 | folderAsset: yes 4 | timeCreated: 1462802844 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5b43fbbf79161e42b5091bd4bde0812 3 | timeCreated: 1515567475 4 | licenseType: Pro 5 | DefaultImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad0ff3d2bc11c4e40b30a87f2514283b 3 | timeCreated: 1515567475 4 | licenseType: Pro 5 | TextScriptImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/Simplygon/Unity.AutoLOD.Editor.Simplygon.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 97faba6440a10904e87153778a1fea59 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Batchers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 707cce36264dcae4b8d2cf8c0b97ec64 3 | folderAsset: yes 4 | timeCreated: 1508522805 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Extensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 998d806ec01d2cc43964b28f5c2720b4 3 | folderAsset: yes 4 | timeCreated: 1505416682 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Helpers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e6794de137081844a1e2e8c74c7bdd5 3 | folderAsset: yes 4 | timeCreated: 1507575992 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Interfaces.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67ac06348aaa9384987cf9f80e5c86e6 3 | folderAsset: yes 4 | timeCreated: 1508522777 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Shaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a83363c27a32fa14c974145e7f0ddd61 3 | folderAsset: yes 4 | timeCreated: 1502931758 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Unity.AutoLOD.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26eed724cfea581479b00aaf8bbfa2f3 3 | timeCreated: 1515458120 4 | licenseType: Pro 5 | AssemblyDefinitionImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Runtime/Utilities.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 09680376c4849894a91805e88a6c15e7 3 | folderAsset: yes 4 | timeCreated: 1505949608 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b1f2a654fd490b4bb40341957d21d66 3 | folderAsset: yes 4 | timeCreated: 1508522959 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Editor/Unity.AutoLOD.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35c6e7274632ace409c481af8db3c121 3 | timeCreated: 1515459014 4 | licenseType: Pro 5 | AssemblyDefinitionImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Runtime/Shaders/SimpleBatcher.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7df92906c2f269408fd4c66cc493ae3 3 | timeCreated: 1509145668 4 | licenseType: Pro 5 | ShaderImporter: 6 | externalObjects: {} 7 | defaultTextures: [] 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Runtime/Helpers/MeshLOD.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b38e559e4600f644ca4bb46d55fb9ab3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AutoLOD.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15dd88ae16c8e874ea95998aa36db2b2 3 | timeCreated: 1463063939 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/LODVolume.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f582a16ceb3c9742a2ebce863ca04a8 3 | timeCreated: 1462797761 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/GridPlacementUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26c561be99b335047819a499fbfbe160 3 | timeCreated: 1462912386 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/Extensions/IEnumeratorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace Unity.AutoLOD 4 | { 5 | public static class IEnumeratorExtensions 6 | { 7 | public static void ExecuteCompletely(this IEnumerator enumerator) 8 | { 9 | while (enumerator.MoveNext()) ; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IBatcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a01d0cd6a09a1634f82d298338fe16d8 3 | timeCreated: 1462976162 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IPreferences.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 04006abb58ee28a4597decea5d2af0b4 3 | timeCreated: 1462976162 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/Helpers/MonoBehaviourHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79807ece484b0134e8b63635baa2c449 3 | timeCreated: 1460591574 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IMeshSimplifier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8fc9a723cf4be24bb2e3c8b4ee81b81 3 | timeCreated: 1462976162 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/LODData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e927627221206e04dba83232f13d1949 3 | timeCreated: 1508523305 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/SceneLOD.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43d209e41f42c2d49a38d5324316a439 3 | timeCreated: 1506550058 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/RequiresLayerAttribute.cs: -------------------------------------------------------------------------------- 1 | // From: https://github.com/Unity-Technologies/EditorVR 2 | using System; 3 | 4 | namespace Unity.AutoLOD 5 | { 6 | sealed class RequiresLayerAttribute : Attribute 7 | { 8 | public string layer; 9 | 10 | public RequiresLayerAttribute(string layer) 11 | { 12 | this.layer = layer; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Editor/LODDataEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe83869f5248b394d839673943a693ee 3 | timeCreated: 1508524272 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/QuadricMeshSimplifier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb37518376344af48948102fb0185c79 3 | timeCreated: 1462976430 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/SimulatedMeshSimplifier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce1496897eb8ac6468e0bd409034f12a 3 | timeCreated: 1462976430 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/TagManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07d3d7fabc3cf4748bcf119a79e56fcd 3 | timeCreated: 1507575828 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/TextureAtlas.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66dc9fb3daab1dc49a560146f353eebc 3 | timeCreated: 1507937763 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AssemblyInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95963fadf18f84442b9ba2a420881cc2 3 | timeCreated: 1507576729 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/LODImportSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c558ef89a6859364597449a053ee8b24 3 | timeCreated: 1508523605 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/TextureAtlas.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Unity.AutoLOD 4 | { 5 | [CreateAssetMenu(fileName = "TextureAtlas", menuName = "AutoLOD/Texture Atlas")] 6 | public class TextureAtlas : ScriptableObject 7 | { 8 | public Texture2D textureAtlas; 9 | public Texture2D[] textures; 10 | public Rect[] uvs; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Editor/TextureAtlasModule.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 850c77e2dfeb69748b1b1ddefbf3a032 3 | timeCreated: 1508175761 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/Batchers/SimpleBatcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd3861774217b694684b9f12414cb95e 3 | timeCreated: 1503352811 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/LODImportSettingsDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e2d5bfbf1bc85d9408bfeff5e560f152 3 | timeCreated: 1508523605 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/WorkingMesh.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45ba66b279be27a4f9ebb0c46e4e830b 3 | timeCreated: 1506025355 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Utilities/ObjectUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 557ede46cf8107244a3e7a6fd1f23e99 3 | timeCreated: 1505949608 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/Helpers/ProfileAnimation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 392192f7e93302c4f86061375caf8c82 3 | timeCreated: 1512760307 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/LODImportSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Unity.AutoLOD 4 | { 5 | [Serializable] 6 | public class LODImportSettings 7 | { 8 | public bool generateOnImport; 9 | public string meshSimplifier; 10 | public string batcher; 11 | public int maxLODGenerated; 12 | public int initialLODMaxPolyCount; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Editor/ModelImporterLODGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a1ea41f51e17b63479d49a614b9c389f 3 | timeCreated: 1502928510 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/TimedEnumerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e836967d92633a248bf7cf9ebe6d581a 3 | timeCreated: 1506556717 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Extensions/IEnumeratorExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1003775d992f7b941abe4495b46adb45 3 | timeCreated: 1507934476 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Extensions/LODGroupExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 493e5e35d9e265f499004a07237f579c 3 | timeCreated: 1504815833 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/RequiresLayerAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 407163f8ad39afc4bbc0c8d0dad6501c 3 | timeCreated: 1507575992 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/RequiresTagAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1488099044ca7d44699b1db1041f98cd 3 | timeCreated: 1507575992 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/Batchers/MaterialPreservingBatcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 191ca6974b9b9424995a6ed76e714a17 3 | timeCreated: 1503352811 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/InstaLODMeshSimplifier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3a83df15799a8fc41bcc11c614762965 3 | timeCreated: 1506536618 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/OptionalDependencyAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0051087bfec815e4d93d39aa7d0ce8f3 3 | timeCreated: 1515451464 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/Simplygon/SimplygonMeshSimplifier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc6d450447299b644bb82aa7c5b59c29 3 | timeCreated: 1512153951 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/Helpers/RequiresTagAttribute.cs: -------------------------------------------------------------------------------- 1 | // From: https://github.com/Unity-Technologies/EditorVR 2 | using System; 3 | 4 | namespace Unity.AutoLOD 5 | { 6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 7 | sealed class RequiresTagAttribute : Attribute 8 | { 9 | public string tag; 10 | 11 | public RequiresTagAttribute(string tag) 12 | { 13 | this.tag = tag; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IPreferences.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.AutoLOD 2 | { 3 | public interface IPreferences 4 | { 5 | /// 6 | /// Callback from AutoLOD preferences GUI to show GUI options related to a simplifier / batcher. 7 | /// Settings should be saved here as well and also utilized by the simplifier / batcher. 8 | /// 9 | void OnPreferencesGUI(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IBatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | namespace Unity.AutoLOD 5 | { 6 | public interface IBatcher 7 | { 8 | /// 9 | /// Combine children renderers of this GameObject (NOTE: Runs as a coroutine) 10 | /// 11 | /// GameObject hierarchy to batch 12 | IEnumerator Batch(GameObject go); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | AutoLOD copyright © 2018 Unity Technologies ApS 2 | 3 | Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). 4 | 5 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. 6 | -------------------------------------------------------------------------------- /Editor/Unity.AutoLOD.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.AutoLOD.Editor", 3 | "references": [ 4 | "Unity.AutoLOD", 5 | "Whinarn.UnityMeshSimplifier.Runtime" 6 | ], 7 | "optionalUnityReferences": [], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [] 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.unity.autolod", 3 | "displayName": "AutoLOD", 4 | "version": "0.0.2", 5 | "unity": "2018.4", 6 | "description": "Automatic LOD generation", 7 | "keywords": [ 8 | "autolod", 9 | "lod", 10 | "3d", 11 | "mesh", 12 | "polygon", 13 | "triangle", 14 | "simplification", 15 | "decimation", 16 | "reduction", 17 | "optimization" 18 | ], 19 | "author": { 20 | "name": "Unity" 21 | }, 22 | "bugs": "https://github.com/Unity-Technologies/AutoLOD/issues" 23 | } -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/Simplygon/Unity.AutoLOD.Editor.Simplygon.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.AutoLOD.Editor.Simplygon", 3 | "references": [ 4 | "Unity.AutoLOD.Editor", 5 | "Unity.AutoLOD", 6 | "Unity.Formats.USD.Runtime" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Runtime/Unity.AutoLOD.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.AutoLOD", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [ 6 | "Editor", 7 | "LinuxStandalone32", 8 | "LinuxStandalone64", 9 | "LinuxStandaloneUniversal", 10 | "macOSStandalone", 11 | "WindowsStandalone32", 12 | "WindowsStandalone64" 13 | ], 14 | "excludePlatforms": [], 15 | "allowUnsafeCode": false, 16 | "overrideReferences": false, 17 | "precompiledReferences": [], 18 | "autoReferenced": true, 19 | "defineConstraints": [] 20 | } -------------------------------------------------------------------------------- /Runtime/Helpers/OptionalDependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Unity.AutoLOD 5 | { 6 | [Conditional("UNITY_CCU")] 7 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 8 | sealed class OptionalDependencyAttribute : Attribute 9 | { 10 | public string dependentClass; 11 | public string define; 12 | 13 | public OptionalDependencyAttribute(string dependentClass, string define) 14 | { 15 | this.dependentClass = dependentClass; 16 | this.define = define; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Runtime/Interfaces/IMeshSimplifier.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.AutoLOD 2 | { 3 | public interface IMeshSimplifier 4 | { 5 | /// 6 | /// Simplify an input mesh to quality threshold (NOTE: Runs on a separate thread) 7 | /// 8 | /// A working copy of the input mesh 9 | /// Where the output mesh should be stored 10 | /// Percentage of quality requested in relation to the input mesh 11 | void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Editor/Batchers/MaterialPreservingBatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | namespace Unity.AutoLOD 5 | { 6 | /// 7 | /// A batcher that preserves materials when combining meshes (does not reduce draw calls) 8 | /// 9 | class MaterialPreservingBatcher : IBatcher 10 | { 11 | public IEnumerator Batch(GameObject go) 12 | { 13 | var renderers = go.GetComponentsInChildren(); 14 | foreach (var r in renderers) 15 | { 16 | r.enabled = true; 17 | yield return null; 18 | } 19 | StaticBatchingUtility.Combine(go); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this package will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.0.2 - 2021-08-13 8 | ### Added 9 | - Update SimplygonMeshSimplifier to support Simplygon9 (requires USD 2.0.0-exp.1 package version) 10 | 11 | ### Fixed 12 | - Fixed WorkingMesh name length termination 13 | - Fixed GetTriangleRange not respecting subMeshCount 14 | - Fixed missing MeshFilter causing Unity editor window updates to stall 15 | 16 | ### Changed 17 | - Removed MeshDecimator simplifier since the project has been archived 18 | 19 | ## 0.0.1 20 | - First internal build. 21 | 22 | #Contributors 23 | - Amir Ebrahimi 24 | - Elliot Cuzzillo 25 | - Yuangguang Liao -------------------------------------------------------------------------------- /Editor/Helpers/ProfileAnimation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEditor; 3 | using UnityEditorInternal; 4 | using UnityEngine; 5 | using UnityEngine.Playables; 6 | 7 | namespace Unity.AutoLOD 8 | { 9 | public class ProfileAnimation : MonoBehaviour 10 | { 11 | public PlayableDirector playableDirector; 12 | public bool showProfilerWindow = true; 13 | 14 | IEnumerator Start() 15 | { 16 | var profilerWindowType = typeof(EditorApplication).Assembly.GetType("UnityEditor.ProfilerWindow"); 17 | 18 | ProfilerDriver.ClearAllFrames(); 19 | 20 | // Skip first frame stutter 21 | ProfilerDriver.enabled = false; 22 | yield return null; 23 | ProfilerDriver.enabled = true; 24 | 25 | if (showProfilerWindow) 26 | { 27 | var window = EditorWindow.GetWindow(profilerWindowType); 28 | window.maximized = true; 29 | } 30 | 31 | while (playableDirector.playableGraph.IsValid() && playableDirector.playableGraph.IsPlaying()) 32 | yield return null; 33 | 34 | ProfilerDriver.enabled = false; 35 | EditorApplication.isPlaying = false; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Runtime/Shaders/SimpleBatcher.shader: -------------------------------------------------------------------------------- 1 | Shader "Custom/AutoLOD/SimpleBatcher" { 2 | Properties { 3 | _Color ("Color", Color) = (1,1,1,1) 4 | _MainTex ("Albedo (RGB)", 2D) = "white" {} 5 | _Glossiness("Smoothness", Range(0,1)) = 0.5 6 | _Metallic("Metallic", Range(0,1)) = 0.0 7 | } 8 | SubShader { 9 | Tags { "RenderType"="Opaque" } 10 | LOD 200 11 | 12 | CGPROGRAM 13 | // Physically based Standard lighting model, and enable shadows on all light types 14 | #pragma surface surf Standard vertex:vert fullforwardshadows 15 | 16 | // Use shader model 3.0 target, to get nicer looking lighting 17 | #pragma target 3.0 18 | 19 | sampler2D _MainTex; 20 | 21 | struct Input { 22 | float2 uv_MainTex; 23 | float3 vertexColor; // Vertex color stored here by vert() method 24 | }; 25 | 26 | half _Glossiness; 27 | half _Metallic; 28 | fixed4 _Color; 29 | 30 | half4 LightingUnlit(SurfaceOutput s, half3 lightDir, half atten) 31 | { 32 | half4 c; 33 | c.rgb = s.Albedo; 34 | c.a = s.Alpha; 35 | return c; 36 | } 37 | 38 | // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. 39 | // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. 40 | // #pragma instancing_options assumeuniformscaling 41 | UNITY_INSTANCING_BUFFER_START(Props) 42 | // put more per-instance properties here 43 | UNITY_INSTANCING_BUFFER_END(Props) 44 | 45 | void vert(inout appdata_full v, out Input o) 46 | { 47 | UNITY_INITIALIZE_OUTPUT(Input, o); 48 | o.vertexColor = v.color.rgb; // Save the Vertex Color in the Input for the surf() method 49 | } 50 | 51 | void surf (Input IN, inout SurfaceOutputStandard o) { 52 | // Albedo comes from a texture tinted by color 53 | fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; 54 | o.Albedo = c.rgb * IN.vertexColor; 55 | // Metallic and smoothness come from slider variables 56 | o.Metallic = _Metallic; 57 | o.Smoothness = _Glossiness; 58 | o.Alpha = c.a; 59 | } 60 | ENDCG 61 | } 62 | FallBack "Diffuse" 63 | } 64 | -------------------------------------------------------------------------------- /Editor/LODData.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Unity.AutoLOD 4 | { 5 | public class LODData : ScriptableObject 6 | { 7 | public const int MaxLOD = 7; 8 | 9 | public bool overrideDefaults; 10 | public LODImportSettings importSettings; 11 | public Renderer[] lod0; 12 | public Renderer[] lod1; 13 | public Renderer[] lod2; 14 | public Renderer[] lod3; 15 | public Renderer[] lod4; 16 | public Renderer[] lod5; 17 | public Renderer[] lod6; 18 | public Renderer[] lod7; 19 | 20 | public Renderer[] this[int index] 21 | { 22 | get 23 | { 24 | switch (index) 25 | { 26 | default: 27 | return lod0; 28 | case 1: 29 | return lod1; 30 | case 2: 31 | return lod2; 32 | case 3: 33 | return lod3; 34 | case 4: 35 | return lod4; 36 | case 5: 37 | return lod5; 38 | case 6: 39 | return lod6; 40 | case 7: 41 | return lod7; 42 | } 43 | } 44 | set 45 | { 46 | switch (index) 47 | { 48 | default: 49 | lod0 = value; 50 | break; 51 | case 1: 52 | lod1 = value; 53 | break; 54 | case 2: 55 | lod2 = value; 56 | break; 57 | case 3: 58 | lod3 = value; 59 | break; 60 | case 4: 61 | lod4 = value; 62 | break; 63 | case 5: 64 | lod5 = value; 65 | break; 66 | case 6: 67 | lod6 = value; 68 | break; 69 | case 7: 70 | lod7 = value; 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/InstaLODMeshSimplifier.cs: -------------------------------------------------------------------------------- 1 | #if ENABLE_INSTALOD 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using InstaLOD; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using Unity.AutoLOD; 10 | using UnityObject = UnityEngine.Object; 11 | #endif 12 | 13 | #if ENABLE_INSTALOD 14 | namespace Unity.AutoLOD 15 | { 16 | public struct InstaLODMeshSimplifier : IMeshSimplifier 17 | { 18 | static object executionLock = new object(); 19 | 20 | public void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality) 21 | { 22 | Renderer renderer = null; 23 | 24 | MonoBehaviourHelper.ExecuteOnMainThread(() => 25 | { 26 | var go = EditorUtility.CreateGameObjectWithHideFlags("Temp", HideFlags.HideAndDontSave, typeof(MeshRenderer), typeof(MeshFilter)); 27 | var mf = go.GetComponent(); 28 | var mesh = new Mesh(); 29 | inputMesh.ApplyToMesh(mesh); 30 | mf.sharedMesh = mesh; 31 | renderer = go.GetComponent(); 32 | var material = new Material(Shader.Find("Standard")); 33 | var sharedMaterials = new Material[mesh.subMeshCount]; 34 | for (int i = 0; i < mesh.subMeshCount; i++) 35 | sharedMaterials[i] = material; 36 | renderer.sharedMaterials = sharedMaterials; 37 | renderer.enabled = false; 38 | }); 39 | 40 | var settings = new InstaLODOptimizeSettings(quality); 41 | settings.PercentTriangles = quality; 42 | var nativeMeshSettings = new InstaLODNativeMeshOperationSettings(true); 43 | nativeMeshSettings.hideSourceGameObjects = false; 44 | 45 | lock (executionLock) 46 | { 47 | if (!MonoBehaviourHelper.IsMainThread()) 48 | { 49 | while (InstaLODNative.currentMeshOperationState != null) 50 | Thread.Sleep(100); 51 | } 52 | 53 | MonoBehaviourHelper.ExecuteOnMainThread(() => 54 | { 55 | EditorWindow.GetWindow(); // Necessary for background processing 56 | InstaLODNative.Optimize(new List() { renderer }, settings, nativeMeshSettings); 57 | Selection.activeGameObject = null; // Necessary to avoid errors from InstaLOD trying to add settings component to imported model 58 | }); 59 | } 60 | 61 | while (InstaLODNative.currentMeshOperationState != null) 62 | { 63 | if (MonoBehaviourHelper.IsMainThread()) 64 | InstaLODMainThreadAction.RunMainThreadActions(); 65 | else 66 | Thread.Sleep(100); 67 | } 68 | 69 | MonoBehaviourHelper.ExecuteOnMainThread(() => 70 | { 71 | var mf = renderer.GetComponent(); 72 | mf.sharedMesh.ApplyToWorkingMesh(outputMesh); 73 | UnityObject.DestroyImmediate(mf.gameObject); 74 | }); 75 | } 76 | } 77 | } 78 | #endif -------------------------------------------------------------------------------- /Editor/LODImportSettingsDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Unity.AutoLOD.Utilities; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace Unity.AutoLOD 8 | { 9 | [CustomPropertyDrawer(typeof(LODImportSettings))] 10 | public class LODImportSettingsDrawer : PropertyDrawer 11 | { 12 | // Draw the property inside the given rect 13 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 14 | { 15 | // Using BeginProperty / EndProperty on the parent property means that 16 | // prefab override logic works on the entire property. 17 | EditorGUI.BeginProperty(position, label, property); 18 | 19 | // Draw label 20 | EditorGUILayout.PrefixLabel(label); 21 | 22 | // Draw fields - passs GUIContent.none to each so they are drawn without labels 23 | var generateOnImportProperty = property.FindPropertyRelative("generateOnImport"); 24 | EditorGUILayout.PropertyField(generateOnImportProperty, new GUIContent("Generate on Import")); 25 | 26 | if (generateOnImportProperty.boolValue) 27 | { 28 | PropertyTypeField(property.FindPropertyRelative("meshSimplifier")); 29 | PropertyTypeField(property.FindPropertyRelative("batcher")); 30 | 31 | var maxLODProperty = property.FindPropertyRelative("maxLODGenerated"); 32 | var maxLODValues = Enumerable.Range(0, LODData.MaxLOD + 1).ToArray(); 33 | EditorGUI.BeginChangeCheck(); 34 | int maxLODGenerated = EditorGUILayout.IntPopup("Maximum LOD Generated", maxLODProperty.intValue, maxLODValues.Select(v => v.ToString()).ToArray(), maxLODValues); 35 | if (EditorGUI.EndChangeCheck()) 36 | maxLODProperty.intValue = maxLODGenerated; 37 | 38 | var initialLODMaxPolyCountProperty = property.FindPropertyRelative("initialLODMaxPolyCount"); 39 | EditorGUI.BeginChangeCheck(); 40 | var maxPolyCount = EditorGUILayout.IntField("Initial LOD Max Poly Count", initialLODMaxPolyCountProperty.intValue); 41 | if (EditorGUI.EndChangeCheck()) 42 | initialLODMaxPolyCountProperty.intValue = maxPolyCount; 43 | } 44 | 45 | EditorGUILayout.Space(); 46 | EditorGUILayout.Space(); 47 | EditorGUILayout.Space(); 48 | 49 | EditorGUI.EndProperty(); 50 | } 51 | 52 | void PropertyTypeField(SerializedProperty property) 53 | { 54 | var implementations = ObjectUtils.GetImplementationsOfInterface(typeof(T)).ToList(); 55 | var type = Type.GetType(property.stringValue); 56 | if (type == null && implementations.Count > 0) 57 | type = Type.GetType(implementations[0].AssemblyQualifiedName); 58 | 59 | var displayedOptions = implementations.Select(t => t.Name).ToArray(); 60 | EditorGUI.BeginChangeCheck(); 61 | var selected = EditorGUILayout.Popup(property.displayName, Array.IndexOf(displayedOptions, type.Name), displayedOptions); 62 | if (EditorGUI.EndChangeCheck()) 63 | property.stringValue = implementations[selected].AssemblyQualifiedName; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Editor/TextureAtlasModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using UnityEditor; 7 | using UnityEngine; 8 | using UnityObject = UnityEngine.Object; 9 | 10 | namespace Unity.AutoLOD 11 | { 12 | public class TextureAtlasModule : ScriptableSingleton 13 | { 14 | List m_Atlases = new List(); 15 | 16 | void OnEnable() 17 | { 18 | m_Atlases.Clear(); 19 | 20 | var atlases = Resources.FindObjectsOfTypeAll(); 21 | m_Atlases.AddRange(atlases); 22 | } 23 | 24 | static void SaveUniqueAtlasAsset(UnityObject asset) 25 | { 26 | var directory = "Assets/AutoLOD/Generated/Atlases/"; 27 | if (!Directory.Exists(directory)) 28 | Directory.CreateDirectory(directory); 29 | 30 | var path = directory + Path.GetRandomFileName(); 31 | path = Path.ChangeExtension(path, "asset"); 32 | AssetDatabase.CreateAsset(asset, path); 33 | } 34 | 35 | public IEnumerator GetTextureAtlas(Texture2D[] textures, Action callback) 36 | { 37 | TextureAtlas atlas = null; 38 | 39 | // Clear out any atlases that were removed 40 | m_Atlases.RemoveAll(a => a == null); 41 | yield return null; 42 | 43 | foreach (var a in m_Atlases) 44 | { 45 | // At a minimum the atlas should have all of the textures requested, but can be a superset 46 | if (!textures.Except(a.textures).Any()) 47 | { 48 | atlas = a; 49 | break; 50 | } 51 | 52 | yield return null; 53 | } 54 | 55 | if (!atlas) 56 | { 57 | atlas = ScriptableObject.CreateInstance(); 58 | 59 | foreach (var t in textures) 60 | { 61 | var assetImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t)); 62 | var textureImporter = assetImporter as TextureImporter; 63 | if (textureImporter && !textureImporter.isReadable) 64 | { 65 | textureImporter.isReadable = true; 66 | textureImporter.SaveAndReimport(); 67 | } 68 | else if (!assetImporter) 69 | { 70 | // In-memory textures need to be saved to disk in order to be referenced by the texture atlas 71 | SaveUniqueAtlasAsset(t); 72 | } 73 | yield return null; 74 | } 75 | 76 | var textureAtlas = new Texture2D(0, 0, TextureFormat.RGB24, true, PlayerSettings.colorSpace == ColorSpace.Linear); 77 | var uvs = textureAtlas.PackTextures(textures.ToArray(), 0, 1024, true); 78 | 79 | if (uvs != null) 80 | { 81 | atlas.textureAtlas = textureAtlas; 82 | atlas.uvs = uvs; 83 | atlas.textures = textures; 84 | 85 | SaveUniqueAtlasAsset(textureAtlas); 86 | SaveUniqueAtlasAsset(atlas); 87 | 88 | m_Atlases.Add(atlas); 89 | } 90 | 91 | yield return null; 92 | } 93 | 94 | if (callback != null) 95 | callback(atlas); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Editor/LODDataEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using Unity.AutoLOD; 3 | using UnityEngine; 4 | 5 | namespace Unity.AutoLOD 6 | { 7 | [CustomEditor(typeof(LODData))] 8 | [CanEditMultipleObjects] 9 | public class LODDataEditor : Editor 10 | { 11 | public static int maxLODGenerated { set; get; } 12 | public static int initialLODMaxPolyCount { set; get; } 13 | public static string meshSimplifier; 14 | public static string batcher; 15 | 16 | SerializedProperty m_OverrideDefaults; 17 | SerializedProperty m_ImportSettings; 18 | SerializedProperty[] m_LODs = new SerializedProperty[LODData.MaxLOD + 1]; 19 | 20 | void OnEnable() 21 | { 22 | m_OverrideDefaults = serializedObject.FindProperty("overrideDefaults"); 23 | m_ImportSettings = serializedObject.FindProperty("importSettings"); 24 | for (int i = 0; i < m_LODs.Length; i++) 25 | { 26 | m_LODs[i] = serializedObject.FindProperty("lod" + i); 27 | } 28 | } 29 | 30 | public override void OnInspectorGUI() 31 | { 32 | serializedObject.Update(); 33 | 34 | EditorGUI.BeginChangeCheck(); 35 | var settingsOverridden = m_OverrideDefaults.boolValue; 36 | EditorGUI.BeginChangeCheck(); 37 | EditorGUILayout.PropertyField(m_OverrideDefaults, new GUIContent("Override Defaults")); 38 | if (EditorGUI.EndChangeCheck() && m_OverrideDefaults.boolValue) 39 | { 40 | m_ImportSettings.FindPropertyRelative("generateOnImport").boolValue = true; 41 | m_ImportSettings.FindPropertyRelative("meshSimplifier").stringValue = meshSimplifier; 42 | m_ImportSettings.FindPropertyRelative("batcher").stringValue = batcher; 43 | m_ImportSettings.FindPropertyRelative("maxLODGenerated").intValue = maxLODGenerated; 44 | m_ImportSettings.FindPropertyRelative("initialLODMaxPolyCount").intValue = initialLODMaxPolyCount; 45 | } 46 | 47 | if (settingsOverridden) 48 | { 49 | EditorGUILayout.PropertyField(m_ImportSettings, new GUIContent("Import Settings"), true); 50 | } 51 | 52 | EditorGUI.BeginDisabledGroup(!settingsOverridden || m_ImportSettings.FindPropertyRelative("generateOnImport").boolValue); 53 | for (int i = 0; i < m_LODs.Length; i++) 54 | { 55 | var lod = m_LODs[i]; 56 | if (lod.arraySize > 0) 57 | { 58 | EditorGUI.BeginDisabledGroup(i == 0); 59 | EditorGUILayout.PropertyField(lod, new GUIContent("LOD" + i), true); 60 | EditorGUI.EndDisabledGroup(); 61 | } 62 | else 63 | { 64 | if (settingsOverridden) 65 | { 66 | EditorGUILayout.BeginHorizontal(); 67 | EditorGUI.BeginDisabledGroup(i == 1); 68 | if (GUILayout.Button("Remove LOD")) 69 | m_LODs[i - 1].ClearArray(); 70 | EditorGUI.EndDisabledGroup(); 71 | if (GUILayout.Button("Add LOD")) 72 | lod.InsertArrayElementAtIndex(0); 73 | EditorGUILayout.EndHorizontal(); 74 | } 75 | break; 76 | } 77 | } 78 | EditorGUI.EndDisabledGroup(); 79 | 80 | if (EditorGUI.EndChangeCheck()) 81 | serializedObject.ApplyModifiedProperties(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Editor/TagManager.cs: -------------------------------------------------------------------------------- 1 | // From: https://github.com/Unity-Technologies/EditorVR 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace Unity.AutoLOD.Utilities 7 | { 8 | static class TagManager 9 | { 10 | const int k_MaxLayer = 31; 11 | const int k_MinLayer = 8; 12 | 13 | /// 14 | /// Add a tag to the tag manager if it doesn't already exist 15 | /// 16 | /// Tag to add 17 | public static void AddTag(string tag) 18 | { 19 | SerializedObject so; 20 | var tags = GetTagManagerProperty("tags", out so); 21 | if (tags != null) 22 | { 23 | var found = false; 24 | for (var i = 0; i < tags.arraySize; i++) 25 | { 26 | if (tags.GetArrayElementAtIndex(i).stringValue == tag) 27 | { 28 | found = true; 29 | break; 30 | } 31 | } 32 | 33 | if (!found) 34 | { 35 | var arraySize = tags.arraySize; 36 | tags.InsertArrayElementAtIndex(arraySize); 37 | tags.GetArrayElementAtIndex(arraySize - 1).stringValue = tag; 38 | } 39 | so.ApplyModifiedProperties(); 40 | so.Update(); 41 | } 42 | } 43 | 44 | /// 45 | /// Add a layer to the tag manager if it doesn't already exist 46 | /// Start at layer 31 (max) and work down 47 | /// 48 | /// 49 | public static void AddLayer(string layerName) 50 | { 51 | SerializedObject so; 52 | var layers = GetTagManagerProperty("layers", out so); 53 | if (layers != null) 54 | { 55 | var found = false; 56 | for (var i = 0; i < layers.arraySize; i++) 57 | { 58 | if (layers.GetArrayElementAtIndex(i).stringValue == layerName) 59 | { 60 | found = true; 61 | break; 62 | } 63 | } 64 | 65 | if (!found) 66 | { 67 | var added = false; 68 | for (var i = k_MaxLayer; i >= k_MinLayer; i--) 69 | { 70 | var layer = layers.GetArrayElementAtIndex(i); 71 | if (!string.IsNullOrEmpty(layer.stringValue)) 72 | continue; 73 | 74 | layer.stringValue = layerName; 75 | added = true; 76 | break; 77 | } 78 | 79 | if (!added) 80 | Debug.LogWarning("Could not add layer " + layerName + " because there are no free layers"); 81 | } 82 | so.ApplyModifiedProperties(); 83 | so.Update(); 84 | } 85 | } 86 | 87 | public static SerializedProperty GetTagManagerProperty(string name, out SerializedObject so) 88 | { 89 | var asset = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset"); 90 | if ((asset != null) && (asset.Length > 0)) 91 | { 92 | so = new SerializedObject(asset[0]); 93 | return so.FindProperty(name); 94 | } 95 | 96 | so = null; 97 | return null; 98 | } 99 | 100 | public static List GetRequiredTags() 101 | { 102 | var requiredTags = new List(); 103 | ObjectUtils.ForEachType(t => 104 | { 105 | var tagAttributes = (RequiresTagAttribute[])t.GetCustomAttributes(typeof(RequiresTagAttribute), true); 106 | foreach (var attribute in tagAttributes) 107 | requiredTags.Add(attribute.tag); 108 | }); 109 | return requiredTags; 110 | } 111 | 112 | public static List GetRequiredLayers() 113 | { 114 | var requiredLayers = new List(); 115 | ObjectUtils.ForEachType(t => 116 | { 117 | var layerAttributes = (RequiresLayerAttribute[])t.GetCustomAttributes(typeof(RequiresLayerAttribute), true); 118 | foreach (var attribute in layerAttributes) 119 | requiredLayers.Add(attribute.layer); 120 | }); 121 | return requiredLayers; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Editor/GridPlacementUtility.cs: -------------------------------------------------------------------------------- 1 | using Unity.AutoLOD.Utilities; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Unity.AutoLOD 6 | { 7 | public class GridPlacementUtility : EditorWindow 8 | { 9 | PrimitiveType m_PrimitiveType = PrimitiveType.Cube; 10 | Vector3 m_Number = Vector3.one; 11 | Vector3 m_Spacing = Vector3.one; 12 | GameObject m_Prefab; 13 | Material m_Material; 14 | bool m_UniqueMaterials; 15 | 16 | [MenuItem("GameObject/AutoLOD/Grid Placement Utility")] 17 | static void Init() 18 | { 19 | EditorWindow.GetWindow(true, "Grid Placement Utility").Show(); 20 | } 21 | 22 | void OnGUI() 23 | { 24 | EditorGUILayout.Space(); 25 | 26 | EditorGUI.BeginChangeCheck(); 27 | m_Prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", m_Prefab, typeof(GameObject), true); 28 | if (EditorGUI.EndChangeCheck()) 29 | { 30 | var bounds = ObjectUtils.GetBounds(m_Prefab.transform); 31 | m_Spacing = bounds.size; 32 | } 33 | 34 | if (!m_Prefab) 35 | { 36 | GUILayout.Label("OR"); 37 | m_PrimitiveType = (PrimitiveType)EditorGUILayout.EnumPopup("Primitive", m_PrimitiveType); 38 | } 39 | 40 | EditorGUILayout.Space(); 41 | EditorGUILayout.Space(); 42 | 43 | 44 | m_Number = EditorGUILayout.Vector3Field("Number", m_Number); 45 | m_Spacing = EditorGUILayout.Vector3Field("Spacing", m_Spacing); 46 | 47 | EditorGUILayout.Space(); 48 | EditorGUILayout.Space(); 49 | 50 | m_UniqueMaterials = EditorGUILayout.Toggle("Unique Materials", m_UniqueMaterials); 51 | m_Material = (Material)EditorGUILayout.ObjectField("Material", m_Material, typeof(Material), true); 52 | 53 | EditorGUILayout.Space(); 54 | EditorGUILayout.Space(); 55 | 56 | if (GUILayout.Button("Create")) 57 | { 58 | GameObject parent = new GameObject("Primitives"); 59 | for (int i = 0; i < Mathf.FloorToInt(m_Number.x); i++) 60 | { 61 | for (int j = 0; j < Mathf.FloorToInt(m_Number.y); j++) 62 | { 63 | for (int k = 0; k < Mathf.FloorToInt(m_Number.z); k++) 64 | { 65 | GameObject go = m_Prefab ? (GameObject)PrefabUtility.InstantiatePrefab(m_Prefab) : GameObject.CreatePrimitive(m_PrimitiveType); 66 | go.name = string.Format("{0} {1}_{2}_{3}", m_Prefab ? m_Prefab.name : m_PrimitiveType.ToString(), i, j, k); 67 | go.transform.position = Vector3.right * (m_Spacing.x * i + i) + Vector3.up * (m_Spacing.y * j + j) + Vector3.forward * (m_Spacing.z * k + k); 68 | go.transform.parent = parent.transform; 69 | 70 | var meshRenderers = go.GetComponentsInChildren(); 71 | foreach (var mr in meshRenderers) 72 | { 73 | var sharedMaterials = mr.sharedMaterials; 74 | for (int m = 0; m < sharedMaterials.Length; m++) 75 | { 76 | var material = m_Material ? m_Material : sharedMaterials[m]; 77 | sharedMaterials[m] = m_UniqueMaterials ? Instantiate(material) : material; 78 | } 79 | mr.sharedMaterials = sharedMaterials; 80 | } 81 | } 82 | } 83 | } 84 | Close(); 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/SimulatedMeshSimplifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using Mesh = Unity.AutoLOD.WorkingMesh; 5 | 6 | namespace Unity.AutoLOD 7 | { 8 | [HideInInspector] 9 | public struct SimulatedMeshSimplifier : IMeshSimplifier 10 | { 11 | public void Simplify(Mesh inputMesh, Mesh outputMesh, float quality) 12 | { 13 | Vector3[] vertices = inputMesh.vertices; 14 | Vector2[] uv = inputMesh.uv; 15 | Vector2[] uv2 = inputMesh.uv2; 16 | Color[] colors = inputMesh.colors; 17 | Vector3[] normals = inputMesh.normals; 18 | Vector4[] tangents = inputMesh.tangents; 19 | 20 | var usedVertices = new Dictionary(); 21 | var submeshTriangles = new Dictionary>(); 22 | for (int i = 0; i < inputMesh.subMeshCount; i++) 23 | { 24 | var triangles = new List(inputMesh.GetTriangles(i)); 25 | var targetCount = Mathf.FloorToInt(triangles.Count * quality); 26 | targetCount = Mathf.Max(0, targetCount - targetCount % 3); 27 | 28 | var random = new System.Random(); 29 | while (triangles.Count > targetCount) 30 | { 31 | var randomTriangle = Mathf.CeilToInt((float)random.NextDouble() * (triangles.Count - 1)); 32 | randomTriangle -= randomTriangle % 3; 33 | 34 | triangles.RemoveRange(randomTriangle, 3); 35 | } 36 | 37 | for (int t = 0; t < triangles.Count; t++) 38 | { 39 | var index = triangles[t]; 40 | usedVertices[index] = vertices[index]; 41 | } 42 | 43 | submeshTriangles[i] = triangles; 44 | } 45 | 46 | var trimmedVertices = new List(); 47 | var trimmedUVs = new List(); 48 | var trimmedUV2s = new List(); 49 | var trimmedColors = new List(); 50 | var trimmedNormals = new List(); 51 | var trimmedTangents = new List(); 52 | 53 | var vertexRemap = new Dictionary(); 54 | for (int i = 0; i < vertices.Length; i++) 55 | { 56 | Vector3 vertex; 57 | if (usedVertices.TryGetValue(i, out vertex)) 58 | { 59 | vertexRemap[i] = trimmedVertices.Count; 60 | trimmedVertices.Add(vertex); 61 | 62 | if (uv.Length > 0) 63 | trimmedUVs.Add(uv[i]); 64 | 65 | if (uv2.Length > 0) 66 | trimmedUV2s.Add(uv2[i]); 67 | 68 | if (colors.Length > 0) 69 | trimmedColors.Add(colors[i]); 70 | 71 | if (normals.Length > 0) 72 | trimmedNormals.Add(normals[i]); 73 | 74 | if (tangents.Length > 0) 75 | trimmedTangents.Add(tangents[i]); 76 | } 77 | } 78 | 79 | outputMesh.vertices = trimmedVertices.ToArray(); 80 | 81 | for (int i = 0; i < inputMesh.subMeshCount; i++) 82 | { 83 | var triangles = submeshTriangles[i]; 84 | 85 | for (int t = 0; t < triangles.Count; t++) 86 | { 87 | triangles[t] = vertexRemap[triangles[t]]; 88 | } 89 | 90 | outputMesh.SetTriangles(triangles.ToArray(), i); 91 | } 92 | 93 | outputMesh.uv = trimmedUVs.ToArray(); 94 | outputMesh.uv2 = trimmedUV2s.ToArray(); 95 | outputMesh.colors = trimmedColors.ToArray(); 96 | outputMesh.normals = trimmedNormals.ToArray(); 97 | outputMesh.tangents = trimmedTangents.ToArray(); 98 | 99 | outputMesh.RecalculateBounds(); 100 | outputMesh.RecalculateNormals(); 101 | outputMesh.RecalculateTangents(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## From https://gist.github.com/MattOstgard/046b122d7d14b6c10b7083c732efb93c w/ modifications 2 | 3 | # Set the default behavior, in case people don't have core.autocrlf set. 4 | * text=auto 5 | 6 | # Unity 7 | *.cginc text 8 | *.cs diff=csharp text eol=lf 9 | *.shader text 10 | 11 | 12 | # Unity YAML 13 | *.anim merge=unityyamlmerge eol=lf 14 | *.controller merge=unityyamlmerge eol=lf 15 | *.mat merge=unityyamlmerge eol=lf 16 | *.meta merge=unityyamlmerge eol=lf 17 | *.physicMaterial merge=unityyamlmerge eol=lf 18 | *.physicMaterial2D merge=unityyamlmerge eol=lf 19 | *.prefab merge=unityyamlmerge eol=lf 20 | *.unity merge=unityyamlmerge eol=lf 21 | 22 | 23 | # Unity LFS 24 | *.lfs.* filter=lfs diff=lfs merge=lfs -crlf 25 | *.cubemap filter=lfs diff=lfs merge=lfs -text 26 | *.unitypackage filter=lfs diff=lfs merge=lfs -text 27 | 28 | 29 | # Image 30 | *.ai filter=lfs diff=lfs merge=lfs -text 31 | *.apng filter=lfs diff=lfs merge=lfs -text 32 | *.astc filter=lfs diff=lfs merge=lfs -text 33 | *.bmp filter=lfs diff=lfs merge=lfs -text 34 | *.dds filter=lfs diff=lfs merge=lfs -text 35 | *.eps filter=lfs diff=lfs merge=lfs -text 36 | *.exr filter=lfs diff=lfs merge=lfs -text 37 | *.gif filter=lfs diff=lfs merge=lfs -text 38 | *.hdr filter=lfs diff=lfs merge=lfs -text 39 | *.jpeg filter=lfs diff=lfs merge=lfs -text 40 | *.jpg filter=lfs diff=lfs merge=lfs -text 41 | *.ktx filter=lfs diff=lfs merge=lfs -text 42 | *.png filter=lfs diff=lfs merge=lfs -text 43 | *.psd filter=lfs diff=lfs merge=lfs -text 44 | *.pvr filter=lfs diff=lfs merge=lfs -text 45 | *.svg filter=lfs diff=lfs merge=lfs -text 46 | *.svgz filter=lfs diff=lfs merge=lfs -text 47 | *.tga filter=lfs diff=lfs merge=lfs -text 48 | *.tif filter=lfs diff=lfs merge=lfs -text 49 | *.tiff filter=lfs diff=lfs merge=lfs -text 50 | *.webm filter=lfs diff=lfs merge=lfs -text 51 | *.webp filter=lfs diff=lfs merge=lfs -text 52 | 53 | 54 | # Audio 55 | *.aif filter=lfs diff=lfs merge=lfs -text 56 | *.m4a filter=lfs diff=lfs merge=lfs -text 57 | *.mp3 filter=lfs diff=lfs merge=lfs -text 58 | *.ogg filter=lfs diff=lfs merge=lfs -text 59 | *.wav filter=lfs diff=lfs merge=lfs -text 60 | 61 | 62 | # Video 63 | *.asf filter=lfs diff=lfs merge=lfs -text 64 | *.avi filter=lfs diff=lfs merge=lfs -text 65 | *.flv filter=lfs diff=lfs merge=lfs -text 66 | *.mov filter=lfs diff=lfs merge=lfs -text 67 | *.mp4 filter=lfs diff=lfs merge=lfs -text 68 | *.mpeg filter=lfs diff=lfs merge=lfs -text 69 | *.mpg filter=lfs diff=lfs merge=lfs -text 70 | *.ogv filter=lfs diff=lfs merge=lfs -text 71 | *.wmv filter=lfs diff=lfs merge=lfs -text 72 | 73 | 74 | # 3D Object 75 | *.blend filter=lfs diff=lfs merge=lfs -text 76 | *.dxf filter=lfs diff=lfs merge=lfs -text 77 | *.fbx filter=lfs diff=lfs merge=lfs -text 78 | *.lxo filter=lfs diff=lfs merge=lfs -text 79 | *.ma filter=lfs diff=lfs merge=lfs -text 80 | *.max filter=lfs diff=lfs merge=lfs -text 81 | *.mb filter=lfs diff=lfs merge=lfs -text 82 | *.obj filter=lfs diff=lfs merge=lfs -text 83 | 84 | 85 | # Compressed Archive 86 | *.7z filter=lfs diff=lfs merge=lfs -text 87 | *.bz2 filter=lfs diff=lfs merge=lfs -text 88 | *.gz filter=lfs diff=lfs merge=lfs -text 89 | *.rar filter=lfs diff=lfs merge=lfs -text 90 | *.tar filter=lfs diff=lfs merge=lfs -text 91 | *.zip filter=lfs diff=lfs merge=lfs -text 92 | 93 | 94 | # Compiled Dynamic Library 95 | *.dll filter=lfs diff=lfs merge=lfs -text 96 | *.pdb filter=lfs diff=lfs merge=lfs -text 97 | *.so filter=lfs diff=lfs merge=lfs -text 98 | 99 | 100 | # Compiled Static Library 101 | *.a filter=lfs diff=lfs merge=lfs -text 102 | *.la filter=lfs diff=lfs merge=lfs -text 103 | *.lai filter=lfs diff=lfs merge=lfs -text 104 | *.lib filter=lfs diff=lfs merge=lfs -text 105 | *.llblgenproj filter=lfs diff=lfs merge=lfs -text 106 | 107 | 108 | # Font 109 | *.otf filter=lfs diff=lfs merge=lfs -text 110 | *.ttf filter=lfs diff=lfs merge=lfs -text 111 | 112 | 113 | # Executable/Installer 114 | *.apk filter=lfs diff=lfs merge=lfs -text 115 | *.exe filter=lfs diff=lfs merge=lfs -text 116 | 117 | 118 | # Documents 119 | *.pdf filter=lfs diff=lfs merge=lfs -text 120 | 121 | 122 | # Other 123 | *.reason filter=lfs diff=lfs merge=lfs -text 124 | *.rns filter=lfs diff=lfs merge=lfs -text -------------------------------------------------------------------------------- /Runtime/Helpers/MeshLOD.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Unity.Collections; 6 | using Unity.Jobs; 7 | using UnityEngine; 8 | 9 | namespace Unity.AutoLOD 10 | { 11 | interface IMeshGenerateLODJob : IJob, IDisposable 12 | { 13 | WorkingMesh InputMesh { set; } 14 | WorkingMesh OutputMesh { get; set; } 15 | float Quality { set; } 16 | } 17 | 18 | interface IMeshLOD 19 | { 20 | Mesh InputMesh { set; } 21 | Mesh OutputMesh { get; set; } 22 | float Quality { set; } 23 | 24 | IEnumerator GenerateAfterDependencies(List jobDependencies); 25 | JobHandle Generate(); 26 | } 27 | 28 | struct MeshGenerateLODJob : IMeshGenerateLODJob 29 | where TSimplifier : struct, IMeshSimplifier 30 | { 31 | public WorkingMesh InputMesh { get; set; } 32 | public WorkingMesh OutputMesh { get; set; } 33 | public float Quality { get; set; } 34 | 35 | public void Execute() 36 | { 37 | var meshSimplifier = default(TSimplifier); 38 | meshSimplifier.Simplify(InputMesh, OutputMesh, Quality); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | InputMesh.Dispose(); 44 | OutputMesh.Dispose(); 45 | } 46 | } 47 | 48 | struct MeshLOD : IMeshLOD 49 | where T : struct, IMeshGenerateLODJob 50 | { 51 | public Mesh InputMesh { get; set; } 52 | public Mesh OutputMesh { get; set; } 53 | public float Quality { get; set; } 54 | 55 | IEnumerator UpdateMesh(JobHandle jobHandle, IMeshGenerateLODJob job) 56 | { 57 | while (!jobHandle.IsCompleted) 58 | yield return new WaitForSecondsRealtime(0.5f); 59 | 60 | jobHandle.Complete(); 61 | 62 | var finalMesh = OutputMesh; 63 | var jobOutputMesh = job.OutputMesh; 64 | jobOutputMesh.name = finalMesh.name; 65 | jobOutputMesh.ApplyToMesh(OutputMesh); 66 | finalMesh.RecalculateBounds(); 67 | 68 | job.Dispose(); 69 | } 70 | 71 | public IEnumerator GenerateAfterDependencies(List jobDependencies) 72 | { 73 | while (jobDependencies.Count > 0) 74 | { 75 | var jobDependency = jobDependencies[0]; 76 | if (!jobDependency.IsCompleted) 77 | yield return new WaitForSecondsRealtime(0.5f); 78 | else 79 | jobDependencies.Remove(jobDependency); 80 | } 81 | 82 | Generate(); 83 | } 84 | 85 | public JobHandle Generate() 86 | { 87 | // A NOP to make sure we have an instance before launching into threads that may need to execute on the main thread 88 | MonoBehaviourHelper.ExecuteOnMainThread(() => { }); 89 | 90 | var job = default(T); 91 | var inputMesh = InputMesh; 92 | job.InputMesh = inputMesh.ToWorkingMesh(Allocator.Persistent); 93 | job.Quality = Quality; 94 | // Allocate a persistent working mesh for output, so that we can apply it after the job completes (i.e. memory 95 | // allocated in a job is freed when the job completes) 96 | var workingMesh = new WorkingMesh(Allocator.Persistent, inputMesh.vertexCount, inputMesh.GetTriangleCount(), 97 | inputMesh.subMeshCount, inputMesh.blendShapeCount); 98 | workingMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 99 | job.OutputMesh = workingMesh; 100 | 101 | var jobHandle = job.Schedule(); 102 | MonoBehaviourHelper.StartCoroutine(UpdateMesh(jobHandle, job)); 103 | 104 | return jobHandle; 105 | } 106 | } 107 | 108 | static class MeshLOD 109 | { 110 | static Type GetGenericType(Type meshSimplifierType) 111 | { 112 | var genericJobType = typeof(MeshGenerateLODJob<>).MakeGenericType(meshSimplifierType); 113 | var genericType = typeof(MeshLOD<>).MakeGenericType(genericJobType); 114 | 115 | return genericType; 116 | } 117 | 118 | public static IMeshLOD GetGenericInstance(Type meshSimplifierType) 119 | { 120 | return (IMeshLOD)Activator.CreateInstance(GetGenericType(meshSimplifierType)); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Runtime/Helpers/TimedEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using UnityEngine; 5 | using Debug = UnityEngine.Debug; 6 | 7 | namespace Unity.AutoLOD 8 | { 9 | public class TimedEnumerator : IEnumerator 10 | { 11 | public float? maxIterationTimeMS { get; set; } 12 | public bool logEnabled { get; set; } 13 | public float totalExecutionTime { get; private set; } 14 | public float selfExecutionTime { get; private set; } 15 | public float iterationExecutionTime { get; private set; } 16 | 17 | float? m_FirstIterationTime; 18 | float? m_FinalIterationTime; 19 | Stack m_EnumeratorStack = new Stack(); 20 | 21 | public bool MoveNext() 22 | { 23 | var sw = new Stopwatch(); 24 | sw.Start(); 25 | 26 | // We won't be able to capture time spent in other YieldInstructions because those are objects that get waited 27 | // on separately, so we keep track of absolute time until the iterator is complete 28 | if (!m_FirstIterationTime.HasValue) 29 | m_FirstIterationTime = Time.realtimeSinceStartup; 30 | 31 | var routine = m_EnumeratorStack.Peek(); 32 | var result = true; 33 | if (maxIterationTimeMS.HasValue) 34 | { 35 | var maxTime = maxIterationTimeMS * 0.001f * Stopwatch.Frequency; 36 | 37 | // Execute through as many yields as time permits 38 | do 39 | { 40 | result &= routine.MoveNext(); 41 | 42 | if (!result) 43 | { 44 | if (m_EnumeratorStack.Count > 1) 45 | { 46 | // Nested coroutine ended 47 | m_EnumeratorStack.Pop(); 48 | result = true; 49 | } 50 | else 51 | break; 52 | } 53 | else 54 | { 55 | var current = Current; 56 | if (current is YieldInstruction || current is CustomYieldInstruction) 57 | { 58 | // We have to leave these to Unity to resolve 59 | break; 60 | } 61 | 62 | // Handle nesting 63 | var enumerator = current as IEnumerator; 64 | if (enumerator != null) 65 | { 66 | m_EnumeratorStack.Push(enumerator); 67 | } 68 | } 69 | 70 | routine = m_EnumeratorStack.Peek(); 71 | } while (sw.ElapsedTicks < maxTime); 72 | } 73 | else 74 | { 75 | result = routine.MoveNext(); 76 | } 77 | 78 | sw.Stop(); 79 | var endTime = Time.realtimeSinceStartup; 80 | 81 | if (!result && !m_FinalIterationTime.HasValue) 82 | m_FinalIterationTime = endTime; 83 | 84 | iterationExecutionTime = (float)sw.ElapsedTicks / Stopwatch.Frequency; 85 | selfExecutionTime += iterationExecutionTime; 86 | 87 | if (m_FinalIterationTime.HasValue) 88 | totalExecutionTime = m_FinalIterationTime.Value - m_FirstIterationTime.Value; 89 | else 90 | totalExecutionTime = endTime - m_FirstIterationTime.Value; 91 | 92 | if (logEnabled) 93 | Debug.LogFormat("Iteration: {0} / Self: {1} / Total: {2}", iterationExecutionTime, selfExecutionTime, totalExecutionTime); 94 | 95 | return result; 96 | } 97 | 98 | public void Reset() 99 | { 100 | while (m_EnumeratorStack.Count > 1) 101 | m_EnumeratorStack.Pop(); 102 | 103 | m_EnumeratorStack.Peek().Reset(); 104 | iterationExecutionTime = 0f; 105 | selfExecutionTime = 0f; 106 | totalExecutionTime = 0f; 107 | m_FirstIterationTime = null; 108 | m_FinalIterationTime = null; 109 | } 110 | 111 | public object Current 112 | { 113 | get { return m_EnumeratorStack.Count > 0 ? m_EnumeratorStack.Peek().Current : null; } 114 | } 115 | 116 | public TimedEnumerator(IEnumerator routine, float? maxIterationTimeMS = null) 117 | { 118 | m_EnumeratorStack.Push(routine); 119 | 120 | if (maxIterationTimeMS.HasValue) 121 | this.maxIterationTimeMS = maxIterationTimeMS; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Runtime/Extensions/LODGroupExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Unity.AutoLOD 4 | { 5 | public static class LODGroupExtensions 6 | { 7 | public static void SetEnabled(this LODGroup lodGroup, bool enabled) 8 | { 9 | if (lodGroup.enabled != enabled) 10 | { 11 | lodGroup.enabled = enabled; 12 | lodGroup.SetRenderersEnabled(enabled); 13 | } 14 | } 15 | 16 | public static void SetRenderersEnabled(this LODGroup lodGroup, bool enabled) 17 | { 18 | var lods = lodGroup.GetLODs(); 19 | SetRenderersEnabled(lods, enabled); 20 | } 21 | 22 | public static void SetEnabled(this LODVolume.LODGroupHelper lodGroupHelper, bool enabled) 23 | { 24 | var lodGroup = lodGroupHelper.lodGroup; 25 | if (lodGroup == null || lodGroup.enabled != enabled) 26 | { 27 | if (lodGroup) 28 | lodGroup.enabled = enabled; 29 | SetRenderersEnabled(lodGroupHelper.lods, enabled); 30 | } 31 | } 32 | 33 | static void SetRenderersEnabled(LOD[] lods, bool enabled) 34 | { 35 | for (var i = 0; i < lods.Length; i++) 36 | { 37 | var lod = lods[i]; 38 | 39 | var renderers = lod.renderers; 40 | foreach (var r in renderers) 41 | { 42 | if (r) 43 | r.enabled = enabled; 44 | } 45 | } 46 | } 47 | 48 | public static int GetMaxLOD(this LODGroup lodGroup) 49 | { 50 | return lodGroup.lodCount - 1; 51 | } 52 | 53 | public static int GetCurrentLOD(this LODGroup lodGroup, Camera camera = null) 54 | { 55 | var lods = lodGroup.GetLODs(); 56 | var relativeHeight = lodGroup.GetRelativeHeight(camera ?? Camera.current); 57 | 58 | var lodIndex = GetCurrentLOD(lods, lodGroup.GetMaxLOD(), relativeHeight, camera); 59 | 60 | return lodIndex; 61 | } 62 | 63 | public static int GetMaxLOD(this LODVolume.LODGroupHelper lodGroupHelper) 64 | { 65 | return lodGroupHelper.maxLOD; 66 | } 67 | 68 | public static int GetCurrentLOD(this LODVolume.LODGroupHelper lodGroupHelper, Camera camera = null, Vector3? cameraPosition = null) 69 | { 70 | var lods = lodGroupHelper.lods; 71 | camera = camera ?? Camera.current; 72 | cameraPosition = cameraPosition ?? camera.transform.position; 73 | var relativeHeight = lodGroupHelper.GetRelativeHeight(camera, cameraPosition.Value); 74 | return GetCurrentLOD(lods, lodGroupHelper.GetMaxLOD(), relativeHeight, camera); 75 | } 76 | 77 | public static float GetWorldSpaceSize(this LODGroup lodGroup) 78 | { 79 | return GetWorldSpaceScale(lodGroup.transform) * lodGroup.size; 80 | } 81 | 82 | static int GetCurrentLOD(LOD[] lods, int maxLOD, float relativeHeight, Camera camera = null) 83 | { 84 | var lodIndex = maxLOD; 85 | 86 | for (var i = 0; i < lods.Length; i++) 87 | { 88 | var lod = lods[i]; 89 | 90 | if (relativeHeight >= lod.screenRelativeTransitionHeight) 91 | { 92 | lodIndex = i; 93 | break; 94 | } 95 | } 96 | 97 | return lodIndex; 98 | } 99 | 100 | static float GetWorldSpaceScale(Transform t) 101 | { 102 | var scale = t.lossyScale; 103 | float largestAxis = Mathf.Abs(scale.x); 104 | largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.y)); 105 | largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.z)); 106 | return largestAxis; 107 | } 108 | 109 | static float DistanceToRelativeHeight(Camera camera, float distance, float size) 110 | { 111 | if (camera.orthographic) 112 | return size * 0.5F / camera.orthographicSize; 113 | 114 | var halfAngle = Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView * 0.5F); 115 | var relativeHeight = size * 0.5F / (distance * halfAngle); 116 | return relativeHeight; 117 | } 118 | 119 | static float GetRelativeHeight(this LODGroup lodGroup, Camera camera) 120 | { 121 | var distance = (lodGroup.transform.TransformPoint(lodGroup.localReferencePoint) - camera.transform.position).magnitude; 122 | return DistanceToRelativeHeight(camera, distance, lodGroup.GetWorldSpaceSize()); 123 | } 124 | 125 | static float GetRelativeHeight(this LODVolume.LODGroupHelper lodGroupHelper, Camera camera, Vector3 cameraPosition) 126 | { 127 | var distance = (lodGroupHelper.referencePoint - cameraPosition).magnitude; 128 | return DistanceToRelativeHeight(camera, distance, lodGroupHelper.worldSpaceSize); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoLOD 2 | Automatic LOD generation + scene optimization - Initial release was on January 12, 2018 via [blogpost](https://blogs.unity3d.com/2018/01/12/unity-labs-autolod-experimenting-with-automatic-performance-improvements/) 3 | 4 | AutoLOD is primarily a framework for enabling automatic post-processing of geometrical model assets on import to create simplified levels-of-detail (LOD). A [default mesh simplifier](https://github.com/Whinarn/UnityMeshSimplifier/) is included, but can be swapped out with other simplifiers and on a per-model basis if needed. Additionally, a whole scene can be hierarchically chunked into LODs with [SceneLOD](https://github.com/Unity-Technologies/AutoLOD/wiki/Scenelod). 5 | 6 | ## Experimental Status 7 | It’s important to note that AutoLOD is an experimental feature. As such, there is no formal support (e.g. FogBugz, support@unity3d.com, Premium Support, etc.) offered, so please do not use these channels. Instead, post your questions, comments, suggestions, and issues here on GitHub. 8 | 9 | **As with anything experimental/preview/alpha/beta, it is always a good idea to make a backup of your project before using.** 10 | 11 | Experimental means this: 12 | - Namespaces, classes, software architecture, prefabs, etc. can change at any point. If you are writing your own tools, then you might need to update them as these things change. 13 | - There won’t always be an upgrade path from one release to the next, so you might need to fix things manually, which leads to the next point... 14 | - Stuff can and will break (!) 15 | - There’s **no guarantee** that this project will move out of experimental status within any specific timeframe. 16 | - As such, there is no guarantee that this will remain an actively supported project. 17 | 18 | ## Features 19 | - LOD generation on model import with sensible [defaults](https://github.com/Unity-Technologies/AutoLOD/wiki/Home) 20 | - Project-wide and per-model LOD import settings 21 | - Asynchronous, pluggable LOD generation framework with built-in support for: 22 | - [UnityMeshSimplifier](https://github.com/Whinarn/UnityMeshSimplifier/) 23 | - [Simplygon](https://simplygon.com/) 24 | - [InstaLOD](https://instalod.com/) 25 | - Hierarchical LOD support via [SceneLOD](https://github.com/Unity-Technologies/AutoLOD/wiki/Scenelod) -> [watch a [quick tutorial video](http://www.youtube.com/watch?v=EuBeZvzVwrw "SceneLOD Tutorial")] 26 | 27 | ### Useful classes (for your own projects, too!) 28 | - [MonoBehaviourHelper](Scripts/Helpers/MonoBehaviourHelper.cs) - a way to run coroutines in the editor + main thread execution from worker threads 29 | - [LODGroupExtensions](Scripts/Extensions/LODGroupExtensions.cs) - useful extension methods (e.g. GetCurrentLOD) 30 | - [TimedEnumerator](Scripts/Helpers/TimedEnumerator.cs) - a way to control maximum execution time of coroutines 31 | - [TextureAtlasModule](Scripts/Editor/TextureAtlasModule.cs) - automatically generate texture atlases 32 | - [WorkingMesh](Scripts/Helpers/WorkingMesh.cs) - a thread-safe mesh (_and now job-friendly!_) struct 33 | 34 | ## Evaluating 35 | Unity 2018.4 (LTS) or a later version is required 36 | 37 | ### Install via package manager 38 | - Using the UI 39 | 1. Follow the steps provided [here](https://docs.unity3d.com/Manual/upm-ui-giturl.html). 40 | 2. The Git URL to use is `https://github.com/Unity-Technologies/AutoLOD.git` 41 | - Manually through editing manifest.json 42 | 1. Read the instructions from the official documentation [here](https://docs.unity3d.com/Manual/upm-git.html). 43 | 2. Open up *manifest.json* inside the *Packages* directory in your Unity project using a text editor. 44 | 3. Under the dependencies section of this file, you should add the following line at the top: 45 | ```"com.unity.autolod": "https://github.com/Unity-Technologies/AutoLOD.git",``` 46 | 1. You should now see something like this: 47 | ```json 48 | { 49 | "dependencies": { 50 | "com.unity.autolod": "https://github.com/Unity-Technologies/AutoLOD.git", 51 | "com.unity.ads": "2.0.8", 52 | "com.unity.analytics": "3.2.3", 53 | "com.unity.collab-proxy": "1.2.15", 54 | "...": "...", 55 | } 56 | } 57 | ``` 58 | 59 | 60 | ### Cloning locally to your project (requires [git-lfs](https://git-lfs.github.com/)) 61 | 1. Create a new Unity project or use an existing one 62 | 2. From the command line change directory to your project's `Packages` directory. 63 | 3. Run `git lfs clone https://github.com/Unity-Technologies/AutoLOD` 64 | 65 | ### Project Settings 66 | If you plan on making changes to AutoLOD and/or contributing back, then you'll need to set the `Asset Serialization` property under Edit->Project Settings->Editor to `Force Text`. 67 | 68 | ## License 69 | Unity Companion License (see [LICENSE](LICENSE)) 70 | 71 | ## All contributions are subject to the [Unity Contribution Agreement (UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) 72 | By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. 73 | 74 | ## Initial Contributors 75 | [Amir Ebrahimi](https://github.com/amirebrahimi/) 76 |
[Elliot Cuzzillo](https://github.com/ecuzzillo) 77 |
[Yuangguang Liao](https://github.com/liaoyg) 78 | 79 | ## Community Contributors 80 | [@Camarent](https://github.com/Camarent), [@LoneDev6](https://github.com/LoneDev6), [@marwie](https://github.com/marwie), [@msellens](https://github.com/msellens), [@redwyre](https://github.com/redwyre) -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/QuadricMeshSimplifier.cs: -------------------------------------------------------------------------------- 1 | #if ENABLE_UNITYMESHSIMPLIFIER 2 | using System; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityMeshSimplifier; 6 | using Mesh = Unity.AutoLOD.WorkingMesh; 7 | #endif 8 | 9 | #if ENABLE_UNITYMESHSIMPLIFIER 10 | namespace Unity.AutoLOD 11 | { 12 | public struct QuadricMeshSimplifier : IMeshSimplifier, IPreferences 13 | { 14 | [InitializeOnLoad] 15 | class Preferences : ScriptableSingleton 16 | { 17 | const string k_Options = "AutoLOD.QuadricMeshSimplifier.Options"; 18 | 19 | public static SimplificationOptions Options; 20 | 21 | // Needed for SerializedObject/SerializedProperty 22 | [SerializeField] 23 | internal SimplificationOptions m_Options; 24 | 25 | // Load the options statically, so they can be used in jobs 26 | static Preferences() 27 | { 28 | EditorApplication.delayCall += () => 29 | { 30 | var preferences = Preferences.instance; 31 | Debug.Assert(!preferences.Equals(default), "QuadricMeshSimplifier preferences should never be the default struct"); 32 | }; 33 | } 34 | 35 | void OnEnable() 36 | { 37 | // It's okay that it doesn't save, but don't hide it otherwise the inspector GUI won't work 38 | hideFlags = HideFlags.DontSave; 39 | 40 | var savedPrefs = EditorPrefs.GetString(k_Options, null); 41 | if (string.IsNullOrEmpty(savedPrefs)) 42 | m_Options = SimplificationOptions.Default; 43 | else 44 | m_Options = JsonUtility.FromJson(savedPrefs); 45 | 46 | // Update the static version 47 | Options = m_Options; 48 | } 49 | 50 | public void ResetToDefaults() 51 | { 52 | m_Options = SimplificationOptions.Default; 53 | Save(); 54 | } 55 | 56 | public void Save() 57 | { 58 | var savedPrefs = JsonUtility.ToJson(m_Options); 59 | EditorPrefs.SetString(k_Options, savedPrefs); 60 | Options = m_Options; 61 | } 62 | } 63 | 64 | SerializedObject m_SerializedObject; 65 | 66 | public void Simplify(Mesh inputMesh, Mesh outputMesh, float quality) 67 | { 68 | var meshSimplifier = new MeshSimplifier(); 69 | meshSimplifier.SimplificationOptions = Preferences.Options; 70 | meshSimplifier.Vertices = inputMesh.vertices; 71 | meshSimplifier.Normals = inputMesh.normals; 72 | meshSimplifier.Tangents = inputMesh.tangents; 73 | meshSimplifier.UV1 = inputMesh.uv; 74 | meshSimplifier.UV2 = inputMesh.uv2; 75 | meshSimplifier.UV3 = inputMesh.uv3; 76 | meshSimplifier.UV4 = inputMesh.uv4; 77 | meshSimplifier.Colors = inputMesh.colors; 78 | 79 | var triangles = new int[inputMesh.subMeshCount][]; 80 | for (var submesh = 0; submesh < inputMesh.subMeshCount; submesh++) 81 | { 82 | triangles[submesh] = inputMesh.GetTriangles(submesh); 83 | } 84 | meshSimplifier.AddSubMeshTriangles(triangles); 85 | 86 | meshSimplifier.SimplifyMesh(quality); 87 | 88 | outputMesh.vertices = meshSimplifier.Vertices; 89 | outputMesh.normals = meshSimplifier.Normals; 90 | outputMesh.tangents = meshSimplifier.Tangents; 91 | outputMesh.uv = meshSimplifier.UV1; 92 | outputMesh.uv2 = meshSimplifier.UV2; 93 | outputMesh.uv3 = meshSimplifier.UV3; 94 | outputMesh.uv4 = meshSimplifier.UV4; 95 | outputMesh.colors = meshSimplifier.Colors; 96 | outputMesh.subMeshCount = meshSimplifier.SubMeshCount; 97 | for (var submesh = 0; submesh < outputMesh.subMeshCount; submesh++) 98 | { 99 | outputMesh.SetTriangles(meshSimplifier.GetSubMeshTriangles(submesh), submesh); 100 | } 101 | } 102 | 103 | public void OnPreferencesGUI() 104 | { 105 | var preferences = Preferences.instance; 106 | if (m_SerializedObject == null) 107 | m_SerializedObject = new SerializedObject(preferences); 108 | 109 | m_SerializedObject.Update(); 110 | var property = m_SerializedObject.FindProperty("m_Options"); 111 | 112 | EditorGUI.BeginChangeCheck(); 113 | 114 | EditorGUILayout.BeginHorizontal(); 115 | EditorGUILayout.PropertyField(property); 116 | if (property.isExpanded) 117 | { 118 | GUI.enabled = !preferences.m_Options.Equals(SimplificationOptions.Default); 119 | if (GUILayout.Button("Reset", EditorStyles.miniButton, GUILayout.ExpandWidth(false))) 120 | { 121 | preferences.ResetToDefaults(); 122 | EditorGUIUtility.ExitGUI(); 123 | } 124 | GUI.enabled = true; 125 | } 126 | EditorGUILayout.EndHorizontal(); 127 | if (property.isExpanded) 128 | { 129 | EditorGUI.indentLevel++; 130 | property.NextVisible(true); 131 | do 132 | { 133 | EditorGUILayout.BeginHorizontal(); 134 | EditorGUILayout.LabelField(property.name, GUILayout.Width(200f)); 135 | EditorGUILayout.PropertyField(property, GUIContent.none, true, GUILayout.ExpandWidth(false)); 136 | EditorGUILayout.EndHorizontal(); 137 | } while (property.NextVisible(false)); 138 | EditorGUI.indentLevel--; 139 | } 140 | 141 | if (EditorGUI.EndChangeCheck()) 142 | { 143 | m_SerializedObject.ApplyModifiedProperties(); 144 | preferences.Save(); 145 | } 146 | } 147 | } 148 | } 149 | #endif -------------------------------------------------------------------------------- /Editor/Batchers/SimpleBatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using UnityEngine.Rendering; 8 | 9 | namespace Unity.AutoLOD 10 | { 11 | /// 12 | /// A simple batcher that combines textures into an atlas and meshes (non material-preserving) 13 | /// 14 | class SimpleBatcher : IBatcher 15 | { 16 | Texture2D whiteTexture 17 | { 18 | get 19 | { 20 | if (!m_WhiteTexture) 21 | { 22 | var path = "Assets/AutoLOD/Generated/Atlases/white.asset"; 23 | m_WhiteTexture = AssetDatabase.LoadAssetAtPath(path); 24 | if (!m_WhiteTexture) 25 | { 26 | m_WhiteTexture = Object.Instantiate(Texture2D.whiteTexture); 27 | var directory = Path.GetDirectoryName(path); 28 | if (!Directory.Exists(directory)) 29 | Directory.CreateDirectory(directory); 30 | 31 | AssetDatabase.CreateAsset(m_WhiteTexture, path); 32 | } 33 | } 34 | 35 | return m_WhiteTexture; 36 | } 37 | } 38 | 39 | Texture2D m_WhiteTexture; 40 | 41 | public IEnumerator Batch(GameObject go) 42 | { 43 | var renderers = go.GetComponentsInChildren(); 44 | var materials = new HashSet(renderers.SelectMany(r => r.sharedMaterials)); 45 | 46 | var textures = new HashSet(materials.Select(m => 47 | { 48 | if (m) 49 | return m.mainTexture as Texture2D; 50 | 51 | return null; 52 | }).Where(t => t != null)).ToList(); 53 | textures.Add(whiteTexture); 54 | 55 | TextureAtlas atlas = null; 56 | yield return TextureAtlasModule.instance.GetTextureAtlas(textures.ToArray(), a => atlas = a); 57 | 58 | var atlasLookup = new Dictionary(); 59 | var atlasTextures = atlas.textures; 60 | for (int i = 0; i < atlasTextures.Length; i++) 61 | { 62 | atlasLookup[atlasTextures[i]] = atlas.uvs[i]; 63 | } 64 | 65 | MeshFilter[] meshFilters = go.GetComponentsInChildren(); 66 | var combine = new List(); 67 | for (int i = 0; i < meshFilters.Length; i++) 68 | { 69 | var mf = meshFilters[i]; 70 | var sharedMesh = mf.sharedMesh; 71 | 72 | if (!sharedMesh) 73 | continue; 74 | 75 | if (!sharedMesh.isReadable) 76 | { 77 | var assetPath = AssetDatabase.GetAssetPath(sharedMesh); 78 | if (!string.IsNullOrEmpty(assetPath)) 79 | { 80 | var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter; 81 | if (importer) 82 | { 83 | importer.isReadable = true; 84 | importer.SaveAndReimport(); 85 | } 86 | } 87 | } 88 | 89 | var ci = new CombineInstance(); 90 | 91 | var mesh = Object.Instantiate(sharedMesh); 92 | 93 | var mr = mf.GetComponent(); 94 | var sharedMaterials = mr.sharedMaterials; 95 | var uv = mesh.uv; 96 | var colors = mesh.colors; 97 | if (colors == null || colors.Length == 0) 98 | colors = new Color[uv.Length]; 99 | var updated = new bool[uv.Length]; 100 | var triangles = new List(); 101 | 102 | // Some meshes have submeshes that either aren't expected to render or are missing a material, so go ahead and skip 103 | var subMeshCount = Mathf.Min(mesh.subMeshCount, sharedMaterials.Length); 104 | for (int j = 0; j < subMeshCount; j++) 105 | { 106 | var sharedMaterial = sharedMaterials[Mathf.Min(j, sharedMaterials.Length - 1)]; 107 | var mainTexture = whiteTexture; 108 | var materialColor = Color.white; 109 | 110 | if (sharedMaterial) 111 | { 112 | var texture = sharedMaterial.mainTexture as Texture2D; 113 | if (texture) 114 | mainTexture = texture; 115 | 116 | if (sharedMaterial.HasProperty("_Color")) 117 | materialColor = sharedMaterial.color; 118 | } 119 | 120 | if (mesh.GetTopology(j) != MeshTopology.Triangles) 121 | { 122 | Debug.LogWarning("Mesh must have triangles", mf); 123 | continue; 124 | } 125 | 126 | triangles.Clear(); 127 | mesh.GetTriangles(triangles, j); 128 | var uvOffset = atlasLookup[mainTexture]; 129 | foreach (var t in triangles) 130 | { 131 | if (!updated[t]) 132 | { 133 | var uvCoord = uv[t]; 134 | if (mainTexture == whiteTexture) 135 | { 136 | // Sample at center of white texture to avoid sampling edge colors incorrectly 137 | uvCoord.x = 0.5f; 138 | uvCoord.y = 0.5f; 139 | } 140 | 141 | uvCoord.x = Mathf.Lerp(uvOffset.xMin, uvOffset.xMax, uvCoord.x); 142 | uvCoord.y = Mathf.Lerp(uvOffset.yMin, uvOffset.yMax, uvCoord.y); 143 | uv[t] = uvCoord; 144 | 145 | if (mainTexture == whiteTexture) 146 | colors[t] = materialColor; 147 | else 148 | colors[t] = Color.white; 149 | 150 | updated[t] = true; 151 | } 152 | } 153 | 154 | yield return null; 155 | } 156 | mesh.uv = uv; 157 | mesh.uv2 = null; 158 | mesh.colors = colors; 159 | 160 | ci.mesh = mesh; 161 | ci.transform = mf.transform.localToWorldMatrix; 162 | combine.Add(ci); 163 | 164 | mf.gameObject.SetActive(false); 165 | 166 | yield return null; 167 | } 168 | 169 | var combinedMesh = new Mesh(); 170 | combinedMesh.indexFormat = IndexFormat.UInt32; 171 | combinedMesh.CombineMeshes(combine.ToArray(), true, true); 172 | combinedMesh.RecalculateBounds(); 173 | var meshFilter = go.AddComponent(); 174 | meshFilter.sharedMesh = combinedMesh; 175 | 176 | for (int i = 0; i < meshFilters.Length; i++) 177 | { 178 | Object.DestroyImmediate(meshFilters[i].gameObject); 179 | } 180 | 181 | var meshRenderer = go.AddComponent(); 182 | var material = new Material(Shader.Find("Custom/AutoLOD/SimpleBatcher")); 183 | material.mainTexture = atlas.textureAtlas; 184 | meshRenderer.sharedMaterial = material; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Runtime/Helpers/MonoBehaviourHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using Unity.AutoLOD.Utilities; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | namespace Unity.AutoLOD 10 | { 11 | // Useful for launching co-routines in the Editor or executing something on the main thread 12 | #if UNITY_EDITOR 13 | [InitializeOnLoad] 14 | #else 15 | public class ScriptableSingleton : ScriptableObject where T : ScriptableObject 16 | { 17 | static T s_Instance; 18 | 19 | public ScriptableSingleton() 20 | { 21 | if (s_Instance != null) 22 | Debug.LogError((object) "ScriptableSingleton already exists. Did you query the singleton in a constructor?"); 23 | else 24 | s_Instance = this as T; 25 | } 26 | 27 | public static T instance 28 | { 29 | get 30 | { 31 | if (s_Instance == null) 32 | CreateInstance().hideFlags = HideFlags.HideAndDontSave; 33 | 34 | return s_Instance; 35 | } 36 | } 37 | } 38 | #endif 39 | 40 | public class MonoBehaviourHelper : ScriptableSingleton 41 | { 42 | partial class Surrogate : MonoBehaviour { } 43 | 44 | Surrogate m_MonoBehaviour; 45 | 46 | MonoBehaviour monoBehaviour 47 | { 48 | get 49 | { 50 | if (!m_MonoBehaviour) 51 | { 52 | #if UNITY_EDITOR 53 | var go = EditorUtility.CreateGameObjectWithHideFlags("MonoBehaviourHelper", HideFlags.DontSave, typeof(Surrogate)); 54 | #else 55 | var go = new GameObject("MonoBehaviourHelper", typeof(Surrogate)); 56 | #endif 57 | m_MonoBehaviour = go.GetComponent(); 58 | #if UNITY_EDITOR 59 | m_MonoBehaviour.runInEditMode = true; 60 | #endif 61 | } 62 | 63 | return m_MonoBehaviour; 64 | } 65 | } 66 | 67 | public static float? maxSharedExecutionTimeMS { get; set; } 68 | 69 | List m_Coroutines = new List(); 70 | readonly Queue m_MainThreadActionQueue = new Queue(); 71 | 72 | static int s_MainThreadId; 73 | 74 | public static Coroutine StartCoroutine(IEnumerator routine, float? maxIterationTimeMS = null) 75 | { 76 | return instance.monoBehaviour.StartCoroutine(CoroutineWrapper(routine, maxIterationTimeMS)); 77 | } 78 | 79 | static IEnumerator CoroutineWrapper(IEnumerator routine, float? maxIterationTimeMS = null) 80 | { 81 | if (maxSharedExecutionTimeMS.HasValue) 82 | { 83 | if (maxIterationTimeMS.HasValue) 84 | Debug.Log("Overriding specified iteration time because there is a shared time set."); 85 | 86 | maxIterationTimeMS = maxSharedExecutionTimeMS / (instance.m_Coroutines.Count + 1); 87 | } 88 | 89 | var timedEnumerator = new TimedEnumerator(routine, maxIterationTimeMS); 90 | 91 | //timedEnumerator.logEnabled = true; 92 | instance.m_Coroutines.Add(timedEnumerator); 93 | yield return timedEnumerator; 94 | instance.m_Coroutines.Remove(timedEnumerator); 95 | } 96 | 97 | static IEnumerator ActionCoroutine(Action action) 98 | { 99 | action(); 100 | yield break; 101 | } 102 | 103 | // Simplified version of https://github.com/PimDeWitte/UnityMainThreadDispatcher 104 | public static void ExecuteOnMainThread(Action action) 105 | { 106 | if (IsMainThread()) 107 | { 108 | if (instance) 109 | action(); 110 | } 111 | else 112 | { 113 | var completed = false; 114 | Action completionWrapper = () => 115 | { 116 | action(); 117 | completed = true; 118 | }; 119 | 120 | var actionQueue = instance.m_MainThreadActionQueue; 121 | lock (actionQueue) 122 | actionQueue.Enqueue(completionWrapper); 123 | 124 | while (!completed) 125 | Thread.Sleep(100); 126 | } 127 | } 128 | 129 | public static bool IsMainThread() 130 | { 131 | return Thread.CurrentThread.ManagedThreadId == s_MainThreadId; 132 | } 133 | 134 | static MonoBehaviourHelper() 135 | { 136 | s_MainThreadId = Thread.CurrentThread.ManagedThreadId; 137 | } 138 | 139 | void OnEnable() 140 | { 141 | #if UNITY_EDITOR 142 | EditorApplication.update += EditorUpdate; 143 | #endif 144 | } 145 | 146 | void OnDisable() 147 | { 148 | #if UNITY_EDITOR 149 | EditorApplication.update -= EditorUpdate; 150 | #endif 151 | 152 | if (m_MonoBehaviour) 153 | DestroyImmediate(m_MonoBehaviour.gameObject); 154 | } 155 | 156 | #if !UNITY_EDITOR 157 | void Update() 158 | { 159 | EditorUpdate(); 160 | } 161 | #endif 162 | 163 | static void EditorUpdate() 164 | { 165 | var actionQueue = instance.m_MainThreadActionQueue; 166 | lock (actionQueue) 167 | { 168 | while (actionQueue.Count > 0) 169 | StartCoroutine(ActionCoroutine(actionQueue.Dequeue())); 170 | } 171 | 172 | var coroutines = instance.m_Coroutines; 173 | if (coroutines.Count > 0) 174 | { 175 | if (maxSharedExecutionTimeMS.HasValue) 176 | { 177 | var timeShare = maxSharedExecutionTimeMS / coroutines.Count; 178 | foreach (var co in coroutines) 179 | { 180 | co.maxIterationTimeMS = timeShare; 181 | } 182 | } 183 | 184 | #if UNITY_EDITOR 185 | // this is necessary to cause our GameObject co-routines to tick at a stable frequency 186 | EditorApplication.QueuePlayerLoopUpdate(); 187 | #endif 188 | } 189 | } 190 | 191 | partial class Surrogate 192 | { 193 | // 194 | // Coroutine tests 195 | // 196 | [ContextMenu("CoTest")] 197 | void CoTest() 198 | { 199 | //StartCoroutine(TestRoutine()); 200 | StartCoroutine(PrintNumbersSlowly()); 201 | } 202 | 203 | [ContextMenu("MaxTimeTest")] 204 | void MaxTimeTest() 205 | { 206 | maxSharedExecutionTimeMS = 1f; 207 | 208 | //((MonoBehaviour)instance).StartCoroutine(ContinuallyFindObjects()); 209 | //StartCoroutine(ContinuallyFindObjects()); 210 | //StartCoroutine(ContinuallyFindObjects(), 1f); 211 | //StartCoroutine(ContinuallyFindObjects(), 1f); 212 | 213 | MonoBehaviourHelper.StartCoroutine(BurnCycles(), 1f); 214 | MonoBehaviourHelper.StartCoroutine(BurnCycles(), 1f); 215 | 216 | //((MonoBehaviour)instance).StartCoroutine(ContinuallyFindObjects()); 217 | } 218 | 219 | IEnumerator ContinuallyFindObjects() 220 | { 221 | List renderers = new List(); 222 | 223 | var counter = 0; 224 | while (true) 225 | { 226 | counter++; 227 | renderers.Clear(); 228 | 229 | yield return ObjectUtils.FindObjectsOfType(renderers); 230 | 231 | Debug.Log("Renderers: " + renderers.Count); 232 | 233 | if (counter > 1000) 234 | break; 235 | } 236 | } 237 | 238 | IEnumerator BurnCycles() 239 | { 240 | var counter = 0; 241 | while (true) 242 | { 243 | counter++; 244 | yield return null; 245 | 246 | if (counter > 10000) 247 | break; 248 | } 249 | } 250 | 251 | IEnumerator TestRoutine() 252 | { 253 | yield return StartCoroutine(PrintNumbersSlowly()); 254 | } 255 | 256 | IEnumerator PrintNumbersSlowly() 257 | { 258 | for (int i = 0; i < 10; i++) 259 | { 260 | Debug.Log(i); 261 | yield return new WaitForSecondsRealtime(1f); 262 | } 263 | } 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /Editor/MeshSimplifiers/Simplygon/SimplygonMeshSimplifier.cs: -------------------------------------------------------------------------------- 1 | #if ENABLE_SIMPLYGON 2 | using Simplygon; 3 | using Simplygon.Unity.EditorPlugin; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using Unity.Formats.USD; 10 | using USD.NET; 11 | using USD.NET.Unity; 12 | 13 | namespace Unity.AutoLOD 14 | { 15 | public struct SimplygonMeshSimplifier : IMeshSimplifier 16 | { 17 | static pxr.TfToken s_MaterialBindToken = new pxr.TfToken("materialBind"); 18 | static pxr.TfToken s_SubMeshesToken = new pxr.TfToken("subMeshes"); 19 | 20 | static object s_ExecutionLock = new object(); 21 | static ISimplygon s_Simplygon; 22 | static int s_ReferenceCount; 23 | 24 | public void Simplify(WorkingMesh inputMesh, WorkingMesh outputMesh, float quality) 25 | { 26 | // We can only have one instance of Simplygon, but we can use it from multiple threads once initialized 27 | lock (s_ExecutionLock) 28 | { 29 | if (s_Simplygon == null) 30 | { 31 | s_Simplygon = Loader.InitSimplygon(out var simplygonErrorCode, out var simplygonErrorMessage); 32 | if (s_Simplygon == null && simplygonErrorCode != EErrorCodes.NoError) 33 | Debug.Log($"Initializing failed! {simplygonErrorCode}: {simplygonErrorMessage}"); 34 | } 35 | 36 | s_ReferenceCount++; 37 | } 38 | 39 | var simplygon = s_Simplygon; 40 | if (simplygon != null) 41 | { 42 | string exportTempDirectory = SimplygonUtils.GetNewTempDirectory(); 43 | 44 | using (spScene sgScene = ExportSimplygonScene(simplygon, exportTempDirectory, inputMesh)) 45 | { 46 | using (spReductionPipeline reductionPipeline = simplygon.CreateReductionPipeline()) 47 | using (spReductionSettings reductionSettings = reductionPipeline.GetReductionSettings()) 48 | { 49 | reductionSettings.SetReductionTargets(EStopCondition.All, true, false, false, false); 50 | reductionSettings.SetReductionTargetTriangleRatio(quality); 51 | 52 | reductionPipeline.RunScene(sgScene, EPipelineRunMode.RunInThisProcess); 53 | 54 | MonoBehaviourHelper.ExecuteOnMainThread(() => 55 | { 56 | string baseFolder = "Assets/SimplygonTemp"; 57 | if (!AssetDatabase.IsValidFolder(baseFolder)) 58 | AssetDatabase.CreateFolder("Assets", "SimplygonTemp"); 59 | 60 | string meshName = inputMesh.name; 61 | string assetFolderGuid = AssetDatabase.CreateFolder(baseFolder, meshName); 62 | string assetFolderPath = AssetDatabase.GUIDToAssetPath(assetFolderGuid); 63 | 64 | int startingLodIndex = 0; 65 | List importedGameObjects = new List(); 66 | SimplygonImporter.Import(simplygon, reductionPipeline, ref startingLodIndex, 67 | assetFolderPath, meshName, importedGameObjects); 68 | 69 | Debug.Assert(importedGameObjects.Count == 1, "AutoLOD: There should only be one imported mesh."); 70 | if (importedGameObjects.Count == 1) 71 | { 72 | GameObject go = importedGameObjects[0]; 73 | MeshFilter mf = go.GetComponentInChildren(); 74 | mf.sharedMesh.ApplyToWorkingMesh(ref outputMesh); 75 | } 76 | 77 | foreach (var go in importedGameObjects) 78 | { 79 | GameObject.DestroyImmediate(go); 80 | } 81 | 82 | AssetDatabase.DeleteAsset(baseFolder); 83 | }); 84 | } 85 | } 86 | } 87 | 88 | lock (s_ExecutionLock) 89 | { 90 | s_ReferenceCount--; 91 | 92 | // Clean up on our way out if we are the last thread using the Simplygon singleton 93 | if (s_Simplygon != null && s_ReferenceCount == 0) 94 | { 95 | s_Simplygon.Dispose(); 96 | s_Simplygon = null; 97 | } 98 | } 99 | } 100 | 101 | static spScene ExportSimplygonScene(ISimplygon simplygon, string tempDirectory, WorkingMesh mesh) 102 | { 103 | if (string.IsNullOrEmpty(tempDirectory)) 104 | return (spScene)null; 105 | 106 | string filePath = Path.Combine(tempDirectory, "export.usd"); 107 | InitUsd.Initialize(); 108 | Scene scene = Scene.Create(filePath); 109 | 110 | var context = new ExportContext(); 111 | context.scene = scene; 112 | context.basisTransform = BasisTransformation.SlowAndSafe; 113 | // context.exportRoot = root.transform.parent; 114 | 115 | ExportMesh(context, mesh); 116 | 117 | scene.Save(); 118 | scene.Close(); 119 | 120 | using (spSceneImporter sceneImporter = simplygon.CreateSceneImporter()) 121 | { 122 | sceneImporter.SetImportFilePath(filePath); 123 | sceneImporter.RunImport(); 124 | // SimplygonExporter.ExportSelectionSetsInSelection(simplygon, sceneImporter.GetScene(), selectedGameObjects, rootName); 125 | return sceneImporter.GetScene(); 126 | } 127 | } 128 | 129 | static void ExportMesh(ExportContext exportContext, WorkingMesh mesh) 130 | { 131 | // path = /build_bighouse_02/build_bighouse_01_dragonhead_01_LOD0 132 | var path = new pxr.SdfPath($"/{mesh.name}"); 133 | 134 | var scene = exportContext.scene; 135 | bool slowAndSafeConversion = exportContext.basisTransform == BasisTransformation.SlowAndSafe; 136 | var sample = new MeshSample(); 137 | 138 | if (mesh.bounds.center == Vector3.zero && mesh.bounds.extents == Vector3.zero) 139 | { 140 | mesh.RecalculateBounds(); 141 | } 142 | 143 | sample.extent = mesh.bounds; 144 | 145 | if (slowAndSafeConversion) 146 | { 147 | // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of 148 | // basis is required. There are shortcuts, but this is fully general. 149 | sample.ConvertTransform(); 150 | sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); 151 | } 152 | 153 | // TODO: Technically a mesh could be the root transform, which is not handled correctly here. 154 | // It should have the same logic for root prims as in ExportXform. 155 | // sample.transform = XformExporter.GetLocalTransformMatrix( 156 | // null, 157 | // scene.UpAxis == Scene.UpAxes.Z, 158 | // new pxr.SdfPath(path).IsRootPrimPath(), 159 | // exportContext.basisTransform); 160 | 161 | sample.normals = mesh.normals; 162 | sample.points = mesh.vertices; 163 | sample.tangents = mesh.tangents; 164 | 165 | sample.colors = mesh.colors; 166 | if (sample.colors != null && sample.colors.Length == 0) 167 | { 168 | sample.colors = null; 169 | } 170 | 171 | // Gah. There is no way to inspect a meshes UVs. 172 | sample.st = mesh.uv; 173 | 174 | // Set face vertex counts and indices. 175 | var tris = mesh.triangles; 176 | 177 | if (slowAndSafeConversion) 178 | { 179 | // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change 180 | // of basis is required. There are shortcuts, but this is fully general. 181 | 182 | for (int i = 0; i < sample.points.Length; i++) 183 | { 184 | sample.points[i] = UnityTypeConverter.ChangeBasis(sample.points[i]); 185 | if (sample.normals != null && sample.normals.Length == sample.points.Length) 186 | { 187 | sample.normals[i] = UnityTypeConverter.ChangeBasis(sample.normals[i]); 188 | } 189 | 190 | if (sample.tangents != null && sample.tangents.Length == sample.points.Length) 191 | { 192 | var w = sample.tangents[i].w; 193 | var t = UnityTypeConverter.ChangeBasis(sample.tangents[i]); 194 | sample.tangents[i] = new Vector4(t.x, t.y, t.z, w); 195 | } 196 | } 197 | 198 | for (int i = 0; i < tris.Length; i += 3) 199 | { 200 | var t = tris[i]; 201 | tris[i] = tris[i + 1]; 202 | tris[i + 1] = t; 203 | } 204 | 205 | sample.SetTriangles(tris); 206 | 207 | scene.Write(path, sample); 208 | 209 | // TODO: this is a bit of a half-measure, we need real support for primvar interpolation. 210 | // Set interpolation based on color count. 211 | if (sample.colors != null && sample.colors.Length == 1) 212 | { 213 | pxr.UsdPrim usdPrim = scene.GetPrimAtPath(path); 214 | var colorPrimvar = 215 | new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayColor)); 216 | colorPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); 217 | var opacityPrimvar = 218 | new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayOpacity)); 219 | opacityPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); 220 | } 221 | 222 | // In USD subMeshes are represented as UsdGeomSubsets. 223 | // When there are multiple subMeshes, convert them into UsdGeomSubsets. 224 | if (mesh.subMeshCount > 1) 225 | { 226 | // Build a table of face indices, used to convert the subMesh triangles to face indices. 227 | var faceTable = new Dictionary(); 228 | for (int i = 0; i < tris.Length; i += 3) 229 | { 230 | if (!slowAndSafeConversion) 231 | { 232 | faceTable.Add(new Vector3(tris[i], tris[i + 1], tris[i + 2]), i / 3); 233 | } 234 | else 235 | { 236 | // Under slow and safe export, index 0 and 1 are swapped. 237 | // This swap will not be present in the subMesh indices, so must be undone here. 238 | faceTable.Add(new Vector3(tris[i + 1], tris[i], tris[i + 2]), i / 3); 239 | } 240 | } 241 | 242 | var usdPrim = scene.GetPrimAtPath(path); 243 | var usdGeomMesh = new pxr.UsdGeomMesh(usdPrim); 244 | 245 | // Process each subMesh and create a UsdGeomSubset of faces this subMesh targets. 246 | for (int si = 0; si < mesh.subMeshCount; si++) 247 | { 248 | int[] indices = mesh.GetTriangles(si); 249 | int[] faceIndices = new int[indices.Length / 3]; 250 | 251 | for (int i = 0; i < indices.Length; i += 3) 252 | { 253 | faceIndices[i / 3] = faceTable[new Vector3(indices[i], indices[i + 1], indices[i + 2])]; 254 | } 255 | 256 | var vtIndices = UnityTypeConverter.ToVtArray(faceIndices); 257 | var subset = pxr.UsdGeomSubset.CreateUniqueGeomSubset( 258 | usdGeomMesh, // The object of which this subset belongs. 259 | s_SubMeshesToken, // An arbitrary name for the subset. 260 | pxr.UsdGeomTokens.face, // Indicator that these represent face indices 261 | vtIndices, // The actual face indices. 262 | s_MaterialBindToken // familyName = "materialBind" 263 | ); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | } 270 | #endif -------------------------------------------------------------------------------- /Runtime/Utilities/ObjectUtils.cs: -------------------------------------------------------------------------------- 1 | // From: https://github.com/Unity-Technologies/EditorVR 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using UnityEditor; 9 | using UnityEngine; 10 | using UnityEngine.SceneManagement; 11 | using UnityObject = UnityEngine.Object; 12 | 13 | namespace Unity.AutoLOD.Utilities 14 | { 15 | /// 16 | /// Object related EditorVR utilities 17 | /// 18 | public static class ObjectUtils 19 | { 20 | public static HideFlags hideFlags 21 | { 22 | get { return s_HideFlags; } 23 | set { s_HideFlags = value; } 24 | } 25 | 26 | static HideFlags s_HideFlags = HideFlags.DontSave; 27 | static List s_RootGameObjects = new List(); 28 | 29 | public static GameObject Instantiate(GameObject prefab, Transform parent = null, bool worldPositionStays = true, bool runInEditMode = true, bool active = true) 30 | { 31 | var go = UnityObject.Instantiate(prefab, parent, worldPositionStays); 32 | if (worldPositionStays) 33 | { 34 | var goTransform = go.transform; 35 | var prefabTransform = prefab.transform; 36 | goTransform.position = prefabTransform.position; 37 | goTransform.rotation = prefabTransform.rotation; 38 | } 39 | 40 | go.SetActive(active); 41 | if (!Application.isPlaying && runInEditMode) 42 | { 43 | SetRunInEditModeRecursively(go, runInEditMode); 44 | go.hideFlags = hideFlags; 45 | } 46 | 47 | return go; 48 | } 49 | 50 | public static void RemoveAllChildren(GameObject obj) 51 | { 52 | var children = new List(); 53 | foreach (Transform child in obj.transform) 54 | children.Add(child.gameObject); 55 | 56 | foreach (var child in children) 57 | UnityObject.Destroy(child); 58 | } 59 | 60 | public static bool IsInLayer(GameObject o, string s) 61 | { 62 | return o.layer == LayerMask.NameToLayer(s); 63 | } 64 | 65 | /// 66 | /// Create an empty VR GameObject. 67 | /// 68 | /// Name of the new GameObject 69 | /// Transform to parent new object under 70 | /// The newly created empty GameObject 71 | public static GameObject CreateEmptyGameObject(string name = null, Transform parent = null) 72 | { 73 | GameObject empty = null; 74 | if (string.IsNullOrEmpty(name)) 75 | name = "New Game Object"; 76 | 77 | #if UNITY_EDITOR 78 | empty = EditorUtility.CreateGameObjectWithHideFlags(name, hideFlags); 79 | #else 80 | empty = new GameObject(name); 81 | empty.hideFlags = hideFlags; 82 | #endif 83 | empty.transform.parent = parent; 84 | empty.transform.localPosition = Vector3.zero; 85 | 86 | return empty; 87 | } 88 | 89 | public static T CreateGameObjectWithComponent(Transform parent = null, bool worldPositionStays = true) where T : Component 90 | { 91 | return (T)CreateGameObjectWithComponent(typeof(T), parent, worldPositionStays); 92 | } 93 | 94 | public static Component CreateGameObjectWithComponent(Type type, Transform parent = null, bool worldPositionStays = true) 95 | { 96 | #if UNITY_EDITOR 97 | var component = EditorUtility.CreateGameObjectWithHideFlags(type.Name, hideFlags, type).GetComponent(type); 98 | if (!Application.isPlaying) 99 | SetRunInEditModeRecursively(component.gameObject, true); 100 | #else 101 | var component = new GameObject(type.Name).AddComponent(type); 102 | #endif 103 | component.transform.SetParent(parent, worldPositionStays); 104 | 105 | return component; 106 | } 107 | 108 | public static void SetLayerRecursively(GameObject root, int layer) 109 | { 110 | var transforms = root.GetComponentsInChildren(); 111 | for (var i = 0; i < transforms.Length; i++) 112 | transforms[i].gameObject.layer = layer; 113 | } 114 | 115 | public static Bounds GetBounds(Transform[] transforms) 116 | { 117 | Bounds? bounds = null; 118 | foreach (var go in transforms) 119 | { 120 | var goBounds = GetBounds(go); 121 | if (!bounds.HasValue) 122 | { 123 | bounds = goBounds; 124 | } 125 | else 126 | { 127 | goBounds.Encapsulate(bounds.Value); 128 | bounds = goBounds; 129 | } 130 | } 131 | return bounds ?? new Bounds(); 132 | } 133 | 134 | public static Bounds GetBounds(Transform transform) 135 | { 136 | var b = new Bounds(transform.position, Vector3.zero); 137 | var renderers = transform.GetComponentsInChildren(); 138 | for (int i = 0; i < renderers.Length; i++) 139 | { 140 | var r = renderers[i]; 141 | if (r.bounds.size != Vector3.zero) 142 | b.Encapsulate(r.bounds); 143 | } 144 | 145 | // As a fallback when there are no bounds, collect all transform positions 146 | if (b.size == Vector3.zero) 147 | { 148 | var transforms = transform.GetComponentsInChildren(); 149 | foreach (var t in transforms) 150 | b.Encapsulate(t.position); 151 | } 152 | 153 | return b; 154 | } 155 | 156 | public static IEnumerator GetBounds(List renderers, Action callback) 157 | { 158 | Bounds bounds = new Bounds(); 159 | for (int i = 0; i < renderers.Count; i++) 160 | { 161 | var r = renderers[i]; 162 | if (i == 0) 163 | bounds = r.bounds; 164 | else 165 | bounds.Encapsulate(r.bounds); 166 | 167 | yield return null; 168 | } 169 | 170 | callback(bounds); 171 | } 172 | 173 | public static void SetRunInEditModeRecursively(GameObject go, bool enabled) 174 | { 175 | #if UNITY_EDITOR 176 | var monoBehaviours = go.GetComponents(); 177 | foreach (var mb in monoBehaviours) 178 | { 179 | if (mb) 180 | mb.runInEditMode = enabled; 181 | } 182 | 183 | foreach (Transform child in go.transform) 184 | { 185 | SetRunInEditModeRecursively(child.gameObject, enabled); 186 | } 187 | #endif 188 | } 189 | 190 | public static T AddComponent(GameObject go) where T : Component 191 | { 192 | return (T)AddComponent(typeof(T), go); 193 | } 194 | 195 | public static Component AddComponent(Type type, GameObject go) 196 | { 197 | var component = go.AddComponent(type); 198 | SetRunInEditModeRecursively(go, true); 199 | return component; 200 | } 201 | 202 | static IEnumerable GetAssignableTypes(Type type, Func predicate = null) 203 | { 204 | var list = new List(); 205 | ForEachType(t => 206 | { 207 | if (type.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && (predicate == null || predicate(t)) 208 | && t.GetCustomAttribute() == null) 209 | list.Add(t); 210 | }); 211 | 212 | return list; 213 | } 214 | 215 | public static void ForEachAssembly(Action callback) 216 | { 217 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 218 | foreach (var assembly in assemblies) 219 | { 220 | try 221 | { 222 | callback(assembly); 223 | } 224 | catch (ReflectionTypeLoadException) 225 | { 226 | // Skip any assemblies that don't load properly 227 | continue; 228 | } 229 | } 230 | } 231 | 232 | public static void ForEachType(Action callback) 233 | { 234 | ForEachAssembly(assembly => 235 | { 236 | var types = assembly.GetTypes(); 237 | foreach (var t in types) 238 | callback(t); 239 | }); 240 | } 241 | 242 | public static IEnumerable GetImplementationsOfInterface(Type type) 243 | { 244 | if (type.IsInterface) 245 | return GetAssignableTypes(type); 246 | 247 | return Enumerable.Empty(); 248 | } 249 | 250 | public static IEnumerable GetExtensionsOfClass(Type type) 251 | { 252 | if (type.IsClass) 253 | return GetAssignableTypes(type); 254 | 255 | return Enumerable.Empty(); 256 | } 257 | 258 | public static void Destroy(UnityObject o, float t = 0f) 259 | { 260 | if (Application.isPlaying) 261 | { 262 | UnityObject.Destroy(o, t); 263 | } 264 | #if UNITY_EDITOR && UNITY_EDITORVR 265 | else 266 | { 267 | if (Mathf.Approximately(t, 0f)) 268 | UnityObject.DestroyImmediate(o); 269 | else 270 | VRView.StartCoroutine(DestroyInSeconds(o, t)); 271 | } 272 | #endif 273 | } 274 | 275 | static IEnumerator DestroyInSeconds(UnityObject o, float t) 276 | { 277 | var startTime = Time.realtimeSinceStartup; 278 | while (Time.realtimeSinceStartup <= startTime + t) 279 | yield return null; 280 | 281 | UnityObject.DestroyImmediate(o); 282 | } 283 | 284 | /// 285 | /// Strip "PPtr<> and $ from a string for getting a System.Type from SerializedProperty.type 286 | /// TODO: expose internal SerializedProperty.objectReferenceTypeString to remove this hack 287 | /// 288 | /// Type string 289 | /// Nicified type string 290 | public static string NicifySerializedPropertyType(string type) 291 | { 292 | return type.Replace("PPtr<", "").Replace(">", "").Replace("$", ""); 293 | } 294 | 295 | /// 296 | /// Search through all assemblies in the current AppDomain for a class that is assignable to UnityObject and matches the given weak name 297 | /// TODO: expose internal SerialzedProperty.ValidateObjectReferenceValue to remove his hack 298 | /// 299 | /// Weak type name 300 | /// Best guess System.Type 301 | public static Type TypeNameToType(string name) 302 | { 303 | return AppDomain.CurrentDomain.GetAssemblies() 304 | .SelectMany(x => x.GetTypes()) 305 | .FirstOrDefault(x => x.Name.Equals(name) && typeof(UnityObject).IsAssignableFrom(x)); 306 | } 307 | 308 | #if UNITY_EDITOR 309 | public static IEnumerator GetAssetPreview(UnityObject obj, Action callback) 310 | { 311 | var texture = AssetPreview.GetAssetPreview(obj); 312 | 313 | while (AssetPreview.IsLoadingAssetPreview(obj.GetInstanceID())) 314 | { 315 | texture = AssetPreview.GetAssetPreview(obj); 316 | yield return null; 317 | } 318 | 319 | if (!texture) 320 | texture = AssetPreview.GetMiniThumbnail(obj); 321 | 322 | callback(texture); 323 | } 324 | 325 | public static void CreateAssetFromObjects(UnityObject[] objects, string path) 326 | { 327 | var method = typeof(AssetDatabase).GetMethod("CreateAssetFromObjects", BindingFlags.NonPublic | BindingFlags.Static); 328 | method.Invoke(null, new object[] { objects, path }); 329 | } 330 | #endif 331 | 332 | public static IEnumerator FindObjectsOfType(List objects) where T : Component 333 | { 334 | var scene = SceneManager.GetActiveScene(); 335 | s_RootGameObjects.Clear(); 336 | scene.GetRootGameObjects(s_RootGameObjects); 337 | yield return null; 338 | 339 | foreach (var go in s_RootGameObjects) 340 | { 341 | var children = go.GetComponentsInChildren(); 342 | objects.AddRange(children); 343 | 344 | yield return null; 345 | } 346 | } 347 | 348 | public static IEnumerator FindGameObject(string name, Action callback, GameObject root = null) 349 | { 350 | if (root) 351 | { 352 | if (root.name == name) 353 | { 354 | callback(root); 355 | yield break; 356 | } 357 | 358 | foreach (Transform child in root.transform) 359 | { 360 | yield return FindGameObject(name, callback, child.gameObject); 361 | if (!root) 362 | yield break; 363 | } 364 | } 365 | else 366 | { 367 | var scene = SceneManager.GetActiveScene(); 368 | s_RootGameObjects.Clear(); 369 | scene.GetRootGameObjects(s_RootGameObjects); 370 | foreach (var go in s_RootGameObjects) 371 | { 372 | yield return FindGameObject(name, callback, go); 373 | } 374 | } 375 | 376 | yield return null; 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /Editor/ModelImporterLODGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Unity.Collections; 6 | using Unity.Jobs; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using UnityObject = UnityEngine.Object; 10 | 11 | namespace Unity.AutoLOD 12 | { 13 | public class ModelImporterLODGenerator : AssetPostprocessor 14 | { 15 | public static bool saveAssets { set; get; } 16 | public static bool enabled { set; get; } 17 | public static Type meshSimplifierType { set; get; } 18 | public static int maxLOD { set; get; } 19 | public static int initialLODMaxPolyCount { set; get; } 20 | 21 | const HideFlags k_DefaultHideFlags = HideFlags.None; 22 | 23 | static List s_ModelAssetsProcessed = new List(); 24 | 25 | public static bool IsEditable(string assetPath) 26 | { 27 | var attributes = File.GetAttributes(assetPath); 28 | 29 | return AssetDatabase.IsOpenForEdit(assetPath, StatusQueryOptions.ForceUpdate) 30 | && (attributes & FileAttributes.ReadOnly) == 0; 31 | } 32 | 33 | void OnPostprocessModel(GameObject go) 34 | { 35 | if (!go.GetComponentInChildren() && meshSimplifierType != null && IsEditable(assetPath)) 36 | { 37 | if (go.GetComponentsInChildren().Any()) 38 | { 39 | Debug.LogWarning("Automatic LOD generation on skinned meshes is not currently supported"); 40 | return; 41 | } 42 | 43 | var originalMeshFilters = go.GetComponentsInChildren(); 44 | uint polyCount = 0; 45 | foreach (var mf in originalMeshFilters) 46 | { 47 | var m = mf.sharedMesh; 48 | for (int i = 0; i < m.subMeshCount; i++) 49 | { 50 | var topology = m.GetTopology(i); 51 | var indexCount = m.GetIndexCount(i); 52 | 53 | switch (topology) 54 | { 55 | case MeshTopology.Quads: 56 | indexCount /= 4; 57 | break; 58 | 59 | case MeshTopology.Triangles: 60 | indexCount /= 3; 61 | break; 62 | 63 | case MeshTopology.Lines: 64 | case MeshTopology.LineStrip: 65 | indexCount /= 2; 66 | break; 67 | } 68 | 69 | polyCount += indexCount; 70 | } 71 | } 72 | 73 | var meshLODs = new List(); 74 | var preprocessMeshes = new HashSet(); 75 | 76 | var lodData = GetLODData(assetPath); 77 | var overrideDefaults = lodData.overrideDefaults; 78 | var importSettings = lodData.importSettings; 79 | 80 | // It's possible to override defaults to either generate on import or to not generate and use specified 81 | // LODs in the override, but in the case where we are not overriding and globally we are not generating 82 | // on import, then there should be no further processing. 83 | if (!overrideDefaults && !enabled) 84 | return; 85 | 86 | if (importSettings.generateOnImport) 87 | { 88 | if (importSettings.maxLODGenerated == 0 && polyCount <= importSettings.initialLODMaxPolyCount) 89 | return; 90 | 91 | var simplifierType = Type.GetType(importSettings.meshSimplifier) ?? meshSimplifierType; 92 | 93 | if (polyCount > importSettings.initialLODMaxPolyCount) 94 | { 95 | foreach (var mf in originalMeshFilters) 96 | { 97 | var inputMesh = mf.sharedMesh; 98 | 99 | var outputMesh = new Mesh(); 100 | outputMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 101 | outputMesh.name = inputMesh.name; 102 | outputMesh.bounds = inputMesh.bounds; 103 | mf.sharedMesh = outputMesh; 104 | 105 | var meshLOD = MeshLOD.GetGenericInstance(meshSimplifierType); 106 | meshLOD.InputMesh = inputMesh; 107 | meshLOD.OutputMesh = outputMesh; 108 | meshLOD.Quality = (float)importSettings.initialLODMaxPolyCount / (float)polyCount; 109 | meshLODs.Add(meshLOD); 110 | 111 | preprocessMeshes.Add(outputMesh.GetInstanceID()); 112 | } 113 | } 114 | 115 | // Clear out previous LOD data in case the number of LODs has been reduced 116 | for (int i = 0; i <= LODData.MaxLOD; i++) 117 | { 118 | lodData[i] = null; 119 | } 120 | 121 | lodData[0] = originalMeshFilters.Select(mf => mf.GetComponent()).ToArray(); 122 | 123 | for (int i = 1; i <= importSettings.maxLODGenerated; i++) 124 | { 125 | var lodMeshes = new List(); 126 | 127 | foreach (var mf in originalMeshFilters) 128 | { 129 | var inputMesh = mf.sharedMesh; 130 | 131 | var lodTransform = EditorUtility.CreateGameObjectWithHideFlags(mf.name, 132 | k_DefaultHideFlags, typeof(MeshFilter), typeof(MeshRenderer)).transform; 133 | lodTransform.parent = mf.transform; 134 | lodTransform.localPosition = Vector3.zero; 135 | lodTransform.localRotation = Quaternion.identity; 136 | lodTransform.localScale = new Vector3(1, 1, 1); 137 | 138 | var lodMF = lodTransform.GetComponent(); 139 | var lodRenderer = lodTransform.GetComponent(); 140 | 141 | AppendLODNameToRenderer(lodRenderer, i); 142 | 143 | var outputMesh = new Mesh(); 144 | outputMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 145 | outputMesh.name = string.Format("{0} LOD{1}", inputMesh.name, i); 146 | outputMesh.bounds = inputMesh.bounds; 147 | lodMF.sharedMesh = outputMesh; 148 | 149 | lodMeshes.Add(lodRenderer); 150 | 151 | EditorUtility.CopySerialized(mf.GetComponent(), lodRenderer); 152 | 153 | var meshLOD = MeshLOD.GetGenericInstance(meshSimplifierType); 154 | meshLOD.InputMesh = inputMesh; 155 | meshLOD.OutputMesh = outputMesh; 156 | meshLOD.Quality = Mathf.Pow(0.5f, i); 157 | meshLODs.Add(meshLOD); 158 | } 159 | 160 | lodData[i] = lodMeshes.ToArray(); 161 | } 162 | 163 | // Change the name of the original renderers last, so the name change doesn't end up in the clones for other LODs 164 | AppendLODNameToRenderers(go.transform, 0); 165 | 166 | if (meshLODs.Count > 0) 167 | { 168 | if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(lodData))) 169 | { 170 | AssetDatabase.CreateAsset(lodData, GetLODDataPath(assetPath)); 171 | } 172 | else 173 | { 174 | var objects = AssetDatabase.LoadAllAssetsAtPath(GetLODDataPath(assetPath)); 175 | foreach (var o in objects) 176 | { 177 | var mesh = o as Mesh; 178 | if (mesh) 179 | UnityObject.DestroyImmediate(mesh, true); 180 | } 181 | EditorUtility.SetDirty(lodData); 182 | } 183 | meshLODs.ForEach(ml => AssetDatabase.AddObjectToAsset(ml.OutputMesh, lodData)); 184 | if (saveAssets) 185 | AssetDatabase.SaveAssets(); 186 | 187 | if (preprocessMeshes.Count > 0) 188 | { 189 | // Process dependencies first 190 | var jobDependencies = new List(); 191 | meshLODs.RemoveAll(ml => 192 | { 193 | if (preprocessMeshes.Contains(ml.OutputMesh.GetInstanceID())) 194 | { 195 | jobDependencies.Add(ml.Generate()); 196 | return true; 197 | } 198 | 199 | return false; 200 | }); 201 | 202 | // Process remaining meshes 203 | foreach (var ml in meshLODs) 204 | { 205 | MonoBehaviourHelper.StartCoroutine(ml.GenerateAfterDependencies(jobDependencies)); 206 | } 207 | } 208 | else 209 | { 210 | foreach (var ml in meshLODs) 211 | { 212 | ml.Generate(); 213 | } 214 | } 215 | } 216 | } 217 | else 218 | { 219 | // Don't allow overriding LOD0 220 | lodData[0] = originalMeshFilters.Select(mf => 221 | { 222 | var r = mf.GetComponent(); 223 | AppendLODNameToRenderer(r, 0); 224 | return r; 225 | }).ToArray(); 226 | 227 | for (int i = 1; i <= LODData.MaxLOD; i++) 228 | { 229 | var renderers = lodData[i]; 230 | for (int j = 0; j < renderers.Length; j++) 231 | { 232 | var r = renderers[j]; 233 | 234 | var lodTransform = EditorUtility.CreateGameObjectWithHideFlags(r.name, 235 | k_DefaultHideFlags, typeof(MeshFilter), typeof(MeshRenderer)).transform; 236 | lodTransform.parent = go.transform; 237 | lodTransform.localPosition = Vector3.zero; 238 | 239 | var lodMF = lodTransform.GetComponent(); 240 | var lodRenderer = lodTransform.GetComponent(); 241 | 242 | EditorUtility.CopySerialized(r.GetComponent(), lodMF); 243 | EditorUtility.CopySerialized(r, lodRenderer); 244 | 245 | AppendLODNameToRenderer(lodRenderer, i); 246 | 247 | renderers[j] = lodRenderer; 248 | } 249 | } 250 | } 251 | 252 | List lods = new List(); 253 | var maxLODFound = -1; 254 | for (int i = 0; i <= LODData.MaxLOD; i++) 255 | { 256 | var renderers = lodData[i]; 257 | if (renderers == null || renderers.Length == 0) 258 | break; 259 | 260 | maxLODFound++; 261 | } 262 | 263 | var importerRef = new SerializedObject(assetImporter); 264 | var importerLODLevels = importerRef.FindProperty("m_LODScreenPercentages"); 265 | for (int i = 0; i <= maxLODFound; i++) 266 | { 267 | var lod = new LOD(); 268 | lod.renderers = lodData[i]; 269 | var screenPercentage = i == maxLODFound ? 0.01f : Mathf.Pow(0.5f, i + 1); 270 | 271 | // Use the model importer percentages if they exist 272 | if (i < importerLODLevels.arraySize && maxLODFound == importerLODLevels.arraySize) 273 | { 274 | var element = importerLODLevels.GetArrayElementAtIndex(i); 275 | screenPercentage = element.floatValue; 276 | } 277 | 278 | lod.screenRelativeTransitionHeight = screenPercentage; 279 | lods.Add(lod); 280 | } 281 | 282 | if (importerLODLevels.arraySize != 0 && maxLODFound != importerLODLevels.arraySize - 1) 283 | { 284 | Debug.LogWarning("The model has an existing lod group, but it's settings will not be used because " + 285 | "the specified lod count in the AutoLOD settings is different."); 286 | } 287 | 288 | var lodGroup = go.AddComponent(); 289 | lodGroup.SetLODs(lods.ToArray()); 290 | lodGroup.RecalculateBounds(); 291 | 292 | // Keep model importer in sync 293 | importerLODLevels.ClearArray(); 294 | for (int i = 0; i < lods.Count; i++) 295 | { 296 | var lod = lods[i]; 297 | importerLODLevels.InsertArrayElementAtIndex(i); 298 | var element = importerLODLevels.GetArrayElementAtIndex(i); 299 | element.floatValue = lod.screenRelativeTransitionHeight; 300 | } 301 | importerRef.ApplyModifiedPropertiesWithoutUndo(); 302 | 303 | s_ModelAssetsProcessed.Add(assetPath); 304 | } 305 | } 306 | 307 | static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) 308 | { 309 | bool assetsImported = false; 310 | 311 | foreach (var asset in importedAssets) 312 | { 313 | if (s_ModelAssetsProcessed.Remove(asset)) 314 | { 315 | var go = (GameObject)AssetDatabase.LoadMainAssetAtPath(asset); 316 | var lodData = AssetDatabase.LoadAssetAtPath(GetLODDataPath(asset)); 317 | 318 | var lodGroup = go.GetComponentInChildren(); 319 | var lods = lodGroup.GetLODs(); 320 | for (int i = 0; i < lods.Length; i++) 321 | { 322 | var lod = lods[i]; 323 | lodData[i] = lod.renderers; 324 | } 325 | 326 | EditorUtility.SetDirty(lodData); 327 | assetsImported = true; 328 | } 329 | } 330 | 331 | if (assetsImported && saveAssets) 332 | AssetDatabase.SaveAssets(); 333 | } 334 | 335 | internal static string GetLODDataPath(string assetPath) 336 | { 337 | var pathPrefix = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(assetPath); 338 | return pathPrefix + "_lods.asset"; 339 | } 340 | 341 | internal static LODData GetLODData(string assetPath) 342 | { 343 | var lodData = AssetDatabase.LoadAssetAtPath(GetLODDataPath(assetPath)); 344 | if (!lodData) 345 | lodData = ScriptableObject.CreateInstance(); 346 | 347 | var overrideDefaults = lodData.overrideDefaults; 348 | 349 | var importSettings = lodData.importSettings; 350 | if (importSettings == null) 351 | { 352 | importSettings = new LODImportSettings(); 353 | lodData.importSettings = importSettings; 354 | } 355 | 356 | if (!overrideDefaults) 357 | { 358 | importSettings.generateOnImport = enabled; 359 | importSettings.meshSimplifier = meshSimplifierType.AssemblyQualifiedName; 360 | importSettings.maxLODGenerated = maxLOD; 361 | importSettings.initialLODMaxPolyCount = initialLODMaxPolyCount; 362 | } 363 | 364 | return lodData; 365 | } 366 | 367 | static void AppendLODNameToRenderers(Transform root, int lod) 368 | { 369 | var renderers = root.GetComponentsInChildren(); 370 | foreach (var r in renderers) 371 | { 372 | AppendLODNameToRenderer(r, lod); 373 | } 374 | } 375 | 376 | static void AppendLODNameToRenderer(Renderer r, int lod) 377 | { 378 | if (r.name.IndexOf("_LOD", StringComparison.OrdinalIgnoreCase) == -1) 379 | r.name = string.Format("{0}_LOD{1}", r.name, lod); 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /Runtime/Helpers/WorkingMesh.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using Unity.Collections; 5 | using UnityEngine; 6 | using UnityEngine.Rendering; 7 | 8 | namespace Unity.AutoLOD 9 | { 10 | public static class MeshExtensions 11 | { 12 | public static int GetTriangleCount(this Mesh mesh) 13 | { 14 | var triangleCount = 0; 15 | for (var i = 0; i < mesh.subMeshCount; i++) 16 | { 17 | triangleCount += (int)mesh.GetIndexCount(i); 18 | } 19 | 20 | return triangleCount; 21 | } 22 | 23 | public static WorkingMesh ToWorkingMesh(this Mesh mesh, Allocator allocator) 24 | { 25 | var bindposes = mesh.bindposes; 26 | var wm = new WorkingMesh(allocator, mesh.vertexCount, mesh.GetTriangleCount(), mesh.subMeshCount, bindposes.Length); 27 | mesh.ApplyToWorkingMesh(ref wm, bindposes); 28 | 29 | return wm; 30 | } 31 | 32 | // Taking bindposes optional parameter is ugly, but saves an additional array allocation if it was already 33 | // accessed to get the length 34 | public static void ApplyToWorkingMesh(this Mesh mesh, ref WorkingMesh wm, Matrix4x4[] bindposes = null) 35 | { 36 | wm.indexFormat = mesh.indexFormat; 37 | wm.vertices = mesh.vertices; 38 | wm.normals = mesh.normals; 39 | wm.tangents = mesh.tangents; 40 | wm.uv = mesh.uv; 41 | wm.uv2 = mesh.uv2; 42 | wm.uv3 = mesh.uv3; 43 | wm.uv4 = mesh.uv4; 44 | wm.colors = mesh.colors; 45 | wm.boneWeights = mesh.boneWeights; 46 | wm.bindposes = bindposes ?? mesh.bindposes; 47 | wm.subMeshCount = mesh.subMeshCount; 48 | for (int i = 0; i < mesh.subMeshCount; i++) 49 | { 50 | wm.SetTriangles(mesh.GetTriangles(i), i); 51 | } 52 | wm.name = mesh.name; 53 | wm.bounds = mesh.bounds; 54 | } 55 | } 56 | 57 | public struct WorkingMesh : IDisposable 58 | { 59 | enum Channel 60 | { 61 | Vertices, 62 | Normals, 63 | Tangents, 64 | UV, 65 | UV2, 66 | UV3, 67 | UV4, 68 | Colors, 69 | BoneWeights, 70 | Bindposes, 71 | Triangles, 72 | SubmeshOffset 73 | } 74 | 75 | const int k_MaxNameSize = 128; 76 | 77 | public Vector3[] vertices 78 | { 79 | get 80 | { 81 | return m_Vertices.Slice(0, vertexCount).ToArray(); 82 | } 83 | set 84 | { 85 | vertexCount = value.Length; 86 | m_Vertices.Slice(0, vertexCount).CopyFrom(value); 87 | } 88 | } 89 | NativeArray m_Vertices; 90 | 91 | public int vertexCount 92 | { 93 | get { return m_Counts[(int)Channel.Vertices]; } 94 | private set { m_Counts[(int)Channel.Vertices] = value; } 95 | } 96 | 97 | public int[] triangles 98 | { 99 | get { return m_Triangles.Slice(0, trianglesCount).ToArray(); } 100 | set 101 | { 102 | subMeshCount = 1; 103 | trianglesCount = value.Length; 104 | SetTriangles(value, 0); 105 | } 106 | } 107 | NativeArray m_Triangles; 108 | 109 | int trianglesCount 110 | { 111 | get { return m_Counts[(int)Channel.Triangles]; } 112 | set { m_Counts[(int)Channel.Triangles] = value; } 113 | } 114 | 115 | public Vector3[] normals 116 | { 117 | get { return m_Normals.Slice(0, normalsCount).ToArray(); } 118 | set 119 | { 120 | if (value == null || value.Length == 0) 121 | { 122 | normalsCount = 0; 123 | } 124 | else 125 | { 126 | normalsCount = value.Length; 127 | m_Normals.Slice(0, normalsCount).CopyFrom(value); 128 | } 129 | } 130 | } 131 | NativeArray m_Normals; 132 | 133 | int normalsCount 134 | { 135 | get { return m_Counts[(int)Channel.Normals]; } 136 | set { m_Counts[(int)Channel.Normals] = value; } 137 | } 138 | 139 | public Vector4[] tangents 140 | { 141 | get { return m_Tangents.Slice(0, tangentsCount).ToArray(); } 142 | set 143 | { 144 | if (value == null || value.Length == 0) 145 | { 146 | tangentsCount = 0; 147 | } 148 | else 149 | { 150 | tangentsCount = value.Length; 151 | m_Tangents.Slice(0, tangentsCount).CopyFrom(value); 152 | } 153 | } 154 | } 155 | NativeArray m_Tangents; 156 | 157 | int tangentsCount 158 | { 159 | get { return m_Counts[(int)Channel.Tangents]; } 160 | set { m_Counts[(int)Channel.Tangents] = value; } 161 | } 162 | 163 | public Vector2[] uv 164 | { 165 | get { return m_UV.Slice(0, uvCount).ToArray(); } 166 | set 167 | { 168 | if (value == null || value.Length == 0) 169 | { 170 | uvCount = 0; 171 | } 172 | else 173 | { 174 | uvCount = value.Length; 175 | m_UV.Slice(0, uvCount).CopyFrom(value); 176 | } 177 | } 178 | } 179 | NativeArray m_UV; 180 | 181 | int uvCount 182 | { 183 | get { return m_Counts[(int)Channel.UV]; } 184 | set { m_Counts[(int)Channel.UV] = value; } 185 | } 186 | 187 | public Vector2[] uv2 188 | { 189 | get { return m_UV2.Slice(0, uv2Count).ToArray(); } 190 | set 191 | { 192 | if (value == null || value.Length == 0) 193 | { 194 | uv2Count = 0; 195 | } 196 | else 197 | { 198 | uv2Count = value.Length; 199 | m_UV2.Slice(0, uv2Count).CopyFrom(value); 200 | } 201 | } 202 | } 203 | NativeArray m_UV2; 204 | 205 | int uv2Count 206 | { 207 | get { return m_Counts[(int)Channel.UV2]; } 208 | set { m_Counts[(int)Channel.UV2] = value; } 209 | } 210 | 211 | public Vector2[] uv3 212 | { 213 | get { return m_UV3.Slice(0, uv3Count).ToArray(); } 214 | set 215 | { 216 | if (value == null || value.Length == 0) 217 | { 218 | uv3Count = 0; 219 | } 220 | else 221 | { 222 | uv3Count = value.Length; 223 | m_UV3.Slice(0, uv3Count).CopyFrom(value); 224 | } 225 | } 226 | } 227 | NativeArray m_UV3; 228 | 229 | int uv3Count 230 | { 231 | get { return m_Counts[(int)Channel.UV3]; } 232 | set { m_Counts[(int)Channel.UV3] = value; } 233 | } 234 | 235 | public Vector2[] uv4 236 | { 237 | get { return m_UV4.Slice(0, uv4Count).ToArray(); } 238 | set 239 | { 240 | if (value == null || value.Length == 0) 241 | { 242 | uv4Count = 0; 243 | } 244 | else 245 | { 246 | uv4Count = value.Length; 247 | m_UV4.Slice(0, uv4Count).CopyFrom(value); 248 | } 249 | } 250 | } 251 | NativeArray m_UV4; 252 | 253 | int uv4Count 254 | { 255 | get { return m_Counts[(int)Channel.UV4]; } 256 | set { m_Counts[(int)Channel.UV4] = value; } 257 | } 258 | 259 | public Color[] colors 260 | { 261 | get { return m_Colors.Slice(0, colorsCount).ToArray(); } 262 | set 263 | { 264 | if (value == null || value.Length == 0) 265 | { 266 | colorsCount = 0; 267 | } 268 | else 269 | { 270 | colorsCount = value.Length; 271 | m_Colors.Slice(0, colorsCount).CopyFrom(value); 272 | } 273 | } 274 | } 275 | NativeArray m_Colors; 276 | 277 | int colorsCount 278 | { 279 | get { return m_Counts[(int)Channel.Colors]; } 280 | set { m_Counts[(int)Channel.Colors] = value; } 281 | } 282 | 283 | public Color32[] colors32 284 | { 285 | get { return colors != null ? colors.Select(c => (Color32)c).ToArray() : null; } 286 | set { colors = value != null ? value.Select(c => (Color)c).ToArray() : null; } 287 | } 288 | 289 | public BoneWeight[] boneWeights 290 | { 291 | get { return m_BoneWeights.Slice(0, boneWeightsCount).ToArray(); } 292 | set 293 | { 294 | if (value == null || value.Length == 0) 295 | { 296 | boneWeightsCount = 0; 297 | } 298 | else 299 | { 300 | boneWeightsCount = value.Length; 301 | m_BoneWeights.Slice(0, boneWeightsCount).CopyFrom(value); 302 | } 303 | } 304 | } 305 | NativeArray m_BoneWeights; 306 | 307 | int boneWeightsCount 308 | { 309 | get { return m_Counts[(int)Channel.BoneWeights]; } 310 | set { m_Counts[(int)Channel.BoneWeights] = value; } 311 | } 312 | 313 | public Matrix4x4[] bindposes 314 | { 315 | get { return m_Bindposes.Slice(0, bindposesCount).ToArray(); } 316 | set 317 | { 318 | if (value == null || value.Length == 0) 319 | { 320 | bindposesCount = 0; 321 | } 322 | else 323 | { 324 | bindposesCount = value.Length; 325 | m_Bindposes.Slice(0, bindposesCount).CopyFrom(value); 326 | } 327 | } 328 | } 329 | NativeArray m_Bindposes; 330 | 331 | int bindposesCount 332 | { 333 | get { return m_Counts[(int)Channel.Bindposes]; } 334 | set { m_Counts[(int)Channel.Bindposes] = value; } 335 | } 336 | 337 | public int subMeshCount 338 | { 339 | get { return submeshOffsetCount; } 340 | set 341 | { 342 | if (submeshOffsetCount == value) 343 | return; 344 | 345 | var previousCount = submeshOffsetCount; 346 | submeshOffsetCount = value; 347 | for (var i = previousCount; i < submeshOffsetCount; i++) 348 | { 349 | // Initialize these offsets to be invalid, so we don't use stale values 350 | m_SubmeshOffset[i] = -1; 351 | } 352 | } 353 | } 354 | NativeArray m_SubmeshOffset; 355 | 356 | int submeshOffsetCount 357 | { 358 | get { return m_Counts[(int)Channel.SubmeshOffset]; } 359 | set { m_Counts[(int)Channel.SubmeshOffset] = value; } 360 | } 361 | 362 | public string name 363 | { 364 | get 365 | { 366 | var bytes = m_Name.Slice(0, m_NameLength).ToArray(); 367 | return Encoding.UTF8.GetString(bytes); 368 | } 369 | set 370 | { 371 | if (value == null) 372 | value = string.Empty; 373 | 374 | var bytes = Encoding.UTF8.GetBytes(value); 375 | m_NameLength = Mathf.Min(bytes.Length, k_MaxNameSize); 376 | m_Name.Slice(0, m_NameLength).CopyFrom(bytes); 377 | } 378 | } 379 | NativeArray m_Name; 380 | int m_NameLength; 381 | 382 | // This data does not cross the job threshold, so if it needs to be read back, then it will need to be 383 | // in a NativeArray or some other type of NativeContainer 384 | public IndexFormat indexFormat { get; set; } 385 | public Bounds bounds { get; set; } 386 | 387 | NativeArray m_Counts; 388 | 389 | // These are stubbed out for API completeness, but obviously don't do anything 390 | public void RecalculateBounds() { } 391 | public void RecalculateNormals() { } 392 | public void RecalculateTangents() { } 393 | 394 | public void SetTriangles(int[] triangles, int submesh) 395 | { 396 | if (submesh >= subMeshCount) 397 | subMeshCount = submesh + 1; 398 | 399 | var preSliceLength = m_SubmeshOffset[submesh]; 400 | if (preSliceLength < 0) 401 | { 402 | if (submesh > 0) 403 | { 404 | m_SubmeshOffset[submesh] = trianglesCount; 405 | preSliceLength = trianglesCount; 406 | } 407 | else 408 | { 409 | m_SubmeshOffset[submesh] = 0; 410 | preSliceLength = 0; 411 | } 412 | } 413 | var totalCount = preSliceLength; // count prior to submesh 414 | totalCount += triangles.Length; // new submesh triangle count 415 | 416 | var postSliceOffset = 0; 417 | var postSliceLength = 0; 418 | if (submesh < subMeshCount - 2) // count of all triangles after submesh 419 | { 420 | postSliceOffset = m_SubmeshOffset[submesh + 1]; 421 | if (postSliceOffset >= 0) 422 | { 423 | postSliceLength = trianglesCount - postSliceOffset; 424 | totalCount += postSliceLength; 425 | } 426 | } 427 | 428 | trianglesCount = totalCount; 429 | 430 | // Shift other following triangles up/down 431 | if (postSliceOffset > 0) 432 | { 433 | var offset = preSliceLength + triangles.Length; 434 | m_SubmeshOffset[submesh + 1] = offset; 435 | var sourceSlice = new NativeSlice(m_Triangles, postSliceOffset, postSliceLength); 436 | var destSlice = new NativeSlice(m_Triangles, offset, postSliceLength); 437 | destSlice.CopyFrom(sourceSlice); 438 | } 439 | 440 | m_Triangles.Slice(preSliceLength, triangles.Length).CopyFrom(triangles); 441 | } 442 | 443 | public int[] GetTriangles(int submesh) 444 | { 445 | if (submesh < subMeshCount) 446 | { 447 | var start = 0; 448 | var stop = 0; 449 | GetTriangleRange(submesh, out start, out stop); 450 | var length = stop - start; 451 | 452 | var slice = new NativeSlice(m_Triangles, start, length); 453 | return slice.ToArray(); 454 | } 455 | 456 | return new int[0]; 457 | } 458 | 459 | void GetTriangleRange(int submesh, out int start, out int stop) 460 | { 461 | if (submesh < subMeshCount) 462 | { 463 | start = m_SubmeshOffset[submesh]; 464 | stop = trianglesCount; 465 | if (submesh < subMeshCount - 1) 466 | stop = m_SubmeshOffset[submesh + 1]; 467 | 468 | return; 469 | } 470 | 471 | start = -1; 472 | stop = -1; 473 | return; 474 | } 475 | 476 | public WorkingMesh(Allocator allocator, int maxVertices, int maxTriangles, int maxSubmeshes, int maxBindposes) : this() 477 | { 478 | m_Counts = new NativeArray(Enum.GetValues(typeof(Channel)).Length, allocator); 479 | m_Vertices = new NativeArray(maxVertices, allocator); 480 | m_Normals = new NativeArray(maxVertices, allocator); 481 | m_Tangents = new NativeArray(maxVertices, allocator); 482 | m_UV = new NativeArray(maxVertices, allocator); 483 | m_UV2 = new NativeArray(maxVertices, allocator); 484 | m_UV3 = new NativeArray(maxVertices, allocator); 485 | m_UV4 = new NativeArray(maxVertices, allocator); 486 | m_Colors = new NativeArray(maxVertices, allocator); 487 | m_BoneWeights = new NativeArray(maxVertices, allocator); 488 | m_Bindposes = new NativeArray(maxBindposes, allocator); 489 | m_Name = new NativeArray(k_MaxNameSize, allocator); 490 | m_SubmeshOffset = new NativeArray(maxSubmeshes, allocator); 491 | m_Triangles = new NativeArray(maxTriangles, allocator); 492 | } 493 | 494 | public void Dispose() 495 | { 496 | if (m_Counts.IsCreated) 497 | m_Counts.Dispose(); 498 | 499 | if (m_Vertices.IsCreated) 500 | m_Vertices.Dispose(); 501 | 502 | if (m_Normals.IsCreated) 503 | m_Normals.Dispose(); 504 | 505 | if (m_Tangents.IsCreated) 506 | m_Tangents.Dispose(); 507 | 508 | if (m_UV.IsCreated) 509 | m_UV.Dispose(); 510 | 511 | if (m_UV2.IsCreated) 512 | m_UV2.Dispose(); 513 | 514 | if (m_UV3.IsCreated) 515 | m_UV3.Dispose(); 516 | 517 | if (m_UV4.IsCreated) 518 | m_UV4.Dispose(); 519 | 520 | if (m_Colors.IsCreated) 521 | m_Colors.Dispose(); 522 | 523 | if (m_BoneWeights.IsCreated) 524 | m_BoneWeights.Dispose(); 525 | 526 | if (m_Bindposes.IsCreated) 527 | m_Bindposes.Dispose(); 528 | 529 | if (m_Name.IsCreated) 530 | m_Name.Dispose(); 531 | 532 | if (m_SubmeshOffset.IsCreated) 533 | m_SubmeshOffset.Dispose(); 534 | 535 | if (m_Triangles.IsCreated) 536 | m_Triangles.Dispose(); 537 | } 538 | 539 | public void ApplyToMesh(Mesh mesh) 540 | { 541 | mesh.indexFormat = indexFormat; 542 | mesh.vertices = vertices; 543 | mesh.normals = normals; 544 | mesh.tangents = tangents; 545 | mesh.uv = uv; 546 | mesh.uv2 = uv2; 547 | mesh.uv3 = uv3; 548 | mesh.uv4 = uv4; 549 | mesh.colors = colors; 550 | mesh.boneWeights = boneWeights; 551 | mesh.bindposes = bindposes; 552 | mesh.subMeshCount = subMeshCount; 553 | for (int i = 0; i < subMeshCount; i++) 554 | { 555 | mesh.SetTriangles(GetTriangles(i), i); 556 | } 557 | mesh.name = name; 558 | mesh.bounds = bounds; 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /Editor/SceneLOD.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using Unity.AutoLOD.Utilities; 8 | using UnityEditor; 9 | using UnityEngine; 10 | using UnityEngine.SceneManagement; 11 | using Dbg = UnityEngine.Debug; 12 | using UnityObject = UnityEngine.Object; 13 | 14 | 15 | namespace Unity.AutoLOD 16 | { 17 | public class SceneLOD : ScriptableSingleton 18 | { 19 | class SceneLODAssetProcessor : UnityEditor.AssetModificationProcessor 20 | { 21 | public static string[] OnWillSaveAssets(string[] paths) 22 | { 23 | foreach (string path in paths) 24 | { 25 | if (path.Contains(".unity")) 26 | { 27 | AssetDatabase.StartAssetEditing(); 28 | 29 | var scene = SceneManager.GetSceneByPath(path); 30 | if(!scene.IsValid()) continue; 31 | var rootGameObjects = scene.GetRootGameObjects(); 32 | foreach (var go in rootGameObjects) 33 | { 34 | var lodVolume = go.GetComponent(); 35 | if (lodVolume) 36 | PersistHLODs(lodVolume, path); 37 | } 38 | 39 | AssetDatabase.StopAssetEditing(); 40 | } 41 | } 42 | 43 | return paths; 44 | } 45 | 46 | static void PersistHLODs(LODVolume lodVolume, string scenePath) 47 | { 48 | var hlodRoot = lodVolume.hlodRoot; 49 | if (hlodRoot) 50 | { 51 | var mf = hlodRoot.GetComponent(); 52 | if (mf) 53 | { 54 | var sharedMesh = mf.sharedMesh; 55 | if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(sharedMesh))) 56 | SaveUniqueHLODAsset(sharedMesh, scenePath); 57 | } 58 | } 59 | 60 | foreach (Transform child in lodVolume.transform) 61 | { 62 | var childLODVolume = child.GetComponent(); 63 | if (childLODVolume) 64 | PersistHLODs(childLODVolume, scenePath); 65 | } 66 | } 67 | 68 | static void SaveUniqueHLODAsset(UnityObject asset, string scenePath) 69 | { 70 | if (!string.IsNullOrEmpty(scenePath)) 71 | { 72 | var directory = Path.GetDirectoryName(scenePath) + "/" + Path.GetFileNameWithoutExtension(scenePath) + "_HLOD/"; 73 | if (!Directory.Exists(directory)) 74 | Directory.CreateDirectory(directory); 75 | 76 | var path = directory + Path.GetRandomFileName(); 77 | path = Path.ChangeExtension(path, "asset"); 78 | AssetDatabase.CreateAsset(asset, path); 79 | } 80 | } 81 | } 82 | 83 | public static bool activated { get { return s_Activated; } } 84 | 85 | public int coroutineQueueRemaining { get { return m_CoroutineQueue.Count; }} 86 | public long coroutineCurrentExecutionTime { get { return m_ServiceCoroutineExecutionTime.ElapsedMilliseconds; }} 87 | 88 | static bool s_HLODEnabled = true; 89 | static bool s_Activated; 90 | 91 | string m_CreateRootVolumeForScene = "Default"; // Set to some value, so new scenes don't auto-create 92 | LODVolume m_RootVolume; 93 | GameObject[] m_SelectedObjects; 94 | Dictionary m_SelectedObjectLastPose = new Dictionary(); 95 | Queue m_CoroutineQueue = new Queue(); 96 | Coroutine m_ServiceCoroutineQueue; 97 | bool m_SceneDirty; 98 | Stopwatch m_ServiceCoroutineExecutionTime = new Stopwatch(); 99 | Camera m_LastCamera; 100 | HashSet m_ExcludedRenderers = new HashSet(); 101 | Vector3 m_LastCameraPosition; 102 | Quaternion m_LastCameraRotation; 103 | 104 | // Local method variable caching 105 | List m_FoundRenderers = new List(); 106 | HashSet m_ExistingRenderers = new HashSet(); 107 | HashSet m_AddedRenderers = new HashSet(); 108 | HashSet m_RemovedRenderers = new HashSet(); 109 | 110 | void OnEnable() 111 | { 112 | if (LayerMask.NameToLayer(LODVolume.HLODLayer) == -1) 113 | { 114 | var layers = TagManager.GetRequiredLayers(); 115 | foreach (var layer in layers) 116 | { 117 | TagManager.AddLayer(layer); 118 | } 119 | } 120 | 121 | if (LayerMask.NameToLayer(LODVolume.HLODLayer) != -1) 122 | { 123 | Tools.lockedLayers |= LayerMask.GetMask(LODVolume.HLODLayer); 124 | s_Activated = true; 125 | } 126 | 127 | if (s_Activated) 128 | AddCallbacks(); 129 | 130 | ResetServiceCoroutineQueue(); 131 | } 132 | 133 | void OnDisable() 134 | { 135 | s_Activated = false; 136 | RemoveCallbacks(); 137 | } 138 | 139 | void AddCallbacks() 140 | { 141 | EditorApplication.update += EditorUpdate; 142 | EditorApplication.hierarchyChanged += OnHierarchyChanged; 143 | Selection.selectionChanged += OnSelectionChanged; 144 | Camera.onPreCull += PreCull; 145 | #if UNITY_2019_1_OR_NEWER 146 | SceneView.duringSceneGui += OnSceneGUI; 147 | #else 148 | SceneView.onSceneGUIDelegate += OnSceneGUI; 149 | #endif 150 | } 151 | 152 | void RemoveCallbacks() 153 | { 154 | EditorApplication.update -= EditorUpdate; 155 | EditorApplication.hierarchyChanged -= OnHierarchyChanged; 156 | Selection.selectionChanged -= OnSelectionChanged; 157 | Camera.onPreCull -= PreCull; 158 | #if UNITY_2019_1_OR_NEWER 159 | SceneView.duringSceneGui -= OnSceneGUI; 160 | #else 161 | SceneView.onSceneGUIDelegate -= OnSceneGUI; 162 | #endif 163 | } 164 | 165 | void OnHierarchyChanged() 166 | { 167 | m_SceneDirty = true; 168 | m_ExcludedRenderers.Clear(); 169 | } 170 | 171 | void OnSelectionChanged() 172 | { 173 | if (!m_RootVolume) 174 | return; 175 | 176 | if (m_SelectedObjects != null) 177 | m_CoroutineQueue.Enqueue(UpdateOctreeBounds(m_SelectedObjects)); 178 | 179 | m_SelectedObjects = Selection.gameObjects; 180 | if (m_SelectedObjects != null) 181 | { 182 | foreach (var selected in m_SelectedObjects) 183 | { 184 | if (selected) 185 | { 186 | var selectedTransform = selected.transform; 187 | m_SelectedObjectLastPose[selected] = new Pose(selectedTransform.position, selectedTransform.rotation); 188 | } 189 | } 190 | } 191 | } 192 | 193 | void OnSceneGUI(SceneView sceneView) 194 | { 195 | var activeSceneName = SceneManager.GetActiveScene().name; 196 | 197 | var rect = sceneView.position; 198 | rect.x = 0f; 199 | rect.y = 0f; 200 | 201 | Handles.BeginGUI(); 202 | GUILayout.BeginArea(rect); 203 | GUILayout.BeginHorizontal(); 204 | if (m_RootVolume && GUILayout.Button(s_HLODEnabled ? "Disable HLOD" : "Enable HLOD")) 205 | { 206 | s_HLODEnabled = !s_HLODEnabled; 207 | m_LastCamera = null; 208 | } 209 | else if (!m_RootVolume && m_CreateRootVolumeForScene != activeSceneName && GUILayout.Button("Activate SceneLOD")) 210 | { 211 | m_CreateRootVolumeForScene = activeSceneName; 212 | m_SceneDirty = true; 213 | m_LastCamera = null; 214 | } 215 | 216 | GUILayout.FlexibleSpace(); 217 | GUILayout.EndHorizontal(); 218 | GUILayout.EndArea(); 219 | Handles.EndGUI(); 220 | } 221 | 222 | IEnumerator UpdateOctreeBounds(GameObject[] gameObjects) 223 | { 224 | foreach (var go in gameObjects) 225 | { 226 | if (!go) 227 | continue; 228 | 229 | Pose pose; 230 | if (m_SelectedObjectLastPose.TryGetValue(go, out pose)) 231 | { 232 | var goTransform = go.transform; 233 | if (pose.position == goTransform.position && pose.rotation == goTransform.rotation) 234 | continue; 235 | } 236 | 237 | yield return UpdateChangedRenderer(go); 238 | } 239 | } 240 | 241 | IEnumerator UpdateChangedRenderer(GameObject go) 242 | { 243 | if (!go) 244 | yield break; 245 | 246 | while (!m_RootVolume) 247 | yield return UpdateOctree(); 248 | 249 | var transform = go.transform; 250 | var renderer = go.GetComponent(); 251 | if (renderer) 252 | { 253 | if (transform.hasChanged && m_RootVolume.renderers.Contains(renderer)) 254 | { 255 | yield return m_RootVolume.UpdateRenderer(renderer); 256 | yield return SetRootLODVolume(); // In case the BVH has grown or shrunk 257 | transform.hasChanged = false; 258 | } 259 | } 260 | 261 | foreach (Transform child in transform) 262 | { 263 | yield return UpdateChangedRenderer(child.gameObject); 264 | if (!transform) 265 | yield break; 266 | } 267 | } 268 | 269 | IEnumerator UpdateOctree() 270 | { 271 | if (!m_RootVolume) 272 | { 273 | yield return SetRootLODVolume(); 274 | 275 | if (!m_RootVolume) 276 | { 277 | if (m_CreateRootVolumeForScene == SceneManager.GetActiveScene().name) 278 | m_RootVolume = LODVolume.Create(); 279 | else 280 | yield break; 281 | } 282 | } 283 | 284 | var renderers = m_FoundRenderers; 285 | renderers.Clear(); 286 | 287 | yield return ObjectUtils.FindObjectsOfType(renderers); 288 | 289 | // Remove any renderers that should not be there (e.g. HLODs) 290 | renderers.RemoveAll(r => m_ExcludedRenderers.Contains(r)); 291 | renderers.RemoveAll(r => 292 | { 293 | if (r) 294 | { 295 | // Check against previous collection 296 | if (m_ExistingRenderers.Contains(r)) 297 | return false; 298 | 299 | if (r.gameObject.layer == LayerMask.NameToLayer(LODVolume.HLODLayer)) 300 | { 301 | m_ExcludedRenderers.Add(r); 302 | return true; 303 | } 304 | 305 | var mf = r.GetComponent(); 306 | if (!mf || (mf.sharedMesh && mf.sharedMesh.GetTopology(0) != MeshTopology.Triangles)) 307 | { 308 | m_ExcludedRenderers.Add(r); 309 | return true; 310 | } 311 | 312 | var lodGroup = r.GetComponentInParent(); 313 | if (lodGroup) 314 | { 315 | var lods = lodGroup.GetLODs(); 316 | 317 | // Skip LOD0, so that we keep the original renderers in the list 318 | for (int i = 1; i < lods.Length; i++) 319 | { 320 | if (lods[i].renderers.Contains(r)) 321 | { 322 | m_ExcludedRenderers.Add(r); 323 | return true; 324 | } 325 | } 326 | } 327 | else 328 | { 329 | // HLODs should come after traditional LODs, so exclude any standalone renderers 330 | m_ExcludedRenderers.Add(r); 331 | return true; 332 | } 333 | } 334 | 335 | return false; 336 | }); 337 | 338 | var existingRenderers = m_ExistingRenderers; 339 | existingRenderers.Clear(); 340 | existingRenderers.UnionWith(m_RootVolume.renderers); 341 | 342 | var removed = m_RemovedRenderers; 343 | removed.Clear(); 344 | removed.UnionWith(m_ExistingRenderers); 345 | removed.ExceptWith(renderers); 346 | 347 | var added = m_AddedRenderers; 348 | added.Clear(); 349 | added.UnionWith(renderers); 350 | added.ExceptWith(existingRenderers); 351 | 352 | foreach (var r in removed) 353 | { 354 | if (existingRenderers.Contains(r)) 355 | { 356 | yield return m_RootVolume.RemoveRenderer(r); 357 | 358 | // Check if the BVH shrunk 359 | yield return SetRootLODVolume(); 360 | } 361 | } 362 | 363 | foreach (var r in added) 364 | { 365 | if (!existingRenderers.Contains(r)) 366 | { 367 | yield return m_RootVolume.AddRenderer(r); 368 | r.transform.hasChanged = false; 369 | 370 | // Check if the BVH grew 371 | yield return SetRootLODVolume(); 372 | } 373 | } 374 | } 375 | 376 | IEnumerator SetRootLODVolume() 377 | { 378 | if (m_RootVolume) 379 | { 380 | var rootVolumeTransform = m_RootVolume.transform; 381 | var transformRoot = rootVolumeTransform.root; 382 | 383 | // Handle the case where the BVH has grown 384 | if (rootVolumeTransform != transformRoot) 385 | m_RootVolume = transformRoot.GetComponent(); 386 | 387 | yield break; 388 | } 389 | 390 | // Handle initialization or the case where the BVH has shrunk 391 | LODVolume lodVolume = null; 392 | var scene = SceneManager.GetActiveScene(); 393 | var rootGameObjects = scene.GetRootGameObjects(); 394 | foreach (var go in rootGameObjects) 395 | { 396 | if (!go) 397 | continue; 398 | 399 | lodVolume = go.GetComponent(); 400 | if (lodVolume) 401 | break; 402 | 403 | yield return null; 404 | } 405 | 406 | if (lodVolume) 407 | m_RootVolume = lodVolume; 408 | 409 | m_ExcludedRenderers.Clear(); 410 | } 411 | 412 | void EditorUpdate() 413 | { 414 | if ((m_CoroutineQueue.Count > 0 || m_SceneDirty || (m_RootVolume && m_RootVolume.dirty)) 415 | && m_ServiceCoroutineQueue == null) 416 | { 417 | m_ServiceCoroutineQueue = MonoBehaviourHelper.StartCoroutine(ServiceCoroutineQueue()); 418 | } 419 | } 420 | IEnumerator ServiceCoroutineQueue() 421 | { 422 | m_ServiceCoroutineExecutionTime.Start(); 423 | 424 | if (m_SceneDirty) 425 | { 426 | m_CoroutineQueue.Enqueue(UpdateOctree()); 427 | m_SceneDirty = false; 428 | } 429 | 430 | if (m_RootVolume && m_RootVolume.dirty) 431 | m_CoroutineQueue.Enqueue(m_RootVolume.UpdateHLODs()); 432 | 433 | while (m_CoroutineQueue.Count > 0) 434 | { 435 | yield return MonoBehaviourHelper.StartCoroutine(m_CoroutineQueue.Dequeue()); 436 | } 437 | 438 | ResetServiceCoroutineQueue(); 439 | } 440 | 441 | void ResetServiceCoroutineQueue() 442 | { 443 | m_ServiceCoroutineQueue = null; 444 | m_ServiceCoroutineExecutionTime.Reset(); 445 | } 446 | 447 | // PreCull is called before LODGroup updates 448 | void PreCull(Camera camera) 449 | { 450 | if (!m_RootVolume) 451 | return; 452 | 453 | var cameraType = camera.cameraType; 454 | var cameraTransform = camera.transform; 455 | var cameraPosition = cameraTransform.position; 456 | var cameraRotation = cameraTransform.rotation; 457 | 458 | if (((cameraType == CameraType.Game && camera == Camera.main) || cameraType == CameraType.SceneView) 459 | && (m_LastCamera != camera || m_LastCameraPosition != cameraPosition || m_LastCameraRotation != cameraRotation || m_SceneDirty)) 460 | { 461 | var deltaForward = Vector3.Dot(cameraPosition - m_LastCameraPosition, cameraTransform.forward); 462 | 463 | UpdateLODGroup(m_RootVolume, camera, cameraPosition, m_LastCamera == camera && deltaForward < 0f); 464 | 465 | m_LastCamera = camera; 466 | m_LastCameraPosition = cameraPosition; 467 | m_LastCameraRotation = cameraRotation; 468 | } 469 | } 470 | 471 | bool UpdateLODGroup(LODVolume lodVolume, Camera camera, Vector3 cameraPosition, bool fastPath) 472 | { 473 | var lodGroupEnabled = s_HLODEnabled; 474 | 475 | var lodGroup = lodVolume.lodGroup; 476 | var lodGroupExists = lodGroup != null && lodGroup.lodGroup; 477 | 478 | // Start with leaf nodes first 479 | var lodVolumeTransform = lodVolume.transform; 480 | var childVolumes = lodVolume.childVolumes; 481 | foreach (var childVolume in childVolumes) 482 | { 483 | if (childVolume) 484 | { 485 | if (!fastPath || !lodGroupExists || !lodGroup.lodGroup.enabled) 486 | lodGroupEnabled &= UpdateLODGroup(childVolume, camera, cameraPosition, fastPath); 487 | } 488 | } 489 | 490 | if (lodGroupEnabled) 491 | { 492 | var allChildrenUsingCoarsestLOD = true; 493 | if (lodVolumeTransform.childCount == 0) // Leaf node 494 | { 495 | var cached = lodVolume.cached; 496 | 497 | // Disable all children LODGroups if an HLOD LODGroup could replace it 498 | foreach (var r in cached) 499 | { 500 | var childLODGroup = r as LODVolume.LODGroupHelper; 501 | 502 | if (childLODGroup != null && childLODGroup.GetCurrentLOD(camera, cameraPosition) != childLODGroup.GetMaxLOD()) 503 | { 504 | allChildrenUsingCoarsestLOD = false; 505 | break; 506 | } 507 | } 508 | 509 | foreach (var r in cached) 510 | { 511 | var childLODGroup = r as LODVolume.LODGroupHelper; 512 | 513 | if (childLODGroup != null) 514 | childLODGroup.SetEnabled(!allChildrenUsingCoarsestLOD); 515 | else if (r != null) 516 | ((Renderer)r).enabled = !allChildrenUsingCoarsestLOD; 517 | } 518 | } 519 | else 520 | { 521 | foreach (var childVolume in childVolumes) 522 | { 523 | var childLODGroup = childVolume.lodGroup; 524 | if (childLODGroup != null && childLODGroup.lodGroup) 525 | { 526 | var maxLOD = childLODGroup.GetMaxLOD(); 527 | if (maxLOD > 0 && childLODGroup.GetCurrentLOD(camera, cameraPosition) != maxLOD) 528 | { 529 | allChildrenUsingCoarsestLOD = false; 530 | break; 531 | } 532 | } 533 | } 534 | 535 | foreach (var childVolume in childVolumes) 536 | { 537 | var childLODGroup = childVolume.lodGroup; 538 | if (childLODGroup != null && childLODGroup.lodGroup) 539 | childLODGroup.SetEnabled(!allChildrenUsingCoarsestLOD); 540 | } 541 | } 542 | 543 | lodGroupEnabled &= allChildrenUsingCoarsestLOD; 544 | } 545 | else if (!s_HLODEnabled && lodVolumeTransform.childCount == 0) // Re-enable default renderers 546 | { 547 | foreach (var r in lodVolume.renderers) 548 | { 549 | if (!r) 550 | continue; 551 | 552 | var childLODGroup = r.GetComponentInParent(); 553 | if (childLODGroup) 554 | childLODGroup.SetEnabled(true); 555 | else 556 | r.enabled = true; 557 | } 558 | } 559 | 560 | if (lodGroupExists) 561 | lodGroup.SetEnabled(lodGroupEnabled); 562 | 563 | return lodGroupEnabled; 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /Runtime/LODVolume.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using Unity.AutoLOD; 7 | using Unity.AutoLOD.Utilities; 8 | using UnityEditor; 9 | using UnityEngine; 10 | using UnityEngine.Rendering; 11 | 12 | [RequiresLayer(HLODLayer)] 13 | public class LODVolume : MonoBehaviour 14 | { 15 | [Serializable] 16 | public class LODGroupHelper 17 | { 18 | public LODGroup lodGroup 19 | { 20 | get { return m_LODGroup; } 21 | set 22 | { 23 | m_LODGroup = value; 24 | m_LODs = null; 25 | } 26 | } 27 | public LOD[] lods 28 | { 29 | get 30 | { 31 | if (m_LODs == null && m_LODGroup) 32 | m_LODs = m_LODGroup.GetLODs(); 33 | 34 | return m_LODs; 35 | } 36 | } 37 | public Vector3 referencePoint 38 | { 39 | get 40 | { 41 | if (!m_ReferencePoint.HasValue) 42 | m_ReferencePoint = m_LODGroup ? m_LODGroup.transform.TransformPoint(m_LODGroup.localReferencePoint) : Vector3.zero; 43 | 44 | return m_ReferencePoint.Value; 45 | } 46 | } 47 | public float worldSpaceSize 48 | { 49 | get 50 | { 51 | if (!m_WorldSpaceSize.HasValue && m_LODGroup) 52 | m_WorldSpaceSize = m_LODGroup.GetWorldSpaceSize(); 53 | 54 | return m_WorldSpaceSize ?? 0f; 55 | } 56 | 57 | } 58 | 59 | public int maxLOD 60 | { 61 | get 62 | { 63 | if (!m_MaxLOD.HasValue) 64 | m_MaxLOD = lodGroup.GetMaxLOD(); 65 | 66 | return m_MaxLOD.Value; 67 | } 68 | } 69 | 70 | [SerializeField] 71 | LODGroup m_LODGroup; 72 | 73 | //Transform m_Transform; 74 | LOD[] m_LODs; 75 | Vector3? m_ReferencePoint; 76 | float? m_WorldSpaceSize; 77 | int? m_MaxLOD; 78 | } 79 | 80 | public const string HLODLayer = "HLOD"; 81 | public static Type meshSimplifierType { set; get; } 82 | public static Type batcherType { set; get; } 83 | public static bool drawBounds { set; get; } 84 | 85 | public LODGroupHelper lodGroup 86 | { 87 | get 88 | { 89 | if (m_LODGroupHelper == null) 90 | { 91 | m_LODGroupHelper = new LODGroupHelper(); 92 | m_LODGroupHelper.lodGroup = GetComponent(); 93 | } 94 | 95 | return m_LODGroupHelper; 96 | } 97 | } 98 | 99 | public bool dirty; 100 | public Bounds bounds; 101 | public GameObject hlodRoot; 102 | public List renderers = new List(); 103 | public List childVolumes = new List(); 104 | 105 | public List cached 106 | { 107 | get 108 | { 109 | if (m_Cached.Count == 0) 110 | { 111 | renderers.RemoveAll(r => r == null); 112 | foreach (var r in renderers) 113 | { 114 | var lg = r.GetComponentInParent(); 115 | if (lg) 116 | { 117 | var lgh = new LODGroupHelper(); 118 | lgh.lodGroup = lg; 119 | m_Cached.Add(lgh); 120 | } 121 | else 122 | m_Cached.Add(r); 123 | } 124 | } 125 | 126 | return m_Cached; 127 | } 128 | } 129 | 130 | const HideFlags k_DefaultHideFlags = HideFlags.None; 131 | const ushort k_VolumeSplitRendererCount = 32;//ushort.MaxValue; 132 | const string k_DefaultName = "LODVolumeNode"; 133 | const string k_HLODRootContainer = "HLODs"; 134 | const int k_Splits = 2; 135 | 136 | static int s_VolumesCreated; 137 | 138 | LODGroupHelper m_LODGroupHelper; 139 | List m_Cached = new List(); 140 | 141 | IMeshSimplifier m_MeshSimplifier; 142 | 143 | static readonly Color[] k_DepthColors = new Color[] 144 | { 145 | Color.red, 146 | Color.green, 147 | Color.blue, 148 | Color.magenta, 149 | Color.yellow, 150 | Color.cyan, 151 | Color.grey, 152 | }; 153 | 154 | void Awake() 155 | { 156 | if (Application.isPlaying) 157 | { 158 | // Prime helper object properties on start to avoid hitches later 159 | var primeCached = cached; 160 | var primeLODGroup = lodGroup; 161 | var primeTransform = transform; 162 | if (lodGroup.lodGroup) 163 | { 164 | var primeLODs = lodGroup.lods; 165 | var primeMaxLOD = lodGroup.maxLOD; 166 | var primeReferencePoint = lodGroup.referencePoint; 167 | var primeWorldSize = lodGroup.worldSpaceSize; 168 | } 169 | 170 | foreach (var c in cached) 171 | { 172 | var lgh = c as LODGroupHelper; 173 | if (lgh != null) 174 | { 175 | var primeLODs = lgh.lods; 176 | var primeMaxLOD = lgh.maxLOD; 177 | var primeReferencePoint = lgh.referencePoint; 178 | var primeWorldSize = lgh.worldSpaceSize; 179 | } 180 | } 181 | } 182 | } 183 | 184 | public static LODVolume Create() 185 | { 186 | GameObject go = new GameObject(k_DefaultName + s_VolumesCreated++, typeof(LODVolume)); 187 | go.layer = LayerMask.NameToLayer(HLODLayer); 188 | LODVolume volume = go.GetComponent(); 189 | return volume; 190 | } 191 | 192 | public IEnumerator UpdateRenderer(Renderer renderer) 193 | { 194 | yield return RemoveRenderer(renderer); 195 | 196 | if (!this) 197 | yield break; 198 | 199 | var parent = transform.parent; 200 | var rootVolume = parent ? parent.GetComponentInParent() : this; // In case the BVH has shrunk 201 | yield return rootVolume.AddRenderer(renderer); 202 | } 203 | 204 | public IEnumerator AddRenderer(Renderer renderer) 205 | { 206 | if (!this) 207 | yield break; 208 | 209 | if (!renderer) 210 | yield break; 211 | 212 | if (renderer.gameObject.layer == LayerMask.NameToLayer(HLODLayer)) 213 | yield break; 214 | 215 | { 216 | var mf = renderer.GetComponent(); 217 | if (mf && mf.sharedMesh && mf.sharedMesh.GetTopology(0) != MeshTopology.Triangles) 218 | yield break; 219 | } 220 | 221 | 222 | if (renderers.Count == 0 && Mathf.Approximately(bounds.size.magnitude, 0f)) 223 | { 224 | bounds = renderer.bounds; 225 | bounds = GetCuboidBounds(bounds); 226 | 227 | transform.position = bounds.min; 228 | } 229 | 230 | // Each LODVolume maintains it's own list of renderers, which includes renderers in children nodes 231 | if (WithinBounds(renderer, bounds)) 232 | { 233 | if (!renderers.Contains(renderer)) 234 | renderers.Add(renderer); 235 | 236 | if (transform.childCount == 0) 237 | { 238 | if (renderers.Count > k_VolumeSplitRendererCount) 239 | yield return Split(); 240 | else 241 | yield return SetDirty(); 242 | } 243 | else 244 | { 245 | foreach (Transform child in transform) 246 | { 247 | if (!renderer) 248 | yield break; 249 | 250 | var lodVolume = child.GetComponent(); 251 | if (WithinBounds(renderer, lodVolume.bounds)) 252 | { 253 | yield return lodVolume.AddRenderer(renderer); 254 | break; 255 | } 256 | yield return null; 257 | } 258 | } 259 | } 260 | else if (!transform.parent) 261 | { 262 | if (transform.childCount == 0 && renderers.Count < k_VolumeSplitRendererCount) 263 | { 264 | bounds.Encapsulate(renderer.bounds); 265 | bounds = GetCuboidBounds(bounds); 266 | if (!renderers.Contains(renderer)) 267 | renderers.Add(renderer); 268 | 269 | yield return SetDirty(); 270 | } 271 | else 272 | { 273 | // Expand and then try to add at the larger bounds 274 | var targetBounds = bounds; 275 | targetBounds.Encapsulate(renderer.bounds); 276 | targetBounds = GetCuboidBounds(targetBounds); 277 | yield return Grow(targetBounds); 278 | yield return transform.parent.GetComponent().AddRenderer(renderer); 279 | } 280 | } 281 | 282 | } 283 | 284 | public IEnumerator RemoveRenderer(Renderer renderer) 285 | { 286 | if (renderers != null && renderers.Remove(renderer)) 287 | { 288 | foreach (Transform child in transform) 289 | { 290 | var lodVolume = child.GetComponent(); 291 | if (lodVolume) 292 | yield return lodVolume.RemoveRenderer(renderer); 293 | 294 | yield return null; 295 | } 296 | 297 | if (!transform.parent) 298 | yield return Shrink(); 299 | 300 | if (!this) 301 | yield break; 302 | 303 | if (transform.childCount == 0) 304 | yield return SetDirty(); 305 | } 306 | } 307 | 308 | [ContextMenu("Split")] 309 | void SplitContext() 310 | { 311 | MonoBehaviourHelper.StartCoroutine(Split()); 312 | } 313 | 314 | IEnumerator Split() 315 | { 316 | Vector3 size = bounds.size; 317 | size.x /= k_Splits; 318 | size.y /= k_Splits; 319 | size.z /= k_Splits; 320 | 321 | for (int i = 0; i < k_Splits; i++) 322 | { 323 | for (int j = 0; j < k_Splits; j++) 324 | { 325 | for (int k = 0; k < k_Splits; k++) 326 | { 327 | var lodVolume = Create(); 328 | var lodVolumeTransform = lodVolume.transform; 329 | lodVolumeTransform.parent = transform; 330 | var center = bounds.min + size * 0.5f + Vector3.Scale(size, new Vector3(i, j, k)); 331 | lodVolumeTransform.position = center; 332 | lodVolume.bounds = new Bounds(center, size); 333 | 334 | foreach (var r in renderers) 335 | { 336 | if (r && WithinBounds(r, lodVolume.bounds)) 337 | { 338 | lodVolume.renderers.Add(r); 339 | yield return lodVolume.SetDirty(); 340 | } 341 | } 342 | 343 | yield return null; 344 | } 345 | } 346 | } 347 | } 348 | 349 | [ContextMenu("Grow")] 350 | void GrowContext() 351 | { 352 | var targetBounds = bounds; 353 | targetBounds.center += Vector3.up; 354 | MonoBehaviourHelper.StartCoroutine(Grow(targetBounds)); 355 | } 356 | 357 | IEnumerator Grow(Bounds targetBounds) 358 | { 359 | var direction = Vector3.Normalize(targetBounds.center - bounds.center); 360 | Vector3 size = bounds.size; 361 | size.x *= k_Splits; 362 | size.y *= k_Splits; 363 | size.z *= k_Splits; 364 | 365 | var corners = new Vector3[] 366 | { 367 | bounds.min, 368 | bounds.min + Vector3.right * bounds.size.x, 369 | bounds.min + Vector3.forward * bounds.size.z, 370 | bounds.min + Vector3.up * bounds.size.y, 371 | bounds.min + Vector3.right * bounds.size.x + Vector3.forward * bounds.size.z, 372 | bounds.min + Vector3.right * bounds.size.x + Vector3.up * bounds.size.y, 373 | bounds.min + Vector3.forward * bounds.size.x + Vector3.up * bounds.size.y, 374 | bounds.min + Vector3.right * bounds.size.x + Vector3.forward * bounds.size.z + Vector3.up * bounds.size.y 375 | }; 376 | 377 | // Determine where the current volume is situated in the new expanded volume 378 | var best = 0f; 379 | var expandedVolumeCenter = bounds.min; 380 | foreach (var c in corners) 381 | { 382 | var dot = Vector3.Dot(c, direction); 383 | if (dot > best) 384 | { 385 | best = dot; 386 | expandedVolumeCenter = c; 387 | } 388 | yield return null; 389 | } 390 | 391 | var expandedVolume = Create(); 392 | var expandedVolumeTransform = expandedVolume.transform; 393 | expandedVolumeTransform.position = expandedVolumeCenter; 394 | expandedVolume.bounds = new Bounds(expandedVolumeCenter, size); 395 | expandedVolume.renderers = new List(renderers); 396 | var expandedBounds = expandedVolume.bounds; 397 | 398 | transform.parent = expandedVolumeTransform; 399 | 400 | var splitSize = bounds.size; 401 | var currentCenter = bounds.center; 402 | for (int i = 0; i < k_Splits; i++) 403 | { 404 | for (int j = 0; j < k_Splits; j++) 405 | { 406 | for (int k = 0; k < k_Splits; k++) 407 | { 408 | var center = expandedBounds.min + splitSize * 0.5f + Vector3.Scale(splitSize, new Vector3(i, j, k)); 409 | if (Mathf.Approximately(Vector3.Distance(center, currentCenter), 0f)) 410 | continue; // Skip the existing LODVolume we are growing from 411 | 412 | var lodVolume = Create(); 413 | var lodVolumeTransform = lodVolume.transform; 414 | lodVolumeTransform.parent = expandedVolumeTransform; 415 | lodVolumeTransform.position = center; 416 | lodVolume.bounds = new Bounds(center, splitSize); 417 | } 418 | } 419 | } 420 | } 421 | 422 | IEnumerator Shrink() 423 | { 424 | var populatedChildrenNodes = 0; 425 | foreach (Transform child in transform) 426 | { 427 | var lodVolume = child.GetComponent(); 428 | var renderers = lodVolume.renderers; 429 | if (renderers != null && renderers.Count > 0 && renderers.Count(r => r != null) > 0) 430 | populatedChildrenNodes++; 431 | 432 | yield return null; 433 | } 434 | 435 | if (populatedChildrenNodes <= 1) 436 | { 437 | var lodVolumes = GetComponentsInChildren(); 438 | LODVolume newRootVolume = null; 439 | if (lodVolumes.Length > 0) 440 | { 441 | newRootVolume = lodVolumes[lodVolumes.Length - 1]; 442 | newRootVolume.transform.parent = null; 443 | } 444 | 445 | // Clean up child HLODs before destroying the GameObject; Otherwise, we'd leak into the scene 446 | foreach (var lodVolume in lodVolumes) 447 | { 448 | if (lodVolume != newRootVolume) 449 | lodVolume.CleanupHLOD(); 450 | } 451 | DestroyImmediate(gameObject); 452 | 453 | if (newRootVolume) 454 | yield return newRootVolume.Shrink(); 455 | } 456 | } 457 | 458 | IEnumerator SetDirty() 459 | { 460 | dirty = true; 461 | 462 | childVolumes.Clear(); 463 | foreach (Transform child in transform) 464 | { 465 | var cv = child.GetComponent(); 466 | if (cv) 467 | childVolumes.Add(cv); 468 | } 469 | 470 | cached.Clear(); 471 | 472 | var lodVolumeParent = transform.parent; 473 | var parentLODVolume = lodVolumeParent ? lodVolumeParent.GetComponentInParent() : null; 474 | if (parentLODVolume) 475 | yield return parentLODVolume.SetDirty(); 476 | } 477 | 478 | static Bounds GetCuboidBounds(Bounds bounds) 479 | { 480 | // Expand bounds side lengths to maintain a cube 481 | var maxSize = Mathf.Max(Mathf.Max(bounds.size.x, bounds.size.y), bounds.size.z); 482 | var extents = Vector3.one * maxSize * 0.5f; 483 | bounds.center = bounds.min + extents; 484 | bounds.extents = extents; 485 | 486 | return bounds; 487 | } 488 | 489 | void OnDrawGizmos() 490 | { 491 | if (drawBounds) 492 | { 493 | var depth = GetDepth(transform); 494 | DrawGizmos(Mathf.Max(1f - Mathf.Pow(0.9f, depth), 0.2f), GetDepthColor(depth)); 495 | } 496 | } 497 | 498 | #if UNITY_EDITOR 499 | void OnDrawGizmosSelected() 500 | { 501 | if (Selection.activeGameObject == gameObject) 502 | DrawGizmos(1f, Color.magenta); 503 | } 504 | #endif 505 | 506 | void DrawGizmos(float alpha, Color color) 507 | { 508 | color.a = alpha; 509 | Gizmos.color = color; 510 | Gizmos.DrawWireCube(bounds.center, bounds.size); 511 | } 512 | 513 | #if UNITY_EDITOR 514 | [ContextMenu("GenerateHLOD")] 515 | void GenerateHLODContext() 516 | { 517 | MonoBehaviourHelper.StartCoroutine(GenerateHLOD()); 518 | } 519 | 520 | public IEnumerator UpdateHLODs() 521 | { 522 | // Process children first, since we are now combining children HLODs to make parent HLODs 523 | foreach (Transform child in transform) 524 | { 525 | var childLODVolume = child.GetComponent(); 526 | if (childLODVolume) 527 | yield return childLODVolume.UpdateHLODs(); 528 | 529 | if (!this) 530 | yield break; 531 | } 532 | 533 | if (dirty) 534 | { 535 | yield return GenerateHLOD(false); 536 | dirty = false; 537 | } 538 | } 539 | 540 | public IEnumerator GenerateHLOD(bool propagateUpwards = true) 541 | { 542 | var mergeChildrenVolumes = false; 543 | HashSet hlodRenderers = new HashSet(); 544 | 545 | var rendererMaterials = renderers.SelectMany(r => r.sharedMaterials); 546 | yield return null; 547 | 548 | foreach (Transform child in transform) 549 | { 550 | var childLODVolume = child.GetComponent(); 551 | if (childLODVolume && childLODVolume.renderers.Count > 0) 552 | { 553 | if (rendererMaterials.Except(childLODVolume.renderers.SelectMany(r => r.sharedMaterials)).Any()) 554 | { 555 | hlodRenderers.Clear(); 556 | mergeChildrenVolumes = false; 557 | break; 558 | } 559 | 560 | var childHLODRoot = childLODVolume.hlodRoot; 561 | if (childHLODRoot) 562 | { 563 | var childHLODRenderer = childHLODRoot.GetComponent(); 564 | if (childHLODRenderer) 565 | { 566 | mergeChildrenVolumes = true; 567 | hlodRenderers.Add(childHLODRenderer); 568 | continue; 569 | } 570 | } 571 | 572 | hlodRenderers.Clear(); 573 | mergeChildrenVolumes = false; 574 | break; 575 | } 576 | 577 | yield return null; 578 | } 579 | 580 | if (!mergeChildrenVolumes) 581 | { 582 | foreach (var r in renderers) 583 | { 584 | var mr = r as MeshRenderer; 585 | if (mr) 586 | { 587 | // Use the coarsest LOD if it exists 588 | var mrLODGroup = mr.GetComponentInParent(); 589 | if (mrLODGroup) 590 | { 591 | var mrLODs = mrLODGroup.GetLODs(); 592 | var maxLOD = mrLODGroup.GetMaxLOD(); 593 | var mrLOD = mrLODs[maxLOD]; 594 | foreach (var lr in mrLOD.renderers) 595 | { 596 | if (lr && lr.GetComponent()) 597 | hlodRenderers.Add(lr); 598 | } 599 | } 600 | else if (mr.GetComponent()) 601 | { 602 | hlodRenderers.Add(mr); 603 | } 604 | } 605 | 606 | yield return null; 607 | } 608 | } 609 | 610 | var lodRenderers = new List(); 611 | CleanupHLOD(); 612 | 613 | GameObject hlodRootContainer = null; 614 | yield return ObjectUtils.FindGameObject(k_HLODRootContainer, root => 615 | { 616 | if (root) 617 | hlodRootContainer = root; 618 | }); 619 | 620 | if (!hlodRootContainer) 621 | hlodRootContainer = new GameObject(k_HLODRootContainer); 622 | 623 | var hlodLayer = LayerMask.NameToLayer(HLODLayer); 624 | 625 | hlodRoot = new GameObject("HLOD"); 626 | hlodRoot.layer = hlodLayer; 627 | hlodRoot.transform.parent = hlodRootContainer.transform; 628 | 629 | if (mergeChildrenVolumes) 630 | { 631 | Material sharedMaterial = null; 632 | 633 | CombineInstance[] combine = new CombineInstance[hlodRenderers.Count]; 634 | var i = 0; 635 | foreach (var r in hlodRenderers) 636 | { 637 | var mf = r.GetComponent(); 638 | var ci = new CombineInstance(); 639 | ci.transform = r.transform.localToWorldMatrix; 640 | ci.mesh = mf.sharedMesh; 641 | combine[i] = ci; 642 | 643 | if (sharedMaterial == null) 644 | sharedMaterial = r.sharedMaterial; 645 | 646 | i++; 647 | } 648 | 649 | var sharedMesh = new Mesh(); 650 | sharedMesh.indexFormat = IndexFormat.UInt32; 651 | sharedMesh.CombineMeshes(combine, true, true); 652 | sharedMesh.RecalculateBounds(); 653 | var meshFilter = hlodRoot.AddComponent(); 654 | meshFilter.sharedMesh = sharedMesh; 655 | 656 | var meshRenderer = hlodRoot.AddComponent(); 657 | meshRenderer.sharedMaterial = sharedMaterial; 658 | } 659 | else 660 | { 661 | var parent = hlodRoot.transform; 662 | foreach (var r in hlodRenderers) 663 | { 664 | var rendererTransform = r.transform; 665 | 666 | var child = new GameObject(r.name, typeof(MeshFilter), typeof(MeshRenderer)); 667 | child.layer = hlodLayer; 668 | var childTransform = child.transform; 669 | childTransform.SetPositionAndRotation(rendererTransform.position, rendererTransform.rotation); 670 | childTransform.localScale = rendererTransform.lossyScale; 671 | childTransform.SetParent(parent, true); 672 | 673 | var mr = child.GetComponent(); 674 | EditorUtility.CopySerialized(r.GetComponent(), child.GetComponent()); 675 | EditorUtility.CopySerialized(r.GetComponent(), mr); 676 | 677 | lodRenderers.Add(mr); 678 | } 679 | } 680 | LOD lod = new LOD(); 681 | 682 | var lodGroup = GetComponent(); 683 | if (!lodGroup) 684 | lodGroup = gameObject.AddComponent(); 685 | this.lodGroup.lodGroup = lodGroup; 686 | 687 | if (!mergeChildrenVolumes) 688 | { 689 | var batcher = (IBatcher)Activator.CreateInstance(batcherType); 690 | yield return batcher.Batch(hlodRoot); 691 | } 692 | 693 | lod.renderers = hlodRoot.GetComponentsInChildren(false); 694 | lodGroup.SetLODs(new LOD[] { lod }); 695 | 696 | if (propagateUpwards) 697 | { 698 | var lodVolumeParent = transform.parent; 699 | var parentLODVolume = lodVolumeParent ? lodVolumeParent.GetComponentInParent() : null; 700 | if (parentLODVolume) 701 | yield return parentLODVolume.GenerateHLOD(); 702 | } 703 | } 704 | #endif 705 | 706 | void CleanupHLOD() 707 | { 708 | if (hlodRoot) // Clean up old HLOD 709 | { 710 | #if UNITY_EDITOR 711 | var mf = hlodRoot.GetComponent(); 712 | if (mf) 713 | DestroyImmediate(mf.sharedMesh, true); // Clean up file on disk 714 | #endif 715 | DestroyImmediate(hlodRoot); 716 | } 717 | } 718 | 719 | #if UNITY_EDITOR 720 | [ContextMenu("GenerateLODs")] 721 | void GenerateLODsContext() 722 | { 723 | GenerateLODs(); 724 | } 725 | 726 | void GenerateLODs() 727 | { 728 | int maxLOD = 1; 729 | var go = gameObject; 730 | 731 | var hlodLayer = LayerMask.NameToLayer(HLODLayer); 732 | 733 | var lodGroup = go.GetComponent(); 734 | if (lodGroup) 735 | { 736 | var lods = new LOD[maxLOD + 1]; 737 | var lod0 = lodGroup.GetLODs()[0]; 738 | lod0.screenRelativeTransitionHeight = 0.5f; 739 | lods[0] = lod0; 740 | 741 | var meshes = new List(); 742 | 743 | var totalMeshCount = maxLOD * lod0.renderers.Length; 744 | for (int l = 1; l <= maxLOD; l++) 745 | { 746 | var lodRenderers = new List(); 747 | foreach (var mr in lod0.renderers) 748 | { 749 | var mf = mr.GetComponent(); 750 | var sharedMesh = mf.sharedMesh; 751 | 752 | var lodTransform = EditorUtility.CreateGameObjectWithHideFlags(string.Format("{0} LOD{1}", sharedMesh.name, l), 753 | k_DefaultHideFlags, typeof(MeshFilter), typeof(MeshRenderer)).transform; 754 | lodTransform.gameObject.layer = hlodLayer; 755 | lodTransform.SetPositionAndRotation(mf.transform.position, mf.transform.rotation); 756 | lodTransform.localScale = mf.transform.lossyScale; 757 | lodTransform.SetParent(mf.transform, true); 758 | 759 | var lodMF = lodTransform.GetComponent(); 760 | var lodRenderer = lodTransform.GetComponent(); 761 | 762 | lodRenderers.Add(lodRenderer); 763 | 764 | EditorUtility.CopySerialized(mf, lodMF); 765 | EditorUtility.CopySerialized(mf.GetComponent(), lodRenderer); 766 | 767 | var simplifiedMesh = new Mesh(); 768 | simplifiedMesh.name = sharedMesh.name + string.Format(" LOD{0}", l); 769 | lodMF.sharedMesh = simplifiedMesh; 770 | meshes.Add(simplifiedMesh); 771 | 772 | var meshLOD = MeshLOD.GetGenericInstance(meshSimplifierType); 773 | meshLOD.InputMesh = sharedMesh; 774 | meshLOD.OutputMesh = simplifiedMesh; 775 | meshLOD.Quality = Mathf.Pow(0.5f, l); 776 | } 777 | 778 | var lod = lods[l]; 779 | lod.renderers = lodRenderers.ToArray(); 780 | lod.screenRelativeTransitionHeight = l == maxLOD ? 0.01f : Mathf.Pow(0.5f, l + 1); 781 | lods[l] = lod; 782 | } 783 | 784 | lodGroup.ForceLOD(0); 785 | lodGroup.SetLODs(lods.ToArray()); 786 | lodGroup.RecalculateBounds(); 787 | lodGroup.ForceLOD(-1); 788 | 789 | var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go); 790 | if (prefab) 791 | { 792 | var assetPath = AssetDatabase.GetAssetPath(prefab); 793 | var pathPrefix = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(assetPath); 794 | var lodsAssetPath = pathPrefix + "_lods.asset"; 795 | ObjectUtils.CreateAssetFromObjects(meshes.ToArray(), lodsAssetPath); 796 | } 797 | } 798 | } 799 | #endif 800 | 801 | public static int GetDepth(Transform transform) 802 | { 803 | int count = 0; 804 | Transform parent = transform.parent; 805 | while (parent) 806 | { 807 | count++; 808 | parent = parent.parent; 809 | } 810 | 811 | return count; 812 | } 813 | 814 | public static Color GetDepthColor(int depth) 815 | { 816 | return k_DepthColors[depth % k_DepthColors.Length]; 817 | } 818 | 819 | static bool WithinBounds(Renderer r, Bounds bounds) 820 | { 821 | // Use this approach if we are not going to split meshes and simply put the object in one volume or another 822 | return Mathf.Approximately(bounds.size.magnitude, 0f) || bounds.Contains(r.bounds.center); 823 | } 824 | } 825 | --------------------------------------------------------------------------------