Select Git revision
IngameUI_OnOffButton_mobile.cs
ShapeGenerator.cs 7.17 KiB
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using UnityEngine;
public abstract class ShapeGenerator : MonoBehaviour
{
#region InspectorVariables
[Header("Parts")]
public List<MeshFilter> Meshs;
public List<float> NormalOffset;
public bool AlternateNormals = false;
#endregion InspectorVariables
#region UnityMethods
void Awake() => GenerateShapeForAll();
#if UNITY_EDITOR
void OnValidate()
{
// prevent 'SendMessage cannot be called during Awake, CheckConsistency, or OnValidate' warning
UnityEditor.EditorApplication.delayCall += GenerateShapeForAll;
}
#endif
#endregion UnityMethods
protected void GenerateShapeForAll()
{
float[] _NormalOffset = new float[Meshs.Count];
NormalOffset?.CopyTo(0, _NormalOffset, 0
, math.min(NormalOffset.Count, Meshs.Count));
(Vector3[] vertices, int[] triangles) topology =
GenerateTopology();
if (topology.vertices.Length == 0)
return;
for (int i = 0; i < Meshs.Count; i++)
{
MeshFilter filter = Meshs[i];
if (filter == null)
continue;
Mesh mesh = CreateMesh(topology);
Vector3[] normals = AlternateNormals
? GetUnweightedNormals(mesh)
: mesh.normals;
mesh.SetVertices(
mesh.vertices
.Zip(normals, (v, n)
=> v + n * _NormalOffset[i])
.ToList()
);
if (filter.sharedMesh != null)
filter.sharedMesh.Clear();
filter.sharedMesh = mesh;
if (filter.transform.TryGetComponent(out MeshCollider meshCol))
meshCol.sharedMesh = filter.sharedMesh;
}
}
protected abstract (Vector3[] vertices, int[] triangles) GenerateTopology();
#region Helper
protected static Mesh CreateMesh((Vector3[] vertices, int[] triangles) meshValues)
{
Mesh mesh = new()
{
vertices = meshValues.vertices,
triangles = meshValues.triangles
};
mesh.Optimize();
mesh.RecalculateNormals(); //fix lighting
return mesh;
}
protected static Vector3[] GetCirclePoints(float circleRadius, int pointCount)
=> GetCirclePoints(circleRadius, pointCount, Vector3.zero);
protected static Vector3[] GetCirclePoints(float circleRadius, int pointCount, Vector3 offset)
{
Vector3[] circle = new Vector3[pointCount];
float slice = (2f * Mathf.PI) / pointCount;
for (int i = 0; i < pointCount; i++)
{
float rad_angle = i * slice;
circle[i] =
new Vector3(
circleRadius * Mathf.Sin(rad_angle),
0,
circleRadius * Mathf.Cos(rad_angle)
) + offset;
}
return circle;
}
/// <summary>
/// Creates triangles for a set of vertecies of a flat, convex shape
/// </summary>
/// <param name="points"></param>
/// <param name="invert"></param>
/// <returns></returns>
protected static (Vector3[] vertices, int[] triangles) CreatePlane(Vector3[] vertices, bool invert = false)
{
if (vertices.Length < 3)
return (vertices, new int[0]);
int[] triangles = new int[(vertices.Length - 2) * 3];
for (int i = 1; i < vertices.Length - 1; i++)
{
triangles[(i - 1) * 3 + 0] = 0;
triangles[(i - 1) * 3 + 1] = i;
triangles[(i - 1) * 3 + 2] = (i + 1);
}
return (vertices, invert ? triangles.Reverse().ToArray() : triangles);
}
protected static (Vector3[] vertices, int[] triangles) CreatePrism(
(Vector3[] vertices, int[] triangles) top,
(Vector3[] vertices, int[] triangles) bottom,
int[] boundary_top,
int[] boundary_bottom
)
{
boundary_top ??= Enumerable.Range(0, top.vertices.Length).ToArray();
boundary_bottom ??= Enumerable.Range(0, bottom.vertices.Length).ToArray();
if (boundary_top.Length != boundary_bottom.Length
&& !(boundary_top.Length == 1 ^ boundary_bottom.Length == 1))
Debug.LogWarning("Arguments of different Size; Resulting mesh may be weird!");
int max = math.max(boundary_top.Length, boundary_bottom.Length);
int tri_sum = top.triangles.Length + bottom.triangles.Length;
Vector3[] new_vertices = top.vertices.ShallowCloneAppend(bottom.vertices);
int[] new_triangles = new int[tri_sum + 2 * max * 3];
top.triangles.CopyTo(new_triangles, 0);
bottom.triangles
.Select(i => i + top.vertices.Length)
.ToArray()
.CopyTo(new_triangles, top.triangles.Length);
for (int i = 0; i < max; i++)
{
if (boundary_top.Length > 1)
{
new_triangles[tri_sum + i * 6 + 0] = boundary_top[(i + 1) % boundary_top.Length];
new_triangles[tri_sum + i * 6 + 1] = boundary_top[i % boundary_top.Length];
new_triangles[tri_sum + i * 6 + 2] =
top.vertices.Length + boundary_bottom[i % boundary_bottom.Length];
}
if (boundary_bottom.Length > 1)
{
new_triangles[tri_sum + i * 6 + 3] =
top.vertices.Length + boundary_bottom[i % boundary_bottom.Length];
new_triangles[tri_sum + i * 6 + 4] =
top.vertices.Length + boundary_bottom[(i + 1) % boundary_bottom.Length];
new_triangles[tri_sum + i * 6 + 5] = boundary_top[(i + 1) % boundary_top.Length];
}
}
return (new_vertices, new_triangles);
}
// Artefacts appear when mesh is unbalanced (e.g. many edges at one vert)
public static Vector3[] GetUnweightedNormals(Mesh mesh)
{
int[] triangles = mesh.triangles;
Vector3[] verts = mesh.vertices;
Vector3[] normals = new Vector3[mesh.vertexCount];
int[] weightsum = new int[mesh.vertexCount];
HashSet<int>[] deltas = new HashSet<int>[mesh.vertexCount];
for (int i = 0; i < deltas.Length; i++)
deltas[i] = new();
for (int i = 0; i < triangles.Length; i += 3)
{
AddEdge(triangles[i + 0], triangles[i + 1]);
AddEdge(triangles[i + 0], triangles[i + 2]);
AddEdge(triangles[i + 1], triangles[i + 0]);
AddEdge(triangles[i + 1], triangles[i + 2]);
AddEdge(triangles[i + 2], triangles[i + 0]);
AddEdge(triangles[i + 2], triangles[i + 1]);
}
for (int i = 0; i < normals.Length; i++)
normals[i] = (normals[i] / weightsum[i]).normalized;
return normals;
void AddEdge(int this_id, int other_id)
{
if (deltas[this_id].Add(other_id))
{
Vector3 delta = (verts[this_id] - verts[other_id]).normalized;
if (Math3d.IsApproximatelyEqual(delta, Vector3.zero))
return;
normals[this_id] += delta;
weightsum[this_id]++;
}
}
}
#endregion Helper
}