Merge branch 'package-prep' of github.com:Unity-Technologies/XRLineRenderer into package-prep

This commit is contained in:
Jason Smalridge 2019-11-25 09:27:52 -05:00
Родитель 0e4332f724 ea9fdeb5ec
Коммит ba1b4744ae
35 изменённых файлов: 1788 добавлений и 1596 удалений

Просмотреть файл

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 7678c616003e2c34f905fa62e8206550
timeCreated: 1486447834
licenseType: Pro
guid: 7c2a55c0f54054f4d8093d6c66d7946b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,30 @@
# XR Line Renderer
An XR-Optimized line renderer that is also capable of producing very inexpensive glow effects. The XRLineRenderer mimics rendering with 3d capsules while only using two quads worth of geometry.
## Setup and usage
1. Place the XRLineRenderer folder into Assets\XR Utilities\XRLineRenderer in your project.
2. Add a XRLineRenderer or XRTrailRenderer component to your gameobject. The interface is nearly identical to the built in Unity Line and Trail Renderers.
3. Create a new material using the XRLineRenderer shaders. You can find some examples in XRLineRenderer\Materials
4. Apply this material to the mesh renderer of your XRLineRenderer or XRTrailRenderer.
## VRLineRenderer Shader
You will find five shader variants under the XRLineRenderer category. Each of these corresponds to a shader blend mode.
Max Color and Min Color are the cheapest variants - if you are using the line renderer to mimic glow effects these variants also are stable in that color will not blow out.
Explanation of interesting shader parameters:
Line Rendering Levels - This allows control over the blend between the inner(most opaque/intense) part of the line and outer(transparent) area. Adjust the level curve to 0 will give a very glow-like effect while setting the cruve to 1 will make the line completely solid.
Line Scaled by Depth - Turning this option off means the line will stay the same thickness regardless of your distance from it. This is excellent for drafting lines and also for simulating glow. Radius minimum and maximum allow you to clamp this size adjustment.
## Custom VR Line Rendering
The Scripts\Meshchain class provides everything you need to make your own custom line rendering constructs. XRLineRenderer and XRTrailRenderer emulate what the classic Unity components provide, but there are many more use cases out there.
### Project Settings
If you plan on making changes to The XR Line Renderer and/or contributing back, then you'll need to set the `Asset Serialization` property under Edit->Project Settings->Editor to `Force Text`

Просмотреть файл

Просмотреть файл

