You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

227 lines
9.1 KiB

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>();
meshFilter.sharedMesh = mesh;
var pipelineAsset = GraphicsSettings.currentRenderPipeline;
var baseMaterial = pipelineAsset ? pipelineAsset.defaultMaterial : AssetDatabase.GetBuiltinExtraResource<Material>("Default-Diffuse.mat");
var meshRenderer = gameObject.AddComponent<MeshRenderer>();
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<Vector3> vertices, List<Vector3> normals, List<int> indices)
{
var newVertices = new List<Vector3>();
var newNormals = new List<Vector3>();
var newIndices = new List<int>();
var centers = new Dictionary<int, List<int>>();
var edges = new HashSet<int>();
{
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<int> FindNeighbors(int index, List<int> indices)
{
var results = new List<int>();
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<int> FindCenters(HashSet<int> edges, List<int> indices)
{
var results = new List<int>();
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<int> { 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();
}
}
}