using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.AssetImporters; using UnityEngine; using UnityEngine.Rendering; namespace Metamesh { [ScriptedImporter(1, "gph")] public class GoldbergPolyhedronImporter : ScriptedImporter { [SerializeField] float radius = 1; [SerializeField, Min(2)] uint subdivision = 2; [SerializeField] bool generateLightmapUVs = false; [SerializeField] bool readWriteMeshes = false; [MenuItem("Assets/Create/GoldbergPolyhedron")] public static void CreateNewAsset() => ProjectWindowUtil.CreateAssetWithContent("New GoldbergPolyhedron.gph", ""); public override void OnImportAsset(AssetImportContext context) { var gameObject = new GameObject(); var mesh = ImportAsMesh(); var meshFilter = gameObject.AddComponent(); meshFilter.sharedMesh = mesh; var pipelineAsset = GraphicsSettings.currentRenderPipeline; var baseMaterial = pipelineAsset ? pipelineAsset.defaultMaterial : AssetDatabase.GetBuiltinExtraResource("Default-Diffuse.mat"); var meshRenderer = gameObject.AddComponent(); meshRenderer.sharedMaterial = baseMaterial; context.AddObjectToAsset("prefab", gameObject); if (mesh != null) context.AddObjectToAsset("mesh", mesh); context.SetMainObject(gameObject); } private Mesh ImportAsMesh() { var mesh = new Mesh(); mesh.name = "Mesh"; var builder = new IcosphereBuilder(); for (var i = 1; i < subdivision; ++i) { builder = new IcosphereBuilder(builder); } var vertices = builder.Vertices.Select(v => (Vector3)(v * radius)).ToList(); var normals = builder.Vertices.Select(v => (Vector3)v).ToList(); var indices = builder.Indices.ToList(); ApplyGoldbergTransformation(vertices, normals, indices); if (builder.VertexCount > 65535) mesh.indexFormat = IndexFormat.UInt32; mesh.SetVertices(vertices); mesh.SetNormals(normals); mesh.SetIndices(indices, MeshTopology.Triangles, 0); mesh.RecalculateBounds(); mesh.RecalculateNormals(); if (generateLightmapUVs) Unwrapping.GenerateSecondaryUVSet(mesh); mesh.UploadMeshData(!readWriteMeshes); return mesh; } private void ApplyGoldbergTransformation(List vertices, List normals, List indices) { var newVertices = new List(); var newNormals = new List(); var newIndices = new List(); var centers = new Dictionary>(); var edges = new HashSet(); { var neighbors = FindNeighbors(0, indices); centers.Add(0, neighbors); foreach (var i in neighbors) { edges.Add(i); } } int inc = 0; while (edges.Count + centers.Count < vertices.Count) { inc++; if (inc > 3) break; var ccs = FindCenters(edges, indices).Where(c => !centers.ContainsKey(c)).Distinct().ToArray(); Debug.Log($"while cc in nb: {ccs.Where(c => edges.Contains(c)).Count()}"); var nbs = ccs.SelectMany(c => { var neighbors = FindNeighbors(c, indices); centers.Add(c, neighbors); return neighbors; }).Distinct().Where(i => !edges.Contains(i) && !centers.ContainsKey(i)).ToArray(); Debug.Log($"while nb in cc: {nbs.Where(c => centers.ContainsKey(c)).Count()}"); foreach (var i in nbs) { edges.Add(i); } Debug.Log($"centers: {ccs.Count()}/{centers.Count}, edges: {nbs.Count()}/{edges.Count}, vertices: {vertices.Count}"); } Debug.Log($"cc in nb: {centers.Keys.Where(c => edges.Contains(c)).Count()}"); Debug.Log($"nb in cc: {edges.Where(c => centers.Keys.Contains(c)).Count()}"); Debug.Log($"missing: {Enumerable.Range(0, vertices.Count).Where(i => !centers.ContainsKey(i)).Where(i => !edges.Contains(i)).Count()}"); var pentagon = 0; var hexagon = 0; var start = 0; foreach (var i in centers.Keys) { var neighbors = centers[i].Distinct().ToArray(); var neighborCount = neighbors.Length; if (neighborCount != 5 && neighborCount != 6) { Debug.Log($"Unexpected number of neighbors: {neighborCount}"); continue; } if (neighborCount == 5) pentagon++; if (neighborCount == 6) hexagon++; /* var p1 = vertices[neighbors[0]]; var p2 = vertices[neighbors[2]]; var p3 = vertices[neighbors[4]]; var edge1 = p2 - p1; var edge2 = p3 - p1; var normal = Vector3.Cross(edge1, edge2).normalized; var projection = ProjectPointOntoPlane(p1, normal); */ var x = neighbors.Average(i => vertices[i].x); var y = neighbors.Average(i => vertices[i].y); var z = neighbors.Average(i => vertices[i].z); vertices[i] = new Vector3(x, y, z); var normal = vertices[i]; var q = Quaternion.FromToRotation(vertices[i], Vector3.up); //var mtx = Matrix4x4.TRS(-vertices[i], q, Vector3.one); var mtx = Matrix4x4.TRS(Vector3.zero, q, Vector3.one) * Matrix4x4.TRS(-vertices[i], Quaternion.identity, Vector3.one); var sortedNeighbors = neighbors.OrderBy(n => { var direction = mtx.MultiplyPoint(vertices[n]).normalized; var crossProduct = Vector3.Cross(direction, Vector3.up); return Mathf.Atan2(crossProduct.z, crossProduct.x); }).ToArray(); newVertices.Add(vertices[i]); newVertices.AddRange(sortedNeighbors.Select(n => vertices[n])); newNormals.AddRange(Enumerable.Repeat(normal, neighborCount + 1)); for (int k = 0; k < neighborCount; ++k) { newIndices.Add(start); newIndices.Add(start + (k + 1) % neighborCount); newIndices.Add(start + (k + 0) % neighborCount); } start += neighborCount + 1; } vertices.Clear(); vertices.AddRange(newVertices); normals.Clear(); normals.AddRange(newNormals); indices.Clear(); indices.AddRange(newIndices); Debug.Log($"Pentagon: {pentagon}, Hexagon: {hexagon}"); } private Vector3 ProjectPointOntoPlane(Vector3 planePoint, Vector3 planeNormal) { float distance = Vector3.Dot(planeNormal, Vector3.zero - planePoint); Vector3 projection = Vector3.zero - planeNormal * distance; return projection; } private List FindNeighbors(int index, List indices) { var results = new List(); for (int i = 0; i < indices.Count; i += 3) { var i0 = indices[i + 0]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; if (i0 == index) results.AddRange(new[] { i1, i2 }); if (i1 == index) results.AddRange(new[] { i0, i2 }); if (i2 == index) results.AddRange(new[] { i0, i1 }); } results = results.Distinct().ToList(); if (results.Count != 5 && results.Count != 6) Debug.Log($"Center#{index}'s neighbors count is {results.Count}"); return results; } private List FindCenters(HashSet edges, List indices) { var results = new List(); for (int i = 0; i < indices.Count; i += 3) { var i0 = indices[i + 0]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; var notInEdges = new List { i0, i1, i2 } .Where(v => !edges.Contains(v)) .ToList(); if (notInEdges.Count == 1) { var candidate = notInEdges[0]; var neighbors = FindNeighbors(candidate, indices); if (neighbors.Count != 5 && neighbors.Count != 6) Debug.Log($"candidate#{candidate}'s neighbors count is {neighbors.Count}"); results.Add(candidate); } } return results.Distinct().ToList(); } } }