// 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); } } } }