515 lines
20 KiB
C#
515 lines
20 KiB
C#
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// 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 UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
/// This abstract class should be implemented for pointer based input, and used with
|
|
/// the GvrPointerInputModule script.
|
|
///
|
|
/// It provides methods called on pointer interaction with in-game objects and UI,
|
|
/// trigger events, and 'BaseInputModule' class state changes.
|
|
///
|
|
/// To have the methods called, an instance of this (implemented) class must be
|
|
/// registered with the **GvrPointerManager** script in 'Start' by calling
|
|
/// GvrPointerInputModule.OnPointerCreated.
|
|
///
|
|
/// This abstract class should be implemented by pointers doing 1 of 2 things:
|
|
/// 1. Responding to movement of the users head (Cardboard gaze-based-pointer).
|
|
/// 2. Responding to the movement of the daydream controller (Daydream 3D pointer).
|
|
public abstract class GvrBasePointer : MonoBehaviour, IGvrControllerInputDeviceReceiver {
|
|
public enum RaycastMode {
|
|
/// Casts a ray from the camera through the target of the pointer.
|
|
/// This is ideal for reticles that are always rendered on top.
|
|
/// The object that is selected will always be the object that appears
|
|
/// underneath the reticle from the perspective of the camera.
|
|
/// This also prevents the reticle from appearing to "jump" when it starts/stops hitting an object.
|
|
///
|
|
/// Recommended for reticles that are always rendered on top such as the GvrReticlePointer
|
|
/// prefab which is used for cardboard apps.
|
|
///
|
|
/// Note: This will prevent the user from pointing around an object to hit something that is out of sight.
|
|
/// This isn't a problem in a typical use case.
|
|
///
|
|
/// When used with the standard daydream controller,
|
|
/// the hit detection will not account for the laser correctly for objects that are closer to the
|
|
/// camera than the end of the laser.
|
|
/// In that case, it is recommended to do one of the following things:
|
|
///
|
|
/// 1. Hide the laser.
|
|
/// 2. Use a full-length laser pointer in Direct mode.
|
|
/// 3. Use the Hybrid raycast mode.
|
|
Camera,
|
|
/// Cast a ray directly from the pointer origin.
|
|
///
|
|
/// Recommended for full-length laser pointers.
|
|
Direct,
|
|
/// Default method for casting ray.
|
|
///
|
|
/// Combines the Camera and Direct raycast modes.
|
|
/// Uses a Direct ray up until the CameraRayIntersectionDistance, and then switches to use
|
|
/// a Camera ray starting from the point where the two rays intersect.
|
|
///
|
|
/// Recommended for use with the standard settings of the GvrControllerPointer prefab.
|
|
/// This is the most versatile raycast mode. Like Camera mode, this prevents the reticle
|
|
/// appearing jumpy. Additionally, it still allows the user to target objects that are close
|
|
/// to them by using the laser as a visual reference.
|
|
Hybrid,
|
|
}
|
|
|
|
/// Represents a ray segment for a series of intersecting rays.
|
|
/// This is useful for Hybrid raycast mode, which uses two sequential rays.
|
|
public struct PointerRay {
|
|
/// The ray for this segment of the pointer.
|
|
public Ray ray;
|
|
|
|
/// The distance along the pointer from the origin of the first ray to this ray.
|
|
public float distanceFromStart;
|
|
|
|
/// Distance that this ray extends to.
|
|
public float distance;
|
|
}
|
|
|
|
/// Determines which raycast mode to use for this raycaster.
|
|
/// • Camera - Ray is cast from the camera through the pointer.
|
|
/// • Direct - Ray is cast forward from the pointer.
|
|
/// • Hybrid - Begins with a Direct ray and transitions to a Camera ray.
|
|
[Tooltip("Determines which raycast mode to use for this raycaster.\n" +
|
|
" • Camera - Ray is cast from camera.\n" +
|
|
" • Direct - Ray is cast from pointer.\n" +
|
|
" • Hybrid - Transitions from Direct ray to Camera ray.")]
|
|
public RaycastMode raycastMode = RaycastMode.Hybrid;
|
|
|
|
/// Determines the eventCamera for _GvrPointerPhysicsRaycaster_ and _GvrPointerGraphicRaycaster_.
|
|
/// Additionaly, this is used to control what camera to use when calculating the Camera ray for
|
|
/// the Hybrid and Camera raycast modes.
|
|
[Tooltip("Optional: Use a camera other than Camera.main.")]
|
|
public Camera overridePointerCamera;
|
|
|
|
#if UNITY_EDITOR
|
|
/// Determines if the rays used for raycasting will be drawn in the editor.
|
|
[Tooltip("Determines if the rays used for raycasting will be drawn in the editor.")]
|
|
public bool drawDebugRays = false;
|
|
#endif // UNITY_EDITOR
|
|
|
|
/// Convenience function to access what the pointer is currently hitting.
|
|
public RaycastResult CurrentRaycastResult {
|
|
get {
|
|
return GvrPointerInputModule.CurrentRaycastResult;
|
|
}
|
|
}
|
|
|
|
[System.Obsolete("Replaced by CurrentRaycastResult.worldPosition")]
|
|
public Vector3 PointerIntersection {
|
|
get {
|
|
RaycastResult raycastResult = CurrentRaycastResult;
|
|
return raycastResult.worldPosition;
|
|
}
|
|
}
|
|
|
|
[System.Obsolete("Replaced by CurrentRaycastResult.gameObject != null")]
|
|
public bool IsPointerIntersecting {
|
|
get {
|
|
RaycastResult raycastResult = CurrentRaycastResult;
|
|
return raycastResult.gameObject != null;
|
|
}
|
|
}
|
|
|
|
/// This is used to determine if the enterRadius or the exitRadius should be used for the raycast.
|
|
/// It is set by GvrPointerInputModule and doesn't need to be controlled manually.
|
|
public bool ShouldUseExitRadiusForRaycast { get; set; }
|
|
|
|
/// If ShouldUseExitRadiusForRaycast is true, returns the exitRadius.
|
|
/// Otherwise, returns the enterRadius.
|
|
public float CurrentPointerRadius {
|
|
get {
|
|
float enterRadius, exitRadius;
|
|
GetPointerRadius(out enterRadius, out exitRadius);
|
|
if (ShouldUseExitRadiusForRaycast) {
|
|
return exitRadius;
|
|
} else {
|
|
return enterRadius;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the transform that represents this pointer.
|
|
/// It is used by GvrBasePointerRaycaster as the origin of the ray.
|
|
public virtual Transform PointerTransform {
|
|
get {
|
|
return transform;
|
|
}
|
|
}
|
|
|
|
public GvrControllerInputDevice ControllerInputDevice { get; set; }
|
|
|
|
/// If true, the trigger was just pressed. This is an event flag:
|
|
/// it will be true for only one frame after the event happens.
|
|
/// Defaults to mouse button 0 down on Cardboard or
|
|
/// ControllerInputDevice.GetButtonDown(TouchPadButton) on Daydream.
|
|
/// Can be overridden to change the trigger.
|
|
public virtual bool TriggerDown {
|
|
get {
|
|
bool isTriggerDown = Input.GetMouseButtonDown(0);
|
|
if (ControllerInputDevice != null) {
|
|
isTriggerDown |=
|
|
ControllerInputDevice.GetButtonDown(GvrControllerButton.TouchPadButton);
|
|
}
|
|
return isTriggerDown;
|
|
}
|
|
}
|
|
|
|
/// If true, the trigger is currently being pressed. This is not
|
|
/// an event: it represents the trigger's state (it remains true while the trigger is being
|
|
/// pressed).
|
|
/// Defaults to mouse button 0 state on Cardboard or
|
|
/// ControllerInputDevice.GetButton(TouchPadButton) on Daydream.
|
|
/// Can be overridden to change the trigger.
|
|
public virtual bool Triggering {
|
|
get {
|
|
bool isTriggering = Input.GetMouseButton(0);
|
|
if (ControllerInputDevice != null) {
|
|
isTriggering |=
|
|
ControllerInputDevice.GetButton(GvrControllerButton.TouchPadButton);
|
|
}
|
|
return isTriggering;
|
|
}
|
|
}
|
|
|
|
/// If true, the trigger was just released. This is an event flag:
|
|
/// it will be true for only one frame after the event happens.
|
|
/// Defaults to mouse button 0 up on Cardboard or
|
|
/// ControllerInputDevice.GetButtonUp(TouchPadButton) on Daydream.
|
|
/// Can be overridden to change the trigger.
|
|
public virtual bool TriggerUp {
|
|
get {
|
|
bool isTriggerUp = Input.GetMouseButtonUp(0);
|
|
if (ControllerInputDevice == null) {
|
|
isTriggerUp |=
|
|
ControllerInputDevice.GetButtonUp(GvrControllerButton.TouchPadButton);
|
|
}
|
|
return isTriggerUp;
|
|
}
|
|
}
|
|
|
|
/// If true, the user just started touching the touchpad. This is an event flag (it is true
|
|
/// for only one frame after the event happens, then reverts to false).
|
|
/// Used by _GvrPointerScrollInput_ to generate OnScroll events using Unity's Event System.
|
|
/// Defaults to ControllerInputDevice.GetButtonDown(TouchPadTouch), can be overridden to change
|
|
/// the input source.
|
|
public virtual bool TouchDown {
|
|
get {
|
|
if (ControllerInputDevice == null) {
|
|
return false;
|
|
} else {
|
|
return ControllerInputDevice.GetButtonDown(GvrControllerButton.TouchPadTouch);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// If true, the user is currently touching the touchpad.
|
|
/// Used by _GvrPointerScrollInput_ to generate OnScroll events using Unity's Event System.
|
|
/// Defaults to ControllerInputDevice.GetButton(TouchPadTouch), can be overridden to change
|
|
/// the input source.
|
|
public virtual bool IsTouching {
|
|
get {
|
|
if (ControllerInputDevice == null) {
|
|
return false;
|
|
} else {
|
|
return ControllerInputDevice.GetButton(GvrControllerButton.TouchPadTouch);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// If true, the user just stopped touching the touchpad. This is an event flag (it is true
|
|
/// for only one frame after the event happens, then reverts to false).
|
|
/// Used by _GvrPointerScrollInput_ to generate OnScroll events using Unity's Event System.
|
|
/// Defaults to ControllerInputDevice.GetButtonUp(TouchPadTouch), can be overridden to change
|
|
/// the input source.
|
|
public virtual bool TouchUp {
|
|
get {
|
|
if (ControllerInputDevice == null) {
|
|
return false;
|
|
} else {
|
|
return ControllerInputDevice.GetButtonUp(GvrControllerButton.TouchPadTouch);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Position of the current touch, if touching the touchpad.
|
|
/// If not touching, this is the position of the last touch (when the finger left the touchpad).
|
|
/// The X and Y range is from 0 to 1.
|
|
/// (0, 0) is the top left of the touchpad and (1, 1) is the bottom right of the touchpad.
|
|
/// Used by `GvrPointerScrollInput` to generate OnScroll events using Unity's Event System.
|
|
/// Defaults to `ControllerInputDevice.TouchPos` but translated to top-left-relative coordinates
|
|
/// for backwards compatibility. Can be overridden to change the input source.
|
|
public virtual Vector2 TouchPos {
|
|
get {
|
|
if (ControllerInputDevice == null) {
|
|
return Vector2.zero;
|
|
} else {
|
|
Vector2 touchPos = ControllerInputDevice.TouchPos;
|
|
touchPos.x = (touchPos.x / 2.0f) + 0.5f;
|
|
touchPos.y = (-touchPos.y / 2.0f) + 0.5f;
|
|
return touchPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the end point of the pointer when it is MaxPointerDistance away from the origin.
|
|
public virtual Vector3 MaxPointerEndPoint {
|
|
get {
|
|
Transform pointerTransform = PointerTransform;
|
|
if (pointerTransform == null) {
|
|
return Vector3.zero;
|
|
}
|
|
|
|
Vector3 maxEndPoint = GetPointAlongPointer(MaxPointerDistance);
|
|
return maxEndPoint;
|
|
}
|
|
}
|
|
|
|
/// If true, the pointer will be used for generating input events by _GvrPointerInputModule_.
|
|
public virtual bool IsAvailable {
|
|
get {
|
|
Transform pointerTransform = PointerTransform;
|
|
if (pointerTransform == null) {
|
|
return false;
|
|
}
|
|
|
|
if (!enabled) {
|
|
return false;
|
|
}
|
|
|
|
return pointerTransform.gameObject.activeInHierarchy;
|
|
}
|
|
}
|
|
|
|
/// When using the Camera raycast mode, this is used to calculate
|
|
/// where the ray from the pointer will intersect with the ray from the camera.
|
|
public virtual float CameraRayIntersectionDistance {
|
|
get {
|
|
return MaxPointerDistance;
|
|
}
|
|
}
|
|
|
|
public Camera PointerCamera {
|
|
get {
|
|
if (overridePointerCamera != null) {
|
|
return overridePointerCamera;
|
|
}
|
|
|
|
return Camera.main;
|
|
}
|
|
}
|
|
|
|
/// Returns the max distance from the pointer that raycast hits will be detected.
|
|
public abstract float MaxPointerDistance { get; }
|
|
|
|
/// Called when the pointer is facing a valid GameObject. This can be a 3D
|
|
/// or UI element.
|
|
///
|
|
/// **raycastResult** is the hit detection result for the object being pointed at.
|
|
/// **isInteractive** is true if the object being pointed at is interactive.
|
|
public abstract void OnPointerEnter(RaycastResult raycastResult, bool isInteractive);
|
|
|
|
/// Called every frame the user is still pointing at a valid GameObject. This
|
|
/// can be a 3D or UI element.
|
|
///
|
|
/// **raycastResult** is the hit detection result for the object being pointed at.
|
|
/// **isInteractive** is true if the object being pointed at is interactive.
|
|
public abstract void OnPointerHover(RaycastResult raycastResultResult, bool isInteractive);
|
|
|
|
/// Called when the pointer no longer faces an object previously
|
|
/// intersected with a ray projected from the camera.
|
|
/// This is also called just before **OnInputModuleDisabled**
|
|
/// previousObject will be null in this case.
|
|
///
|
|
/// **previousObject** is the object that was being pointed at the previous frame.
|
|
public abstract void OnPointerExit(GameObject previousObject);
|
|
|
|
/// Called when a click is initiated.
|
|
public abstract void OnPointerClickDown();
|
|
|
|
/// Called when click is finished.
|
|
public abstract void OnPointerClickUp();
|
|
|
|
/// Return the radius of the pointer. It is used by GvrPointerPhysicsRaycaster when
|
|
/// searching for valid pointer targets. If a radius is 0, then a ray is used to find
|
|
/// a valid pointer target. Otherwise it will use a SphereCast.
|
|
/// The *enterRadius* is used for finding new targets while the *exitRadius*
|
|
/// is used to see if you are still nearby the object currently pointed at
|
|
/// to avoid a flickering effect when just at the border of the intersection.
|
|
///
|
|
/// NOTE: This is only works with GvrPointerPhysicsRaycaster. To use it with uGUI,
|
|
/// add 3D colliders to your canvas elements.
|
|
public abstract void GetPointerRadius(out float enterRadius, out float exitRadius);
|
|
|
|
/// Returns a point in worldspace a specified distance along the pointer.
|
|
/// What this point will be is different depending on the raycastMode.
|
|
///
|
|
/// Because raycast modes differ, use this function instead of manually calculating a point
|
|
/// projected from the pointer.
|
|
public Vector3 GetPointAlongPointer(float distance) {
|
|
PointerRay pointerRay = GetRayForDistance(distance);
|
|
return pointerRay.ray.GetPoint(distance - pointerRay.distanceFromStart);
|
|
}
|
|
|
|
/// Returns the ray used for projecting points out of the pointer for the given distance.
|
|
/// In Hybrid raycast mode, the ray will be different depending upon the distance.
|
|
/// In Camera or Direct raycast mode, the ray will always be the same.
|
|
public PointerRay GetRayForDistance(float distance) {
|
|
PointerRay result = new PointerRay();
|
|
|
|
if (raycastMode == RaycastMode.Hybrid) {
|
|
float directDistance = CameraRayIntersectionDistance;
|
|
if (distance < directDistance) {
|
|
result = CalculateHybridRay(this, RaycastMode.Direct);
|
|
} else {
|
|
result = CalculateHybridRay(this, RaycastMode.Camera);
|
|
}
|
|
} else {
|
|
result = CalculateRay(this, raycastMode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Calculates the ray for a given Raycast mode.
|
|
/// Will throw an exception if the raycast mode Hybrid is passed in.
|
|
/// If you need to calculate the ray for the direct or camera segment of the Hybrid raycast,
|
|
/// use CalculateHybridRay instead.
|
|
public static PointerRay CalculateRay(GvrBasePointer pointer, RaycastMode mode) {
|
|
PointerRay result = new PointerRay();
|
|
|
|
if (pointer == null || !pointer.IsAvailable) {
|
|
Debug.LogError("Cannot calculate ray when the pointer isn't available.");
|
|
return result;
|
|
}
|
|
|
|
Transform pointerTransform = pointer.PointerTransform;
|
|
|
|
if (pointerTransform == null) {
|
|
Debug.LogError("Cannot calculate ray when pointerTransform is null.");
|
|
return result;
|
|
}
|
|
|
|
result.distance = pointer.MaxPointerDistance;
|
|
|
|
switch (mode) {
|
|
case RaycastMode.Camera:
|
|
Camera camera = pointer.PointerCamera;
|
|
if (camera == null) {
|
|
Debug.LogError("Cannot calculate ray because pointer.PointerCamera is null." +
|
|
"To fix this, either tag a Camera as \"MainCamera\" or set overridePointerCamera.");
|
|
return result;
|
|
}
|
|
|
|
Vector3 rayPointerStart = pointerTransform.position;
|
|
Vector3 rayPointerEnd = rayPointerStart +
|
|
(pointerTransform.forward * pointer.CameraRayIntersectionDistance);
|
|
|
|
Vector3 cameraLocation = camera.transform.position;
|
|
Vector3 finalRayDirection = rayPointerEnd - cameraLocation;
|
|
finalRayDirection.Normalize();
|
|
|
|
Vector3 finalRayStart = cameraLocation + (finalRayDirection * camera.nearClipPlane);
|
|
|
|
result.ray = new Ray(finalRayStart, finalRayDirection);
|
|
break;
|
|
case RaycastMode.Direct:
|
|
result.ray = new Ray(pointerTransform.position, pointerTransform.forward);
|
|
break;
|
|
default:
|
|
throw new UnityException("Invalid RaycastMode " + mode + " passed into CalculateRay.");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Calculates the ray for the segment of the Hybrid raycast determined by the raycast mode
|
|
/// passed in. Throws an exception if Hybrid is passed in.
|
|
public static PointerRay CalculateHybridRay(GvrBasePointer pointer, RaycastMode hybridMode) {
|
|
PointerRay result;
|
|
|
|
switch (hybridMode) {
|
|
case RaycastMode.Direct:
|
|
result = CalculateRay(pointer, hybridMode);
|
|
result.distance = pointer.CameraRayIntersectionDistance;
|
|
break;
|
|
case RaycastMode.Camera:
|
|
result = CalculateRay(pointer, hybridMode);
|
|
PointerRay directRay = CalculateHybridRay(pointer, RaycastMode.Direct);
|
|
result.ray.origin = directRay.ray.GetPoint(directRay.distance);
|
|
result.distanceFromStart = directRay.distance;
|
|
result.distance = pointer.MaxPointerDistance - directRay.distance;
|
|
break;
|
|
default:
|
|
throw new UnityException("Invalid RaycastMode " + hybridMode + " passed into CalculateHybridRay.");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected virtual void Start() {
|
|
GvrPointerInputModule.OnPointerCreated(this);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected virtual void OnDrawGizmos() {
|
|
if (drawDebugRays && Application.isPlaying && isActiveAndEnabled) {
|
|
switch (raycastMode) {
|
|
case RaycastMode.Camera:
|
|
// Camera line.
|
|
Gizmos.color = Color.green;
|
|
PointerRay pointerRay = CalculateRay(this, RaycastMode.Camera);
|
|
Gizmos.DrawLine(pointerRay.ray.origin, pointerRay.ray.GetPoint(pointerRay.distance));
|
|
Camera camera = PointerCamera;
|
|
|
|
// Pointer to intersection dotted line.
|
|
Vector3 intersection =
|
|
PointerTransform.position + (PointerTransform.forward * CameraRayIntersectionDistance);
|
|
UnityEditor.Handles.DrawDottedLine(PointerTransform.position, intersection, 1.0f);
|
|
break;
|
|
case RaycastMode.Direct:
|
|
// Direct line.
|
|
Gizmos.color = Color.blue;
|
|
pointerRay = CalculateRay(this, RaycastMode.Direct);
|
|
Gizmos.DrawLine(pointerRay.ray.origin, pointerRay.ray.GetPoint(pointerRay.distance));
|
|
break;
|
|
case RaycastMode.Hybrid:
|
|
// Direct line.
|
|
Gizmos.color = Color.blue;
|
|
pointerRay = CalculateHybridRay(this, RaycastMode.Direct);
|
|
Gizmos.DrawLine(pointerRay.ray.origin, pointerRay.ray.GetPoint(pointerRay.distance));
|
|
|
|
// Camera line.
|
|
Gizmos.color = Color.green;
|
|
pointerRay = CalculateHybridRay(this, RaycastMode.Camera);
|
|
Gizmos.DrawLine(pointerRay.ray.origin, pointerRay.ray.GetPoint(pointerRay.distance));
|
|
|
|
// Camera to intersection dotted line.
|
|
camera = PointerCamera;
|
|
if (camera != null) {
|
|
UnityEditor.Handles.DrawDottedLine(camera.transform.position, pointerRay.ray.origin, 1.0f);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif // UNITY_EDITOR
|
|
}
|