//
// Procedural Lightning for Unity
// (c) 2015 Digital Ruby, LLC
// Source code may be used for personal or commercial projects.
// Source code may NOT be redistributed or sold.
//
using System.Collections.Generic;
using UnityEngine;
namespace DigitalRuby.ThunderAndLightning
{
///
/// Mesh helper methods
///
public class MeshHelper
{
private Mesh mesh;
private int[] triangles;
private Vector3[] vertices;
private Vector3[] normals;
private float[] normalizedAreaWeights;
///
/// Constructor
///
/// Mesh
public MeshHelper(Mesh mesh)
{
this.mesh = mesh;
this.triangles = mesh.triangles;
this.vertices = mesh.vertices;
this.normals = mesh.normals;
CalculateNormalizedAreaWeights();
}
///
/// Get random point on mesh
///
/// Raycast
/// Received indice
public void GenerateRandomPoint(ref RaycastHit hit, out int triangleIndex)
{
triangleIndex = SelectRandomTriangle();
GetRaycastFromTriangleIndex(triangleIndex, ref hit);
}
///
/// Get ray dir from indice
///
/// Indice
/// Raycast result
public void GetRaycastFromTriangleIndex(int triangleIndex, ref RaycastHit hit)
{
Vector3 bc = GenerateRandomBarycentricCoordinates();
Vector3 p1 = vertices[triangles[triangleIndex]];
Vector3 p2 = vertices[triangles[triangleIndex + 1]];
Vector3 p3 = vertices[triangles[triangleIndex + 2]];
hit.barycentricCoordinate = bc;
hit.point = ((p1 * bc.x) + (p2 * bc.y) + (p3 * bc.z));
if (normals == null)
{
// face normal
hit.normal = Vector3.Cross((p3 - p2), (p1 - p2)).normalized;
}
else
{
// interpolated vertex normal
p1 = normals[triangles[triangleIndex]];
p2 = normals[triangles[triangleIndex + 1]];
p3 = normals[triangles[triangleIndex + 2]];
hit.normal = (p1 * bc.x) + (p2 * bc.y) + (p3 * bc.z);
}
}
///
/// Mesh
///
public Mesh Mesh
{
get { return mesh; }
}
///
/// Indices
///
public int[] Triangles
{
get { return triangles; }
}
///
/// Vertices
///
public Vector3[] Vertices
{
get { return vertices; }
}
///
/// Normals
///
public Vector3[] Normals
{
get { return normals; }
}
private float[] CalculateSurfaceAreas(out float totalSurfaceArea)
{
int idx = 0;
totalSurfaceArea = 0.0f;
float[] surfaceAreas = new float[triangles.Length / 3];
for (int triangleIndex = 0; triangleIndex < triangles.Length; triangleIndex += 3)
{
Vector3 p1 = vertices[triangles[triangleIndex]];
Vector3 p2 = vertices[triangles[triangleIndex + 1]];
Vector3 p3 = vertices[triangles[triangleIndex + 2]];
// http://www.wikihow.com/Sample/Area-of-a-Triangle-Side-Length
float a = (p1 - p2).sqrMagnitude;
float b = (p1 - p3).sqrMagnitude;
float c = (p2 - p3).sqrMagnitude;
// faster with only 1 square root: http://www.iquilezles.org/blog/?p=1579
// A² = (2ab + 2bc + 2ca – a² – b² – c²)/16
float areaSquared = ((2.0f * a * b) + (2.0f * b * c) + (2.0f * c * a) - (a * a) - (b * b) - (c * c)) / 16.0f;
float area = PathGenerator.SquareRoot(areaSquared);
surfaceAreas[idx++] = area;
totalSurfaceArea += area;
}
return surfaceAreas;
}
private void CalculateNormalizedAreaWeights()
{
// create a sorted array of normalized area weights - this is an aggregate and is easily binary searched with a random value between 0 and 1 to find
// a random triangle. Larger triangles have bigger gaps in the array.
float totalSurfaceArea;
normalizedAreaWeights = CalculateSurfaceAreas(out totalSurfaceArea);
if (normalizedAreaWeights.Length == 0)
{
return;
}
float normalizedArea;
float normalizedAggregate = 0.0f;
for (int i = 0; i < normalizedAreaWeights.Length; i++)
{
normalizedArea = normalizedAreaWeights[i] / totalSurfaceArea;
normalizedAreaWeights[i] = normalizedAggregate;
normalizedAggregate += normalizedArea;
}
}
private int SelectRandomTriangle()
{
float randomValue = Random.value;
int imin = 0;
int imax = normalizedAreaWeights.Length - 1;
while (imin < imax)
{
int imid = (imin + imax) / 2;
if (normalizedAreaWeights[imid] < randomValue)
{
imin = imid + 1;
}
else
{
imax = imid;
}
}
return imin * 3;
}
private Vector3 GenerateRandomBarycentricCoordinates()
{
Vector3 barycentric = new Vector3(Random.Range(Mathf.Epsilon, 1.0f), Random.Range(Mathf.Epsilon, 1.0f), Random.Range(Mathf.Epsilon, 1.0f));
// normalize the barycentric coordinates. These are normalized such that x + y + z = 1, as opposed to
// normal vectors which are normalized such that Sqrt(x^2 + y^2 + z^2) = 1. See:
// http://en.wikipedia.org/wiki/Barycentric_coordinate_system
return barycentric / (barycentric.x + barycentric.y + barycentric.z);
}
}
}