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"/> -