using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Google.Maps.Examples.Shared {
///
/// Component to update the 's Floating Origin whenever the
/// moves far enough.
///
/// The Floating Origin is used to periodically recenter the world, moving the world until the
/// player is back at the origin (0f, 0f, 0f). The prevents geometry being created with
/// increasingly large floating point coordinates, ultimately resulting in floating point rounding
/// errors.
///
public sealed class FloatingOriginUpdater : MonoBehaviour {
/// The last set floating origin.
public Vector3 FloatingOrigin { get; private set; }
///
/// Optional called whenever this script updates the world's Floating
/// Origin.
///
/// Passes in the amount the Floating Origin was moved by.
public OriginEvent OnFloatingOriginUpdate = new OriginEvent();
[Tooltip("Script for controlling Camera movement. Used to detect when the Camera has moved.")]
public CameraController CameraController;
[Tooltip(
"Distance in meters the Camera should move before the world's Floating Origin is " +
"reset. This value must be positive.")]
public float FloatingOriginRange = 200f;
[Tooltip("Should a debug message be shown whenever the Floating Origin is re-centered?")]
public bool DebugFloatingOrigin = true;
///
/// The to update the floating origin of.
///
public MapsService MapsService;
///
/// All s to be moved when the world's Floating Origin is moved.
///
///
/// If this array is not set by calling , then this array
/// is initialized with during . This is so, by
/// default, the scene's is moved when the Floating Origin is recentered,
/// resulting in a seamless recentering of the world that should be invisible to the user.
///
private GameObject[] AdditionalGameObjects;
///
/// Use 's OnMove event to detect when the
/// has moved far enough that the Floating Origin needs to be recentered.
///
private void Awake() {
if (MapsService == null) {
Debug.LogError(ExampleErrors.MissingParameter(
this, MapsService, "Maps Service", "is required for this script to work."));
return;
}
// Verify a Camera Controller has been given.
if (CameraController == null) {
Debug.LogError(ExampleErrors.MissingParameter(
this, CameraController, "Camera Controller", "to tell when the Camera has moved"));
return;
}
// Verify that a valid Floating Origin range was given, i.e. that given distance was not
// negative nor zero. Comparison is made to float.Epsilon instead of zero to account for float
// rounding errors.
if (FloatingOriginRange <= float.Epsilon) {
Debug.LogError(ExampleErrors.NotGreaterThanZero(
this,
FloatingOriginRange,
"Floating Origin Range",
"to tell how far the Camera should move before the Floating " + "Origin is reset"));
return;
}
// Store the initial position of the Camera on the ground plane.
FloatingOrigin = GetCameraPositionOnGroundPlane();
// If no additional GameObjects have been set (to be moved when the world's Floating Origin is
// recentered), set this array to be just Camera.main's GameObject. This is so that, by
// default, the scene's Camera is moved when the world is recentered, resulting in a seamless
// recentering of the world that should be invisible to the user.
if (AdditionalGameObjects == null) {
AdditionalGameObjects = new[] { Camera.main.gameObject };
}
}
private Vector3 GetCameraPositionOnGroundPlane() {
Vector3 result = Camera.main.transform.position;
// Ignore the Y value since the floating origin only really makes sense on the ground plane.
result.y = 0;
return result;
}
///
/// See if has moved far enough that the world's Floating Origin needs
/// to be recentered.
///
///
/// Amount has moved (not used as this value is recalculated here with
/// height ignored).
///
private void TryMoveFloatingOrigin(Vector3 moveAmount) {
if (MapsService == null) {
return;
}
Vector3 newFloatingOrigin = GetCameraPositionOnGroundPlane();
float distance = Vector3.Distance(FloatingOrigin, newFloatingOrigin);
// Reset the world's Floating Origin if (and only if) the Camera has moved far enough.
if (distance < FloatingOriginRange) {
return;
}
// The Camera's current position is given to MapsService's MoveFloatingOrigin function,
// along with any GameObjects to move along with the world (which will at least be the the
// Camera itself). This is so that the world, the Camera, and any extra GameObjects can all be
// moved together, until the Camera is over the origin again. Note that the MoveFloatingOrigin
// function automatically moves all geometry loaded by the Maps Service.
Vector3 originOffset =
MapsService.MoveFloatingOrigin(newFloatingOrigin, AdditionalGameObjects);
// Use event to inform other classes of change in origin. Note that because this is a Unity
// Event a null reference exception will not be triggered if no listeners have been added.
OnFloatingOriginUpdate.Invoke(originOffset);
// Set the new Camera origin. This ensures that we can accurately tell when the Camera has
// moved away from this new origin, and the world needs to be recentered again.
FloatingOrigin = newFloatingOrigin;
// Optionally print a debug message, saying how much the Floating Origin was moved by.
if (DebugFloatingOrigin && !Mathf.Approximately(distance, 0)) {
Debug.LogFormat("Floating Origin moved: world moved by {0}", originOffset);
}
}
///
/// Set an array of s to be moved whenever the world's Floating Origin
/// is recentered.
///
///
/// 's is automatically added to the given
/// array of s (if it is not already present), so that by default the
/// scene's is moved when the Floating Origin is recentered, resulting in a
/// seamless recentering of the world that should be invisible to the user.
///
///
/// Array of s to move with the world's Floating Origin.
///
public void SetAdditionalGameObjects(ICollection objects) {
// Check to see if the main Camera's GameObject is already a part of this given set of
// GameObjects, adding it if not and storing as the array of GameObjects to move when the
// world's Floating Origin is recentered.
GameObject cameraGameObject = Camera.main.gameObject;
List objectList = new List(objects);
if (!objects.Contains(cameraGameObject)) {
objectList.Add(cameraGameObject);
}
AdditionalGameObjects = objectList.ToArray();
}
void OnEnable() {
// Whenever the Camera moves, check to see if it has moved far enough that the world's
// Floating Origin needs to be re-centered.
CameraController.OnMove.AddListener(TryMoveFloatingOrigin);
}
void OnDisable() {
CameraController.OnMove.RemoveListener(TryMoveFloatingOrigin);
}
///
/// Optional called every frame this script updates the world's
/// floating origin.
///
/// Passes in the world's new floating origin.
[Serializable]
public class OriginEvent : UnityEvent {}
}
}