@ -1,12 +1,12 @@
using UnityEngine;
using UnityEditor;
using UnityEngine;
namespace UnityEditor
namespace Unity.Labs.XR
{
public class MeshChainShaderGUI : ShaderGUI
class MeshChainShaderGUI : ShaderGUI
{
protected static class Styles
static class Styles
{
public static string emptyTootip = "";
public static GUIContent colorText = new GUIContent("Line Tint", "Line Color (RGB) and Transparency (A)");
public static GUIContent lineDataSpaceText = new GUIContent("World Space Data", "If true, the data " +
"will not be transformed before rendering");
@ -40,9 +40,9 @@ namespace UnityEditor
MaterialProperty m_LineDataSpace;
MaterialProperty m_LineDepthScaleMode;
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
FindProperties(props); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly
FindProperties(properties); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly
m_MaterialEditor = materialEditor;
Material material = materialEditor.target as Material;
@ -56,7 +56,7 @@ namespace UnityEditor
}
}
public void FindProperties(MaterialProperty[] props)
void FindProperties(MaterialProperty[] props)
{
m_LineColor = FindProperty("_Color", props);
m_LineSettings = FindProperty("_lineSettings", props);
@ -67,7 +67,7 @@ namespace UnityEditor
m_LineDepthScaleMode = FindProperty("_LineDepthScale", props, false);
}
public void ShaderPropertiesGUI(Material material)
void ShaderPropertiesGUI(Material material)
{
// Use default labelWidth
EditorGUIUtility.labelWidth = 0f;

Просмотреть файл

@ -0,0 +1,17 @@
{
"name": "Unity.Labs.XRLineRenderer.Editor",
"references": [
"Unity.Labs.XRLineRenderer"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e484b69e484584edd9f645b14ea9e149
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -1,10 +1,11 @@
using UnityEngine;
using UnityEditor;
using UnityEngine;
namespace UnityEditor
namespace Unity.Labs.XR
{
[CustomEditor(typeof(XRLineRenderer))]
[CanEditMultipleObjects]
public class XRLineRendererEditor : Editor
class XRLineRendererEditor : Editor
{
SerializedProperty m_Materials;
SerializedProperty m_Positions;
@ -35,7 +36,7 @@ namespace UnityEditor
{
EditorGUILayout.PropertyField(m_UseWorldSpace, true);
}
EditorGUILayout.PropertyField(m_Loop, true);
EditorGUILayout.PropertyField(m_Width, true);
EditorGUILayout.CurveField(m_WidthCurve, Color.red, new Rect(0, 0, 1, 1));
@ -43,4 +44,4 @@ namespace UnityEditor
serializedObject.ApplyModifiedProperties();
}
}
}
}

Просмотреть файл

@ -1,10 +1,11 @@
using UnityEngine;
using UnityEditor;
using UnityEngine;
namespace UnityEditor
namespace Unity.Labs.XR
{
[CustomEditor(typeof(XRTrailRenderer))]
[CanEditMultipleObjects]
public class XRTrailRendererEditor : Editor
class XRTrailRendererEditor : Editor
{
SerializedProperty m_Materials;
SerializedProperty m_Time;
@ -47,4 +48,4 @@ namespace UnityEditor
serializedObject.ApplyModifiedProperties();
}
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
com.unity.labs.xrlinerenderer copyright © 2019 Unity Technologies ApS
XR Line Renderer copyright © 2019 Unity Technologies ApS
Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.

7
LICENSE.md.meta Normal file
Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9d7c689de92b8439a8ba59e4ebe085ab
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -1,32 +0,0 @@
Unity Companion License 1.0 ("License")
Copyright (C) 2017 Unity Technologies ApS ("Unity")
Unity hereby grants to you a worldwide, non-exclusive, no-charge, and royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the software that is made available with this License ("Software"), subject to the following terms and conditions:
1. Unity Companion Use Only. Exercise of the license granted herein is limited to exercise for the creation, use, and/or distribution of applications, software, or other content pursuant to a valid Unity development engine software license ("Engine License"). That means while use of the Software is not limited to use in the software licensed under the Engine License, the Software may not be used for any purpose other than the creation, use, and/or distribution of Engine License-dependent applications, software, or other content. No other exercise of the license granted herein is permitted.
2. No Modification of Engine License. Neither this License nor any exercise of the license granted herein modifies the Engine License in any way.
3. Ownership & Grant Back to You.
3.1 You own your content. In this License, "derivative works" means derivatives of the Software itself--works derived only from the Software by you under this License (for example, modifying the code of the Software itself to improve its efficacy); "derivative works" of the Software do not include, for example, games, apps, or content that you create using the Software. You keep all right, title, and interest to your own content.
3.2 Unity owns its content. While you keep all right, title, and interest to your own content per the above, as between Unity and you, Unity will own all right, title, and interest to all intellectual property rights (including patent, trademark, and copyright) in the Software and derivative works of the Software, and you hereby assign and agree to assign all such rights in those derivative works to Unity.
3.3 You have a license to those derivative works. Subject to this License, Unity grants to you the same worldwide, non-exclusive, no-charge, and royalty-free copyright license to derivative works of the Software you create as is granted to you for the Software under this License.
4. Trademarks. You are not granted any right or license under this License to use any trademarks, service marks, trade names, products names, or branding of Unity or its affiliates ("Trademarks"). Descriptive uses of Trademarks are permitted; see, for example, Unity's Branding Usage Guidelines at https://unity3d.com/public-relations/brand.
5. Notices & Third-Party Rights. This License, including the copyright notice above, must be provided in all substantial portions of the Software and derivative works thereof (or, if that is impracticable, in any other location where such notices are customarily placed). Further, if the Software is accompanied by a Unity "third-party notices" or similar file, you acknowledge and agree that software identified in that file is governed by those separate license terms.
6. DISCLAIMER, LIMITATION OF LIABILITY. THE SOFTWARE AND ANY DERIVATIVE WORKS THEREOF IS PROVIDED ON AN "AS IS" BASIS, AND IS PROVIDED WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND/OR NONINFRINGEMENT. IN NO EVENT SHALL ANY COPYRIGHT HOLDER OR AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES (WHETHER DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL, INCLUDING PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, AND BUSINESS INTERRUPTION), OR OTHER LIABILITY WHATSOEVER, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM OR OUT OF, OR IN CONNECTION WITH, THE SOFTWARE OR ANY DERIVATIVE WORKS THEREOF OR THE USE OF OR OTHER DEALINGS IN SAME, EVEN WHERE ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
7. USE IS ACCEPTANCE and License Versions. Your receipt and use of the Software constitutes your acceptance of this License and its terms and conditions. Software released by Unity under this License may be modified or updated and the License with it; upon any such modification or update, you will comply with the terms of the updated License for any use of any of the Software under the updated License.
8. Use in Compliance with Law and Termination. Your exercise of the license granted herein will at all times be in compliance with applicable law and will not infringe any proprietary rights (including intellectual property rights); this License will terminate immediately on any breach by you of this License.
9. Severability. If any provision of this License is held to be unenforceable or invalid, that provision will be enforced to the maximum extent possible and the other provisions will remain in full force and effect.
10. Governing Law and Venue. This License is governed by and construed in accordance with the laws of Denmark, except for its conflict of laws rules; the United Nations Convention on Contracts for the International Sale of Goods will not apply. If you reside (or your principal place of business is) within the United States, you and Unity agree to submit to the personal and exclusive jurisdiction of and venue in the state and federal courts located in San Francisco County, California concerning any dispute arising out of this License ("Dispute"). If you reside (or your principal place of business is) outside the United States, you and Unity agree to submit to the personal and exclusive jurisdiction of and venue in the courts located in Copenhagen, Denmark concerning any Dispute.

Просмотреть файл

Просмотреть файл

@ -0,0 +1,436 @@
using System;
using UnityEngine;
namespace Unity.Labs.XR
{
/// <summary>
/// A unified base class for the XR Line Renderer and XR Trail Renderer
/// </summary>
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[ExecuteInEditMode]
[DisallowMultipleComponent]
public abstract class MeshChainRenderer : MonoBehaviour
{
static readonly GradientColorKey k_DefaultStartColor = new GradientColorKey(Color.white, 0);
static readonly GradientColorKey k_DefaultEndColor = new GradientColorKey(Color.white, 1);
static readonly GradientAlphaKey k_DefaultStartAlpha = new GradientAlphaKey(1, 0);
static readonly GradientAlphaKey k_DefaultEndAlpha = new GradientAlphaKey(1, 1);
/// <summary>
/// Materials to use when rendering.
/// </summary>
[SerializeField]
[Tooltip("Materials to use when rendering.")]
protected Material[] m_Materials;
/// <summary>
/// The multiplier applied to the curve, describing the width (in world space) along the line.
/// </summary>
[SerializeField]
[Tooltip("The multiplier applied to the curve, describing the width (in world space) along the line.")]
protected float m_Width = 1.0f;
/// <summary>
/// The curve describing the width of the line at various points along its length.
/// </summary>
[SerializeField]
[Tooltip("The curve describing the width of the line at various points along its length.")]
protected AnimationCurve m_WidthCurve = new AnimationCurve();
/// <summary>
/// The gradient describing color along the line.
/// </summary>
[SerializeField]
[Tooltip("The gradient describing color along the line.")]
protected Gradient m_Color = new Gradient();
/// <summary>
/// The MeshRenderer used to render the line
/// </summary>
[SerializeField]
[HideInInspector]
protected MeshRenderer m_MeshRenderer;
/// <summary>
/// Cached mesh data
/// </summary>
protected XRMeshChain m_XRMeshData;
/// <summary>
/// Whether the mesh data needs to be refreshed
/// </summary>
protected bool m_MeshNeedsRefreshing;
/// <summary>
/// The step size
/// </summary>
protected float m_StepSize = 1.0f;
/// <summary>
/// Returns the first instantiated Material assigned to the renderer.
/// </summary>
public virtual Material material
{
get { return m_MeshRenderer.material; }
set { m_MeshRenderer.material = value; }
}
/// <summary>
/// Returns all the instantiated materials of this object.
/// </summary>
public virtual Material[] materials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.materials = value; }
}
/// <summary>
/// Returns the shared material of this object.
/// </summary>
public virtual Material sharedMaterial
{
get { return m_MeshRenderer.sharedMaterial; }
set { m_MeshRenderer.sharedMaterial = value; }
}
/// <summary>
/// Returns all shared materials of this object.
/// </summary>
public virtual Material[] SharedMaterials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.sharedMaterials = value; }
}
/// <summary>
/// Set the width at the start of the line.
/// </summary>
public float widthStart
{
get { return m_WidthCurve.Evaluate(0) * m_Width; }
set
{
var keys = m_WidthCurve.keys;
keys[0].value = value;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
/// <summary>
/// Set the width at the end of the line.
/// </summary>
public float widthEnd
{
get { return m_WidthCurve.Evaluate(1) * m_Width; }
set
{
var keys = m_WidthCurve.keys;
var lastIndex = keys.Length - 1;
keys[lastIndex].value = value;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
/// <summary>
/// Set an overall multiplier that is applied to the LineRenderer.widthCurve to get the final width of the line.
/// </summary>
public float widthMultiplier
{
get { return m_Width; }
set
{
m_Width = value;
UpdateWidth();
}
}
/// <summary>
/// Set the curve describing the width of the line at various points along its length.
/// </summary>
public AnimationCurve widthCurve
{
get { return m_WidthCurve; }
set
{
m_WidthCurve = value ?? new AnimationCurve(new Keyframe(0, 1.0f));
UpdateWidth();
}
}
/// <summary>
/// Set the color gradient describing the color of the line at various points along its length.
/// </summary>
public Gradient colorGradient
{
get { return m_Color; }
set
{
if (m_Color == value)
{
return;
}
m_Color = value ?? new Gradient
{
alphaKeys = new[] { k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new[] { k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend
};
UpdateColors();
}
}
/// <summary>
/// Set the color at the start of the line.
/// </summary>
public Color colorStart
{
get { return m_Color.Evaluate(0); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[0].color = flatColor;
alphaKeys[0].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
/// <summary>
/// Set the color at the end of the line.
/// </summary>
public Color colorEnd
{
get { return m_Color.Evaluate(1); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var lastColorIndex = colorKeys.Length - 1;
var lastAlphaIndex = alphaKeys.Length - 1;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[lastColorIndex].color = flatColor;
alphaKeys[lastAlphaIndex].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
/// <summary>
/// Resets the width to a straight curve at the given value
/// </summary>
/// <param name="newWidth">The new width for the curve to display</param>
public void SetTotalWidth(float newWidth)
{
m_Width = newWidth;
m_WidthCurve = new AnimationCurve(new Keyframe(0, 1.0f));
UpdateWidth();
}
/// <summary>
/// Resets the color to a single value
/// </summary>
/// <param name="newColor">The new color for the curve to display</param>
public void SetTotalColor(Color newColor)
{
var flatColor = newColor;
flatColor.a = 1.0f;
m_Color = new Gradient
{
alphaKeys = new[] { new GradientAlphaKey(newColor.a, 0), new GradientAlphaKey(newColor.a, 1), },
colorKeys = new[] { new GradientColorKey(flatColor, 0), new GradientColorKey(flatColor, 1) },
mode = GradientMode.Blend
};
UpdateColors();
}
void OnValidate()
{
SetupMeshBackend();
if (NeedsReinitialize())
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.delayCall += EditorCheckForUpdate;
#endif
}
else
{
EditorCheckForUpdate();
}
}
/// <summary>
/// Cleans up the visible interface of the meshrenderer by hiding unneeded components
/// Also makes sure the animation curves are set up properly to defualts
/// </summary>
void SetupMeshBackend()
{
m_MeshRenderer = GetComponent<MeshRenderer>();
m_MeshRenderer.hideFlags = HideFlags.HideInInspector;
var meshFilter = GetComponent<MeshFilter>();
meshFilter.hideFlags = HideFlags.HideInInspector;
if (m_Materials == null || m_Materials.Length == 0)
{
m_Materials = m_MeshRenderer.sharedMaterials;
}
m_MeshRenderer.sharedMaterials = m_Materials;
if (m_WidthCurve == null || m_WidthCurve.keys == null || m_WidthCurve.keys.Length == 0)
{
m_WidthCurve = new AnimationCurve(new Keyframe(0, 1.0f));
}
m_Color = m_Color ?? new Gradient
{
alphaKeys = new[] { k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new[] { k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend
};
}
/// <summary>
/// Makes the sure mesh renderer reference is initialized before any functions try to access it
/// </summary>
protected virtual void Awake()
{
SetupMeshBackend();
Initialize();
}
/// <summary>
/// Makes the sure mesh renderer reference is initialized on reset of the component
/// </summary>
void Reset()
{
SetupMeshBackend();
Initialize();
}
/// <summary>
/// Ensures the lines have all their data precached upon loading
/// </summary>
void Start()
{
Initialize();
}
#if UNITY_EDITOR
/// <summary>
/// Ensures the hidden mesh/meshfilters are destroyed if users are messing with the components in edit mode
/// </summary>
void OnDestroy()
{
if (!Application.isPlaying)
{
var rendererToDestroy = m_MeshRenderer;
var filterToDestroy = gameObject.GetComponent<MeshFilter>();
UnityEditor.EditorApplication.delayCall += () =>
{
if (!Application.isPlaying)
{
if (rendererToDestroy != null)
{
DestroyImmediate(rendererToDestroy);
}
if (filterToDestroy != null)
{
DestroyImmediate(filterToDestroy);
}
}
};
}
}
#endif
/// <summary>
/// Does the actual internal mesh updating as late as possible so nothing ends up a frame behind
/// </summary>
protected virtual void LateUpdate()
{
if (m_MeshNeedsRefreshing == true)
{
m_XRMeshData.RefreshMesh();
m_MeshNeedsRefreshing = false;
}
}
/// <summary>
/// Allows the component to be referenced as a renderer, forwarding the MeshRenderer ahead
/// </summary>
/// <param name="meshChainRenderer"></param>
/// <returns>The MeshChainRenderer's MeshRenderer</returns>
public static implicit operator Renderer(MeshChainRenderer meshChainRenderer)
{
return meshChainRenderer.m_MeshRenderer;
}
/// <summary>
/// Triggered by validation - forced initialization to make sure data changed
/// in the editor is reflected immediately to the user.
/// </summary>
void EditorCheckForUpdate()
{
// Because this gets delay-called, it can be referring to a destroyed component when a scene starts
if (this != null)
{
// If we did not initialize, refresh all the properties instead
Initialize();
}
}
/// <summary>
/// Updates any internal variables to represent the new color that has been applied
/// </summary>
protected virtual void UpdateColors() { }
/// <summary>
/// Updates any internal variables to represent the new width that has been applied
/// </summary>
protected virtual void UpdateWidth() { }
/// <summary>
/// Creates or updates the underlying mesh data
/// </summary>
protected virtual void Initialize() { }
/// <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 virtual bool NeedsReinitialize()
{
return true;
}
/// <summary>
/// Enables the internal mesh representing the line
/// </summary>
protected virtual void OnEnable()
{
m_MeshRenderer.enabled = true;
}
/// <summary>
/// Disables the internal mesh representing the line
/// </summary>
protected virtual void OnDisable()
{
m_MeshRenderer.enabled = false;
}
}
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1,8 @@
{
"name": "Unity.Labs.XRLineRenderer",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
}

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 507fb7fe0a2794ac481eab674e8b99cf
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

508
Runtime/XRLineRenderer.cs Normal file
Просмотреть файл

@ -0,0 +1,508 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Labs.XR
{
/// <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()
{
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);
}
// 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);
}
}
}

Просмотреть файл

347
Runtime/XRMeshChain.cs Normal file
Просмотреть файл

@ -0,0 +1,347 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Labs.XR
{
/// <summary>
/// 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.
/// </summary>
public class XRMeshChain
{
/// <summary>
/// What part of the mesh to refresh
/// </summary>
[System.Flags]
public enum MeshRefreshFlag
{
/// <summary>
/// Don't refresh any of the mesh
/// </summary>
None = 0,
/// <summary>
/// Refresh positions
/// </summary>
Positions = 1,
/// <summary>
/// Refresh colors
/// </summary>
Colors = 2,
/// <summary>
/// Refresh sizes
/// </summary>
Sizes = 4,
/// <summary>
/// Refresh all mesh components
/// </summary>
All = 7
}
Vector3[] m_Verts;
Color32[] m_Colors;
List<Vector4> m_ShapeData; // xy: UV coordinates for GPU expansion zw: Size of this vertex, size of the neighbor
List<Vector3> 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;
/// <summary>
/// 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
/// </summary>
public bool worldSpaceData
{
get { return m_WorldSpaceData; }
set { m_WorldSpaceData = value; }
}
/// <summary>
/// How many primitives/quads this mesh chain supports and has reserved memory for
/// </summary>
public int reservedElements { get; private set; }
/// <summary>
/// If using world space data, this option will force the center of the bounding box to be the root
/// </summary>
public bool centerAtRoot { get; set; }
/// <summary>
/// Initialize a new XRMeshChain
/// </summary>
public XRMeshChain()
{
reservedElements = 0;
}
/// <summary>
/// Creates or re-creates the mesh with all the data needed for billboard-pipe based line rendering
/// </summary>
/// <param name="owner">The gameobject that will own the created mesh</param>
/// <param name="dynamic">Whether this mesh is going to updated frequently or not</param>
/// <param name="totalElements">How many total billboards and pipes are needed for this renderer</param>
public void GenerateMesh(GameObject owner, bool dynamic, int totalElements)
{
// Precache neccessary data
// The mesh, vertex and triangle counts
if (m_Mesh == null)
{
m_Mesh = new Mesh();
}
if (dynamic == true)
{
m_Mesh.MarkDynamic();
}
owner.GetComponent<MeshFilter>().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<Vector4>(vertCount);
m_NeighborPoints = new List<Vector3>(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;
}
/// <summary>
/// Updates any mesh vertex data that is marked as dirty
/// </summary>
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;
}
}
/// <summary>
/// Used by external classes to alert the mesh chain that they have modified its data
/// </summary>
/// <param name="dataThatNeedsUpdate">Which type of data (position, color, size) has been changed</param>
public void SetMeshDataDirty(MeshRefreshFlag dataThatNeedsUpdate)
{
m_DataThatNeedsUpdate |= dataThatNeedsUpdate;
}
/// <summary>
/// Sets the position of a specific point in the chain
/// </summary>
/// <param name="elementIndex">Which control point to update</param>
/// <param name="position">The updated position of the control point</param>
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;
}
/// <summary>
/// Sets the endpoints of a pipe in the chain - The pipe equivalent of SetElementPosition
/// </summary>
/// <param name="elementIndex">Which control pipe to update</param>
/// <param name="startPoint">The position of the previous control point being connected to</param>
/// <param name="endPoint">The position of the next control point being connected to</param>
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;
}
/// <summary>
/// Sets the size of the billboard or pipe being rendered
/// </summary>
/// <param name="elementIndex">The index of the control point to update</param>
/// <param name="sizeModification">What the radius or width of the element should be</param>
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);
}
/// <summary>
/// Sets the size of the pipe being rendered
/// </summary>
/// <param name="elementIndex">The index of the pipe control point to update</param>
/// <param name="startSize">The start size of the pipe</param>
/// <param name="endSize">The end size of the pipe</param>
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);
}
/// <summary>
/// Sets the color of a billboard or pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the element we are coloring</param>
/// <param name="color">What the color of this element should be</param>
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];
}
/// <summary>
/// Sets the color of a billboard or pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the element we are coloring</param>
/// <param name="color">What the color of this element should be</param>
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;
}
/// <summary>
/// Sets the colors of a pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the pipe we are coloring</param>
/// <param name="startColor">The color of the startpoint of the pipe</param>
/// <param name="endColor">The color of the endpoint of the pipe</param>
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];
}
/// <summary>
/// Sets the colors of a pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the pipe we are coloring</param>
/// <param name="startColor">The color of the startpoint of the pipe</param>
/// <param name="endColor">The color of the endpoint of the pipe</param>
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];
}
}
}

