Files
2026-02-27 17:44:21 +08:00

233 lines
9.2 KiB
C#

// ╔════════════════════════════════════════════════════════════════╗
// ║ 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
{
/// <summary>
/// Utility class for mesh processing operations used by WaterObject.
/// Handles mesh simplification, convexification, vertex welding, and volume calculations.
/// </summary>
public static class MeshUtility
{
/// <summary>
/// Generates a simulation mesh from the original mesh with optional processing steps.
/// Processes the mesh according to the specified flags and simplification ratio.
/// </summary>
/// <param name="originalMesh">Source mesh to process.</param>
/// <param name="simMesh">Output simulation mesh.</param>
/// <param name="simplifyMesh">Should the mesh be simplified to reduce triangle count.</param>
/// <param name="convexifyMesh">Should the mesh be made convex.</param>
/// <param name="weldColocatedVertices">Should vertices at the same position be merged.</param>
/// <param name="simplificationRatio">Target triangle count as ratio of original (0-1).</param>
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();
}
/// <summary>
/// Generate mesh from vertices and triangles.
/// </summary>
/// <param name="vertices"> Array of vertices. </param>
/// <param name="triangles"> Array of triangles (indices). </param>
/// <returns> </returns>
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;
}
/// <summary>
/// Merges vertices that are closer than the specified distance threshold.
/// Improves performance by reducing vertex count and removing duplicates.
/// </summary>
/// <param name="aMesh">Mesh to process.</param>
/// <param name="aMaxDelta">Maximum distance between vertices to be considered duplicates.</param>
public static void WeldVertices(ref Mesh aMesh, float aMaxDelta = 0.001f)
{
Vector3[] verts = aMesh.vertices;
List<int> 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;
}
/// <summary>
/// Reduces poly count of the mesh while trying to preserve features.
/// </summary>
/// <param name="om"> Mesh to simplify. </param>
/// <param name="ratio"> Percent of the triangles the new mesh will have </param>
/// <returns> </returns>
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;
}
/// <summary>
/// Calculates the signed volume of a triangle with respect to the origin.
/// Used for mesh volume calculations.
/// </summary>
/// <param name="p1">First vertex.</param>
/// <param name="p2">Second vertex.</param>
/// <param name="p3">Third vertex.</param>
/// <returns>Signed volume of the triangle.</returns>
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);
}
/// <summary>
/// Calculates the volume of a mesh in world space.
/// Takes into account the transform's scale, position and rotation.
/// </summary>
/// <param name="mesh">Mesh to calculate volume for.</param>
/// <param name="transform">Transform containing scale information.</param>
/// <returns>Volume of the mesh in cubic meters.</returns>
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);
}
/// <summary>
/// Creates a convex hull from the input mesh.
/// Useful for partially hollow meshes or open hulls.
/// </summary>
/// <param name="mesh">Mesh to convexify.</param>
/// <returns>Convex mesh wrapping the input mesh.</returns>
public static Mesh GenerateConvexMesh(Mesh mesh)
{
IEnumerable<Vector3> stars = mesh.vertices;
Mesh m = new();
List<int> triangles = new();
List<Vertex> vertices = stars.Select(x => new Vertex(x)).ToList();
ConvexHull<Vertex, DefaultConvexFace<Vertex>> result = ConvexHull.Create(vertices);
m.vertices = result.Points.Select(x => x.ToVec()).ToArray();
List<Vertex> xxx = result.Points.ToList();
foreach (DefaultConvexFace<Vertex> 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;
}
}
}