Nav apraksta
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

LightUtility.cs 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Collections;
  4. using Unity.Mathematics;
  5. using UnityEngine.U2D;
  6. namespace UnityEngine.Rendering.Universal
  7. {
  8. // Per Light parameters to batch.
  9. struct PerLight2D
  10. {
  11. internal float4x4 InvMatrix;
  12. internal float4 Color;
  13. internal float4 Position;
  14. internal float FalloffIntensity;
  15. internal float FalloffDistance;
  16. internal float OuterAngle;
  17. internal float InnerAngle;
  18. internal float InnerRadiusMult;
  19. internal float VolumeOpacity;
  20. internal float ShadowIntensity;
  21. internal int LightType;
  22. };
  23. internal static class LightUtility
  24. {
  25. public static bool CheckForChange(Light2D.LightType a, ref Light2D.LightType b)
  26. {
  27. var changed = a != b;
  28. b = a;
  29. return changed;
  30. }
  31. public static bool CheckForChange(Component a, ref Component b)
  32. {
  33. var changed = a != b;
  34. b = a;
  35. return changed;
  36. }
  37. public static bool CheckForChange(int a, ref int b)
  38. {
  39. var changed = a != b;
  40. b = a;
  41. return changed;
  42. }
  43. public static bool CheckForChange(float a, ref float b)
  44. {
  45. var changed = a != b;
  46. b = a;
  47. return changed;
  48. }
  49. public static bool CheckForChange(bool a, ref bool b)
  50. {
  51. var changed = a != b;
  52. b = a;
  53. return changed;
  54. }
  55. private enum PivotType
  56. {
  57. PivotBase,
  58. PivotCurve,
  59. PivotIntersect,
  60. PivotSkip,
  61. PivotClip
  62. };
  63. [Serializable]
  64. internal struct LightMeshVertex
  65. {
  66. public Vector3 position;
  67. public Color color;
  68. public Vector2 uv;
  69. public static readonly VertexAttributeDescriptor[] VertexLayout = new[]
  70. {
  71. new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
  72. new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
  73. new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
  74. };
  75. }
  76. static bool TestPivot(List<IntPoint> path, int activePoint, long lastPoint)
  77. {
  78. for (int i = activePoint; i < path.Count; ++i)
  79. {
  80. if (path[i].N > lastPoint)
  81. return true;
  82. }
  83. return (path[activePoint].N == -1);
  84. }
  85. // Degenerate Pivots at the End Points.
  86. static List<IntPoint> DegeneratePivots(List<IntPoint> path, List<IntPoint> inPath, ref int interiorStart)
  87. {
  88. List<IntPoint> degenerate = new List<IntPoint>();
  89. var minN = path[0].N;
  90. var maxN = path[0].N;
  91. for (int i = 1; i < path.Count; ++i)
  92. {
  93. if (path[i].N != -1)
  94. {
  95. minN = Math.Min(minN, path[i].N);
  96. maxN = Math.Max(maxN, path[i].N);
  97. }
  98. }
  99. for (long i = 0; i < minN; ++i)
  100. {
  101. IntPoint ins = path[(int)minN];
  102. ins.N = i;
  103. degenerate.Add(ins);
  104. }
  105. degenerate.AddRange(path.GetRange(0, path.Count));
  106. interiorStart = degenerate.Count;
  107. for (long i = maxN + 1; i < inPath.Count; ++i)
  108. {
  109. IntPoint ins = inPath[(int)i];
  110. ins.N = i;
  111. degenerate.Add(ins);
  112. }
  113. return degenerate;
  114. }
  115. // Ensure that we get a valid path from 0.
  116. static List<IntPoint> SortPivots(List<IntPoint> outPath, List<IntPoint> inPath)
  117. {
  118. List<IntPoint> sorted = new List<IntPoint>();
  119. var min = outPath[0].N;
  120. var max = outPath[0].N;
  121. var minIndex = 0;
  122. bool newMin = true;
  123. for (int i = 1; i < outPath.Count; ++i)
  124. {
  125. if (max > outPath[i].N && newMin && outPath[i].N != -1)
  126. {
  127. min = max = outPath[i].N;
  128. minIndex = i;
  129. newMin = false;
  130. }
  131. else if (outPath[i].N >= max)
  132. {
  133. max = outPath[i].N;
  134. newMin = true;
  135. }
  136. }
  137. sorted.AddRange(outPath.GetRange(minIndex, (outPath.Count - minIndex)));
  138. sorted.AddRange(outPath.GetRange(0, minIndex));
  139. return sorted;
  140. }
  141. // Ensure that all points eliminated due to overlaps and intersections are accounted for Tessellation.
  142. static List<IntPoint> FixPivots(List<IntPoint> outPath, List<IntPoint> inPath, ref int interiorStart)
  143. {
  144. var path = SortPivots(outPath, inPath);
  145. long pivotPoint = path[0].N;
  146. // Connect Points for Overlaps.
  147. for (int i = 1; i < path.Count; ++i)
  148. {
  149. var j = (i == path.Count - 1) ? 0 : (i + 1);
  150. var prev = path[i - 1];
  151. var curr = path[i];
  152. var next = path[j];
  153. if (prev.N > curr.N)
  154. {
  155. var incr = TestPivot(path, i, pivotPoint);
  156. if (incr)
  157. {
  158. if (prev.N == next.N)
  159. curr.N = prev.N;
  160. else
  161. curr.N = (pivotPoint + 1) < inPath.Count ? (pivotPoint + 1) : 0;
  162. curr.D = 3;
  163. path[i] = curr;
  164. }
  165. }
  166. pivotPoint = path[i].N;
  167. }
  168. // Insert Skipped Points.
  169. for (int i = 1; i < path.Count - 1;)
  170. {
  171. var prev = path[i - 1];
  172. var curr = path[i];
  173. var next = path[i + 1];
  174. if (curr.N - prev.N > 1)
  175. {
  176. if (curr.N == next.N)
  177. {
  178. IntPoint ins = curr;
  179. ins.N = (ins.N - 1);
  180. path[i] = ins;
  181. }
  182. else
  183. {
  184. IntPoint ins = curr;
  185. ins.N = (ins.N - 1);
  186. path.Insert(i, ins);
  187. }
  188. }
  189. else
  190. {
  191. i++;
  192. }
  193. }
  194. path = DegeneratePivots(path, inPath, ref interiorStart);
  195. return path;
  196. }
  197. // Rough shape only used in Inspector for quick preview.
  198. internal static List<Vector2> GetOutlinePath(Vector3[] shapePath, float offsetDistance)
  199. {
  200. const float kClipperScale = 10000.0f;
  201. List<IntPoint> path = new List<IntPoint>();
  202. List<Vector2> output = new List<Vector2>();
  203. for (var i = 0; i < shapePath.Length; ++i)
  204. {
  205. var newPoint = new Vector2(shapePath[i].x, shapePath[i].y) * kClipperScale;
  206. path.Add(new IntPoint((System.Int64)(newPoint.x), (System.Int64)(newPoint.y)));
  207. }
  208. List<List<IntPoint>> solution = new List<List<IntPoint>>();
  209. ClipperOffset clipOffset = new ClipperOffset(24.0f);
  210. clipOffset.AddPath(path, JoinTypes.jtRound, EndTypes.etClosedPolygon);
  211. clipOffset.Execute(ref solution, kClipperScale * offsetDistance, path.Count);
  212. if (solution.Count > 0)
  213. {
  214. int interiorStart = 0;
  215. var outPath = solution[0];
  216. outPath = FixPivots(outPath, path, ref interiorStart);
  217. for (int i = 0; i < outPath.Count; ++i)
  218. output.Add(new Vector2(outPath[i].X / kClipperScale, outPath[i].Y / kClipperScale));
  219. }
  220. return output;
  221. }
  222. static void TransferToMesh(NativeArray<LightMeshVertex> vertices, int vertexCount, NativeArray<ushort> indices,
  223. int indexCount, Light2D light)
  224. {
  225. var mesh = light.lightMesh;
  226. mesh.SetVertexBufferParams(vertexCount, LightMeshVertex.VertexLayout);
  227. mesh.SetVertexBufferData(vertices, 0, 0, vertexCount);
  228. mesh.SetIndices(indices, 0, indexCount, MeshTopology.Triangles, 0, true);
  229. light.vertices = new LightMeshVertex[vertexCount];
  230. NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertexCount);
  231. light.indices = new ushort[indexCount];
  232. NativeArray<ushort>.Copy(indices, light.indices, indexCount);
  233. }
  234. public static Bounds GenerateShapeMesh(Light2D light, Vector3[] shapePath, float falloffDistance, float batchColor)
  235. {
  236. const float kClipperScale = 10000.0f;
  237. var restoreState = Random.state;
  238. Random.InitState(123456); // for deterministic output
  239. // todo Revisit this while we do Batching.
  240. var meshInteriorColor = new Color(0, 0, batchColor, 1.0f);
  241. var meshExteriorColor = new Color(0, 0, batchColor, 0.0f);
  242. // Create shape geometry based on edges
  243. int inEdgeCount = shapePath.Length;
  244. NativeArray<int2> tessInEdges = new NativeArray<int2>(inEdgeCount, Allocator.Temp);
  245. NativeArray<float2> tessInVertices = new NativeArray<float2>(inEdgeCount, Allocator.Temp);
  246. for (int i = 0; i < inEdgeCount; ++i)
  247. {
  248. int edgeEnd = i + 1;
  249. if (edgeEnd == inEdgeCount)
  250. edgeEnd = 0;
  251. int2 edge = new int2(i, edgeEnd);
  252. tessInEdges[i] = edge;
  253. int index = edge.x;
  254. tessInVertices[index] = new float2(shapePath[index].x, shapePath[index].y);
  255. }
  256. // Do tessellation
  257. NativeArray<int> tessOutIndices = new NativeArray<int>(tessInEdges.Length * 8, Allocator.Temp);
  258. NativeArray<float2> tessOutVertices = new NativeArray<float2>(tessInEdges.Length * 8, Allocator.Temp);
  259. NativeArray<int2> tessOutEdges = new NativeArray<int2>(tessInEdges.Length * 8, Allocator.Temp);
  260. int tessOutVertexCount = 0;
  261. int tessOutIndexCount = 0;
  262. int tessOutEdgeCount = 0;
  263. UTess.ModuleHandle.Tessellate(Allocator.Temp, tessInVertices, tessInEdges, ref tessOutVertices, ref tessOutVertexCount, ref tessOutIndices, ref tessOutIndexCount, ref tessOutEdges, ref tessOutEdgeCount);
  264. // Create falloff geometry with random noise to account for collinear points
  265. var inputPointCount = shapePath.Length;
  266. List<IntPoint> path = new List<IntPoint>();
  267. for (var i = 0; i < inputPointCount; ++i)
  268. {
  269. var nx = (System.Int64)((double)shapePath[i].x * (double)kClipperScale);
  270. var ny = (System.Int64)((double)shapePath[i].y * (double)kClipperScale);
  271. var addPoint = new IntPoint(nx + Random.Range(-10, 10), ny + Random.Range(-10, 10));
  272. addPoint.N = i; addPoint.D = -1;
  273. path.Add(addPoint);
  274. }
  275. var lastPointIndex = inputPointCount - 1;
  276. var interiorStartPoint = 0;
  277. // Generate Bevels.
  278. List<List<IntPoint>> solution = new List<List<IntPoint>>();
  279. ClipperOffset clipOffset = new ClipperOffset(24.0f);
  280. clipOffset.AddPath(path, JoinTypes.jtRound, EndTypes.etClosedPolygon);
  281. clipOffset.Execute(ref solution, kClipperScale * falloffDistance, path.Count);
  282. if (solution.Count > 0)
  283. {
  284. // Fix path for Pivots.
  285. var outPath = solution[0];
  286. var minPath = (long)inputPointCount;
  287. for (int i = 0; i < outPath.Count; ++i)
  288. minPath = (outPath[i].N != -1) ? Math.Min(minPath, outPath[i].N) : minPath;
  289. var containsStart = minPath == 0;
  290. outPath = FixPivots(outPath, path, ref interiorStartPoint);
  291. // Size accounts for light mesh + falloff geometry(outer + inner)
  292. int totalOutVertices = tessOutVertexCount + outPath.Count + inputPointCount;
  293. int totalOutIndices = tessOutIndexCount + (outPath.Count * 6) + 6;
  294. var outVertices = new NativeArray<LightMeshVertex>(totalOutVertices, Allocator.Temp);
  295. var outIndices = new NativeArray<ushort>(totalOutIndices, Allocator.Temp);
  296. for (int i = 0; i < tessOutIndexCount; ++i)
  297. outIndices[i] = (ushort)tessOutIndices[i];
  298. for (int i = 0; i < tessOutVertexCount; ++i)
  299. {
  300. outVertices[i] = new LightMeshVertex()
  301. {
  302. position = new float3(tessOutVertices[i].x, tessOutVertices[i].y, 0),
  303. color = meshInteriorColor
  304. };
  305. }
  306. var vcount = tessOutVertexCount;
  307. var icount = tessOutIndexCount;
  308. var innerIndices = new ushort[inputPointCount];
  309. // Inner Vertices. (These may or may not be part of the created path. Beware!!)
  310. for (int i = 0; i < inputPointCount; ++i)
  311. {
  312. outVertices[vcount++] = new LightMeshVertex()
  313. {
  314. position = new float3(shapePath[i].x, shapePath[i].y, 0),
  315. color = meshInteriorColor
  316. };
  317. innerIndices[i] = (ushort)(vcount - 1);
  318. }
  319. var saveIndex = (ushort)vcount;
  320. var pathStart = saveIndex;
  321. var prevIndex = outPath[0].N == -1 ? 0 : outPath[0].N;
  322. // Outer Vertices
  323. for (int i = 0; i < outPath.Count; ++i)
  324. {
  325. var curr = outPath[i];
  326. var currPoint = new float2(curr.X / kClipperScale, curr.Y / kClipperScale);
  327. var currIndex = curr.N == -1 ? 0 : curr.N;
  328. outVertices[vcount++] = new LightMeshVertex()
  329. {
  330. position = new float3(currPoint.x, currPoint.y, 0),
  331. color = (interiorStartPoint > i) ? meshExteriorColor : meshInteriorColor
  332. };
  333. if (prevIndex != currIndex)
  334. {
  335. outIndices[icount++] = innerIndices[prevIndex];
  336. outIndices[icount++] = innerIndices[currIndex];
  337. outIndices[icount++] = (ushort)(vcount - 1);
  338. }
  339. outIndices[icount++] = innerIndices[prevIndex];
  340. outIndices[icount++] = saveIndex;
  341. outIndices[icount++] = saveIndex = (ushort)(vcount - 1);
  342. prevIndex = currIndex;
  343. }
  344. // Close the Loop.
  345. {
  346. outIndices[icount++] = pathStart;
  347. outIndices[icount++] = innerIndices[minPath];
  348. outIndices[icount++] = containsStart ? innerIndices[lastPointIndex] : saveIndex;
  349. outIndices[icount++] = containsStart ? pathStart : saveIndex;
  350. outIndices[icount++] = containsStart ? saveIndex : innerIndices[minPath];
  351. // Last Triangle. todo: Remove Clipper Usage and use SpriteShape Geometry Generator for falloff potentially.
  352. if (containsStart)
  353. {
  354. var kTolerance = 0.001f;
  355. var connectingPoint = innerIndices[lastPointIndex];
  356. // End point detection is tricky and depends on convexity of shape. Simple test is to just check the vertices and detect.
  357. var testA = MathF.Abs(outVertices[connectingPoint].position.x - outVertices[outIndices[icount - 1]].position.x) > kTolerance || MathF.Abs(outVertices[connectingPoint].position.y - outVertices[outIndices[icount - 1]].position.y) > kTolerance;
  358. var testB = MathF.Abs(outVertices[connectingPoint].position.x - outVertices[outIndices[icount - 2]].position.x) > kTolerance || MathF.Abs(outVertices[connectingPoint].position.y - outVertices[outIndices[icount - 2]].position.y) > kTolerance;
  359. if (!testA || !testB)
  360. connectingPoint = (ushort)(interiorStartPoint + inputPointCount + tessOutVertexCount - 1);
  361. outIndices[icount++] = connectingPoint;
  362. }
  363. else
  364. outIndices[icount++] = innerIndices[minPath - 1];
  365. }
  366. TransferToMesh(outVertices, vcount, outIndices, icount, light);
  367. }
  368. Random.state = restoreState;
  369. return light.lightMesh.GetSubMesh(0).bounds;
  370. }
  371. public static Bounds GenerateParametricMesh(Light2D light, float radius, float falloffDistance, float angle, int sides, float batchColor)
  372. {
  373. var angleOffset = Mathf.PI / 2.0f + Mathf.Deg2Rad * angle;
  374. if (sides < 3)
  375. {
  376. radius = 0.70710678118654752440084436210485f * radius;
  377. sides = 4;
  378. }
  379. if (sides == 4)
  380. {
  381. angleOffset = Mathf.PI / 4.0f + Mathf.Deg2Rad * angle;
  382. }
  383. var vertexCount = 1 + 2 * sides;
  384. var indexCount = 3 * 3 * sides;
  385. var vertices = new NativeArray<LightMeshVertex>(vertexCount, Allocator.Temp);
  386. var triangles = new NativeArray<ushort>(indexCount, Allocator.Temp);
  387. var centerIndex = (ushort)(2 * sides);
  388. var mesh = light.lightMesh;
  389. // Only Alpha value in Color channel is ever used. May remove it or keep it for batching params in the future.
  390. var color = new Color(0, 0, batchColor, 1.0f);
  391. vertices[centerIndex] = new LightMeshVertex
  392. {
  393. position = float3.zero,
  394. color = color
  395. };
  396. var radiansPerSide = 2 * Mathf.PI / sides;
  397. var min = new float3(float.MaxValue, float.MaxValue, 0);
  398. var max = new float3(float.MinValue, float.MinValue, 0);
  399. for (var i = 0; i < sides; i++)
  400. {
  401. var endAngle = (i + 1) * radiansPerSide;
  402. var extrudeDir = new float3(math.cos(endAngle + angleOffset), math.sin(endAngle + angleOffset), 0);
  403. var endPoint = radius * extrudeDir;
  404. var vertexIndex = (2 * i + 2) % (2 * sides);
  405. vertices[vertexIndex] = new LightMeshVertex
  406. {
  407. position = endPoint,
  408. color = new Color(extrudeDir.x, extrudeDir.y, batchColor, 0)
  409. };
  410. vertices[vertexIndex + 1] = new LightMeshVertex
  411. {
  412. position = endPoint,
  413. color = color
  414. };
  415. // Triangle 1 (Tip)
  416. var triangleIndex = 9 * i;
  417. triangles[triangleIndex] = (ushort)(vertexIndex + 1);
  418. triangles[triangleIndex + 1] = (ushort)(2 * i + 1);
  419. triangles[triangleIndex + 2] = centerIndex;
  420. // Triangle 2 (Upper Top Left)
  421. triangles[triangleIndex + 3] = (ushort)(vertexIndex);
  422. triangles[triangleIndex + 4] = (ushort)(2 * i);
  423. triangles[triangleIndex + 5] = (ushort)(2 * i + 1);
  424. // Triangle 2 (Bottom Top Left)
  425. triangles[triangleIndex + 6] = (ushort)(vertexIndex + 1);
  426. triangles[triangleIndex + 7] = (ushort)(vertexIndex);
  427. triangles[triangleIndex + 8] = (ushort)(2 * i + 1);
  428. min = math.min(min, endPoint + extrudeDir * falloffDistance);
  429. max = math.max(max, endPoint + extrudeDir * falloffDistance);
  430. }
  431. mesh.SetVertexBufferParams(vertexCount, LightMeshVertex.VertexLayout);
  432. mesh.SetVertexBufferData(vertices, 0, 0, vertexCount);
  433. mesh.SetIndices(triangles, MeshTopology.Triangles, 0, false);
  434. light.vertices = new LightMeshVertex[vertexCount];
  435. NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertexCount);
  436. light.indices = new ushort[indexCount];
  437. NativeArray<ushort>.Copy(triangles, light.indices, indexCount);
  438. return new Bounds
  439. {
  440. min = min,
  441. max = max
  442. };
  443. }
  444. public static Bounds GenerateSpriteMesh(Light2D light, Sprite sprite, float batchColor)
  445. {
  446. var mesh = light.lightMesh;
  447. if (sprite == null)
  448. {
  449. mesh.Clear();
  450. return new Bounds(Vector3.zero, Vector3.zero);
  451. }
  452. // this needs to be called before getting UV at the line below.
  453. // Venky fixed it, enroute to trunk
  454. var uvs = sprite.uv;
  455. var srcVertices = sprite.GetVertexAttribute<Vector3>(VertexAttribute.Position);
  456. var srcUVs = sprite.GetVertexAttribute<Vector2>(VertexAttribute.TexCoord0);
  457. var srcIndices = sprite.GetIndices();
  458. var center = 0.5f * (sprite.bounds.min + sprite.bounds.max);
  459. var vertices = new NativeArray<LightMeshVertex>(srcIndices.Length, Allocator.Temp);
  460. var color = new Color(0, 0, batchColor, 1);
  461. for (var i = 0; i < srcVertices.Length; i++)
  462. {
  463. vertices[i] = new LightMeshVertex
  464. {
  465. position = new Vector3(srcVertices[i].x, srcVertices[i].y, 0),
  466. color = color,
  467. uv = srcUVs[i]
  468. };
  469. }
  470. mesh.SetVertexBufferParams(vertices.Length, LightMeshVertex.VertexLayout);
  471. mesh.SetVertexBufferData(vertices, 0, 0, vertices.Length);
  472. mesh.SetIndices(srcIndices, MeshTopology.Triangles, 0, true);
  473. light.vertices = new LightMeshVertex[vertices.Length];
  474. NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertices.Length);
  475. light.indices = new ushort[srcIndices.Length];
  476. NativeArray<ushort>.Copy(srcIndices, light.indices, srcIndices.Length);
  477. return mesh.GetSubMesh(0).bounds;
  478. }
  479. public static int GetShapePathHash(Vector3[] path)
  480. {
  481. unchecked
  482. {
  483. int hashCode = (int)2166136261;
  484. if (path != null)
  485. {
  486. foreach (var point in path)
  487. hashCode = hashCode * 16777619 ^ point.GetHashCode();
  488. }
  489. else
  490. {
  491. hashCode = 0;
  492. }
  493. return hashCode;
  494. }
  495. }
  496. }
  497. }