Просмотреть файл

383
Runtime/XRTrailRenderer.cs Normal file
Просмотреть файл

@ -0,0 +1,383 @@
using System;
using UnityEngine;
namespace Unity.Labs.XR
{
/// <summary>
/// An XR-Focused drop-in replacement for the Trail 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 XRTrailRenderer : MeshChainRenderer
{
const float k_AbsoluteMinVertexDistance = 0.01f;
// Stored Trail Data
[SerializeField]
[Tooltip("How many points to store for tracing.")]
int m_MaxTrailPoints = 20;
[SerializeField]
[Tooltip("Whether to use the last point or the first point of the trail when more are needed and none are available.")]
bool m_StealLastPointWhenEmpty = true;
[SerializeField]
[Tooltip("How long the tail should be (second) [ 0, infinity ].")]
float m_Time = 5.0f;
[SerializeField]
[Tooltip("The minimum distance to spawn a new point on the trail [ 0, infinity ].")]
float m_MinVertexDistance = 0.1f;
[SerializeField]
[Tooltip("Destroy GameObject when there is no trail?")]
bool m_Autodestruct = false;
[SerializeField]
[Tooltip("With this enabled, the last point will smooth lerp between the last recorded anchor point and the one after it")]
bool m_SmoothInterpolation = false;
// Circular array support for trail point recording
Vector3[] m_Points;
float[] m_PointTimes;
int m_PointIndexStart = 0;
int m_PointIndexEnd = 0;
// Cached Data
Vector3 m_LastRecordedPoint = Vector3.zero;
float m_LastPointTime;
float m_EditorDeltaHelper; // This lets us have access to a time data while not in play mode
/// <summary>
/// How long does the trail take to fade out.
/// </summary>
public float time
{
get { return m_Time; }
set { m_Time = Mathf.Max(value, 0); }
}
/// <summary>
/// Set the minimum distance the trail can travel before a new vertex is added to it.
/// </summary>
public float minVertexDistance
{
get { return m_MinVertexDistance; }
set { m_MinVertexDistance = Mathf.Max(value, k_AbsoluteMinVertexDistance); }
}
/// <summary>
/// Get the number of line segments in the trail
/// </summary>
public int positionCount { get; private set; }
/// <summary>
/// Destroy GameObject when there is no trail?
/// </summary>
public bool autodestruct
{
get { return m_Autodestruct; }
set { m_Autodestruct = value; }
}
/// <summary>
/// Set if the last point will smooth lerp between the last recorded anchor point and the one after it
/// </summary>
public bool smoothInterpolation
{
get { return m_SmoothInterpolation; }
set { m_SmoothInterpolation = value; }
}
/// <summary>
/// Updates the built-in mesh data for each control point of the trail
/// </summary>
protected override void LateUpdate()
{
// We do the actual internal mesh updating as late as possible so nothing ends up a frame behind
var deltaTime = Time.deltaTime;
// We give the editor a little help with handling delta time in edit mode
if (Application.isPlaying == false)
{
deltaTime = Time.realtimeSinceStartup - m_EditorDeltaHelper;
m_EditorDeltaHelper = Time.realtimeSinceStartup;
}
// Get the current position of the renderer
var currentPoint = transform.position;
var pointDistance = (currentPoint - m_LastRecordedPoint).sqrMagnitude;
var shrunkThisFrame = false;
// Is it more than minVertexDistance from the last position?
if (pointDistance > (m_MinVertexDistance * m_MinVertexDistance))
{
// In the situation we have no points, we need to record the start point as well
if (m_PointIndexStart == m_PointIndexEnd)
{
m_Points[m_PointIndexStart] = m_LastRecordedPoint;
m_PointTimes[m_PointIndexStart] = m_Time;
}
// Make space for a new point
var newEndIndex = (m_PointIndexEnd + 1) % m_MaxTrailPoints;
// In the situation that we are rendering all available vertices
// We can either keep using the current point, or take the last point, depending on the user's preference
if (newEndIndex != m_PointIndexStart)
{
m_PointIndexEnd = newEndIndex;
m_PointTimes[m_PointIndexEnd] = 0;
positionCount++;
}
else
{
if (m_StealLastPointWhenEmpty)
{
m_XRMeshData.SetElementSize(m_PointIndexStart * 2, 0);
m_XRMeshData.SetElementSize((m_PointIndexStart * 2) + 1, 0);
m_PointIndexStart = (m_PointIndexStart + 1) % m_MaxTrailPoints;
m_PointIndexEnd = newEndIndex;
m_PointTimes[m_PointIndexEnd] = 0;
m_LastPointTime = m_PointTimes[m_PointIndexStart];
}
}
m_Points[m_PointIndexEnd] = currentPoint;
// Update the last recorded point
m_LastRecordedPoint = currentPoint;
}
// Do time processing
// The end point counts up to a maximum of 'time'
m_PointTimes[m_PointIndexEnd] = Mathf.Min(m_PointTimes[m_PointIndexEnd] + deltaTime, m_Time);
if (m_PointIndexStart != m_PointIndexEnd)
{
// Run down the counter on the start point
m_PointTimes[m_PointIndexStart] -= deltaTime;
// If we've hit 0, this point is done for
if (m_PointTimes[m_PointIndexStart] <= 0.0f)
{
m_XRMeshData.SetElementSize(m_PointIndexStart * 2, 0);
m_XRMeshData.SetElementSize((m_PointIndexStart * 2) + 1, 0);
m_PointIndexStart = (m_PointIndexStart + 1) % m_MaxTrailPoints;
m_LastPointTime = m_PointTimes[m_PointIndexStart];
positionCount--;
shrunkThisFrame = true;
}
}
if (m_PointIndexStart != m_PointIndexEnd)
{
m_MeshNeedsRefreshing = true;
m_MeshRenderer.enabled = true;
}
else
{
m_MeshNeedsRefreshing = false;
m_MeshRenderer.enabled = false;
if (m_Autodestruct && Application.isPlaying && shrunkThisFrame)
{
Destroy(gameObject);
}
}
if (m_MeshNeedsRefreshing)
{
m_MeshRenderer.enabled = true;
// Update first and last points position-wise
var nextIndex = (m_PointIndexStart + 1) % m_MaxTrailPoints;
if (m_SmoothInterpolation)
{
var toNextPoint = 1.0f - (m_PointTimes[m_PointIndexStart] / m_LastPointTime);
var lerpPoint = Vector3.Lerp(m_Points[m_PointIndexStart], m_Points[nextIndex], toNextPoint);
m_XRMeshData.SetElementPosition((m_PointIndexStart * 2), ref lerpPoint);
m_XRMeshData.SetElementPipe((m_PointIndexStart * 2) + 1, ref lerpPoint, ref m_Points[nextIndex]);
}
else
{
m_XRMeshData.SetElementPosition((m_PointIndexStart * 2), ref m_Points[m_PointIndexStart]);
m_XRMeshData.SetElementPipe((m_PointIndexStart * 2) + 1, ref m_Points[m_PointIndexStart], ref m_Points[nextIndex]);
}
var prevIndex = m_PointIndexEnd - 1;
if (prevIndex < 0)
{
prevIndex = m_MaxTrailPoints - 1;
}
m_XRMeshData.SetElementPipe((prevIndex * 2) + 1, ref m_Points[prevIndex], ref m_Points[m_PointIndexEnd]);
m_XRMeshData.SetElementPosition((m_PointIndexEnd * 2), ref m_Points[m_PointIndexEnd]);
// Go through all points and update size and color
var pointUpdateCounter = m_PointIndexStart;
var pointCount = 0;
m_StepSize = (positionCount > 0) ? (1.0f / positionCount) : 1.0f;
var percent = 0.0f;
var lastWidth = m_WidthCurve.Evaluate(percent) * m_Width;
var lastColor = m_Color.Evaluate(percent);
percent += m_StepSize;
while (pointUpdateCounter != m_PointIndexEnd)
{
var nextWidth = m_WidthCurve.Evaluate(percent) * m_Width;
m_XRMeshData.SetElementSize(pointUpdateCounter * 2, lastWidth);
m_XRMeshData.SetElementSize((pointUpdateCounter * 2) + 1, lastWidth, nextWidth);
lastWidth = nextWidth;
var nextColor = m_Color.Evaluate(percent);
m_XRMeshData.SetElementColor(pointUpdateCounter * 2, ref lastColor);
m_XRMeshData.SetElementColor((pointUpdateCounter * 2) + 1, ref lastColor, ref nextColor);
lastColor = nextColor;
pointUpdateCounter = (pointUpdateCounter + 1) % m_MaxTrailPoints;
pointCount++;
percent += m_StepSize;
}
lastWidth = m_WidthCurve.Evaluate(1) * m_Width;
m_XRMeshData.SetElementSize((m_PointIndexEnd * 2), lastWidth);
lastColor = m_Color.Evaluate(1);
m_XRMeshData.SetElementColor((m_PointIndexEnd * 2), ref lastColor);
m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
m_XRMeshData.RefreshMesh();
}
}
/// <summary>
/// Editor helper function to ensure changes are reflected in edit-mode
/// </summary>
public void EditorCheckForUpdate()
{
// If we did not initialize, refresh all the properties instead
Initialize();
}
/// <summary>
/// Removes all points from the TrailRenderer. Useful for restarting a trail from a new position.
/// </summary>
public void Clear()
{
var zeroVec = Vector3.zero;
var zeroColor = Color.clear;
var elementCounter = 0;
var pointCounter = 0;
while (pointCounter < m_Points.Length)
{
// Start point
m_XRMeshData.SetElementSize(elementCounter, 0);
m_XRMeshData.SetElementPosition(elementCounter, ref zeroVec);
m_XRMeshData.SetElementColor(elementCounter, ref zeroColor);
elementCounter++;
// Pipe to the next point
m_XRMeshData.SetElementSize(elementCounter, 0);
m_XRMeshData.SetElementPipe(elementCounter, ref zeroVec, ref zeroVec);
m_XRMeshData.SetElementColor(elementCounter, ref zeroColor);
// Go onto the next point while retaining previous values we might need to lerp between
elementCounter++;
pointCounter++;
}
m_PointIndexStart = 0;
m_PointIndexEnd = 0;
positionCount = 0;
m_LastRecordedPoint = transform.position;
}
/// <summary>
/// Creates or updates the underlying mesh data
/// </summary>
protected override void Initialize()
{
base.Initialize();
m_MaxTrailPoints = Mathf.Max(m_MaxTrailPoints, 3);
// If we already have the right amount of points and mesh, then we can get away with just clearing the curve out
if (m_Points != null && m_MaxTrailPoints == m_Points.Length && m_XRMeshData != null)
{
Clear();
return;
}
m_Points = new Vector3[m_MaxTrailPoints];
m_PointTimes = new float[m_MaxTrailPoints];
// For a trail renderer we assume one big chain
// We need a control point for each billboard and a control point for each pipe connecting them together
// We make this a circular trail so the update logic is easier. This gives us (position * 2)
var neededPoints = Mathf.Max((m_MaxTrailPoints * 2), 0);
if (m_XRMeshData == null)
{
m_XRMeshData = new XRMeshChain();
}
if (m_XRMeshData.reservedElements != neededPoints)
{
m_XRMeshData.worldSpaceData = true;
m_XRMeshData.centerAtRoot = true;
m_XRMeshData.GenerateMesh(gameObject, true, neededPoints);
if (neededPoints == 0)
{
return;
}
// Dirty all the VRMeshChain flags so everything gets refreshed
m_MeshRenderer.enabled = false;
m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
m_MeshNeedsRefreshing = true;
}
Clear();
}
/// <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;
}
// Mismatched point data means we definitely need to reinitialize
if (m_Points == null || m_MaxTrailPoints != m_Points.Length)
{
return true;
}
m_MaxTrailPoints = Mathf.Max(m_MaxTrailPoints, 3);
var neededPoints = Mathf.Max((m_MaxTrailPoints * 2), 0);
return (m_XRMeshData.reservedElements != neededPoints);
}
/// <summary>
/// Enables the internal mesh representing the line
/// </summary>
protected override void OnEnable()
{
m_MeshRenderer.enabled = (m_PointIndexStart != m_PointIndexEnd);
}
}
}

