VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.interaction/Runtime/Scripts/DistanceGrab/Visuals/TubeRenderer.cs
IonutMocanu d7aba243a2 Main
2025-09-08 11:04:02 +03:00

567 lines
19 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace Oculus.Interaction
{
public struct TubePoint
{
public Vector3 position;
public Quaternion rotation;
public float relativeLength;
}
/// <summary>
/// Creates and renders a tube mesh from a sequence of points.
/// </summary>
public class TubeRenderer : MonoBehaviour
{
[StructLayout(LayoutKind.Sequential)]
private struct VertexLayout
{
public Vector3 pos;
public Color32 color;
public Vector2 uv;
}
/// <summary>
/// The Mesh Filter that's included in the ReticleLine prefab.
/// </summary>
[Tooltip("The Mesh Filter that's included in the ReticleLine prefab.")]
[SerializeField]
private MeshFilter _filter;
/// <summary>
/// The Mesh Renderer that's included in the ReticleLine prefab.
/// </summary>
[Tooltip("The Mesh Renderer that's included in the ReticleLine prefab.")]
[SerializeField]
private MeshRenderer _renderer;
/// <summary>
/// The number of divisions to use when calculating the tube mesh's vertices.
/// </summary>
[Tooltip("The number of divisions to use when calculating the tube mesh's vertices.")]
[SerializeField]
private int _divisions = 6;
/// <summary>
/// The number of bevels to use when calculating the tube mesh's vertices.
/// </summary>
[Tooltip("The number of bevels to use when calculating the tube mesh's vertices.")]
[SerializeField]
private int _bevel = 4;
/// <summary>
/// Unity shader queue that determines when the tube is rendered. Defaults to -1, which uses the render queue of the shader.
/// </summary>
[Tooltip("Unity shader queue that determines when the tube is rendered. Defaults to -1, which uses the render queue of the shader.")]
[SerializeField]
private int _renderQueue = -1;
public int RenderQueue
{
get
{
return _renderQueue;
}
set
{
_renderQueue = value;
}
}
[SerializeField]
private Vector2 _renderOffset = Vector2.zero;
public Vector2 RenderOffset
{
get
{
return _renderOffset;
}
set
{
_renderOffset = value;
}
}
/// <summary>
/// The thickness of the tube.
/// </summary>
[Tooltip("The thickness of the tube.")]
[SerializeField]
private float _radius = 0.005f;
public float Radius
{
get
{
return _radius;
}
set
{
_radius = value;
}
}
/// <summary>
/// The gradient of the tube.
/// </summary>
[Tooltip("The gradient of the tube.")]
[SerializeField]
private Gradient _gradient;
public Gradient Gradient
{
get
{
return _gradient;
}
set
{
_gradient = value;
}
}
/// <summary>
/// The color of the tube.
/// </summary>
[Tooltip("The color of the tube.")]
[SerializeField]
private Color _tint = Color.white;
public Color Tint
{
get
{
return _tint;
}
set
{
_tint = value;
}
}
[SerializeField, Range(0f, 1f)]
private float _progressFade = 0.2f;
public float ProgressFade
{
get
{
return _progressFade;
}
set
{
_progressFade = value;
}
}
/// <summary>
/// Defines the length of the transparent portion at the beginning of the tube. The higher the value, the longer the transparent portion.
/// </summary>
[Tooltip("Defines the length of the transparent portion at the beginning of the tube. The higher the value, the longer the transparent portion.")]
[SerializeField]
private float _startFadeThresold = 0.2f;
public float StartFadeThresold
{
get
{
return _startFadeThresold;
}
set
{
_startFadeThresold = value;
}
}
/// <summary>
/// Defines the length of the transparent portion at the end of the tube. The higher the value, the longer the transparent portion.
/// </summary>
[Tooltip("Defines the length of the transparent portion at the end of the tube. The higher the value, the longer the transparent portion.")]
[SerializeField]
private float _endFadeThresold = 0.2f;
public float EndFadeThresold
{
get
{
return _endFadeThresold;
}
set
{
_endFadeThresold = value;
}
}
/// <summary>
/// Determines if the transparent portion of the tube should be in the middle instead of at the beginning and end.
/// </summary>
[Tooltip("Should the transparent portion of the tube be in the middle instead of at the beginning and end?")]
[SerializeField]
private bool _invertThreshold = false;
public bool InvertThreshold
{
get
{
return _invertThreshold;
}
set
{
_invertThreshold = value;
}
}
[SerializeField]
private float _feather = 0.2f;
public float Feather
{
get
{
return _feather;
}
set
{
_feather = value;
}
}
[SerializeField]
private bool _mirrorTexture;
public bool MirrorTexture
{
get
{
return _mirrorTexture;
}
set
{
_mirrorTexture = value;
}
}
public float Progress { get; set; } = 0f;
public float TotalLength => _totalLength;
private VertexAttributeDescriptor[] _dataLayout;
private NativeArray<VertexLayout> _vertsData;
private VertexLayout _layout = new VertexLayout();
private Mesh _mesh;
private int[] _tris;
private int _initializedSteps = -1;
private int _vertsCount;
private float _totalLength = 0f;
private bool _hidden = false;
private static readonly int _fadeLimitsShaderID = Shader.PropertyToID("_FadeLimit");
private static readonly int _fadeSignShaderID = Shader.PropertyToID("_FadeSign");
private static readonly int _offsetFactorShaderPropertyID = Shader.PropertyToID("_OffsetFactor");
private static readonly int _offsetUnitsShaderPropertyID = Shader.PropertyToID("_OffsetUnits");
#region Editor events
protected virtual void Reset()
{
_filter = this.GetComponent<MeshFilter>();
_renderer = this.GetComponent<MeshRenderer>();
}
#endregion
protected virtual void Awake()
{
_hidden = this.enabled;
}
protected virtual void OnEnable()
{
_renderer.enabled = !_hidden;
}
protected virtual void OnDisable()
{
_renderer.enabled = false;
}
/// <summary>
/// Updates the mesh data for the tube with the specified points.
/// If the component is enabled it will automatically show the renderer.
/// </summary>
/// <param name="points">The points that the tube must follow</param>
/// <param name="space">Indicates if the points are specified in local space or world space</param>
public void RenderTube(TubePoint[] points, Space space = Space.Self)
{
int steps = points.Length;
if (steps != _initializedSteps)
{
InitializeMeshData(steps);
_initializedSteps = steps;
}
_vertsData = new NativeArray<VertexLayout>(_vertsCount, Allocator.Temp);
UpdateMeshData(points, space);
_renderer.enabled = this.enabled;
_hidden = false;
}
/// <summary>
/// Hides the renderer of the tube
/// </summary>
public void Hide()
{
_renderer.enabled = false;
_hidden = true;
}
/// <summary>
/// Shows the renderer of the tube
/// </summary>
public void Show()
{
_renderer.enabled = true;
_hidden = false;
}
private void InitializeMeshData(int steps)
{
_dataLayout = new VertexAttributeDescriptor[]
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
_vertsCount = SetVertexCount(steps, _divisions, _bevel);
SubMeshDescriptor submeshDesc = new SubMeshDescriptor(0, _tris.Length, MeshTopology.Triangles);
_mesh = new Mesh();
_mesh.SetVertexBufferParams(_vertsCount, _dataLayout);
_mesh.SetIndexBufferParams(_tris.Length, IndexFormat.UInt32);
_mesh.SetIndexBufferData(_tris, 0, 0, _tris.Length);
_mesh.subMeshCount = 1;
_mesh.SetSubMesh(0, submeshDesc);
_filter.mesh = _mesh;
}
private void UpdateMeshData(TubePoint[] points, Space space)
{
int steps = points.Length;
float totalLength = 0f;
Vector3 prevPoint = Vector3.zero;
Pose pose = Pose.identity;
Pose start = Pose.identity;
Pose end = Pose.identity;
Pose rootPose = this.transform.GetPose(Space.World);
Quaternion inverseRootRotation = Quaternion.Inverse(rootPose.rotation);
Vector3 rootPositionScaled = new Vector3(
rootPose.position.x / this.transform.lossyScale.x,
rootPose.position.y / this.transform.lossyScale.y,
rootPose.position.z / this.transform.lossyScale.z);
float uniformScale = space == Space.World ? this.transform.lossyScale.x : 1f;
TransformPose(points[0], ref start);
TransformPose(points[points.Length - 1], ref end);
BevelCap(start, false, 0);
for (int i = 0; i < steps; i++)
{
TransformPose(points[i], ref pose);
Vector3 point = pose.position;
Quaternion rotation = pose.rotation;
float progress = points[i].relativeLength;
Color color = Gradient.Evaluate(progress) * _tint;
if (i > 0)
{
totalLength += Vector3.Distance(point, prevPoint);
}
prevPoint = point;
if (i / (steps - 1f) < Progress)
{
color.a *= ProgressFade;
}
_layout.color = color;
WriteCircle(point, rotation, _radius, i + _bevel, progress);
}
BevelCap(end, true, _bevel + steps);
_mesh.bounds = new Bounds(
(start.position + end.position) * 0.5f,
end.position - start.position);
_mesh.SetVertexBufferData(_vertsData, 0, 0, _vertsData.Length, 0, MeshUpdateFlags.DontRecalculateBounds);
_totalLength = totalLength * uniformScale;
RedrawFadeThresholds();
void TransformPose(in TubePoint tubePoint, ref Pose pose)
{
if (space == Space.Self)
{
pose.position = tubePoint.position;
pose.rotation = tubePoint.rotation;
return;
}
pose.position = inverseRootRotation * (tubePoint.position - rootPositionScaled);
pose.rotation = inverseRootRotation * tubePoint.rotation;
}
}
/// <summary>
/// Resubmits the fading thresholds data to the material without re-generating the mesh
/// </summary>
public void RedrawFadeThresholds()
{
float originFadeIn = StartFadeThresold / _totalLength;
float originFadeOut = (StartFadeThresold + Feather) / _totalLength;
float endFadeIn = (_totalLength - EndFadeThresold) / _totalLength;
float endFadeOut = (_totalLength - EndFadeThresold - Feather) / _totalLength;
_renderer.material.SetVector(_fadeLimitsShaderID, new Vector4(
_invertThreshold ? originFadeOut : originFadeIn,
_invertThreshold ? originFadeIn : originFadeOut,
endFadeOut,
endFadeIn));
_renderer.material.SetFloat(_fadeSignShaderID, _invertThreshold ? -1 : 1);
_renderer.material.renderQueue = _renderQueue;
_renderer.material.SetFloat(_offsetFactorShaderPropertyID, _renderOffset.x);
_renderer.material.SetFloat(_offsetUnitsShaderPropertyID, _renderOffset.y);
}
private void BevelCap(in Pose pose, bool end, int indexOffset)
{
Vector3 origin = pose.position;
Quaternion rotation = pose.rotation;
for (int i = 0; i < _bevel; i++)
{
float radiusFactor = Mathf.InverseLerp(-1, _bevel + 1, i);
if (end)
{
radiusFactor = 1 - radiusFactor;
}
float positionFactor = Mathf.Sqrt(1 - radiusFactor * radiusFactor);
Vector3 point = origin + (end ? 1 : -1) * (rotation * Vector3.forward) * _radius * positionFactor;
WriteCircle(point, rotation, _radius * radiusFactor, i + indexOffset, end ? 1 : 0);
}
}
private void WriteCircle(Vector3 point, Quaternion rotation, float width, int index, float progress)
{
Color color = Gradient.Evaluate(progress) * _tint;
if (progress < Progress)
{
color.a *= ProgressFade;
}
_layout.color = color;
for (int j = 0; j <= _divisions; j++)
{
float radius = 2 * Mathf.PI * j / _divisions;
Vector3 circle = new Vector3(Mathf.Sin(radius), Mathf.Cos(radius), 0);
Vector3 normal = rotation * circle;
_layout.pos = point + normal * width;
if (_mirrorTexture)
{
float x = (j / (float)_divisions) * 2f;
if (j >= _divisions * 0.5f)
{
x = 2 - x;
}
_layout.uv = new Vector2(x, progress);
}
else
{
_layout.uv = new Vector2(j / (float)_divisions, progress);
}
int vertIndex = index * (_divisions + 1) + j;
_vertsData[vertIndex] = _layout;
}
}
private int SetVertexCount(int positionCount, int divisions, int bevelCap)
{
bevelCap = bevelCap * 2;
int vertsPerPosition = divisions + 1;
int vertCount = (positionCount + bevelCap) * vertsPerPosition;
int tubeTriangles = (positionCount - 1 + bevelCap) * divisions * 6;
int capTriangles = (divisions - 2) * 3;
int triangleCount = tubeTriangles + capTriangles * 2;
_tris = new int[triangleCount];
// handle triangulation
for (int i = 0; i < positionCount - 1 + bevelCap; i++)
{
// add faces
for (int j = 0; j < divisions; j++)
{
int vert0 = i * vertsPerPosition + j;
int vert1 = (i + 1) * vertsPerPosition + j;
int t = (i * divisions + j) * 6;
_tris[t] = vert0;
_tris[t + 1] = _tris[t + 4] = vert1;
_tris[t + 2] = _tris[t + 3] = vert0 + 1;
_tris[t + 5] = vert1 + 1;
}
}
// triangulate the ends
Cap(tubeTriangles, 0, divisions - 1, true);
Cap(tubeTriangles + capTriangles, vertCount - divisions, vertCount - 1);
void Cap(int t, int firstVert, int lastVert, bool clockwise = false)
{
for (int i = firstVert + 1; i < lastVert; i++)
{
_tris[t++] = firstVert;
_tris[t++] = clockwise ? i : i + 1;
_tris[t++] = clockwise ? i + 1 : i;
}
}
return vertCount;
}
#region Inject
public void InjectAllTubeRenderer(MeshFilter filter,
MeshRenderer renderer, int divisions, int bevel)
{
InjectFilter(filter);
InjectRenderer(renderer);
InjectDivisions(divisions);
InjectBevel(bevel);
}
public void InjectFilter(MeshFilter filter)
{
_filter = filter;
}
public void InjectRenderer(MeshRenderer renderer)
{
_renderer = renderer;
}
public void InjectDivisions(int divisions)
{
_divisions = divisions;
}
public void InjectBevel(int bevel)
{
_bevel = bevel;
}
#endregion
}
}