|
|
using System;using UnityEngine;using UnityEngine.Serialization;// ReSharper disable UnusedMember.Global
namespace ARLocation{ using Utils;
/// <summary>
/// This component, when attached to a GameObject, makes it traverse a
/// path that interpolates a given set of geographical locations.
/// </summary>
[AddComponentMenu("AR+GPS/Move Along Path")] [HelpURL("https://http://docs.unity-ar-gps-location.com/guide/#movealongpath")] [DisallowMultipleComponent] public class MoveAlongPath : MonoBehaviour { [Serializable] public class PathSettingsData { /// <summary>
/// The LocationPath describing the path to be traversed.
/// </summary>
[Tooltip("The LocationPath describing the path to be traversed.")] public LocationPath LocationPath;
/// <summary>
/// The number of points-per-segment used to calculate the spline.
/// </summary>
[Tooltip("The number of points-per-segment used to calculate the spline.")] public int SplineSampleCount = 250;
/// <summary>
/// If present, renders the spline in the scene using the given line renderer.
/// </summary>
[FormerlySerializedAs("lineRenderer")] [Tooltip("If present, renders the spline in the scene using the given line renderer.")] public LineRenderer LineRenderer; }
[Serializable] public class PlaybackSettingsData { /// <summary>
/// The speed along the path.
/// </summary>
[Tooltip("The speed along the path.")] public float Speed = 0f;
/// <summary>
/// The up direction to be used for orientation along the path.
/// </summary>
[Tooltip("The up direction to be used for orientation along the path.")] public Vector3 Up = Vector3.up;
/// <summary>
/// If true, play the path traversal in a loop.
/// </summary>
[Tooltip("If true, play the path traversal in a loop.")] public bool Loop = true;
/// <summary>
/// If true, start playing automatically.
/// </summary>
[Tooltip("If true, start playing automatically.")] public bool AutoPlay = true;
[FormerlySerializedAs("offset")] [Tooltip("The parameters offset; marks the initial position of the object along the curve.")] public float Offset; }
[Serializable] public class PlacementSettingsData { [Tooltip("The altitude mode. The altitude modes of the individual path locations are ignored, and this will be used instead.")] public AltitudeMode AltitudeMode = AltitudeMode.DeviceRelative;
[Tooltip( "The maximum number of times this object will be affected by GPS location updates. Zero means no limits are imposed.")] public uint MaxNumberOfLocationUpdates = 4; }
[Serializable] public class StateData { public uint UpdateCount; public Vector3[] Points; public int PointCount; public bool Playing; public Spline Spline; public Vector3 Translation; public float Speed; }
public PathSettingsData PathSettings = new PathSettingsData(); public PlaybackSettingsData PlaybackSettings = new PlaybackSettingsData(); public PlacementSettingsData PlacementSettings = new PlacementSettingsData();
public float Speed { get => state.Speed; set => state.Speed = value; }
[Space(4.0f)]
[Header("Debug")] [Tooltip("When debug mode is enabled, this component will print relevant messages to the console. Filter by 'MoveAlongPath' in the log output to see the messages.")] public bool DebugMode;
[Space(4.0f)]
private StateData state = new StateData(); private ARLocationProvider locationProvider; private float u; private GameObject arLocationRoot; private Transform mainCameraTransform; private bool useLineRenderer; private bool hasInitialized; private GroundHeight groundHeight;
private bool HeightRelativeToDevice => PlacementSettings.AltitudeMode == AltitudeMode.DeviceRelative; private bool HeightGroundRelative => PlacementSettings.AltitudeMode == AltitudeMode.GroundRelative;
/// <summary>
/// Change the `LocationPath` the GameObject will traverse.
/// </summary>
/// <param name="path"></param>
public void SetLocationPath(LocationPath path) { PathSettings.LocationPath = path;
state.PointCount = PathSettings.LocationPath.Locations.Length; state.Points = new Vector3[state.PointCount];
BuildSpline(locationProvider.CurrentLocation.ToLocation()); }
void Start() { if (PathSettings.LocationPath == null) { throw new NullReferenceException("[AR+GPS][MoveAlongPath]: Null Path! Please set the 'LocationPath' property!"); }
locationProvider = ARLocationProvider.Instance; locationProvider.OnLocationUpdatedEvent(LocationUpdated);
mainCameraTransform = ARLocationManager.Instance.MainCamera.transform; arLocationRoot = ARLocationManager.Instance.gameObject; // Misc.FindAndLogError("ARLocationRoot", "[ARLocationMoveAlongPath]: ARLocationRoot GameObject not found.");
Initialize(); hasInitialized = true; }
private void Initialize() { state.PointCount = PathSettings.LocationPath.Locations.Length; state.Points = new Vector3[state.PointCount]; state.Speed = PlaybackSettings.Speed;
useLineRenderer = PathSettings.LineRenderer != null;
transform.SetParent(arLocationRoot.transform);
state.Playing = PlaybackSettings.AutoPlay;
u += PlaybackSettings.Offset;
groundHeight = GetComponent<GroundHeight>(); if (PlacementSettings.AltitudeMode == AltitudeMode.GroundRelative) { if (!groundHeight) { groundHeight = gameObject.AddComponent<GroundHeight>(); groundHeight.Settings.DisableUpdate = true; } } else { if (groundHeight) { Destroy(groundHeight); groundHeight = null; } }
if (!hasInitialized) { locationProvider.OnProviderRestartEvent(ProviderRestarted); }
if (locationProvider.IsEnabled) { LocationUpdated(locationProvider.CurrentLocation, locationProvider.LastLocation); } }
private void ProviderRestarted() { state.UpdateCount = 0; }
public void Restart() { state = new StateData(); Initialize(); }
/// <summary>
/// Starts playing or resumes the playback.
/// </summary>
public void Play() { state.Playing = true; }
/// <summary>
/// Moves the object to the spline point corresponding
/// to the given parameter.
/// </summary>
/// <param name="t">Between 0 and 1</param>
public void GoTo(float t) { u = Mathf.Clamp(t, 0, 1); }
/// <summary>
/// Pauses the movement along the path.
/// </summary>
public void Pause() { state.Playing = false; }
/// <summary>
/// Stops the movement along the path.
/// </summary>
public void Stop() { state.Playing = false; u = 0; }
private void BuildSpline(Location location) { for (var i = 0; i < state.PointCount; i++) { var loc = PathSettings.LocationPath.Locations[i];
state.Points[i] = Location.GetGameObjectPositionForLocation(arLocationRoot.transform, mainCameraTransform, location, loc, HeightRelativeToDevice || HeightGroundRelative);
Logger.LogFromMethod("MoveAlongPath", "BuildSpline", $"({gameObject.name}): Points[{i}] = {state.Points[i]}, geo-location = {loc}", DebugMode); }
state.Spline = Misc.BuildSpline(PathSettings.LocationPath.SplineType, state.Points, PathSettings.SplineSampleCount, PathSettings.LocationPath.Alpha); }
private void LocationUpdated(LocationReading location, LocationReading _) { Logger.LogFromMethod("MoveAlongPath", "LocationUpdated", $"({gameObject.name}): New device location {location}", DebugMode);
if (PlacementSettings.MaxNumberOfLocationUpdates > 0 && state.UpdateCount > PlacementSettings.MaxNumberOfLocationUpdates) { Logger.LogFromMethod("MoveAlongPath", "LocationUpdated", $"({gameObject.name}): Max number of updates reached! returning", DebugMode); return; }
BuildSpline(location.ToLocation()); state.Translation = new Vector3(0, 0, 0);
state.UpdateCount++; }
private void Update() { if (!state.Playing) { return; }
// If there is no location provider, or spline, do nothing
if (state.Spline == null || !locationProvider.IsEnabled) { return; }
// Get spline point at current parameter
var s = state.Spline.Length * u;
var data = state.Spline.GetPointAndTangentAtArcLength(s); var tan = arLocationRoot.transform.InverseTransformVector(data.tangent);
transform.position = data.point;
var groundY = 0.0f; if (groundHeight) { var position = transform.position; groundY = groundHeight.CurrentGroundY; position = MathUtils.SetY(position, position.y + groundY); transform.position = position; }
// Set orientation
transform.localRotation = Quaternion.LookRotation(tan, PlaybackSettings.Up);
// Check if we reached the end of the spline
u = u + (state.Speed * Time.deltaTime) / state.Spline.Length; if (u >= 1 && !PlaybackSettings.Loop) { u = 0; state.Playing = false; } else { u = u % 1.0f; }
// If there is a line renderer, render the path
if (useLineRenderer) { PathSettings.LineRenderer.useWorldSpace = true; var t = arLocationRoot.transform; state.Spline.DrawCurveWithLineRenderer(PathSettings.LineRenderer, p => MathUtils.SetY(p, p.y + groundY)); //t.TransformVector(p - state.Translation));
} }
private void OnDestroy() { locationProvider.OnLocationUpdatedDelegate -= LocationUpdated; locationProvider.OnRestartDelegate -= ProviderRestarted; } }}
|