I did a few tests… it is WAAAAY easier to make a quad-based sphere than dealing with anything triangular.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class PlanetGenerator: MonoBehaviour{
[SerializeField] int sphereGridSize = 6;
[SerializeField] float radius = 1.0f;
[SerializeField] int numTetraSubdivisions = 3;
[SerializeField] bool showSphereGrid = true;
[SerializeField] bool showTetraGrid = true;
GameObject tetra = null;
GameObject sphereGrid = null;
Mesh sphereMesh = null;
Mesh tetraMesh = null;
MeshRenderer sphereRenderer = null;
MeshRenderer tetraRenderer = null;
MeshFilter sphereFilter = null;
MeshFilter tetraFilter = null;
[SerializeField] Material meshMaterial = null;
List<Vector3> verts = new List<Vector3>();
List<int> indexes = new List<int>();
List<Vector3> normals = new List<Vector3>();
float lastRadius = -1.0f;
int lastGridSize = -1;
int lastSubdivisions = -1;
void killObjects(){
DestroyImmediate(tetraFilter);
DestroyImmediate(sphereFilter);
DestroyImmediate(tetraRenderer);
DestroyImmediate(sphereRenderer);
DestroyImmediate(sphereMesh);
DestroyImmediate(tetraMesh);
DestroyImmediate(tetra);
DestroyImmediate(sphereGrid);
}
C getOrCreateTmpComponent<C>(GameObject obj) where C: Component{
var result = obj.GetComponent<C>();
if (result)
return result;
result = obj.AddComponent<C>();
result.hideFlags = HideFlags.DontSave;
return result;
}
void rebuildMeshes(){
if (!tetra){
tetra = new GameObject();
tetra.transform.SetParent (transform);
tetra.transform.localPosition = new Vector3(radius, 0.0f, 0.0f);
tetra.hideFlags = HideFlags.DontSave;
}
if (!sphereGrid){
sphereGrid = new GameObject();
sphereGrid.transform.SetParent (transform);
sphereGrid.transform.localPosition = new Vector3(-radius, 0.0f, 0.0f);
sphereGrid.hideFlags = HideFlags.DontSave;
}
sphereFilter = getOrCreateTmpComponent<MeshFilter>(sphereGrid);
tetraFilter = getOrCreateTmpComponent<MeshFilter>(tetra);
sphereRenderer = getOrCreateTmpComponent<MeshRenderer>(sphereGrid);
tetraRenderer = getOrCreateTmpComponent<MeshRenderer>(tetra);
if (!sphereMesh){
sphereMesh = new Mesh();
sphereMesh.hideFlags = HideFlags.DontSave;
sphereFilter.mesh = sphereMesh;
}
if (!tetraMesh){
tetraMesh = new Mesh();
tetraMesh.hideFlags = HideFlags.DontSave;
tetraFilter.mesh = tetraMesh;
}
sphereRenderer.sharedMaterial = meshMaterial;
tetraRenderer.sharedMaterial = meshMaterial;
verts.Clear();
normals.Clear();
indexes.Clear();
processTetraGrid(numTetraSubdivisions, (a, b, c)=>{
var idx = verts.Count;
var n = Vector3.Cross(b - a, c -a).normalized;
verts.Add(a * radius);
verts.Add(b * radius);
verts.Add(c * radius);
normals.Add(n);
normals.Add(n);
normals.Add(n);
indexes.Add(idx + 0);
indexes.Add(idx + 1);
indexes.Add(idx + 2);
});
tetraMesh.Clear();
tetraMesh.subMeshCount = 1;
tetraMesh.SetVertices(verts);
tetraMesh.SetNormals(normals);
tetraMesh.SetTriangles(indexes, 0);
tetraRenderer.sharedMaterial = meshMaterial;
verts.Clear();
indexes.Clear();
normals.Clear();
processSphereGrid(sphereGridSize, (a, b, c, d) => {
var idx = verts.Count;
var n = Vector3.Cross(c - a, b - a).normalized;
verts.Add(a * radius);
verts.Add(b * radius);
verts.Add(c * radius);
verts.Add(d * radius);
normals.Add(n);
normals.Add(n);
normals.Add(n);
normals.Add(n);
indexes.Add(idx + 0);
indexes.Add(idx + 2);
indexes.Add(idx + 1);
indexes.Add(idx + 1);
indexes.Add(idx + 2);
indexes.Add(idx + 3);
});
sphereMesh.Clear();
sphereMesh.subMeshCount = 1;
sphereMesh.SetVertices(verts);
sphereMesh.SetNormals(normals);
sphereMesh.SetTriangles(indexes, 0);
sphereRenderer.sharedMaterial = meshMaterial;
}
static readonly float sq3 = Mathf.Sqrt(1.0f/3);
static Vector3 getPoint(float u, float v, Vector3 xVec, Vector3 yVec, Vector3 zVec, Vector3 posVec){
var xMinTop = new Vector3(-sq3, sq3, sq3);
var xMinBottom = new Vector3(-sq3, -sq3, sq3);
var xMaxTop = new Vector3(sq3, sq3, sq3);
var xMaxBottom = new Vector3(sq3, -sq3, sq3);
//slerp
var xMin = Vector3.Slerp(xMinTop, xMinBottom, v);
var xMax = Vector3.Slerp(xMaxTop, xMaxBottom, v);
var result = Vector3.Slerp(xMin, xMax, u);
/*
//lerp
var xMin = Vector3.Lerp(xMinTop, xMinBottom, v);
var xMax = Vector3.Lerp(xMaxTop, xMaxBottom, v);
var result = Vector3.Lerp(xMin, xMax, u).normalized;
*/
return posVec + result.x * xVec + result.y * yVec + result.z * zVec;
}
static void processFaceGrid(Vector3 xVec, Vector3 yVec, Vector3 zVec, int gridSize, QuadCallback callback){
float valStep = 1.0f/gridSize;//Mathf.PI/(2.0f*gridSize);
float valStart = 0.0f;//-Mathf.PI*0.25f;
for(int x = 0; x < gridSize; x++){
float u1 = valStart + valStep * x;
float u2 = valStart + valStep * (x + 1);
for (int y = 0; y < gridSize; y++){
float v1 = valStart + valStep * y;
float v2 = valStart + valStep * (y + 1);
var p1 = getPoint(u1, v1, xVec, yVec, zVec, Vector3.zero);
var p2 = getPoint(u2, v1, xVec, yVec, zVec, Vector3.zero);
var p3 = getPoint(u1, v2, xVec, yVec, zVec, Vector3.zero);
var p4 = getPoint(u2, v2, xVec, yVec, zVec, Vector3.zero);
callback(p1, p2, p3, p4);
/*Gizmos.DrawLine(p1, p2);
Gizmos.DrawLine(p3, p4);
Gizmos.DrawLine(p1, p3);
Gizmos.DrawLine(p2, p4);*/
}
}
}
static void processSphereGrid(int gridSize, QuadCallback cb){
processFaceGrid(Vector3.right, Vector3.up, Vector3.forward, gridSize, cb);
processFaceGrid(-Vector3.right, Vector3.up, -Vector3.forward, gridSize, cb);
processFaceGrid(Vector3.forward, Vector3.up, -Vector3.right, gridSize, cb);
processFaceGrid(-Vector3.forward, Vector3.up, Vector3.right, gridSize, cb);
processFaceGrid(Vector3.right, -Vector3.forward, Vector3.up, gridSize, cb);
processFaceGrid(-Vector3.right, -Vector3.forward, -Vector3.up, gridSize, cb);
}
void drawSphereGrid(){
if (!showSphereGrid)
return;
Gizmos.color = Color.green;
var offset = transform.up * radius * 2.0f - transform.right * radius;
processSphereGrid(sphereGridSize, (a, b, c, d) => {
var a1 = transform.TransformPoint(a * radius) + offset;
var b1 = transform.TransformPoint(b * radius) + offset;
var c1 = transform.TransformPoint(c * radius) + offset;
var d1 = transform.TransformPoint(d * radius) + offset;
Gizmos.DrawLine(a1, b1);
Gizmos.DrawLine(b1, c1);
Gizmos.DrawLine(c1, d1);
Gizmos.DrawLine(d1, a1);
});
}
static void processSphereTriangle(Vector3 a, Vector3 b, Vector3 c, int numSubdivisions, TriangleCallback callback){
if (numSubdivisions <= 1){
callback(a, b, c);
return;
}
else{
var ab = ((a + b) * 0.5f).normalized;
var ac = ((a + c) * 0.5f).normalized;
var bc = ((b + c) * 0.5f).normalized;
var nextSubdiv = numSubdivisions-1;
processSphereTriangle(a, ab, ac, nextSubdiv, callback);
processSphereTriangle(ab, b, bc, nextSubdiv, callback);
processSphereTriangle(bc, c, ac, nextSubdiv, callback);
processSphereTriangle(ab, bc, ac, nextSubdiv, callback);
}
}
public delegate void TriangleCallback(Vector3 a, Vector3 b, Vector3 c);
public delegate void QuadCallback(Vector3 a, Vector3 b, Vector3 c, Vector3 d);
static void processTetraGrid(int numSubdivs, TriangleCallback callback){
var f2 = 1.0f/Mathf.Sqrt(2.0f);
var a = new Vector3(1.0f, 0.0f, -f2);
var b = new Vector3(-1.0f, 0.0f, -f2);
var c = new Vector3(0.0f, 1.0f, f2);
var d = new Vector3(0.0f, -1.0f, f2);
a = a.normalized;
b = b.normalized;
c = c.normalized;
d = d.normalized;
processSphereTriangle(a, b, c, numSubdivs, callback);
processSphereTriangle(a, d, b, numSubdivs, callback);
processSphereTriangle(a, c, d, numSubdivs, callback);
processSphereTriangle(b, d, c, numSubdivs, callback);
}
void drawTetraGrid(){
if (!showTetraGrid)
return;
var offset = transform.up * radius * 2.0f + transform.right * radius;
Gizmos.color = Color.yellow;
processTetraGrid(numTetraSubdivisions, (a, b, c) =>{
var a1 = transform.TransformPoint(a * radius) + offset;
var b1 = transform.TransformPoint(b * radius) + offset;
var c1 = transform.TransformPoint(c * radius) + offset;
Gizmos.DrawLine(a1, b1);
Gizmos.DrawLine(a1, c1);
Gizmos.DrawLine(b1, c1);
});
}
void OnDrawGizmos(){
drawSphereGrid();
drawTetraGrid();
}
void OnEnable(){
rebuildMeshes();
}
void OnDisable(){
killObjects();
}
void Update(){
if ((lastRadius == radius) && (lastGridSize == sphereGridSize)
&& (lastSubdivisions == numTetraSubdivisions))
return;
lastRadius = radius;
lastGridSize = sphereGridSize;
lastSubdivisions = numTetraSubdivisions;
rebuildMeshes();
}
}
Distortion of quads near corners can be minimized, to extent, but in turn it can make determining sector coordinates more difficult.
As far as I can tell, a good idea in general would be to determine quad based sector, put its center to origin, and align one of the sides with, say, global X coordinate.