123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- using UnityEngine;
- using UnityEngine.Serialization;
- namespace Unity.XRTools.Rendering
- {
- /// <summary>
- /// An XR-Focused drop-in replacement for the Line Renderer
- /// This renderer draws fixed-width lines with simulated volume and glow.
- /// This has many of the advantages of the traditional Line Renderer, old-school system-level line rendering functions,
- /// and volumetric (a linked series of capsules or cubes) rendering
- /// </summary>
- [RequireComponent(typeof(MeshRenderer))]
- [RequireComponent(typeof(MeshFilter))]
- [ExecuteInEditMode]
- public class XRLineRenderer : MeshChainRenderer
- {
- // Stored Line Data
- [SerializeField]
- [Tooltip("All of the connected points to render as a line.")]
- Vector3[] m_Positions;
- [SerializeField]
- [FormerlySerializedAs("m_WorldSpaceData")]
- [Tooltip("Draw lines in worldspace (or local space) - driven via shader.")]
- bool m_UseWorldSpace;
- [SerializeField]
- [Tooltip("Connect the first and last vertices, to create a loop.")]
- bool m_Loop;
- /// <summary>
- /// Connect the first and last vertices, to create a loop.
- /// </summary>
- public bool loop
- {
- get { return m_Loop; }
- set
- {
- m_Loop = value;
- if (NeedsReinitialize())
- Initialize();
- }
- }
- /// <summary>
- /// Draw lines in worldspace (or local space)
- /// </summary>
- public bool useWorldSpace
- {
- get { return m_UseWorldSpace; }
- set { m_UseWorldSpace = value; }
- }
- /// <summary>
- /// Returns the first instantiated Material assigned to the renderer.
- /// </summary>
- public override Material material
- {
- get { return m_MeshRenderer.material; }
- set
- {
- m_MeshRenderer.material = value;
- CopyWorldSpaceDataFromMaterial();
- }
- }
- /// <summary>
- /// Returns all the instantiated materials of this object.
- /// </summary>
- public override Material[] materials
- {
- get { return m_MeshRenderer.materials; }
- set
- {
- m_MeshRenderer.materials = value;
- CopyWorldSpaceDataFromMaterial();
- }
- }
- /// <summary>
- /// Returns the shared material of this object.
- /// </summary>
- public override Material sharedMaterial
- {
- get { return m_MeshRenderer.sharedMaterial; }
- set
- {
- m_MeshRenderer.sharedMaterial = value;
- CopyWorldSpaceDataFromMaterial();
- }
- }
- /// <summary>
- /// Returns all shared materials of this object.
- /// </summary>
- public override Material[] SharedMaterials
- {
- get { return m_MeshRenderer.materials; }
- set
- {
- m_MeshRenderer.sharedMaterials = value;
- CopyWorldSpaceDataFromMaterial();
- }
- }
- /// <summary>
- /// Makes sure that the internal world space flag of the line renderer
- /// matches the world space flag of the first material on the object
- /// </summary>
- void CopyWorldSpaceDataFromMaterial()
- {
- var firstMaterial = m_MeshRenderer.sharedMaterial;
- if (firstMaterial == null)
- {
- return;
- }
- if (firstMaterial.HasProperty("_WorldData"))
- {
- m_UseWorldSpace = !Mathf.Approximately(firstMaterial.GetFloat("_WorldData"), 0.0f);
- }
- else
- {
- m_UseWorldSpace = false;
- }
- }
- /// <summary>
- /// Gets the position of the vertex in the line.
- /// </summary>
- /// <param name="index">The index of the position to retrieve</param>
- /// <returns>The position at the specified index of the array</returns>
- public Vector3 GetPosition(int index)
- {
- return m_Positions[index];
- }
- /// <summary>
- /// Sets the position of the vertex in the line.
- /// </summary>
- /// <param name="index">Which vertex to set</param>
- /// <param name="position">The new location in space of this vertex</param>
- public void SetPosition(int index, Vector3 position)
- {
- // Update internal data
- m_Positions[index] = position;
- // See if the data needs initializing
- if (NeedsReinitialize())
- {
- Initialize();
- return;
- }
- // Otherwise, do fast setting
- var prevIndex = (index - 1 + m_Positions.Length) % m_Positions.Length;
- var endIndex = (index + 1) % m_Positions.Length;
- if (index > 0 || m_Loop)
- {
- m_XRMeshData.SetElementPipe((index * 2) - 1, ref m_Positions[prevIndex], ref m_Positions[index]);
- }
- m_XRMeshData.SetElementPosition(index * 2, ref m_Positions[index]);
- if (index < (m_Positions.Length - 1) || m_Loop)
- {
- m_XRMeshData.SetElementPipe((index * 2) + 1, ref m_Positions[index], ref m_Positions[endIndex]);
- }
- m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Positions);
- m_MeshNeedsRefreshing = true;
- }
- /// <summary>
- /// Get the position of all vertices in the line.
- /// </summary>
- /// <param name="positions">The array of positions to retrieve. The array passed should be of at least numPositions in size.</param>
- /// <returns>How many positions were actually stored in the output array.</returns>
- public int GetPositions(Vector3[] positions)
- {
- if (m_Positions != null)
- {
- m_Positions.CopyTo(positions, 0);
- return m_Positions.Length;
- }
- return 0;
- }
- /// <summary>
- /// Sets all positions in the line. Cheaper than calling SetPosition repeatedly
- /// </summary>
- /// <param name="newPositions">All of the new endpoints of the line</param>
- /// <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>
- public void SetPositions(Vector3[] newPositions, bool knownSizeChange = false)
- {
- // Update internal data
- m_Positions = newPositions;
- if (NeedsReinitialize())
- {
- if (knownSizeChange == false)
- {
- Debug.LogWarning("New positions does not match size of existing array. Adjusting vertex count as well");
- }
- Initialize();
- return;
- }
- if (m_Positions.Length <= 0)
- {
- return;
- }
- // Otherwise, do fast setting
- var pointCounter = 0;
- var elementCounter = 0;
- m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
- elementCounter++;
- pointCounter++;
- while (pointCounter < m_Positions.Length)
- {
- m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[pointCounter]);
- elementCounter++;
- m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
- elementCounter++;
- pointCounter++;
- }
- if (m_Loop)
- {
- m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[0]);
- }
- // Dirty all the VRMeshChain flags so everything gets refreshed
- m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Positions);
- m_MeshNeedsRefreshing = true;
- }
- /// <summary>
- /// Sets the number of billboard-line chains. This function regenerates the point list if the
- /// number of vertex points changes, so use it sparingly.
- /// </summary>
- /// <param name="count">The new number of vertices in the line</param>
- public void SetVertexCount(int count)
- {
- // See if anything needs updating
- if (m_Positions.Length == count)
- {
- return;
- }
- // Adjust this array
- var newPositions = new Vector3[count];
- var copyCount = Mathf.Min(m_Positions.Length, count);
- var copyIndex = 0;
- while (copyIndex < copyCount)
- {
- newPositions[copyIndex] = m_Positions[copyIndex];
- copyIndex++;
- }
- m_Positions = newPositions;
- // Do an initialization, this changes everything
- Initialize();
- }
- /// <summary>
- /// Get the number of billboard-line chains.
- /// </summary>
- /// <returns>The number of chains</returns>
- public int GetVertexCount()
- {
- return m_Positions.Length;
- }
- /// <summary>
- /// Updates any internal variables to represent the new color that has been applied
- /// </summary>
- protected override void UpdateColors()
- {
- // See if the data needs initializing
- if (NeedsReinitialize())
- {
- Initialize();
- return;
- }
- if (m_Positions.Length <= 0)
- {
- return;
- }
- // If it doesn't, go through each point and set the data
- var pointCounter = 0;
- var elementCounter = 0;
- var stepPercent = 0.0f;
- var lastColor = m_Color.Evaluate(stepPercent);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- while (pointCounter < m_Positions.Length)
- {
- var currentColor = m_Color.Evaluate(stepPercent);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
- elementCounter++;
- m_XRMeshData.SetElementColor(elementCounter, ref currentColor);
- lastColor = currentColor;
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- }
- if (m_Loop)
- {
- lastColor = m_Color.Evaluate(stepPercent);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
- }
- // Dirty the color meshChain flags so the mesh gets new data
- m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Colors);
- m_MeshNeedsRefreshing = true;
- }
- /// <summary>
- /// Updates any internal variables to represent the new width that has been applied
- /// </summary>
- protected override void UpdateWidth()
- {
- // See if the data needs initializing
- if (NeedsReinitialize())
- {
- Initialize();
- return;
- }
- if (m_Positions.Length <= 0)
- {
- return;
- }
- // Otherwise, do fast setting
- var pointCounter = 0;
- var elementCounter = 0;
- var stepPercent = 0.0f;
- // We go through the element list, much like initialization, but only update the width part of the variables
- var lastWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- m_XRMeshData.SetElementSize(elementCounter, lastWidth);
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- while (pointCounter < m_Positions.Length)
- {
- var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
- elementCounter++;
- m_XRMeshData.SetElementSize(elementCounter, currentWidth);
- lastWidth = currentWidth;
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- }
- if (m_Loop)
- {
- var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
- }
- // Dirty all the VRMeshChain flags so everything gets refreshed
- m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.Sizes);
- m_MeshNeedsRefreshing = true;
- }
- /// <summary>
- /// Creates or updates the underlying mesh data
- /// </summary>
- protected override void Initialize(bool setMesh = true)
- {
- base.Initialize();
- CopyWorldSpaceDataFromMaterial();
- if (m_Positions == null)
- m_Positions = new Vector3[0];
- // For a line renderer we assume one big chain
- // We need a control point for each billboard and a control point for each pipe connecting them together
- // Except for the end, which must be capped with another billboard. This gives us (positions * 2) - 1
- // If we're looping, then we do need one more pipe
- var neededPoints = m_Loop ? 1 : 0;
- neededPoints = Mathf.Max(neededPoints + (m_Positions.Length * 2) - 1, 0);
- if (m_XRMeshData == null)
- {
- m_XRMeshData = new XRMeshChain();
- }
- if (m_XRMeshData.reservedElements != neededPoints)
- {
- m_XRMeshData.worldSpaceData = useWorldSpace;
- m_XRMeshData.GenerateMesh(gameObject, true, neededPoints, setMesh);
- }
- // If we have no points, then just assume that stepping through a single point would take us through the whole line
- if (neededPoints == 0)
- {
- m_StepSize = 1.0f;
- return;
- }
- m_StepSize = 1.0f / Mathf.Max(m_Loop ? m_Positions.Length : m_Positions.Length - 1, 1.0f);
- var pointCounter = 0;
- var elementCounter = 0;
- var stepPercent = 0.0f;
- var lastColor = m_Color.Evaluate(stepPercent);
- var lastWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- // Initialize the single starting point
- m_XRMeshData.SetElementSize(elementCounter, lastWidth);
- m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor);
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- // Now do the chain
- while (pointCounter < m_Positions.Length)
- {
- var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- var currentColor = m_Color.Evaluate(stepPercent);
- // Create a pipe from the previous point to here
- m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
- m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[pointCounter]);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
- elementCounter++;
- // Now record our own point data
- m_XRMeshData.SetElementSize(elementCounter, currentWidth);
- m_XRMeshData.SetElementPosition(elementCounter, ref m_Positions[pointCounter]);
- m_XRMeshData.SetElementColor(elementCounter, ref currentColor);
- // Go onto the next point while retaining previous values we might need to lerp between
- lastWidth = currentWidth;
- lastColor = currentColor;
- elementCounter++;
- pointCounter++;
- stepPercent += m_StepSize;
- }
- if (m_Loop)
- {
- var currentWidth = m_WidthCurve.Evaluate(stepPercent) * m_Width;
- var currentColor = m_Color.Evaluate(stepPercent);
- m_XRMeshData.SetElementSize(elementCounter, lastWidth, currentWidth);
- m_XRMeshData.SetElementPipe(elementCounter, ref m_Positions[pointCounter - 1], ref m_Positions[0]);
- m_XRMeshData.SetElementColor(elementCounter, ref lastColor, ref currentColor);
- }
- // Dirty all the VRMeshChain flags so everything gets refreshed
- m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
- m_MeshNeedsRefreshing = true;
- }
- /// <summary>
- /// Tests if the mesh data needs to be created or rebuilt
- /// </summary>
- /// <returns>true if the mesh data needs recreation, false if it is already set up properly</returns>
- protected override bool NeedsReinitialize()
- {
- // No mesh data means we definitely need to reinitialize
- if (m_XRMeshData == null)
- {
- return true;
- }
- // If we have any positions, figure out how many points we need to render a line along it
- var neededPoints = 0;
- if (m_Positions != null)
- {
- neededPoints = Mathf.Max((m_Positions.Length * 2) - 1, 0);
- if (m_Loop)
- {
- neededPoints++;
- }
- }
- return (m_XRMeshData.reservedElements != neededPoints);
- }
- }
- }
|