From 321d9fbb03d11348e271241f11cb5588fc964a45 Mon Sep 17 00:00:00 2001 From: hoba Date: Tue, 11 Feb 2025 09:16:52 +0800 Subject: [PATCH] feat: add Goldberg polyhedron generation functionality --- .../Resources/New GoldbergPolyhedron.gph.meta | 4 +- .../Editor/GoldbergPolyhedronImporter.cs | 21 +- Assets/Scripts/GeodesicMesh.cs | 987 ++++++++++++++++++ Assets/Scripts/GeodesicMesh.cs.meta | 2 + Assets/Scripts/GoldbergBuilder.cs | 174 +++ Assets/Scripts/GoldbergBuilder.cs.meta | 2 + Assets/Scripts/GoldbergPolyhedron.cs | 43 - Assets/Scripts/GoldbergPolyhedron.cs.meta | 2 - Assets/Scripts/IsoVector.cs | 67 ++ Assets/Scripts/IsoVector.cs.meta | 2 + 10 files changed, 1255 insertions(+), 49 deletions(-) create mode 100644 Assets/Scripts/GeodesicMesh.cs create mode 100644 Assets/Scripts/GeodesicMesh.cs.meta create mode 100644 Assets/Scripts/GoldbergBuilder.cs create mode 100644 Assets/Scripts/GoldbergBuilder.cs.meta delete mode 100644 Assets/Scripts/GoldbergPolyhedron.cs delete mode 100644 Assets/Scripts/GoldbergPolyhedron.cs.meta create mode 100644 Assets/Scripts/IsoVector.cs create mode 100644 Assets/Scripts/IsoVector.cs.meta diff --git a/Assets/Resources/New GoldbergPolyhedron.gph.meta b/Assets/Resources/New GoldbergPolyhedron.gph.meta index b8f7e30..df6b419 100644 --- a/Assets/Resources/New GoldbergPolyhedron.gph.meta +++ b/Assets/Resources/New GoldbergPolyhedron.gph.meta @@ -9,6 +9,8 @@ ScriptedImporter: assetBundleVariant: script: {fileID: 11500000, guid: 80f1e0e8eacc4a945b07bba9e3065c99, type: 3} radius: 1 - subdivision: 3 + subdivision: 4 + m: 1 + n: 0 generateLightmapUVs: 0 readWriteMeshes: 0 diff --git a/Assets/Scripts/Editor/GoldbergPolyhedronImporter.cs b/Assets/Scripts/Editor/GoldbergPolyhedronImporter.cs index 444b1c6..c307bd3 100644 --- a/Assets/Scripts/Editor/GoldbergPolyhedronImporter.cs +++ b/Assets/Scripts/Editor/GoldbergPolyhedronImporter.cs @@ -4,6 +4,7 @@ using UnityEditor; using UnityEditor.AssetImporters; using UnityEngine; using UnityEngine.Rendering; +using static Metamesh.GoldbergBuilder; namespace Metamesh { @@ -12,6 +13,9 @@ namespace Metamesh { [SerializeField] float radius = 1; [SerializeField, Min(2)] uint subdivision = 2; + [SerializeField, Min(1)] int m = 1; + [SerializeField, Min(0)] int n = 0; + [SerializeField] bool generateLightmapUVs = false; [SerializeField] bool readWriteMeshes = false; @@ -42,7 +46,7 @@ namespace Metamesh { var mesh = new Mesh(); mesh.name = "Mesh"; - + /* var builder = new IcosphereBuilder(); for (var i = 1; i < subdivision; ++i) { @@ -54,8 +58,19 @@ namespace Metamesh var indices = builder.Indices.ToList(); ApplyGoldbergTransformation(vertices, normals, indices); - - if (builder.VertexCount > 65535) mesh.indexFormat = IndexFormat.UInt32; + */ + var builder = new GoldbergBuilder(); + var options = new GoldbergCreationOption() + { + m = m, + n = n, + }; + var data = builder.CreateGoldberg(options); + var vertices = data.vertices; + var normals = data.normals; + var indices = data.indices; + + if (vertices.Count > 65535) mesh.indexFormat = IndexFormat.UInt32; mesh.SetVertices(vertices); mesh.SetNormals(normals); mesh.SetIndices(indices, MeshTopology.Triangles, 0); diff --git a/Assets/Scripts/GeodesicMesh.cs b/Assets/Scripts/GeodesicMesh.cs new file mode 100644 index 0000000..afe1ce4 --- /dev/null +++ b/Assets/Scripts/GeodesicMesh.cs @@ -0,0 +1,987 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Metamesh +{ + public class PrimaryIsoTriangle + { + public int m; + public int n; + public List cartesian = new List(); + public List vertices = new List(); + public List max = new List(); + public List min = new List(); + public Dictionary vecToidx = new Dictionary(); + public Dictionary> vertByDist = new Dictionary>(); + public List> closestTo = new List>(); + + public List> innerFacets = new List>(); + public List> isoVecsABOB = new List>(); + public List> isoVecsOBOA = new List>(); + public List> isoVecsBAOA = new List>(); + public List> vertexTypes = new List>(); + + public float coau; + public float cobu; + public float coav; + public float cobv; + + //float PHI = (1 + Mathf.Sqrt(5f)) / 2; + const float PHI = 1.618034f; + public PolyhedronData IDATA = new PolyhedronData( + "icosahedron", + "Regular", + new List { + new Vector3(0, PHI, -1), + new Vector3(-PHI, 1, 0), + new Vector3(-1, 0, -PHI), + new Vector3(1, 0, -PHI), + new Vector3(PHI, 1, 0), + new Vector3(0, PHI, 1), + new Vector3(-1, 0, PHI), + new Vector3(-PHI, -1, 0), + new Vector3(0, -PHI, -1), + new Vector3(PHI, -1, 0), + new Vector3(1, 0, PHI), + new Vector3(0, -PHI, 1), + }, + new List> { + new List {0, 2, 1}, + new List {0, 3, 2}, + new List {0, 4, 3}, + new List {0, 5, 4}, + new List {0, 1, 5}, + new List {7, 6, 1}, + new List {8, 7, 2}, + new List {9, 8, 3}, + new List {10, 9, 4}, + new List {6, 10, 5}, + new List {2, 7, 1}, + new List {3, 8, 2}, + new List {4, 9, 3}, + new List {5, 10, 4}, + new List {1, 6, 5}, + new List {11, 6, 7}, + new List {11, 7, 8}, + new List {11, 8, 9}, + new List {11, 9, 10}, + new List {11, 10, 6}, + } + ); + + private int HighestCommonFactor(int a, int b) + { + var r = a % b; + if (r == 0) + { + return b; + } + return HighestCommonFactor(b, r); + } + + + + public void SetIndices() + { + var indexCount = 12; // 12 vertices already assigned + var vecToidx = new Dictionary(); + var m = this.m; + var n = this.n; + var g = m; // hcf of m, n when n != 0 + var m1 = 1; + var n1 = 0; + if (n != 0) + { + g = HighestCommonFactor(m, n); + } + m1 = m / g; + n1 = n / g; + + int fr; //face to the right of current face + string rot; //rotation about which vertex for fr + int O; + int A; + int B; + var oVec = IsoVector.Zero(); + var aVec = new IsoVector(m, n); + var bVec = new IsoVector(-n, m + n); + var oaVec = IsoVector.Zero(); + var abVec = IsoVector.Zero(); + var obVec = IsoVector.Zero(); + var verts = new List(); + string idx; + string idxR; + string isoId; + string isoIdR; + + var closestTo = new List>(); + var vDist = this.vertByDist; + + System.Action matchIdx = (f, fr, isoId, isoIdR) => + { + idx = f + "|" + isoId; + idxR = fr + "|" + isoIdR; + if (!(vecToidx.ContainsKey(idx) || vecToidx.ContainsKey(idxR))) + { + vecToidx[idx] = indexCount; + vecToidx[idxR] = indexCount; + indexCount++; + } + else if (vecToidx.ContainsKey(idx) && !(vecToidx.ContainsKey(idxR))) + { + vecToidx[idxR] = vecToidx[idx]; + } + else if (vecToidx.ContainsKey(idxR) && !(vecToidx.ContainsKey(idx))) + { + vecToidx[idx] = vecToidx[idxR]; + } + if (vDist[isoId][0] > 2) + { + closestTo[vecToidx[idx]] = new List { -vDist[isoId][0], vDist[isoId][1], vecToidx[idx] }; + } + else + { + closestTo[vecToidx[idx]] = new List { verts[vDist[isoId][0]], vDist[isoId][1], vecToidx[idx] }; + } + }; + + + this.IDATA.edgematch = new List<(int, string, int, string)> { + (1, "B", 0, ""), + (2, "B", 0, ""), + (3, "B", 0, ""), + (4, "B", 0, ""), + (0, "B", 0, ""), + (10, "O", 14, "A"), + (11, "O", 10, "A"), + (12, "O", 11, "A"), + (13, "O", 12, "A"), + (14, "O", 13, "A"), + (0, "O", 0, ""), + (1, "O", 0, ""), + (2, "O", 0, ""), + (3, "O", 0, ""), + (4, "O", 0, ""), + (19, "B", 5, "A"), + (15, "B", 6, "A"), + (16, "B", 7, "A"), + (17, "B", 8, "A"), + (18, "B", 9, "A"), + }; + + /***edges AB to OB***** rotation about B*/ + for (var f = 0; f < 20; f++) + { + //f current face + + verts = this.IDATA.face[f]; + O = verts[2]; + A = verts[1]; + B = verts[0]; + + isoId = oVec.x + "|" + oVec.y; + idx = f + "|" + isoId; + if (!vecToidx.ContainsKey(idx)) + { + vecToidx[idx] = O; + closestTo[O] = new List { verts[vDist[isoId][0]], vDist[isoId][1] }; + } + + isoId = aVec.x + "|" + aVec.y; + idx = f + "|" + isoId; + if (!vecToidx.ContainsKey(idx)) + { + vecToidx[idx] = A; + closestTo[A] = new List { verts[vDist[isoId][0]], vDist[isoId][1] }; + } + + isoId = bVec.x + "|" + bVec.y; + idx = f + "|" + isoId; + if (!vecToidx.ContainsKey(idx)) + { + vecToidx[idx] = B; + closestTo[B] = new List { verts[vDist[isoId][0]], vDist[isoId][1] }; + } + + //for edge vertices + fr = this.IDATA.edgematch[f].Item1; + rot = this.IDATA.edgematch[f].Item2; + if (rot == "B") + { + for (var i = 1; i < g; i++) + { + abVec.x = m - i * (m1 + n1); + abVec.y = n + i * m1; + obVec.x = -i * n1; + obVec.y = i * (m1 + n1); + isoId = abVec.x + "|" + abVec.y; + isoIdR = obVec.x + "|" + obVec.y; + matchIdx(f, fr, isoId, isoIdR); + } + } + + if (rot == "O") + { + for (var i = 1; i < g; i++) + { + obVec.x = -i * n1; + obVec.y = i * (m1 + n1); + oaVec.x = i * m1; + oaVec.y = i * n1; + isoId = obVec.x + "|" + obVec.y; + isoIdR = oaVec.x + "|" + oaVec.y; + matchIdx(f, fr, isoId, isoIdR); + } + } + + fr = this.IDATA.edgematch[f].Item3; + rot = this.IDATA.edgematch[f].Item4; + if (rot == "A") + { + for (var i = 1; i < g; i++) + { + oaVec.x = i * m1; + oaVec.y = i * n1; + abVec.x = m - (g - i) * (m1 + n1); //reversed for BA + abVec.y = n + (g - i) * m1; //reversed for BA + isoId = oaVec.x + "|" + oaVec.y; + isoIdR = abVec.x + "|" + abVec.y; + matchIdx(f, fr, isoId, isoIdR); + } + } + + for (var i = 0; i < this.vertices.Count; i++) + { + isoId = this.vertices[i].x + "|" + this.vertices[i].y; + idx = f + "|" + isoId; + if (!vecToidx.ContainsKey(idx)) + { + vecToidx[idx] = indexCount++; + if (vDist[isoId][0] > 2) + { + closestTo[vecToidx[idx]] = new List { -vDist[isoId][0], vDist[isoId][1], vecToidx[idx] }; + } + else + { + closestTo[vecToidx[idx]] = new List { verts[vDist[isoId][0]], vDist[isoId][1], vecToidx[idx] }; + } + } + } + } + + this.closestTo = closestTo; + this.vecToidx = vecToidx; + } + + public void CalcCoeffs() + { + var m = this.m; + var n = this.n; + var thirdR3 = Mathf.Sqrt(3) / 3; + + var LSQD = m * m + n * n + m * n; + + this.coau = (m + n) / LSQD; + this.cobu = -n / LSQD; + this.coav = (-thirdR3 * (m - n)) / LSQD; + this.cobv = (thirdR3 * (2 * m + n)) / LSQD; + } + + public void CreateInnerFacets() + { + var m = this.m; + var n = this.n; + for (var y = 0; y < n + m + 1; y++) + { + for (var x = this.min[y]; x < this.max[y] + 1; x++) + { + if (x < this.max[y] && x < this.max[y + 1] + 1) + { + this.innerFacets.Add(new List { "|" + x + "|" + y, "|" + x + "|" + (y + 1), "|" + (x + 1) + "|" + y }); + } + if (y > 0 && x < this.max[y - 1] && x + 1 < this.max[y] + 1) + { + this.innerFacets.Add(new List { "|" + x + "|" + y, "|" + (x + 1) + "|" + y, "|" + (x + 1) + "|" + (y - 1) }); + } + } + } + } + + public void EdgeVecsABOB() + { + var m = this.m; + var n = this.n; + + var B = new IsoVector(-n, m + n); + + for (var y = 1; y < m + n; y++) + { + var point = new IsoVector(this.min[y], y); + var prev = new IsoVector(this.min[y - 1], y - 1); + var next = new IsoVector(this.min[y + 1], y + 1); + var pointR = point.Clone(); + var prevR = prev.Clone(); + var nextR = next.Clone(); + + pointR.Rotate60About(B); + prevR.Rotate60About(B); + nextR.Rotate60About(B); + + var maxPoint = new IsoVector(this.max[pointR.y], pointR.y); + var maxPrev = new IsoVector(this.max[pointR.y - 1], pointR.y - 1); + var maxLeftPrev = new IsoVector(this.max[pointR.y - 1] - 1, pointR.y - 1); + + if (pointR.x != maxPoint.x || pointR.y != maxPoint.y) + { + if (pointR.x != maxPrev.x) + { + // type2 + //up + this.vertexTypes.Add(new List { 1, 0, 0 }); + this.isoVecsABOB.Add(new List { point, maxPrev, maxLeftPrev }); + //down + this.vertexTypes.Add(new List { 1, 0, 0 }); + this.isoVecsABOB.Add(new List { point, maxLeftPrev, maxPoint }); + } + else if (pointR.y == nextR.y) + { + // type1 + //up + this.vertexTypes.Add(new List { 1, 1, 0 }); + this.isoVecsABOB.Add(new List { point, prev, maxPrev }); + //down + this.vertexTypes.Add(new List { 1, 0, 1 }); + this.isoVecsABOB.Add(new List { point, maxPrev, next }); + } + else + { + // type 0 + //up + this.vertexTypes.Add(new List { 1, 1, 0 }); + this.isoVecsABOB.Add(new List { point, prev, maxPrev }); + //down + this.vertexTypes.Add(new List { 1, 0, 0 }); + this.isoVecsABOB.Add(new List { point, maxPrev, maxPoint }); + } + } + } + } + + public void MapABOBtoOBOA() + { + var point = new IsoVector(0, 0); + for (var i = 0; i < this.isoVecsABOB.Count; i++) + { + var temp = new List(); + for (var j = 0; j < 3; j++) + { + point.x = this.isoVecsABOB[i][j].x; + point.y = this.isoVecsABOB[i][j].y; + if (this.vertexTypes[i][j] == 0) + { + point.RotateNeg120(this.m, this.n); + } + temp.Add(point.Clone()); + } + this.isoVecsOBOA.Add(temp); + } + } + + public void MapABOBtoBAOA() + { + var point = new IsoVector(0, 0); + for (var i = 0; i < this.isoVecsABOB.Count; i++) + { + var temp = new List(); + for (var j = 0; j < 3; j++) + { + point.x = this.isoVecsABOB[i][j].x; + point.y = this.isoVecsABOB[i][j].y; + if (this.vertexTypes[i][j] == 1) + { + point.Rotate120(this.m, this.n); + } + temp.Add(point.Clone()); + } + this.isoVecsBAOA.Add(temp); + } + } + + public void MapToFace(int faceNb, PolyhedronData geodesicData) + { + var F = this.IDATA.face[faceNb]; + var oidx = F[2]; + var aidx = F[1]; + var bidx = F[0]; + + var oidxVec = this.IDATA.vertex[oidx]; + var aidxVec = this.IDATA.vertex[aidx]; + var bidxVec = this.IDATA.vertex[bidx]; + var O = new Vector3(oidxVec[0], oidxVec[1], oidxVec[2]); + var A = new Vector3(aidxVec[0], aidxVec[1], aidxVec[2]); + var B = new Vector3(bidxVec[0], bidxVec[1], bidxVec[2]); + + var OA = A - O; + var OB = B - O; + + var x = OA * this.coau + OB * this.cobu; + var y = OA * this.coav + OB * this.cobv; + + var mapped = new List>(); + + string idx; + for (var i = 0; i < this.cartesian.Count; i++) + { + var tempVec = x * this.cartesian[i].x + y * this.cartesian[i].y + O; + mapped[i] = new List { tempVec.x, tempVec.y, tempVec.z }; + idx = faceNb + "|" + this.vertices[i].x + "|" + this.vertices[i].y; + geodesicData.vertex[this.vecToidx[idx]] = tempVec; + } + } + + //statics + /**Creates a primary triangle + * @internal + */ + + public PrimaryIsoTriangle Build(int m, int n) + { + var vertices = new List(); + + var O = IsoVector.Zero(); + var A = new IsoVector(m, n); + var B = new IsoVector(-n, m + n); + vertices.AddRange(new IsoVector[] { O, A, B }); + + //max internal isoceles triangle vertices + for (var iy = n; iy < m + 1; iy++) + { + for (var ix = 0; ix < m + 1 - iy; ix++) + { + vertices.Add(new IsoVector(ix, iy)); + } + } + + //shared vertices along edges when needed + if (n > 0) + { + var g = HighestCommonFactor(m, n); + var m1 = m / g; + var n1 = n / g; + + for (var i = 1; i < g; i++) + { + vertices.Add(new IsoVector(i * m1, i * n1)); //OA + vertices.Add(new IsoVector(-i * n1, i * (m1 + n1))); //OB + vertices.Add(new IsoVector(m - i * (m1 + n1), n + i * m1)); // AB + } + + //lower rows vertices and their rotations + var ratio = m / n; + for (var iy = 1; iy < n; iy++) + { + for (var ix = 0; ix < iy * ratio; ix++) + { + vertices.Add(new IsoVector(ix, iy)); + vertices.Add(new IsoVector(ix, iy).Rotate120(m, n)); + vertices.Add(new IsoVector(ix, iy).RotateNeg120(m, n)); + } + } + } + //order vertices by x and then y + vertices.Sort((a, b) => + { + return a.x - b.x; + }); + + vertices.Sort((a, b) => + { + return a.y - b.y; + }); + + var min = new List { m + n + 1 }; + var max = new List { m + n + 1 }; + for (var i = 0; i < min.Count; i++) + { + min[i] = int.MaxValue; + max[i] = int.MinValue; + } + + var y = 0; + var x = 0; + + var len = vertices.Count; + for (var i = 0; i < len; i++) + { + x = vertices[i].x; + y = vertices[i].y; + min[y] = Mathf.Min(x, min[y]); + max[y] = Mathf.Max(x, max[y]); + } + + //calculates the distance of a vertex from a given primary vertex + Func distFrom = (IsoVector vert, string primVert) => + { + var v = vert.Clone(); + if (primVert == "A") + { + v.RotateNeg120(m, n); + } + if (primVert == "B") + { + v.Rotate120(m, n); + } + if (v.x < 0) + { + return v.y; + } + return v.x + v.y; + }; + + var cartesian = new List(); + var distFromO = new List(); + var distFromA = new List(); + var distFromB = new List(); + var vertByDist = new Dictionary>(); + ; + var vertData = new List>(); + var closest = -1; + var dist = -1; + for (var i = 0; i < len; i++) + { + cartesian[i] = vertices[i].ToCartesianOrigin(new IsoVector(0, 0), 0.5f); + distFromO[i] = distFrom(vertices[i], "O"); + distFromA[i] = distFrom(vertices[i], "A"); + distFromB[i] = distFrom(vertices[i], "B"); + + if (distFromO[i] == distFromA[i] && distFromA[i] == distFromB[i]) + { + closest = 3; + dist = distFromO[i]; + } + else if (distFromO[i] == distFromA[i]) + { + closest = 4; + dist = distFromO[i]; + } + else if (distFromA[i] == distFromB[i]) + { + closest = 5; + dist = distFromA[i]; + } + else if (distFromB[i] == distFromO[i]) + { + closest = 6; + dist = distFromO[i]; + } + if (distFromO[i] < distFromA[i] && distFromO[i] < distFromB[i]) + { + closest = 2; + dist = distFromO[i]; + } + if (distFromA[i] < distFromO[i] && distFromA[i] < distFromB[i]) + { + closest = 1; + dist = distFromA[i]; + } + if (distFromB[i] < distFromA[i] && distFromB[i] < distFromO[i]) + { + closest = 0; + dist = distFromB[i]; + } + vertData.Add(new List { closest, dist, vertices[i].x, vertices[i].y }); + } + + vertData.Sort((a, b) => + { + return a[2].CompareTo(b[2]); + }); + vertData.Sort((a, b) => + { + return a[3].CompareTo(b[3]); + }); + vertData.Sort((a, b) => + { + return a[1].CompareTo(b[1]); + }); + vertData.Sort((a, b) => + { + return a[0].CompareTo(b[0]); + }); + + for (var v = 0; v < vertData.Count; v++) + { + vertByDist[vertData[v][2] + "|" + vertData[v][3]] = new List { vertData[v][0], vertData[v][1], v }; + } + + this.m = m; + this.n = n; + this.vertices = vertices; + this.vertByDist = vertByDist; + this.cartesian = cartesian; + this.min = min; + this.max = max; + + return this; + } + } + + public class PolyhedronData + { + public string name; + public string category; + public List vertex; + public List> face; + public List<(int, string, int, string)> edgematch; + + public PolyhedronData( + string name, + string category, + List vertex, + List> face + ) + { + this.name = name; + this.category = category; + this.vertex = vertex; + this.face = face; + } + } + + public class GeodesicData : PolyhedronData + { + //public override edgematch: (number | string)[][]; + public List> adjacentFaces; + public int sharedNodes; + public int poleNodes; + + public GeodesicData( + string name, + string category, + List vertex, + List> face + ) : base(name, category, vertex, face) + { + + } + + public void innerToData(int face, PrimaryIsoTriangle primTri) + { + for (var i = 0; i < primTri.innerFacets.Count; i++) + { + this.face.Add(primTri.innerFacets[i].Select((el) => primTri.vecToidx[face + el]).ToList()); + } + } + + public void MapABOBtoDATA(int faceNb, PrimaryIsoTriangle primTri) + { + var fr = primTri.IDATA.edgematch[faceNb].Item1; + for (var i = 0; i < primTri.isoVecsABOB.Count; i++) + { + var temp = new List(); + for (var j = 0; j < 3; j++) + { + if (primTri.vertexTypes[i][j] == 0) + { + temp.Add(faceNb + "|" + primTri.isoVecsABOB[i][j].x + "|" + primTri.isoVecsABOB[i][j].y); + } + else + { + temp.Add(fr + "|" + primTri.isoVecsABOB[i][j].x + "|" + primTri.isoVecsABOB[i][j].y); + } + } + this.face.Add(new List { primTri.vecToidx[temp[0]], primTri.vecToidx[temp[1]], primTri.vecToidx[temp[2]] }); + } + } + /** + * @internal + */ + public void MapOBOAtoDATA(int faceNb, PrimaryIsoTriangle primTri) + { + var fr = primTri.IDATA.edgematch[faceNb].Item1; + for (var i = 0; i < primTri.isoVecsOBOA.Count; i++) + { + var temp = new List(); + for (var j = 0; j < 3; j++) + { + if (primTri.vertexTypes[i][j] == 1) + { + temp.Add(faceNb + "|" + primTri.isoVecsOBOA[i][j].x + "|" + primTri.isoVecsOBOA[i][j].y); + } + else + { + temp.Add(fr + "|" + primTri.isoVecsOBOA[i][j].x + "|" + primTri.isoVecsOBOA[i][j].y); + } + } + this.face.Add(new List { primTri.vecToidx[temp[0]], primTri.vecToidx[temp[1]], primTri.vecToidx[temp[2]] }); + } + } + /** + * @internal + */ + public void MapBAOAtoDATA(int faceNb, PrimaryIsoTriangle primTri) + { + var fr = primTri.IDATA.edgematch[faceNb].Item3; + for (var i = 0; i < primTri.isoVecsBAOA.Count; i++) + { + var temp = new List(); + for (var j = 0; j < 3; j++) + { + if (primTri.vertexTypes[i][j] == 1) + { + temp.Add(faceNb + "|" + primTri.isoVecsBAOA[i][j].x + "|" + primTri.isoVecsBAOA[i][j].y); + } + else + { + temp.Add(fr + "|" + primTri.isoVecsBAOA[i][j].x + "|" + primTri.isoVecsBAOA[i][j].y); + } + } + this.face.Add(new List { primTri.vecToidx[temp[0]], primTri.vecToidx[temp[1]], primTri.vecToidx[temp[2]] }); + } + } + /** + * @internal + */ + public void OrderData(PrimaryIsoTriangle primTri) + { + var nearTo = new Dictionary>>(); + for (var i = 0; i < 13; i++) + { + nearTo[i] = new List>(); + } + var close = primTri.closestTo; + for (var i = 0; i < close.Count; i++) + { + if (close[i][0] > -1) + { + if (close[i][1] > 0) + { + nearTo[close[i][0]].Add(new List { i, close[i][1] }); + } + } + else + { + nearTo[12].Add(new List { i, close[i][0] }); + } + } + /* + var near = new List(); + for (var i = 0; i < 12; i++) + { + near[i] = i; + } + */ + var near = Enumerable.Range(0, 12).ToList(); + var nearIndex = 12; + for (var i = 0; i < 12; i++) + { + nearTo[i].Sort((a, b) => + { + return a[1] - b[1]; + }); + /* + for (var j = 0; j < nearTo[i].Count; j++) + { + near[nearTo[i][j][0]] = nearIndex++; + } + */ + foreach (var item in nearTo[i]) + { + near[item[0]] = nearIndex++; + } + } + /* + for (var j = 0; j < nearTo[12].Count; j++) + { + near[nearTo[12][j][0]] = nearIndex++; + } + */ + foreach (var item in nearTo[12]) + { + near[item[0]] = nearIndex++; + } + /* + for (var i = 0; i < this.vertex.Count; i++) + { + //FIX + this.vertex[i].Add(near[i]); + } + + this.vertex.Sort((a, b) => + { + return a[3].CompareTo(b[3]); + }); + + for (var i = 0; i < this.vertex.Count; i++) + { + //FIX + this.vertex[i].RemoveAt(this.vertex[i].Count - 1); + } + */ + this.vertex = this.vertex + .Select((v, index) => (Vertex: v, SortKey: near[index])) + .OrderBy(x => x.SortKey) + .Select(x => x.Vertex) + .ToList(); + + for (var i = 0; i < this.face.Count; i++) + { + for (var j = 0; j < this.face[i].Count; j++) + { + this.face[i][j] = near[this.face[i][j]]; + } + } + + this.sharedNodes = nearTo[12].Count; + this.poleNodes = this.vertex.Count - this.sharedNodes; + } + + public List SetOrder(int m, List faces) + { + var adjVerts = new List(); + var dualFaces = new List(); + var face = faces.Last(); + faces.RemoveAt(faces.Count - 1); + dualFaces.Add(face); + var index = this.face[face].IndexOf(m); + index = (index + 2) % 3; + var v = this.face[face][index]; + adjVerts.Add(v); + var f = 0; + while (faces.Count > 0) + { + face = faces[f]; + if (this.face[face].IndexOf(v) > -1) + { + // v is a vertex of face f + index = (this.face[face].IndexOf(v) + 1) % 3; + v = this.face[face][index]; + adjVerts.Add(v); + dualFaces.Add(face); + faces.RemoveAt(f); + f = 0; + } + else + { + f++; + } + } + this.adjacentFaces.Add(adjVerts); + return dualFaces; + } + + public PolyhedronData ToGoldbergPolyhedronData() + { + var goldbergPolyhedronData = new PolyhedronData("GeoDual", "Goldberg", new List(), new List>()); + goldbergPolyhedronData.name = "GD dual"; + var verticesNb = this.vertex.Count; + var map = new List[verticesNb]; + for (var v = 0; v < verticesNb; v++) + { + map[v] = new List(); + } + for (var f = 0; f < this.face.Count; f++) + { + for (var i = 0; i < 3; i++) + { + map[this.face[f][i]].Add(f); + } + } + float cx; + float cy; + float cz; + List face; + Vector3 vertex; + this.adjacentFaces = new List>(); + for (var m = 0; m < map.Length; m++) + { + goldbergPolyhedronData.face[m] = this.SetOrder(m, new List(map[m])); + foreach (var el in map[m]) + { + cx = 0; + cy = 0; + cz = 0; + face = this.face[el]; + for (var i = 0; i < 3; i++) + { + vertex = this.vertex[face[i]]; + cx += vertex[0]; + cy += vertex[1]; + cz += vertex[2]; + } + goldbergPolyhedronData.vertex[el] = new Vector3(cx / 3, cy / 3, cz / 3); + } + ; + } + return goldbergPolyhedronData; + } + + public static GeodesicData BuildGeodesicData(PrimaryIsoTriangle primTri) + { + float PHI = (1 + Mathf.Sqrt(5f)) / 2; + var geodesicData = new GeodesicData( + "Geodesic-m-n", + "Geodesic", + new List() { + new Vector3(0, PHI, -1), + new Vector3(-PHI, 1, 0), + new Vector3(-1, 0, -PHI), + new Vector3(1, 0, -PHI), + new Vector3(PHI, 1, 0), + new Vector3(0, PHI, 1), + new Vector3(-1, 0, PHI), + new Vector3(-PHI, -1, 0), + new Vector3(0, -PHI, -1), + new Vector3(PHI, -1, 0), + new Vector3(1, 0, PHI), + new Vector3(0, -PHI, 1), + }, + new List>() + ); + + primTri.SetIndices(); + primTri.CalcCoeffs(); + primTri.CreateInnerFacets(); + primTri.EdgeVecsABOB(); + primTri.MapABOBtoOBOA(); + primTri.MapABOBtoBAOA(); + + for (var f = 0; f < primTri.IDATA.face.Count; f++) + { + primTri.MapToFace(f, geodesicData); + geodesicData.innerToData(f, primTri); + if (primTri.IDATA.edgematch[f].Item2 == "B") + { + geodesicData.MapABOBtoDATA(f, primTri); + } + if (primTri.IDATA.edgematch[f].Item2 == "O") + { + geodesicData.MapOBOAtoDATA(f, primTri); + } + if (primTri.IDATA.edgematch[f].Item4 == "A") + { + geodesicData.MapBAOAtoDATA(f, primTri); + } + } + + geodesicData.OrderData(primTri); + var radius = 1f; + geodesicData.vertex = geodesicData.vertex.Select((el) => + { + var a = el[0]; + var b = el[1]; + var c = el[2]; + var d = Mathf.Sqrt(a * a + b * b + c * c); + el[0] *= radius / d; + el[1] *= radius / d; + el[2] *= radius / d; + return el; + }).ToList(); + + return geodesicData; + } + } + +} diff --git a/Assets/Scripts/GeodesicMesh.cs.meta b/Assets/Scripts/GeodesicMesh.cs.meta new file mode 100644 index 0000000..3ea77e8 --- /dev/null +++ b/Assets/Scripts/GeodesicMesh.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ba9950a73f64cb4fa05dd46d677a642 \ No newline at end of file diff --git a/Assets/Scripts/GoldbergBuilder.cs b/Assets/Scripts/GoldbergBuilder.cs new file mode 100644 index 0000000..587df97 --- /dev/null +++ b/Assets/Scripts/GoldbergBuilder.cs @@ -0,0 +1,174 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Metamesh +{ + public class GoldbergBuilder + { + public class GoldbergVertexDataOption + { + public float size = 1; + public float sizeX = 1; + public float sizeY = 1; + public float sizeZ = 1; + }; + + public class GoldbergCreationOption : GoldbergVertexDataOption + { + public int m; + public int n; + } + + public class VertexData + { + public List vertices; + public List indices; + public List normals; + public List uvs; + } + + public VertexData CreateGoldbergVertexData(GoldbergVertexDataOption options, PolyhedronData goldbergData) + { + var size = options.size; + var sizeX = options.sizeX;// || size || 1; + var sizeY = options.sizeY;// || size || 1; + var sizeZ = options.sizeZ;// || size || 1; + + var positions = new List(); + var indices = new List(); + var normals = new List(); + var uvs = new List(); + + var minX = float.MaxValue; + var maxX = float.MinValue; + var minY = float.MaxValue; + var maxY = float.MinValue; + + for (var v = 0; v < goldbergData.vertex.Count; v++) + { + minX = Mathf.Min(minX, goldbergData.vertex[v].x * sizeX); + maxX = Mathf.Max(maxX, goldbergData.vertex[v].x * sizeX); + minY = Mathf.Min(minY, goldbergData.vertex[v].y * sizeY); + maxY = Mathf.Max(maxY, goldbergData.vertex[v].y * sizeY); + } + + var index = 0; + for (var f = 0; f < goldbergData.face.Count; f++) + { + var verts = goldbergData.face[f]; + var a = goldbergData.vertex[verts[0]]; + var b = goldbergData.vertex[verts[2]]; + var c = goldbergData.vertex[verts[1]]; + var ba = b - a; + var ca = c - a; + var norm = Vector3.Cross(ca, ba).normalized; + for (var v = 0; v < verts.Count; v++) + { + normals.Add(norm); + var pdata = goldbergData.vertex[verts[v]]; + positions.Add(new Vector3(pdata[0] * sizeX, pdata[1] * sizeY, pdata[2] * sizeZ)); + var vCoord = (pdata[1] * sizeY - minY) / (maxY - minY); + uvs.Add(new Vector2((pdata[0] * sizeX - minX) / (maxX - minX), vCoord)); + } + for (var v = 0; v < verts.Count - 2; v++) + { + indices.AddRange(new int[] { index, index + v + 2, index + v + 1 }); + } + index += verts.Count; + } + /* + VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs); + + const vertexData = new VertexData(); + vertexData.positions = positions; + vertexData.indices = indices; + vertexData.normals = normals; + vertexData.uvs = uvs; + return vertexData; + */ + return new VertexData() + { + vertices = positions, + indices = indices, + normals = normals, + uvs = uvs, + }; + } + + /** + * Creates the Mesh for a Goldberg Polyhedron which is made from 12 pentagonal and the rest hexagonal faces + * @see https://en.wikipedia.org/wiki/Goldberg_polyhedron + * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/goldberg_poly + * @param name defines the name of the mesh + * @param options an object used to set the following optional parameters for the polyhedron, required but can be empty + * @param scene defines the hosting scene + * @returns Goldberg mesh + */ + public VertexData CreateGoldberg(GoldbergCreationOption options) + { + var size = options.size; + var sizeX = options.sizeX;// || size || 1; + var sizeY = options.sizeY;// || size || 1; + var sizeZ = options.sizeZ;// || size || 1; + var m = options.m;// || 1; + var n = options.n;// || 0; + if (n > m) + { + var temp = n; + n = m; + m = temp; + Debug.LogWarning("n > m therefore m and n swapped"); + } + var primTri = new PrimaryIsoTriangle(); + primTri.Build(m, n); + var geodesicData = GeodesicData.BuildGeodesicData(primTri); + var goldbergData = geodesicData.ToGoldbergPolyhedronData(); + + //var goldberg = new Mesh(); + //goldberg.name = name; + + var vertexData = CreateGoldbergVertexData(options, goldbergData); + return vertexData; + + /* + vertexData.applyToMesh(goldberg, options.updatable); + + goldberg.goldbergData.nbSharedFaces = geodesicData.sharedNodes; + goldberg.goldbergData.nbUnsharedFaces = geodesicData.poleNodes; + goldberg.goldbergData.adjacentFaces = geodesicData.adjacentFaces; + goldberg.goldbergData.nbFaces = goldberg.goldbergData.nbSharedFaces + goldberg.goldbergData.nbUnsharedFaces; + goldberg.goldbergData.nbFacesAtPole = (goldberg.goldbergData.nbUnsharedFaces - 12) / 12; + + for (var f = 0; f < geodesicData.vertex.Count; f++) + { + + goldberg.goldbergData.faceCenters.push(Vector3.FromArray(geodesicData.vertex[f])); + goldberg.goldbergData.faceCenters[f].x *= sizeX; + goldberg.goldbergData.faceCenters[f].y *= sizeY; + goldberg.goldbergData.faceCenters[f].z *= sizeZ; + goldberg.goldbergData.faceColors.push(new Color4(1, 1, 1, 1)); + + } + + for (var f = 0; f < goldbergData.face.Count; f++) + { + var verts = goldbergData.face[f]; + var a = goldbergData.vertex[verts[0]]; + var b = goldbergData.vertex[verts[2]]; + var c = goldbergData.vertex[verts[1]]; + var ba = b - a; + var ca = c - a; + var norm = Vector3.Cross(ca, ba).normalized; + var z = Vector3.Cross(ca, norm).normalized; + //goldberg.goldbergData.faceXaxis.push(ca.normalize()); + //goldberg.goldbergData.faceYaxis.push(norm); + //goldberg.goldbergData.faceZaxis.push(z); + } + + return goldberg; + */ + } + } + +} \ No newline at end of file diff --git a/Assets/Scripts/GoldbergBuilder.cs.meta b/Assets/Scripts/GoldbergBuilder.cs.meta new file mode 100644 index 0000000..5d7f306 --- /dev/null +++ b/Assets/Scripts/GoldbergBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7d8371f81baddd64e8c1450945f7f75f \ No newline at end of file diff --git a/Assets/Scripts/GoldbergPolyhedron.cs b/Assets/Scripts/GoldbergPolyhedron.cs deleted file mode 100644 index 0b6e163..0000000 --- a/Assets/Scripts/GoldbergPolyhedron.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using UnityEngine; -using UnityEngine.Rendering; - -namespace Metamesh -{ - public class GoldbergPolyhedron : MonoBehaviour - { - public float radius = 1; - public uint subdivision = 2; - - private void Start() - { - - } - - private void Update() - { - - } - - public void Generate() - { - var builder = new IcosphereBuilder(); - for (var i = 1; i < subdivision; ++i) - { - builder = new IcosphereBuilder(builder); - } - - var vertices = builder.Vertices.Select(v => (Vector3)(v * radius)); - var normals = builder.Vertices.Select(v => (Vector3)v); - var indices = builder.Indices; - - /* - if (builder.VertexCount > 65535) mesh.indexFormat = IndexFormat.UInt32; - mesh.SetVertices(vtx.ToList()); - mesh.SetNormals(nrm.ToList()); - mesh.SetIndices(idx.ToList(), MeshTopology.Triangles, 0); - */ - } - } -} - diff --git a/Assets/Scripts/GoldbergPolyhedron.cs.meta b/Assets/Scripts/GoldbergPolyhedron.cs.meta deleted file mode 100644 index f20c30a..0000000 --- a/Assets/Scripts/GoldbergPolyhedron.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 825ce87dd8b71e444a6c78436e3d9903 \ No newline at end of file diff --git a/Assets/Scripts/IsoVector.cs b/Assets/Scripts/IsoVector.cs new file mode 100644 index 0000000..2ac0256 --- /dev/null +++ b/Assets/Scripts/IsoVector.cs @@ -0,0 +1,67 @@ +using UnityEngine; + +namespace Metamesh +{ + public class IsoVector + { + public int x; + public int y; + + public IsoVector(int x, int y) + { + this.x = x; + this.y = y; + } + + public IsoVector Clone() + { + return new IsoVector(x, y); + } + + public IsoVector Rotate60About(IsoVector other) + { + var x = this.x; + this.x = other.x + other.y - this.y; + this.y = x + this.y - other.x; + return this; + } + + public IsoVector RotateNeg60About(IsoVector other) + { + var x = this.x; + this.x = x + this.y - other.y; + this.y = other.x + other.y - x; + return this; + } + + public IsoVector Rotate120(int m, int n) + { + var x = this.x; + this.x = m - x - this.y; + this.y = n + x; + return this; + } + + public IsoVector RotateNeg120(int m, int n) + { + var x = this.x; + this.x = this.y - n; + this.y = m + n - x - this.y; + return this; + } + + public Vector3 ToCartesianOrigin(IsoVector origin, float isoGridSize) + { + var point = Vector3.zero; + point.x = origin.x + 2 * this.x * isoGridSize + this.y * isoGridSize; + point.y = origin.y + Mathf.Sqrt(3) * this.y * isoGridSize; + return point; + } + + public static IsoVector Zero() + { + return new IsoVector(0, 0); + } + } +} + diff --git a/Assets/Scripts/IsoVector.cs.meta b/Assets/Scripts/IsoVector.cs.meta new file mode 100644 index 0000000..a110e22 --- /dev/null +++ b/Assets/Scripts/IsoVector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1b57a079742a0234e892f0128b45703f \ No newline at end of file