using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using Unity.Collections; using Unity.HLODSystem.Simplifier; using Unity.HLODSystem.SpaceManager; using Unity.HLODSystem.Streaming; using Unity.HLODSystem.Utils; using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; namespace Unity.HLODSystem { public class TerrainHLODCreator { public static IEnumerator Create(TerrainHLOD hlod) { TerrainHLODCreator creator = new TerrainHLODCreator(hlod); yield return creator.CreateImpl(); } public static IEnumerator Destroy(TerrainHLOD hlod) { var controller = hlod.GetComponent(); if (controller == null) yield break; try { EditorUtility.DisplayProgressBar("Destroy HLOD", "Destroying HLOD files", 0.0f); var convertedPrefabObjects = hlod.ConvertedPrefabObjects; for (int i = 0; i < convertedPrefabObjects.Count; ++i) { PrefabUtility.UnpackPrefabInstance(convertedPrefabObjects[i], PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); } var generatedObjects = hlod.GeneratedObjects; for (int i = 0; i < generatedObjects.Count; ++i) { if (generatedObjects[i] == null) continue; var path = AssetDatabase.GetAssetPath(generatedObjects[i]); if (string.IsNullOrEmpty(path) == false) { AssetDatabase.DeleteAsset(path); } else { //It means scene object. //Destroy it. Object.DestroyImmediate(generatedObjects[i]); } EditorUtility.DisplayProgressBar("Destroy HLOD", "Destroying HLOD files", (float)i / (float)generatedObjects.Count); } generatedObjects.Clear(); Object.DestroyImmediate(controller); } finally { EditorUtility.ClearProgressBar(); } EditorUtility.SetDirty(hlod.gameObject); EditorUtility.SetDirty(hlod); } class Layer : IDisposable { private NativeArray m_detector = new NativeArray(1, Allocator.Persistent); public float NormalScale => m_normalScale; public Layer(TerrainLayer layer, float chunkSize) { m_diffuseTextures = new DisposableList(); m_maskTextures = new DisposableList(); m_normalTextures = new DisposableList(); m_offset = layer.tileOffset; m_size = layer.tileSize; m_normalScale = layer.normalScale; m_chunkSize = chunkSize; MakeTexture(layer, layer.diffuseTexture, layer.diffuseRemapMin, layer.diffuseRemapMax, m_diffuseTextures); MakeTexture(layer, layer.maskMapTexture, layer.maskMapRemapMin, layer.maskMapRemapMax, m_maskTextures); MakeTexture(layer, layer.normalMapTexture, Vector4.zero, Vector4.one, m_normalTextures); } void MakeTexture(TerrainLayer layer, Texture2D texture, Vector4 min, Vector4 max, DisposableList results) { bool linear = !GraphicsFormatUtility.IsSRGBFormat(texture.graphicsFormat); var offset = layer.tileOffset; var size = layer.tileSize; if (!linear) { min.x = Mathf.Pow(min.x, 0.45f); min.y = Mathf.Pow(min.y, 0.45f); min.z = Mathf.Pow(min.z, 0.45f); max.x = Mathf.Pow(max.x, 0.45f); max.y = Mathf.Pow(max.y, 0.45f); max.z = Mathf.Pow(max.z, 0.45f); } //make to texture readable. var assetImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)); var textureImporter = assetImporter as TextureImporter; TextureImporterType type = TextureImporterType.Default; if (textureImporter) { type = textureImporter.textureType; textureImporter.isReadable = true; textureImporter.textureType = TextureImporterType.Default; textureImporter.SaveAndReimport(); } try { for (int i = 0; i < texture.mipmapCount; ++i) { int width = texture.width >> i; int height = texture.height >> i; WorkingTexture workingTexture = new WorkingTexture(Allocator.Persistent, texture.format, width, height, linear); Color[] colors = texture.GetPixels(i); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { workingTexture.SetPixel(x, y, colors[y * width + x]); } } RemapTexture(workingTexture, min, max); results.Add(workingTexture); } } finally { if (textureImporter) { textureImporter.isReadable = false; textureImporter.textureType = type; textureImporter.SaveAndReimport(); } } } public void Dispose() { m_diffuseTextures?.Dispose(); m_maskTextures?.Dispose(); m_normalTextures?.Dispose(); m_detector.Dispose(); } public Color GetColor(float u, float v, int mipLevel = 0) { u = u - Mathf.Floor(u); v = v - Mathf.Floor(v); mipLevel = Mathf.Min(mipLevel, m_diffuseTextures.Count - 1); return m_diffuseTextures[mipLevel].GetPixel(u, v); } public Vector3 GetUVByWorld(float wx, float wz, float sx, float sz) { float u = (wx + m_offset.x) / m_size.x; float v = (wz + m_offset.y) / m_size.y; float mipx = Mathf.Max(0, sx / (m_chunkSize * 2.0f) - 1); float mipy = Mathf.Max(0, sz / (m_chunkSize * 2.0f) - 1); float mip = Mathf.Max(mipx, mipy); return new Vector3(u, v, mip); } public Color GetColorByWorld(float wx, float wz, float sx, float sz) { Vector3 uv = GetUVByWorld(wx, wz, sx, sz); return GetColor(uv.x, uv.y, Mathf.RoundToInt(uv.z)); } public Color GetMask(float u, float v, int mipLevel = 0) { u = u - Mathf.Floor(u); v = v - Mathf.Floor(v); mipLevel = Mathf.Min(mipLevel, m_diffuseTextures.Count - 1); return m_maskTextures[mipLevel].GetPixel(u, v); } public Color GetMaskByWorld(float wx, float wz, float sx, float sz) { Vector3 uv = GetUVByWorld(wx, wz, sx, sz); return GetMask(uv.x, uv.y, Mathf.RoundToInt(uv.z)); } public Color GetNormal(float u, float v, int mipLevel = 0) { u = u - Mathf.Floor(u); v = v - Mathf.Floor(v); mipLevel = Mathf.Min(mipLevel, m_normalTextures.Count - 1); return m_normalTextures[mipLevel].GetPixel(u, v); } public Color GetNormalByWorld(float wx, float wz, float sx, float sz) { Vector3 uv = GetUVByWorld(wx, wz, sx, sz); return GetNormal(uv.x, uv.y, Mathf.RoundToInt(uv.z)); } private WorkingTexture GenerateMipmap(WorkingTexture source) { int sx = Mathf.Max(source.Width >> 1, 1); int sy = Mathf.Max(source.Height >> 1, 1); WorkingTexture mipmap = new WorkingTexture(Allocator.Persistent, source.Format, sx, sy, source.Linear); mipmap.Name = source.Name; for (int y = 0; y < sy; ++y) { for (int x = 0; x < sx; ++x) { Color color = new Color(); int x1 = Mathf.Min(x * 2 + 0, source.Width -1); int x2 = Mathf.Min(x * 2 + 1, source.Width - 1); int y1 = Mathf.Min(y * 2 + 0, source.Height - 1); int y2 = Mathf.Min(y * 2 + 1, source.Height - 1); color += source.GetPixel(x1, y1); color += source.GetPixel(x1, y2); color += source.GetPixel(x2, y1); color += source.GetPixel(x2, y2); color /= 4; mipmap.SetPixel(x, y, color); } } return mipmap; } private void RemapTexture(WorkingTexture source, Color min, Color max) { for (int y = 0; y < source.Height; ++y) { for (int x = 0; x < source.Width; ++x) { var color = source.GetPixel(x, y); color = color * max + min; source.SetPixel(x, y, color); } } } private DisposableList m_diffuseTextures; private DisposableList m_maskTextures; private DisposableList m_normalTextures; private Vector2 m_offset; private Vector2 m_size; private float m_chunkSize; private float m_normalScale; } private TerrainHLOD m_hlod; private JobQueue m_queue; private Heightmap m_heightmap; private Vector3 m_size; private DisposableList m_alphamaps; private DisposableList m_layers; private Material m_terrainMaterial; private int m_terrainMaterialInstanceId; private string m_terrainMaterialName; private Material m_terrainMaterialLow; private int m_terrainMaterialLowInstanceId; private string m_terrainMaterialLowName; private TerrainHLODCreator(TerrainHLOD hlod) { m_hlod = hlod; } private Heightmap CreateSubHightmap(Bounds bounds) { int beginX = Mathf.RoundToInt(bounds.min.x / m_size.x * (m_heightmap.Width-1)); int beginZ = Mathf.RoundToInt(bounds.min.z / m_size.z * (m_heightmap.Height-1)); int endX = Mathf.RoundToInt(bounds.max.x / m_size.x * (m_heightmap.Width-1)); int endZ = Mathf.RoundToInt(bounds.max.z / m_size.z * (m_heightmap.Height-1)); int width = endX - beginX + 1; int height = endZ - beginZ + 1; return m_heightmap.GetHeightmap(beginX, beginZ, width, height); } private WorkingObject CreateBakedTerrain(string name, Bounds bounds, Heightmap heightmap, int distance, bool isLeaf) { WorkingObject wo = new WorkingObject(Allocator.Persistent); wo.Name = name; wo.LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; m_queue.EnqueueJob(() => { WorkingMesh mesh = CreateBakedGeometry(name, heightmap, bounds, distance); wo.SetMesh(mesh); }); m_queue.EnqueueJob(() => { WorkingMaterial material = CreateBakedMaterial(name, bounds, isLeaf); wo.Materials.Add(material); }); return wo; } private WorkingMesh CreateBakedGeometry(string name, Heightmap heightmap, Bounds bounds, int distance) { int borderWidth = CalcBorderWidth(heightmap, distance); int borderWidth2x = borderWidth * 2; WorkingMesh mesh = new WorkingMesh(Allocator.Persistent, heightmap.Width * heightmap.Height, (heightmap.Width - borderWidth2x - 1) * (heightmap.Height - borderWidth2x - 1) * 6, 1, 0); mesh.name = name + "_Mesh"; Vector3[] vertices = new Vector3[(heightmap.Width - borderWidth2x) * (heightmap.Height - borderWidth2x)]; Vector3[] normals = new Vector3[(heightmap.Width - borderWidth2x) * (heightmap.Height - borderWidth2x)]; Vector2[] uvs = new Vector2[(heightmap.Width - borderWidth2x) * (heightmap.Height - borderWidth2x)]; int[] triangles = new int[(heightmap.Width - borderWidth2x - 1) * (heightmap.Height - borderWidth2x - 1) * 6]; int vi = 0; //except boder line for (int z = borderWidth; z < heightmap.Height -borderWidth; ++z) { for (int x = borderWidth; x < heightmap.Width -borderWidth; ++x) { int index = vi++; vertices[index].x = bounds.size.x * (x) / (heightmap.Width - 1) + bounds.min.x; vertices[index].y = heightmap.Size.y * heightmap[z, x]; vertices[index].z = bounds.size.z * (z) / (heightmap.Height - 1) + bounds.min.z; uvs[index].x = (float)x / (heightmap.Width - 1); uvs[index].y = (float)z / (heightmap.Height - 1); normals[index] = heightmap.GetInterpolatedNormal(uvs[index].x, uvs[index].y); } } int ii = 0; for (int z = 0; z < heightmap.Height - borderWidth2x - 1; ++z) { for (int x = 0; x < heightmap.Width - borderWidth2x - 1; ++x) { int i00 = z * (heightmap.Width -borderWidth2x)+ x; int i10 = z * (heightmap.Width -borderWidth2x)+ x + 1; int i01 = (z + 1) * (heightmap.Width -borderWidth2x)+ x; int i11 = (z + 1) * (heightmap.Width -borderWidth2x)+ x + 1; triangles[ii + 0] = i00; triangles[ii + 1] = i11; triangles[ii + 2] = i10; triangles[ii + 3] = i11; triangles[ii + 4] = i00; triangles[ii + 5] = i01; ii += 6; } } mesh.vertices = vertices; mesh.normals = normals; mesh.uv = uvs; mesh.SetTriangles(triangles, 0); return mesh; } private WorkingMaterial CreateBakedMaterial(string name, Bounds bounds, bool useHighMaterial) { int matInstanceID = useHighMaterial ? m_terrainMaterialInstanceId : m_terrainMaterialLowInstanceId; string matName = useHighMaterial ? m_terrainMaterialName : m_terrainMaterialLowName; WorkingMaterial material = new WorkingMaterial(Allocator.Persistent, matInstanceID, matName); material.Name = name + "_Material"; m_queue.EnqueueJob(() => { WorkingTexture albedo = BakeAlbedo(name, bounds, m_hlod.TextureSize); material.AddTexture(m_hlod.AlbedoPropertyName, albedo); }); if (m_hlod.UseNormal) { m_queue.EnqueueJob(() => { WorkingTexture normal = BakeNormal(name, bounds, m_hlod.TextureSize); material.AddTexture(m_hlod.NormalPropertyName, normal); }); } if (m_hlod.UseMask) { m_queue.EnqueueJob(() => { WorkingTexture mask = BakeMask(name, bounds, m_hlod.TextureSize); material.AddTexture(m_hlod.MaskPropertyName, mask); }); } return material; } private Color UnPackNormal(Color c, float scale) { c.r = (c.r * 2 - 1) * scale; c.g = (c.g * 2 - 1) * scale; c.b = c.b * 2 - 1; return c; } private Color PackNormal(Color c) { c.r = c.r * 0.5f + 0.5f; c.g = c.g * 0.5f + 0.5f; c.b = c.b * 0.5f + 0.5f; return c; } private void EnqueueBlendTextureJob(WorkingTexture texture, Bounds bounds, int resolution, System.Func getColor, System.Func packColor = null) { bool linear = texture.Linear; m_queue.EnqueueJob(() => { float ustart = (bounds.min.x) / m_size.x; float vstart = (bounds.min.z) / m_size.z; float usize = (bounds.max.x - bounds.min.x) / m_size.x; float vsize = (bounds.max.z - bounds.min.z) / m_size.z; for (int y = 0; y < resolution; ++y) { for (int x = 0; x < resolution; ++x) { float u = (float)x / (float)resolution * usize + ustart; float v = (float)y / (float)resolution * vsize + vstart; Color color = new Color(0.0f, 0.0f, 0.0f, 0.0f); for (int li = 0; li < m_layers.Count; ++li) { float weight = 0.0f; switch (li % 4) { case 0: weight = m_alphamaps[li / 4].GetPixel(u, v).r; break; case 1: weight = m_alphamaps[li / 4].GetPixel(u, v).g; break; case 2: weight = m_alphamaps[li / 4].GetPixel(u, v).b; break; case 3: weight = m_alphamaps[li / 4].GetPixel(u, v).a; break; } //optimize to skip not effect pixels. if (weight < 0.01f) continue; float wx = (float)x / (float)resolution * bounds.size.x + bounds.min.x; float wy = (float)y / (float)resolution * bounds.size.z + bounds.min.z; Color c = getColor(li, wx, wy, bounds.size.x, bounds.size.z, linear); // blend in linear space. color.r += c.r * weight; color.g += c.g * weight; color.b += c.b * weight; color.a += c.a * weight; } if (packColor != null) { color = packColor(color); } if (!linear) { color = color.gamma; } texture.SetPixel(x, y, color); } } }); } private WorkingTexture BakeAlbedo(string name, Bounds bounds, int resolution) { WorkingTexture albedoTexture = new WorkingTexture(Allocator.Persistent, TextureFormat.RGB24, resolution, resolution, false); albedoTexture.Name = name + "_Albedo"; albedoTexture.WrapMode = TextureWrapMode.Clamp; EnqueueBlendTextureJob(albedoTexture, bounds, resolution, (layer, wx, wz, sx, sz, linear) => { Color c = m_layers[layer].GetColorByWorld(wx, wz, sx, sz); if (!linear) c = c.linear; return c; }); return albedoTexture; } private WorkingTexture BakeMask(string name, Bounds bounds, int resolution) { WorkingTexture maskTexture = new WorkingTexture(Allocator.Persistent, TextureFormat.ARGB32, resolution, resolution, false); maskTexture.Name = name + "_Mask"; maskTexture.WrapMode = TextureWrapMode.Clamp; EnqueueBlendTextureJob(maskTexture, bounds, resolution, (layer, wx, wz, sx, sz, linear) => { Color c = m_layers[layer].GetMaskByWorld(wx, wz, sx, sz); if (!linear) c = c.linear; return c; }); return maskTexture; } private WorkingTexture BakeNormal(string name, Bounds bounds, int resolution) { WorkingTexture normalTexture = new WorkingTexture(Allocator.Persistent, TextureFormat.RGB24, resolution, resolution, true); normalTexture.Name = name + "_Normal"; normalTexture.WrapMode = TextureWrapMode.Clamp; EnqueueBlendTextureJob(normalTexture, bounds, resolution, (layer, wx, wz, sx, sz, linear) => { Color c = m_layers[layer].GetNormalByWorld(wx, wz, sx, sz); c = UnPackNormal(c, m_layers[layer].NormalScale); return c; },(c) => { Vector3 n = new Vector3(c.r, c.g, c.b); n.Normalize(); c = new Color(n.x, n.y, n.z); return PackNormal(c); }); return normalTexture; } private List GetEdgeList(List tris) { HashSet candidates = new HashSet(); int trisCount = tris.Count / 3; for (int i = 0; i < trisCount; ++i) { Vector2Int[] edges = new[] { new Vector2Int(tris[i * 3 + 0], tris[i * 3 + 1]), new Vector2Int(tris[i * 3 + 1], tris[i * 3 + 2]), new Vector2Int(tris[i * 3 + 2], tris[i * 3 + 0]) }; for (int ei = 0; ei < edges.Length; ++ei) { Vector2Int otherSideEdge = new Vector2Int(edges[ei].y, edges[ei].x); if (candidates.Contains(otherSideEdge) == true) { candidates.Remove(otherSideEdge); } else { candidates.Add(edges[ei]); } } } return candidates.ToList(); } struct BorderVertex { public Vector3 Pos; public int ClosestIndex; } private List GenerateBorderVertices(Heightmap heightmap, int borderCount) { //generate border vertices List borderVertices = new List((heightmap.Width + heightmap.Height) * 2); int xBorderOffset = Mathf.Max((heightmap.Width - 1) / borderCount, 1 ); //< avoid 0 int yBorderOffset = Mathf.Max((heightmap.Height - 1) / borderCount, 1); //< avoid 0 //upside for (int i = 0; i < heightmap.Width-1; i += xBorderOffset) { float h = heightmap[0, i]; BorderVertex v; v.Pos.x = (heightmap.Size.x * i) / (heightmap.Width-1); v.Pos.y = (heightmap.Size.y * h); v.Pos.z = 0.0f; v.Pos += heightmap.Offset; v.ClosestIndex = -1; borderVertices.Add(v); } //rightside for (int i = 0; i < heightmap.Height-1; i += yBorderOffset) { float h = heightmap[i, heightmap.Width - 1]; BorderVertex v; v.Pos.x = heightmap.Size.x; v.Pos.y = (heightmap.Size.y * h); v.Pos.z = (heightmap.Size.z * i) / (heightmap.Height-1); v.Pos += heightmap.Offset; v.ClosestIndex = -1; borderVertices.Add(v); } //downside for (int i = heightmap.Width-1; i > 0; i -= xBorderOffset) { float h = heightmap[heightmap.Height - 1, i]; BorderVertex v; v.Pos.x = (heightmap.Size.x * i) / (heightmap.Width-1); v.Pos.y = (heightmap.Size.y * h); v.Pos.z = heightmap.Size.z; v.Pos += heightmap.Offset; v.ClosestIndex = -1; borderVertices.Add(v); } //leftside for (int i = heightmap.Height - 1; i > 0; i -= yBorderOffset) { float h = heightmap[i, 0]; BorderVertex v; v.Pos.x = 0.0f; v.Pos.y = (heightmap.Size.y * h); v.Pos.z = (heightmap.Size.z * i) / (heightmap.Height-1); v.Pos += heightmap.Offset; v.ClosestIndex = -1; borderVertices.Add(v); } return borderVertices; } private WorkingMesh MakeBorder(WorkingMesh source, Heightmap heightmap, int borderCount) { List vertices = source.vertices.ToList(); List normals = source.normals.ToList(); List uvs = source.uv.ToList(); List subMeshTris = new List(); int maxTris = 0; for (int si = 0; si < source.subMeshCount; ++si) { List tris = source.GetTriangles(si).ToList(); List edges = GetEdgeList(tris); HashSet vertexIndces = new HashSet(); List edgeVertices = new List(); for (int ei = 0; ei < edges.Count; ++ei) { vertexIndces.Add(edges[ei].x); vertexIndces.Add(edges[ei].y); } List borderVertices = GenerateBorderVertices(heightmap, borderCount); //calculate closest vertex from border vertices. for (int i = 0; i < borderVertices.Count; ++i) { float closestDistance = Single.MaxValue; BorderVertex v = borderVertices[i]; foreach (var index in vertexIndces) { Vector3 pos = vertices[index]; float dist = Vector3.SqrMagnitude(pos - borderVertices[i].Pos); if (dist < closestDistance) { closestDistance = dist; v.ClosestIndex = index; } } borderVertices[i] = v; } //generate tris int startAddIndex = vertices.Count; for (int bi = 0; bi < borderVertices.Count; ++bi) { int next = (bi == borderVertices.Count - 1) ? 0 : bi + 1; tris.Add(bi + startAddIndex); tris.Add(borderVertices[bi].ClosestIndex); tris.Add(next + startAddIndex); Vector2 uv; uv.x = (borderVertices[bi].Pos.x - heightmap.Offset.x) / heightmap.Size.x; uv.y = (borderVertices[bi].Pos.z - heightmap.Offset.z) / heightmap.Size.z; vertices.Add(borderVertices[bi].Pos); normals.Add(heightmap.GetInterpolatedNormal(uv.x, uv.y)); uvs.Add(uv); if (borderVertices[bi].ClosestIndex == borderVertices[next].ClosestIndex) continue; tris.Add(borderVertices[bi].ClosestIndex); tris.Add(borderVertices[next].ClosestIndex); tris.Add(next + startAddIndex); } maxTris += tris.Count; subMeshTris.Add(tris.ToArray()); } WorkingMesh mesh = new WorkingMesh(Allocator.Persistent, vertices.Count, maxTris, subMeshTris.Count, 0); mesh.name = source.name; mesh.vertices = vertices.ToArray(); mesh.normals = normals.ToArray(); mesh.uv = uvs.ToArray(); for (int i = 0; i < subMeshTris.Count; ++i) { mesh.SetTriangles(subMeshTris[i], i); } return mesh; } private void ReampUV(WorkingMesh mesh, Heightmap heightmap) { var vertices = mesh.vertices; var uvs = mesh.uv; for (int i = 0; i < mesh.vertexCount; ++i) { Vector2 uv; uv.x = (vertices[i].x - heightmap.Offset.x) / heightmap.Size.x; uv.y = (vertices[i].z - heightmap.Offset.z) / heightmap.Size.z; uvs[i] = uv; //vertices[i]. } mesh.uv = uvs; } private int CalcBorderWidth(Heightmap heightmap, int distance) { if (m_hlod.SimplifierType == typeof(Simplifier.None)) { return 1; } dynamic options = m_hlod.SimplifierOptions; int maxPolygonCount = options.SimplifyMaxPolygonCount; int minPolygonCount = options.SimplifyMinPolygonCount; float polygonRatio = options.SimplifyPolygonRatio; int triangleCount = (heightmap.Width - 1) * (heightmap.Height - 1) * 2; float maxQuality = Mathf.Min((float) maxPolygonCount / (float) triangleCount, polygonRatio); float minQuality = Mathf.Max((float) minPolygonCount / (float) triangleCount, 0.0f); var ratio = maxQuality * Mathf.Pow(polygonRatio, distance); ratio = Mathf.Max(ratio, minQuality); int expectPolygonCount = (int)(triangleCount * ratio); float areaSize = (heightmap.Size.x * heightmap.Size.z); float sourceSizePerTri = areaSize/ triangleCount; float targetSizePerTri = areaSize / expectPolygonCount; float sizeRatio = targetSizePerTri / sourceSizePerTri; float sizeRatioSqrt = Mathf.Sqrt(sizeRatio); //sizeRatioSqrt is little bit big i think. //So I adjust the value by divide 2. return Mathf.Max((int) sizeRatioSqrt / 2, 1); } public class EdgeGroup { public int Begin = -1; public int End = -1; public List EdgeList = new List(); } private WorkingMesh MakeFillHoleMesh(WorkingMesh source) { int totalTris = 0; List newTris = new List(); for (int si = 0; si < source.subMeshCount; ++si) { List tris = source.GetTriangles(si).ToList(); List edgeList = GetEdgeList(tris); List groups = new List(); for (int i = 0; i < edgeList.Count; ++i) { EdgeGroup group = new EdgeGroup(); group.Begin = edgeList[i].x; group.End = edgeList[i].y; group.EdgeList.Add(edgeList[i]); groups.Add(group); } bool isFinish = false; while (isFinish == false) { isFinish = true; for (int gi1 = 0; gi1 < groups.Count; ++gi1) { for (int gi2 = gi1 + 1; gi2 < groups.Count; ++gi2) { EdgeGroup g1 = groups[gi1]; EdgeGroup g2 = groups[gi2]; if (g1.End == g2.Begin) { g1.End = g2.End; g1.EdgeList.AddRange(g2.EdgeList); groups[gi2] = groups[groups.Count - 1]; groups.RemoveAt(groups.Count - 1); gi2 -= 1; isFinish = false; } else if (g1.Begin == g2.End) { g2.End = g1.End; g2.EdgeList.AddRange(g1.EdgeList); groups[gi1] = groups[gi2]; groups[gi2] = groups[groups.Count - 1]; groups.RemoveAt(groups.Count - 1); gi2 -= 1; isFinish = false; } } } } for (int gi = 0; gi < groups.Count; ++gi) { EdgeGroup group = groups[gi]; for (int ei1 = 1; ei1 < group.EdgeList.Count-1; ++ei1) { for (int ei2 = ei1 + 1; ei2 < group.EdgeList.Count; ++ei2) { if (group.EdgeList[ei1].x == group.EdgeList[ei2].y) { EdgeGroup ng = new EdgeGroup(); ng.Begin = group.EdgeList[ei1].x; ng.End = group.EdgeList[ei2].y; for (int i = ei1; i <= ei2; ++i) { ng.EdgeList.Add(group.EdgeList[i]); } for (int i = ei2; i >= ei1; --i) { group.EdgeList.RemoveAt(i); } groups.Add(ng); ei1 = 0; // goto first break; } } } } if (groups.Count == 0) continue; groups.Sort((g1, g2) => { return g2.EdgeList.Count - g1.EdgeList.Count; }); //first group( longest group ) is outline. for (int i = 1; i < groups.Count; ++i) { EdgeGroup group = groups[i]; for (int ei = 1; ei < group.EdgeList.Count - 1; ++ei) { tris.Add(group.Begin); tris.Add(group.EdgeList[ei].y); tris.Add(group.EdgeList[ei].x); } } totalTris += tris.Count; newTris.Add(tris.ToArray()); } WorkingMesh mesh = new WorkingMesh(Allocator.Persistent, source.vertexCount, totalTris, source.subMeshCount, 0); mesh.name = source.name; mesh.vertices = source.vertices; mesh.normals = source.normals; mesh.uv = source.uv; for (int i = 0; i < newTris.Count; ++i) { mesh.SetTriangles(newTris[i], i); } return mesh; } private DisposableList CreateBuildInfo(TerrainData data, SpaceNode root) { DisposableList results = new DisposableList(); Queue trevelQueue = new Queue(); Queue parentQueue = new Queue(); Queue nameQueue = new Queue(); Queue depthQueue = new Queue(); int maxDepth = 0; trevelQueue.Enqueue(root); parentQueue.Enqueue(-1); nameQueue.Enqueue("HLOD"); depthQueue.Enqueue(0); while (trevelQueue.Count > 0) { int currentNodeIndex = results.Count; string name = nameQueue.Dequeue(); SpaceNode node = trevelQueue.Dequeue(); int depth = depthQueue.Dequeue(); HLODBuildInfo info = new HLODBuildInfo { Name = name, ParentIndex = parentQueue.Dequeue(), Target = node, }; for (int i = 0; i < node.GetChildCount(); ++i) { trevelQueue.Enqueue(node.GetChild(i)); parentQueue.Enqueue(currentNodeIndex); nameQueue.Enqueue(name + "_" + (i + 1)); depthQueue.Enqueue(depth + 1); } info.Heightmap = CreateSubHightmap(node.Bounds); info.WorkingObjects.Add(CreateBakedTerrain(name, node.Bounds, info.Heightmap, depth, node.GetChildCount() == 0)); info.Distances.Add(depth); results.Add(info); if (depth > maxDepth) maxDepth = depth; } //convert depth to distance for (int i = 0; i < results.Count; ++i) { HLODBuildInfo info = results[i]; for (int di = 0; di < info.Distances.Count; ++di) { info.Distances[di] = maxDepth - info.Distances[di]; } } return results; } public IEnumerator CreateImpl() { try { using (m_queue = new JobQueue(8)) { Stopwatch sw = new Stopwatch(); AssetDatabase.Refresh(); AssetDatabase.SaveAssets(); sw.Reset(); sw.Start(); EditorUtility.DisplayProgressBar("Bake HLOD", "Initialize Bake", 0.0f); TerrainData data = m_hlod.TerrainData; m_size = data.size; m_heightmap = new Heightmap(data.heightmapResolution, data.heightmapResolution, data.size, data.GetHeights(0, 0, data.heightmapResolution, data.heightmapResolution)); string materialPath = AssetDatabase.GUIDToAssetPath(m_hlod.MaterialGUID); m_terrainMaterial = AssetDatabase.LoadAssetAtPath(materialPath); if (m_terrainMaterial == null) { m_terrainMaterial = new Material(Shader.Find("Standard")); } m_terrainMaterialInstanceId = m_terrainMaterial.GetInstanceID(); m_terrainMaterialName = m_terrainMaterial.name; materialPath = AssetDatabase.GUIDToAssetPath(m_hlod.MaterialLowGUID); m_terrainMaterialLow = AssetDatabase.LoadAssetAtPath(materialPath); if (m_terrainMaterialLow == null) { m_terrainMaterialLow = new Material(Shader.Find("Standard")); } m_terrainMaterialLowInstanceId = m_terrainMaterialLow.GetInstanceID(); m_terrainMaterialLowName = m_terrainMaterialLow.name; using (m_alphamaps = new DisposableList()) using (m_layers = new DisposableList()) { for (int i = 0; i < data.alphamapTextures.Length; ++i) { m_alphamaps.Add(new WorkingTexture(Allocator.Persistent, data.alphamapTextures[i])); } for (int i = 0; i < data.terrainLayers.Length; ++i) { m_layers.Add(new Layer(data.terrainLayers[i], m_hlod.ChunkSize)); } QuadTreeSpaceSplitter splitter = new QuadTreeSpaceSplitter(null); List rootNodeList = splitter.CreateSpaceTree(m_hlod.GetBounds(), m_hlod.ChunkSize * 2.0f, m_hlod.transform, null, progress => { }); EditorUtility.DisplayProgressBar("Bake HLOD", "Create mesh", 0.0f); foreach (var rootNode in rootNodeList) { using (DisposableList buildInfos = CreateBuildInfo(data, rootNode)) { yield return m_queue.WaitFinish(); //Write material & textures for (int i = 0; i < buildInfos.Count; ++i) { int curIndex = i; m_queue.EnqueueJob(() => { ISimplifier simplifier = (ISimplifier)Activator.CreateInstance( m_hlod.SimplifierType, new object[] { m_hlod.SimplifierOptions }); simplifier.SimplifyImmidiate(buildInfos[curIndex]); }); } EditorUtility.DisplayProgressBar("Bake HLOD", "Simplify meshes", 0.0f); yield return m_queue.WaitFinish(); Debug.Log("[TerrainHLOD] Simplify: " + sw.Elapsed.ToString("g")); sw.Reset(); sw.Start(); EditorUtility.DisplayProgressBar("Bake HLOD", "Make border", 0.0f); for (int i = 0; i < buildInfos.Count; ++i) { HLODBuildInfo info = buildInfos[i]; m_queue.EnqueueJob(() => { for (int oi = 0; oi < info.WorkingObjects.Count; ++oi) { WorkingObject o = info.WorkingObjects[oi]; int borderVertexCount = m_hlod.BorderVertexCount * Mathf.RoundToInt(Mathf.Pow(2.0f, (float)info.Distances[oi])); using (WorkingMesh m = MakeBorder(o.Mesh, info.Heightmap, borderVertexCount)) { ReampUV(m, info.Heightmap); o.SetMesh(MakeFillHoleMesh(m)); } } }); } yield return m_queue.WaitFinish(); Debug.Log("[TerrainHLOD] Make Border: " + sw.Elapsed.ToString("g")); sw.Reset(); sw.Start(); for (int i = 0; i < buildInfos.Count; ++i) { SpaceNode node = buildInfos[i].Target; HLODBuildInfo info = buildInfos[i]; if (node.HasChild() == false) { SpaceNode parent = node.ParentNode; node.ParentNode = null; GameObject go = new GameObject(buildInfos[i].Name); for (int wi = 0; wi < info.WorkingObjects.Count; ++wi) { WorkingObject wo = info.WorkingObjects[wi]; GameObject targetGO = null; if (wi == 0) { targetGO = go; } else { targetGO = new GameObject(wi.ToString()); targetGO.transform.SetParent(go.transform, false); } List materials = new List(); for (int mi = 0; mi < wo.Materials.Count; ++mi) { WorkingMaterial wm = wo.Materials[mi]; if (wm.NeedWrite() == false) { materials.Add(wm.ToMaterial()); continue; } Material mat = new Material(wm.ToMaterial()); string[] textureNames = wm.GetTextureNames(); for (int ti = 0; ti < textureNames.Length; ++ti) { WorkingTexture wt = wm.GetTexture(textureNames[ti]); Texture2D tex = wt.ToTexture(); tex.wrapMode = wt.WrapMode; mat.name = targetGO.name + "_Mat"; mat.SetTexture(textureNames[ti], tex); } mat.EnableKeyword("_NORMALMAP"); materials.Add(mat); } targetGO.AddComponent().sharedMesh = wo.Mesh.ToMesh(); var mr = targetGO.AddComponent(); mr.sharedMaterials = materials.ToArray(); mr.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; } go.transform.SetParent(m_hlod.transform, false); m_hlod.AddGeneratedResource(go); parent.Objects.Add(go); buildInfos.RemoveAt(i); i -= 1; } } //controller IStreamingBuilder builder = (IStreamingBuilder)Activator.CreateInstance(m_hlod.StreamingType, new object[] { m_hlod, m_hlod.StreamingOptions }); builder.Build(rootNode, buildInfos, m_hlod.gameObject, m_hlod.CullDistance, m_hlod.LODDistance, true, false, progress => { EditorUtility.DisplayProgressBar("Bake HLOD", "Storing results.", 0.75f + progress * 0.25f); }); Debug.Log("[TerrainHLOD] Build: " + sw.Elapsed.ToString("g")); } } } EditorUtility.SetDirty(m_hlod.gameObject); } } finally { EditorUtility.ClearProgressBar(); GC.Collect(); } } } }