using Jobberwocky.GeometryAlgorithms.Source.Core; using Jobberwocky.GeometryAlgorithms.Source.Parameters; using Jobberwocky.MIConvexHull; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Jobberwocky.GeometryAlgorithms.Source.Algorithms.Voronoi3D { public class Voronoi3DWrapper { public Voronoi3DWrapper() { } public Geometry Voronoi3D(Voronoi3DParameters parameters) { return Voronoi3DBase(parameters); } /// /// The base method for the creation of a 3D voronoi diagram /// /// /// /// private Geometry Voronoi3DBase(Voronoi3DParameters parameters) { var geometry = new Geometry(); if (parameters == null) { parameters = new Voronoi3DParameters(); } var points = parameters.Points; if (points != null && points.Length > 3) { // Translates the unity vector points to vertices var pointVertices = VectorToVertex(points, parameters.CoordinateSystem); float minX = Mathf.Infinity, minY = Mathf.Infinity, minZ = Mathf.Infinity; float maxX = Mathf.NegativeInfinity, maxY = Mathf.NegativeInfinity, maxZ = Mathf.NegativeInfinity; foreach (var point in points) { minX = minX > point.x ? point.x : minX; minY = minY > point.y ? point.y : minY; minZ = minZ > point.z ? point.z : minZ; maxX = maxX < point.x ? point.x : maxX; maxY = maxY < point.y ? point.y : maxY; maxZ = maxZ < point.z ? point.z : maxZ; } //// Creates the necessary information for a 3D voronoi diagram var voronoi = VoronoiMesh.Create(pointVertices); var vertices = new Dictionary(); var indices = new List(); int index = 0; //// Create the voronoi edges by connecting the circumcenters of the tetrahydrons foreach (var voronoiEdge in voronoi.Edges) { var circumcenterSource = GetCircumcenter(voronoiEdge.Source.Vertices, parameters.CoordinateSystem); var circumcenterTarget = GetCircumcenter(voronoiEdge.Target.Vertices, parameters.CoordinateSystem); if (circumcenterSource.Position.x < minX || circumcenterSource.Position.y < minY || circumcenterSource.Position.z < minZ || circumcenterSource.Position.x > maxX || circumcenterSource.Position.y > maxY || circumcenterSource.Position.z > maxZ || circumcenterTarget.Position.x < minX || circumcenterTarget.Position.y < minY || circumcenterTarget.Position.z < minZ || circumcenterTarget.Position.x > maxX || circumcenterTarget.Position.y > maxY || circumcenterTarget.Position.z > maxZ) { continue; } var sourceID = GetUniqueID(voronoiEdge.Source.Vertices); if (!vertices.ContainsKey(sourceID)) { circumcenterSource.Index = index++; vertices.Add(sourceID, circumcenterSource); } else { circumcenterSource = vertices[sourceID]; } var targetID = GetUniqueID(voronoiEdge.Target.Vertices); if (!vertices.ContainsKey(targetID)) { circumcenterTarget.Index = index++; vertices.Add(targetID, circumcenterTarget); } else { circumcenterTarget = vertices[targetID]; } indices.Add(circumcenterSource.Index); indices.Add(circumcenterTarget.Index); } /*if (false) { // Create the voronoi edges going outwards foreach (var cell in voronoi.Vertices) { for (int i = 0; i < cell.Adjacency.Length; i++) { // An outward edge is only created if a tetrahydron is at the boundary of the 3D mesh if (cell.Adjacency[i] == null) { var from = GetCircumcenter(cell.Vertices); var t = cell.Vertices.Where((_, j) => j != i).ToArray(); var to = new Vertex(); for (int j = 0; j < t.Length; j++) { to.Position += new Vector3((float) t[j].Position[0], (float) t[j].Position[1], (float) t[j].Position[2]) / t.Length; } Matrix4x4 matrix = new Matrix4x4(); int row = 0; foreach (var v in cell.Vertices) { matrix.SetRow(row, new Vector4((float) v.Position[0], (float) v.Position[1], (float) v.Position[2], 1)); row++; } matrix.SetRow(i, new Vector4(from.Position.x, from.Position.y, from.Position.z, 1)); var d = matrix.determinant; // we only create an outward edge when the circumcenter lies within the tetrahydron if (d > 0) { from.Index = index++; to.Index = index++; vertices.Add(from); vertices.Add(to); indices.Add(from.Index); indices.Add(to.Index); } } } } }*/ geometry.Vertices = vertices.Values.ToArray(); geometry.Indices = indices.ToArray(); geometry.Topology = MeshTopology.Lines; } return geometry; } private string GetUniqueID(VertexId[] vertices) { var id = ""; foreach (var vertex in vertices) { id += vertex.Id.ToString(); } return id; } private Vertex GetCircumcenter(VertexId[] vertices, CoordinateSystem coordinateSystem) { // From MathWorld: http://mathworld.wolfram.com/Circumsphere.html var points = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { points[i] = Utils.FromCoordinateSystemDefaultTo( new Vector3((float) vertices[i].Position[0], (float) vertices[i].Position[1], (float) vertices[i].Position[2]), coordinateSystem); } Matrix4x4 m = new Matrix4x4(); // x, y, z, 1 for (int i = 0; i < 4; i++) { m.SetRow(i, new Vector4(points[i].x, points[i].y, points[i].z, 1)); } var a = m.determinant; // size, y, z, 1 for (int i = 0; i < 4; i++) { m[i, 0] = points[i].sqrMagnitude; } var dx = m.determinant; // size, x, z, 1 for (int i = 0; i < 4; i++) { m[i, 1] = points[i].x; } var dy = -m.determinant; // size, x, y, 1 for (int i = 0; i < 4; i++) { m[i, 2] = points[i].y; } var dz = m.determinant; // size, x, y, z for (int i = 0; i < 4; i++) { m[i, 3] = points[i].z; } var s = 1.0f / (2.0f * a); return new Vertex(s * dx, s * dy, s * dz); } /// /// Transforms a vector3 array to a vertex array that is usable for the miconvexhull library /// /// /// private VertexId[] VectorToVertex(Vector3[] vectors, CoordinateSystem coordinateSystem) { VertexId[] vertices = new VertexId[vectors.Length]; for (int i = 0; i < vectors.Length; i++) { var vector = Utils.ToCoordinateSystemDefault(vectors[i], coordinateSystem); vertices[i] = new VertexId { Position = new double[3] {vector.x, vector.y, vector.z}, Id = i }; } return vertices; } private class VertexId : DefaultVertex { public int Id { get; set; } } } }