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 SupportTextureFormatIndex; static Styles() { SupportTextureFormatStrings = new string[SupportTextureFormats.Length]; SupportTextureFormatIndex = new Dictionary(); 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 m_shaderGuids = new HashSet(); 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 infos, GameObject root, float cullDistance, float lodDistance, bool writeNoPrefab, bool extractMaterial, Action 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 hlodDatas = new Dictionary(); 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 rootDatas = new Dictionary(); 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(filename); m_manager.AddGeneratedResource(rootData); rootDatas.Add(item.Key, rootData); } var addressableController = root.AddComponent(); 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 hlodDatas, string filenamePrefix) { Dictionary hlodAllMaterials = new Dictionary(); //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 extractedMaterials = new Dictionary(); //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(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(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 convertedTable = new Dictionary(); private HLODTreeNode ConvertNode(HLODTreeNodeContainer container, SpaceNode rootNode) { HLODTreeNode root = new HLODTreeNode(); root.SetContainer(container); Queue hlodTreeNodes = new Queue(); Queue spaceNodes = new Queue(); Queue levels = new Queue(); 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 childTreeNodes = new List(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; } } }