using System.Collections.Generic; using UnityEngine; namespace Unity.XRTools.Rendering { /// /// The mesh chain handles all the translation between Unity's mesh class, /// what the line renderers want to do, and what the billboard-pipe based shaders expect /// If you need more custom/optimized access to this kind of mesh information, feel /// free to hook into this structure directly. /// public class XRMeshChain { /// /// What part of the mesh to refresh /// [System.Flags] public enum MeshRefreshFlag { /// /// Don't refresh any of the mesh /// None = 0, /// /// Refresh positions /// Positions = 1, /// /// Refresh colors /// Colors = 2, /// /// Refresh sizes /// Sizes = 4, /// /// Refresh all mesh components /// All = 7 } Vector3[] m_Verts; Color32[] m_Colors; List m_ShapeData; // xy: UV coordinates for GPU expansion zw: Size of this vertex, size of the neighbor List m_NeighborPoints; // Location of the next point this pipe connects to, or itself if it is a billboard // Update flags to prevent unnecessary mesh data generation MeshRefreshFlag m_DataThatNeedsUpdate = MeshRefreshFlag.All; // Cached runtime data Mesh m_Mesh; Transform m_OwnerTransform; bool m_WorldSpaceData; /// /// Whether the control points of this mesh chain have been referenced in world or local space /// This makes sure the bounding box of the mesh is updated appropriately for culling /// public bool worldSpaceData { get { return m_WorldSpaceData; } set { m_WorldSpaceData = value; } } /// /// How many primitives/quads this mesh chain supports and has reserved memory for /// public int reservedElements { get; private set; } /// /// If using world space data, this option will force the center of the bounding box to be the root /// public bool centerAtRoot { get; set; } /// /// Initialize a new XRMeshChain /// public XRMeshChain() { reservedElements = 0; } /// /// Creates or re-creates the mesh with all the data needed for billboard-pipe based line rendering /// /// The gameobject that will own the created mesh /// Whether this mesh is going to updated frequently or not /// How many total billboards and pipes are needed for this renderer public void GenerateMesh(GameObject owner, bool dynamic, int totalElements, bool setMesh = true) { // Precache neccessary data // The mesh, vertex and triangle counts if (m_Mesh == null) { m_Mesh = new Mesh(); } if (dynamic == true) { m_Mesh.MarkDynamic(); } if (setMesh) owner.GetComponent().mesh = m_Mesh; m_OwnerTransform = owner.transform; reservedElements = totalElements; var vertCount = 4 * reservedElements; var triCount = 6 * reservedElements; m_Verts = new Vector3[vertCount]; m_Colors = new Color32[vertCount]; m_ShapeData = new List(vertCount); m_NeighborPoints = new List(vertCount); var triangles = new int[triCount]; var defaultWhite = new Color32(255, 255, 255, 255); var uvSet1 = new Vector4(0, 0, 1, 1); var uvSet2 = new Vector4(1, 0, 1, 1); var uvSet3 = new Vector4(1, 1, 1, 1); var uvSet4 = new Vector4(0, 1, 1, 1); // Set up the basic data for all of our geometry var pointCounter = 0; while (pointCounter < reservedElements) { // Get where in the various indices we need to write var vertOffset = pointCounter * 4; var triOffset = pointCounter * 6; // Store default color m_Colors[vertOffset] = defaultWhite; m_Colors[vertOffset + 1] = defaultWhite; m_Colors[vertOffset + 2] = defaultWhite; m_Colors[vertOffset + 3] = defaultWhite; // Write traditional billboard coordinates // We use the UV coordinates to determine direction each // individual vertex will expand in, in screen space // Last two coordinates are size expansion m_ShapeData.Add(uvSet1); m_ShapeData.Add(uvSet2); m_ShapeData.Add(uvSet3); m_ShapeData.Add(uvSet4); // Zero out neighbor points m_NeighborPoints.Add(Vector3.zero); m_NeighborPoints.Add(Vector3.zero); m_NeighborPoints.Add(Vector3.zero); m_NeighborPoints.Add(Vector3.zero); // And a proper index buffer for this element triangles[triOffset] = vertOffset; triangles[triOffset + 1] = vertOffset + 1; triangles[triOffset + 2] = vertOffset + 2; triangles[triOffset + 3] = vertOffset; triangles[triOffset + 4] = vertOffset + 2; triangles[triOffset + 5] = vertOffset + 3; pointCounter++; } // Now set any values we can m_Mesh.triangles = null; m_Mesh.vertices = m_Verts; m_Mesh.SetUVs(0, m_ShapeData); m_Mesh.SetUVs(1, m_NeighborPoints); m_Mesh.triangles = triangles; } /// /// Updates any mesh vertex data that is marked as dirty /// public void RefreshMesh() { if ((m_DataThatNeedsUpdate & MeshRefreshFlag.Positions) != 0) { m_Mesh.vertices = m_Verts; m_Mesh.SetUVs(1, m_NeighborPoints); } if ((m_DataThatNeedsUpdate & MeshRefreshFlag.Colors) != 0) { m_Mesh.colors32 = m_Colors; } if ((m_DataThatNeedsUpdate & MeshRefreshFlag.Sizes) != 0) { m_Mesh.SetUVs(0, m_ShapeData); } m_DataThatNeedsUpdate = MeshRefreshFlag.None; m_Mesh.RecalculateBounds(); if (m_WorldSpaceData == true) { var newBounds = m_Mesh.bounds; newBounds.center = centerAtRoot ? Vector3.zero : m_OwnerTransform.InverseTransformPoint(newBounds.center); m_Mesh.bounds = newBounds; } } /// /// Used by external classes to alert the mesh chain that they have modified its data /// /// Which type of data (position, color, size) has been changed public void SetMeshDataDirty(MeshRefreshFlag dataThatNeedsUpdate) { m_DataThatNeedsUpdate |= dataThatNeedsUpdate; } /// /// Sets the position of a specific point in the chain /// /// Which control point to update /// The updated position of the control point public void SetElementPosition(int elementIndex, ref Vector3 position) { var offset = elementIndex * 4; m_Verts[offset] = position; m_Verts[offset + 1] = position; m_Verts[offset + 2] = position; m_Verts[offset + 3] = position; m_NeighborPoints[offset] = position; m_NeighborPoints[offset + 1] = position; m_NeighborPoints[offset + 2] = position; m_NeighborPoints[offset + 3] = position; } /// /// Sets the endpoints of a pipe in the chain - The pipe equivalent of SetElementPosition /// /// Which control pipe to update /// The position of the previous control point being connected to /// The position of the next control point being connected to public void SetElementPipe(int elementIndex, ref Vector3 startPoint, ref Vector3 endPoint) { var offset = elementIndex * 4; m_Verts[offset] = startPoint; m_Verts[offset + 1] = startPoint; m_Verts[offset + 2] = endPoint; m_Verts[offset + 3] = endPoint; m_NeighborPoints[offset] = endPoint; m_NeighborPoints[offset + 1] = endPoint; m_NeighborPoints[offset + 2] = startPoint; m_NeighborPoints[offset + 3] = startPoint; } /// /// Sets the size of the billboard or pipe being rendered /// /// The index of the control point to update /// What the radius or width of the element should be public void SetElementSize(int elementIndex, float sizeModification) { var offset = elementIndex * 4; m_ShapeData[offset] = new Vector4(0, 0, sizeModification, sizeModification); m_ShapeData[offset + 1] = new Vector4(1, 0, sizeModification, sizeModification); m_ShapeData[offset + 2] = new Vector4(1, 1, sizeModification, sizeModification); m_ShapeData[offset + 3] = new Vector4(0, 1, sizeModification, sizeModification); } /// /// Sets the size of the pipe being rendered /// /// The index of the pipe control point to update /// The start size of the pipe /// The end size of the pipe public void SetElementSize(int elementIndex, float startSize, float endSize) { var offset = elementIndex * 4; m_ShapeData[offset] = new Vector4(0, 0, startSize, endSize); m_ShapeData[offset + 1] = new Vector4(1, 0, startSize, endSize); m_ShapeData[offset + 2] = new Vector4(1, 1, endSize, startSize); m_ShapeData[offset + 3] = new Vector4(0, 1, endSize, startSize); } /// /// Sets the color of a billboard or pipe in the chain /// /// The index of the element we are coloring /// What the color of this element should be public void SetElementColor(int elementIndex, ref Color color) { var offset = elementIndex * 4; m_Colors[offset] = color; m_Colors[offset + 1] = m_Colors[offset]; m_Colors[offset + 2] = m_Colors[offset]; m_Colors[offset + 3] = m_Colors[offset]; } /// /// Sets the color of a billboard or pipe in the chain /// /// The index of the element we are coloring /// What the color of this element should be public void SetElementColor32(int elementIndex, ref Color32 color) { var offset = elementIndex * 4; m_Colors[offset] = color; m_Colors[offset + 1] = color; m_Colors[offset + 2] = color; m_Colors[offset + 3] = color; } /// /// Sets the colors of a pipe in the chain /// /// The index of the pipe we are coloring /// The color of the startpoint of the pipe /// The color of the endpoint of the pipe public void SetElementColor(int elementIndex, ref Color startColor, ref Color endColor) { var offset = elementIndex * 4; m_Colors[offset] = startColor; m_Colors[offset + 1] = m_Colors[offset]; m_Colors[offset + 2] = endColor; m_Colors[offset + 3] = m_Colors[offset + 2]; } /// /// Sets the colors of a pipe in the chain /// /// The index of the pipe we are coloring /// The color of the startpoint of the pipe /// The color of the endpoint of the pipe public void SetElementColor32(int elementIndex, ref Color32 startColor, ref Color32 endColor) { var offset = elementIndex * 4; m_Colors[offset] = startColor; m_Colors[offset + 1] = m_Colors[offset]; m_Colors[offset + 2] = endColor; m_Colors[offset + 3] = m_Colors[offset + 2]; } } }