LightningGenerator.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. //
  2. // Procedural Lightning for Unity
  3. // (c) 2015 Digital Ruby, LLC
  4. // Source code may be used for personal or commercial projects.
  5. // Source code may NOT be redistributed or sold.
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using UnityEngine;
  12. namespace DigitalRuby.ThunderAndLightning
  13. {
  14. /// <summary>
  15. /// Lightning generator base class
  16. /// </summary>
  17. public class LightningGenerator
  18. {
  19. internal const float oneOver255 = 1.0f / 255.0f;
  20. internal const float mainTrunkMultiplier = 255.0f * oneOver255 * oneOver255;
  21. private void GetPerpendicularVector(ref Vector3 directionNormalized, out Vector3 side)
  22. {
  23. if (directionNormalized == Vector3.zero)
  24. {
  25. side = Vector3.right;
  26. }
  27. else
  28. {
  29. // use cross product to find any perpendicular vector around directionNormalized:
  30. // 0 = x * px + y * py + z * pz
  31. // => pz = -(x * px + y * py) / z
  32. // for computational stability use the component farthest from 0 to divide by
  33. float x = directionNormalized.x;
  34. float y = directionNormalized.y;
  35. float z = directionNormalized.z;
  36. float px, py, pz;
  37. float ax = Mathf.Abs(x), ay = Mathf.Abs(y), az = Mathf.Abs(z);
  38. if (ax >= ay && ay >= az)
  39. {
  40. // x is the max, so we can pick (py, pz) arbitrarily at (1, 1):
  41. py = 1.0f;
  42. pz = 1.0f;
  43. px = -(y * py + z * pz) / x;
  44. }
  45. else if (ay >= az)
  46. {
  47. // y is the max, so we can pick (px, pz) arbitrarily at (1, 1):
  48. px = 1.0f;
  49. pz = 1.0f;
  50. py = -(x * px + z * pz) / y;
  51. }
  52. else
  53. {
  54. // z is the max, so we can pick (px, py) arbitrarily at (1, 1):
  55. px = 1.0f;
  56. py = 1.0f;
  57. pz = -(x * px + y * py) / z;
  58. }
  59. side = new Vector3(px, py, pz).normalized;
  60. }
  61. }
  62. /// <summary>
  63. /// Fires when a lightning bolt needs to be generated
  64. /// </summary>
  65. /// <param name="bolt">Lightning bolt</param>
  66. /// <param name="start">Start position</param>
  67. /// <param name="end">End position</param>
  68. /// <param name="parameters">Parameters</param>
  69. protected virtual void OnGenerateLightningBolt(LightningBolt bolt, Vector3 start, Vector3 end, LightningBoltParameters parameters)
  70. {
  71. GenerateLightningBoltStandard(bolt, start, end, parameters.Generations, parameters.Generations, 0.0f, parameters);
  72. }
  73. /// <summary>
  74. /// Determines if a fork should be created
  75. /// </summary>
  76. /// <param name="parameters">Parameters</param>
  77. /// <param name="generation">Generation</param>
  78. /// <param name="totalGenerations">Max generation</param>
  79. /// <returns>True if fork should be created, false otherwise</returns>
  80. public bool ShouldCreateFork(LightningBoltParameters parameters, int generation, int totalGenerations)
  81. {
  82. return (generation > parameters.generationWhereForksStop && generation >= totalGenerations - parameters.forkednessCalculated && (float)parameters.Random.NextDouble() < parameters.Forkedness);
  83. }
  84. /// <summary>
  85. /// Create a fork in a lightning bolt
  86. /// </summary>
  87. /// <param name="bolt">Lightning bolt</param>
  88. /// <param name="parameters">Parameters</param>
  89. /// <param name="generation">Generation</param>
  90. /// <param name="totalGenerations">Max generation</param>
  91. /// <param name="start">Start position</param>
  92. /// <param name="midPoint">Middle position</param>
  93. public void CreateFork(LightningBolt bolt, LightningBoltParameters parameters, int generation, int totalGenerations, Vector3 start, Vector3 midPoint)
  94. {
  95. if (ShouldCreateFork(parameters, generation, totalGenerations))
  96. {
  97. Vector3 branchVector = (midPoint - start) * parameters.ForkMultiplier();
  98. Vector3 splitEnd = midPoint + branchVector;
  99. GenerateLightningBoltStandard(bolt, midPoint, splitEnd, generation, totalGenerations, 0.0f, parameters);
  100. }
  101. }
  102. /// <summary>
  103. /// Generate a normal/standard lightning bolt
  104. /// </summary>
  105. /// <param name="bolt">Lightning bolt</param>
  106. /// <param name="start">Start position</param>
  107. /// <param name="end">End position</param>
  108. /// <param name="generation">Generation</param>
  109. /// <param name="totalGenerations">Max generation</param>
  110. /// <param name="offsetAmount">Offset amount for variance</param>
  111. /// <param name="parameters">Parameters</param>
  112. public void GenerateLightningBoltStandard(LightningBolt bolt, Vector3 start, Vector3 end, int generation, int totalGenerations, float offsetAmount, LightningBoltParameters parameters)
  113. {
  114. if (generation < 1)
  115. {
  116. return;
  117. }
  118. LightningBoltSegmentGroup group = bolt.AddGroup();
  119. group.Segments.Add(new LightningBoltSegment { Start = start, End = end });
  120. // every generation, get the percentage we have gone down and square it, this makes lines thinner
  121. float widthMultiplier = (float)generation / (float)totalGenerations;
  122. widthMultiplier *= widthMultiplier;
  123. Vector3 randomVector;
  124. group.LineWidth = parameters.TrunkWidth * widthMultiplier;
  125. group.Generation = generation;
  126. group.Color = parameters.Color;
  127. if (generation == parameters.Generations &&
  128. (parameters.MainTrunkTintColor.r != 255 || parameters.MainTrunkTintColor.g != 255 || parameters.MainTrunkTintColor.b != 255 || parameters.MainTrunkTintColor.a != 255))
  129. {
  130. group.Color.r = (byte)(mainTrunkMultiplier * (float)group.Color.r * (float)parameters.MainTrunkTintColor.r);
  131. group.Color.g = (byte)(mainTrunkMultiplier * (float)group.Color.g * (float)parameters.MainTrunkTintColor.g);
  132. group.Color.b = (byte)(mainTrunkMultiplier * (float)group.Color.b * (float)parameters.MainTrunkTintColor.b);
  133. group.Color.a = (byte)(mainTrunkMultiplier * (float)group.Color.a * (float)parameters.MainTrunkTintColor.a);
  134. }
  135. group.Color.a = (byte)(255.0f * widthMultiplier);
  136. group.EndWidthMultiplier = parameters.EndWidthMultiplier * parameters.ForkEndWidthMultiplier;
  137. if (offsetAmount <= 0.0f)
  138. {
  139. offsetAmount = (end - start).magnitude * (generation == totalGenerations ? parameters.ChaosFactor : parameters.ChaosFactorForks);
  140. }
  141. while (generation-- > 0)
  142. {
  143. int previousStartIndex = group.StartIndex;
  144. group.StartIndex = group.Segments.Count;
  145. for (int i = previousStartIndex; i < group.StartIndex; i++)
  146. {
  147. start = group.Segments[i].Start;
  148. end = group.Segments[i].End;
  149. // determine a new direction for the split
  150. Vector3 midPoint = (start + end) * 0.5f;
  151. // adjust the mid point to be the new location
  152. RandomVector(bolt, ref start, ref end, offsetAmount, parameters.Random, out randomVector);
  153. midPoint += randomVector;
  154. // add two new segments
  155. group.Segments.Add(new LightningBoltSegment { Start = start, End = midPoint });
  156. group.Segments.Add(new LightningBoltSegment { Start = midPoint, End = end });
  157. CreateFork(bolt, parameters, generation, totalGenerations, start, midPoint);
  158. }
  159. // halve the distance the lightning can deviate for each generation down
  160. offsetAmount *= 0.5f;
  161. }
  162. }
  163. /// <summary>
  164. /// Get a random 3D direction
  165. /// </summary>
  166. /// <param name="random">Random</param>
  167. /// <returns>Random 3D direction vector</returns>
  168. public Vector3 RandomDirection3D(System.Random random)
  169. {
  170. float z = (2.0f * (float)random.NextDouble()) - 1.0f; // z is in the range [-1,1]
  171. Vector3 planar = RandomDirection2D(random) * Mathf.Sqrt(1.0f - (z * z));
  172. planar.z = z;
  173. return planar;
  174. }
  175. /// <summary>
  176. /// Get random 2D direction in XY plane
  177. /// </summary>
  178. /// <param name="random">Random</param>
  179. /// <returns>Random 2D direction</returns>
  180. public Vector3 RandomDirection2D(System.Random random)
  181. {
  182. float azimuth = (float)random.NextDouble() * 2.0f * Mathf.PI;
  183. return new Vector3(Mathf.Cos(azimuth), Mathf.Sin(azimuth), 0.0f);
  184. }
  185. /// <summary>
  186. /// Get random 2D direction in XZ plane
  187. /// </summary>
  188. /// <param name="random">Random</param>
  189. /// <returns>Random 2D direction</returns>
  190. public Vector3 RandomDirection2DXZ(System.Random random)
  191. {
  192. float azimuth = (float)random.NextDouble() * 2.0f * Mathf.PI;
  193. return new Vector3(Mathf.Cos(azimuth), 0.0f, Mathf.Sin(azimuth));
  194. }
  195. /// <summary>
  196. /// Generate a random vector
  197. /// </summary>
  198. /// <param name="bolt">Lightning bolt</param>
  199. /// <param name="start">Start position</param>
  200. /// <param name="end">End position</param>
  201. /// <param name="offsetAmount">Offset amount for variance</param>
  202. /// <param name="random">Random instance</param>
  203. /// <param name="result">Receives random vector</param>
  204. public void RandomVector(LightningBolt bolt, ref Vector3 start, ref Vector3 end, float offsetAmount, System.Random random, out Vector3 result)
  205. {
  206. if (bolt.CameraMode == CameraMode.Perspective)
  207. {
  208. Vector3 direction = (end - start).normalized;
  209. Vector3 side = Vector3.Cross(start, end);
  210. if (side == Vector3.zero)
  211. {
  212. // slow path, rarely hit unless cross product is zero
  213. GetPerpendicularVector(ref direction, out side);
  214. }
  215. else
  216. {
  217. side.Normalize();
  218. }
  219. // generate random distance and angle
  220. float distance = (((float)random.NextDouble() + 0.1f) * offsetAmount);
  221. #if DEBUG
  222. float rotationAngle = ((float)random.NextDouble() * 360.0f);
  223. result = Quaternion.AngleAxis(rotationAngle, direction) * side * distance;
  224. #else
  225. // optimized path for RELEASE mode, skips two normalize and two multiplies in Quaternion.AngleAxis
  226. float rotationAngle = ((float)random.NextDouble() * Mathf.PI);
  227. direction *= (float)System.Math.Sin(rotationAngle);
  228. Quaternion rotation;
  229. rotation.x = direction.x;
  230. rotation.y = direction.y;
  231. rotation.z = direction.z;
  232. rotation.w = (float)System.Math.Cos(rotationAngle);
  233. result = rotation * side * distance;
  234. #endif
  235. }
  236. else if (bolt.CameraMode == CameraMode.OrthographicXY)
  237. {
  238. // XY plane
  239. end.z = start.z;
  240. Vector3 directionNormalized = (end - start).normalized;
  241. Vector3 side = new Vector3(-directionNormalized.y, directionNormalized.x, 0.0f);
  242. float distance = ((float)random.NextDouble() * offsetAmount * 2.0f) - offsetAmount;
  243. result = side * distance;
  244. }
  245. else
  246. {
  247. // XZ plane
  248. end.y = start.y;
  249. Vector3 directionNormalized = (end - start).normalized;
  250. Vector3 side = new Vector3(-directionNormalized.z, 0.0f, directionNormalized.x);
  251. float distance = ((float)random.NextDouble() * offsetAmount * 2.0f) - offsetAmount;
  252. result = side * distance;
  253. }
  254. }
  255. /// <summary>
  256. /// Generate a lightning bolt
  257. /// </summary>
  258. /// <param name="bolt">Lightning bolt</param>
  259. /// <param name="parameters">Parameters</param>
  260. public void GenerateLightningBolt(LightningBolt bolt, LightningBoltParameters parameters)
  261. {
  262. Vector3 start, end;
  263. GenerateLightningBolt(bolt, parameters, out start, out end);
  264. }
  265. /// <summary>
  266. /// Generate a lightning bolt
  267. /// </summary>
  268. /// <param name="bolt">Lightning bolt</param>
  269. /// <param name="parameters">Parameters</param>
  270. /// <param name="start">Start position</param>
  271. /// <param name="end">End position</param>
  272. public void GenerateLightningBolt(LightningBolt bolt, LightningBoltParameters parameters, out Vector3 start, out Vector3 end)
  273. {
  274. start = parameters.ApplyVariance(parameters.Start, parameters.StartVariance);
  275. end = parameters.ApplyVariance(parameters.End, parameters.EndVariance);
  276. OnGenerateLightningBolt(bolt, start, end, parameters);
  277. }
  278. /// <summary>
  279. /// Singleton lightning generator instance
  280. /// </summary>
  281. public static readonly LightningGenerator GeneratorInstance = new LightningGenerator();
  282. }
  283. /// <summary>
  284. /// Generates lightning that follows a path
  285. /// </summary>
  286. public class LightningGeneratorPath : LightningGenerator
  287. {
  288. /// <summary>
  289. /// Singleton path generator
  290. /// </summary>
  291. public static readonly LightningGeneratorPath PathGeneratorInstance = new LightningGeneratorPath();
  292. /// <summary>
  293. /// Generate lightning bolt path
  294. /// </summary>
  295. /// <param name="bolt">Lightning bolt</param>
  296. /// <param name="start">Start</param>
  297. /// <param name="end">End</param>
  298. /// <param name="parameters">Parameters</param>
  299. public void GenerateLightningBoltPath(LightningBolt bolt, Vector3 start, Vector3 end, LightningBoltParameters parameters)
  300. {
  301. if (parameters.Points.Count < 2)
  302. {
  303. Debug.LogError("Lightning path should have at least two points");
  304. return;
  305. }
  306. int generation = parameters.Generations;
  307. int totalGenerations = generation;
  308. float offsetAmount, d;
  309. float chaosFactor = (generation == parameters.Generations ? parameters.ChaosFactor : parameters.ChaosFactorForks);
  310. int smoothingFactor = parameters.SmoothingFactor - 1;
  311. Vector3 distance, randomVector;
  312. LightningBoltSegmentGroup group = bolt.AddGroup();
  313. group.LineWidth = parameters.TrunkWidth;
  314. group.Generation = generation--;
  315. group.EndWidthMultiplier = parameters.EndWidthMultiplier;
  316. group.Color = parameters.Color;
  317. if (generation == parameters.Generations &&
  318. (parameters.MainTrunkTintColor.r != 255 || parameters.MainTrunkTintColor.g != 255 || parameters.MainTrunkTintColor.b != 255 || parameters.MainTrunkTintColor.a != 255))
  319. {
  320. group.Color.r = (byte)(mainTrunkMultiplier * (float)group.Color.r * (float)parameters.MainTrunkTintColor.r);
  321. group.Color.g = (byte)(mainTrunkMultiplier * (float)group.Color.g * (float)parameters.MainTrunkTintColor.g);
  322. group.Color.b = (byte)(mainTrunkMultiplier * (float)group.Color.b * (float)parameters.MainTrunkTintColor.b);
  323. group.Color.a = (byte)(mainTrunkMultiplier * (float)group.Color.a * (float)parameters.MainTrunkTintColor.a);
  324. }
  325. parameters.Start = parameters.Points[0] + start;
  326. parameters.End = parameters.Points[parameters.Points.Count - 1] + end;
  327. end = parameters.Start;
  328. for (int i = 1; i < parameters.Points.Count; i++)
  329. {
  330. start = end;
  331. end = parameters.Points[i];
  332. distance = (end - start);
  333. d = PathGenerator.SquareRoot(distance.sqrMagnitude);
  334. if (chaosFactor > 0.0f)
  335. {
  336. if (bolt.CameraMode == CameraMode.Perspective)
  337. {
  338. end += (d * chaosFactor * RandomDirection3D(parameters.Random));
  339. }
  340. else if (bolt.CameraMode == CameraMode.OrthographicXY)
  341. {
  342. end += (d * chaosFactor * RandomDirection2D(parameters.Random));
  343. }
  344. else
  345. {
  346. end += (d * chaosFactor * RandomDirection2DXZ(parameters.Random));
  347. }
  348. distance = (end - start);
  349. }
  350. group.Segments.Add(new LightningBoltSegment { Start = start, End = end });
  351. offsetAmount = d * chaosFactor;
  352. RandomVector(bolt, ref start, ref end, offsetAmount, parameters.Random, out randomVector);
  353. if (ShouldCreateFork(parameters, generation, totalGenerations))
  354. {
  355. Vector3 branchVector = distance * parameters.ForkMultiplier() * smoothingFactor * 0.5f;
  356. Vector3 forkEnd = end + branchVector + randomVector;
  357. GenerateLightningBoltStandard(bolt, start, forkEnd, generation, totalGenerations, 0.0f, parameters);
  358. }
  359. if (--smoothingFactor == 0)
  360. {
  361. smoothingFactor = parameters.SmoothingFactor - 1;
  362. }
  363. }
  364. }
  365. /// <summary>
  366. /// Fires when lightning bolt needs to be generated
  367. /// </summary>
  368. /// <param name="bolt">Lightning bolt</param>
  369. /// <param name="start">Start position</param>
  370. /// <param name="end">End position</param>
  371. /// <param name="parameters">Parameters</param>
  372. protected override void OnGenerateLightningBolt(LightningBolt bolt, Vector3 start, Vector3 end, LightningBoltParameters parameters)
  373. {
  374. GenerateLightningBoltPath(bolt, start, end, parameters);
  375. }
  376. }
  377. }