Files
2024-11-20 15:21:28 +01:00

269 lines
10 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using UnityEngine;
namespace Gaia
{
public struct MaskedMeshParamters
{
public UnityHeightMap Heightmap;
public UnityHeightMap Maskmap;
/// <summary>
/// 0-1 below the threshold then are considered outside.
/// </summary>
public float MaskThreshold;
/// <summary>
/// terrain.terrainData.size
/// </summary>
public Vector3 MeshScale;
/// <summary>
/// //0, 1, 2, 3, 4 (full, half, quarter, eighth, sixteenth)
/// </summary>
public int MeshResolution;
/// <summary>
/// 0 = triangles, 1 = quads
/// </summary>
public int MeshType;
/// <summary>
/// CounterClockwise works since x in negated.
/// </summary>
public WindingOrder Winding;
public enum WindingOrder
{
Clockwise = 1,
CounterClockwise = 2
}
public MaskedMeshParamters(UnityHeightMap heightMap, UnityHeightMap maskMap, float maskThreshold, int meshResolution, Vector3 meshScale, int meshType, WindingOrder winding)
{
Heightmap = heightMap;
Maskmap = maskMap;
MaskThreshold = maskThreshold;
MeshResolution = meshResolution;
MeshScale = meshScale;
MeshType = meshType;
Winding = winding;
}
}
public static class MaskedTerrainMesh
{
/// <summary>
/// Creates 2 Meshes based on a mask heightmap
/// </summary>
/// <param name="parms"></param>
/// <param name="outsideMesh">The parts that are below the threshold.</param>
/// <param name="insideMesh">The parts that are equal or above the threshold.</param>
public static void CreateMaskedTerrainMeshes(MaskedMeshParamters parms, out MeshBuilder outsideMesh, out MeshBuilder insideMesh)
{
int heightmapResolution = parms.Heightmap.Width();
outsideMesh = new MeshBuilder(1.0f / (float )heightmapResolution);
insideMesh = new MeshBuilder(1.0f / (float)heightmapResolution);
int tRes = 1 << parms.MeshResolution;
Vector3 meshScale = new Vector3(parms.MeshScale.x / (heightmapResolution - 1), parms.MeshScale.y, parms.MeshScale.z / (heightmapResolution - 1));
bool clockwise = parms.Winding == MaskedMeshParamters.WindingOrder.Clockwise;
for (int y = 0; y < heightmapResolution - tRes; y += tRes)
{
float yNorm = (float )y / (float )heightmapResolution;
float yNorm2 = (float)(y+tRes) / (float)heightmapResolution;
for (float x = 0; x < heightmapResolution - tRes; x += tRes)
{
float xNorm = (float)x / (float)heightmapResolution;
float xNorm2 = (float)(x+tRes) / (float)heightmapResolution;
// Add a quad or 2 triangles to the main or masked mesh with lower left corner given
float h1 = parms.Heightmap[yNorm2, xNorm];
float h2 = parms.Heightmap[yNorm2, xNorm2];
float h3 = parms.Heightmap[yNorm, xNorm];
float h4 = parms.Heightmap[yNorm, xNorm2];
Vector3 v1 = Vector3.Scale(new Vector3( -x, h1, y+tRes), meshScale);
Vector3 v2 = Vector3.Scale(new Vector3(-(x+tRes), h2, y+tRes), meshScale);
Vector3 v3 = Vector3.Scale(new Vector3( -x, h3, y), meshScale);
Vector3 v4 = Vector3.Scale(new Vector3(-(x+tRes), h4, y), meshScale);
if (parms.Maskmap[xNorm, yNorm] < parms.MaskThreshold)
{
if (parms.MeshType == 0)
{
outsideMesh.AddTriangle(clockwise, v3, v1, v2);
outsideMesh.AddTriangle(clockwise, v3, v2, v4);
}
else
{
outsideMesh.AddQuad(clockwise, v1, v2, v3, v4);
}
}
else
{
if (parms.MeshType == 0)
{
insideMesh.AddTriangle(clockwise, v3, v1, v2);
insideMesh.AddTriangle(clockwise, v3, v2, v4);
}
else
{
insideMesh.AddQuad(clockwise, v1, v2, v3, v4);
}
}
}
}
}
}
public class MeshBuilder
{
float m_uvScale = 1.0f;
List<Vector3> m_vertices;
List<Vector2> m_uvs;
List<int> m_indices;
Dictionary<Vector2, int> m_verticeToIndex;
bool usingTriangles = true;
public List<Vector3> Vertices { get { return m_vertices; } }
public List<Vector2> UVs { get { return m_uvs; } }
public List<int> Indices { get { return m_indices; } }
public MeshBuilder(float uvScale)
{
m_uvScale = uvScale;
m_vertices = new List<Vector3>();
m_uvs = new List<Vector2>();
m_indices = new List<int>();
m_verticeToIndex = new Dictionary<Vector2, int>();
}
/// <summary>
/// Add a triangle to the mesh given 3 vertices.
/// </summary>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <param name="v3"></param>
public void AddTriangle(bool clockwise, Vector3 v1, Vector3 v2, Vector3 v3)
{
if (clockwise)
{
m_indices.Add(VertexIndex(v1));
m_indices.Add(VertexIndex(v2));
m_indices.Add(VertexIndex(v3));
}
else
{
m_indices.Add(VertexIndex(v3));
m_indices.Add(VertexIndex(v2));
m_indices.Add(VertexIndex(v1));
}
usingTriangles = true;
}
public void AddQuad(bool clockwise, Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
{
if (clockwise)
{
m_indices.Add(VertexIndex(v1));
m_indices.Add(VertexIndex(v2));
m_indices.Add(VertexIndex(v4));
m_indices.Add(VertexIndex(v3));
}
else
{
m_indices.Add(VertexIndex(v1));
m_indices.Add(VertexIndex(v3));
m_indices.Add(VertexIndex(v4));
m_indices.Add(VertexIndex(v2));
}
usingTriangles = false;
}
/// <summary>
/// Retrieves the index of a vertex in the mesh.
/// Adds the vertex if it does not already exist.
/// </summary>
/// <param name="vert">The Vector3 vertice to get the index for.</param>
/// <returns>The index in to the m_vertices list of the vertice.</returns>
int VertexIndex(Vector3 vert)
{
Vector2 v = new Vector2(vert.x, vert.z);
if (!m_verticeToIndex.TryGetValue(v, out int vIndex))
{
vIndex = m_vertices.Count;
m_verticeToIndex.Add(v, vIndex);
m_vertices.Add(vert);
v.x = -v.x;
v *= m_uvScale;
m_uvs.Add(v);
}
return vIndex;
}
public void Save(string fileName)
{
// Export to .obj
StreamWriter sw = new StreamWriter(fileName);
try
{
sw.WriteLine("# Unity terrain OBJ File");
// Write vertices
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
for (int i = 0; i < m_vertices.Count; i++)
{
StringBuilder sb = new StringBuilder("v ", 64);
// StringBuilder stuff is done this way because it's faster than using the "{0} {1} {2}"etc. format
// Which is important when you're exporting huge terrains.
sb.Append(m_vertices[i].x.ToString()).Append(" ").
Append(m_vertices[i].y.ToString()).Append(" ").
Append(m_vertices[i].z.ToString());
sw.WriteLine(sb);
}
// Write UVs
for (int i = 0; i < m_uvs.Count; i++)
{
StringBuilder sb = new StringBuilder("vt ", 48);
sb.Append(m_uvs[i].x.ToString()).Append(" ").
Append(m_uvs[i].y.ToString());
sw.WriteLine(sb);
}
if (usingTriangles)
{
// Write triangles
for (int i = 0; i < m_indices.Count; i += 3)
{
StringBuilder sb = new StringBuilder("f ", 43);
sb.Append(m_indices[i] + 1).Append("/").Append(m_indices[i] + 1).Append(" ").
Append(m_indices[i + 1] + 1).Append("/").Append(m_indices[i + 1] + 1).Append(" ").
Append(m_indices[i + 2] + 1).Append("/").Append(m_indices[i + 2] + 1);
sw.WriteLine(sb);
}
}
else
{
// Write quads
for (int i = 0; i < m_indices.Count; i += 4)
{
StringBuilder sb = new StringBuilder("f ", 64);
sb.Append(m_indices[i] + 1).Append("/").Append(m_indices[i] + 1).Append(" ").
Append(m_indices[i + 1] + 1).Append("/").Append(m_indices[i + 1] + 1).Append(" ").
Append(m_indices[i + 2] + 1).Append("/").Append(m_indices[i + 2] + 1).Append(" ").
Append(m_indices[i + 3] + 1).Append("/").Append(m_indices[i + 3] + 1);
sw.WriteLine(sb);
}
}
}
catch (System.Exception err)
{
Debug.Log("Error saving file: " + err.Message);
}
sw.Close();
}
}
}