// Base Pointer Renderer|PointerRenderers|10010
namespace VRTK
{
using UnityEngine;
using System;
using System.Collections.Generic;
#if UNITY_5_5_OR_NEWER
using UnityEngine.AI;
#endif
///
/// Provides a base that all pointer renderers can inherit from.
///
///
/// **Script Usage:**
/// > This is an abstract class that is to be inherited to a concrete class that provides pointer renderer functionality, therefore this script should not be directly used.
///
public abstract class VRTK_BasePointerRenderer : MonoBehaviour
{
///
/// States of Pointer Visibility.
///
public enum VisibilityStates
{
///
/// Only shows the object when the pointer is active.
///
OnWhenActive,
///
/// Ensures the object is always.
///
AlwaysOn,
///
/// Ensures the object beam is never visible.
///
AlwaysOff
}
[Serializable]
public sealed class PointerOriginSmoothingSettings
{
[Tooltip("Whether or not to smooth the position of the pointer origin when positioning the pointer tip.")]
public bool smoothsPosition;
[Tooltip("The maximum allowed distance between the unsmoothed pointer origin and the smoothed pointer origin per frame to use for smoothing.")]
public float maxAllowedPerFrameDistanceDifference = 0.003f;
[Tooltip("Whether or not to smooth the rotation of the pointer origin when positioning the pointer tip.")]
public bool smoothsRotation;
[Tooltip("The maximum allowed angle between the unsmoothed pointer origin and the smoothed pointer origin per frame to use for smoothing.")]
public float maxAllowedPerFrameAngleDifference = 1.5f;
}
[Header("Renderer Supplement Settings")]
[Tooltip("An optional Play Area Cursor generator to add to the destination position of the pointer tip.")]
public VRTK_PlayAreaCursor playareaCursor;
[Tooltip("A custom VRTK_PointerDirectionIndicator to use to determine the rotation given to the destination set event.")]
public VRTK_PointerDirectionIndicator directionIndicator;
[Header("General Renderer Settings")]
[Tooltip("A custom raycaster to use for the pointer's raycasts to ignore.")]
public VRTK_CustomRaycast customRaycast;
[Tooltip("Specifies the smoothing to be applied to the pointer origin when positioning the pointer tip.")]
public PointerOriginSmoothingSettings pointerOriginSmoothingSettings = new PointerOriginSmoothingSettings();
[Header("General Appearance Settings")]
[Tooltip("The colour to change the pointer materials when the pointer collides with a valid object. Set to `Color.clear` to bypass changing material colour on valid collision.")]
public Color validCollisionColor = Color.green;
[Tooltip("The colour to change the pointer materials when the pointer is not colliding with anything or with an invalid object. Set to `Color.clear` to bypass changing material colour on invalid collision.")]
public Color invalidCollisionColor = Color.red;
[Tooltip("Determines when the main tracer of the pointer renderer will be visible.")]
public VisibilityStates tracerVisibility = VisibilityStates.OnWhenActive;
[Tooltip("Determines when the cursor/tip of the pointer renderer will be visible.")]
public VisibilityStates cursorVisibility = VisibilityStates.OnWhenActive;
protected const float BEAM_ADJUST_OFFSET = 0.0001f;
protected VRTK_Pointer controllingPointer;
protected RaycastHit destinationHit = new RaycastHit();
protected Material defaultMaterial;
protected Color previousColor;
protected Color currentColor;
protected VRTK_PolicyList invalidListPolicy;
protected VRTK_NavMeshData navMeshData;
protected bool headsetPositionCompensation;
protected GameObject objectInteractor;
protected GameObject objectInteractorAttachPoint;
protected GameObject pointerOriginTransformFollowGameObject;
protected VRTK_TransformFollow pointerOriginTransformFollow;
protected VRTK_InteractGrab controllerGrabScript;
protected Rigidbody savedAttachPoint;
protected bool attachedToInteractorAttachPoint = false;
protected float savedBeamLength = 0f;
protected HashSet makeRendererVisible = new HashSet();
protected bool tracerVisible;
protected bool cursorVisible;
protected LayerMask defaultIgnoreLayer = Physics.IgnoreRaycastLayer;
protected SDK_BaseController.ControllerHand cachedAttachedHand = SDK_BaseController.ControllerHand.None;
protected Transform cachedPointerAttachPoint = null;
///
/// The GetPointerObjects returns an array of the auto generated GameObjects associated with the pointer.
///
/// An array of pointer auto generated GameObjects.
public abstract GameObject[] GetPointerObjects();
///
/// The InitalizePointer method is used to set up the state of the pointer renderer.
///
/// The VRTK_Pointer that is controlling the pointer renderer.
/// The VRTK_PolicyList for managing valid and invalid pointer locations.
/// The given distance from a nav mesh that the pointer can be to be valid.
/// Determines whether the play area cursor will take the headset position within the play area into account when being displayed.
[Obsolete("`VRTK_BasePointerRenderer.InitalizePointer(givenPointer, givenInvalidListPolicy, givenNavMeshCheckDistance, givenHeadsetPositionCompensation)` has been replaced with the method `VRTK_BasePointerRenderer.InitalizePointer(givenPointer, givenInvalidListPolicy, givenNavMeshData, givenHeadsetPositionCompensation)`. This method will be removed in a future version of VRTK.")]
public virtual void InitalizePointer(VRTK_Pointer givenPointer, VRTK_PolicyList givenInvalidListPolicy, float givenNavMeshCheckDistance, bool givenHeadsetPositionCompensation)
{
VRTK_NavMeshData givenData = gameObject.AddComponent();
givenData.distanceLimit = givenNavMeshCheckDistance;
InitalizePointer(givenPointer, givenInvalidListPolicy, givenData, givenHeadsetPositionCompensation);
}
///
/// The InitalizePointer method is used to set up the state of the pointer renderer.
///
/// The VRTK_Pointer that is controlling the pointer renderer.
/// The VRTK_PolicyList for managing valid and invalid pointer locations.
/// The NavMeshData object that contains the Nav Mesh restriction options.
/// Determines whether the play area cursor will take the headset position within the play area into account when being displayed.
public virtual void InitalizePointer(VRTK_Pointer givenPointer, VRTK_PolicyList givenInvalidListPolicy, VRTK_NavMeshData givenNavMeshData, bool givenHeadsetPositionCompensation)
{
controllingPointer = givenPointer;
invalidListPolicy = givenInvalidListPolicy;
navMeshData = givenNavMeshData;
headsetPositionCompensation = givenHeadsetPositionCompensation;
if (controllingPointer != null && controllingPointer.interactWithObjects && controllingPointer.controllerEvents != null && objectInteractor == null)
{
controllerGrabScript = controllingPointer.controllerEvents.GetComponentInChildren();
CreateObjectInteractor();
}
SetupDirectionIndicator();
}
///
/// The ResetPointerObjects method is used to destroy any existing pointer objects and recreate them at runtime.
///
public virtual void ResetPointerObjects()
{
DestroyPointerOriginTransformFollow();
DestroyPointerObjects();
CreatePointerOriginTransformFollow();
CreatePointerObjects();
}
///
/// The Toggle Method is used to enable or disable the pointer renderer.
///
/// The activation state of the pointer.
/// The actual state of the activation button press.
public virtual void Toggle(bool pointerState, bool actualState)
{
if (pointerState)
{
destinationHit = new RaycastHit();
}
else if (controllingPointer != null)
{
controllingPointer.ResetActivationTimer();
PointerExit(destinationHit);
}
ToggleInteraction(pointerState);
ToggleRenderer(pointerState, actualState);
}
///
/// The ToggleInteraction method is used to enable or disable the controller extension interactions.
///
/// If true then the object interactor will be enabled.
public virtual void ToggleInteraction(bool state)
{
ToggleObjectInteraction(state);
}
///
/// The UpdateRenderer method is used to run an Update routine on the pointer.
///
public virtual void UpdateRenderer()
{
if (playareaCursor != null)
{
playareaCursor.SetHeadsetPositionCompensation(headsetPositionCompensation);
playareaCursor.ToggleState(IsCursorVisible());
}
if (directionIndicator != null)
{
UpdateDirectionIndicator();
}
}
///
/// The GetDestinationHit method is used to get the RaycastHit of the pointer destination.
///
/// The RaycastHit containing the information where the pointer is hitting.
public virtual RaycastHit GetDestinationHit()
{
return destinationHit;
}
///
/// The ValidPlayArea method is used to determine if there is a valid play area and if it has had any collisions.
///
/// Returns true if there is a valid play area and no collisions. Returns false if there is no valid play area or there is but with a collision detected.
public virtual bool ValidPlayArea()
{
return (playareaCursor == null || !playareaCursor.IsActive() || !playareaCursor.HasCollided());
}
///
/// The IsVisible method determines if the pointer renderer is at all visible by checking the state of the tracer and the cursor.
///
/// Returns true if either the tracer or cursor renderers are visible. Returns false if none are visible.
public virtual bool IsVisible()
{
return (IsTracerVisible() || IsCursorVisible());
}
///
/// The IsTracerVisible method determines if the pointer tracer renderer is visible.
///
/// Returns true if the tracer renderers are visible.
public virtual bool IsTracerVisible()
{
return (tracerVisibility == VisibilityStates.AlwaysOn || tracerVisible);
}
///
/// The IsCursorVisible method determines if the pointer cursor renderer is visible.
///
/// Returns true if the cursor renderers are visible.
public virtual bool IsCursorVisible()
{
return (cursorVisibility == VisibilityStates.AlwaysOn || cursorVisible);
}
///
/// The IsValidCollision method determines if the pointer is currently in it's valid collision state.
///
/// Returns true if the pointer is in a valid collision, returns false if the pointer is in an invalid collision state.
public virtual bool IsValidCollision()
{
return (currentColor != invalidCollisionColor);
}
///
/// The GetObjectInteractor method returns the auto generated GameObject that acts as the controller extension for interacting with objects.
///
/// The auto generated object interactor GameObject.
///
public virtual GameObject GetObjectInteractor()
{
return objectInteractor;
}
protected abstract void CreatePointerObjects();
protected abstract void DestroyPointerObjects();
protected abstract void ToggleRenderer(bool pointerState, bool actualState);
protected virtual void Awake()
{
VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void OnEnable()
{
cachedPointerAttachPoint = null;
cachedAttachedHand = SDK_BaseController.ControllerHand.None;
defaultMaterial = Resources.Load("WorldPointer") as Material;
makeRendererVisible.Clear();
CreatePointerOriginTransformFollow();
CreatePointerObjects();
}
protected virtual void OnDisable()
{
DestroyPointerObjects();
if (objectInteractor != null)
{
Destroy(objectInteractor);
}
controllerGrabScript = null;
DestroyPointerOriginTransformFollow();
}
protected virtual void OnDestroy()
{
VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void OnValidate()
{
pointerOriginSmoothingSettings.maxAllowedPerFrameDistanceDifference = Mathf.Max(0.0001f, pointerOriginSmoothingSettings.maxAllowedPerFrameDistanceDifference);
pointerOriginSmoothingSettings.maxAllowedPerFrameAngleDifference = Mathf.Max(0.0001f, pointerOriginSmoothingSettings.maxAllowedPerFrameAngleDifference);
}
protected virtual void FixedUpdate()
{
if (controllingPointer != null && controllingPointer.interactWithObjects && objectInteractor != null && objectInteractor.activeInHierarchy)
{
UpdateObjectInteractor();
}
if (pointerOriginTransformFollow != null)
{
UpdatePointerOriginTransformFollow();
}
}
protected virtual void ToggleObjectInteraction(bool state)
{
if (controllingPointer != null && controllingPointer.interactWithObjects)
{
if (state && controllingPointer.grabToPointerTip && controllerGrabScript != null && objectInteractorAttachPoint != null)
{
savedAttachPoint = controllerGrabScript.controllerAttachPoint;
controllerGrabScript.controllerAttachPoint = objectInteractorAttachPoint.GetComponent();
attachedToInteractorAttachPoint = true;
}
if (!state && controllingPointer.grabToPointerTip && controllerGrabScript != null)
{
if (attachedToInteractorAttachPoint)
{
controllerGrabScript.ForceRelease(true);
}
if (savedAttachPoint != null)
{
controllerGrabScript.controllerAttachPoint = savedAttachPoint;
savedAttachPoint = null;
}
attachedToInteractorAttachPoint = false;
savedBeamLength = 0f;
}
if (objectInteractor != null)
{
objectInteractor.SetActive(state);
}
}
}
protected virtual void UpdateObjectInteractor()
{
objectInteractor.transform.position = destinationHit.point;
}
protected virtual VRTK_ControllerReference GetControllerReference(GameObject reference = null)
{
reference = (reference == null && controllingPointer != null && controllingPointer.controllerEvents != null ? controllingPointer.controllerEvents.gameObject : reference);
return VRTK_ControllerReference.GetControllerReference(reference);
}
protected virtual Transform GetPointerOriginTransform()
{
VRTK_ControllerReference controllerReference = GetControllerReference((controllingPointer != null ? controllingPointer.attachedTo : null));
if (VRTK_ControllerReference.IsValid(controllerReference) && (cachedAttachedHand != controllerReference.hand || cachedPointerAttachPoint == null))
{
cachedPointerAttachPoint = controllerReference.model.transform.Find(VRTK_SDK_Bridge.GetControllerElementPath(SDK_BaseController.ControllerElements.AttachPoint, controllerReference.hand));
cachedAttachedHand = controllerReference.hand;
pointerOriginTransformFollow.gameObject.SetActive(false);
}
return (cachedPointerAttachPoint != null ? cachedPointerAttachPoint : transform);
}
protected virtual void UpdatePointerOriginTransformFollow()
{
pointerOriginTransformFollow.gameObject.SetActive((controllingPointer != null));
if (controllingPointer != null)
{
pointerOriginTransformFollow.gameObjectToFollow = (controllingPointer.customOrigin == null ? GetPointerOriginTransform() : controllingPointer.customOrigin).gameObject;
pointerOriginTransformFollow.enabled = controllingPointer != null;
pointerOriginTransformFollowGameObject.SetActive(controllingPointer != null);
pointerOriginTransformFollow.smoothsPosition = pointerOriginSmoothingSettings.smoothsPosition;
pointerOriginTransformFollow.maxAllowedPerFrameDistanceDifference = pointerOriginSmoothingSettings.maxAllowedPerFrameDistanceDifference;
pointerOriginTransformFollow.smoothsRotation = pointerOriginSmoothingSettings.smoothsRotation;
pointerOriginTransformFollow.maxAllowedPerFrameAngleDifference = pointerOriginSmoothingSettings.maxAllowedPerFrameAngleDifference;
}
}
protected Transform GetOrigin(bool smoothed = true)
{
return (smoothed ? pointerOriginTransformFollow.gameObjectToChange.transform : (controllingPointer.customOrigin == null ? GetPointerOriginTransform() : controllingPointer.customOrigin));
}
protected virtual void PointerEnter(RaycastHit givenHit)
{
controllingPointer.PointerEnter(givenHit);
}
protected virtual void PointerExit(RaycastHit givenHit)
{
controllingPointer.PointerExit(givenHit);
}
protected virtual bool ValidDestination()
{
bool validNavMeshLocation = false;
if (navMeshData != null)
{
if (destinationHit.transform != null)
{
NavMeshHit hit;
validNavMeshLocation = NavMesh.SamplePosition(destinationHit.point, out hit, navMeshData.distanceLimit, navMeshData.validAreas);
}
}
else
{
validNavMeshLocation = true;
}
return (validNavMeshLocation && destinationHit.collider != null && !(VRTK_PolicyList.Check(destinationHit.collider.gameObject, invalidListPolicy)));
}
protected virtual void ToggleElement(GameObject givenObject, bool pointerState, bool actualState, VisibilityStates givenVisibility, ref bool currentVisible)
{
if (givenObject != null)
{
currentVisible = (givenVisibility == VisibilityStates.AlwaysOn ? true : pointerState);
givenObject.SetActive(currentVisible);
if (givenVisibility == VisibilityStates.AlwaysOff)
{
currentVisible = false;
ToggleRendererVisibility(givenObject, false);
}
else
{
if (actualState && givenVisibility != VisibilityStates.AlwaysOn)
{
ToggleRendererVisibility(givenObject, false);
AddVisibleRenderer(givenObject);
}
else
{
ToggleRendererVisibility(givenObject, true);
}
}
}
}
protected virtual void AddVisibleRenderer(GameObject givenObject)
{
makeRendererVisible.Add(givenObject);
}
protected virtual void MakeRenderersVisible()
{
foreach (GameObject currentRenderer in new HashSet(makeRendererVisible))
{
ToggleRendererVisibility(currentRenderer, true);
}
makeRendererVisible.Clear();
}
protected virtual void ToggleRendererVisibility(GameObject givenObject, bool state)
{
if (givenObject != null)
{
Renderer[] renderers = givenObject.GetComponentsInChildren();
for (int i = 0; i < renderers.Length; i++)
{
renderers[i].enabled = state;
}
}
}
protected virtual void SetupMaterialRenderer(GameObject givenObject)
{
if (givenObject != null)
{
MeshRenderer pointerRenderer = givenObject.GetComponent();
pointerRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
pointerRenderer.receiveShadows = false;
pointerRenderer.material = defaultMaterial;
}
}
protected virtual void ChangeColor(Color givenColor)
{
previousColor = currentColor;
if ((playareaCursor != null && playareaCursor.IsActive() && playareaCursor.HasCollided()) || !ValidDestination() || (controllingPointer != null && !controllingPointer.CanSelect()))
{
givenColor = invalidCollisionColor;
}
if (givenColor != Color.clear)
{
currentColor = givenColor;
ChangeMaterial(givenColor);
}
if (previousColor != currentColor)
{
EmitStateEvent();
}
}
protected virtual void EmitStateEvent()
{
if (controllingPointer != null)
{
if (IsValidCollision())
{
controllingPointer.OnPointerStateValid();
}
else
{
controllingPointer.OnPointerStateInvalid();
}
}
}
protected virtual void ChangeMaterial(Color givenColor)
{
if (playareaCursor != null)
{
playareaCursor.SetMaterialColor(givenColor, IsValidCollision());
}
if (directionIndicator != null)
{
directionIndicator.SetMaterialColor(givenColor, IsValidCollision());
}
}
protected virtual void ChangeMaterialColor(GameObject givenObject, Color givenColor)
{
if (givenObject != null)
{
Renderer[] foundRenderers = givenObject.GetComponentsInChildren();
for (int i = 0; i < foundRenderers.Length; i++)
{
Renderer foundRenderer = foundRenderers[i];
if (foundRenderer.material != null)
{
foundRenderer.material.EnableKeyword("_EMISSION");
if (foundRenderer.material.HasProperty("_Color"))
{
foundRenderer.material.color = givenColor;
}
if (foundRenderer.material.HasProperty("_EmissionColor"))
{
foundRenderer.material.SetColor("_EmissionColor", VRTK_SharedMethods.ColorDarken(givenColor, 50));
}
}
}
}
}
protected virtual void CreateObjectInteractor()
{
objectInteractor = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "BasePointerRenderer_ObjectInteractor_Container"));
objectInteractor.transform.SetParent(controllingPointer.controllerEvents.transform);
objectInteractor.transform.localPosition = Vector3.zero;
objectInteractor.layer = LayerMask.NameToLayer("Ignore Raycast");
VRTK_PlayerObject.SetPlayerObject(objectInteractor, VRTK_PlayerObject.ObjectTypes.Pointer);
GameObject objectInteractorCollider = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "BasePointerRenderer_ObjectInteractor_Collider"));
objectInteractorCollider.transform.SetParent(objectInteractor.transform);
objectInteractorCollider.transform.localPosition = Vector3.zero;
objectInteractorCollider.layer = LayerMask.NameToLayer("Ignore Raycast");
SphereCollider tmpCollider = objectInteractorCollider.AddComponent();
tmpCollider.isTrigger = true;
VRTK_PlayerObject.SetPlayerObject(objectInteractorCollider, VRTK_PlayerObject.ObjectTypes.Pointer);
if (controllingPointer.grabToPointerTip)
{
objectInteractorAttachPoint = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "BasePointerRenderer_ObjectInteractor_AttachPoint"));
objectInteractorAttachPoint.transform.SetParent(objectInteractor.transform);
objectInteractorAttachPoint.transform.localPosition = Vector3.zero;
objectInteractorAttachPoint.layer = LayerMask.NameToLayer("Ignore Raycast");
Rigidbody objectInteratorRigidBody = objectInteractorAttachPoint.AddComponent();
objectInteratorRigidBody.isKinematic = true;
objectInteratorRigidBody.freezeRotation = true;
#if UNITY_2018_3_OR_NEWER
objectInteratorRigidBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
#else
objectInteratorRigidBody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
#endif
VRTK_PlayerObject.SetPlayerObject(objectInteractorAttachPoint, VRTK_PlayerObject.ObjectTypes.Pointer);
}
ScaleObjectInteractor(Vector3.one);
objectInteractor.SetActive(false);
}
protected virtual void ScaleObjectInteractor(Vector3 scaleAmount)
{
if (objectInteractor != null)
{
VRTK_SharedMethods.SetGlobalScale(objectInteractor.transform, scaleAmount);
}
}
protected virtual void CreatePointerOriginTransformFollow()
{
pointerOriginTransformFollowGameObject = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "BasePointerRenderer_Origin_Smoothed"));
pointerOriginTransformFollow = pointerOriginTransformFollowGameObject.AddComponent();
pointerOriginTransformFollow.enabled = false;
pointerOriginTransformFollow.moment = VRTK_TransformFollow.FollowMoment.OnFixedUpdate;
pointerOriginTransformFollow.followsScale = false;
}
protected virtual void DestroyPointerOriginTransformFollow()
{
if (pointerOriginTransformFollowGameObject != null)
{
Destroy(pointerOriginTransformFollowGameObject);
pointerOriginTransformFollowGameObject = null;
pointerOriginTransformFollow = null;
}
}
protected virtual float OverrideBeamLength(float currentLength)
{
if (controllerGrabScript == null || !controllerGrabScript.GetGrabbedObject())
{
savedBeamLength = 0f;
}
if (controllingPointer != null && controllingPointer.interactWithObjects && controllingPointer.grabToPointerTip && attachedToInteractorAttachPoint && controllerGrabScript != null && controllerGrabScript.GetGrabbedObject())
{
savedBeamLength = (savedBeamLength == 0f ? currentLength : savedBeamLength);
return savedBeamLength;
}
return currentLength;
}
protected virtual void UpdateDependencies(Vector3 location)
{
if (playareaCursor != null)
{
playareaCursor.SetPlayAreaCursorTransform(location);
}
}
protected virtual void SetupDirectionIndicator()
{
if (directionIndicator != null && controllingPointer != null && controllingPointer.controllerEvents != null)
{
directionIndicator.Initialize(controllingPointer.controllerEvents);
}
}
protected virtual void UpdateDirectionIndicator()
{
RaycastHit destinationHit = GetDestinationHit();
directionIndicator.SetPosition((ShowDirectionIndicator() && destinationHit.collider != null), destinationHit.point);
}
protected virtual bool ShowDirectionIndicator()
{
switch (directionIndicator.indicatorVisibility)
{
case VRTK_PointerDirectionIndicator.VisibilityState.OnWhenPointerActive:
return controllingPointer.IsPointerActive();
case VRTK_PointerDirectionIndicator.VisibilityState.AlwaysOnWithPointerCursor:
return (IsCursorVisible() || controllingPointer.IsPointerActive());
}
return false;
}
}
}