XRLineRenderer.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. using UnityEngine;
  2. using UnityEngine.Serialization;
  3. namespace Unity.XRTools.Rendering
  4. {
  5. /// <summary>
  6. /// An XR-Focused drop-in replacement for the Line Renderer
  7. /// This renderer draws fixed-width lines with simulated volume and glow.
  8. /// This has many of the advantages of the traditional Line Renderer, old-school system-level line rendering functions,
  9. /// and volumetric (a linked series of capsules or cubes) rendering
  10. /// </summary>
  11. [RequireComponent(typeof(MeshRenderer))]
  12. [RequireComponent(typeof(MeshFilter))]
  13. [ExecuteInEditMode]
  14. public class XRLineRenderer : MeshChainRenderer
  15. {
  16. // Stored Line Data
  17. [SerializeField]
  18. [Tooltip("All of the connected points to render as a line.")]
  19. Vector3[] m_Positions;
  20. [SerializeField]
  21. [FormerlySerializedAs("m_WorldSpaceData")]
  22. [Tooltip("Draw lines in worldspace (or local space) - driven via shader.")]
  23. bool m_UseWorldSpace;
  24. [SerializeField]
  25. [Tooltip("Connect the first and last vertices, to create a loop.")]
  26. bool m_Loop;
  27. /// <summary>
  28. /// Connect the first and last vertices, to create a loop.
  29. /// </summary>
  30. public bool loop
  31. {
  32. get { return m_Loop; }
  33. set
  34. {
  35. m_Loop = value;
  36. if (NeedsReinitialize())
  37. Initialize();
  38. }
  39. }
  40. /// <summary>
  41. /// Draw lines in worldspace (or local space)
  42. /// </summary>
  43. public bool useWorldSpace
  44. {
  45. get { return m_UseWorldSpace; }
  46. set { m_UseWorldSpace = value; }
  47. }
  48. /// <summary>
  49. /// Returns the first instantiated Material assigned to the renderer.
  50. /// </summary>
  51. public override Material material
  52. {
  53. get { return m_MeshRenderer.material; }
  54. set
  55. {
  56. m_MeshRenderer.material = value;
  57. CopyWorldSpaceDataFromMaterial();
  58. }
  59. }
  60. /// <summary>
  61. /// Returns all the instantiated materials of this object.
  62. /// </summary>
  63. public override Material[] materials
  64. {
  65. get { return m_MeshRenderer.materials; }
  66. set
  67. {
  68. m_MeshRenderer.materials = value;
  69. CopyWorldSpaceDataFromMaterial();
  70. }
  71. }
  72. /// <summary>
  73. /// Returns the shared material of this object.
  74. /// </summary>
  75. public override Material sharedMaterial
  76. {
  77. get { return m_MeshRenderer.sharedMaterial; }
  78. set
  79. {
  80. m_MeshRenderer.sharedMaterial = value;
  81. CopyWorldSpaceDataFromMaterial();
  82. }
  83. }
  84. /// <summary>
  85. /// Returns all shared materials of this object.
  86. /// </summary>
  87. public override Material[] SharedMaterials
  88. {
  89. get { return m_MeshRenderer.materials; }
  90. set
  91. {
  92. m_MeshRenderer.sharedMaterials = value;
  93. CopyWorldSpaceDataFromMaterial();
  94. }
  95. }
  96. /// <summary>
  97. /// Makes sure that the internal world space flag of the line renderer
  98. /// matches the world space flag of the first material on the object
  99. /// </summary>
  100. void CopyWorldSpaceDataFromMaterial()
  101. {
  102. var firstMaterial = m_MeshRenderer.sharedMaterial;
  103. if (firstMaterial == null)
  104. {
  105. return;
  106. }
  107. if (firstMaterial.HasProperty("_WorldData"))
  108. {
  109. m_UseWorldSpace = !Mathf.Approximately(firstMaterial.GetFloat("_WorldData"), 0.0f);
  110. }
  111. else
  112. {
  113. m_UseWorldSpace = false;
  114. }
  115. }
  116. /// <summary>
  117. /// Gets the position of the vertex in the line.
  118. /// </summary>
  119. /// <param name="index">The index of the position to retrieve</param>
  120. /// <returns>The position at the specified index of the array</returns>
  121. public Vector3 GetPosition(int index)
  122. {
  123. return m_Positions[index];
  124. }
  125. /// <summary>
  126. /// Sets the position of the vertex in the line.
  127. /// </summary>
  128. /// <param name="index">Which vertex to set</param>
  129. /// <param name="position">The new location in space of this vertex</param>
  130. public void SetPosition(int index, Vector3 position)
  131. {
  132. // Update internal data
  133. m_Positions[index] = position;
  134. // See if the data needs initializing
  135. if (NeedsReinitialize())
  136. {
  137. Initialize();
  138. return;
  139. }
  140. // Otherwise, do fast setting
  141. var prevIndex = (index - 1 + m_Positions.Length) % m_Positions.Length;
  142. var endIndex = (index + 1) % m_Positions.Length;
  143. if (index > 0 || m_Loop)
  144. {
  145. m_XRMeshData.SetElementPipe((index * 2) - 1, ref m_Positions[prevIndex], ref m_Positions[index]);
  146. }
  147. m_XRMeshData.SetElementPosition(index * 2, ref m_Positions[index]);
  148. if (index < (m_Positions.Length - 1) || m_Loop)
  149. {
  150. m_XRMeshData.SetElementPipe((index * 2) + 1, ref m_Positions[index], ref m_Positions[endIndex]);
  151. }
  152. m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Positions);
  153. m_MeshNeedsRefreshing = true;
  154. }
  155. /// <summary>
  156. /// Get the position of all vertices in the line.
  157. /// </summary>
  158. /// <param name="positions">The array of positions to retrieve. The array passed should be of at least numPositions in size.</param>
  159. /// <returns>How many positions were actually stored in the output array.</returns>
  160. public int GetPositions(Vector3[] positions)
  161. {
  162. if (m_Positions != null)
  163. {
  164. m_Positions.CopyTo(positions, 0);
  165. return m_Positions.Length;
  166. }
  167. return 0;
  168. }
  169. /// <summary>
  170. /// Sets all positions in the line. Cheaper than calling SetPosition repeatedly
  171. /// </summary>
  172. /// <param name="newPositions">All of the new endpoints of the line</param>
  173. /// <param name="knownSizeChange">Turn on to run a safety check to make sure the number of endpoints does not change (bad for garbage collection)</param>
  174. public void SetPositions(Vector3[] newPositions, bool knownSizeChange = false)
  175. {
  176. // Update internal data
  177. m_Positions = newPositions;
  178. if (NeedsReinitialize())
  179. {
  180. if (knownSizeChange == false)
  181. {
  182. Debug.LogWarning("New positions does not match size of existing array. Adjusting vertex count as well");
  183. }
  184. Initialize();
  185. return;
  186. }
  187. if (m_Positions.Length <= 0)
  188. {
  189. return;
  190. }
  191. // Otherwise, do fast setting
  192. var pointCounter = 0;
  193. var elementCounter = 0;
  194. m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
  195. elementCounter++;
  196. pointCounter++;
  197. while (pointCounter < m_Positions.Length)
  198. {
  199. m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[pointCounter]);
  200. elementCounter++;
  201. m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
  202. elementCounter++;
  203. pointCounter++;
  204. }
  205. if (m_Loop)
  206. {
  207. m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[0]);
  208. }
  209. // Dirty all the VRMeshChain flags so everything gets refreshed
  210. m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Positions);
  211. m_MeshNeedsRefreshing = true;
  212. }
  213. /// <summary>
  214. /// Sets the number of billboard-line chains. This function regenerates the point list if the
  215. /// number of vertex points changes, so use it sparingly.
  216. /// </summary>
  217. /// <param name="count">The new number of vertices in the line</param>
  218. public void SetVertexCount(int count)
  219. {
  220. // See if anything needs updating
  221. if (m_Positions.Length == count)
  222. {
  223. return;
  224. }
  225. // Adjust this array
  226. var newPositions = new Vector3[count];
  227. var copyCount = Mathf.Min(m_Positions.Length, count);
  228. var copyIndex = 0;
  229. while (copyIndex < copyCount)
  230. {
  231. newPositions[copyIndex] = m_Positions[copyIndex];
  232. copyIndex++;
  233. }
  234. m_Positions = newPositions;
  235. // Do an initialization, this changes everything
  236. Initialize();
  237. }
  238. /// <summary>
  239. /// Get the number of billboard-line chains.
  240. /// </summary>
  241. /// <returns>The number of chains</returns>
  242. public int GetVertexCount()
  243. {
  244. return m_Positions.Length;
  245. }
  246. /// <summary>
  247. /// Updates any internal variables to represent the new color that has been applied
  248. /// </summary>
  249. protected override void UpdateColors()
  250. {
  251. // See if the data needs initializing
  252. if (NeedsReinitialize())
  253. {
  254. Initialize();
  255. return;
  256. }
  257. if (m_Positions.Length <= 0)
  258. {
  259. return;
  260. }
  261. // If it doesn't, go through each point and set the data
  262. var pointCounter = 0;
  263. var elementCounter = 0;
  264. var stepPercent = 0.0f;
  265. var lastColor = m_Color.Evaluate(stepPercent);
  266. m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
  267. elementCounter++;
  268. pointCounter++;
  269. stepPercent += m_StepSize;
  270. while (pointCounter < m_Positions.Length)
  271. {
  272. var currentColor = m_Color.Evaluate(stepPercent);
  273. m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
  274. elementCounter++;
  275. m_XRMeshData.SetElementColor(elementCounter, ref currentColor);
  276. lastColor = currentColor;
  277. elementCounter++;
  278. pointCounter++;
  279. stepPercent += m_StepSize;
  280. }
  281. if (m_Loop)
  282. {
  283. lastColor = m_Color.Evaluate(stepPercent);
  284. m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
  285. }
  286. // Dirty the color meshChain flags so the mesh gets new data
  287. m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Colors);
  288. m_MeshNeedsRefreshing = true;
  289. }
  290. /// <summary>
  291. /// Updates any internal variables to represent the new width that has been applied
  292. /// </summary>
  293. protected override void UpdateWidth()
  294. {
  295. // See if the data needs initializing
  296. if (NeedsReinitialize())
  297. {
  298. Initialize();
  299. return;
  300. }
  301. if (m_Positions.Length <= 0)
  302. {
  303. return;
  304. }
  305. // Otherwise, do fast setting
  306. var pointCounter = 0;
  307. var elementCounter = 0;
  308. var stepPercent = 0.0f;
  309. // We go through the element list, much like initialization, but only update the width part of the variables
  310. var lastWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  311. m_XRMeshData.SetElementSize(elementCounter, lastWidth);
  312. elementCounter++;
  313. pointCounter++;
  314. stepPercent += m_StepSize;
  315. while (pointCounter < m_Positions.Length)
  316. {
  317. var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  318. m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
  319. elementCounter++;
  320. m_XRMeshData.SetElementSize(elementCounter, currentWidth);
  321. lastWidth = currentWidth;
  322. elementCounter++;
  323. pointCounter++;
  324. stepPercent += m_StepSize;
  325. }
  326. if (m_Loop)
  327. {
  328. var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  329. m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
  330. }
  331. // Dirty all the VRMeshChain flags so everything gets refreshed
  332. m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Sizes);
  333. m_MeshNeedsRefreshing = true;
  334. }
  335. /// <summary>
  336. /// Creates or updates the underlying mesh data
  337. /// </summary>
  338. protected override void Initialize(bool setMesh = true)
  339. {
  340. base.Initialize();
  341. CopyWorldSpaceDataFromMaterial();
  342. if (m_Positions == null)
  343. m_Positions = new Vector3[0];
  344. // For a line renderer we assume one big chain
  345. // We need a control point for each billboard and a control point for each pipe connecting them together
  346. // Except for the end, which must be capped with another billboard. This gives us (positions * 2) - 1
  347. // If we're looping, then we do need one more pipe
  348. var neededPoints = m_Loop ? 1 : 0;
  349. neededPoints = Mathf.Max(neededPoints + (m_Positions.Length * 2) - 1, 0);
  350. if (m_XRMeshData == null)
  351. {
  352. m_XRMeshData = new XRMeshChain();
  353. }
  354. if (m_XRMeshData.reservedElements != neededPoints)
  355. {
  356. m_XRMeshData.worldSpaceData = useWorldSpace;
  357. m_XRMeshData.GenerateMesh(gameObject, true, neededPoints, setMesh);
  358. }
  359. // If we have no points, then just assume that stepping through a single point would take us through the whole line
  360. if (neededPoints == 0)
  361. {
  362. m_StepSize = 1.0f;
  363. return;
  364. }
  365. m_StepSize = 1.0f / Mathf.Max(m_Loop ? m_Positions.Length : m_Positions.Length - 1, 1.0f);
  366. var pointCounter = 0;
  367. var elementCounter = 0;
  368. var stepPercent = 0.0f;
  369. var lastColor = m_Color.Evaluate(stepPercent);
  370. var lastWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  371. // Initialize the single starting point
  372. m_XRMeshData.SetElementSize(elementCounter, lastWidth);
  373. m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
  374. m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
  375. elementCounter++;
  376. pointCounter++;
  377. stepPercent += m_StepSize;
  378. // Now do the chain
  379. while (pointCounter < m_Positions.Length)
  380. {
  381. var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  382. var currentColor = m_Color.Evaluate(stepPercent);
  383. // Create a pipe from the previous point to here
  384. m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
  385. m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[pointCounter]);
  386. m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
  387. elementCounter++;
  388. // Now record our own point data
  389. m_XRMeshData.SetElementSize(elementCounter, currentWidth);
  390. m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
  391. m_XRMeshData.SetElementColor(elementCounter, ref currentColor);
  392. // Go onto the next point while retaining previous values we might need to lerp between
  393. lastWidth = currentWidth;
  394. lastColor = currentColor;
  395. elementCounter++;
  396. pointCounter++;
  397. stepPercent += m_StepSize;
  398. }
  399. if (m_Loop)
  400. {
  401. var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
  402. var currentColor = m_Color.Evaluate(stepPercent);
  403. m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
  404. m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[0]);
  405. m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
  406. }
  407. // Dirty all the VRMeshChain flags so everything gets refreshed
  408. m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
  409. m_MeshNeedsRefreshing = true;
  410. }
  411. /// <summary>
  412. /// Tests if the mesh data needs to be created or rebuilt
  413. /// </summary>
  414. /// <returns>true if the mesh data needs recreation, false if it is already set up properly</returns>
  415. protected override bool NeedsReinitialize()
  416. {
  417. // No mesh data means we definitely need to reinitialize
  418. if (m_XRMeshData == null)
  419. {
  420. return true;
  421. }
  422. // If we have any positions, figure out how many points we need to render a line along it
  423. var neededPoints = 0;
  424. if (m_Positions != null)
  425. {
  426. neededPoints = Mathf.Max((m_Positions.Length * 2) - 1, 0);
  427. if (m_Loop)
  428. {
  429. neededPoints++;
  430. }
  431. }
  432. return (m_XRMeshData.reservedElements != neededPoints);
  433. }
  434. }
  435. }