350 lines
12 KiB
C#
350 lines
12 KiB
C#
|
|
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 = 1.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. This will
|
||
|
|
/// have the effect of reseting the movement to the start of the path.
|
||
|
|
/// </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];
|
||
|
|
u = 0;
|
||
|
|
|
||
|
|
//added by thuang
|
||
|
|
if (locationProvider != null)
|
||
|
|
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<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);
|
||
|
|
}
|
||
|
|
|
||
|
|
locationProvider.OnLocationUpdatedEvent(LocationUpdated);
|
||
|
|
|
||
|
|
//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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|