Files
2025-11-30 08:35:03 +02:00

180 lines
6.6 KiB
C#

// 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<MeshFilter> GetAllMeshFilters(Scene scene)
{
var objects = scene.GetRootGameObjects();
var meshFilters = new List<MeshFilter>();
if (objects != null && objects.Length > 0)
{
foreach (var gameObject in objects)
{
meshFilters.AddRange(gameObject.GetComponentsInChildren<MeshFilter>());
}
}
return meshFilters;
}
internal static MeshFilter[] GetAllMeshFilters(GameObject gameObject)
{
return gameObject.GetComponentsInChildren<MeshFilter>();
}
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<MeshFilter> meshFilters, string directory = null, bool setupMeshDecoder = false)
{
var instances = new Dictionary<TextAsset, DecodeInstance>();
var meshDecoder = Object.FindObjectOfType<DracoSceneDecoder>();
if (meshDecoder == null && setupMeshDecoder)
{
meshDecoder = new GameObject("MeshDecoder").AddComponent<DracoSceneDecoder>();
}
directory = directory ?? $"Assets/{k_CompressedMeshesDirName}";
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var dracoMeshes = new List<DracoMeshAsset>();
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<TextAsset>(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<DecodeInstance>();
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();
}
}
}
}