Просмотреть файл

Просмотреть файл

@ -1,390 +0,0 @@
using System;
using UnityEngine;
/// <summary>
/// A unified base class for the XR Line Renderer and XR Trail Renderer
/// </summary>
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[ExecuteInEditMode]
[DisallowMultipleComponent]
public abstract class MeshChainRenderer : MonoBehaviour
{
static readonly GradientColorKey k_DefaultStartColor = new GradientColorKey(Color.white, 0);
static readonly GradientColorKey k_DefaultEndColor = new GradientColorKey(Color.white, 1);
static readonly GradientAlphaKey k_DefaultStartAlpha = new GradientAlphaKey(1,0);
static readonly GradientAlphaKey k_DefaultEndAlpha = new GradientAlphaKey(1,1);
[SerializeField]
[Tooltip("Materials to use when rendering.")]
protected Material[] m_Materials;
[SerializeField]
[Tooltip("The multiplier applied to the curve, describing the width (in world space) along the line.")]
protected float m_Width = 1.0f;
[SerializeField]
[Tooltip("The curve describing the width of the line at various points along its length.")]
protected AnimationCurve m_WidthCurve = new AnimationCurve();
[SerializeField]
[Tooltip("The gradient describing color along the line.")]
protected Gradient m_Color = new Gradient();
[SerializeField]
[HideInInspector]
protected MeshRenderer m_MeshRenderer;
// Cached Data
protected XRMeshChain m_XRMeshData;
protected bool m_MeshNeedsRefreshing;
protected float m_StepSize = 1.0f;
/// <summary>
/// Returns the first instantiated Material assigned to the renderer.
/// </summary>
public virtual Material material
{
get { return m_MeshRenderer.material; }
set { m_MeshRenderer.material = value; }
}
/// <summary>
/// Returns all the instantiated materials of this object.
/// </summary>
public virtual Material[] materials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.materials = value; }
}
/// <summary>
/// Returns the shared material of this object.
/// </summary>
public virtual Material sharedMaterial
{
get { return m_MeshRenderer.sharedMaterial; }
set { m_MeshRenderer.sharedMaterial = value; }
}
/// <summary>
/// Returns all shared materials of this object.
/// </summary>
public virtual Material[] SharedMaterials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.sharedMaterials = value; }
}
/// <summary>
/// Set the width at the start of the line.
/// </summary>
public float widthStart
{
get { return m_WidthCurve.Evaluate(0) * m_Width; }
set
{
var keys = m_WidthCurve.keys;
keys[0].value = value;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
/// <summary>
/// Set the width at the end of the line.
/// </summary>
public float widthEnd
{
get { return m_WidthCurve.Evaluate(1) * m_Width; }
set
{
var keys = m_WidthCurve.keys;
var lastIndex = keys.Length - 1;
keys[lastIndex].value = value;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
/// <summary>
/// Set an overall multiplier that is applied to the LineRenderer.widthCurve to get the final width of the line.
/// </summary>
public float widthMultiplier
{
get { return m_Width; }
set
{
m_Width = value;
UpdateWidth();
}
}
/// <summary>
/// Set the curve describing the width of the line at various points along its length.
/// </summary>
public AnimationCurve widthCurve
{
get { return m_WidthCurve; }
set
{
m_WidthCurve = value ?? new AnimationCurve(new Keyframe(0,1.0f));
UpdateWidth();
}
}
/// <summary>
/// Set the color gradient describing the color of the line at various points along its length.
/// </summary>
public Gradient colorGradient
{
get { return m_Color; }
set
{
if (m_Color == value)
{
return;
}
m_Color = value ?? new Gradient { alphaKeys = new []{ k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new []{ k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend };
UpdateColors();
}
}
/// <summary>
/// Set the color at the start of the line.
/// </summary>
public Color colorStart
{
get { return m_Color.Evaluate(0); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[0].color = flatColor;
alphaKeys[0].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
/// <summary>
/// Set the color at the end of the line.
/// </summary>
public Color colorEnd
{
get { return m_Color.Evaluate(1); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var lastColorIndex = colorKeys.Length - 1;
var lastAlphaIndex = alphaKeys.Length - 1;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[lastColorIndex].color = flatColor;
alphaKeys[lastAlphaIndex].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
/// <summary>
/// Resets the width to a straight curve at the given value
/// </summary>
/// <param name="newWidth">The new width for the curve to display</param>
public void SetTotalWidth(float newWidth)
{
m_Width = newWidth;
m_WidthCurve = new AnimationCurve(new Keyframe(0,1.0f));
UpdateWidth();
}
/// <summary>
/// Resets the color to a single value
/// </summary>
/// <param name="newColor">The new color for the curve to display</param>
public void SetTotalColor(Color newColor)
{
var flatColor = newColor;
flatColor.a = 1.0f;
m_Color = new Gradient { alphaKeys = new []{ new GradientAlphaKey(newColor.a, 0), new GradientAlphaKey(newColor.a, 1), },
colorKeys = new []{ new GradientColorKey(flatColor, 0), new GradientColorKey(flatColor, 1) },
mode = GradientMode.Blend };
UpdateColors();
}
void OnValidate()
{
SetupMeshBackend();
if (NeedsReinitialize())
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.delayCall += EditorCheckForUpdate;
#endif
}
else
{
EditorCheckForUpdate();
}
}
/// <summary>
/// Cleans up the visible interface of the meshrenderer by hiding unneeded components
/// Also makes sure the animation curves are set up properly to defualts
/// </summary>
void SetupMeshBackend()
{
m_MeshRenderer = GetComponent<MeshRenderer>();
m_MeshRenderer.hideFlags = HideFlags.HideInInspector;
var meshFilter = GetComponent<MeshFilter>();
meshFilter.hideFlags = HideFlags.HideInInspector;
if (m_Materials == null || m_Materials.Length == 0)
{
m_Materials = m_MeshRenderer.sharedMaterials;
}
m_MeshRenderer.sharedMaterials = m_Materials;
if (m_WidthCurve == null || m_WidthCurve.keys == null || m_WidthCurve.keys.Length == 0)
{
m_WidthCurve = new AnimationCurve(new Keyframe(0, 1.0f));
}
m_Color = m_Color ?? new Gradient { alphaKeys = new[] { k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new[] { k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend };
}
/// <summary>
/// Makes the sure mesh renderer reference is initialized before any functions try to access it
/// </summary>
protected virtual void Awake()
{
SetupMeshBackend();
Initialize();
}
/// <summary>
/// Makes the sure mesh renderer reference is initialized on reset of the component
/// </summary>
void Reset()
{
SetupMeshBackend();
Initialize();
}
/// <summary>
/// Ensures the lines have all their data precached upon loading
/// </summary>
void Start()
{
Initialize();
}
#if UNITY_EDITOR
/// <summary>
/// Ensures the hidden mesh/meshfilters are destroyed if users are messing with the components in edit mode
/// </summary>
void OnDestroy()
{
if(!Application.isPlaying)
{
var rendererToDestroy = m_MeshRenderer;
var filterToDestroy = gameObject.GetComponent<MeshFilter>();
UnityEditor.EditorApplication.delayCall += ()=>
{
if (!Application.isPlaying)
{
if (rendererToDestroy != null)
{
DestroyImmediate(rendererToDestroy);
}
if (filterToDestroy != null)
{
DestroyImmediate(filterToDestroy);
}
}
};
}
}
#endif
/// <summary>
/// Does the actual internal mesh updating as late as possible so nothing ends up a frame behind
/// </summary>
protected virtual void LateUpdate()
{
if (m_MeshNeedsRefreshing == true)
{
m_XRMeshData.RefreshMesh();
m_MeshNeedsRefreshing = false;
}
}
/// <summary>
/// Allows the component to be referenced as a renderer, forwarding the MeshRenderer ahead
/// </summary>
public static implicit operator Renderer(MeshChainRenderer lr)
{
return lr.m_MeshRenderer;
}
/// <summary>
/// Triggered by validation - forced initialization to make sure data changed
/// in the editor is reflected immediately to the user.
/// </summary>
void EditorCheckForUpdate()
{
// Because this gets delay-called, it can be referring to a destroyed component when a scene starts
if (this != null)
{
// If we did not initialize, refresh all the properties instead
Initialize();
}
}
/// <summary>
/// Updates any internal variables to represent the new color that has been applied
/// </summary>
protected virtual void UpdateColors() {}
/// <summary>
/// Updates any internal variables to represent the new width that has been applied
/// </summary>
protected virtual void UpdateWidth() {}
/// <summary>
/// Creates or updates the underlying mesh data
/// </summary>
protected virtual void Initialize() { }
/// <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 virtual bool NeedsReinitialize() { return true; }
/// <summary>
/// Enables the internal mesh representing the line
/// </summary>
protected virtual void OnEnable()
{
m_MeshRenderer.enabled = true;
}
/// <summary>
/// Disables the internal mesh representing the line
/// </summary>
protected virtual void OnDisable()
{
m_MeshRenderer.enabled = false;
}
}

Просмотреть файл

@ -1,464 +0,0 @@
using UnityEngine;
using UnityEngine.Serialization;
/// <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;
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; } }
public override Material material
{
get { return m_MeshRenderer.material; }
set
{
m_MeshRenderer.material = value;
CopyWorldSpaceDataFromMaterial();
}
}
public override Material[] materials
{
get { return m_MeshRenderer.materials; }
set
{
m_MeshRenderer.materials = value;
CopyWorldSpaceDataFromMaterial();
}
}
public override Material sharedMaterial
{
get { return m_MeshRenderer.sharedMaterial; }
set
{
m_MeshRenderer.sharedMaterial = value;
CopyWorldSpaceDataFromMaterial();
}
}
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>
public int GetVertexCount()
{
return m_Positions.Length;
}
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;
}
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;
}
protected override void Initialize()
{
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);
}
// 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;
}
protected override bool NeedsReinitialize()
{
// No mesh data means we definately 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);
}
}

Просмотреть файл

@ -1,309 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 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.
/// </summary>
public class XRMeshChain
{
[System.Flags]
public enum MeshRefreshFlag
{
None = 0,
Positions = 1,
Colors = 2,
Sizes = 4,
All = 7
}
Vector3[] m_Verts;
Color32[] m_Colors;
List<Vector4> m_ShapeData; // xy: UV coordinates for GPU expansion zw: Size of this vertex, size of the neighbor
List<Vector3> m_NeighborPoints; // Location of the next point this pipe connects to, or itself if it is a billboard
// Update flags to prevent unncessary mesh data generation
MeshRefreshFlag m_DataThatNeedsUpdate = MeshRefreshFlag.All;
// Cached runtime data
Mesh m_Mesh;
Transform m_OwnerTransform;
bool m_WorldSpaceData;
/// <summary>
/// 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
/// </summary>
public bool worldSpaceData { get { return m_WorldSpaceData; } set { m_WorldSpaceData = value; } }
/// <summary>
/// How many primitives/quads this mesh chain supports and has reserved memory for
/// </summary>
public int reservedElements { get; private set; }
/// <summary>
/// If using world space data, this option will force the center of the bounding box to be the root
/// </summary>
public bool centerAtRoot { get; set; }
public XRMeshChain()
{
reservedElements = 0;
}
/// <summary>
/// Creates or re-creates the mesh with all the data needed for billboard-pipe based line rendering
/// </summary>
/// <param name="owner">The gameobject that will own the created mesh</param>
/// <param name="dynamic">Whether this mesh is going to updated frequently or not</param>
/// <param name="totalElements">How many total billboards and pipes are needed for this renderer</param>
public void GenerateMesh(GameObject owner, bool dynamic, int totalElements)
{
// Precache neccessary data
// The mesh, vertex and triangle counts
if (m_Mesh == null)
{
m_Mesh = new Mesh();
}
if (dynamic == true)
{
m_Mesh.MarkDynamic();
}
owner.GetComponent<MeshFilter>().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<Vector4>(vertCount);
m_NeighborPoints = new List<Vector3>(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;
}
/// <summary>
/// Updates any mesh vertex data that is marked as dirty
/// </summary>
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;
}
}
/// <summary>
/// Used by external classes to alert the mesh chain that they have modified its data
/// </summary>
/// <param name="dataThatNeedsUpdate">Which type of data (position, color, size) has been changed</param>
public void SetMeshDataDirty(MeshRefreshFlag dataThatNeedsUpdate)
{
m_DataThatNeedsUpdate |= dataThatNeedsUpdate;
}
/// <summary>
/// Sets the position of a specific point in the chain
/// </summary>
/// <param name="elementIndex">Which control point to update</param>
/// <param name="position">The updated position of the control point</param>
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;
}
/// <summary>
/// Sets the endpoints of a pipe in the chain - The pipe equivalent of SetElementPosition
/// </summary>
/// <param name="elementIndex">Which control pipe to update</param>
/// <param name="startPoint">The position of the previous control point being connected to</param>
/// <param name="endPoint">The position of the next control point being connected to</param>
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;
}
/// <summary>
/// Sets the size of the billboard or pipe being rendered
/// </summary>
/// <param name="elementIndex">The index of the control point to update</param>
/// <param name="sizeModification">What the radius or width of the element should be</param>
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);
}
/// <summary>
/// Sets the size of the pipe being rendered
/// </summary>
/// <param name="elementIndex">The index of the pipe control point to update</param>
/// <param name="sizeModification">What the width of the pipe should be</param>
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);
}
/// <summary>
/// Sets the color of a billboard or pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the element we are coloring</param>
/// <param name="color">What the color of this element should be</param>
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];
}
/// <summary>
/// Sets the color of a billboard or pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the element we are coloring</param>
/// <param name="color">What the color of this element should be</param>
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;
}
/// <summary>
/// Sets the colors of a pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the pipe we are coloring</param>
/// <param name="startColor">The color of the startpoint of the pipe</param>
/// <param name="endColor">The color of the endpoint of the pipe</param>
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];
}
/// <summary>
/// Sets the colors of a pipe in the chain
/// </summary>
/// <param name="elementIndex">The index of the pipe we are coloring</param>
/// <param name="startColor">The color of the startpoint of the pipe</param>
/// <param name="endColor">The color of the endpoint of the pipe</param>
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];
}
}

