// SPDX-FileCopyrightText: 2023 Unity Technologies and the Draco for Unity authors // SPDX-License-Identifier: Apache-2.0 using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Draco.Encode; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Assertions; namespace Draco.Sample.SceneEncodeDecode.Editor { static class SceneEncoder { const string k_CompressedMeshesDirName = "CompressedMeshes"; internal static List GetAllMeshFilters(Scene scene) { var objects = scene.GetRootGameObjects(); var meshFilters = new List(); if (objects != null && objects.Length > 0) { foreach (var gameObject in objects) { meshFilters.AddRange(gameObject.GetComponentsInChildren()); } } return meshFilters; } internal static MeshFilter[] GetAllMeshFilters(GameObject gameObject) { return gameObject.GetComponentsInChildren(); } internal static async Task CompressScene(Scene scene, bool setupMeshDecoder = false) { var scenePath = scene.path; var sceneDir = scenePath.Substring(0, scenePath.Length - 6); if (!Directory.Exists(sceneDir)) { Directory.CreateDirectory(sceneDir); } sceneDir = Path.Combine(sceneDir, k_CompressedMeshesDirName); if (!Directory.Exists(sceneDir)) { Directory.CreateDirectory(sceneDir); } var meshFilters = GetAllMeshFilters(scene); await CompressMeshFilters(meshFilters.ToArray(), sceneDir, setupMeshDecoder); } internal static async Task CompressMeshFilters(IEnumerable meshFilters, string directory = null, bool setupMeshDecoder = false) { var instances = new Dictionary(); var meshDecoder = Object.FindObjectOfType(); if (meshDecoder == null && setupMeshDecoder) { meshDecoder = new GameObject("MeshDecoder").AddComponent(); } directory = directory ?? $"Assets/{k_CompressedMeshesDirName}"; if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var dracoMeshes = new List(); var dracoFilesUpdated = false; foreach (var meshFilter in meshFilters) { var mesh = meshFilter.sharedMesh; if (mesh == null) continue; #if !UNITY_EDITOR if (!mesh.isReadable) { Debug.LogError("Mesh is not readable!"); return; } #endif var dracoMesh = new DracoMeshAsset(meshFilter, directory); var dracoFilesMissing = !dracoMesh.TryLoadDracoAssets(out var sampleAsset); if (dracoFilesMissing) { var scale = meshFilter.transform.localToWorldMatrix.lossyScale; var dracoData = await DracoEncoder.EncodeMesh( mesh, QuantizationSettings.FromWorldSize(mesh.bounds, scale, .0001f), SpeedSettings.Default ); if (dracoData != null && dracoData.Length > 0) { var projectPath = Directory.GetParent(Application.dataPath); Assert.IsNotNull(projectPath); for (var submesh = 0; submesh < dracoData.Length; submesh++) { if (submesh > 0) Debug.LogWarning("more than one submesh. not supported yet."); var subPath = dracoMesh.GetSubmeshAssetPath(submesh); File.WriteAllBytes( Path.Combine(projectPath.FullName, subPath), dracoData[submesh].data.ToArray() ); dracoData[submesh].Dispose(); dracoFilesUpdated = true; AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"`{meshFilter.name}` was encoded to {subPath}.", AssetDatabase.LoadAssetAtPath(subPath)); } } else { Debug.LogError($"Encoding `{meshFilter.name}` failed", meshFilter); return; } } else { Debug.Log($"Skipping `{meshFilter.name}`. Draco file(s) have been encoded before (e.g. at {dracoMesh.GetSubmeshAssetPath(0)}.", sampleAsset); } dracoMeshes.Add(dracoMesh); } if (dracoFilesUpdated) { foreach (var dracoMesh in dracoMeshes) { if (!dracoMesh.TryLoadDracoAssets(out _)) { Debug.LogError("Loading draco assets failed"); return; } } } foreach (var dracoMesh in dracoMeshes) { for (var submesh = 0; submesh < dracoMesh.submeshCount; submesh++) { var dracoAsset = dracoMesh.dracoAssets[submesh]; if (instances.TryGetValue(dracoAsset, out var instance)) { instance.AddTarget(dracoMesh.target); } else { var newInstance = ScriptableObject.CreateInstance(); var bounds = dracoMesh.target.sharedMesh.bounds; newInstance.SetAsset(dracoAsset, bounds); newInstance.AddTarget(dracoMesh.target); instances[dracoAsset] = newInstance; } } if (setupMeshDecoder) { dracoMesh.target.mesh = null; } } if (setupMeshDecoder) { meshDecoder.instances = instances.Values.ToArray(); } } } }