using System;
using UnityEngine;
using UnityEngine.Serialization;
// ReSharper disable UnusedMember.Global
namespace ARLocation
{
using Utils;
///
/// This component, when attached to a GameObject, makes it traverse a
/// path that interpolates a given set of geographical locations.
///
[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
{
///
/// The LocationPath describing the path to be traversed.
///
[Tooltip("The LocationPath describing the path to be traversed.")]
public LocationPath LocationPath;
///
/// The number of points-per-segment used to calculate the spline.
///
[Tooltip("The number of points-per-segment used to calculate the spline.")]
public int SplineSampleCount = 250;
///
/// If present, renders the spline in the scene using the given line renderer.
///
[FormerlySerializedAs("lineRenderer")] [Tooltip("If present, renders the spline in the scene using the given line renderer.")]
public LineRenderer LineRenderer;
}
[Serializable]
public class PlaybackSettingsData
{
///
/// The speed along the path.
///
[Tooltip("The speed along the path.")]
public float Speed = 1.0f;
///
/// The up direction to be used for orientation along the path.
///
[Tooltip("The up direction to be used for orientation along the path.")]
public Vector3 Up = Vector3.up;
///
/// If true, play the path traversal in a loop.
///
[Tooltip("If true, play the path traversal in a loop.")]
public bool Loop = true;
///
/// If true, start playing automatically.
///
[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;
///
/// Change the `LocationPath` the GameObject will traverse. This will
/// have the effect of reseting the movement to the start of the path.
///
///
public void SetLocationPath(LocationPath path)
{
PathSettings.LocationPath = path;
state.PointCount = PathSettings.LocationPath.Locations.Length;
state.Points = new Vector3[state.PointCount];
u = 0;
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;
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;
Debug.Log(state.PointCount);
Debug.Log(state.Points);
useLineRenderer = PathSettings.LineRenderer != null;
transform.SetParent(arLocationRoot.transform);
state.Playing = PlaybackSettings.AutoPlay;
u += PlaybackSettings.Offset;
groundHeight = GetComponent();
if (PlacementSettings.AltitudeMode == AltitudeMode.GroundRelative)
{
if (!groundHeight)
{
groundHeight = gameObject.AddComponent();
groundHeight.Settings.DisableUpdate = true;
}
}
else
{
if (groundHeight)
{
Destroy(groundHeight);
groundHeight = null;
}
}
if (!hasInitialized)
{
locationProvider.OnProviderRestartEvent(ProviderRestarted);
}
locationProvider.OnLocationUpdatedEvent(LocationUpdated);
//if (locationProvider.IsEnabled)
// {
// LocationUpdated(locationProvider.CurrentLocation, locationProvider.LastLocation);
//}
}
private void ProviderRestarted()
{
state.UpdateCount = 0;
}
public void Restart()
{
state = new StateData();
Initialize();
}
///
/// Starts playing or resumes the playback.
///
public void Play()
{
state.Playing = true;
}
///
/// Moves the object to the spline point corresponding
/// to the given parameter.
///
/// Between 0 and 1
public void GoTo(float t)
{
u = Mathf.Clamp(t, 0, 1);
}
///
/// Pauses the movement along the path.
///
public void Pause()
{
state.Playing = false;
}
///
/// Stops the movement along the path.
///
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;
}
}
}