Просмотреть файл

@ -1,364 +0,0 @@
using System;
using UnityEngine;
/// <summary>
/// An XR-Focused drop-in replacement for the Trail 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 XRTrailRenderer : MeshChainRenderer
{
const float k_AbsoluteMinVertexDistance = 0.01f;
// Stored Trail Data
[SerializeField]
[Tooltip("How many points to store for tracing.")]
int m_MaxTrailPoints = 20;
[SerializeField]
[Tooltip("Whether to use the last point or the first point of the trail when more are needed and none are available.")]
bool m_StealLastPointWhenEmpty = true;
[SerializeField]
[Tooltip("How long the tail should be (second) [ 0, infinity ].")]
float m_Time = 5.0f;
[SerializeField]
[Tooltip("The minimum distance to spawn a new point on the trail [ 0, infinity ].")]
float m_MinVertexDistance = 0.1f;
[SerializeField]
[Tooltip("Destroy GameObject when there is no trail?")]
bool m_Autodestruct = false;
[SerializeField]
[Tooltip("With this enabled, the last point will smooth lerp between the last recorded anchor point and the one after it")]
bool m_SmoothInterpolation = false;
// Circular array support for trail point recording
Vector3[] m_Points;
float[] m_PointTimes;
int m_PointIndexStart = 0;
int m_PointIndexEnd = 0;
// Cached Data
Vector3 m_LastRecordedPoint = Vector3.zero;
float m_LastPointTime;
float m_EditorDeltaHelper; // This lets us have access to a time data while not in play mode
/// <summary>
/// How long does the trail take to fade out.
/// </summary>
public float time
{
get { return m_Time; }
set { m_Time = Mathf.Max(value, 0); }
}
/// <summary>
/// Set the minimum distance the trail can travel before a new vertex is added to it.
/// </summary>
public float minVertexDistance
{
get { return m_MinVertexDistance; }
set { m_MinVertexDistance = Mathf.Max(value, k_AbsoluteMinVertexDistance); }
}
/// <summary>
/// Get the number of line segments in the trail
/// </summary>
public int positionCount { get; private set; }
/// <summary>
/// Destroy GameObject when there is no trail?
/// </summary>
public bool autodestruct
{
get { return m_Autodestruct; }
set { m_Autodestruct = value; }
}
/// <summary>
/// Set if the last point will smooth lerp between the last recorded anchor point and the one after it
/// </summary>
public bool smoothInterpolation
{
get { return m_SmoothInterpolation; }
set { m_SmoothInterpolation = value; }
}
/// <summary>
/// Updates the built-in mesh data for each control point of the trail
/// </summary>
protected override void LateUpdate()
{
// We do the actual internal mesh updating as late as possible so nothing ends up a frame behind
var deltaTime = Time.deltaTime;
// We give the editor a little help with handling delta time in edit mode
if (Application.isPlaying == false)
{
deltaTime = Time.realtimeSinceStartup - m_EditorDeltaHelper;
m_EditorDeltaHelper = Time.realtimeSinceStartup;
}
// Get the current position of the renderer
var currentPoint = transform.position;
var pointDistance = (currentPoint - m_LastRecordedPoint).sqrMagnitude;
var shrunkThisFrame = false;
// Is it more than minVertexDistance from the last position?
if (pointDistance > (m_MinVertexDistance*m_MinVertexDistance))
{
// In the situation we have no points, we need to record the start point as well
if (m_PointIndexStart == m_PointIndexEnd)
{
m_Points[m_PointIndexStart] = m_LastRecordedPoint;
m_PointTimes[m_PointIndexStart] = m_Time;
}
// Make space for a new point
var newEndIndex = (m_PointIndexEnd + 1) % m_MaxTrailPoints;
// In the situation that we are rendering all available vertices
// We can either keep using the current point, or take the last point, depending on the user's preference
if (newEndIndex != m_PointIndexStart)
{
m_PointIndexEnd = newEndIndex;
m_PointTimes[m_PointIndexEnd] = 0;
positionCount++;
}
else
{
if (m_StealLastPointWhenEmpty)
{
m_XRMeshData.SetElementSize(m_PointIndexStart * 2, 0);
m_XRMeshData.SetElementSize((m_PointIndexStart * 2) + 1, 0);
m_PointIndexStart = (m_PointIndexStart + 1) % m_MaxTrailPoints;
m_PointIndexEnd = newEndIndex;
m_PointTimes[m_PointIndexEnd] = 0;
m_LastPointTime = m_PointTimes[m_PointIndexStart];
}
}
m_Points[m_PointIndexEnd] = currentPoint;
// Update the last recorded point
m_LastRecordedPoint = currentPoint;
}
// Do time processing
// The end point counts up to a maximum of 'time'
m_PointTimes[m_PointIndexEnd] = Mathf.Min(m_PointTimes[m_PointIndexEnd] + deltaTime, m_Time);
if (m_PointIndexStart != m_PointIndexEnd)
{
// Run down the counter on the start point
m_PointTimes[m_PointIndexStart] -= deltaTime;
// If we've hit 0, this point is done for
if (m_PointTimes[m_PointIndexStart] <= 0.0f)
{
m_XRMeshData.SetElementSize(m_PointIndexStart * 2, 0);
m_XRMeshData.SetElementSize((m_PointIndexStart * 2) + 1, 0);
m_PointIndexStart = (m_PointIndexStart + 1) % m_MaxTrailPoints;
m_LastPointTime = m_PointTimes[m_PointIndexStart];
positionCount--;
shrunkThisFrame = true;
}
}
if (m_PointIndexStart != m_PointIndexEnd)
{
m_MeshNeedsRefreshing = true;
m_MeshRenderer.enabled = true;
}
else
{
m_MeshNeedsRefreshing = false;
m_MeshRenderer.enabled = false;
if (m_Autodestruct && Application.isPlaying && shrunkThisFrame)
{
Destroy(gameObject);
}
}
if (m_MeshNeedsRefreshing)
{
m_MeshRenderer.enabled = true;
// Update first and last points position-wise
var nextIndex = (m_PointIndexStart + 1) % m_MaxTrailPoints;
if (m_SmoothInterpolation)
{
var toNextPoint = 1.0f - (m_PointTimes[m_PointIndexStart] / m_LastPointTime);
var lerpPoint = Vector3.Lerp(m_Points[m_PointIndexStart], m_Points[nextIndex], toNextPoint);
m_XRMeshData.SetElementPosition((m_PointIndexStart * 2), ref lerpPoint);
m_XRMeshData.SetElementPipe((m_PointIndexStart * 2) + 1, ref lerpPoint, ref m_Points[nextIndex]);
}
else
{
m_XRMeshData.SetElementPosition((m_PointIndexStart * 2), ref m_Points[m_PointIndexStart]);
m_XRMeshData.SetElementPipe((m_PointIndexStart * 2) + 1, ref m_Points[m_PointIndexStart], ref m_Points[nextIndex]);
}
var prevIndex = m_PointIndexEnd - 1;
if (prevIndex < 0)
{
prevIndex = m_MaxTrailPoints - 1;
}
m_XRMeshData.SetElementPipe((prevIndex * 2) + 1, ref m_Points[prevIndex], ref m_Points[m_PointIndexEnd]);
m_XRMeshData.SetElementPosition((m_PointIndexEnd * 2), ref m_Points[m_PointIndexEnd]);
// Go through all points and update size and color
var pointUpdateCounter = m_PointIndexStart;
var pointCount = 0;
m_StepSize = (positionCount > 0) ? (1.0f / positionCount) : 1.0f;
var percent = 0.0f;
var lastWidth = m_WidthCurve.Evaluate(percent) * m_Width;
var lastColor = m_Color.Evaluate(percent);
percent += m_StepSize;
while (pointUpdateCounter != m_PointIndexEnd)
{
var nextWidth = m_WidthCurve.Evaluate(percent) * m_Width;
m_XRMeshData.SetElementSize(pointUpdateCounter * 2, lastWidth);
m_XRMeshData.SetElementSize((pointUpdateCounter * 2) + 1, lastWidth, nextWidth);
lastWidth = nextWidth;
var nextColor = m_Color.Evaluate(percent);
m_XRMeshData.SetElementColor(pointUpdateCounter * 2, ref lastColor);
m_XRMeshData.SetElementColor((pointUpdateCounter * 2) + 1, ref lastColor, ref nextColor);
lastColor = nextColor;
pointUpdateCounter = (pointUpdateCounter + 1) % m_MaxTrailPoints;
pointCount++;
percent += m_StepSize;
}
lastWidth = m_WidthCurve.Evaluate(1) * m_Width;
m_XRMeshData.SetElementSize((m_PointIndexEnd * 2), lastWidth);
lastColor = m_Color.Evaluate(1);
m_XRMeshData.SetElementColor((m_PointIndexEnd * 2), ref lastColor);
m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
m_XRMeshData.RefreshMesh();
}
}
/// <summary>
/// Editor helper function to ensure changes are reflected in edit-mode
/// </summary>
public void EditorCheckForUpdate()
{
// If we did not initialize, refresh all the properties instead
Initialize();
}
/// <summary>
/// Removes all points from the TrailRenderer. Useful for restarting a trail from a new position.
/// </summary>
public void Clear()
{
var zeroVec = Vector3.zero;
var zeroColor = Color.clear;
var elementCounter = 0;
var pointCounter = 0;
while (pointCounter < m_Points.Length)
{
// Start point
m_XRMeshData.SetElementSize(elementCounter, 0);
m_XRMeshData.SetElementPosition(elementCounter, ref zeroVec);
m_XRMeshData.SetElementColor(elementCounter, ref zeroColor);
elementCounter++;
// Pipe to the next point
m_XRMeshData.SetElementSize(elementCounter, 0);
m_XRMeshData.SetElementPipe(elementCounter, ref zeroVec, ref zeroVec);
m_XRMeshData.SetElementColor(elementCounter, ref zeroColor);
// Go onto the next point while retaining previous values we might need to lerp between
elementCounter++;
pointCounter++;
}
m_PointIndexStart = 0;
m_PointIndexEnd = 0;
positionCount = 0;
m_LastRecordedPoint = transform.position;
}
protected override void Initialize()
{
base.Initialize();
m_MaxTrailPoints = Mathf.Max(m_MaxTrailPoints, 3);
// If we already have the right amount of points and mesh, then we can get away with just clearing the curve out
if (m_Points != null && m_MaxTrailPoints == m_Points.Length && m_XRMeshData != null)
{
Clear();
return;
}
m_Points = new Vector3[m_MaxTrailPoints];
m_PointTimes = new float[m_MaxTrailPoints];
// For a trail renderer we assume one big chain
// We need a control point for each billboard and a control point for each pipe connecting them together
// We make this a circular trail so the update logic is easier. This gives us (position * 2)
var neededPoints = Mathf.Max((m_MaxTrailPoints * 2), 0);
if (m_XRMeshData == null)
{
m_XRMeshData = new XRMeshChain();
}
if (m_XRMeshData.reservedElements != neededPoints)
{
m_XRMeshData.worldSpaceData = true;
m_XRMeshData.centerAtRoot = true;
m_XRMeshData.GenerateMesh(gameObject, true, neededPoints);
if (neededPoints == 0)
{
return;
}
// Dirty all the VRMeshChain flags so everything gets refreshed
m_MeshRenderer.enabled = false;
m_XRMeshData.SetMeshDataDirty(XRMeshChain.MeshRefreshFlag.All);
m_MeshNeedsRefreshing = true;
}
Clear();
}
protected override bool NeedsReinitialize()
{
// No mesh data means we definately need to reinitialize
if (m_XRMeshData == null)
{
return true;
}
// Mismatched point data means we definately need to reinitialize
if (m_Points == null || m_MaxTrailPoints != m_Points.Length)
{
return true;
}
m_MaxTrailPoints = Mathf.Max(m_MaxTrailPoints, 3);
var neededPoints = Mathf.Max((m_MaxTrailPoints * 2), 0);
return (m_XRMeshData.reservedElements != neededPoints);
}
/// <summary>
/// Enables the internal mesh representing the line
/// </summary>
protected override void OnEnable()
{
m_MeshRenderer.enabled = (m_PointIndexStart != m_PointIndexEnd);
}
}

