// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections.Generic;
using System.Linq;
using NWH.DWP2.MeshDecimation;
using NWH.DWP2.MiConvexHull;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
///
/// Utility class for mesh processing operations used by WaterObject.
/// Handles mesh simplification, convexification, vertex welding, and volume calculations.
///
public static class MeshUtility
{
///
/// Generates a simulation mesh from the original mesh with optional processing steps.
/// Processes the mesh according to the specified flags and simplification ratio.
///
/// Source mesh to process.
/// Output simulation mesh.
/// Should the mesh be simplified to reduce triangle count.
/// Should the mesh be made convex.
/// Should vertices at the same position be merged.
/// Target triangle count as ratio of original (0-1).
public static void GenerateSimMesh(ref Mesh originalMesh, ref Mesh simMesh,
bool simplifyMesh = false, bool convexifyMesh = false, bool weldColocatedVertices = true,
float simplificationRatio = 1f)
{
simMesh.vertices = originalMesh.vertices;
simMesh.triangles = originalMesh.triangles;
if (simplifyMesh)
{
simMesh = GenerateSimplifiedMesh(ref originalMesh, ref simMesh, simplificationRatio);
}
if (convexifyMesh)
{
simMesh = GenerateConvexMesh(simMesh);
}
if (weldColocatedVertices)
{
WeldVertices(ref simMesh);
}
simMesh.name = "DWP_SIM_MESH";
simMesh.RecalculateNormals();
simMesh.RecalculateTangents();
}
///
/// Generate mesh from vertices and triangles.
///
/// Array of vertices.
/// Array of triangles (indices).
///
public static Mesh GenerateMesh(Vector3[] vertices, int[] triangles)
{
Mesh m = new();
m.vertices = vertices;
m.triangles = triangles;
m.RecalculateBounds();
m.RecalculateNormals();
m.name = "DWP_SIM_MESH";
return m;
}
///
/// Merges vertices that are closer than the specified distance threshold.
/// Improves performance by reducing vertex count and removing duplicates.
///
/// Mesh to process.
/// Maximum distance between vertices to be considered duplicates.
public static void WeldVertices(ref Mesh aMesh, float aMaxDelta = 0.001f)
{
Vector3[] verts = aMesh.vertices;
List newVerts = new();
int[] map = new int[verts.Length];
// create mapping and filter duplicates.
for (int i = 0; i < verts.Length; i++)
{
Vector3 p = verts[i];
bool duplicate = false;
for (int i2 = 0; i2 < newVerts.Count; i2++)
{
int a = newVerts[i2];
if ((verts[a] - p).sqrMagnitude <= aMaxDelta)
{
map[i] = i2;
duplicate = true;
break;
}
}
if (!duplicate)
{
map[i] = newVerts.Count;
newVerts.Add(i);
}
}
Vector3[] verts2 = new Vector3[newVerts.Count];
for (int i = 0; i < newVerts.Count; i++)
{
int a = newVerts[i];
verts2[i] = verts[a];
}
int[] tris = aMesh.triangles;
for (int i = 0; i < tris.Length; i++)
{
tris[i] = map[tris[i]];
}
aMesh.triangles = tris;
aMesh.vertices = verts2;
}
///
/// Reduces poly count of the mesh while trying to preserve features.
///
/// Mesh to simplify.
/// Percent of the triangles the new mesh will have
///
private static Mesh GenerateSimplifiedMesh(ref Mesh om, ref Mesh dummyMesh, float ratio)
{
MeshDecimate meshDecimate = new();
meshDecimate.ratio = ratio;
meshDecimate.PreCalculate(om);
meshDecimate.Calculate(om);
Mesh sm = new();
sm.vertices = meshDecimate.finalVertices;
sm.triangles = meshDecimate.finalTriangles;
sm.normals = meshDecimate.finalNormals;
sm.name = "DWP_SIM_MESH";
return sm;
}
///
/// Calculates the signed volume of a triangle with respect to the origin.
/// Used for mesh volume calculations.
///
/// First vertex.
/// Second vertex.
/// Third vertex.
/// Signed volume of the triangle.
public static float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
{
float v321 = p3.x * p2.y * p1.z;
float v231 = p2.x * p3.y * p1.z;
float v312 = p3.x * p1.y * p2.z;
float v132 = p1.x * p3.y * p2.z;
float v213 = p2.x * p1.y * p3.z;
float v123 = p1.x * p2.y * p3.z;
return 1.0f / 6.0f * (-v321 + v231 + v312 - v132 - v213 + v123);
}
///
/// Calculates the volume of a mesh in world space.
/// Takes into account the transform's scale, position and rotation.
///
/// Mesh to calculate volume for.
/// Transform containing scale information.
/// Volume of the mesh in cubic meters.
public static float VolumeOfMesh(Mesh mesh, Transform transform)
{
float volume = 0;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
Matrix4x4 transformMatrix = transform.localToWorldMatrix;
for (int i = 0; i < mesh.triangles.Length; i += 3)
{
Vector3 p1 = transformMatrix.MultiplyPoint(vertices[triangles[i + 0]]);
Vector3 p2 = transformMatrix.MultiplyPoint(vertices[triangles[i + 1]]);
Vector3 p3 = transformMatrix.MultiplyPoint(vertices[triangles[i + 2]]);
volume += SignedVolumeOfTriangle(p1, p2, p3);
}
return Mathf.Abs(volume);
}
///
/// Creates a convex hull from the input mesh.
/// Useful for partially hollow meshes or open hulls.
///
/// Mesh to convexify.
/// Convex mesh wrapping the input mesh.
public static Mesh GenerateConvexMesh(Mesh mesh)
{
IEnumerable stars = mesh.vertices;
Mesh m = new();
List triangles = new();
List vertices = stars.Select(x => new Vertex(x)).ToList();
ConvexHull> result = ConvexHull.Create(vertices);
m.vertices = result.Points.Select(x => x.ToVec()).ToArray();
List xxx = result.Points.ToList();
foreach (DefaultConvexFace face in result.Faces)
{
triangles.Add(xxx.IndexOf(face.Vertices[0]));
triangles.Add(xxx.IndexOf(face.Vertices[1]));
triangles.Add(xxx.IndexOf(face.Vertices[2]));
}
m.triangles = triangles.ToArray();
m.RecalculateNormals();
m.name = "DWP_SIM_MESH";
return m;
}
}
}