1261 lines
49 KiB
C#
1261 lines
49 KiB
C#
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<HLODControllerBase>();
|
|
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<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
|
|
public float NormalScale => m_normalScale;
|
|
|
|
public Layer(TerrainLayer layer, float chunkSize)
|
|
{
|
|
m_diffuseTextures = new DisposableList<WorkingTexture>();
|
|
m_maskTextures = new DisposableList<WorkingTexture>();
|
|
m_normalTextures = new DisposableList<WorkingTexture>();
|
|
|
|
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<WorkingTexture> 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<WorkingTexture> m_diffuseTextures;
|
|
private DisposableList<WorkingTexture> m_maskTextures;
|
|
private DisposableList<WorkingTexture> 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<WorkingTexture> m_alphamaps;
|
|
private DisposableList<Layer> 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<int, float, float, float, float, bool, Color> getColor, System.Func<Color, Color> 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<Vector2Int> GetEdgeList(List<int> tris)
|
|
{
|
|
HashSet<Vector2Int> candidates = new HashSet<Vector2Int>();
|
|
|
|
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<BorderVertex> GenerateBorderVertices(Heightmap heightmap, int borderCount)
|
|
{
|
|
//generate border vertices
|
|
List<BorderVertex> borderVertices = new List<BorderVertex>((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<Vector3> vertices = source.vertices.ToList();
|
|
List<Vector3> normals = source.normals.ToList();
|
|
List<Vector2> uvs = source.uv.ToList();
|
|
List<int[]> subMeshTris = new List<int[]>();
|
|
|
|
int maxTris = 0;
|
|
|
|
for (int si = 0; si < source.subMeshCount; ++si)
|
|
{
|
|
List<int> tris = source.GetTriangles(si).ToList();
|
|
List<Vector2Int> edges = GetEdgeList(tris);
|
|
HashSet<int> vertexIndces = new HashSet<int>();
|
|
List<BorderVertex> edgeVertices = new List<BorderVertex>();
|
|
|
|
for (int ei = 0; ei < edges.Count; ++ei)
|
|
{
|
|
vertexIndces.Add(edges[ei].x);
|
|
vertexIndces.Add(edges[ei].y);
|
|
}
|
|
|
|
List<BorderVertex> 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<Vector2Int> EdgeList = new List<Vector2Int>();
|
|
}
|
|
|
|
private WorkingMesh MakeFillHoleMesh(WorkingMesh source)
|
|
{
|
|
int totalTris = 0;
|
|
List<int[]> newTris = new List<int[]>();
|
|
|
|
for (int si = 0; si < source.subMeshCount; ++si)
|
|
{
|
|
List<int> tris = source.GetTriangles(si).ToList();
|
|
List<Vector2Int> edgeList = GetEdgeList(tris);
|
|
|
|
|
|
List<EdgeGroup> groups = new List<EdgeGroup>();
|
|
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<HLODBuildInfo> CreateBuildInfo(TerrainData data, SpaceNode root)
|
|
{
|
|
DisposableList<HLODBuildInfo> results = new DisposableList<HLODBuildInfo>();
|
|
Queue<SpaceNode> trevelQueue = new Queue<SpaceNode>();
|
|
Queue<int> parentQueue = new Queue<int>();
|
|
Queue<string> nameQueue = new Queue<string>();
|
|
Queue<int> depthQueue = new Queue<int>();
|
|
|
|
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<Material>(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<Material>(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<WorkingTexture>())
|
|
using (m_layers = new DisposableList<Layer>())
|
|
{
|
|
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<SpaceNode> 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<HLODBuildInfo> 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<Material> materials = new List<Material>();
|
|
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<MeshFilter>().sharedMesh = wo.Mesh.ToMesh();
|
|
|
|
var mr = targetGO.AddComponent<MeshRenderer>();
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
} |