Просмотреть файл

@ -1,6 +1,6 @@
Shader "XRLineRenderer/MeshChain - Additive"
{
Properties
Properties
{
_Color("Color Tint", COLOR) = (1,1,1,1)
_lineSettings ("Line Thickness Settings", VECTOR) = (0, 1, .5, 1)
@ -24,7 +24,7 @@ Shader "XRLineRenderer/MeshChain - Additive"
Pass
{
// In the first pass we write only to the alpha channel.
// This lets us punch a hole in the background that our
// This lets us punch a hole in the background that our
// line color then shows through
Blend One One
BlendOp Min
@ -73,5 +73,5 @@ Shader "XRLineRenderer/MeshChain - Additive"
}
}
FallBack "Diffuse"
CustomEditor "MeshChainShaderGUI"
CustomEditor "Unity.Labs.XRLineRenderer.MeshChainShaderGUI"
}

Просмотреть файл

@ -1,6 +1,6 @@
Shader "XRLineRenderer/MeshChain - Alpha Blended"
{
Properties
Properties
{
_Color("Color Tint", COLOR) = (1,1,1,1)
_lineSettings ("Line Thickness Settings", VECTOR) = (0, 1, .5, 1)
@ -24,7 +24,7 @@ Shader "XRLineRenderer/MeshChain - Alpha Blended"
Pass
{
// In the first pass we write only to the alpha channel.
// This lets us punch a hole in the background that our
// This lets us punch a hole in the background that our
// line color then shows through
Blend One One
BlendOp Min
@ -73,5 +73,5 @@ Shader "XRLineRenderer/MeshChain - Alpha Blended"
}
}
FallBack "Diffuse"
CustomEditor "MeshChainShaderGUI"
CustomEditor "Unity.Labs.XRLineRenderer.MeshChainShaderGUI"
}

