// ╔════════════════════════════════════════════════════════════════╗ // ║ 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; } } }