AR + GPS Location  3.0.0
All Classes Namespaces Functions Variables Properties Events Pages
PlaceAtLocation.cs
1 using System;
2 using ARLocation.UI;
3 using UnityEngine;
4 using UnityEngine.Events;
5 
6 namespace ARLocation
7 {
8  using Utils;
9 
10  [Serializable]
11  public class OverrideAltitudeData
12  {
13  [Tooltip("If true, override the LocationData's altitude options.")]
14  public bool OverrideAltitude;
15 
16  [Tooltip("The override altitude value.")]
17  public double Altitude;
18 
19  [Tooltip("The override altitude mode.")]
20  public AltitudeMode AltitudeMode = AltitudeMode.GroundRelative;
21  }
22 
23  [Serializable]
24  public class LocationPropertyData
25  {
26  [Serializable]
27  public enum LocationPropertyType
28  {
29  Location,
31  }
32 
33  [Tooltip("The type of location coordinate input used. Either 'Location' to directly input location " +
34  "coordinates, or 'LocationData' to use a ScriptableObject.")]
35  public LocationPropertyType LocationInputType = LocationPropertyType.Location;
36 
37  [Tooltip("A LocationData ScriptableObject storing the desired GPS coordinates to place the object.")]
39 
40  [Tooltip("Input the desired GPS coordinates here.")]
41  public Location Location = new Location();
42 
43  [Tooltip("Use this to override the LocationData's altitude options.")]
45  }
46 
50  [AddComponentMenu("AR+GPS/Place At Location")]
51  [HelpURL("https://http://docs.unity-ar-gps-location.com/guide/#placeatlocation")]
52  [DisallowMultipleComponent]
53  public class PlaceAtLocation : MonoBehaviour
54  {
55  [Serializable]
56  public class ObjectUpdatedEvent : UnityEvent<GameObject, Location, int>
57  {
58  }
59 
60  [Serializable]
61  public class PlaceAtOptions
62  {
63  [Tooltip(
64  "The smoothing factor for movement due to GPS location adjustments; if set to zero it is disabled."),
65  Range(0, 1)]
66  public float MovementSmoothing = 0.05f;
67 
68  [Tooltip(
69  "The maximum number of times this object will be affected by GPS location updates. Zero means no limits are imposed.")]
70  public int MaxNumberOfLocationUpdates = 4;
71 
72  [Tooltip("If true, use a moving average filter.")]
73  public bool UseMovingAverage;
74 
75  [Tooltip(
76  "If true, the object will be hidden until the object is placed at the geolocation. If will enable/disable the MeshRenderer or SkinnedMeshRenderer " +
77  "when available, and enable/disable all child game objects.")]
78  public bool HideObjectUntilItIsPlaced = true;
79 
80  [ConditionalPropertyAttribute("HideObjectUntilItIsPlaced")]
81  [Tooltip("The number of location updates to wait until the object is shown after being initially " +
82  "hidden from view. Only works when 'Hide Object Until It Is Placed' is set to true. If this "+
83  "is set to 0, 'Hide Object Until It Is Placed' will be disabled.")]
84  public uint ShowObjectAfterThisManyUpdates = 1;
85  }
86 
87  [Serializable]
88  public class LocationSettingsData
89  {
90  public LocationPropertyData LocationInput = new LocationPropertyData();
91 
92  public Location GetLocation()
93  {
94  Location location;
95 
96  if (LocationInput.LocationInputType ==
97  LocationPropertyData.LocationPropertyType.LocationData)
98  {
99  if (LocationInput.LocationData == null)
100  {
101  Debug.LogWarning("[AR+GPS][LocationSettingsData#GetLocation]: " +
102  "Null LocationData; falling back to Location. When using `Location Input Type = Location Data` " +
103  "make sure you associate a LocationData ScriptableObject to it.");
104 
105  location = LocationInput.Location.Clone();
106  }
107  else
108  {
109  location = LocationInput.LocationData.Location.Clone();
110 
111  if (LocationInput.OverrideAltitudeData.OverrideAltitude)
112  {
113  location.Altitude = LocationInput.OverrideAltitudeData.Altitude;
114  location.AltitudeMode = LocationInput.OverrideAltitudeData.AltitudeMode;
115  }
116  }
117  }
118  else
119  {
120  location = LocationInput.Location.Clone();
121  }
122 
123  return location;
124  }
125  }
126 
127  [Serializable]
128  public class StateData
129  {
130  public Location Location;
131  public uint LocationUpdatedCount;
132  public uint PositionUpdatedCount;
133  public bool Paused;
134  }
135 
136  public LocationSettingsData LocationOptions = new LocationSettingsData();
137 
138  [Space(4.0f)] public PlaceAtOptions PlacementOptions = new PlaceAtOptions();
139 
140  [Space(4.0f)]
141 
142  [Header("Debug")]
143  [Tooltip("When debug mode is enabled, this component will print relevant messages to the console. Filter by 'PlateAtLocation' in the log output to see the messages. It will also " +
144  "display the direction from the user to the object on the screen, as well as a line renderer from the camera to the object location. To customize how this line looks, add " +
145  "a Line Renderer component to this game object.")]
146  public bool DebugMode;
147 
148  [Space(4.0f)]
149 
150  [Header("Events")]
151  [Space(4.0f)]
152  [Tooltip(
153  "Event called when the object's location is updated. The arguments are the current GameObject, the location, and the number of location updates received " +
154  "by the object so far.")]
155  public ObjectUpdatedEvent ObjectLocationUpdated = new ObjectUpdatedEvent();
156 
157  [Tooltip(
158  "Event called when the object's position is updated after a location update. " +
159  "If the Movement Smoothing is larger than 0, this will fire at a later time than the Location Updated event. The arguments are the current GameObject, the location, and the number of position updates received " +
160  "by the object so far.")]
161  public ObjectUpdatedEvent ObjectPositionUpdated = new ObjectUpdatedEvent();
162 
163 
164  public Location Location
165  {
166  get => state.Location;
167 
168  set
169  {
170  if (!hasInitialized)
171  {
172  LocationOptions.LocationInput.LocationInputType =
173  LocationPropertyData.LocationPropertyType.Location;
174 
175  LocationOptions.LocationInput.LocationData = null;
176  LocationOptions.LocationInput.Location = value.Clone();
177 
178  return;
179  }
180 
181  if (groundHeight != null)
182  {
183  groundHeight.Settings.Altitude = (float) value.Altitude;
184  }
185 
186  state.Location = value.Clone();
187  UpdatePosition(locationProvider.CurrentLocation.ToLocation(), true);
188  }
189  }
190 
191  public float SceneDistance
192  {
193  get
194  {
195  var cameraPos = mainCameraTransform.position;
196 
197  return Vector3.Distance(cameraPos, transform.position);
198  }
199  }
200 
201  public double RawGpsDistance =>
202  Location.HorizontalDistance(locationProvider.Provider.CurrentLocationRaw.ToLocation(),
203  state.Location);
204 
205  public bool Paused
206  {
207  get => state.Paused;
208  set => state.Paused = value;
209  }
210 
211  public bool UseGroundHeight => state.Location.AltitudeMode == AltitudeMode.GroundRelative;
212 
213  private StateData state = new StateData();
214 
215  private ARLocationProvider locationProvider;
216  private Transform arLocationRoot;
217  private SmoothMove smoothMove;
218  private MovingAveragePosition movingAverageFilter;
219  private GameObject debugPanel;
220  private ARLocationManager arLocationManager;
221  private Transform mainCameraTransform;
222  private bool hasInitialized;
223  private GroundHeight groundHeight;
224 
225  // Use this for initialization
226  void Awake()
227  {
228  locationProvider = ARLocationProvider.Instance;
229  arLocationManager = ARLocationManager.Instance;
230  arLocationRoot = arLocationManager.gameObject.transform;
231  mainCameraTransform = arLocationManager.MainCamera.transform;
232 
233  if (locationProvider == null)
234  {
235  Debug.LogError("[AR+GPS][PlaceAtLocation]: LocationProvider GameObject or Component not found.");
236  return;
237  }
238 
239  Initialize();
240 
241  hasInitialized = true;
242  }
243 
244  public void Restart()
245  {
246  Logger.LogFromMethod("PlaceAtLocation", "Restart", $"({gameObject.name}) - Restarting!", DebugMode);
247 
248  RemoveLocationProviderListeners();
249 
250  state = new StateData();
251  Initialize();
252 
253  if (locationProvider.IsEnabled)
254  {
255  locationUpdatedHandler(locationProvider.CurrentLocation, locationProvider.LastLocation);
256  }
257  }
258 
259  void Initialize()
260  {
261  state.Location = LocationOptions.GetLocation();
262 
263  Transform transform1;
264  (transform1 = transform).SetParent(arLocationRoot.transform, false);
265  transform1.localPosition = Vector3.zero;
266 
267  if (!hasInitialized)
268  {
269  if (PlacementOptions.HideObjectUntilItIsPlaced)
270  {
271  Misc.HideGameObject(gameObject);
272  }
273 
274  if (PlacementOptions.MovementSmoothing > 0)
275  {
276  smoothMove = SmoothMove.AddSmoothMove(gameObject, PlacementOptions.MovementSmoothing);
277  }
278 
279  if (UseGroundHeight)
280  {
281  groundHeight = gameObject.AddComponent<GroundHeight>();
282  groundHeight.Settings.Altitude = (float) state.Location.Altitude;
283  }
284 
285  if (PlacementOptions.UseMovingAverage)
286  {
287  movingAverageFilter = new MovingAveragePosition
288  {
289  aMax = locationProvider.Provider.Options.AccuracyRadius > 0
290  ? locationProvider.Provider.Options.AccuracyRadius
291  : 20
292  };
293  }
294 
295  if (DebugMode)
296  {
297  gameObject.AddComponent<DebugDistance>();
298  }
299 
300  if (PlacementOptions.ShowObjectAfterThisManyUpdates == 0)
301  {
302  PlacementOptions.HideObjectUntilItIsPlaced = false;
303  }
304 
305  RegisterLocationProviderListeners();
306  }
307 
308  Logger.LogFromMethod("PlaceAtLocation", "Initialize", $"({gameObject.name}) initialized object with geo-location {state.Location}", DebugMode);
309  }
310 
311  private void RegisterLocationProviderListeners()
312  {
313  // NOTE(daniel): `useRawIfEnabled` = true in this call so that when adding objects at runtime it uses the
314  // best guess for the user location.
315  locationProvider.OnLocationUpdatedEvent(locationUpdatedHandler, true);
316 
317  locationProvider.OnProviderRestartEvent(ProviderRestarted);
318  }
319 
320  private void RemoveLocationProviderListeners()
321  {
322  locationProvider.OnLocationUpdatedDelegate -= locationUpdatedHandler;
323  locationProvider.OnRestartDelegate -= ProviderRestarted;
324  }
325 
326  private void ProviderRestarted()
327  {
328  Logger.LogFromMethod("PlaceAtLocation", "ProviderRestarted", $"({gameObject.name})", DebugMode);
329 
330  state.LocationUpdatedCount = 0;
331  state.PositionUpdatedCount = 0;
332  }
333 
334  private void locationUpdatedHandler(LocationReading currentLocation, LocationReading lastLocation)
335  {
336  UpdatePosition(currentLocation.ToLocation());
337  }
338 
339  public void UpdatePosition(Location deviceLocation, bool forceUpdate = false)
340  {
341  Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Received location update, location = {deviceLocation}", DebugMode);
342 
343  if (state.Paused)
344  {
345  Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Updates are paused; returning", DebugMode);
346  return;
347  }
348 
349  Vector3 targetPosition;
350  var location = state.Location;
351  var useSmoothMove = smoothMove != null;
352  var isHeightRelative = location.AltitudeMode == AltitudeMode.DeviceRelative;
353  // If we have reached the max number of location updates, do nothing
354  if ((PlacementOptions.MaxNumberOfLocationUpdates > 0) &&
355  (state.LocationUpdatedCount >= PlacementOptions.MaxNumberOfLocationUpdates) && !forceUpdate)
356  {
357  return;
358  }
359 
360  // Calculate the target position where the object will be placed next
361  if (movingAverageFilter != null)
362  {
363  var position = Location.GetGameObjectPositionForLocation(
364  arLocationRoot, mainCameraTransform, deviceLocation, location, isHeightRelative
365  );
366 
367  var accuracy = locationProvider.CurrentLocation.accuracy;
368 
369  movingAverageFilter.AddEntry(new DVector3(position), accuracy);
370 
371  targetPosition = movingAverageFilter.CalculateAveragePosition().toVector3();
372  }
373  else
374  {
375  targetPosition = Location.GetGameObjectPositionForLocation(
376  arLocationRoot, mainCameraTransform, deviceLocation, location, isHeightRelative
377  );
378  }
379 
380  // If GroundHeight is enabled, don't change the objects position
381  if (UseGroundHeight)
382  {
383  targetPosition.y = transform.position.y;
384 
385  if (useSmoothMove)
386  {
387  smoothMove.SmoothMoveMode = SmoothMove.Mode.Horizontal;
388  }
389  }
390 
391  Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Moving object to target position {targetPosition}", DebugMode);
392 
393  if (useSmoothMove && state.PositionUpdatedCount > 0)
394  {
395  Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Using smooth move...", DebugMode);
396  smoothMove.Move(targetPosition, PositionUpdated);
397  }
398  else
399  {
400  transform.position = targetPosition;
401  PositionUpdated();
402  }
403 
404  state.LocationUpdatedCount++;
405  ObjectLocationUpdated?.Invoke(gameObject, location, (int) state.LocationUpdatedCount);
406  }
407 
408  private void PositionUpdated()
409  {
410  if (PlacementOptions.HideObjectUntilItIsPlaced)// && state.PositionUpdatedCount <= 0)
411  {
412  if (state.PositionUpdatedCount == (PlacementOptions.ShowObjectAfterThisManyUpdates - 1))
413  {
414  Misc.ShowGameObject(gameObject);
415  }
416  }
417 
418  state.PositionUpdatedCount++;
419 
420  Logger.LogFromMethod("PlaceAtLocation", "PositionUpdated", $"({gameObject.name}): Object position updated! PositionUpdatedCount = {state.PositionUpdatedCount}, transform.position = {transform.position}", DebugMode);
421 
422  ObjectPositionUpdated?.Invoke(gameObject, state.Location, (int) state.PositionUpdatedCount);
423  }
424 
425  public static GameObject CreatePlacedInstance(GameObject go, Location location, PlaceAtOptions options, bool useDebugMode = false)
426  {
427  var instance = Instantiate(go, ARLocationManager.Instance.gameObject.transform);
428 
429  AddPlaceAtComponent(instance, location, options, useDebugMode);
430 
431  return instance;
432  }
433 
434  public static PlaceAtLocation AddPlaceAtComponent(GameObject go, Location location, PlaceAtOptions options,
435  bool useDebugMode = false)
436  {
437  var placeAt = go.AddComponent<PlaceAtLocation>();
438 
439  placeAt.PlacementOptions = options;
440  placeAt.LocationOptions.LocationInput.LocationInputType =
441  LocationPropertyData.LocationPropertyType.Location;
442  placeAt.LocationOptions.LocationInput.Location = location.Clone();
443  placeAt.DebugMode = useDebugMode;
444 
445  return placeAt;
446  }
447 
448  public static GameObject CreatePlacedInstanceAtWorldPosition(GameObject go, Vector3 worldPosition, PlaceAtOptions options, out Location location, bool useDebugMode = false)
449  {
450  location = ARLocationProvider.Instance.GetLocationForWorldPosition(worldPosition);
451 
452  return CreatePlacedInstance(go, location, options, useDebugMode);
453  }
454 
455 
456  private void OnDestroy()
457  {
458  RemoveLocationProviderListeners();
459  }
460  }
461 }
ARLocation.LocationPropertyData
Definition: PlaceAtLocation.cs:25
ARLocation.Location.Clone
Location Clone()
Clones this instance.
Definition: Location.cs:59
ARLocation.LocationData
Data used to construct a spline passing trough a set of geographical locations.
Definition: LocationData.cs:12
ARLocation.ConditionalPropertyAttribute
Definition: ConditionalPropertyAttribute.cs:7
ARLocation.UI.DebugDistance
Definition: DebugDistance.cs:10
ARLocation.Location
Represents a geographical location.
Definition: Location.cs:19
ARLocation.UI
Definition: ARTrackingInfo.cs:5
ARLocation.PlaceAtLocation.PlaceAtOptions
Definition: PlaceAtLocation.cs:62
ARLocation.OverrideAltitudeData
Definition: PlaceAtLocation.cs:12
ARLocation.PlaceAtLocation.ObjectUpdatedEvent
Definition: PlaceAtLocation.cs:57
ARLocation.ARLocationProvider.CurrentLocation
LocationReading CurrentLocation
The latest location data.
Definition: ARLocationProvider.cs:88
ARLocation.LocationData.Location
Location Location
The geographical locations that the path will interpolate.
Definition: LocationData.cs:17
ARLocation.Location.GetGameObjectPositionForLocation
static Vector3 GetGameObjectPositionForLocation(Transform arLocationRoot, Vector3 userPosition, Location userLocation, Location objectLocation, bool heightIsRelative)
Gets the game object world-position for location.
Definition: Location.cs:276
ARLocation.PlaceAtLocation
Apply to a GameObject to place it at a specified geographic location.
Definition: PlaceAtLocation.cs:54
ARLocation
Definition: ARLocationConfigInspector.cs:7
ARLocation.PlaceAtLocation.StateData
Definition: PlaceAtLocation.cs:129
ARLocation.PlaceAtLocation.LocationSettingsData
Definition: PlaceAtLocation.cs:89