// Straight Pointer Renderer|PointerRenderers|10020
namespace VRTK
{
using UnityEngine;
///
/// A visual pointer representation of a straight beam with an optional cursor at the end.
///
///
/// **Optional Components:**
/// * `VRTK_PlayAreaCursor` - A Play Area Cursor that will track the position of the pointer cursor.
/// * `VRTK_PointerDirectionIndicator` - A Pointer Direction Indicator that will track the position of the pointer cursor.
///
/// **Script Usage:**
/// * Place the `VRTK_StraightPointerRenderer` script on the same GameObject as the Pointer script it is linked to.
/// * Link this Pointer Renderer script to the `Pointer Renderer` parameter on the required Pointer script.
///
/// **Script Dependencies:**
/// * A Pointer script to control the activation of this Pointer Renderer script.
///
///
/// `VRTK/Examples/003_Controller_SimplePointer` shows the simple pointer in action and code examples of how the events are utilised and listened to can be viewed in the script `VRTK/Examples/ExampleResources/Scripts/VRTK_ControllerPointerEvents_ListenerExample.cs`
///
[AddComponentMenu("VRTK/Scripts/Pointers/Pointer Renderers/VRTK_StraightPointerRenderer")]
public class VRTK_StraightPointerRenderer : VRTK_BasePointerRenderer
{
[Header("Straight Pointer Appearance Settings")]
[Tooltip("The maximum length the pointer tracer can reach.")]
public float maximumLength = 100f;
[Tooltip("The scale factor to scale the pointer tracer object by.")]
public float scaleFactor = 0.002f;
[Tooltip("The scale multiplier to scale the pointer cursor object by in relation to the `Scale Factor`.")]
public float cursorScaleMultiplier = 25f;
[Tooltip("The cursor will be rotated to match the angle of the target surface if this is true, if it is false then the pointer cursor will always be horizontal.")]
public bool cursorMatchTargetRotation = false;
[Tooltip("Rescale the cursor proportionally to the distance from the tracer origin.")]
public bool cursorDistanceRescale = false;
[Tooltip("The maximum scale the cursor is allowed to reach. This is only used when rescaling the cursor proportionally to the distance from the tracer origin.")]
public Vector3 maximumCursorScale = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
[Header("Straight Pointer Custom Appearance Settings")]
[Tooltip("A custom game object to use as the appearance for the pointer tracer. If this is empty then a Box primitive will be created and used.")]
public GameObject customTracer;
[Tooltip("A custom game object to use as the appearance for the pointer cursor. If this is empty then a Sphere primitive will be created and used.")]
public GameObject customCursor;
protected GameObject actualContainer;
protected GameObject actualTracer;
protected GameObject actualCursor;
protected Vector3 cursorOriginalScale = Vector3.one;
///
/// The UpdateRenderer method is used to run an Update routine on the pointer.
///
public override void UpdateRenderer()
{
if ((controllingPointer != null && controllingPointer.IsPointerActive()) || IsVisible())
{
float tracerLength = CastRayForward();
SetPointerAppearance(tracerLength);
MakeRenderersVisible();
}
base.UpdateRenderer();
}
///
/// The GetPointerObjects returns an array of the auto generated GameObjects associated with the pointer.
///
/// An array of pointer auto generated GameObjects.
public override GameObject[] GetPointerObjects()
{
return new GameObject[] { actualContainer, actualCursor, actualTracer };
}
protected override void ToggleRenderer(bool pointerState, bool actualState)
{
ToggleElement(actualTracer, pointerState, actualState, tracerVisibility, ref tracerVisible);
ToggleElement(actualCursor, pointerState, actualState, cursorVisibility, ref cursorVisible);
}
protected override void CreatePointerObjects()
{
actualContainer = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "StraightPointerRenderer_Container"));
actualContainer.transform.SetParent(pointerOriginTransformFollowGameObject.transform);
actualContainer.transform.localPosition = Vector3.zero;
actualContainer.transform.localRotation = Quaternion.identity;
actualContainer.transform.localScale = Vector3.one;
VRTK_PlayerObject.SetPlayerObject(actualContainer, VRTK_PlayerObject.ObjectTypes.Pointer);
CreateTracer();
CreateCursor();
Toggle(false, false);
if (controllingPointer != null)
{
controllingPointer.ResetActivationTimer(true);
controllingPointer.ResetSelectionTimer(true);
}
}
protected override void DestroyPointerObjects()
{
if (actualContainer != null)
{
Destroy(actualContainer);
}
}
protected override void ChangeMaterial(Color givenColor)
{
base.ChangeMaterial(givenColor);
ChangeMaterialColor(actualTracer, givenColor);
ChangeMaterialColor(actualCursor, givenColor);
}
protected override void UpdateObjectInteractor()
{
base.UpdateObjectInteractor();
//if the object interactor is too far from the pointer tip then set it to the pointer tip position to prevent glitching.
if (objectInteractor != null && actualCursor != null && Vector3.Distance(objectInteractor.transform.position, actualCursor.transform.position) > 0f)
{
objectInteractor.transform.position = actualCursor.transform.position;
}
}
protected virtual void CreateTracer()
{
if (customTracer != null)
{
actualTracer = Instantiate(customTracer);
}
else
{
actualTracer = GameObject.CreatePrimitive(PrimitiveType.Cube);
actualTracer.GetComponent().isTrigger = true;
actualTracer.AddComponent().isKinematic = true;
actualTracer.layer = LayerMask.NameToLayer("Ignore Raycast");
SetupMaterialRenderer(actualTracer);
}
actualTracer.transform.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "StraightPointerRenderer_Tracer");
actualTracer.transform.SetParent(actualContainer.transform);
VRTK_PlayerObject.SetPlayerObject(actualTracer, VRTK_PlayerObject.ObjectTypes.Pointer);
}
protected virtual void CreateCursor()
{
if (customCursor != null)
{
actualCursor = Instantiate(customCursor);
}
else
{
actualCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
actualCursor.transform.localScale = Vector3.one * (scaleFactor * cursorScaleMultiplier);
actualCursor.GetComponent().isTrigger = true;
actualCursor.AddComponent().isKinematic = true;
actualCursor.layer = LayerMask.NameToLayer("Ignore Raycast");
SetupMaterialRenderer(actualCursor);
}
cursorOriginalScale = actualCursor.transform.localScale;
actualCursor.transform.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "StraightPointerRenderer_Cursor");
actualCursor.transform.SetParent(actualContainer.transform);
VRTK_PlayerObject.SetPlayerObject(actualCursor, VRTK_PlayerObject.ObjectTypes.Pointer);
}
protected virtual void CheckRayMiss(bool rayHit, RaycastHit pointerCollidedWith)
{
if (!rayHit || (destinationHit.collider != null && destinationHit.collider != pointerCollidedWith.collider))
{
if (destinationHit.collider != null)
{
PointerExit(destinationHit);
}
destinationHit = new RaycastHit();
ChangeColor(invalidCollisionColor);
}
}
protected virtual void CheckRayHit(bool rayHit, RaycastHit pointerCollidedWith)
{
if (rayHit)
{
PointerEnter(pointerCollidedWith);
destinationHit = pointerCollidedWith;
ChangeColor(validCollisionColor);
}
}
protected virtual float CastRayForward()
{
Transform origin = GetOrigin();
Ray pointerRaycast = new Ray(origin.position, origin.forward);
RaycastHit pointerCollidedWith;
bool rayHit = VRTK_CustomRaycast.Raycast(customRaycast, pointerRaycast, out pointerCollidedWith, defaultIgnoreLayer, maximumLength);
CheckRayMiss(rayHit, pointerCollidedWith);
CheckRayHit(rayHit, pointerCollidedWith);
float actualLength = maximumLength;
if (rayHit && pointerCollidedWith.distance < maximumLength)
{
actualLength = pointerCollidedWith.distance;
}
return OverrideBeamLength(actualLength);
}
protected virtual void SetPointerAppearance(float tracerLength)
{
if (actualContainer != null)
{
//if the additional decimal isn't added then the beam position glitches
float beamPosition = tracerLength / (2f + BEAM_ADJUST_OFFSET);
actualTracer.transform.localScale = new Vector3(scaleFactor, scaleFactor, tracerLength);
actualTracer.transform.localPosition = Vector3.forward * beamPosition;
actualCursor.transform.localScale = Vector3.one * (scaleFactor * cursorScaleMultiplier);
actualCursor.transform.localPosition = new Vector3(0f, 0f, tracerLength);
Transform origin = GetOrigin();
float objectInteractorScaleIncrease = 1.05f;
ScaleObjectInteractor(actualCursor.transform.lossyScale * objectInteractorScaleIncrease);
if (destinationHit.transform != null)
{
if (cursorMatchTargetRotation)
{
actualCursor.transform.forward = -destinationHit.normal;
}
if (cursorDistanceRescale)
{
float collisionDistance = Vector3.Distance(destinationHit.point, origin.position);
actualCursor.transform.localScale = Vector3.Min(cursorOriginalScale * collisionDistance, maximumCursorScale);
}
}
else
{
if (cursorMatchTargetRotation)
{
actualCursor.transform.forward = origin.forward;
}
if (cursorDistanceRescale)
{
actualCursor.transform.localScale = Vector3.Min(cursorOriginalScale * tracerLength, maximumCursorScale);
}
}
ToggleRenderer(controllingPointer.IsPointerActive(), false);
UpdateDependencies(actualCursor.transform.position);
}
}
}
}