// Copyright 2016 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 System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; /// This script provides a raycaster for use with the GvrPointerInputModule. /// It behaves similarly to the standards Physics raycaster, except that it utilize raycast /// modes specifically for Gvr. /// /// View GvrBasePointerRaycaster.cs and GvrPointerInputModule.cs for more details. [AddComponentMenu("GoogleVR/GvrPointerPhysicsRaycaster")] [HelpURL("https://developers.google.com/vr/unity/reference/class/GvrPointerPhysicsRaycaster")] public class GvrPointerPhysicsRaycaster : GvrBasePointerRaycaster { /// Used to sort the raycast hits by distance. private class HitComparer: IComparer { public int Compare(RaycastHit lhs, RaycastHit rhs) { return lhs.distance.CompareTo(rhs.distance); } } /// Const to use for clarity when no event mask is set protected const int NO_EVENT_MASK_SET = -1; /// The maximum allowed value for the field maxRaycastHits. private const int MAX_RAYCAST_HITS_MAX = 512; /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. [SerializeField] protected LayerMask raycasterEventMask = NO_EVENT_MASK_SET; [SerializeField] [Range(1, MAX_RAYCAST_HITS_MAX)] /// The max number of hits that the raycaster can detect at once. /// They are NOT guaranteed to be ordered by distance. This value should be set to a higher number /// than the number of objects the pointer is expected to intersect with in a single frame. /// /// This functionality is used to prevent unnecessary memory allocation to improve performance. /// https://docs.unity3d.com/ScriptReference/Physics.SphereCastNonAlloc.html private int maxRaycastHits = 64; /// Buffer of raycast hits re-used each time PerformRaycast is called. private RaycastHit[] hits; /// Used to sort the hits by distance. private HitComparer hitComparer = new HitComparer(); public int MaxRaycastHits { get { return maxRaycastHits; } set { maxRaycastHits = Mathf.Min(value, MAX_RAYCAST_HITS_MAX); if (Application.isPlaying && hits != null && hits.Length != maxRaycastHits) { hits = new RaycastHit[maxRaycastHits]; } } } /// Camera used for masking layers and determining the screen position of the raycast result. public override Camera eventCamera { get { GvrBasePointer pointer = GvrPointerInputModule.Pointer; if (pointer == null) { return null; } return pointer.PointerCamera; } } /// Event mask used to determine which objects will receive events. public int finalEventMask { get { return (eventCamera != null) ? eventCamera.cullingMask & eventMask : NO_EVENT_MASK_SET; } } /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. public LayerMask eventMask { get { return raycasterEventMask; } set { raycasterEventMask = value; } } protected GvrPointerPhysicsRaycaster() { } protected override void Awake() { base.Awake(); hits = new RaycastHit[maxRaycastHits]; } protected override bool PerformRaycast(GvrBasePointer.PointerRay pointerRay, float radius, PointerEventData eventData, List resultAppendList) { if (eventCamera == null) { return false; } int numHits; if (radius > 0.0f) { numHits = Physics.SphereCastNonAlloc(pointerRay.ray, radius, hits, pointerRay.distance, finalEventMask); } else { numHits = Physics.RaycastNonAlloc(pointerRay.ray, hits, pointerRay.distance, finalEventMask); } if (numHits == 0) { return false; } if (numHits == MaxRaycastHits) { MaxRaycastHits *= 2; Debug.LogWarningFormat("Physics Raycast/Spherecast returned {0} hits, which is the current " + "maximum and means that some hits may have been lost. Setting maxRaycastHits to {1}. " + "Please set maxRaycastHits to a sufficiently high value for your scene.", numHits, MaxRaycastHits); } Array.Sort(hits, 0, numHits, hitComparer); for (int i = 0; i < numHits; ++i) { Vector3 projection = Vector3.Project(hits[i].point - pointerRay.ray.origin, pointerRay.ray.direction); Vector3 hitPosition = projection + pointerRay.ray.origin; float resultDistance = hits[i].distance + pointerRay.distanceFromStart; Transform pointerTransform = GvrPointerInputModule.Pointer.PointerTransform; float delta = (hitPosition - pointerTransform.position).magnitude; if (delta < pointerRay.distanceFromStart) { continue; } RaycastResult result = new RaycastResult { gameObject = hits[i].collider.gameObject, module = this, distance = resultDistance, worldPosition = hitPosition, worldNormal = hits[i].normal, screenPosition = eventCamera.WorldToScreenPoint(hitPosition), index = resultAppendList.Count, sortingLayer = 0, sortingOrder = 0 }; resultAppendList.Add(result); } return true; } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); // Makes sure that the hits buffer is updated if maxRaycastHits is changed in the inspector // while testing in the editor. if (Application.isPlaying) { MaxRaycastHits = maxRaycastHits; } } #endif // UNITY_EDITOR }