520 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.HLODSystem.SpaceManager;
using Unity.HLODSystem.Utils;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using Object = UnityEngine.Object;
namespace Unity.HLODSystem.Streaming
{
public class YooStreaming : IStreamingBuilder
{
static class Styles
{
public static TextureFormat[] SupportTextureFormats = new[]
{
TextureFormat.RGBA32,
TextureFormat.RGB24,
TextureFormat.BC7,
TextureFormat.DXT5,
TextureFormat.DXT1,
TextureFormat.ASTC_4x4,
TextureFormat.ASTC_5x5,
TextureFormat.ASTC_6x6,
TextureFormat.ASTC_8x8,
TextureFormat.ASTC_10x10,
TextureFormat.ASTC_12x12,
TextureFormat.ETC_RGB4,
TextureFormat.ETC2_RGB,
TextureFormat.ETC2_RGBA8,
TextureFormat.PVRTC_RGB4,
TextureFormat.PVRTC_RGB2,
TextureFormat.PVRTC_RGBA4,
TextureFormat.PVRTC_RGBA2,
};
public static string[] SupportTextureFormatStrings;
public static Dictionary<TextureFormat, int> SupportTextureFormatIndex;
static Styles()
{
SupportTextureFormatStrings = new string[SupportTextureFormats.Length];
SupportTextureFormatIndex = new Dictionary<TextureFormat, int>();
for (int i = 0; i < SupportTextureFormats.Length; ++i)
{
SupportTextureFormatStrings[i] = SupportTextureFormats[i].ToString();
SupportTextureFormatIndex[SupportTextureFormats[i]] = i;
}
}
}
[InitializeOnLoadMethod]
static void RegisterType()
{
StreamingBuilderTypes.RegisterType(typeof(YooStreaming));
}
private IGeneratedResourceManager m_manager;
private SerializableDynamicObject m_streamingOptions;
private int m_controllerID;
HashSet<string> m_shaderGuids = new HashSet<string>();
public YooStreaming(IGeneratedResourceManager manager, int controllerID, SerializableDynamicObject streamingOptions)
{
m_manager = manager;
m_streamingOptions = streamingOptions;
m_controllerID = controllerID;
}
public YooStreaming(TerrainHLOD terrainHlod, SerializableDynamicObject streamingOptions)
{
m_manager = terrainHlod;
m_streamingOptions = streamingOptions;
}
public void Build(SpaceNode rootNode, DisposableList<HLODBuildInfo> infos, GameObject root,
float cullDistance, float lodDistance, bool writeNoPrefab, bool extractMaterial, Action<float> onProgress)
{
dynamic options = m_streamingOptions;
string path = options.OutputDirectory;
HLODTreeNodeContainer container = new HLODTreeNodeContainer();
HLODTreeNode convertedRootNode = ConvertNode(container, rootNode);
//create settings if there is no settings.
// var settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
// var group = GetGroup(settings, options.AddressablesGroupName);
m_shaderGuids.Clear();
if (onProgress != null)
onProgress(0.0f);
HLODData.TextureCompressionData compressionData;
compressionData.PCTextureFormat = options.PCCompression;
compressionData.WebGLTextureFormat = options.WebGLCompression;
compressionData.AndroidTextureFormat = options.AndroidCompression;
compressionData.iOSTextureFormat = options.iOSCompression;
compressionData.tvOSTextureFormat = options.tvOSCompression;
string filenamePrefix = $"{path}{root.name}";
if (Directory.Exists(path) == false)
{
Directory.CreateDirectory(path);
}
Dictionary<int, HLODData> hlodDatas = new Dictionary<int, HLODData>();
for (int i = 0; i < infos.Count; ++i)
{
if (hlodDatas.ContainsKey(infos[i].ParentIndex) == false)
{
HLODData newData = new HLODData();
newData.CompressionData = compressionData;
hlodDatas.Add(infos[i].ParentIndex, newData);
}
HLODData data = hlodDatas[infos[i].ParentIndex];
data.AddFromWokringObjects(infos[i].Name, infos[i].WorkingObjects);
data.AddFromWorkingColliders(infos[i].Name, infos[i].Colliders);
if (writeNoPrefab)
{
if (hlodDatas.ContainsKey(i) == false)
{
HLODData newData = new HLODData();
newData.CompressionData = compressionData;
hlodDatas.Add(i, newData);
}
HLODData prefabData = hlodDatas[i];
var spaceNode = infos[i].Target;
for (int oi = 0; oi < spaceNode.Objects.Count; ++oi)
{
if (PrefabUtility.IsAnyPrefabInstanceRoot(spaceNode.Objects[oi]) == false)
{
prefabData.AddFromGameObject(spaceNode.Objects[oi]);
}
}
}
if (onProgress != null)
onProgress((float)i / (float)infos.Count);
}
if (extractMaterial)
{
ExtractMaterial(hlodDatas, filenamePrefix);
}
Dictionary<int, RootData> rootDatas = new Dictionary<int, RootData>();
foreach (var item in hlodDatas)
{
string filename = $"{filenamePrefix}_group{item.Key}.hlod";
using (Stream stream = new FileStream(filename, FileMode.Create))
{
HLODDataSerializer.Write(stream, item.Value);
stream.Close();
}
AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
RootData rootData = AssetDatabase.LoadAssetAtPath<RootData>(filename);
m_manager.AddGeneratedResource(rootData);
rootDatas.Add(item.Key, rootData);
}
var addressableController = root.AddComponent<YooAssetHLODController>();
addressableController.ControllerID = m_controllerID;
m_manager.AddGeneratedResource(addressableController);
for (int i = 0; i < infos.Count; ++i)
{
var spaceNode = infos[i].Target;
var hlodTreeNode = convertedTable[infos[i].Target];
for (int oi = 0; oi < spaceNode.Objects.Count; ++oi)
{
int highId = -1;
GameObject obj = spaceNode.Objects[oi];
if (PrefabUtility.IsPartOfAnyPrefab(obj) == false)
{
GameObject rootGameObject = null;
if (rootDatas.ContainsKey(i))
rootGameObject = rootDatas[i].GetRootObject(obj.name);
if (rootGameObject != null)
{
GameObject go = PrefabUtility.InstantiatePrefab(rootGameObject) as GameObject;
go.transform.SetParent(obj.transform.parent);
go.transform.localPosition = obj.transform.localPosition;
go.transform.localRotation = obj.transform.localRotation;
go.transform.localScale = obj.transform.localScale;
if (m_manager.IsGeneratedResource(obj))
m_manager.AddGeneratedResource(go);
else
m_manager.AddConvertedPrefabResource(go);
spaceNode.Objects.Add(go);
Object.DestroyImmediate(obj);
continue;
}
}
var address = GetAddress(spaceNode.Objects[oi]);
if (string.IsNullOrEmpty(address) && PrefabUtility.IsAnyPrefabInstanceRoot(spaceNode.Objects[oi]))
{
address = GetAddress(spaceNode.Objects[oi]);
}
if (address != null)
{
highId = addressableController.AddHighObject(address, spaceNode.Objects[oi]);
}
else
{
highId = addressableController.AddHighObject(spaceNode.Objects[oi]);
}
hlodTreeNode.HighObjectIds.Add(highId);
}
{
if (rootDatas[infos[i].ParentIndex].GetRootObject(infos[i].Name) != null)
{
string filename = $"{filenamePrefix}_group{infos[i].ParentIndex}.hlod";
int lowId = addressableController.AddLowObject(filename + "[" + infos[i].Name + "]");
hlodTreeNode.LowObjectIds.Add(lowId);
}
}
}
addressableController.Container = container;
addressableController.Root = convertedRootNode;
addressableController.CullDistance = cullDistance;
addressableController.LODDistance = lodDistance;
addressableController.UpdateMaxManualLevel();
}
private void ExtractMaterial(Dictionary<int, HLODData> hlodDatas, string filenamePrefix)
{
Dictionary<string, HLODData.SerializableMaterial> hlodAllMaterials = new Dictionary<string, HLODData.SerializableMaterial>();
//collect all materials
foreach (var hlodData in hlodDatas.Values)
{
var hlodMaterials = hlodData.GetMaterials();
for (int mi = 0; mi < hlodMaterials.Count; ++mi)
{
if (hlodAllMaterials.ContainsKey(hlodMaterials[mi].ID) == false)
{
hlodAllMaterials.Add(hlodMaterials[mi].ID, hlodMaterials[mi]);
}
}
}
Dictionary<string, HLODData.SerializableMaterial> extractedMaterials = new Dictionary<string, HLODData.SerializableMaterial>();
//save files to disk
foreach (var hlodMaterial in hlodAllMaterials)
{
hlodMaterial.Value.GetTextureCount();
Material mat = hlodMaterial.Value.To();
for (int ti = 0; ti < hlodMaterial.Value.GetTextureCount(); ++ti)
{
var serializeTexture = hlodMaterial.Value.GetTexture(ti);
Texture2D texture = serializeTexture.To();
byte[] bytes = texture.EncodeToPNG();
string textureFilename = $"{filenamePrefix}_{mat.name}_{serializeTexture.TextureName}.png";
File.WriteAllBytes(textureFilename, bytes);
AssetDatabase.ImportAsset(textureFilename);
var assetImporter = AssetImporter.GetAtPath(textureFilename);
var textureImporter = assetImporter as TextureImporter;
if (textureImporter)
{
textureImporter.wrapMode = serializeTexture.WrapMode;
textureImporter.sRGBTexture = GraphicsFormatUtility.IsSRGBFormat(serializeTexture.GraphicsFormat);
textureImporter.SaveAndReimport();
}
var storedTexture = AssetDatabase.LoadAssetAtPath<Texture>(textureFilename);
m_manager.AddGeneratedResource(storedTexture);
mat.SetTexture(serializeTexture.Name, storedTexture);
}
string matFilename = $"{filenamePrefix}_{mat.name}.mat";
AssetDatabase.CreateAsset(mat, matFilename);
AssetDatabase.ImportAsset(matFilename);
var storedMaterial = AssetDatabase.LoadAssetAtPath<Material>(matFilename);
m_manager.AddGeneratedResource(storedMaterial);
using (WorkingMaterial newWM = new WorkingMaterial(Collections.Allocator.Temp, storedMaterial))
{
var newSM = new HLODData.SerializableMaterial();
newSM.From(newWM);
extractedMaterials.Add(hlodMaterial.Key, newSM);
}
}
//apply to HLODData
foreach (var hlodData in hlodDatas.Values)
{
var materials = hlodData.GetMaterials();
for (int i = 0; i < materials.Count; ++i)
{
if (extractedMaterials.ContainsKey(materials[i].ID) == false)
continue;
materials[i] = extractedMaterials[materials[i].ID];
}
var objects = hlodData.GetObjects();
for (int oi = 0; oi < objects.Count; ++oi)
{
var matIds = objects[oi].GetMaterialIds();
for (int mi = 0; mi < matIds.Count; ++mi)
{
if (extractedMaterials.ContainsKey(matIds[mi]) == false)
continue;
matIds[mi] = extractedMaterials[matIds[mi]].ID;
}
}
}
}
Dictionary<SpaceNode, HLODTreeNode> convertedTable = new Dictionary<SpaceNode, HLODTreeNode>();
private HLODTreeNode ConvertNode(HLODTreeNodeContainer container, SpaceNode rootNode)
{
HLODTreeNode root = new HLODTreeNode();
root.SetContainer(container);
Queue<HLODTreeNode> hlodTreeNodes = new Queue<HLODTreeNode>();
Queue<SpaceNode> spaceNodes = new Queue<SpaceNode>();
Queue<int> levels = new Queue<int>();
hlodTreeNodes.Enqueue(root);
spaceNodes.Enqueue(rootNode);
levels.Enqueue(0);
while (hlodTreeNodes.Count > 0)
{
var hlodTreeNode = hlodTreeNodes.Dequeue();
var spaceNode = spaceNodes.Dequeue();
int level = levels.Dequeue();
convertedTable[spaceNode] = hlodTreeNode;
hlodTreeNode.Level = level;
hlodTreeNode.Bounds = spaceNode.Bounds;
if (spaceNode.HasChild() == true)
{
List<HLODTreeNode> childTreeNodes = new List<HLODTreeNode>(spaceNode.GetChildCount());
for (int i = 0; i < spaceNode.GetChildCount(); ++i)
{
var treeNode = new HLODTreeNode();
treeNode.SetContainer(container);
childTreeNodes.Add(treeNode);
hlodTreeNodes.Enqueue(treeNode);
spaceNodes.Enqueue(spaceNode.GetChild(i));
levels.Enqueue(level + 1);
}
hlodTreeNode.SetChildTreeNode(childTreeNodes);
}
}
return root;
}
static bool showFormat = true;
public static void OnGUI(SerializableDynamicObject streamingOptions)
{
dynamic options = streamingOptions;
#region Setup default values
if (options.OutputDirectory == null)
{
string path = Application.dataPath;
path = "Assets" + path.Substring(Application.dataPath.Length);
path = path.Replace('\\', '/');
if (path.EndsWith("/") == false)
path += "/";
options.OutputDirectory = path;
}
if (options.AddressablesGroupName == null)
{
options.AddressablesGroupName = "HLOD";
}
if (options.PCCompression == null)
{
options.PCCompression = TextureFormat.BC7;
}
if (options.WebGLCompression == null)
{
options.WebGLCompression = TextureFormat.DXT5;
}
if (options.AndroidCompression == null)
{
options.AndroidCompression = TextureFormat.ETC2_RGBA8;
}
if (options.iOSCompression == null)
{
options.iOSCompression = TextureFormat.PVRTC_RGBA4;
}
if (options.tvOSCompression == null)
{
options.tvOSCompression = TextureFormat.ASTC_4x4;
}
#endregion
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("OutputDirectory");
if (GUILayout.Button(options.OutputDirectory))
{
string selectPath = EditorUtility.OpenFolderPanel("Select output folder", "Assets", "");
if (selectPath.StartsWith(Application.dataPath))
{
selectPath = "Assets" + selectPath.Substring(Application.dataPath.Length);
selectPath = selectPath.Replace('\\', '/');
if (selectPath.EndsWith("/") == false)
selectPath += "/";
options.OutputDirectory = selectPath;
}
else
{
EditorUtility.DisplayDialog("Error", $"Select directory under {Application.dataPath}", "OK");
}
}
EditorGUILayout.EndHorizontal();
options.AddressablesGroupName = EditorGUILayout.TextField("Addressables Group", options.AddressablesGroupName);
// It stores return value from foldout and uses it as a condition.
if (showFormat = EditorGUILayout.Foldout(showFormat, "Compress Format"))
{
EditorGUI.indentLevel += 1;
options.PCCompression = PopupFormat("PC & Console", (TextureFormat)options.PCCompression);
options.WebGLCompression = PopupFormat("WebGL", (TextureFormat)options.WebGLCompression);
options.AndroidCompression = PopupFormat("Android", (TextureFormat)options.AndroidCompression);
options.iOSCompression = PopupFormat("iOS", (TextureFormat)options.iOSCompression);
options.tvOSCompression = PopupFormat("tvOS", (TextureFormat)options.tvOSCompression);
EditorGUI.indentLevel -= 1;
}
}
private static TextureFormat PopupFormat(string label, TextureFormat format)
{
int selectIndex = 0;
//no matter the format exists or not.
Styles.SupportTextureFormatIndex.TryGetValue(format, out selectIndex);
selectIndex = EditorGUILayout.Popup(label, selectIndex, Styles.SupportTextureFormatStrings);
if (selectIndex < 0)
selectIndex = 0;
return Styles.SupportTextureFormats[selectIndex];
}
private string GetAddress(Object obj)
{
if (obj != null)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
var split = assetPath.Split('/');
var name = split.Last();
assetPath = name.Split('.').First();
EditorGUIUtility.systemCopyBuffer = assetPath;
return assetPath;
}
return null;
}
private string GetAssetPath(Object obj)
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path) && PrefabUtility.GetPrefabInstanceStatus(obj) == PrefabInstanceStatus.Connected)
{
Object prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(obj);
path = AssetDatabase.GetAssetPath(prefab);
}
return path;
}
}
}