diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs new file mode 100644 index 0000000..ef352f4 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs @@ -0,0 +1,139 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using Unity.VisualScripting; +using UnityMeshSimplifier; + +public class LODGenerationTool : EditorWindow +{ + private float[] lodLevels = new float[] { 0.8f, 0.6f, 0.4f, 0.2f, 0.1f, 0.05f }; + private int minTriangleCount = 512; + + [MenuItem("Tools/Generate LODs")] + public static void ShowWindow() + { + GetWindow("LOD Generation"); + } + + void OnGUI() + { + GUILayout.Label("LOD Generation Settings", EditorStyles.boldLabel); + + EditorGUILayout.BeginVertical(); + for (int i = 0; i < lodLevels.Length; i++) + { + lodLevels[i] = EditorGUILayout.Slider($"LOD {i + 1} Quality", lodLevels[i], 0.01f, 1f); + } + EditorGUILayout.EndVertical(); + + minTriangleCount = EditorGUILayout.IntField("Minimum Triangle Count", minTriangleCount); + + if (GUILayout.Button("Generate LODs")) + { + GenerateLODs(); + } + } + + void GenerateLODs() + { + GameObject[] selectedObjects = Selection.gameObjects; + + foreach (GameObject obj in selectedObjects) + { + MeshFilter meshFilter = obj.GetComponent(); + MeshRenderer meshRenderer = obj.GetComponent(); + + if (meshFilter != null && meshRenderer != null) + { + Mesh originalMesh = meshFilter.sharedMesh; + Material[] materials = meshRenderer.sharedMaterials; + + LODGroup lodGroup = obj.GetComponent(); + if (lodGroup == null) + { + lodGroup = obj.AddComponent(); + } + + List lods = new List(); + + // Original mesh as LOD0 + LOD originalLOD = new LOD(1f, new Renderer[] { meshRenderer }); + lods.Add(originalLOD); + + for (int i = 0; i < lodLevels.Length; i++) + { + float quality = lodLevels[i]; + Mesh simplifiedMesh = SimplifyMesh(originalMesh, quality); + + if (simplifiedMesh.triangles.Length / 3 <= minTriangleCount) + { + Debug.Log($"Stopped LOD generation for {obj.name} at LOD {i + 1} due to minimum triangle count."); + break; + } + + GameObject lodObject = new GameObject($"LOD_{i + 1}"); + lodObject.transform.SetParent(obj.transform); + lodObject.transform.localPosition = Vector3.zero; + lodObject.transform.localRotation = Quaternion.identity; + lodObject.transform.localScale = Vector3.one; + + MeshFilter lodMeshFilter = lodObject.AddComponent(); + lodMeshFilter.sharedMesh = simplifiedMesh; + + MeshRenderer lodMeshRenderer = lodObject.AddComponent(); + lodMeshRenderer.sharedMaterials = materials; + + float lodThreshold = i < lodLevels.Length - 1 ? (lodLevels[i] + lodLevels[i + 1]) / 2 : 0.01f; + LOD lod = new LOD(lodThreshold, new Renderer[] { lodMeshRenderer }); + lods.Add(lod); + } + + lodGroup.SetLODs(lods.ToArray()); + lodGroup.RecalculateBounds(); + + EditorUtility.SetDirty(obj); + } + } + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + Mesh SimplifyMesh(Mesh originalMesh, float quality) + { + MeshSimplifier meshSimplifier = new MeshSimplifier(); + meshSimplifier.Initialize(originalMesh); + meshSimplifier.SimplifyMesh(quality); + + Mesh simplifiedMesh = meshSimplifier.ToMesh(); + LODGeneratorHelper LGH = new LODGeneratorHelper(); + SimplificationOptions SO = new SimplificationOptions(); + SO.PreserveBorderEdges = false; + SO.PreserveUVSeamEdges = false; + SO.PreserveUVFoldoverEdges = false; + SO.PreserveSurfaceCurvature = false; + SO.EnableSmartLink = true; + SO.VertexLinkDistance = double.Epsilon; + SO.MaxIterationCount = 100; + SO.Agressiveness = 7.0; + SO.ManualUVComponentCount = false; + SO.UVComponentCount = 2; + LGH.SimplificationOptions = SO; + float screenRelativeTransitionHeight = 1.0f; + float fadeTransitionWidth = 2.0f; + float fQuality = 1.0f; + bool combineMeshes = true; + bool combineSubMeshes = true; + Renderer[] renderers = new Renderer[1]; + LGH.Levels[0] = new LODLevel(screenRelativeTransitionHeight, fadeTransitionWidth, fQuality, combineMeshes, combineSubMeshes, renderers); + + // LODGroup lodGroup = simplifiedMesh.GetComponent(); + // LOD[] lods = lodGroup.GetLODs(); + // for (int i = 0; i < lodGroup.lodCount; ++i) + // { + // lods[0].screenRelativeTransitionHeight; + // } + + return simplifiedMesh; + } +} \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs.meta new file mode 100644 index 0000000..1395a17 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/LODGenerationTool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2554d2c73846439dbadf74825f8da1fb +timeCreated: 1728302143 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/MeshMerging.cs b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/MeshMerging.cs new file mode 100644 index 0000000..e69de29 diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/MeshMerging.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/MeshMerging.cs.meta new file mode 100644 index 0000000..96d4f09 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/EditorScripts/MeshMerging.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c6bf1b9aac7f4172baa344c0622431c3 +timeCreated: 1728649223 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs new file mode 100644 index 0000000..d1a4d1a --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs @@ -0,0 +1,124 @@ +using UnityEngine; +using System; +using System.Collections.Generic; +using UnityEngine.Rendering; + +public class CustomMeshRenderer : MonoBehaviour +{ + [System.Serializable] + private struct MeshInfo + { + public int vertexStart; + public int vertexCount; + public int indexStart; + public int indexCount; + } + + private struct ObjRenderFlags + { + public bool isVisible; + public int nLODLevel; + } + + List ObjectRenderFlags = new List(); + public Mesh combinedMesh; + public Mesh[,] m_mesh = new Mesh[2, 5]; + public Material[] m_material = new Material[2]; + private List meshInfos; + public Transform objectTransform; + + private GraphicsBuffer[,] meshIndices = new GraphicsBuffer[2, 5]; + private GraphicsBuffer[,] meshPositions = new GraphicsBuffer[2,5]; + private GraphicsBuffer[,] meshNormals = new GraphicsBuffer[2,5]; + private GraphicsBuffer[,] meshTangents = new GraphicsBuffer[2,5]; + private GraphicsBuffer[,] meshTexcoords = new GraphicsBuffer[2,5]; + + private CommandBuffer cmd; + + // Knowledge of OctTree and built status + // Knowledge of Objects and Mesh data (1to1 array) + // Communication to system to say that a GameObject is no longer considered static + // Communication to server to inform of GameObject state change + // Knowledge of streaming system + // Knowledge of LOD state + + void Start() + { + cmd = new CommandBuffer(); + cmd.name = "Custom Mesh Renderer"; + + // Add this command buffer to the main light's shadow pass + Light mainLight = RenderSettings.sun; + if (mainLight != null) + { + mainLight.AddCommandBuffer(LightEvent.BeforeScreenspaceMask, cmd); + } + } + + void DrawMeshStream(int nOpacity, int nLODLevel, Material _material, int _indexStart, int _indexCount, int _instanceCount) + { + MaterialPropertyBlock properties = new MaterialPropertyBlock(); + properties.SetBuffer("_Positions", meshPositions[nOpacity, nLODLevel]); + properties.SetBuffer("_Normals", meshNormals[nOpacity, nLODLevel]); + properties.SetBuffer("_Tangents", meshTangents[nOpacity, nLODLevel]); + properties.SetBuffer("_Texcoords", meshTexcoords[nOpacity, nLODLevel]); + properties.SetInt("_StartIndex", _indexStart); + cmd.DrawProcedural(meshIndices[nOpacity, nLODLevel], objectTransform.localToWorldMatrix, _material, shaderPass:0, MeshTopology.Triangles, _indexCount, _instanceCount, properties); + } + + void Update() + { + cmd.Clear(); + + int nVertexStart = Int32.MaxValue; + int nIndexStart = Int32.MaxValue; + int nVertexCount = 0; + int nIndexCount = 0; + int nMaxLODLevel = 5; + for (int nOpacity = 0; nOpacity < 2; ++nOpacity) + { + for (int nLODLevel = 0; nLODLevel < nMaxLODLevel; ++nLODLevel) + { + for (int i = 0; i < ObjectRenderFlags.Count; ++i) + { + if (ObjectRenderFlags[i].isVisible == true) + { + if (ObjectRenderFlags[i].nLODLevel == nLODLevel) + { + if (nVertexStart == Int32.MaxValue) + { + nVertexStart = meshInfos[i].vertexStart; + nIndexStart = meshInfos[i].indexStart; + } + + nVertexCount += meshInfos[i].vertexCount; + nIndexCount += meshInfos[i].indexCount; + + continue; + } + } + + if (nVertexCount > 0 && nIndexCount > 0) // Draw all previously collated meshes + { + DrawMeshStream(nOpacity, nLODLevel, m_material[nOpacity], nIndexStart, nIndexCount, 0); + nVertexStart = Int32.MaxValue; + nIndexStart = Int32.MaxValue; + nVertexCount = 0; + nIndexCount = 0; + } + } + DrawMeshStream(nOpacity, nLODLevel, m_material[nOpacity], nIndexStart, nIndexCount, 0); + } + } + } + + void OnDestroy() + { + Light mainLight = RenderSettings.sun; + if (mainLight != null) + { + mainLight.RemoveCommandBuffer(LightEvent.BeforeScreenspaceMask, cmd); + } + cmd.Release(); + } +} \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs.meta new file mode 100644 index 0000000..cb0d72b --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/HLOD.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7f927386ed424993a6f67d2d6ec5d2a0 +timeCreated: 1728378339 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs new file mode 100644 index 0000000..85cad9c --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs @@ -0,0 +1,553 @@ +// using UnityEngine; +// using Unity.Collections; +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Runtime.Serialization.Formatters.Binary; +// using Unity.Mathematics; +// using System.Security.Cryptography; +// using System.Text; +// +// [System.Serializable] +// public class OctTree +// { +// [System.Serializable] +// private class OctreeNode +// { +// public Bounds bounds; +// public List objectIndices; +// public OctreeNode[] children; +// public string nodeHash; +// +// public OctreeNode(Bounds bounds) +// { +// this.bounds = bounds; +// this.objectIndices = new List(); +// this.children = new OctreeNode[8]; +// this.nodeHash = ""; +// } +// +// public void UpdateNodeHash(List allObjects) +// { +// StringBuilder sb = new StringBuilder(); +// foreach (int index in objectIndices) +// { +// GameObject obj = allObjects[index]; +// sb.Append(obj.name); +// sb.Append(obj.transform.position.ToString()); +// Renderer renderer = obj.GetComponent(); +// if (renderer != null) +// { +// sb.Append(renderer.bounds.size.ToString()); +// } +// } +// using (SHA256 sha256 = SHA256.Create()) +// { +// byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())); +// nodeHash = BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 16); +// } +// } +// } +// +// private const int MAX_OBJECTS = 10; +// private const int MAX_LEVELS = 5; +// private const float LARGE_OBJECT_THRESHOLD = 0.5f; // Objects larger than 50% of node size are considered large +// +// //private int level; +// private OctreeNode root; +// private List allObjects; // Needs moving to HLOD object +// public float[] lodDistances; +// public string versionHash; +// public Dictionary componentHashes; +// +// private List smallObjects; // Needs moving to HLOD object +// private List largeObjects; // Needs moving to HLOD object +// +// public OctTree(Bounds worldBounds, float[] lodDistances, string versionHash) +// { +// this.root = new OctreeNode(worldBounds); +// this.lodDistances = lodDistances; +// this.allObjects = new List(); +// this.versionHash = versionHash; +// this.componentHashes = new Dictionary(); +// } +// +// public byte[] Serialize() +// { +// using (MemoryStream ms = new MemoryStream()) +// { +// BinaryFormatter formatter = new BinaryFormatter(); +// formatter.Serialize(ms, this); +// return ms.ToArray(); +// } +// } +// +// public static OctTree Deserialize(byte[] data) +// { +// using (MemoryStream ms = new MemoryStream(data)) +// { +// BinaryFormatter formatter = new BinaryFormatter(); +// return (OctTree)formatter.Deserialize(ms); +// } +// } +// +// public void InitializeTree(NativeArray positions, NativeArray sizes, NativeArray nodeIndices) +// { +// for (int i = 0; i < positions.Length; i++) +// { +// InsertObject(i, positions[i], sizes[i], nodeIndices[i]); +// } +// //UpdateAllNodeHashes(); +// } +// +// private void InsertObject(int objectIndex, float3 position, float3 size, int nodeIndex) +// { +// OctreeNode currentNode = root; +// while (true) +// { +// if (nodeIndex == 0 || currentNode.children[0] == null) +// { +// currentNode.objectIndices.Add(objectIndex); +// break; +// } +// +// int childIndex = nodeIndex % 8; +// nodeIndex /= 8; +// +// if (currentNode.children[childIndex] == null) +// { +// Vector3 childSize = currentNode.bounds.size * 0.5f; +// Vector3 childCenter = currentNode.bounds.center + new Vector3( +// ((childIndex & 1) != 0) ? childSize.x * 0.5f : -childSize.x * 0.5f, +// ((childIndex & 2) != 0) ? childSize.y * 0.5f : -childSize.y * 0.5f, +// ((childIndex & 4) != 0) ? childSize.z * 0.5f : -childSize.z * 0.5f +// ); +// currentNode.children[childIndex] = new OctreeNode(new Bounds(childCenter, childSize)); +// } +// +// currentNode = currentNode.children[childIndex]; +// } +// } +// +// private void UpdateAllNodeHashes() +// { +// UpdateNodeHashRecursive(root); +// } +// +// private void UpdateNodeHashRecursive(OctreeNode node) +// { +// node.UpdateNodeHash(allObjects); +// foreach (var child in node.children) +// { +// if (child != null) +// { +// UpdateNodeHashRecursive(child); +// } +// } +// } +// +// // public void SetObjects(List objects) +// // { +// // this.allObjects = objects; +// // } +// +// public List GetChangedNodes(OctTree newTree) +// { +// List changedNodes = new List(); +// CompareNodesRecursive(this.root, newTree.root, "", changedNodes); +// return changedNodes; +// } +// +// private void CompareNodesRecursive(OctreeNode oldNode, OctreeNode newNode, string path, List changedNodes) +// { +// if (oldNode.nodeHash != newNode.nodeHash) +// { +// changedNodes.Add(path); +// } +// +// for (int i = 0; i < 8; i++) +// { +// if (oldNode.children[i] != null && newNode.children[i] != null) +// { +// CompareNodesRecursive(oldNode.children[i], newNode.children[i], path + i, changedNodes); +// } +// else if (oldNode.children[i] != null || newNode.children[i] != null) +// { +// changedNodes.Add(path + i); +// } +// } +// } +// +// public void UpdateNodes(List nodePaths) +// { +// foreach (string path in nodePaths) +// { +// UpdateNodeByPath(path); +// } +// } +// +// private void UpdateNodeByPath(string path) +// { +// OctreeNode node = root; +// for (int i = 0; i < path.Length; i++) +// { +// int childIndex = int.Parse(path[i].ToString()); +// if (node.children[childIndex] == null) +// { +// // Create the node if it doesn't exist +// Vector3 childSize = node.bounds.size * 0.5f; +// Vector3 childCenter = node.bounds.center + new Vector3( +// ((childIndex & 1) != 0) ? childSize.x * 0.5f : -childSize.x * 0.5f, +// ((childIndex & 2) != 0) ? childSize.y * 0.5f : -childSize.y * 0.5f, +// ((childIndex & 4) != 0) ? childSize.z * 0.5f : -childSize.z * 0.5f +// ); +// node.children[childIndex] = new OctreeNode(new Bounds(childCenter, childSize)); +// } +// node = node.children[childIndex]; +// } +// node.UpdateNode(allObjects, MAX_OBJECTS_PER_NODE, MAX_TREE_DEPTH, path.Length); +// } +// +// public bool Insert(GameObject obj) +// { +// if (!bounds.Intersects(obj.GetComponent().bounds)) +// { +// return false; +// } +// +// if (IsLargeObject(obj)) +// { +// largeObjects.Add(obj); +// return true; +// } +// +// if (nodes[0] != null) +// { +// List indices = GetIndices(obj.GetComponent().bounds); +// bool inserted = false; +// foreach (int index in indices) +// { +// inserted |= nodes[index].Insert(obj); +// } +// return inserted; +// } +// +// smallObjects.Add(obj); +// +// if (smallObjects.Count > MAX_OBJECTS && level < MAX_LEVELS) +// { +// Split(); +// } +// +// return true; +// } +// +// public void BatchInsert(IEnumerable objects) +// { +// foreach (var obj in objects) +// { +// Insert(obj); +// } +// Rebalance(); +// } +// +// public bool Remove(GameObject obj) +// { +// if (!bounds.Intersects(obj.GetComponent().bounds)) +// { +// return false; +// } +// +// if (largeObjects.Remove(obj)) +// { +// return true; +// } +// +// if (nodes[0] != null) +// { +// List indices = GetIndices(obj.GetComponent().bounds); +// bool removed = false; +// foreach (int index in indices) +// { +// removed |= nodes[index].Remove(obj); +// } +// return removed; +// } +// +// return smallObjects.Remove(obj); +// } +// +// private void Split() +// { +// Vector3 subSize = bounds.size / 2f; +// Vector3 center = bounds.center; +// +// for (int i = 0; i < 8; i++) +// { +// Vector3 newCenter = center; +// newCenter.x += (i & 1) == 0 ? subSize.x / 2 : -subSize.x / 2; +// newCenter.y += (i & 2) == 0 ? subSize.y / 2 : -subSize.y / 2; +// newCenter.z += (i & 4) == 0 ? subSize.z / 2 : -subSize.z / 2; +// +// nodes[i] = new OctTree(level + 1, new Bounds(newCenter, subSize), lodDistances); +// } +// +// List objectsToReinsert = new List(smallObjects); +// smallObjects.Clear(); +// +// foreach (var obj in objectsToReinsert) +// { +// List indices = GetIndices(obj.GetComponent().bounds); +// foreach (int index in indices) +// { +// nodes[index].Insert(obj); +// } +// } +// } +// +// private List GetIndices(Bounds objBounds) +// { +// List indices = new List(); +// Vector3 center = bounds.center; +// +// bool rightOfLeft = objBounds.max.x > center.x - bounds.extents.x; +// bool leftOfRight = objBounds.min.x < center.x + bounds.extents.x; +// bool aboveBottom = objBounds.max.y > center.y - bounds.extents.y; +// bool belowTop = objBounds.min.y < center.y + bounds.extents.y; +// bool inFrontOfBack = objBounds.max.z > center.z - bounds.extents.z; +// bool behindFront = objBounds.min.z < center.z + bounds.extents.z; +// +// for (int i = 0; i < 8; i++) +// { +// bool inThisOctant = true; +// +// if (((i & 1) == 0 && !rightOfLeft) || ((i & 1) == 1 && !leftOfRight)) +// inThisOctant = false; +// if (((i & 2) == 0 && !aboveBottom) || ((i & 2) == 2 && !belowTop)) +// inThisOctant = false; +// if (((i & 4) == 0 && !inFrontOfBack) || ((i & 4) == 4 && !behindFront)) +// inThisOctant = false; +// +// if (inThisOctant) +// indices.Add(i); +// } +// +// return indices; +// } +// +// private bool IsLargeObject(GameObject obj) +// { +// Bounds objBounds = obj.GetComponent().bounds; +// return objBounds.size.x > bounds.size.x * LARGE_OBJECT_THRESHOLD || +// objBounds.size.y > bounds.size.y * LARGE_OBJECT_THRESHOLD || +// objBounds.size.z > bounds.size.z * LARGE_OBJECT_THRESHOLD; +// } +// +// public void Rebalance() +// { +// if (nodes[0] != null) +// { +// for (int i = 0; i < 8; i++) +// { +// nodes[i].Rebalance(); +// } +// +// int totalObjects = nodes.Sum(n => n.GetTotalObjects()); +// if (totalObjects <= MAX_OBJECTS) +// { +// MergeChildren(); +// } +// } +// else if (smallObjects.Count > MAX_OBJECTS && level < MAX_LEVELS) +// { +// Split(); +// } +// } +// +// private void MergeChildren() +// { +// for (int i = 0; i < 8; i++) +// { +// smallObjects.AddRange(nodes[i].smallObjects); +// largeObjects.AddRange(nodes[i].largeObjects); +// nodes[i] = null; +// } +// } +// +// private int GetTotalObjects() +// { +// return smallObjects.Count + largeObjects.Count + (nodes[0] != null ? nodes.Sum(n => n.GetTotalObjects()) : 0); +// } +// +// public List GetVisibleObjects(Camera camera, Vector3 cameraPosition) +// { +// List visibleObjects = new List(); +// GetVisibleObjectsRecursive(camera, cameraPosition, visibleObjects); +// return visibleObjects; +// } +// +// private void GetVisibleObjectsRecursive(Camera camera, Vector3 cameraPosition, List visibleObjects) +// { +// if (!IsVisibleToCamera(camera)) +// { +// return; +// } +// +// float distanceToCamera = Vector3.Distance(cameraPosition, bounds.center); +// int lodLevel = GetLODLevel(distanceToCamera); +// +// // Check large objects +// foreach (GameObject obj in largeObjects) +// { +// if (IsObjectVisible(obj, camera)) +// { +// visibleObjects.Add(obj); +// SetLODForObject(obj, lodLevel); +// } +// } +// +// if (lodLevel == level || nodes[0] == null) +// { +// foreach (GameObject obj in smallObjects) +// { +// if (IsObjectVisible(obj, camera)) +// { +// visibleObjects.Add(obj); +// SetLODForObject(obj, lodLevel); +// } +// } +// } +// else if (nodes[0] != null) +// { +// for (int i = 0; i < 8; i++) +// { +// nodes[i].GetVisibleObjectsRecursive(camera, cameraPosition, visibleObjects); +// } +// } +// } +// +// private bool IsVisibleToCamera(Camera camera) +// { +// Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera); +// return GeometryUtility.TestPlanesAABB(frustumPlanes, bounds); +// } +// +// private bool IsObjectVisible(GameObject obj, Camera camera) +// { +// Renderer renderer = obj.GetComponent(); +// return renderer != null && renderer.isVisible; +// } +// +// private int GetLODLevel(float distance) +// { +// for (int i = 0; i < lodDistances.Length; i++) +// { +// if (distance <= lodDistances[i]) +// { +// return i; +// } +// } +// return lodDistances.Length - 1; +// } +// +// private void SetLODForObject(GameObject obj, int lodLevel) +// { +// LODGroup lodGroup = obj.GetComponent(); +// if (lodGroup != null) +// { +// lodGroup.ForceLOD(lodLevel); +// } +// } +// +// public void DrawDebugGizmos(bool drawBounds, bool drawObjects, LayerMask layerMask) +// { +// if (drawBounds) +// { +// Gizmos.color = new Color(1, 1, 1, 0.5f); // Semi-transparent white +// Gizmos.DrawWireCube(bounds.center, bounds.size); +// } +// +// if (drawObjects) +// { +// DrawObjectsGizmos(smallObjects, Color.green, 0.1f, layerMask); +// DrawObjectsGizmos(largeObjects, Color.red, 0.2f, layerMask); +// } +// +// if (nodes[0] != null) +// { +// foreach (var node in nodes) +// { +// node.DrawDebugGizmos(drawBounds, drawObjects, layerMask); +// } +// } +// } +// +// private void DrawObjectsGizmos(List objects, Color color, float size, LayerMask layerMask) +// { +// Gizmos.color = color; +// foreach (var obj in objects) +// { +// if (obj != null && ((1 << obj.layer) & layerMask) != 0) +// { +// Gizmos.DrawSphere(obj.transform.position, size); +// } +// } +// } +// +// public void DrawLODLevels(Camera camera, Vector3 cameraPosition) +// { +// float distanceToCamera = Vector3.Distance(cameraPosition, bounds.center); +// int lodLevel = GetLODLevel(distanceToCamera); +// +// Color lodColor = GetLODColor(lodLevel); +// Gizmos.color = lodColor; +// Gizmos.DrawWireCube(bounds.center, bounds.size); +// +// if (nodes[0] != null) +// { +// foreach (var node in nodes) +// { +// node.DrawLODLevels(camera, cameraPosition); +// } +// } +// } +// +// private Color GetLODColor(int lodLevel) +// { +// switch (lodLevel) +// { +// case 0: return Color.red; // Highest detail +// case 1: return Color.yellow; +// case 2: return Color.green; +// case 3: return Color.blue; +// default: return Color.gray; // Lowest detail +// } +// } +// +// public TreeStatistics GetTreeStatistics() +// { +// TreeStatistics stats = new TreeStatistics(); +// CollectStatistics(stats, 0); +// return stats; +// } +// +// private void CollectStatistics(TreeStatistics stats, int depth) +// { +// stats.TotalNodes++; +// stats.MaxDepth = Mathf.Max(stats.MaxDepth, depth); +// stats.TotalObjects += smallObjects.Count + largeObjects.Count; +// stats.ObjectsPerLevel[depth] = (stats.ObjectsPerLevel.ContainsKey(depth) ? stats.ObjectsPerLevel[depth] : 0) + smallObjects.Count + largeObjects.Count; +// +// if (nodes[0] != null) +// { +// foreach (var node in nodes) +// { +// node.CollectStatistics(stats, depth + 1); +// } +// } +// else +// { +// stats.LeafNodes++; +// } +// } +// } \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs.meta new file mode 100644 index 0000000..f9b606e --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTree.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b47ec23a15f546eca41fce5d1486b564 +timeCreated: 1728405796 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs new file mode 100644 index 0000000..7f8ee02 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs @@ -0,0 +1,46 @@ +using Unity.Jobs; +using Unity.Collections; +using Unity.Burst; +using Unity.Mathematics; + +[BurstCompile] +public struct OctreeBuilderJob : IJobParallelFor +{ + [ReadOnly] public NativeArray Positions; + [ReadOnly] public NativeArray Sizes; + public NativeArray NodeIndices; + public float3 TreeCenter; + public float3 TreeSize; + public int MaxDepth; + + public void Execute(int index) + { + float3 position = Positions[index]; + float3 size = Sizes[index]; + int nodeIndex = 0; + int depth = 0; + + while (depth < MaxDepth) + { + float3 nodeCenter = TreeCenter; + float3 nodeSize = TreeSize; + + for (int i = 0; i < depth; i++) + { + nodeSize *= 0.5f; + int childIndex = 0; + if (position.x >= nodeCenter.x) { childIndex |= 1; nodeCenter.x += nodeSize.x * 0.5f; } else { nodeCenter.x -= nodeSize.x * 0.5f; } + if (position.y >= nodeCenter.y) { childIndex |= 2; nodeCenter.y += nodeSize.y * 0.5f; } else { nodeCenter.y -= nodeSize.y * 0.5f; } + if (position.z >= nodeCenter.z) { childIndex |= 4; nodeCenter.z += nodeSize.z * 0.5f; } else { nodeCenter.z -= nodeSize.z * 0.5f; } + nodeIndex = nodeIndex * 8 + childIndex + 1; + } + + if (math.all(size <= nodeSize)) + break; + + depth++; + } + + NodeIndices[index] = nodeIndex; + } +} \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs.meta new file mode 100644 index 0000000..fd10daa --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeBuilderJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 49f0225911a34b929d6d44e9d00cf4b8 +timeCreated: 1728405862 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs new file mode 100644 index 0000000..60500cb --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs @@ -0,0 +1,560 @@ +// using UnityEngine; +// using Unity.Jobs; +// using Unity.Collections; +// using System; +// using System.Collections; +// using System.Collections.Generic; +// using System.IO; +// using Unity.Mathematics; +// using System.Security.Cryptography; +// using System.Text; +// using System.Linq; +// using System.Diagnostics; +// +// public class OctTreeManager : MonoBehaviour +// { +// public OctTree octTree; +// public Bounds worldBounds; +// public float[] lodDistances = { 10f, 50f, 100f, 200f }; // Example distances +// public Camera mainCamera; +// +// // [SerializeField] +// // private string staticObjectTag = "StaticLODObject"; +// +// [SerializeField] private string staticObjectTag = "StaticLODObject"; +// [SerializeField] private int maxJobBatchSize = 1024; +// [SerializeField] private int objectsPerFrame = 1000; +// [SerializeField] private string serializationPath = "OctreeData.bin"; +// [SerializeField] private bool debugMode = false; +// +// private List staticObjects; +// private Coroutine buildTreeCoroutine; +// private bool isBuilding = false; +// +// private const string CODE_VERSION = "1.0.0"; +// +// // Performance metrics +// private Stopwatch stopwatch = new Stopwatch(); +// private long lastFullRebuildTime; +// private long lastGranularUpdateTime; +// private int lastGranularUpdateNodeCount; +// private int totalFullRebuilds; +// private int totalGranularUpdates; +// private long totalFullRebuildTime; +// private long totalGranularUpdateTime; +// +// [SerializeField] private bool drawDebugGizmos = true; +// [SerializeField] private bool drawBounds = true; +// [SerializeField] private bool drawObjects = true; +// [SerializeField] private bool drawLODLevels = true; +// [SerializeField] private LayerMask visualizationLayerMask = -1; // All layers by default +// +// void OnDrawGizmos() +// { +// if (octTree == null || !drawDebugGizmos) +// return; +// +// if (drawBounds || drawObjects) +// { +// octTree.DrawDebugGizmos(drawBounds, drawObjects, visualizationLayerMask); +// } +// +// if (drawLODLevels && mainCamera != null) +// { +// octTree.DrawLODLevels(mainCamera, mainCamera.transform.position); +// } +// } +// +// public TreeStatistics GetTreeStatistics() +// { +// return octTree.GetTreeStatistics(); +// } +// +// void Awake() +// { +// mainCamera = Camera.main; +// staticObjects = new List(GameObject.FindGameObjectsWithTag(staticObjectTag)); +// +// Dictionary currentHashes = CalculateComponentHashes(); +// +// if (TryLoadSerializedTree(currentHashes)) +// { +// UnityEngine.Debug.Log("Loaded serialized Octree."); +// } +// else +// { +// UnityEngine.Debug.Log("Building new Octree due to changes or missing data."); +// StartBuildingTree(currentHashes); +// } +// } +// +// private Dictionary CalculateComponentHashes() +// { +// Dictionary hashes = new Dictionary +// { +// {"CodeVersion", CODE_VERSION}, +// {"WorldBounds", HashString(worldBounds.ToString())}, +// {"LODDistances", HashString(string.Join(",", lodDistances))}, +// {"ObjectsHash", CalculateObjectsHash()} +// }; +// return hashes; +// } +// +// private string CalculateObjectsHash() +// { +// StringBuilder sb = new StringBuilder(); +// foreach (var obj in staticObjects) +// { +// if (obj != null) +// { +// sb.Append(obj.name); +// sb.Append(obj.transform.position.ToString()); +// Renderer renderer = obj.GetComponent(); +// if (renderer != null) +// { +// sb.Append(renderer.bounds.size.ToString()); +// } +// } +// } +// return HashString(sb.ToString()); +// } +// +// private string HashString(string input) +// { +// using (SHA256 sha256 = SHA256.Create()) +// { +// byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); +// return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 16); +// } +// } +// +// private string CalculateVersionHash() +// { +// StringBuilder sb = new StringBuilder(); +// sb.Append(CODE_VERSION); +// sb.Append(worldBounds.ToString()); +// sb.Append(string.Join(",", lodDistances)); +// +// foreach (var obj in staticObjects) +// { +// if (obj != null) +// { +// sb.Append(obj.name); +// sb.Append(obj.transform.position.ToString()); +// Renderer renderer = obj.GetComponent(); +// if (renderer != null) +// { +// sb.Append(renderer.bounds.size.ToString()); +// } +// } +// } +// +// using (SHA256 sha256 = SHA256.Create()) +// { +// byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())); +// return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 16); +// } +// } +// +// private bool TryLoadSerializedTree(Dictionary currentHashes) +// { +// string fullPath = Path.Combine(Application.persistentDataPath, serializationPath); +// if (File.Exists(fullPath)) +// { +// try +// { +// byte[] data = File.ReadAllBytes(fullPath); +// OctTree loadedTree = OctTree.Deserialize(data); +// +// List changedComponents = CompareHashes(loadedTree.componentHashes, currentHashes); +// +// if (changedComponents.Count == 0) +// { +// octTree = loadedTree; +// octTree.SetObjects(staticObjects); +// return true; +// } +// else +// { +// if (debugMode) +// { +// UnityEngine.Debug.Log($"Rebuild triggered due to changes in: {string.Join(", ", changedComponents)}"); +// } +// +// if (changedComponents.Count == 1 && changedComponents[0] == "ObjectsHash") +// { +// PerformGranularRebuild(loadedTree, currentHashes); +// return true; +// } +// } +// } +// catch (Exception e) +// { +// UnityEngine.Debug.LogError($"Failed to load serialized Octree: {e.Message}"); +// } +// } +// return false; +// } +// +// private List CompareHashes(Dictionary oldHashes, Dictionary newHashes) +// { +// return oldHashes.Where(kvp => !newHashes.ContainsKey(kvp.Key) || newHashes[kvp.Key] != kvp.Value) +// .Select(kvp => kvp.Key) +// .ToList(); +// } +// +// private void PerformGranularRebuild(OctTree oldTree, Dictionary newHashes) +// { +// stopwatch.Restart(); +// +// OctTree newTree = BuildNewTree(newHashes); +// List changedNodes = oldTree.GetChangedNodes(newTree); +// +// if (debugMode) +// { +// UnityEngine.Debug.Log($"Performing granular rebuild. Changed nodes: {changedNodes.Count}"); +// } +// +// oldTree.UpdateNodes(changedNodes); +// octTree = oldTree; // Use the updated old tree +// SaveSerializedTree(); +// +// stopwatch.Stop(); +// lastGranularUpdateTime = stopwatch.ElapsedMilliseconds; +// lastGranularUpdateNodeCount = changedNodes.Count; +// totalGranularUpdates++; +// totalGranularUpdateTime += lastGranularUpdateTime; +// +// if (debugMode) +// { +// UnityEngine.Debug.Log($"Granular update completed in {lastGranularUpdateTime}ms. Updated {lastGranularUpdateNodeCount} nodes."); +// } +// +// OnTreeUpdated(false); +// } +// +// private OctTree BuildNewTree(Dictionary hashes) +// { +// // ... [Implement the tree building logic here, similar to BuildTreeOverTime but without coroutine] ... +// // This is a placeholder implementation +// return new OctTree(worldBounds, lodDistances, HashString(string.Join(",", hashes.Values))); +// } +// +// private void StartBuildingTree(Dictionary hashes) +// { +// if (isBuilding) +// { +// UnityEngine.Debug.LogWarning("Tree building is already in progress."); +// return; +// } +// +// if (buildTreeCoroutine != null) +// { +// StopCoroutine(buildTreeCoroutine); +// } +// buildTreeCoroutine = StartCoroutine(BuildTreeOverTime(hashes)); +// } +// +// private IEnumerator BuildTreeOverTime(Dictionary hashes) +// { +// stopwatch.Restart(); +// +// isBuilding = true; +// int totalObjects = staticObjects.Count; +// int processedObjects = 0; +// +// NativeArray positions = new NativeArray(totalObjects, Allocator.TempJob); +// NativeArray sizes = new NativeArray(totalObjects, Allocator.TempJob); +// NativeArray nodeIndices = new NativeArray(totalObjects, Allocator.TempJob); +// +// try +// { +// while (processedObjects < totalObjects) +// { +// int objectsThisFrame = Mathf.Min(objectsPerFrame, totalObjects - processedObjects); +// +// for (int i = 0; i < objectsThisFrame; i++) +// { +// GameObject obj = staticObjects[processedObjects + i]; +// if (obj != null) +// { +// positions[processedObjects + i] = obj.transform.position; +// sizes[processedObjects + i] = obj.GetComponent().bounds.size; +// } +// else +// { +// UnityEngine.Debug.LogWarning($"Null object found at index {processedObjects + i}"); +// positions[processedObjects + i] = float3.zero; +// sizes[processedObjects + i] = float3.zero; +// } +// } +// +// processedObjects += objectsThisFrame; +// yield return null; +// } +// +// OctreeBuilderJob job = new OctreeBuilderJob +// { +// Positions = positions, +// Sizes = sizes, +// NodeIndices = nodeIndices, +// TreeCenter = worldBounds.center, +// TreeSize = worldBounds.size, +// MaxDepth = 8 // You can adjust this based on your needs +// }; +// +// JobHandle jobHandle = job.Schedule(totalObjects, maxJobBatchSize); +// jobHandle.Complete(); +// +// octTree = new OctTree(worldBounds, lodDistances, string.Join(",", hashes.Values)); +// octTree.InitializeTree(positions, sizes, nodeIndices); +// octTree.SetObjects(staticObjects); +// octTree.componentHashes = hashes; +// +// SaveSerializedTree(); +// +// stopwatch.Stop(); +// lastFullRebuildTime = stopwatch.ElapsedMilliseconds; +// totalFullRebuilds++; +// totalFullRebuildTime += lastFullRebuildTime; +// +// OnTreeUpdated(true); +// +// if (debugMode) +// { +// UnityEngine.Debug.Log($"Full Octree rebuild completed in {lastFullRebuildTime}ms."); +// } +// } +// catch (Exception e) +// { +// UnityEngine.Debug.LogError($"Error during Octree building: {e.Message}"); +// } +// finally +// { +// positions.Dispose(); +// sizes.Dispose(); +// nodeIndices.Dispose(); +// isBuilding = false; +// } +// } +// +// private void SaveSerializedTree() +// { +// string fullPath = Path.Combine(Application.persistentDataPath, serializationPath); +// try +// { +// byte[] data = octTree.Serialize(); +// File.WriteAllBytes(fullPath, data); +// UnityEngine.Debug.Log("Octree serialized and saved successfully."); +// } +// catch (Exception e) +// { +// UnityEngine.Debug.LogError($"Failed to save serialized Octree: {e.Message}"); +// } +// } +// +// private void StartBuildingTree(string versionHash) +// { +// if (isBuilding) +// { +// UnityEngine.Debug.LogWarning("Tree building is already in progress."); +// return; +// } +// +// if (buildTreeCoroutine != null) +// { +// StopCoroutine(buildTreeCoroutine); +// } +// buildTreeCoroutine = StartCoroutine(BuildTreeOverTime(versionHash)); +// } +// +// private IEnumerator BuildTreeOverTime(string versionHash) +// { +// isBuilding = true; +// int totalObjects = staticObjects.Count; +// int processedObjects = 0; +// +// NativeArray positions = new NativeArray(totalObjects, Allocator.TempJob); +// NativeArray sizes = new NativeArray(totalObjects, Allocator.TempJob); +// NativeArray nodeIndices = new NativeArray(totalObjects, Allocator.TempJob); +// +// try +// { +// while (processedObjects < totalObjects) +// { +// int objectsThisFrame = Mathf.Min(objectsPerFrame, totalObjects - processedObjects); +// +// for (int i = 0; i < objectsThisFrame; i++) +// { +// GameObject obj = staticObjects[processedObjects + i]; +// if (obj != null) +// { +// positions[processedObjects + i] = obj.transform.position; +// sizes[processedObjects + i] = obj.GetComponent().bounds.size; +// } +// else +// { +// UnityEngine.Debug.LogWarning($"Null object found at index {processedObjects + i}"); +// positions[processedObjects + i] = float3.zero; +// sizes[processedObjects + i] = float3.zero; +// } +// } +// +// processedObjects += objectsThisFrame; +// yield return null; +// } +// +// OctreeBuilderJob job = new OctreeBuilderJob +// { +// Positions = positions, +// Sizes = sizes, +// NodeIndices = nodeIndices, +// TreeCenter = worldBounds.center, +// TreeSize = worldBounds.size, +// MaxDepth = 8 // You can adjust this based on your needs +// }; +// +// JobHandle jobHandle = job.Schedule(totalObjects, maxJobBatchSize); +// jobHandle.Complete(); +// +// octTree = new OctTree(worldBounds, lodDistances, versionHash); +// octTree.InitializeTree(positions, sizes, nodeIndices); +// octTree.SetObjects(staticObjects); +// +// SaveSerializedTree(); +// +// UnityEngine.Debug.Log("Octree building completed and serialized."); +// } +// catch (Exception e) +// { +// UnityEngine.Debug.LogError($"Error during Octree building: {e.Message}"); +// } +// finally +// { +// positions.Dispose(); +// sizes.Dispose(); +// nodeIndices.Dispose(); +// isBuilding = false; +// } +// } +// +// private void OnDestroy() +// { +// if (buildTreeCoroutine != null) +// { +// StopCoroutine(buildTreeCoroutine); +// } +// } +// +// private void InitializeOctTree() +// { +// octTree = new OctTree(0, worldBounds, lodDistances); +// +// // Batch insert all static objects +// GameObject[] staticObjects = GameObject.FindGameObjectsWithTag(staticObjectTag); +// octTree.BatchInsert(staticObjects); +// +// UnityEngine.Debug.Log($"Advanced Static LOD OctTree initialized with {staticObjects.Length} objects."); +// } +// +// void Update() +// { +// List visibleObjects = octTree.GetVisibleObjects(mainCamera, mainCamera.transform.position); +// +// foreach (GameObject obj in visibleObjects) +// { +// // Additional processing if required +// } +// } +// +// public bool AddObject(GameObject obj) +// { +// if (octTree.Insert(obj)) +// { +// UnityEngine.Debug.Log($"Object {obj.name} added to the octree."); +// return true; +// } +// UnityEngine.Debug.LogWarning($"Failed to add object {obj.name} to the octree."); +// return false; +// } +// +// public bool RemoveObject(GameObject obj) +// { +// if (octTree.Remove(obj)) +// { +// UnityEngine.Debug.Log($"Object {obj.name} removed from the octree."); +// octTree.Rebalance(); +// return true; +// } +// UnityEngine.Debug.LogWarning($"Failed to remove object {obj.name} from the octree. Object not found in the tree."); +// return false; +// } +// +// public void RebalanceTree() +// { +// octTree.Rebalance(); +// UnityEngine.Debug.Log("OctTree rebalanced."); +// } +// +// public void BatchAddObjects(IEnumerable objects) +// { +// octTree.BatchInsert(objects); +// UnityEngine.Debug.Log($"Batch insertion completed. Tree rebalanced."); +// } +// +// public void LogPerformanceMetrics() +// { +// UnityEngine.Debug.Log("OctTree Performance Metrics:"); +// UnityEngine.Debug.Log($"Last Full Rebuild Time: {lastFullRebuildTime}ms"); +// UnityEngine.Debug.Log($"Last Granular Update Time: {lastGranularUpdateTime}ms"); +// UnityEngine.Debug.Log($"Last Granular Update Node Count: {lastGranularUpdateNodeCount}"); +// +// if (lastGranularUpdateNodeCount > 0) +// { +// float avgTimePerNode = (float)lastGranularUpdateTime / lastGranularUpdateNodeCount; +// UnityEngine.Debug.Log($"Average Time per Updated Node: {avgTimePerNode:F2}ms"); +// } +// +// UnityEngine.Debug.Log($"Total Full Rebuilds: {totalFullRebuilds}"); +// UnityEngine.Debug.Log($"Total Granular Updates: {totalGranularUpdates}"); +// +// if (totalFullRebuilds > 0) +// { +// float avgFullRebuildTime = (float)totalFullRebuildTime / totalFullRebuilds; +// UnityEngine.Debug.Log($"Average Full Rebuild Time: {avgFullRebuildTime:F2}ms"); +// } +// +// if (totalGranularUpdates > 0) +// { +// float avgGranularUpdateTime = (float)totalGranularUpdateTime / totalGranularUpdates; +// UnityEngine.Debug.Log($"Average Granular Update Time: {avgGranularUpdateTime:F2}ms"); +// } +// +// if (totalFullRebuilds > 0 && totalGranularUpdates > 0) +// { +// float fullRebuildToUpdateRatio = (float)totalFullRebuildTime / totalGranularUpdateTime; +// UnityEngine.Debug.Log($"Full Rebuild to Granular Update Time Ratio: {fullRebuildToUpdateRatio:F2}"); +// } +// } +// +// // New method to reset performance metrics +// public void ResetPerformanceMetrics() +// { +// lastFullRebuildTime = 0; +// lastGranularUpdateTime = 0; +// lastGranularUpdateNodeCount = 0; +// totalFullRebuilds = 0; +// totalGranularUpdates = 0; +// totalFullRebuildTime = 0; +// totalGranularUpdateTime = 0; +// UnityEngine.Debug.Log("Performance metrics have been reset."); +// } +// +// private void OnTreeUpdated(bool isFullRebuild) +// { +// if (debugMode) +// { +// LogPerformanceMetrics(); +// } +// } +// } \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs.meta new file mode 100644 index 0000000..fa4a315 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f3e3fb610f514ee6b216243caebedb9d +timeCreated: 1728405927 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs new file mode 100644 index 0000000..781bcf5 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs @@ -0,0 +1,67 @@ +// using UnityEngine; +// using System.Collections.Generic; +// using System.Linq; +// +// public class TreeStatistics +// { +// public int TotalNodes { get; set; } +// public int LeafNodes { get; set; } +// public int MaxDepth { get; set; } +// public int TotalObjects { get; set; } +// public Dictionary ObjectsPerLevel { get; set; } = new Dictionary(); +// } +// +// public class OctTreeVisualizer : MonoBehaviour +// { +// public OctTreeManager octTreeManager; +// public bool showObjectCount = true; +// public bool showLODLevels = true; +// public bool showDetailedStats = true; +// +// private TreeStatistics currentStats; +// private float updateInterval = 1f; +// private float lastUpdateTime; +// +// private void Update() +// { +// if (Time.time - lastUpdateTime > updateInterval) +// { +// currentStats = octTreeManager.GetTreeStatistics(); +// lastUpdateTime = Time.time; +// } +// } +// +// private void OnGUI() +// { +// if (octTreeManager == null || octTreeManager.octTree == null) +// return; +// +// GUILayout.BeginArea(new Rect(10, 10, 300, 400)); +// +// if (showObjectCount) +// { +// GUILayout.Label($"Total Objects: {currentStats.TotalObjects}"); +// } +// +// if (showLODLevels) +// { +// GUILayout.Label($"LOD Levels: {octTreeManager.lodDistances.Length}"); +// } +// +// if (showDetailedStats) +// { +// GUILayout.Label("Detailed Statistics:"); +// GUILayout.Label($"Total Nodes: {currentStats.TotalNodes}"); +// GUILayout.Label($"Leaf Nodes: {currentStats.LeafNodes}"); +// GUILayout.Label($"Max Depth: {currentStats.MaxDepth}"); +// +// GUILayout.Label("Objects per Level:"); +// foreach (var kvp in currentStats.ObjectsPerLevel.OrderBy(k => k.Key)) +// { +// GUILayout.Label($" Level {kvp.Key}: {kvp.Value} objects"); +// } +// } +// +// GUILayout.EndArea(); +// } +// } \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs.meta new file mode 100644 index 0000000..b5b20ba --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/OctTreeVisualiser.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0debc23fd83e4b87a88b769fef5e59e4 +timeCreated: 1728406000 \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs new file mode 100644 index 0000000..c7cf911 --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs @@ -0,0 +1,310 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Linq; + +public class PBRMeshMerger : MonoBehaviour +{ + public List meshesToMerge; + public Material opaqueMaterial; + public Material transparentMaterial; + + private Dictionary atlases = new Dictionary(); + private Dictionary texturePositions = new Dictionary(); + private Dictionary encodedValuePositions = new Dictionary(); + + void MergeMeshes() + { + Dictionary> uniqueTextures = new Dictionary> + { + {"albedo", new HashSet()}, + {"metallic", new HashSet()}, + {"normal", new HashSet()}, + {"occlusion", new HashSet()}, + {"emission", new HashSet()} + }; + + Dictionary> encodedValues = new Dictionary> + { + {"albedo", new HashSet()}, + {"metallic", new HashSet()}, + {"emission", new HashSet()} + }; + + // Collect unique textures and encoded values for each map type + foreach (MeshFilter meshFilter in meshesToMerge) + { + Renderer renderer = meshFilter.GetComponent(); + if (renderer != null) + { + Material mat = renderer.sharedMaterial; + CollectTextureOrValue(mat, "_MainTex", "_Color", uniqueTextures["albedo"], encodedValues["albedo"]); + CollectTextureOrValue(mat, "_MetallicGlossMap", "_Metallic", uniqueTextures["metallic"], encodedValues["metallic"]); + CollectTexture(mat, "_BumpMap", uniqueTextures["normal"]); + CollectTexture(mat, "_OcclusionMap", uniqueTextures["occlusion"]); + CollectTextureOrValue(mat, "_EmissionMap", "_EmissionColor", uniqueTextures["emission"], encodedValues["emission"]); + } + } + + // Create atlases for each texture type, including encoded values + foreach (var kvp in uniqueTextures) + { + string mapType = kvp.Key; + HashSet textures = kvp.Value; + HashSet values = encodedValues.ContainsKey(mapType) ? encodedValues[mapType] : new HashSet(); + + if (textures.Count > 0 || values.Count > 0) + { + int atlasSize = CalculateAtlasSize(textures, values); + CalculateTextureAndValuePositions(textures, values, atlasSize); + atlases[mapType] = CreateAtlas(textures, values, atlasSize, mapType == "normal"); + } + } + + // Update UV coordinates and split meshes by transparency + List opaqueCombine = new List(); + List transparentCombine = new List(); + + foreach (MeshFilter meshFilter in meshesToMerge) + { + Mesh mesh = meshFilter.sharedMesh; + Vector2[] meshUVs = mesh.uv; + Renderer renderer = meshFilter.GetComponent(); + Material mat = renderer.sharedMaterial; + + bool isTransparent = IsTransparent(mat); + + // Update UVs based on texture or encoded value position + UpdateUVs(mesh, mat); + + // Add to appropriate combine list + CombineInstance ci = new CombineInstance + { + mesh = mesh, + transform = meshFilter.transform.localToWorldMatrix + }; + + if (isTransparent) + transparentCombine.Add(ci); + else + opaqueCombine.Add(ci); + } + + // Create merged meshes + CreateMergedObject("MergedOpaqueMesh", opaqueCombine, opaqueMaterial); + CreateMergedObject("MergedTransparentMesh", transparentCombine, transparentMaterial); + + // Set atlas textures to materials + SetMaterialTextures(opaqueMaterial); + SetMaterialTextures(transparentMaterial); + + // Optionally, disable or destroy original objects + foreach (MeshFilter mf in meshesToMerge) + { + mf.gameObject.SetActive(false); + } + } + + void CollectTextureOrValue(Material mat, string textureProp, string colorProp, HashSet textureSet, HashSet valueSet) + { + if (mat.HasProperty(textureProp) && mat.GetTexture(textureProp) != null) + { + textureSet.Add(mat.GetTexture(textureProp) as Texture2D); + } + else if (mat.HasProperty(colorProp)) + { + valueSet.Add(mat.GetColor(colorProp)); + } + } + + void CollectTexture(Material mat, string propertyName, HashSet textureSet) + { + if (mat.HasProperty(propertyName) && mat.GetTexture(propertyName) != null) + { + textureSet.Add(mat.GetTexture(propertyName) as Texture2D); + } + } + + bool IsTransparent(Material mat) + { + return mat.renderQueue > 2500 || mat.HasProperty("_Mode") && mat.GetFloat("_Mode") > 0; + } + + int CalculateAtlasSize(HashSet textures, HashSet values) + { + int totalArea = textures.Sum(t => t.width * t.height) + values.Count; + int size = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(totalArea))); + return Mathf.Min(size, 8192); // Limit to 8192x8192 (adjust as needed) + } + + void CalculateTextureAndValuePositions(HashSet textures, HashSet values, int atlasSize) + { + int x = 0, y = 0; + int rowHeight = 0; + + // Position textures + foreach (Texture2D texture in textures) + { + if (x + texture.width > atlasSize) + { + x = 0; + y += rowHeight; + rowHeight = 0; + } + + texturePositions[texture] = new Rect( + (float)x / atlasSize, + (float)y / atlasSize, + (float)texture.width / atlasSize, + (float)texture.height / atlasSize + ); + + x += texture.width; + rowHeight = Mathf.Max(rowHeight, texture.height); + } + + // Position encoded values (1x1 pixel each) + foreach (Color value in values) + { + if (x + 1 > atlasSize) + { + x = 0; + y += rowHeight; + rowHeight = 1; + } + + encodedValuePositions[value.GetHashCode()] = new Rect( + (float)x / atlasSize, + (float)y / atlasSize, + 1f / atlasSize, + 1f / atlasSize + ); + + x += 1; + rowHeight = Mathf.Max(rowHeight, 1); + } + } + + Texture2D CreateAtlas(HashSet textures, HashSet values, int atlasSize, bool isNormalMap) + { + Texture2D atlas = new Texture2D(atlasSize, atlasSize, TextureFormat.RGBA32, true); + + // Copy textures to atlas + foreach (var kvp in texturePositions) + { + Texture2D texture = kvp.Key; + Rect position = kvp.Value; + + int x = Mathf.FloorToInt(position.x * atlasSize); + int y = Mathf.FloorToInt(position.y * atlasSize); + + Color[] pixels = texture.GetPixels(); + if (isNormalMap) + pixels = ProcessNormalMap(pixels); + + atlas.SetPixels(x, y, texture.width, texture.height, pixels); + } + + // Encode single values into atlas + foreach (var kvp in encodedValuePositions) + { + Color value = values.First(c => c.GetHashCode() == kvp.Key); + Rect position = kvp.Value; + + int x = Mathf.FloorToInt(position.x * atlasSize); + int y = Mathf.FloorToInt(position.y * atlasSize); + + atlas.SetPixel(x, y, value); + } + + atlas.Apply(); + return atlas; + } + + Color[] ProcessNormalMap(Color[] pixels) + { + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = new Color(pixels[i].r, pixels[i].g, pixels[i].b, 1); + } + return pixels; + } + + void UpdateUVs(Mesh mesh, Material mat) + { + Vector2[] meshUVs = mesh.uv; + bool uvUpdated = false; + + string[] propertyNames = { "_MainTex", "_MetallicGlossMap", "_BumpMap", "_OcclusionMap", "_EmissionMap" }; + string[] colorProperties = { "_Color", "_Metallic", "", "", "_EmissionColor" }; + + for (int i = 0; i < propertyNames.Length; i++) + { + string textureProp = propertyNames[i]; + string colorProp = colorProperties[i]; + + if (mat.HasProperty(textureProp) && mat.GetTexture(textureProp) is Texture2D texture && texturePositions.TryGetValue(texture, out Rect uvRect)) + { + for (int j = 0; j < meshUVs.Length; j++) + { + meshUVs[j] = new Vector2( + uvRect.x + meshUVs[j].x * uvRect.width, + uvRect.y + meshUVs[j].y * uvRect.height + ); + } + uvUpdated = true; + break; + } + else if (!string.IsNullOrEmpty(colorProp) && mat.HasProperty(colorProp)) + { + Color value = mat.GetColor(colorProp); + if (encodedValuePositions.TryGetValue(value.GetHashCode(), out Rect valueRect)) + { + for (int j = 0; j < meshUVs.Length; j++) + { + meshUVs[j] = new Vector2(valueRect.x + 0.5f / atlases[textureProp].width, valueRect.y + 0.5f / atlases[textureProp].height); + } + uvUpdated = true; + break; + } + } + } + + if (uvUpdated) + { + mesh.uv = meshUVs; + } + } + + void CreateMergedObject(string name, List combines, Material material) + { + if (combines.Count == 0) return; + + Mesh mergedMesh = new Mesh(); + mergedMesh.CombineMeshes(combines.ToArray()); + + GameObject mergedObject = new GameObject(name); + MeshFilter meshFilter = mergedObject.AddComponent(); + meshFilter.sharedMesh = mergedMesh; + + MeshRenderer meshRenderer = mergedObject.AddComponent(); + meshRenderer.sharedMaterial = material; + } + + void SetMaterialTextures(Material material) + { + if (atlases.TryGetValue("albedo", out Texture2D albedoAtlas)) + material.SetTexture("_MainTex", albedoAtlas); + + if (atlases.TryGetValue("metallic", out Texture2D metallicAtlas)) + material.SetTexture("_MetallicGlossMap", metallicAtlas); + + if (atlases.TryGetValue("normal", out Texture2D normalAtlas)) + material.SetTexture("_BumpMap", normalAtlas); + + if (atlases.TryGetValue("occlusion", out Texture2D occlusionAtlas)) + material.SetTexture("_OcclusionMap", occlusionAtlas); + + if (atlases.TryGetValue("emission", out Texture2D emissionAtlas)) + material.SetTexture("_EmissionMap", emissionAtlas); + } +} \ No newline at end of file diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs.meta b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs.meta new file mode 100644 index 0000000..c09da4c --- /dev/null +++ b/asset-bundle-converter/Assets/AssetBundleConverter/HLOD/PBRMeshMerger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ff7f4ffabb5d4b0bab53f2c6f53f892d +timeCreated: 1728301753 \ No newline at end of file