235 lines
8.7 KiB
C#
235 lines
8.7 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.
|
|
|
|
// The controller is not available for versions of Unity without the
|
|
// GVR native integration.
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
/// Implementation of GvrBasePointer for a laser pointer visual.
|
|
/// This script should be attached to the controller object.
|
|
/// The laser visual is important to help users locate their cursor
|
|
/// when its not directly in their field of view.
|
|
public class GvrLaserPointerImpl : GvrBasePointer {
|
|
/// Small offset to prevent z-fighting of the reticle (meters).
|
|
private const float Z_OFFSET_EPSILON = 0.1f;
|
|
|
|
/// Final size of the reticle in meters when it is 1 meter from the camera.
|
|
/// The reticle will be scaled based on the size of the mesh so that it's size
|
|
/// matches this size.
|
|
private const float RETICLE_SIZE_METERS = 0.1f;
|
|
|
|
/// The percentage of the reticle mesh that shows the reticle.
|
|
/// The rest of the reticle mesh is transparent.
|
|
private const float RETICLE_VISUAL_RATIO = 0.1f;
|
|
|
|
public Camera MainCamera { private get; set; }
|
|
|
|
public Color LaserColor { private get; set; }
|
|
|
|
public LineRenderer LaserLineRenderer { get; set; }
|
|
|
|
public float MaxLaserDistance { private get; set; }
|
|
|
|
public float MaxReticleDistance { private get; set; }
|
|
|
|
private GameObject reticle;
|
|
public GameObject Reticle {
|
|
get {
|
|
return reticle;
|
|
}
|
|
set {
|
|
reticle = value;
|
|
reticleMeshSizeMeters = 1.0f;
|
|
reticleMeshSizeRatio = 1.0f;
|
|
|
|
if (reticle != null) {
|
|
MeshFilter meshFilter = reticle.GetComponent<MeshFilter>();
|
|
if (meshFilter != null && meshFilter.mesh != null) {
|
|
reticleMeshSizeMeters = meshFilter.mesh.bounds.size.x;
|
|
if (reticleMeshSizeMeters != 0.0f) {
|
|
reticleMeshSizeRatio = 1.0f / reticleMeshSizeMeters;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Properties exposed for testing purposes.
|
|
public Vector3 PointerIntersection { get; private set; }
|
|
|
|
public bool IsPointerIntersecting { get; private set; }
|
|
|
|
public Ray PointerIntersectionRay { get; private set; }
|
|
|
|
// The size of the reticle's mesh in meters.
|
|
private float reticleMeshSizeMeters;
|
|
|
|
// The ratio of the reticleMeshSizeMeters to 1 meter.
|
|
// If reticleMeshSizeMeters is 10, then reticleMeshSizeRatio is 0.1.
|
|
private float reticleMeshSizeRatio;
|
|
|
|
private Vector3 lineEndPoint = Vector3.zero;
|
|
public override Vector3 LineEndPoint { get { return lineEndPoint; } }
|
|
|
|
public override float MaxPointerDistance {
|
|
get {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
return MaxReticleDistance;
|
|
#else
|
|
return 0;
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
}
|
|
|
|
public GvrLaserPointerImpl() {
|
|
MaxLaserDistance = 0.75f;
|
|
MaxReticleDistance = 2.5f;
|
|
}
|
|
|
|
#if !(UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR))
|
|
public override void OnStart() {
|
|
// Don't call base.Start() so that this pointer isn't activated when
|
|
// the editor doesn't have UNITY_HAS_GOOGLE_VR.
|
|
}
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
|
|
public override void OnInputModuleEnabled() {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
if (LaserLineRenderer != null) {
|
|
LaserLineRenderer.enabled = true;
|
|
}
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
public override void OnInputModuleDisabled() {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
if (LaserLineRenderer != null) {
|
|
LaserLineRenderer.enabled = false;
|
|
}
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
public override void OnPointerEnter(RaycastResult rayastResult, Ray ray,
|
|
bool isInteractive) {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
PointerIntersection = rayastResult.worldPosition;
|
|
PointerIntersectionRay = ray;
|
|
IsPointerIntersecting = true;
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
public override void OnPointerHover(RaycastResult rayastResult, Ray ray,
|
|
bool isInteractive) {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
PointerIntersection = rayastResult.worldPosition;
|
|
PointerIntersectionRay = ray;
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
public override void OnPointerExit(GameObject previousObject) {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
PointerIntersection = Vector3.zero;
|
|
PointerIntersectionRay = new Ray();
|
|
IsPointerIntersecting = false;
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
public override void OnPointerClickDown() {
|
|
// User has performed a click on the target. In a derived class, you could
|
|
// handle visual feedback such as laser or cursor color changes here.
|
|
}
|
|
|
|
public override void OnPointerClickUp() {
|
|
// User has released a click from the target. In a derived class, you could
|
|
// handle visual feedback such as laser or cursor color changes here.
|
|
}
|
|
|
|
public override void GetPointerRadius(out float enterRadius, out float exitRadius) {
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
if (Reticle != null) {
|
|
float reticleScale = Reticle.transform.localScale.x;
|
|
|
|
// Fixed size for enter radius to avoid flickering.
|
|
// This will cause some slight variability based on the distance of the object
|
|
// from the camera, and is optimized for the average case.
|
|
enterRadius = RETICLE_SIZE_METERS * 0.5f * RETICLE_VISUAL_RATIO;
|
|
|
|
// Dynamic size for exit radius.
|
|
// Always correct because we know the intersection point of the object and can
|
|
// therefore use the correct radius based on the object's distance from the camera.
|
|
exitRadius = reticleScale * reticleMeshSizeMeters * RETICLE_VISUAL_RATIO;
|
|
} else {
|
|
enterRadius = 0.0f;
|
|
exitRadius = 0.0f;
|
|
}
|
|
#else
|
|
enterRadius = 0.0f;
|
|
exitRadius = 0.0f;
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|
|
|
|
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
public void OnUpdate() {
|
|
// Set the reticle's position and scale
|
|
if (Reticle != null) {
|
|
if (IsPointerIntersecting) {
|
|
Vector3 controllerDiff = PointerTransform.position - PointerIntersectionRay.origin;
|
|
Vector3 proj = Vector3.Project(controllerDiff, PointerIntersectionRay.direction);
|
|
Vector3 controllerAlongRay = PointerIntersectionRay.origin + proj;
|
|
Vector3 difference = PointerIntersection - controllerAlongRay;
|
|
Vector3 clampedDifference = Vector3.ClampMagnitude(difference, MaxReticleDistance);
|
|
Vector3 clampedPosition = controllerAlongRay + clampedDifference;
|
|
Reticle.transform.position = clampedPosition;
|
|
} else {
|
|
Reticle.transform.localPosition = new Vector3(0, 0, MaxReticleDistance);
|
|
}
|
|
|
|
float reticleDistanceFromCamera =
|
|
(Reticle.transform.position - MainCamera.transform.position).magnitude;
|
|
float scale = RETICLE_SIZE_METERS * reticleMeshSizeRatio * reticleDistanceFromCamera;
|
|
Reticle.transform.localScale = new Vector3(scale, scale, scale);
|
|
}
|
|
|
|
if (LaserLineRenderer == null) {
|
|
Debug.LogWarning("Line renderer is null, returning");
|
|
return;
|
|
}
|
|
|
|
// Set the line renderer positions.
|
|
if (IsPointerIntersecting) {
|
|
Vector3 laserDiff = PointerIntersection - base.PointerTransform.position;
|
|
float intersectionDistance = laserDiff.magnitude;
|
|
Vector3 direction = laserDiff.normalized;
|
|
float laserDistance = intersectionDistance > MaxLaserDistance ? MaxLaserDistance : intersectionDistance;
|
|
lineEndPoint = base.PointerTransform.position + (direction * laserDistance);
|
|
} else {
|
|
lineEndPoint = base.PointerTransform.position + (base.PointerTransform.forward * MaxLaserDistance);
|
|
}
|
|
LaserLineRenderer.SetPosition(0,base.PointerTransform.position);
|
|
LaserLineRenderer.SetPosition(1,lineEndPoint);
|
|
|
|
// Adjust transparency
|
|
float alpha = GvrArmModel.Instance.preferredAlpha;
|
|
#if UNITY_5_6_OR_NEWER
|
|
LaserLineRenderer.startColor = Color.Lerp(Color.clear, LaserColor, alpha);
|
|
LaserLineRenderer.endColor = Color.clear;
|
|
#else
|
|
LaserLineRenderer.SetColors(Color.Lerp(Color.clear, LaserColor, alpha), Color.clear);
|
|
#endif // UNITY_5_6_OR_NEWER
|
|
}
|
|
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
|
|
}
|