diff --git a/LICENSE b/LICENSE
index 3be7b564b..8d733cd3d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2676,6 +2676,43 @@ Developed at SunSoft, a Sun Microsystems, Inc. business.
Permission to use, copy, modify, and distribute this
software is freely granted, provided that this notice
is preserved.
+*******************************************************************************
+Protocol Buffers
+*******************************************************************************
+Copyright 2008, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by the Protocol Buffer compiler is owned by the owner
+of the input file used when generating it. This code is not
+standalone and requires a support library to be linked with it. This
+support library is itself covered by the above license.
+
*******************************************************************************
Protocol Buffers for Java
*******************************************************************************
diff --git a/libraries/include/arcore_c_api.h b/libraries/include/arcore_c_api.h
index 478b840b0..551be35f6 100644
--- a/libraries/include/arcore_c_api.h
+++ b/libraries/include/arcore_c_api.h
@@ -296,6 +296,12 @@
/// @defgroup ArTrackable ArTrackable
/// Something that can be tracked and that anchors can be attached to.
+/// @defgroup ArVpsAvailabilityFuture ArVpsAvailabilityFuture
+/// An asynchronous operation checking VPS availability. The availability of VPS
+/// in a given location helps to improve the quality of Geospatial localization
+/// and tracking accuracy. See @c ::ArSession_checkVpsAvailabilityAsync for more
+/// details.
+
/// @ingroup ArConfig
/// An opaque session configuration object (@ref ownership "value type").
///
@@ -757,8 +763,6 @@ AR_DEFINE_ENUM(ArSessionFeature){
"@c ::ArSession_setCameraConfig with the desired config retrieved "
"from @c ::ArSession_getSupportedCameraConfigsWithFilter.") = 1,
- /// Specifies that the Session will use the Shareable Camera Injection API.
-
};
/// @ingroup shared_types
@@ -1312,13 +1316,34 @@ AR_DEFINE_ENUM(ArCloudAnchorMode){
};
/// @ingroup ArConfig
-/// Describes the desired behavior of ARCore Geospatial API features and
-/// capabilities. Not all devices support all modes. Use @c
-/// ::ArSession_isGeospatialModeSupported to find whether the current device
-/// supports a particular Geospatial mode. The default value is @c
-/// #AR_GEOSPATIAL_MODE_DISABLED.
-///
-/// Use @c ::ArConfig_setGeospatialMode to set the desired mode.
+/// Describes the desired behavior of the ARCore Geospatial API.
+/// The Geospatial API uses a combination of Google's Visual Positioning Service
+/// (VPS) and GPS to determine the geospatial pose.
+///
+/// The Geospatial API is able to provide the best user experience when it is
+/// able to generate high accuracy poses. However, the Geospatial API can be
+/// used anywhere, as long as the device is able to determine its location, even
+/// if the available location information has low accuracy.
+///
+/// - In areas with VPS coverage, the Geospatial API is able to generate high
+/// accuracy poses. This can work even where GPS accuracy is low, such as
+/// dense urban environments. Under typical conditions, VPS can be expected to
+/// provide positional accuracy typically better than 5 meters and often
+/// around 1 meter, and a rotational accuracy of better than 5 degrees. Use @c
+/// ::ArSession_checkVpsAvailabilityAsync to determine if a given location
+/// has VPS coverage.
+/// - In outdoor environments with few or no overhead obstructions, GPS may be
+/// sufficient to generate high accuracy poses. GPS accuracy may be low in
+/// dense urban environments and indoors.
+///
+/// A small number of ARCore supported devices do not support the Geospatial
+/// API. Use @c ::ArSession_isGeospatialModeSupported to determine if the
+/// current device is supported. Affected devices are also indicated on the ARCore supported devices
+/// page.
+///
+/// The default value is @c #AR_GEOSPATIAL_MODE_DISABLED. Use @c
+/// ::ArConfig_setGeospatialMode to set the desired mode.
AR_DEFINE_ENUM(ArGeospatialMode){
/// The Geospatial API is disabled. When a configuration with @c
/// #AR_GEOSPATIAL_MODE_DISABLED becomes active on the @c ::ArSession, current
@@ -5524,15 +5549,71 @@ void ArEarth_getCameraGeospatialPose(
ArGeospatialPose *out_camera_geospatial_pose);
/// @ingroup ArEarth
-/// Creates a new @c ::ArAnchor at the specified geospatial location and
+/// Converts the provided @p pose to a @c ::ArGeospatialPose with respect to the
+/// Earth. The @p out_geospatial_pose's rotation quaternion is a rotation with
+/// respect to an East-Up-South coordinate frame. An identity quaternion will
+/// have the anchor oriented such that X+ points to the east, Y+ points up away
+/// from the center of the earth, and Z+ points to the south.
+///
+/// The heading value for a @c ::ArGeospatialPose obtained by this function
+/// will be 0. See @c ::ArGeospatialPose_getEastUpSouthQuaternion for an
+/// orientation in 3D space.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] earth The @c ::ArEarth handle.
+/// @param[in] pose The local pose to translate to an Earth pose.
+/// @param[out] out_geospatial_pose Pointer to a @c ::ArGeospatialPose
+/// that receives the Geospatial pose.
+/// @return @c #AR_SUCCESS, or any of:
+/// - @c #AR_ERROR_NOT_TRACKING if the Earth's tracking state is not @c
+/// #AR_TRACKING_STATE_TRACKING.
+/// - @c #AR_ERROR_INVALID_ARGUMENT if any of the parameters are null.
+ArStatus ArEarth_getGeospatialPose(const ArSession *session,
+ const ArEarth *earth,
+ const ArPose *pose,
+ ArGeospatialPose *out_geospatial_pose);
+
+/// @ingroup ArEarth
+/// Converts the provided Earth specified horizontal position, altitude and
+/// rotation with respect to an east-up-south coordinate frame to a pose with
+/// respect to GL world coordinates.
+///
+/// Position near the north pole or south pole is not supported. If the
+/// latitude is within 0.1 degrees of the north pole or south pole (90 degrees
+/// or -90 degrees), this function will return @c #AR_ERROR_INVALID_ARGUMENT.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] earth The @c ::ArEarth handle.
+/// @param[in] latitude The latitude of the anchor relative to the WGS84
+/// ellipsoid.
+/// @param[in] longitude The longitude of the anchor relative to the
+/// WGS84 ellipsoid.
+/// @param[in] altitude The altitude of the anchor relative to the WGS84
+/// ellipsoid, in meters.
+/// @param[in] eus_quaternion_4 The rotation quaternion as {qx, qy, qx, qw}.
+/// @param[out] out_pose A pointer to @c ::ArPose of the local pose with the
+/// latest frame.
+/// @return @c #AR_SUCCESS, or any of:
+/// - @c #AR_ERROR_NOT_TRACKING if the Earth's tracking state is not @c
+/// #AR_TRACKING_STATE_TRACKING.
+/// - @c #AR_ERROR_INVALID_ARGUMENT, if any of the parameters are null or @p
+/// latitude is outside the allowable range.
+ArStatus ArEarth_getPose(const ArSession *session,
+ const ArEarth *earth,
+ double latitude,
+ double longitude,
+ double altitude,
+ const float *eus_quaternion_4,
+ ArPose *out_pose);
+
+/// @ingroup ArEarth
+/// Creates a new @c ::ArAnchor at the specified geodetic location and
/// orientation relative to the Earth.
///
/// Latitude and longitude are defined by the
/// WGS84
/// specification, and altitude values are defined as the elevation above
-/// the WGS84 ellipsoid in meters. To create an anchor using an altitude
-/// relative to the Earth's terrain instead of altitude above the WGS84
-/// ellipsoid, use @c ::ArEarth_resolveAndAcquireNewAnchorOnTerrain.
+/// the WGS84 ellipsoid in meters.
///
/// The rotation provided by @p eus_quaternion_4 is a rotation with respect to
/// an east-up-south coordinate frame. An identity rotation will have the anchor
@@ -5540,18 +5621,17 @@ void ArEarth_getCameraGeospatialPose(
/// of the earth, and Z+ points to the south.
///
/// To create an anchor that has the +Z axis pointing in the same direction as
-/// heading contained in an @c ::ArGeospatialPose, use the following formula:
+/// heading obtained from @c ::ArGeospatialPose, use the following formula:
///
/// \code
/// {qx, qy, qz, qw} = {0, sin((pi - heading * M_PI / 180.0) / 2), 0, cos((pi -
/// heading * M_PI / 180.0) / 2)}}.
/// \endcode
///
-/// An anchor's @c ::ArTrackingState will be @c #AR_TRACKING_STATE_PAUSED
-/// while @c ::ArEarth's @c ::ArTrackingState is @c #AR_TRACKING_STATE_PAUSED.
-/// Its tracking state will permanently become @c
-/// #AR_TRACKING_STATE_STOPPED if @c ::ArSession_configure sets the Geospatial
-/// mode to @c #AR_GEOSPATIAL_MODE_DISABLED.
+/// An anchor's @c ::ArTrackingState will be @c #AR_TRACKING_STATE_PAUSED while
+/// @c ::ArEarth is @c #AR_TRACKING_STATE_PAUSED. The tracking state will
+/// permanently become @c #AR_TRACKING_STATE_STOPPED if the @c ::ArSession
+/// configuration is set to @c #AR_GEOSPATIAL_MODE_DISABLED.
///
/// Creating anchors near the north pole or south pole is not supported. If the
/// latitude is within 0.1 degrees of the north pole or south pole (90 degrees
@@ -5585,48 +5665,40 @@ ArStatus ArEarth_acquireNewAnchor(ArSession *session,
ArAnchor **out_anchor);
/// @ingroup ArEarth
-/// Creates a new @c ::ArAnchor at a specified horizontal position and altitude
-/// relative to the horizontal position’s terrain. Terrain means the ground or
-/// ground floor inside a building with VPS coverage. If the altitude relative
-/// to the WGS84 ellipsoid is known, use @c ::ArEarth_acquireNewAnchor instead.
-///
-/// The specified @p altitude_above_terrain is interpreted to be relative to the
-/// Earth's terrain (or floor) at the specified latitude/longitude geospatial
-/// coordinates, rather than relative to the WGS84 ellipsoid. Specifying an
-/// altitude of 0 will position the anchor directly on the terrain (or floor)
-/// whereas specifying a positive altitude will position the anchor above the
-/// terrain (or floor), against the direction of gravity.
+/// Creates an anchor at a specified horizontal position and altitude relative
+/// to the horizontal position’s terrain. Terrain means the ground, or ground
+/// floor inside a building with VPS coverage. For areas not covered by VPS,
+/// consider using @c ::ArEarth_acquireNewAnchor to attach anchors to Earth.
+///
+/// The specified altitude is interpreted to be relative to the Earth's terrain
+/// (or floor) at the specified latitude/longitude geodetic coordinates, rather
+/// than relative to the WGS-84 ellipsoid. Specifying an altitude of 0 will
+/// position the anchor directly on the terrain (or floor) whereas specifying a
+/// positive altitude will position the anchor above the terrain (or floor),
+/// against the direction of gravity.
///
/// This creates a new @c ::ArAnchor and schedules a task to resolve the
/// anchor's pose using the given parameters. You may resolve multiple anchors
-/// at a time, but a session cannot be tracking more than 40 Terrain Anchors at
-/// time. Attempting to resolve more than 40 Terrain Anchors will result in
+/// at a time, but a session cannot be tracking more than 100 Terrain Anchors at
+/// time. Attempting to resolve more than 100 Terrain Anchors will result in
/// resolve calls returning status @c #AR_ERROR_RESOURCE_EXHAUSTED.
///
-/// If this function returns @c #AR_SUCCESS, the Terrain Anchor state of @p
+/// If this function returns @c #AR_SUCCESS, the terrain anchor state of @p
/// out_terrain_anchor will be @c #AR_TERRAIN_ANCHOR_STATE_TASK_IN_PROGRESS, and
-/// its tracking state will be @c #AR_TRACKING_STATE_PAUSED. The anchor
+/// its tracking state will be @c #AR_TRACKING_STATE_PAUSED. This anchor
/// remains in this state until its pose has been successfully resolved. If
-/// the resolving task results in an error, its tracking state will be
-/// permanently set to @c #AR_TRACKING_STATE_STOPPED, and @c
-/// ::ArAnchor_getTerrainAnchorState details the error that occurred using @c
-/// ::ArTerrainAnchorState. If this function's return value is not @c
-/// #AR_SUCCESS, then @p out_anchor will be set to @c NULL.
-///
-/// Creating a Terrain Anchor requires an active @c ::ArEarth which is @c
-/// #AR_EARTH_STATE_ENABLED. If it is not, then this function returns @c
+/// the resolving task results in an error, the tracking state will be set to @c
+/// #AR_TRACKING_STATE_STOPPED. If the return value is not @c #AR_SUCCESS, then
+/// @p out_cloud_anchor will be set to @c NULL.
+///
+/// Creating a terrain anchor requires an active @c ::ArEarth for which the @c
+/// ::ArEarthState is @c #AR_EARTH_STATE_ENABLED and @c ::ArTrackingState is @c
+/// #AR_TRACKING_STATE_TRACKING. If it is not, then this function returns @c
/// #AR_ERROR_ILLEGAL_STATE. This call also requires a working internet
/// connection to communicate with the ARCore API on Google Cloud. ARCore will
/// continue to retry if it is unable to establish a connection to the ARCore
/// service.
///
-/// A Terrain Anchor's @c ::ArTrackingState will be @c #AR_TRACKING_STATE_PAUSED
-/// while @c ::ArEarth's @c ::ArTrackingState is @c #AR_TRACKING_STATE_PAUSED.
-/// The anchor's tracking state will permanently become @c
-/// #AR_TRACKING_STATE_STOPPED if @c ::ArSession_configure is used to set @c
-/// #AR_GEOSPATIAL_MODE_DISABLED.
-///
-///
/// Latitude and longitude are defined by the
/// WGS84
/// specification.
@@ -5637,7 +5709,7 @@ ArStatus ArEarth_acquireNewAnchor(ArSession *session,
/// of the earth, and Z+ points to the south.
///
/// To create an anchor that has the +Z axis pointing in the same direction as
-/// heading contained in an @c ::ArGeospatialPose, use the following formula:
+/// heading obtained from @c ::ArGeospatialPose, use the following formula:
///
/// \code
/// {qx, qy, qz, qw} = {0, sin((pi - heading * M_PI / 180.0) / 2), 0, cos((pi -
@@ -5647,16 +5719,16 @@ ArStatus ArEarth_acquireNewAnchor(ArSession *session,
/// @param[in] session The ARCore session.
/// @param[in] earth The @c ::ArEarth handle.
/// @param[in] latitude The latitude of the anchor relative to the
-/// WGS84 ellipsoid.
+/// WGS-84 ellipsoid.
/// @param[in] longitude The longitude of the anchor relative to the
-/// WGS84 ellipsoid.
+/// WGS-84 ellipsoid.
/// @param[in] altitude_above_terrain The altitude of the anchor above the
/// Earth's terrain (or floor).
/// @param[in] eus_quaternion_4 The rotation quaternion as {qx, qy, qx, qw}.
/// @param[out] out_anchor The newly-created anchor.
/// @return @c #AR_SUCCESS or any of:
-/// - @c #AR_ERROR_ILLEGAL_STATE if @p earth's @c ::ArEarthState is not
-/// @c #AR_EARTH_STATE_ENABLED.
+/// - @c #AR_ERROR_ILLEGAL_STATE if ::ArEarthState is not
+/// AR_EARTH_STATE_ENABLED.
/// - @c #AR_ERROR_INVALID_ARGUMENT if @p latitude is outside the allowable
/// range, or if either @p session, @p earth, or @p eus_quaternion_4 is @c
/// NULL.
@@ -5688,10 +5760,10 @@ AR_DEFINE_ENUM(ArTerrainAnchorState){
/// ::ArSession_update call.
AR_TERRAIN_ANCHOR_STATE_TASK_IN_PROGRESS = 1,
- /// A resolving task for this Terrain Anchor has finished successfully.
+ /// A resolving task for this anchor has been successfully resolved.
AR_TERRAIN_ANCHOR_STATE_SUCCESS = 2,
- /// Resolving task for this Terrain Anchor finished with an internal
+ /// Resolving task for this anchor finished with an internal
/// error. The app should not attempt to recover from this error.
AR_TERRAIN_ANCHOR_STATE_ERROR_INTERNAL = -1,
@@ -5715,10 +5787,7 @@ AR_DEFINE_ENUM(ArTerrainAnchorState){
/// @ingroup ArAnchor
/// Gets the current Terrain Anchor state of the anchor. This state is
-/// guaranteed not to change until @c ::ArSession_update is called. For Anchors
-/// that are not Terrain Anchors, this function returns @c
-/// #AR_TERRAIN_ANCHOR_STATE_NONE. See @c ::ArTerrainAnchorState for the
-/// possible Terrain Anchor states.
+/// guaranteed not to change until @c ::ArSession_update is called.
///
/// @param[in] session The ARCore session.
/// @param[in] anchor The anchor to retrieve the terrain anchor state of.
@@ -5729,6 +5798,186 @@ void ArAnchor_getTerrainAnchorState(const ArSession *session,
const ArAnchor *anchor,
ArTerrainAnchorState *out_state);
+/// @ingroup ArVpsAvailabilityFuture
+/// The result of @c ::ArSession_checkVpsAvailabilityAsync, obtained by @c
+/// ::ArVpsAvailabilityFuture_getResult or from an invocation of an @c
+/// #ArCheckVpsAvailabilityCallback.
+AR_DEFINE_ENUM(ArVpsAvailability){
+ /// The request to the remote service is not yet completed, so the
+ /// availability is not yet known.
+ AR_VPS_AVAILABILITY_UNKNOWN = 0,
+ /// VPS is available at the requested location.
+ AR_VPS_AVAILABILITY_AVAILABLE = 1,
+ /// VPS is not available at the requested location.
+ AR_VPS_AVAILABILITY_UNAVAILABLE = 2,
+ /// An internal error occurred while determining availability.
+ AR_VPS_AVAILABILITY_ERROR_INTERNAL = -1,
+ /// The external service could not be reached due to a network connection
+ /// error.
+ AR_VPS_AVAILABILITY_ERROR_NETWORK_CONNECTION = -2,
+ /// An authorization error occurred when communicating with the Google Cloud
+ /// ARCore API. See Enable
+ /// the Geospatial API for troubleshooting steps.
+ AR_VPS_AVAILABILITY_ERROR_NOT_AUTHORIZED = -3,
+ /// Too many requests were sent.
+ AR_VPS_AVAILABILITY_ERROR_RESOURCE_EXHAUSTED = -4,
+};
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Callback definition for @c ::ArSession_checkVpsAvailabilityAsync. The
+/// @p context argument will be the same as that passed to
+/// @c ::ArSession_checkVpsAvailabilityAsync. The @p availability argument
+/// will be the same as the result obtained from the future returned by
+/// @c ::ArSession_checkVpsAvailabilityAsync.
+///
+/// It is a best practice to free @c context memory provided to @c
+/// ::ArSession_checkVpsAvailabilityAsync at the end of the callback
+/// implementation.
+typedef void (*ArCheckVpsAvailabilityCallback)(void *context,
+ ArVpsAvailability availability);
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Handle to an asynchronous operation launched by
+/// @c ::ArSession_checkVpsAvailabilityAsync.
+/// Release with @c ::ArVpsAvailabilityFuture_release.
+/// (@ref ownership "reference type, long-lived").
+typedef struct ArVpsAvailabilityFuture_ ArVpsAvailabilityFuture;
+
+/// @ingroup ArSession
+/// Gets the availability of the Visual Positioning System (VPS) at a specified
+/// horizontal position. The availability of VPS in a given location helps to
+/// improve the quality of Geospatial localization and tracking accuracy.
+///
+/// This launches an asynchronous operation used to query the Google Cloud
+/// ARCore API. This may be called without first calling @c ::ArSession_resume
+/// or @c ::ArSession_configure, for example to present an "Enter AR" button
+/// only when VPS is available.
+///
+/// The asynchronicity of this operation must be handled in one or
+/// both of the following ways:
+/// - The operation can be continually polled using @p out_future. When the
+/// future is created, its @c #ArFutureState will be set to @c
+/// #AR_FUTURE_STATE_PENDING.
+/// Use @c ::ArVpsAvailabilityFuture_getState to query the state of the
+/// operation. When its state is @c #AR_FUTURE_STATE_DONE, @c
+/// ::ArVpsAvailabilityFuture_getResult can be used to obtain the operation's
+/// result. The future must eventually be released using @c
+/// ::ArVpsAvailabilityFuture_release.
+/// - The operation's result can be reported via a callback. When providing a @p
+/// callback, ARCore will invoke the given function when the operation is
+/// complete, unless the future has been cancelled using @c
+/// ::ArVpsAvailabilityFuture_cancel on @p out_future. This callback will be
+/// invoked on the main
+/// thread. When providing a callback, you may provide a
+/// @p context, which will be passed as the first parameter to the callback.
+/// It is a best practice to free the memory of @p context at the end of the
+/// callback or when @c ::ArVpsAvailabilityFuture_cancel successfully cancels
+/// the callback.
+///
+/// Your app must be properly set up to communicate with the Google Cloud ARCore
+/// API in order to obtain a result from this call. See Check
+/// VPS Availability for more details on setup steps and usage examples.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] latitude_degrees The latitude to query, in degrees.
+/// @param[in] longitude_degrees The longitude to query, in degrees.
+/// @param[in] context An optional void pointer which is passed as the first
+/// parameter to an invocation of @p callback. The app must ensure that the
+/// memory remains valid until the callback has run, or the operation has
+/// been cancelled. This may be null.
+/// @param[in] callback An optional callback function. When the asynchronous
+/// operation is complete this callback will be invoked unless the
+/// operation has been cancelled. This may be null if no callback is
+/// desired, but one of @p callback and @p out_future must be non-null.
+/// @param[out] out_future A optional handler that can be used to poll and
+/// cancel the asynchronous operation. This argument may be null if no
+/// handler is desired, but one of @p callback and @p out_future must be
+/// non-null.
+/// @return @c #AR_SUCCESS, or any of:
+/// - @c #AR_ERROR_INTERNET_PERMISSION_NOT_GRANTED if the @c INTERNET permission
+/// has not been granted.
+/// - @c #AR_ERROR_INVALID_ARGUMENT if @p session is null, or both @p callback
+/// and @p out_future are null.
+ArStatus ArSession_checkVpsAvailabilityAsync(
+ ArSession *session,
+ double latitude_degrees,
+ double longitude_degrees,
+ void *context,
+ ArCheckVpsAvailabilityCallback callback,
+ ArVpsAvailabilityFuture **out_future);
+
+/// @ingroup ArVpsAvailabilityFuture
+/// The state of an asynchronous operation.
+AR_DEFINE_ENUM(ArFutureState){
+ /// The operation is still pending. The result of the operation isn't
+ /// available yet and any associated callback hasn't yet been dispatched or
+ /// invoked.
+ ///
+ /// Do not use this to check if the operation can be cancelled as the state
+ /// can change from another thread between the call to
+ /// @c ::ArVpsAvailabilityFuture_getState and @c
+ /// ::ArVpsAvailabilityFuture_cancel.
+ AR_FUTURE_STATE_PENDING = 0,
+
+ /// The operation has been cancelled. Any associated callback will never be
+ /// invoked.
+ AR_FUTURE_STATE_CANCELLED = 1,
+
+ /// The operation is complete and the result is available. If a callback was
+ /// associated with this future, it will soon be invoked with the result on
+ /// the main thread, if
+ /// it hasn't been invoked already.
+ AR_FUTURE_STATE_DONE = 2,
+};
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Gets the state of an asynchronous operation.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] future The handle for the asynchronous operation.
+/// @param[out] out_state The state of the operation.
+void ArVpsAvailabilityFuture_getState(const ArSession *session,
+ const ArVpsAvailabilityFuture *future,
+ ArFutureState *out_state);
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Returns the result of an asynchronous operation. The returned result is only
+/// valid when @c ::ArVpsAvailabilityFuture_getState returns @c
+/// #AR_FUTURE_STATE_DONE.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] future The handle for the asynchronous operation.
+/// @param[out] out_result_availability The result of the operation, if the
+/// Future's state is @c #AR_FUTURE_STATE_DONE.
+void ArVpsAvailabilityFuture_getResult(
+ const ArSession *session,
+ const ArVpsAvailabilityFuture *future,
+ ArVpsAvailability *out_result_availability);
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Tries to cancel execution of this operation. @p out_was_cancelled will be
+/// set to 1 if the operation was cancelled by this invocation, and in that case
+/// it is a best practice to free @c context memory provided to @c
+/// ::ArSession_checkVpsAvailabilityAsync.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] future The handle for the asynchronous operation.
+/// @param[out] out_was_cancelled Set to 1 if this invocation successfully
+/// cancelled the operation, 0 otherwise. This may be null.
+void ArVpsAvailabilityFuture_cancel(const ArSession *session,
+ ArVpsAvailabilityFuture *future,
+ int32_t *out_was_cancelled);
+
+/// @ingroup ArVpsAvailabilityFuture
+/// Releases a reference to a future. This does not mean that the operation will
+/// be terminated - see @c ::ArVpsAvailabilityFuture_cancel.
+///
+/// This function may safely be called with @c NULL - it will do nothing.
+void ArVpsAvailabilityFuture_release(ArVpsAvailabilityFuture *future);
+
// === ArGeospatialPose functions ===
/// @ingroup ArGeospatialPose
@@ -5828,6 +6077,10 @@ void ArGeospatialPose_getVerticalAccuracy(
/// @ingroup ArGeospatialPose
/// Gets the @c ::ArGeospatialPose's heading.
///
+/// This function will return values for @c ::ArGeospatialPose's
+/// from ArEarth_getCameraGeospatialPose() and returns 0 for all other @c
+/// ::ArGeospatialPose objects.
+///
/// Heading is specified in degrees clockwise from true north and approximates
/// the direction the device is facing. The value returned when facing north is
/// 0°, when facing east is 90°, when facing south is +/-180°, and when facing
@@ -5873,6 +6126,20 @@ void ArGeospatialPose_getHeadingAccuracy(
const ArGeospatialPose *geospatial_pose,
double *out_heading_accuracy_degrees);
+/// @ingroup ArGeospatialPose
+/// Extracts the orientation from an Geospatial pose. It represents rotation of
+/// the target with respect to the east-up-south coordinates. That is, X+ points
+/// east, Y+ points up, and Z+ points south. Right handed coordinate system.
+///
+/// @param[in] session The ARCore session.
+/// @param[in] geospatial_pose The geospatial pose.
+/// @param[out] out_quaternion_4 A pointer of 4 float values, which will store
+/// the quaternion values as [x, y, z, w].
+void ArGeospatialPose_getEastUpSouthQuaternion(
+ const ArSession *session,
+ const ArGeospatialPose *geospatial_pose,
+ float *out_quaternion_4);
+
/// @ingroup ArImageMetadata
/// Defines a rational data type in @c ::ArImageMetadata_const_entry.
///
diff --git a/samples/augmented_faces_java/app/build.gradle b/samples/augmented_faces_java/app/build.gradle
index be8828ce4..39f6b4580 100644
--- a/samples/augmented_faces_java/app/build.gradle
+++ b/samples/augmented_faces_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/augmented_image_c/app/build.gradle b/samples/augmented_image_c/app/build.gradle
index b0909c179..33ed274a3 100644
--- a/samples/augmented_image_c/app/build.gradle
+++ b/samples/augmented_image_c/app/build.gradle
@@ -53,8 +53,8 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
- natives 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
+ natives 'com.google.ar:core:1.34.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
diff --git a/samples/augmented_image_java/app/build.gradle b/samples/augmented_image_java/app/build.gradle
index f16ee01a4..cd208e274 100644
--- a/samples/augmented_image_java/app/build.gradle
+++ b/samples/augmented_image_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/cloud_anchor_java/app/build.gradle b/samples/cloud_anchor_java/app/build.gradle
index dafcbc2e7..8259456d3 100644
--- a/samples/cloud_anchor_java/app/build.gradle
+++ b/samples/cloud_anchor_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/computervision_c/app/build.gradle b/samples/computervision_c/app/build.gradle
index 0b7977cd6..091361a81 100644
--- a/samples/computervision_c/app/build.gradle
+++ b/samples/computervision_c/app/build.gradle
@@ -53,8 +53,8 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
- natives 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
+ natives 'com.google.ar:core:1.34.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
diff --git a/samples/computervision_java/app/build.gradle b/samples/computervision_java/app/build.gradle
index 28ea3c641..ba0c13c7e 100644
--- a/samples/computervision_java/app/build.gradle
+++ b/samples/computervision_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/geospatial_java/app/build.gradle b/samples/geospatial_java/app/build.gradle
index 5030fd330..3c43b461b 100644
--- a/samples/geospatial_java/app/build.gradle
+++ b/samples/geospatial_java/app/build.gradle
@@ -7,7 +7,7 @@ android {
// AR Optional apps must declare minSdkVersion >= 14.
// AR Required apps must declare minSdkVersion >= 24.
- minSdkVersion 24
+ minSdkVersion 28
targetSdkVersion 33
versionCode 1
versionName '1.0'
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
implementation 'com.google.android.gms:play-services-location:19.0.1'
// Obj - a simple Wavefront OBJ file loader
@@ -35,4 +35,6 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
+ implementation 'androidx.concurrent:concurrent-futures:1.1.0'
+ implementation 'com.google.guava:guava:31.1-android'
}
diff --git a/samples/geospatial_java/app/src/main/assets/shaders/cubemap_filter.frag b/samples/geospatial_java/app/src/main/assets/shaders/cubemap_filter.frag
index 3ab833926..94b17f368 100644
--- a/samples/geospatial_java/app/src/main/assets/shaders/cubemap_filter.frag
+++ b/samples/geospatial_java/app/src/main/assets/shaders/cubemap_filter.frag
@@ -82,7 +82,9 @@ vec4 Filter(const vec3 n) {
tangentToWorld[1] = cross(n, tangentToWorld[0]);
tangentToWorld[2] = n;
- ImportanceSampleCache cache = u_ImportanceSampleCaches[u_RoughnessLevel - 1];
+ // TODO(b/243456272): This clamp should not be necessary, but is here due to a
+ // driver issue with certain devices.
+ ImportanceSampleCache cache = u_ImportanceSampleCaches[max(0, u_RoughnessLevel - 1)];
vec3 radiance = vec3(0.0);
for (int i = 0; i < cache.number_of_entries; ++i) {
ImportanceSampleCacheEntry entry = cache.entries[i];
diff --git a/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/GeospatialActivity.java b/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/GeospatialActivity.java
index d4623db51..2836b2b0c 100644
--- a/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/GeospatialActivity.java
+++ b/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/GeospatialActivity.java
@@ -18,17 +18,27 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.location.Location;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.GuardedBy;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.fragment.app.DialogFragment;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.tasks.OnSuccessListener;
import com.google.ar.core.Anchor;
import com.google.ar.core.Anchor.TerrainAnchorState;
import com.google.ar.core.ArCoreApk;
@@ -37,8 +47,17 @@
import com.google.ar.core.Earth;
import com.google.ar.core.Frame;
import com.google.ar.core.GeospatialPose;
+import com.google.ar.core.HitResult;
+import com.google.ar.core.Plane;
+import com.google.ar.core.Point;
+import com.google.ar.core.Point.OrientationMode;
+import com.google.ar.core.PointCloud;
+import com.google.ar.core.Pose;
import com.google.ar.core.Session;
+import com.google.ar.core.Trackable;
import com.google.ar.core.TrackingState;
+import com.google.ar.core.VpsAvailability;
+import com.google.ar.core.VpsAvailabilityFuture;
import com.google.ar.core.examples.java.common.helpers.CameraPermissionHelper;
import com.google.ar.core.examples.java.common.helpers.DisplayRotationHelper;
import com.google.ar.core.examples.java.common.helpers.FullScreenHelper;
@@ -50,8 +69,9 @@
import com.google.ar.core.examples.java.common.samplerender.SampleRender;
import com.google.ar.core.examples.java.common.samplerender.Shader;
import com.google.ar.core.examples.java.common.samplerender.Texture;
+import com.google.ar.core.examples.java.common.samplerender.VertexBuffer;
import com.google.ar.core.examples.java.common.samplerender.arcore.BackgroundRenderer;
-import com.google.ar.core.examples.java.geospatial.PrivacyNoticeDialogFragment.NoticeDialogListener;
+import com.google.ar.core.examples.java.common.samplerender.arcore.PlaneRenderer;
import com.google.ar.core.exceptions.CameraNotAvailableException;
import com.google.ar.core.exceptions.FineLocationPermissionNotGrantedException;
import com.google.ar.core.exceptions.GooglePlayServicesLocationLibraryNotLinkedException;
@@ -62,6 +82,9 @@
import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException;
import com.google.ar.core.exceptions.UnsupportedConfigurationException;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -79,7 +102,9 @@
* and will be recreated once localized.
*/
public class GeospatialActivity extends AppCompatActivity
- implements SampleRender.Renderer, NoticeDialogListener {
+ implements SampleRender.Renderer,
+ VpsAvailabilityNoticeDialogFragment.NoticeDialogListener,
+ PrivacyNoticeDialogFragment.NoticeDialogListener {
private static final String TAG = GeospatialActivity.class.getSimpleName();
@@ -152,13 +177,17 @@ enum State {
private String lastStatusText;
private TextView geospatialPoseTextView;
private TextView statusTextView;
+ private TextView tapScreenTextView;
private Button setAnchorButton;
- private Button setTerrainAnchorButton;
private Button clearAnchorsButton;
+ private Switch terrainAnchorSwitch;
+ private PlaneRenderer planeRenderer;
private BackgroundRenderer backgroundRenderer;
private Framebuffer virtualSceneFramebuffer;
private boolean hasSetTextureNames = false;
+ // Set WGS84 anchor or Terrain anchor.
+ private boolean isTerrainAnchorMode = false;
// Virtual object (ARCore geospatial)
private Mesh virtualObjectMesh;
@@ -175,6 +204,25 @@ enum State {
private final float[] modelViewMatrix = new float[16]; // view x model
private final float[] modelViewProjectionMatrix = new float[16]; // projection x view x model
+ // Locks needed for synchronization
+ private final Object singleTapLock = new Object();
+
+ @GuardedBy("singleTapLock")
+ private MotionEvent queuedSingleTap;
+ // Tap handling and UI.
+ private GestureDetector gestureDetector;
+
+ // Point Cloud
+ private VertexBuffer pointCloudVertexBuffer;
+ private Mesh pointCloudMesh;
+ private Shader pointCloudShader;
+ // Keep track of the last point cloud rendered to avoid updating the VBO if point cloud
+ // was not changed. Do this using the timestamp since we can't compare PointCloud objects.
+ private long lastPointCloudTimestamp = 0;
+
+ // Provides device location.
+ private FusedLocationProviderClient fusedLocationClient;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -184,21 +232,46 @@ protected void onCreate(Bundle savedInstanceState) {
surfaceView = findViewById(R.id.surfaceview);
geospatialPoseTextView = findViewById(R.id.geospatial_pose_view);
statusTextView = findViewById(R.id.status_text_view);
+ tapScreenTextView = findViewById(R.id.tap_screen_text_view);
setAnchorButton = findViewById(R.id.set_anchor_button);
- setTerrainAnchorButton = findViewById(R.id.set_terrain_anchor_button);
clearAnchorsButton = findViewById(R.id.clear_anchors_button);
setAnchorButton.setOnClickListener(view -> handleSetAnchorButton());
- setTerrainAnchorButton.setOnClickListener(view -> handleSetTerrainAnchorButton());
clearAnchorsButton.setOnClickListener(view -> handleClearAnchorsButton());
- displayRotationHelper = new DisplayRotationHelper(/*context=*/ this);
+ terrainAnchorSwitch = findViewById(R.id.terrain_anchor_switch);
+ // Initial terrain anchor mode is DISABLED.
+ terrainAnchorSwitch.setChecked(false);
+ terrainAnchorSwitch.setOnCheckedChangeListener(this::onTerrainAnchorModeChanged);
+
+ displayRotationHelper = new DisplayRotationHelper(/*activity=*/ this);
// Set up renderer.
render = new SampleRender(surfaceView, this, getAssets());
installRequested = false;
clearedAnchorsAmount = null;
+
+ // Set up touch listener.
+ gestureDetector =
+ new GestureDetector(
+ this,
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ synchronized (singleTapLock) {
+ queuedSingleTap = e;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+ });
+ surfaceView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(/*context=*/ this);
}
@Override
@@ -218,7 +291,6 @@ protected void onDestroy() {
@Override
protected void onResume() {
super.onResume();
-
if (sharedPreferences.getBoolean(ALLOW_GEOSPATIAL_ACCESS_KEY, /*defValue=*/ false)) {
createSession();
} else {
@@ -287,6 +359,10 @@ private void createSession() {
return;
}
}
+ // Check VPS availability before configure and resume session.
+ if (session != null) {
+ getLastLocation();
+ }
// Note that order matters - see the note in onPause(), the reverse applies here.
try {
@@ -323,6 +399,56 @@ private void createSession() {
}
}
+ private void getLastLocation() {
+ try {
+ fusedLocationClient
+ .getLastLocation()
+ .addOnSuccessListener(
+ new OnSuccessListener() {
+ @Override
+ public void onSuccess(Location location) {
+ double latitude = 0;
+ double longitude = 0;
+ if (location != null) {
+ latitude = location.getLatitude();
+ longitude = location.getLongitude();
+ } else {
+ Log.e(TAG, "Error location is null");
+ }
+ checkVpsAvailability(latitude, longitude);
+ }
+ });
+ } catch (SecurityException e) {
+ Log.e(TAG, "No location permissions granted by User!");
+ }
+ }
+
+ private void checkVpsAvailability(double latitude, double longitude) {
+ ListenableFuture availabilityFuture =
+ checkVpsAvailabilityFuture(latitude, longitude);
+ Futures.addCallback(
+ availabilityFuture,
+ new FutureCallback() {
+ @Override
+ public void onSuccess(VpsAvailability result) {
+ if (result != VpsAvailability.AVAILABLE) {
+ showVpsNotAvailabilityNoticeDialog();
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Error checking VPS availability", t);
+ }
+ },
+ getMainExecutor());
+ }
+
+ private void showVpsNotAvailabilityNoticeDialog() {
+ DialogFragment dialog = VpsAvailabilityNoticeDialogFragment.createDialog();
+ dialog.show(getSupportFragmentManager(), VpsAvailabilityNoticeDialogFragment.class.getName());
+ }
+
@Override
public void onPause() {
super.onPause();
@@ -377,6 +503,7 @@ public void onSurfaceCreated(SampleRender render) {
// Prepare the rendering objects. This involves reading shaders and 3D model files, so may throw
// an IOException.
try {
+ planeRenderer = new PlaneRenderer(render);
backgroundRenderer = new BackgroundRenderer(render);
virtualSceneFramebuffer = new Framebuffer(render, /*width=*/ 1, /*height=*/ 1);
@@ -414,6 +541,21 @@ public void onSurfaceCreated(SampleRender render) {
backgroundRenderer.setUseDepthVisualization(render, false);
backgroundRenderer.setUseOcclusion(render, false);
+
+ // Point cloud
+ pointCloudShader =
+ Shader.createFromAssets(
+ render, "shaders/point_cloud.vert", "shaders/point_cloud.frag", /*defines=*/ null)
+ .setVec4(
+ "u_Color", new float[] {31.0f / 255.0f, 188.0f / 255.0f, 210.0f / 255.0f, 1.0f})
+ .setFloat("u_PointSize", 5.0f);
+ // four entries per vertex: X, Y, Z, confidence
+ pointCloudVertexBuffer =
+ new VertexBuffer(render, /*numberOfEntriesPerVertex=*/ 4, /*entries=*/ null);
+ final VertexBuffer[] pointCloudVertexBuffers = {pointCloudVertexBuffer};
+ pointCloudMesh =
+ new Mesh(
+ render, Mesh.PrimitiveMode.POINTS, /*indexBuffer=*/ null, pointCloudVertexBuffers);
} catch (IOException e) {
Log.e(TAG, "Failed to read a required asset file", e);
messageSnackbarHelper.showError(this, "Failed to read a required asset file: " + e);
@@ -511,7 +653,7 @@ public void onDrawFrame(SampleRender render) {
pendingTerrainAnchors.remove(anchor);
}
}
- } else if (anchors.size() == 0 && clearedAnchorsAmount == null) {
+ } else if (lastStatusText.equals(getResources().getString(R.string.status_localize_hint))) {
message = getResources().getString(R.string.status_localize_complete);
}
break;
@@ -526,6 +668,9 @@ public void onDrawFrame(SampleRender render) {
});
}
+ // Handle user input.
+ handleTap(frame, camera.getTrackingState());
+
// -- Draw background
if (frame.getTimestamp() != 0) {
@@ -547,6 +692,25 @@ public void onDrawFrame(SampleRender render) {
// Get camera matrix and draw.
camera.getViewMatrix(viewMatrix, 0);
+ // Visualize tracked points.
+ // Use try-with-resources to automatically release the point cloud.
+ try (PointCloud pointCloud = frame.acquirePointCloud()) {
+ if (pointCloud.getTimestamp() > lastPointCloudTimestamp) {
+ pointCloudVertexBuffer.set(pointCloud.getPoints());
+ lastPointCloudTimestamp = pointCloud.getTimestamp();
+ }
+ Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
+ pointCloudShader.setMat4("u_ModelViewProjection", modelViewProjectionMatrix);
+ render.draw(pointCloudMesh, pointCloudShader);
+ }
+
+ // Visualize planes.
+ planeRenderer.drawPlanes(
+ render,
+ session.getAllTrackables(Plane.class),
+ camera.getDisplayOrientedPose(),
+ projectionMatrix);
+
// Visualize anchors created by touch.
render.clear(virtualSceneFramebuffer, 0f, 0f, 0f, 0f);
for (Anchor anchor : anchors) {
@@ -598,6 +762,14 @@ private void configureSession() {
/** Change behavior depending on the current {@link State} of the application. */
private void updateGeospatialState(Earth earth) {
+ if (earth.getEarthState() != Earth.EarthState.ENABLED) {
+ state = State.EARTH_STATE_ERROR;
+ return;
+ }
+ if (earth.getTrackingState() != TrackingState.TRACKING) {
+ state = State.PRETRACKING;
+ return;
+ }
if (state == State.PRETRACKING) {
updatePretrackingState(earth);
} else if (state == State.LOCALIZING) {
@@ -618,11 +790,6 @@ private void updatePretrackingState(Earth earth) {
return;
}
- if (earth.getEarthState() != Earth.EarthState.ENABLED) {
- state = State.EARTH_STATE_ERROR;
- return;
- }
-
runOnUiThread(() -> geospatialPoseTextView.setText(R.string.geospatial_pose_not_tracking));
}
@@ -641,11 +808,16 @@ private void updateLocalizingState(Earth earth) {
if (anchors.isEmpty()) {
createAnchorFromSharedPreferences(earth);
}
- runOnUiThread(
- () -> {
- setAnchorButton.setVisibility(View.VISIBLE);
- setTerrainAnchorButton.setVisibility(View.VISIBLE);
- });
+ if (anchors.size() < MAXIMUM_ANCHORS) {
+ runOnUiThread(
+ () -> {
+ setAnchorButton.setVisibility(View.VISIBLE);
+ tapScreenTextView.setVisibility(View.VISIBLE);
+ if (anchors.size() > 0) {
+ clearAnchorsButton.setVisibility(View.VISIBLE);
+ }
+ });
+ }
return;
}
@@ -679,7 +851,7 @@ private void updateLocalizedState(Earth earth) {
runOnUiThread(
() -> {
setAnchorButton.setVisibility(View.INVISIBLE);
- setTerrainAnchorButton.setVisibility(View.INVISIBLE);
+ tapScreenTextView.setVisibility(View.INVISIBLE);
clearAnchorsButton.setVisibility(View.INVISIBLE);
});
return;
@@ -719,36 +891,34 @@ private void handleSetAnchorButton() {
}
GeospatialPose geospatialPose = earth.getCameraGeospatialPose();
- double latitude = geospatialPose.getLatitude();
- double longitude = geospatialPose.getLongitude();
- double altitude = geospatialPose.getAltitude();
- double headingDegrees = geospatialPose.getHeading();
- createAnchor(earth, latitude, longitude, altitude, headingDegrees);
- storeAnchorParameters(latitude, longitude, altitude, headingDegrees);
- clearAnchorsButton.setVisibility(View.VISIBLE);
- if (clearedAnchorsAmount != null) {
- clearedAnchorsAmount = null;
- }
- String message =
- getResources()
- .getQuantityString(R.plurals.status_anchors_set, anchors.size(), anchors.size());
-
- statusTextView.setVisibility(View.VISIBLE);
- statusTextView.setText(message);
+ createAnchorWithGeospatialPose(earth, geospatialPose);
}
- private void handleSetTerrainAnchorButton() {
- Earth earth = session.getEarth();
- if (earth == null || earth.getTrackingState() != TrackingState.TRACKING) {
- return;
- }
-
- GeospatialPose geospatialPose = earth.getCameraGeospatialPose();
+ /** Creates anchor with the provided GeospatialPose, either from camera or HitResult. */
+ private void createAnchorWithGeospatialPose(Earth earth, GeospatialPose geospatialPose) {
double latitude = geospatialPose.getLatitude();
double longitude = geospatialPose.getLongitude();
+ double altitude = geospatialPose.getAltitude();
double headingDegrees = geospatialPose.getHeading();
- createTerrainAnchor(earth, latitude, longitude, headingDegrees);
- clearAnchorsButton.setVisibility(View.VISIBLE);
+ if (isTerrainAnchorMode) {
+ createTerrainAnchor(earth, latitude, longitude, headingDegrees);
+ storeAnchorParameters(latitude, longitude, 0, headingDegrees);
+ } else {
+ createAnchor(earth, latitude, longitude, altitude, headingDegrees);
+ storeAnchorParameters(latitude, longitude, altitude, headingDegrees);
+ String message =
+ getResources()
+ .getQuantityString(R.plurals.status_anchors_set, anchors.size(), anchors.size());
+ runOnUiThread(
+ () -> {
+ statusTextView.setVisibility(View.VISIBLE);
+ statusTextView.setText(message);
+ });
+ }
+ runOnUiThread(
+ () -> {
+ clearAnchorsButton.setVisibility(View.VISIBLE);
+ });
if (clearedAnchorsAmount != null) {
clearedAnchorsAmount = null;
}
@@ -771,7 +941,7 @@ private void handleClearAnchorsButton() {
clearAnchorsFromSharedPreferences();
clearAnchorsButton.setVisibility(View.INVISIBLE);
setAnchorButton.setVisibility(View.VISIBLE);
- setTerrainAnchorButton.setVisibility(View.VISIBLE);
+ tapScreenTextView.setVisibility(View.VISIBLE);
}
/** Create an anchor at a specific geodetic location using a heading. */
@@ -790,8 +960,11 @@ private void createAnchor(
(float) Math.cos(angleRadians / 2));
anchors.add(anchor);
if (anchors.size() >= MAXIMUM_ANCHORS) {
- setAnchorButton.setVisibility(View.INVISIBLE);
- setTerrainAnchorButton.setVisibility(View.INVISIBLE);
+ runOnUiThread(
+ () -> {
+ setAnchorButton.setVisibility(View.INVISIBLE);
+ tapScreenTextView.setVisibility(View.INVISIBLE);
+ });
}
}
@@ -813,8 +986,11 @@ private void createTerrainAnchor(
(float) Math.cos(angleRadians / 2));
anchors.add(anchor);
if (anchors.size() >= MAXIMUM_ANCHORS) {
- setAnchorButton.setVisibility(View.INVISIBLE);
- setTerrainAnchorButton.setVisibility(View.INVISIBLE);
+ runOnUiThread(
+ () -> {
+ setAnchorButton.setVisibility(View.INVISIBLE);
+ tapScreenTextView.setVisibility(View.INVISIBLE);
+ });
}
} catch (ResourceExhaustedException e) {
messageSnackbarHelper.showMessageWithDismiss(
@@ -838,8 +1014,10 @@ private void storeAnchorParameters(
HashSet newAnchorParameterSet = new HashSet<>(anchorParameterSet);
SharedPreferences.Editor editor = sharedPreferences.edit();
+ String terrain = isTerrainAnchorMode ? "Terrain" : "";
newAnchorParameterSet.add(
- String.format("%.6f,%.6f,%.6f,%.6f", latitude, longitude, altitude, headingDegrees));
+ String.format(
+ terrain + "%.6f,%.6f,%.6f,%.6f", latitude, longitude, altitude, headingDegrees));
editor.putStringSet(SHARED_PREFERENCES_SAVED_ANCHORS, newAnchorParameterSet);
editor.commit();
}
@@ -859,6 +1037,10 @@ private void createAnchorFromSharedPreferences(Earth earth) {
}
for (String anchorParameters : anchorParameterSet) {
+ boolean isTerrain = anchorParameters.contains("Terrain");
+ if (isTerrain) {
+ anchorParameters = anchorParameters.replace("Terrain", "");
+ }
String[] parameters = anchorParameters.split(",");
if (parameters.length != 4) {
Log.d(
@@ -869,7 +1051,11 @@ private void createAnchorFromSharedPreferences(Earth earth) {
double longitude = Double.parseDouble(parameters[1]);
double altitude = Double.parseDouble(parameters[2]);
double heading = Double.parseDouble(parameters[3]);
- createAnchor(earth, latitude, longitude, altitude, heading);
+ if (isTerrain) {
+ createTerrainAnchor(earth, latitude, longitude, heading);
+ } else {
+ createAnchor(earth, latitude, longitude, altitude, heading);
+ }
}
runOnUiThread(() -> clearAnchorsButton.setVisibility(View.VISIBLE));
@@ -883,6 +1069,67 @@ public void onDialogPositiveClick(DialogFragment dialog) {
createSession();
}
+ @Override
+ public void onDialogContinueClick(DialogFragment dialog) {
+ dialog.dismiss();
+ }
+
+ private void onTerrainAnchorModeChanged(CompoundButton button, boolean isChecked) {
+ if (session == null) {
+ return;
+ }
+ isTerrainAnchorMode = isChecked;
+ }
+
+ /**
+ * Handles the most recent user tap.
+ *
+ *
We only ever handle one tap at a time, since this app only allows for a single anchor.
+ *
+ * @param frame the current AR frame
+ * @param cameraTrackingState the current camera tracking state
+ */
+ private void handleTap(Frame frame, TrackingState cameraTrackingState) {
+ // Handle taps. Handling only one tap per frame, as taps are usually low frequency
+ // compared to frame rate.
+ synchronized (singleTapLock) {
+ if (queuedSingleTap == null
+ || anchors.size() >= MAXIMUM_ANCHORS
+ || cameraTrackingState != TrackingState.TRACKING) {
+ queuedSingleTap = null;
+ return;
+ }
+ Earth earth = session.getEarth();
+ if (earth == null || earth.getTrackingState() != TrackingState.TRACKING) {
+ queuedSingleTap = null;
+ return;
+ }
+
+ for (HitResult hit : frame.hitTest(queuedSingleTap)) {
+ if (shouldCreateAnchorWithHit(hit)) {
+ Pose hitPose = hit.getHitPose();
+ GeospatialPose geospatialPose = earth.getGeospatialPose(hitPose);
+ createAnchorWithGeospatialPose(earth, geospatialPose);
+ break; // Only handle the first valid hit.
+ }
+ }
+ queuedSingleTap = null;
+ }
+ }
+
+ /** Returns {@code true} if and only if the hit can be used to create an Anchor reliably. */
+ private static boolean shouldCreateAnchorWithHit(HitResult hit) {
+ Trackable trackable = hit.getTrackable();
+ if (trackable instanceof Plane) {
+ // Check if the hit was within the plane's polygon.
+ return ((Plane) trackable).isPoseInPolygon(hit.getHitPose());
+ } else if (trackable instanceof Point) {
+ // Check if the hit was against an oriented point.
+ return ((Point) trackable).getOrientationMode() == OrientationMode.ESTIMATED_SURFACE_NORMAL;
+ }
+ return false;
+ }
+
/** Listener for the results of a resolving terrain anchor operation. */
private final class TerrainAnchorResolveListener {
public void onTaskComplete(Anchor anchor) {
@@ -903,4 +1150,22 @@ public void onDeadlineExceeded() {
deadlineForMessageMillis = 0;
}
}
+
+ // Wrapper for checkVpsAvailability. Do not block on this future on the Main thread; deadlock will
+ // result.
+ private ListenableFuture checkVpsAvailabilityFuture(
+ double latitude, double longitude) {
+ return CallbackToFutureAdapter.getFuture(
+ completer -> {
+ final VpsAvailabilityFuture future =
+ session.checkVpsAvailabilityAsync(
+ latitude, longitude, availability -> completer.set(availability));
+ completer.addCancellationListener(
+ () -> {
+ boolean cancel = future.cancel();
+ },
+ Runnable::run);
+ return "checkVpsAvailabilityFuture";
+ });
+ }
}
diff --git a/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/VpsAvailabilityNoticeDialogFragment.java b/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/VpsAvailabilityNoticeDialogFragment.java
new file mode 100644
index 000000000..90f559989
--- /dev/null
+++ b/samples/geospatial_java/app/src/main/java/com/google/ar/core/examples/java/geospatial/VpsAvailabilityNoticeDialogFragment.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.ar.core.examples.java.geospatial;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.fragment.app.DialogFragment;
+
+/** A DialogFragment for the VPS availability Notice Dialog Box. */
+public class VpsAvailabilityNoticeDialogFragment extends DialogFragment {
+
+ /** Listener for a VPS availability notice response. */
+ public interface NoticeDialogListener {
+
+ /** Invoked when the user accepts sharing experience. */
+ void onDialogContinueClick(DialogFragment dialog);
+ }
+
+ NoticeDialogListener noticeDialogListener;
+
+ static VpsAvailabilityNoticeDialogFragment createDialog() {
+ VpsAvailabilityNoticeDialogFragment dialogFragment = new VpsAvailabilityNoticeDialogFragment();
+ return dialogFragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ // Verify that the host activity implements the callback interface
+ try {
+ noticeDialogListener = (NoticeDialogListener) context;
+ } catch (ClassCastException e) {
+ throw new AssertionError("Must implement NoticeDialogListener", e);
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ noticeDialogListener = null;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AlertDialogCustom);
+ builder
+ .setTitle(R.string.vps_unavailable_title)
+ .setMessage(R.string.vps_unavailable_message)
+ .setCancelable(false)
+ .setPositiveButton(
+ R.string.continue_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // Send the positive button event back to the host activity
+ noticeDialogListener.onDialogContinueClick(
+ VpsAvailabilityNoticeDialogFragment.this);
+ }
+ });
+ Dialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+}
diff --git a/samples/geospatial_java/app/src/main/res/layout/activity_main.xml b/samples/geospatial_java/app/src/main/res/layout/activity_main.xml
index 1a4efdce9..4b1174709 100644
--- a/samples/geospatial_java/app/src/main/res/layout/activity_main.xml
+++ b/samples/geospatial_java/app/src/main/res/layout/activity_main.xml
@@ -45,19 +45,31 @@
android:textColor="#ffffff"
android:background="#bf323232"/>
-
+
+
+ android:textOff="Off"
+ android:textOn="On" />
+
diff --git a/samples/geospatial_java/app/src/main/res/values/strings.xml b/samples/geospatial_java/app/src/main/res/values/strings.xml
index b7a248798..ec6122cb4 100644
--- a/samples/geospatial_java/app/src/main/res/values/strings.xml
+++ b/samples/geospatial_java/app/src/main/res/values/strings.xml
@@ -16,9 +16,10 @@
-->
Geospatial Java
- SET ANCHOR
- SET TERRAIN ANCHOR
+ SET CAMERA ANCHOR
+ TERRAINCLEAR ALL ANCHORS
+ TAP ON SCREEN TO CREATE ANCHORThis device is not supported.Localizing your device to set anchor.There was an error state error, check that you have a valid authentication for the ARCore API.
@@ -49,4 +50,11 @@
Learn morehttps://developers.google.com/ar/data-privacy
+
+
+ VPS not available
+
+ Your current location does not have VPS coverage. Your session will be using your GPS signal only if VPS is not available.
+
+ Continue
diff --git a/samples/hello_ar_c/app/build.gradle b/samples/hello_ar_c/app/build.gradle
index 141bdf46f..db3250395 100644
--- a/samples/hello_ar_c/app/build.gradle
+++ b/samples/hello_ar_c/app/build.gradle
@@ -53,8 +53,8 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
- natives 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
+ natives 'com.google.ar:core:1.34.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
diff --git a/samples/hello_ar_java/app/build.gradle b/samples/hello_ar_java/app/build.gradle
index d624500af..2e341646b 100644
--- a/samples/hello_ar_java/app/build.gradle
+++ b/samples/hello_ar_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/hello_ar_java/app/src/main/assets/shaders/cubemap_filter.frag b/samples/hello_ar_java/app/src/main/assets/shaders/cubemap_filter.frag
index 3ab833926..94b17f368 100644
--- a/samples/hello_ar_java/app/src/main/assets/shaders/cubemap_filter.frag
+++ b/samples/hello_ar_java/app/src/main/assets/shaders/cubemap_filter.frag
@@ -82,7 +82,9 @@ vec4 Filter(const vec3 n) {
tangentToWorld[1] = cross(n, tangentToWorld[0]);
tangentToWorld[2] = n;
- ImportanceSampleCache cache = u_ImportanceSampleCaches[u_RoughnessLevel - 1];
+ // TODO(b/243456272): This clamp should not be necessary, but is here due to a
+ // driver issue with certain devices.
+ ImportanceSampleCache cache = u_ImportanceSampleCaches[max(0, u_RoughnessLevel - 1)];
vec3 radiance = vec3(0.0);
for (int i = 0; i < cache.number_of_entries; ++i) {
ImportanceSampleCacheEntry entry = cache.entries[i];
diff --git a/samples/hello_ar_kotlin/app/build.gradle b/samples/hello_ar_kotlin/app/build.gradle
index c194767c5..5e5c28734 100644
--- a/samples/hello_ar_kotlin/app/build.gradle
+++ b/samples/hello_ar_kotlin/app/build.gradle
@@ -30,7 +30,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/hello_ar_kotlin/app/src/main/assets/shaders/cubemap_filter.frag b/samples/hello_ar_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
index 3ab833926..94b17f368 100644
--- a/samples/hello_ar_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
+++ b/samples/hello_ar_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
@@ -82,7 +82,9 @@ vec4 Filter(const vec3 n) {
tangentToWorld[1] = cross(n, tangentToWorld[0]);
tangentToWorld[2] = n;
- ImportanceSampleCache cache = u_ImportanceSampleCaches[u_RoughnessLevel - 1];
+ // TODO(b/243456272): This clamp should not be necessary, but is here due to a
+ // driver issue with certain devices.
+ ImportanceSampleCache cache = u_ImportanceSampleCaches[max(0, u_RoughnessLevel - 1)];
vec3 radiance = vec3(0.0);
for (int i = 0; i < cache.number_of_entries; ++i) {
ImportanceSampleCacheEntry entry = cache.entries[i];
diff --git a/samples/hello_ar_kotlin/build.gradle b/samples/hello_ar_kotlin/build.gradle
index 03962aeac..151dadbb6 100644
--- a/samples/hello_ar_kotlin/build.gradle
+++ b/samples/hello_ar_kotlin/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.4.20'
+ ext.kotlin_version = '1.5.30'
repositories {
google()
mavenCentral()
diff --git a/samples/ml_kotlin/app/build.gradle b/samples/ml_kotlin/app/build.gradle
index 29a4f867f..837119932 100644
--- a/samples/ml_kotlin/app/build.gradle
+++ b/samples/ml_kotlin/app/build.gradle
@@ -54,7 +54,7 @@ dependencies {
implementation 'com.google.mlkit:object-detection-custom:16.3.1'
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/ml_kotlin/app/src/main/assets/shaders/cubemap_filter.frag b/samples/ml_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
index 3ab833926..94b17f368 100644
--- a/samples/ml_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
+++ b/samples/ml_kotlin/app/src/main/assets/shaders/cubemap_filter.frag
@@ -82,7 +82,9 @@ vec4 Filter(const vec3 n) {
tangentToWorld[1] = cross(n, tangentToWorld[0]);
tangentToWorld[2] = n;
- ImportanceSampleCache cache = u_ImportanceSampleCaches[u_RoughnessLevel - 1];
+ // TODO(b/243456272): This clamp should not be necessary, but is here due to a
+ // driver issue with certain devices.
+ ImportanceSampleCache cache = u_ImportanceSampleCaches[max(0, u_RoughnessLevel - 1)];
vec3 radiance = vec3(0.0);
for (int i = 0; i < cache.number_of_entries; ++i) {
ImportanceSampleCacheEntry entry = cache.entries[i];
diff --git a/samples/ml_kotlin/build.gradle b/samples/ml_kotlin/build.gradle
index 03962aeac..151dadbb6 100644
--- a/samples/ml_kotlin/build.gradle
+++ b/samples/ml_kotlin/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.4.20'
+ ext.kotlin_version = '1.5.30'
repositories {
google()
mavenCentral()
diff --git a/samples/persistent_cloud_anchor_java/app/build.gradle b/samples/persistent_cloud_anchor_java/app/build.gradle
index 428457620..406eea92b 100644
--- a/samples/persistent_cloud_anchor_java/app/build.gradle
+++ b/samples/persistent_cloud_anchor_java/app/build.gradle
@@ -30,7 +30,7 @@ repositories {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/raw_depth_java/app/build.gradle b/samples/raw_depth_java/app/build.gradle
index 780aa6e4a..d25fc551a 100644
--- a/samples/raw_depth_java/app/build.gradle
+++ b/samples/raw_depth_java/app/build.gradle
@@ -27,7 +27,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/recording_playback_java/app/build.gradle b/samples/recording_playback_java/app/build.gradle
index e4ab7fb98..7f7bfc1c8 100644
--- a/samples/recording_playback_java/app/build.gradle
+++ b/samples/recording_playback_java/app/build.gradle
@@ -26,7 +26,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/samples/shared_camera_java/app/build.gradle b/samples/shared_camera_java/app/build.gradle
index e363575e4..817b1de7f 100644
--- a/samples/shared_camera_java/app/build.gradle
+++ b/samples/shared_camera_java/app/build.gradle
@@ -27,7 +27,7 @@ android {
dependencies {
// ARCore (Google Play Services for AR) library.
- implementation 'com.google.ar:core:1.33.0'
+ implementation 'com.google.ar:core:1.34.0'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
diff --git a/third_party/glm/glm.hpp b/third_party/glm/glm.hpp
index 8b6106496..69c1f3a65 100644
--- a/third_party/glm/glm.hpp
+++ b/third_party/glm/glm.hpp
@@ -111,26 +111,26 @@
#include
#include
#include
-#include "fwd.hpp"
+#include "fwd.hpp" // IWYU pragma: export
-#include "vec2.hpp"
-#include "vec3.hpp"
-#include "vec4.hpp"
-#include "mat2x2.hpp"
-#include "mat2x3.hpp"
-#include "mat2x4.hpp"
-#include "mat3x2.hpp"
-#include "mat3x3.hpp"
-#include "mat3x4.hpp"
-#include "mat4x2.hpp"
-#include "mat4x3.hpp"
-#include "mat4x4.hpp"
+#include "vec2.hpp" // IWYU pragma: export
+#include "vec3.hpp" // IWYU pragma: export
+#include "vec4.hpp" // IWYU pragma: export
+#include "mat2x2.hpp" // IWYU pragma: export
+#include "mat2x3.hpp" // IWYU pragma: export
+#include "mat2x4.hpp" // IWYU pragma: export
+#include "mat3x2.hpp" // IWYU pragma: export
+#include "mat3x3.hpp" // IWYU pragma: export
+#include "mat3x4.hpp" // IWYU pragma: export
+#include "mat4x2.hpp" // IWYU pragma: export
+#include "mat4x3.hpp" // IWYU pragma: export
+#include "mat4x4.hpp" // IWYU pragma: export
-#include "trigonometric.hpp"
-#include "exponential.hpp"
-#include "common.hpp"
-#include "packing.hpp"
-#include "geometric.hpp"
-#include "matrix.hpp"
-#include "vector_relational.hpp"
-#include "integer.hpp"
+#include "trigonometric.hpp" // IWYU pragma: export
+#include "exponential.hpp" // IWYU pragma: export
+#include "common.hpp" // IWYU pragma: export
+#include "packing.hpp" // IWYU pragma: export
+#include "geometric.hpp" // IWYU pragma: export
+#include "matrix.hpp" // IWYU pragma: export
+#include "vector_relational.hpp" // IWYU pragma: export
+#include "integer.hpp" // IWYU pragma: export