// 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; /// Draws a circular reticle in front of any object that the user points at. /// The circle dilates if the object is clickable. public class GvrReticlePointerImpl : GvrBasePointer { // The constants below are expsed for testing. // Minimum inner angle of the reticle (in degrees). public const float RETICLE_MIN_INNER_ANGLE = 0.0f; // Minimum outer angle of the reticle (in degrees). public const float RETICLE_MIN_OUTER_ANGLE = 0.5f; // Angle at which to expand the reticle when intersecting with an object // (in degrees). public const float RETICLE_GROWTH_ANGLE = 1.5f; // Minimum distance of the reticle (in meters). public const float RETICLE_DISTANCE_MIN = 0.45f; // Maximum distance of the reticle (in meters). public const float RETICLE_DISTANCE_MAX = 10.0f; /// Growth speed multiplier for the reticle. public float ReticleGrowthSpeed { private get; set; } public Material MaterialComp { private get; set; } // Current inner angle of the reticle (in degrees). // Exposed for testing. public float ReticleInnerAngle { get; private set; } // Current outer angle of the reticle (in degrees). // Exposed for testing. public float ReticleOuterAngle { get; private set; } // Current distance of the reticle (in meters). // Getter exposed for testing. public float ReticleDistanceInMeters { get; private set; } // Current inner and outer diameters of the reticle, before distance multiplication. // Getters exposed for testing. public float ReticleInnerDiameter { get; private set; } public float ReticleOuterDiameter { get; private set; } private Vector3 targetPoint = Vector3.zero; public override Vector3 LineEndPoint { get { return targetPoint; } } public override float MaxPointerDistance { get { return RETICLE_DISTANCE_MAX; } } public GvrReticlePointerImpl() { ReticleGrowthSpeed = 8.0f; ReticleInnerAngle = 0.0f; ReticleOuterAngle = 0.5f; ReticleDistanceInMeters = 10.0f; ReticleInnerDiameter = 0.0f; ReticleOuterDiameter = 0.0f; } public override void OnStart () { base.OnStart(); } /// This is called when the 'BaseInputModule' system should be enabled. public override void OnInputModuleEnabled() {} /// This is called when the 'BaseInputModule' system should be disabled. public override void OnInputModuleDisabled() {} /// Called when the user is pointing at valid GameObject. This can be a 3D /// or UI element. /// /// The targetObject is the object the user is pointing at. /// The intersectionPosition is where the ray intersected with the targetObject. /// The intersectionRay is the ray that was cast to determine the intersection. public override void OnPointerEnter(RaycastResult rayastResult, Ray ray, bool isInteractive) { SetPointerTarget(rayastResult.worldPosition, isInteractive); } /// Called every frame the user is still pointing at a valid GameObject. This /// can be a 3D or UI element. /// /// The targetObject is the object the user is pointing at. /// The intersectionPosition is where the ray intersected with the targetObject. /// The intersectionRay is the ray that was cast to determine the intersection. public override void OnPointerHover(RaycastResult rayastResult, Ray ray, bool isInteractive) { SetPointerTarget(rayastResult.worldPosition, isInteractive); } /// Called when the user's look no longer intersects an object previously /// intersected with a ray projected from the camera. /// This is also called just before **OnInputModuleDisabled** and may have have any of /// the values set as **null**. public override void OnPointerExit(GameObject previousObject) { ReticleDistanceInMeters = RETICLE_DISTANCE_MAX; ReticleInnerAngle = RETICLE_MIN_INNER_ANGLE; ReticleOuterAngle = RETICLE_MIN_OUTER_ANGLE; } /// Called when a trigger event is initiated. This is practically when /// the user begins pressing the trigger. public override void OnPointerClickDown() {} /// Called when a trigger event is finished. This is practically when /// the user releases the trigger. public override void OnPointerClickUp() {} public override void GetPointerRadius(out float enterRadius, out float exitRadius) { float min_inner_angle_radians = Mathf.Deg2Rad * RETICLE_MIN_INNER_ANGLE; float max_inner_angle_radians = Mathf.Deg2Rad * (RETICLE_MIN_INNER_ANGLE + RETICLE_GROWTH_ANGLE); enterRadius = 2.0f * Mathf.Tan(min_inner_angle_radians); exitRadius = 2.0f * Mathf.Tan(max_inner_angle_radians); } public void UpdateDiameters() { ReticleDistanceInMeters = Mathf.Clamp(ReticleDistanceInMeters, RETICLE_DISTANCE_MIN, RETICLE_DISTANCE_MAX); if (ReticleInnerAngle < RETICLE_MIN_INNER_ANGLE) { ReticleInnerAngle = RETICLE_MIN_INNER_ANGLE; } if (ReticleOuterAngle < RETICLE_MIN_OUTER_ANGLE) { ReticleOuterAngle = RETICLE_MIN_OUTER_ANGLE; } float inner_half_angle_radians = Mathf.Deg2Rad * ReticleInnerAngle * 0.5f; float outer_half_angle_radians = Mathf.Deg2Rad * ReticleOuterAngle * 0.5f; float inner_diameter = 2.0f * Mathf.Tan(inner_half_angle_radians); float outer_diameter = 2.0f * Mathf.Tan(outer_half_angle_radians); ReticleInnerDiameter = Mathf.Lerp(ReticleInnerDiameter, inner_diameter, Time.deltaTime * ReticleGrowthSpeed); ReticleOuterDiameter = Mathf.Lerp(ReticleOuterDiameter, outer_diameter, Time.deltaTime * ReticleGrowthSpeed); MaterialComp.SetFloat("_InnerDiameter", ReticleInnerDiameter * ReticleDistanceInMeters); MaterialComp.SetFloat("_OuterDiameter", ReticleOuterDiameter * ReticleDistanceInMeters); MaterialComp.SetFloat("_DistanceInMeters", ReticleDistanceInMeters); } private bool SetPointerTarget(Vector3 target, bool interactive) { if (base.PointerTransform == null) { Debug.LogWarning("Cannot operate on a null pointer transform"); return false; } targetPoint = target; Vector3 targetLocalPosition = base.PointerTransform.InverseTransformPoint(target); ReticleDistanceInMeters = Mathf.Clamp(targetLocalPosition.z, RETICLE_DISTANCE_MIN, RETICLE_DISTANCE_MAX); if (interactive) { ReticleInnerAngle = RETICLE_MIN_INNER_ANGLE + RETICLE_GROWTH_ANGLE; ReticleOuterAngle = RETICLE_MIN_OUTER_ANGLE + RETICLE_GROWTH_ANGLE; } else { ReticleInnerAngle = RETICLE_MIN_INNER_ANGLE; ReticleOuterAngle = RETICLE_MIN_OUTER_ANGLE; } return true; } }