141 lines
4.5 KiB
C#
141 lines
4.5 KiB
C#
// Perfect Culling (C) 2021 Patrick König
|
|
//
|
|
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace Koenigz.PerfectCulling
|
|
{
|
|
[RequireComponent(typeof(UnityEngine.Terrain))]
|
|
public class TerrainToMeshUtility : MonoBehaviour
|
|
{
|
|
private readonly string EditorOnlyTag = "EditorOnly";
|
|
|
|
[Header("Creates double-sided mesh to make it not see-through from the other side.")]
|
|
public bool DoubleSided = true;
|
|
|
|
[Range(1, 2048)]
|
|
public int MeshResolutionX = 512;
|
|
|
|
[Range(1, 2048)]
|
|
public int MeshResolutionZ = 512;
|
|
|
|
public MeshRenderer meshRendererReference;
|
|
|
|
public void CreateOrUpdateMesh()
|
|
{
|
|
UnityEngine.Terrain terrain = GetComponent<UnityEngine.Terrain>();
|
|
|
|
Mesh mesh = CreateMesh(terrain, MeshResolutionX, MeshResolutionZ, DoubleSided);
|
|
|
|
string terrainName = $"Mesh for {terrain.name} [EditorOnly]";
|
|
|
|
if (meshRendererReference == null)
|
|
{
|
|
GameObject newGo = new GameObject(terrainName);
|
|
newGo.tag = EditorOnlyTag;
|
|
|
|
newGo.AddComponent<MeshFilter>().sharedMesh = mesh;
|
|
|
|
meshRendererReference = newGo.AddComponent<MeshRenderer>();
|
|
meshRendererReference.enabled = false;
|
|
}
|
|
else
|
|
{
|
|
MeshFilter mf = meshRendererReference.GetComponent<MeshFilter>();
|
|
|
|
if (mf.sharedMesh != null)
|
|
{
|
|
GameObject.DestroyImmediate(mf.sharedMesh);
|
|
}
|
|
|
|
mf.sharedMesh = mesh;
|
|
}
|
|
|
|
meshRendererReference.transform.SetPositionAndRotation(terrain.transform.position, Quaternion.identity);
|
|
}
|
|
|
|
public static Mesh CreateMesh(UnityEngine.Terrain terrain, int meshResolutionX, int meshResolutionZ, bool doubleSided)
|
|
{
|
|
string terrainName = $"Mesh for {terrain.name} [EditorOnly]";
|
|
|
|
TerrainData terrainData = terrain.terrainData;
|
|
|
|
if (terrainData == null)
|
|
{
|
|
Debug.LogError("Terrain data is null.");
|
|
|
|
return null;
|
|
}
|
|
|
|
float xSpacing = terrainData.size.x / meshResolutionX;
|
|
float ySpacing = terrainData.size.z / meshResolutionZ;
|
|
|
|
Mesh mesh = new Mesh();
|
|
mesh.indexFormat = IndexFormat.UInt32;
|
|
mesh.name = terrainName;
|
|
|
|
Vector3[] vertices = new Vector3[(meshResolutionX + 1) * (meshResolutionZ + 1)];
|
|
for (int i = 0, y = 0; y <= meshResolutionZ; y++)
|
|
{
|
|
for (int x = 0; x <= meshResolutionX; x++, i++)
|
|
{
|
|
float h = terrain.SampleHeight(new Vector3(x * xSpacing, 0f, y * ySpacing) + terrain.transform.position);
|
|
|
|
vertices[i] = new Vector3(x * xSpacing, h, y * ySpacing);
|
|
}
|
|
}
|
|
|
|
mesh.vertices = vertices;
|
|
|
|
int[] triangles = new int[meshResolutionX * meshResolutionZ * 6];
|
|
for (int ti = 0, vi = 0, y = 0; y < meshResolutionZ; y++, vi++)
|
|
{
|
|
for (int x = 0; x < meshResolutionX; x++, ti += 6, vi++)
|
|
{
|
|
triangles[ti] = vi;
|
|
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
|
|
triangles[ti + 4] = triangles[ti + 1] = vi + meshResolutionX + 1;
|
|
triangles[ti + 5] = vi + meshResolutionX + 2;
|
|
}
|
|
}
|
|
|
|
if (doubleSided)
|
|
{
|
|
triangles = triangles.Concat(triangles.Reverse()).ToArray();
|
|
}
|
|
|
|
mesh.triangles = triangles;
|
|
|
|
return mesh;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
[UnityEditor.CustomEditor(typeof(TerrainToMeshUtility))]
|
|
public class TerrainConvertHelperV1Editor : UnityEditor.Editor
|
|
{
|
|
public override void OnInspectorGUI()
|
|
{
|
|
TerrainToMeshUtility helper = target as TerrainToMeshUtility;
|
|
|
|
DrawDefaultInspector();
|
|
|
|
if (GUILayout.Button(helper.meshRendererReference != null ? "Update mesh" : "Create mesh"))
|
|
{
|
|
helper.CreateOrUpdateMesh();
|
|
}
|
|
|
|
if (helper.meshRendererReference != null)
|
|
{
|
|
if (GUILayout.Button("Select MeshRenderer"))
|
|
{
|
|
UnityEditor.Selection.activeGameObject = helper.meshRendererReference.gameObject;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
} |