//
// 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 UnityEngine;
using System.Collections.Generic;
namespace DigitalRuby.ThunderAndLightning
{
///
/// Script that generates lightning on the surface of a mesh
///
public class LightningMeshSurfaceScript : LightningBoltPrefabScriptBase
{
/// The mesh filter. You must assign a mesh filter in order to create lightning on the mesh.
[Header("Lightning Mesh Properties")]
[Tooltip("The mesh filter. You must assign a mesh filter in order to create lightning on the mesh.")]
public MeshFilter MeshFilter;
/// Random range that the point will offset from the mesh, using the normal of the chosen point to offset
[SingleLine("Random range that the point will offset from the mesh, using the normal of the chosen point to offset")]
public RangeOfFloats MeshOffsetRange = new RangeOfFloats { Minimum = 0.5f, Maximum = 1.0f };
/// Range for points in the lightning path
[Header("Lightning Path Properties")]
[SingleLine("Range for points in the lightning path")]
public RangeOfIntegers PathLengthCount = new RangeOfIntegers { Minimum = 3, Maximum = 6 };
/// Range for minimum distance between points in the lightning path
[SingleLine("Range for minimum distance between points in the lightning path")]
public RangeOfFloats MinimumPathDistanceRange = new RangeOfFloats { Minimum = 0.5f, Maximum = 1.0f };
/// The maximum distance between mesh points. When walking the mesh, if a point is greater than this, the path direction is reversed. This tries to avoid paths crossing between mesh points that are not actually physically touching.
[Tooltip("The maximum distance between mesh points. When walking the mesh, if a point is greater than this, the path direction is reversed. " +
"This tries to avoid paths crossing between mesh points that are not actually physically touching.")]
public float MaximumPathDistance = 2.0f;
private float maximumPathDistanceSquared;
/// Whether to use spline interpolation between the path points. Paths must be at least 4 points long to be splined.
[Tooltip("Whether to use spline interpolation between the path points. Paths must be at least 4 points long to be splined.")]
public bool Spline = false;
/// For spline. the distance hint for each spline segment. Set to <= 0 to use the generations to determine how many spline segments to use. If > 0, it will be divided by Generations before being applied. This value is a guideline and is approximate, and not uniform on the spline.
[Tooltip("For spline. the distance hint for each spline segment. Set to <= 0 to use the generations to determine how many spline segments to use. " +
"If > 0, it will be divided by Generations before being applied. This value is a guideline and is approximate, and not uniform on the spline.")]
public float DistancePerSegmentHint = 0.0f;
private readonly List sourcePoints = new List();
private Mesh previousMesh;
private MeshHelper meshHelper;
private void CheckMesh()
{
if (MeshFilter == null || MeshFilter.sharedMesh == null)
{
meshHelper = null;
}
else if (MeshFilter.sharedMesh != previousMesh)
{
if (previousMesh != null && !previousMesh.isReadable)
{
Debug.LogError("Mesh is not readable, cannot create lightning on mesh");
}
else
{
previousMesh = MeshFilter.sharedMesh;
meshHelper = new MeshHelper(previousMesh);
#if DEBUG
if (previousMesh.GetTopology(0) != MeshTopology.Triangles)
{
Debug.LogError("Mesh topology must be triangles");
}
#endif
}
}
}
///
/// Create lightning bolt path parameters
///
/// Lightning bolt path parameters
protected override LightningBoltParameters OnCreateParameters()
{
LightningBoltParameters p = base.OnCreateParameters();
p.Generator = LightningGeneratorPath.PathGeneratorInstance;
return p;
}
///
/// Populate the points for a lightning path. This implementation simply picks a random point and then spreads out in random directions along the mesh.
///
/// Points for the path to be filled in. Does not need to be cleared.
protected virtual void PopulateSourcePoints(List points)
{
if (meshHelper != null)
{
CreateRandomLightningPath(sourcePoints);
}
}
///
/// Gets a path for lightning starting at a random point on the mesh
///
/// Points list to receive points for the path
public void CreateRandomLightningPath(List points)
{
if (meshHelper == null)
{
return;
}
// we want a path of at least 2 triangles
RaycastHit hit = new RaycastHit();
int triangleIndex;
maximumPathDistanceSquared = MaximumPathDistance * MaximumPathDistance;
meshHelper.GenerateRandomPoint(ref hit, out triangleIndex);
hit.distance = UnityEngine.Random.Range(MeshOffsetRange.Minimum, MeshOffsetRange.Maximum);
Vector3 prevPoint = hit.point + (hit.normal * hit.distance);
float pathDistanceSquared = UnityEngine.Random.Range(MinimumPathDistanceRange.Minimum, MinimumPathDistanceRange.Maximum);
pathDistanceSquared *= pathDistanceSquared;
sourcePoints.Add(MeshFilter.transform.TransformPoint(prevPoint));
int dir = (UnityEngine.Random.Range(0, 1) == 1 ? 3 : -3);
int pathLength = UnityEngine.Random.Range(PathLengthCount.Minimum, PathLengthCount.Maximum);
while (pathLength > 0)
{
triangleIndex += dir;
if (triangleIndex >= 0 && triangleIndex < meshHelper.Triangles.Length)
{
meshHelper.GetRaycastFromTriangleIndex(triangleIndex, ref hit);
}
else
{
dir = -dir;
triangleIndex += dir;
pathLength--;
continue;
}
hit.distance = UnityEngine.Random.Range(MeshOffsetRange.Minimum, MeshOffsetRange.Maximum);
Vector3 hitPoint = hit.point + (hit.normal * hit.distance);
float distanceSquared = (hitPoint - prevPoint).sqrMagnitude;
if (distanceSquared > maximumPathDistanceSquared)
{
break;
}
else if (distanceSquared >= pathDistanceSquared)
{
prevPoint = hitPoint;
sourcePoints.Add(MeshFilter.transform.TransformPoint(hitPoint));
pathLength--;
pathDistanceSquared = UnityEngine.Random.Range(MinimumPathDistanceRange.Minimum, MinimumPathDistanceRange.Maximum);
pathDistanceSquared *= pathDistanceSquared;
}
}
}
///
/// Start
///
protected override void Start()
{
base.Start();
}
///
/// Update
///
protected override void Update()
{
if (Time.timeScale > 0.0f)
{
CheckMesh();
}
base.Update();
}
///
/// Create a lightning bolt
///
/// Parameters
public override void CreateLightningBolt(LightningBoltParameters parameters)
{
if (meshHelper == null)
{
return;
}
Generations = parameters.Generations = Mathf.Clamp(Generations, 1, LightningSplineScript.MaxSplineGenerations);
sourcePoints.Clear();
PopulateSourcePoints(sourcePoints);
if (sourcePoints.Count > 1)
{
parameters.Points.Clear();
if (Spline && sourcePoints.Count > 3)
{
LightningSplineScript.PopulateSpline(parameters.Points, sourcePoints, Generations, DistancePerSegmentHint, Camera);
parameters.SmoothingFactor = (parameters.Points.Count - 1) / sourcePoints.Count;
}
else
{
parameters.Points.AddRange(sourcePoints);
parameters.SmoothingFactor = 1;
}
base.CreateLightningBolt(parameters);
}
}
}
}