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 {} } }