Просмотреть файл

@ -1,6 +1,6 @@
Shader "XRLineRenderer/MeshChain - Max Color"
{
Properties
Properties
{
_Color("Color Tint", COLOR) = (1,1,1,1)
_lineSettings ("Line Thickness Settings", VECTOR) = (0, 1, .5, 1)
@ -43,5 +43,5 @@ Shader "XRLineRenderer/MeshChain - Max Color"
}
}
FallBack "Diffuse"
CustomEditor "MeshChainShaderGUI"
CustomEditor "Unity.Labs.XRLineRenderer.MeshChainShaderGUI"
}

Просмотреть файл

@ -1,6 +1,6 @@
Shader "XRLineRenderer/MeshChain - Min Color"
{
Properties
Properties
{
_Color("Color Tint", COLOR) = (1,1,1,1)
_lineSettings ("Line Thickness Settings", VECTOR) = (0, 1, .5, 1)
@ -43,5 +43,5 @@ Shader "XRLineRenderer/MeshChain - Min Color"
}
}
FallBack "Diffuse"
CustomEditor "MeshChainShaderGUI"
CustomEditor "Unity.Labs.XRLineRenderer.MeshChainShaderGUI"
}

Просмотреть файл

@ -1,6 +1,6 @@
Shader "XRLineRenderer/MeshChain - Subtractive"
{
Properties
Properties
{
_Color("Color Tint", COLOR) = (1,1,1,1)
_lineSettings ("Line Thickness Settings", VECTOR) = (0, 1, .5, 1)
@ -24,7 +24,7 @@ Shader "XRLineRenderer/MeshChain - Subtractive"
Pass
{
// In the first pass we write only to the alpha channel.
// This lets us punch a hole in the background that our
// This lets us punch a hole in the background that our
// line color then shows through
Blend One One
BlendOp Min
@ -73,5 +73,5 @@ Shader "XRLineRenderer/MeshChain - Subtractive"
}
}
FallBack "Diffuse"
CustomEditor "MeshChainShaderGUI"
CustomEditor "Unity.Labs.XRLineRenderer.MeshChainShaderGUI"
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "com.unity.labs.xrlinerenderer",
"displayName": "XRLineRenderer",
"displayName": "XR Line Renderer",
"version": "0.1.0-preview",
"unity": "2019.1",
"unityRelease": "14f1",