using System;
using UnityEngine;
#if !ARGPS_USE_VUFORIA
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
#endif
#pragma warning disable
using Logger = ARLocation.Utils.Logger;
#pragma warning enable
#if ARGPS_USE_VUFORIA
using Vuforia;
#endif
namespace ARLocation
{
///
/// This component will change the Y component of the GameObject's position,
/// so that it is set to the level of the nearest detected ground plane.
///
[DisallowMultipleComponent]
public class GroundHeight : MonoBehaviour
{
[Serializable]
public class SettingsData
{
[Range(0, 10)]
public float InitialGroundHeightGuess = 1.4f;
[Range(0, 10)]
public float MinGroundHeight = 0.4f;
[Range(0, 10)]
public float MaxGroundHeight = 3.0f;
[Range(0, 1)]
public float Smoothing = 0.05f;
public float Altitude;
public bool DisableUpdate;
[Range(0, 0.1f)]
public float Precision = 0.005f;
public bool UseArLocationConfigSettings = true;
#if ARGPS_USE_VUFORIA
public float MinHitDistance = 0.5f;
#endif
}
[Serializable]
public class StateData
{
public float CurrentGroundY;
public float CurrentPlaneDistance = -1.0f;
public Vector3 CurrentPlaneCenter;
public bool NeedsUpdate = true;
}
public float CurrentGroundY => state.CurrentGroundY;
public SettingsData Settings = new SettingsData();
private readonly StateData state = new StateData();
private Camera mainCamera;
#if !ARGPS_USE_VUFORIA
private ARPlaneManager arPlaneManager;
private float targetY;
void Start()
{
arPlaneManager = FindObjectOfType();
var arSessionOrigin = FindObjectOfType();
mainCamera = ARLocationManager.Instance.MainCamera;
if (arPlaneManager == null)
{
if (arSessionOrigin == null)
{
Debug.LogWarning("[AR+GPS][GroundHeight#Start]: ARSessionOrigin not present in the scene!");
return;
}
arPlaneManager = arSessionOrigin.gameObject.AddComponent();
Utils.Misc.RequestPlaneDetectionMode(arPlaneManager, PlaneDetectionMode.Horizontal);
}
if (Settings.UseArLocationConfigSettings)
{
Settings.MaxGroundHeight = ARLocation.Config.MaxGroundHeight;
Settings.MinGroundHeight = ARLocation.Config.MinGroundHeight;
Settings.InitialGroundHeightGuess = ARLocation.Config.InitialGroundHeightGuess;
Settings.Smoothing = ARLocation.Config.GroundHeightSmoothingFactor;
}
state.CurrentGroundY = -Settings.InitialGroundHeightGuess;
arPlaneManager.planesChanged += ArPlaneManagerOnPlanesChanged;
UpdateObjectHeight();
}
void OnEnable()
{
if (arPlaneManager)
{
arPlaneManager.planesChanged += ArPlaneManagerOnPlanesChanged;
}
}
void OnDisable()
{
if (arPlaneManager)
{
arPlaneManager.planesChanged -= ArPlaneManagerOnPlanesChanged;
}
}
private void ArPlaneManagerOnPlanesChanged(ARPlanesChangedEventArgs eventArgs)
{
var addedPlanes = eventArgs.added;
var updatedPlanes = eventArgs.updated;
if (addedPlanes.Count <= 0 && updatedPlanes.Count <= 0)
{
// Debug.Log("[AR+GPS][GroundHeight#ArPlaneManagerOnPlanesChanged]: No added or modified planes!");
return;
}
foreach (ARPlane plane in addedPlanes)
{
ProcessPlane(plane);
}
foreach (ARPlane plane in updatedPlanes)
{
ProcessPlane(plane);
}
UpdateObjectHeight();
}
private void ProcessPlane(ARPlane plane)
{
// Debug.Log("[AR+GPS][GroundHeight#ProcessPlane]: Processing plane " + plane.trackableId.subId1 + ", " + plane.trackableId.subId2);
if (plane.alignment != PlaneAlignment.HorizontalDown && plane.alignment != PlaneAlignment.HorizontalUp)
{
// Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Wrong plane alignment!");
return;
}
if (!IsValidHeightForGround(plane.center.y))
{
// Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Invalid plane height!");
return;
}
var distance = MathUtils.HorizontalDistance(transform.position, plane.center);
if (!(state.CurrentPlaneDistance < 0) && (distance >= state.CurrentPlaneDistance))
{
// Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Plane too far!");
return;
}
// Debug.Log("[AR+GPS][GroundHeight#ProcessPlane]: New plane Y: " + plane.center.y);
state.CurrentPlaneDistance = distance;
state.CurrentGroundY = plane.center.y;
state.CurrentPlaneCenter = plane.center;
state.NeedsUpdate = true;
}
#else
private PlaneFinderBehaviour planeFinderBehaviour;
private void Start()
{
planeFinderBehaviour = FindObjectOfType();
mainCamera = ARLocationManager.Instance.MainCamera;
if (planeFinderBehaviour == null)
{
Logger.WarnFromMethod("VuforiaGroundHeight", "Start", "No planeFinderBehaviour!");
}
if (Settings.UseArLocationConfigSettings)
{
Settings.MaxGroundHeight = ARLocation.Config.MaxGroundHeight;
Settings.MinGroundHeight = ARLocation.Config.MinGroundHeight;
Settings.InitialGroundHeightGuess = ARLocation.Config.InitialGroundHeightGuess;
Settings.MinHitDistance = ARLocation.Config.VuforiaGroundHitTestDistance;
Settings.Smoothing = ARLocation.Config.GroundHeightSmoothingFactor;
}
state.CurrentGroundY = -Settings.InitialGroundHeightGuess;
planeFinderBehaviour.Height = Settings.InitialGroundHeightGuess;
planeFinderBehaviour.HitTestMode = HitTestMode.AUTOMATIC;
planeFinderBehaviour.OnAutomaticHitTest.AddListener(HitTestHandler);
planeFinderBehaviour.OnInteractiveHitTest.AddListener(HitTestHandler);
UpdateObjectHeight();
}
private void HitTestHandler(HitTestResult result)
{
//Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"result.Position = {result.Position}");
// If the ground height is not in range, reject
// var height = -1.0f * result.Position.y;
if (!IsValidHeightForGround(result.Position.y))
{
//Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"Not in range: {result.Position.y} {height}) > {Settings.MinGroundHeight}");
return;
}
var distanceToObject = MathUtils.HorizontalDistance(transform.position, result.Position);
// If hit to close to previous hit do nothing
if (state.CurrentPlaneDistance >= 0 && distanceToObject <= Settings.MinHitDistance)
{
//Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"Too close :{distanceToObject}");
return;
}
// If there is no previous hit, or if the new hit is closes to the object, apply new
// hit point.
if (state.CurrentPlaneDistance < 0 || distanceToObject < state.CurrentPlaneDistance)
{
state.CurrentPlaneDistance = distanceToObject;
state.CurrentGroundY = result.Position.y;
state.NeedsUpdate = true;
UpdateObjectHeight();
// Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"New ground Y = {state.CurrentGroundY}");
}
}
#endif
public void UpdateObjectHeight(bool force = false)
{
if (!state.NeedsUpdate && !force) return;
// Debug.Log("[AR+GPS][GroundHeight#UpdateObjectHeight]: Setting Y to " + state.CurrentGroundY);
if (Settings.Smoothing <= 0)
{
transform.position = MathUtils.SetY(transform.position, state.CurrentGroundY + Settings.Altitude);
}
state.NeedsUpdate = false;
}
private bool IsValidHeightForGround(float y)
{
var diff = (mainCamera.transform.position.y - y);
return (diff >= Settings.MinGroundHeight) && (diff <= Settings.MaxGroundHeight);
}
public void Update()
{
if (Settings.Smoothing <= 0 || Settings.DisableUpdate) return;
if (Mathf.Abs(transform.position.y - (state.CurrentGroundY + Settings.Altitude)) <= Settings.Precision)
{
transform.position = MathUtils.SetY(transform.position, (state.CurrentGroundY + Settings.Altitude));
return;
}
var t = 1.0f - Mathf.Pow(Settings.Smoothing, Time.deltaTime);
var position = transform.position;
var value = Mathf.Lerp(position.y, (state.CurrentGroundY + Settings.Altitude), t);
position = MathUtils.SetY(position, value);
transform.position = position;
}
}
}