diff --git a/README.md b/README.md index 39c440d22..601272371 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,11 @@ Copyright (c) 2017 Google Inc. All rights reserved. [https://developers.google.com/ar/develop/java/getting-started](https://developers.google.com/ar/develop/java/getting-started) Please note, we do not accept pull requests. + +## Additional Notes + +You must disclose the use of ARCore, and how it collects and processes data. +This can be done by displaying a prominent link to the site +"How Google uses data when you use our partners' sites or apps", +(located at www.google.com/policies/privacy/partners/, or any other URL Google +may provide from time to time). diff --git a/libraries/include/arcore_c_api.h b/libraries/include/arcore_c_api.h index bdf5a8f09..fe3152e81 100644 --- a/libraries/include/arcore_c_api.h +++ b/libraries/include/arcore_c_api.h @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef ARCORE_C_API_H_ -#define ARCORE_C_API_H_ +#ifndef THIRD_PARTY_ARCORE_AR_CORE_C_API_ARCORE_C_API_H_ +#define THIRD_PARTY_ARCORE_AR_CORE_C_API_ARCORE_C_API_H_ #include #include @@ -86,20 +86,53 @@ /// beyond the scope of a single rendering frame, either an anchor should be /// created or a position relative to a nearby existing anchor should be used. -/// @defgroup common Common Definitions -/// Shared types and constants. +/// @defgroup anchor Anchor +/// Describes a fixed location and orientation in the real world. +/// @defgroup arcoreapk ArCoreApk +/// Management of the ARCore service APK /// @defgroup session Session /// Session management. +/// @defgroup camera Camera +/// Provides information about the camera that is used to capture images. + /// @defgroup config Configuration /// Session configuration. /// @defgroup frame Frame /// Per-frame state. +/// @defgroup hit HitResult +/// Defines an intersection between a ray and estimated real-world geometry. + +/// @defgroup image ImageMetadata +/// Provides access to metadata from the camera image capture result. + +/// @defgroup light LightEstimate +/// Holds information about the estimated lighting of the real scene. + +/// @defgroup plane Plane +/// Describes the current best knowledge of a real-world planar surface. + +/// @defgroup point Point +/// Represents a point in space that ARCore is tracking. + +/// @defgroup pointcloud PointCloud +/// Contains a set of observed 3D points and confidence values. + +/// @defgroup pose Pose +/// Represents an immutable rigid transformation from one coordinate +/// space to another. + +/// @defgroup session Session +/// Session management. + /// @defgroup trackable Trackable -/// Planes and points, as well as Anchors. +/// Something that can be tracked and that Anchors can be attached to. + +/// @defgroup common Common Definitions +/// Shared types and constants /// @defgroup cpp_helpers C++ helper functions @@ -125,7 +158,7 @@ typedef struct ArSession_ ArSession; /// @} -/// @addtogroup common +/// @addtogroup pose /// @{ /// A structured rigid transformation (@ref ownership "value type"). @@ -136,9 +169,9 @@ typedef struct ArPose_ ArPose; /// @} -// Frame and frame objects. +// Camera. -/// @addtogroup frame +/// @addtogroup camera /// @{ /// The virtual and physical camera @@ -148,6 +181,13 @@ typedef struct ArPose_ ArPose; /// Release with ArCamera_release() typedef struct ArCamera_ ArCamera; +/// @} + +// Frame and frame objects. + +/// @addtogroup frame +/// @{ + /// The world state resulting from an update (@ref ownership "value type"). /// /// Allocate with ArFrame_create()
@@ -155,6 +195,13 @@ typedef struct ArCamera_ ArCamera; /// Release with ArFrame_destroy() typedef struct ArFrame_ ArFrame; +/// @} + +// LightEstimate. + +/// @addtogroup light +/// @{ + /// An estimate of the real-world lighting (@ref ownership "value type"). /// /// Allocate with ArLightEstimate_create()
@@ -162,6 +209,13 @@ typedef struct ArFrame_ ArFrame; /// Release with ArLightEstimate_destroy() typedef struct ArLightEstimate_ ArLightEstimate; +/// @} + +// PointCloud. + +/// @addtogroup pointcloud +/// @{ + /// A cloud of tracked 3D visual feature points /// (@ref ownership "reference type, large data"). /// @@ -169,6 +223,13 @@ typedef struct ArLightEstimate_ ArLightEstimate; /// Release with ArPointCloud_release() typedef struct ArPointCloud_ ArPointCloud; +/// @} + +// ImageMetadata. + +/// @addtogroup image +/// @{ + /// Camera capture metadata (@ref ownership "reference type, large data"). /// /// Acquire with ArFrame_acquireImageMetadata()
@@ -191,20 +252,39 @@ typedef struct ArTrackable_ ArTrackable; /// Release with ArTrackableList_destroy() typedef struct ArTrackableList_ ArTrackableList; +/// @} + +// Plane + +/// @addtogroup plane +/// @{ + /// A detected planar surface (@ref ownership "reference type, long-lived"). /// /// Trackable type: #AR_TRACKABLE_PLANE
/// Release with: ArTrackable_release() typedef struct ArPlane_ ArPlane; +/// @} + +// Point + +/// @addtogroup point +/// @{ + /// An arbitrary point in space (@ref ownership "reference type, long-lived"). /// /// Trackable type: #AR_TRACKABLE_POINT
/// Release with: ArTrackable_release() typedef struct ArPoint_ ArPoint; +/// @} + // Anchors. +/// @addtogroup anchor +/// @{ + /// A position in space attached to a trackable /// (@ref ownership "reference type, long-lived"). /// @@ -223,7 +303,7 @@ typedef struct ArAnchorList_ ArAnchorList; // Hit result functionality. -/// @addtogroup frame +/// @addtogroup hit /// @{ /// A single trackable hit (@ref ownership "value type"). @@ -262,23 +342,23 @@ typedef struct ACameraMetadata ACameraMetadata; #ifdef __cplusplus /// Upcasts to ArTrackable -inline ArTrackable* ArAsTrackable(ArPlane* plane) { - return reinterpret_cast(plane); +inline ArTrackable *ArAsTrackable(ArPlane *plane) { + return reinterpret_cast(plane); } /// Upcasts to ArTrackable -inline ArTrackable* ArAsTrackable(ArPoint* point) { - return reinterpret_cast(point); +inline ArTrackable *ArAsTrackable(ArPoint *point) { + return reinterpret_cast(point); } /// Downcasts to ArPlane. -inline ArPlane* ArAsPlane(ArTrackable* trackable) { - return reinterpret_cast(trackable); +inline ArPlane *ArAsPlane(ArTrackable *trackable) { + return reinterpret_cast(trackable); } /// Downcasts to ArPoint. -inline ArPoint* ArAsPoint(ArTrackable* trackable) { - return reinterpret_cast(trackable); +inline ArPoint *ArAsPoint(ArTrackable *trackable) { + return reinterpret_cast(trackable); } #endif /// @} @@ -308,8 +388,7 @@ AR_DEFINE_ENUM(ArTrackableType){ AR_TRACKABLE_POINT = 0x41520102, /// An invalid Trackable type. - AR_TRACKABLE_NOT_VALID = 0, -}; + AR_TRACKABLE_NOT_VALID = 0}; /// @ingroup common /// Return code indicating success or failure of a method. @@ -368,16 +447,16 @@ AR_DEFINE_ENUM(ArStatus){ /// because the camera hasn't fully started. AR_ERROR_NOT_YET_AVAILABLE = -12, + /// The android camera has been reallocated to a higher priority app or is + /// otherwise unavailable. + AR_ERROR_CAMERA_NOT_AVAILABLE = -13, + /// The ARCore APK is not installed on this device. AR_UNAVAILABLE_ARCORE_NOT_INSTALLED = -100, /// The device is not currently compatible with ARCore. AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE = -101, - /// The version of Android is less than the minimum version ARCore supports - /// (currently N, API level 24). - AR_UNAVAILABLE_ANDROID_VERSION_NOT_SUPPORTED = -102, - /// The ARCore APK currently installed on device is too old and needs to be /// updated. AR_UNAVAILABLE_APK_TOO_OLD = -103, @@ -385,7 +464,10 @@ AR_DEFINE_ENUM(ArStatus){ /// The ARCore APK currently installed no longer supports the ARCore SDK /// that the application was built with. AR_UNAVAILABLE_SDK_TOO_OLD = -104, -}; + + /// The user declined installation of the ARCore APK during this run of the + /// application and the current request was not marked as user-initiated. + AR_UNAVAILABLE_USER_DECLINED_INSTALLATION = -105}; /// @ingroup common /// Describes the tracking state of a @c Trackable, an ::ArAnchor or the @@ -403,8 +485,62 @@ AR_DEFINE_ENUM(ArTrackingState){ /// ARCore has stopped tracking this Trackable and will never resume /// tracking it. - AR_TRACKING_STATE_STOPPED = 2, -}; + AR_TRACKING_STATE_STOPPED = 2}; + +/// @ingroup arcoreapk +/// Describes the current state of ARCore availability on the device. +AR_DEFINE_ENUM(ArAvailability){ + /// An internal error occurred while determining ARCore availability. + AR_AVAILABILITY_UNKNOWN_ERROR = 0, + /// ARCore is not installed, and a query has been issued to check if ARCore + /// is is supported. + AR_AVAILABILITY_UNKNOWN_CHECKING = 1, + /// ARCore is not installed, and the query to check if ARCore is supported + /// timed out. This may be due to the device being offline. + AR_AVAILABILITY_UNKNOWN_TIMED_OUT = 2, + /// ARCore is not supported on this device. + AR_AVAILABILITY_UNSUPPORTED_DEVICE_NOT_CAPABLE = 100, + /// The device and Android version are supported, but the ARCore APK is not + /// installed. + AR_AVAILABILITY_SUPPORTED_NOT_INSTALLED = 201, + /// The device and Android version are supported, and a version of the + /// ARCore APK is installed, but that ARCore APK version is too old. + AR_AVAILABILITY_SUPPORTED_APK_TOO_OLD = 202, + /// ARCore is supported, installed, and available to use. + AR_AVAILABILITY_SUPPORTED_INSTALLED = 203}; + +/// @ingroup arcoreapk +/// Indicates the outcome of a call to ArCoreApk_requestInstall(). +AR_DEFINE_ENUM(ArInstallStatus){ + /// The requested resource is already installed. + AR_INSTALL_STATUS_INSTALLED = 0, + /// Installation of the resource was requested. The current activity will be + /// paused. + AR_INSTALL_STATUS_INSTALL_REQUESTED = 1}; + +/// @ingroup arcoreapk +/// Controls the behavior of the installation UI. +AR_DEFINE_ENUM(ArInstallBehavior){ + /// Hide the Cancel button during initial prompt and prevent user from + /// exiting via tap-outside. + /// + /// Note: The BACK button or tapping outside of any marketplace-provided + /// install dialog will still decline the installation. + AR_INSTALL_BEHAVIOR_REQUIRED = 0, + /// Include Cancel button in initial prompt and allow easily backing out + /// after installation has been initiated. + AR_INSTALL_BEHAVIOR_OPTIONAL = 1}; + +/// @ingroup arcoreapk +/// Controls the message displayed by the installation UI. +AR_DEFINE_ENUM(ArInstallUserMessageType){ + /// Display a localized message like "This application requires ARCore...". + AR_INSTALL_USER_MESSAGE_TYPE_APPLICATION = 0, + /// Display a localized message like "This feature requires ARCore...". + AR_INSTALL_USER_MESSAGE_TYPE_FEATURE = 1, + /// Application has explained why ARCore is required prior to calling + /// ArCoreApk_requestInstall(), skip user education dialog. + AR_INSTALL_USER_MESSAGE_TYPE_USER_ALREADY_INFORMED = 2}; /// @ingroup config /// Select the behavior of the lighting estimation subsystem. @@ -413,8 +549,7 @@ AR_DEFINE_ENUM(ArLightEstimationMode){ AR_LIGHT_ESTIMATION_MODE_DISABLED = 0, /// Lighting estimation is enabled, generating a single-value intensity /// estimate. - AR_LIGHT_ESTIMATION_MODE_AMBIENT_INTENSITY = 1, -}; + AR_LIGHT_ESTIMATION_MODE_AMBIENT_INTENSITY = 1}; /// @ingroup config /// Select the behavior of the plane detection subsystem. @@ -422,8 +557,7 @@ AR_DEFINE_ENUM(ArPlaneFindingMode){ /// Plane detection is disabled. AR_PLANE_FINDING_MODE_DISABLED = 0, /// Detection of only horizontal planes is enabled. - AR_PLANE_FINDING_MODE_HORIZONTAL = 1, -}; + AR_PLANE_FINDING_MODE_HORIZONTAL = 1}; /// @ingroup config /// Selects the behavior of ArSession_update(). @@ -434,27 +568,34 @@ AR_DEFINE_ENUM(ArUpdateMode){ /// @c update() will return immediately without blocking. If no new camera /// image is available, then @c update() will return the most recent /// ::ArFrame object. - AR_UPDATE_MODE_LATEST_CAMERA_IMAGE = 1, -}; + AR_UPDATE_MODE_LATEST_CAMERA_IMAGE = 1}; -/// @ingroup trackable +/// @ingroup plane /// Simple summary of the normal vector of a plane, for filtering purposes. AR_DEFINE_ENUM(ArPlaneType){ /// A horizontal plane facing upward (for example a floor or tabletop). AR_PLANE_HORIZONTAL_UPWARD_FACING = 0, /// A horizontal plane facing downward (for example a ceiling). - AR_PLANE_HORIZONTAL_DOWNWARD_FACING = 1, -}; + AR_PLANE_HORIZONTAL_DOWNWARD_FACING = 1}; -/// @ingroup frame +/// @ingroup light /// Tracks the validity of a light estimate. AR_DEFINE_ENUM(ArLightEstimateState){ /// The light estimate is not valid this frame and should not be used for /// rendering. AR_LIGHT_ESTIMATE_STATE_NOT_VALID = 0, /// The light estimate is valid this frame. - AR_LIGHT_ESTIMATE_STATE_VALID = 1, -}; + AR_LIGHT_ESTIMATE_STATE_VALID = 1}; + +/// @ingroup point +/// Indicates the orientation mode of the ::ArPoint. +AR_DEFINE_ENUM(ArPointOrientationMode){ + /// The orientation of the ::ArPoint is initialized to identity but may + /// adjust slightly over time. + AR_POINT_ORIENTATION_INITIALIZED_TO_IDENTITY = 0, + /// The orientation of the ::ArPoint will follow the behavior described in + /// ArHitResult_getHitPose(). + AR_POINT_ORIENTATION_ESTIMATED_SURFACE_NORMAL = 1}; #undef AR_DEFINE_ENUM @@ -465,6 +606,123 @@ extern "C" { // Note: destroy methods do not take ArSession* to allow late destruction in // finalizers of garbage-collected languages such as Java. +/// @addtogroup arcoreapk +/// @{ + +/// Determines if ARCore is supported on this device. This may initiate a query +/// with a remote service to determine if the device is compatible, in which +/// case it will return immediately with @c out_availability set to +/// #AR_AVAILABILITY_UNKNOWN_CHECKING. +/// +/// Note: A result #AR_AVAILABILITY_SUPPORTED_INSTALLED only indicates presence +/// of a suitably versioned ARCore APK. Session creation may still fail if the +/// ARCore APK has been sideloaded onto an incompatible device. +/// +/// May be called prior to ArSession_create(). +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_context A @c jobject referencing the application's +/// Android @c Context. +/// @param[out] out_availability A pointer to an ArAvailability to receive +/// the result. +void ArCoreApk_checkAvailability(void *env, + void *application_context, + ArAvailability *out_availability); + +/// Initiates installation of ARCore if needed. When your apllication launches +/// or enters an AR mode, it should call this method with @c +/// user_requested_install = 1. +/// +/// If ARCore is installed and compatible, this function will set @c +/// out_install_status to #AR_INSTALL_STATUS_INSTALLED. +/// +/// If ARCore is not currently installed or the installed version not +/// compatible, the function will set @c out_install_status to +/// #AR_INSTALL_STATUS_INSTALL_REQUESTED and return immediately. Your current +/// activity will then pause while the user is informed about the requierment of +/// ARCore and offered the opportunity to install it. +/// +/// When your activity resumes, you should call this method again, this time +/// with @c user_requested_install = 0. This will either set +/// @c out_install_status to #AR_INSTALL_STATUS_INSTALLED or return an error +/// code indicating the reason that installation could not be completed. +/// +/// ARCore-optional applications must ensure that ArCoreApk_checkAvailability() +/// returns one of the AR_AVAILABILITY_SUPPORTED_... values before +/// calling this method. +/// +/// See +/// our sample code for an example of how an ARCore-required application +/// should use this function. +/// +/// May be called prior to ArSession_create(). +/// +/// For more control over the message displayed and ease of exiting the process, +/// see ArCoreApk_requestInstallCustom(). +/// +/// Caution: The value of *out_install_status should only be +/// considered when #AR_SUCCESS is returned. Otherwise this value must be +/// ignored. +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_activity A @c jobject referencing the application's +/// current Android @c Activity. +/// @param[in] user_requested_install if set, override the previous installation +/// failure message and always show the installation interface. +/// @param[out] out_install_status A pointer to an ArInstallStatus to receive +/// the resulting install status, if successful. Note: this value is only +/// valid with the return value is #AR_SUCCESS. +/// @return #AR_SUCCESS, or any of: +/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting +/// installation +/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported +/// on this device. +/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined +/// installation. +ArStatus ArCoreApk_requestInstall(void *env, + void *application_activity, + bool user_requested_install, + ArInstallStatus *out_install_status); + +/// Initiates installation of ARCore if required, with configurable behavior. +/// +/// This is a more flexible version of ArCoreApk_requestInstall() allowing the +/// application control over the initial informational dialog and ease of +/// exiting or cancelling the installation. +/// +/// See ArCoreApk_requestInstall() for details of use and behavior. +/// +/// May be called prior to ArSession_create(). +/// +/// @param[in] env The application's @c JNIEnv object +/// @param[in] application_activity A @c jobject referencing the application's +/// current Android @c Activity. +/// @param[in] user_requested_install if set, override the previous installation +/// failure message and always show the installation interface. +/// @param[in] install_behavior controls the presence of the cancel button at +/// the user education screen and if tapping outside the education screen or +/// install-in-progress screen causes them to dismiss. +/// @param[in] message_type controls the text of the of message displayed +/// before showing the install prompt, or disables display of this message. +/// @param[out] out_install_status A pointer to an ArInstallStatus to receive +/// the resulting install status, if successful. Note: this value is only +/// valid with the return value is #AR_SUCCESS. +/// @return #AR_SUCCESS, or any of: +/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting +/// installation +/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported +/// on this device. +/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined +/// installation. +ArStatus ArCoreApk_requestInstallCustom(void *env, + void *application_activity, + int32_t user_requested_install, + ArInstallBehavior install_behavior, + ArInstallUserMessageType message_type, + ArInstallStatus *out_install_status); + +/// @} /// @addtogroup session /// @{ @@ -481,11 +739,12 @@ extern "C" { /// @return #AR_SUCCESS or any of: /// - #AR_UNAVAILABLE_ARCORE_NOT_INSTALLED /// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE -/// - #AR_UNAVAILABLE_ANDROID_VERSION_NOT_SUPPORTED /// - #AR_UNAVAILABLE_APK_TOO_OLD /// - #AR_UNAVAILABLE_SDK_TOO_OLD -ArStatus ArSession_create(void* env, void* application_context, - ArSession** out_session_pointer); +/// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED +ArStatus ArSession_create(void *env, + void *application_context, + ArSession **out_session_pointer); /// @} @@ -498,42 +757,47 @@ ArStatus ArSession_create(void* env, void* application_context, /// configuration. Plane detection and lighting estimation are enabled, and /// blocking update is selected. This configuration is guaranteed to be /// supported on all devices that support ARCore. -void ArConfig_create(const ArSession* session, ArConfig** out_config); +void ArConfig_create(const ArSession *session, ArConfig **out_config); /// Releases memory used by the provided configuration object. -void ArConfig_destroy(ArConfig* config); +void ArConfig_destroy(ArConfig *config); /// Stores the currently configured lighting estimation mode into /// @c *light_estimation_mode. void ArConfig_getLightEstimationMode( - const ArSession* session, const ArConfig* config, - ArLightEstimationMode* light_estimation_mode); + const ArSession *session, + const ArConfig *config, + ArLightEstimationMode *light_estimation_mode); /// Sets the lighting estimation mode that should be used. See /// ::ArLightEstimationMode for available options. void ArConfig_setLightEstimationMode( - const ArSession* session, ArConfig* config, + const ArSession *session, + ArConfig *config, ArLightEstimationMode light_estimation_mode); /// Stores the currently configured plane finding mode into /// @c *plane_finding_mode. -void ArConfig_getPlaneFindingMode(const ArSession* session, - const ArConfig* config, - ArPlaneFindingMode* plane_finding_mode); +void ArConfig_getPlaneFindingMode(const ArSession *session, + const ArConfig *config, + ArPlaneFindingMode *plane_finding_mode); /// Sets the plane finding mode that should be used. See /// ::ArPlaneFindingMode for available options. -void ArConfig_setPlaneFindingMode(const ArSession* session, ArConfig* config, +void ArConfig_setPlaneFindingMode(const ArSession *session, + ArConfig *config, ArPlaneFindingMode plane_finding_mode); /// Stores the currently configured behavior of @ref ArSession_update() into /// @c *update_mode. -void ArConfig_getUpdateMode(const ArSession* session, const ArConfig* config, - ArUpdateMode* update_mode); +void ArConfig_getUpdateMode(const ArSession *session, + const ArConfig *config, + ArUpdateMode *update_mode); /// Sets the behavior of @ref ArSession_update(). See /// ::ArUpdateMode for available options. -void ArConfig_setUpdateMode(const ArSession* session, ArConfig* config, +void ArConfig_setUpdateMode(const ArSession *session, + ArConfig *config, ArUpdateMode update_mode); /// @} @@ -544,7 +808,7 @@ void ArConfig_setUpdateMode(const ArSession* session, ArConfig* config, /// @{ /// Releases resources used by an ARCore session. -void ArSession_destroy(ArSession* session); +void ArSession_destroy(ArSession *session); /// Checks if the provided configuration is usable on the this device. If this /// method returns #AR_ERROR_UNSUPPORTED_CONFIGURATION, calls to @@ -554,16 +818,16 @@ void ArSession_destroy(ArSession* session); /// @param[in] config The configuration to test /// @return #AR_SUCCESS indicating the configuration is supported, or /// #AR_ERROR_UNSUPPORTED_CONFIGURATION otherwise. -ArStatus ArSession_checkSupported(const ArSession* session, - const ArConfig* config); +ArStatus ArSession_checkSupported(const ArSession *session, + const ArConfig *config); -/// Configures the session with the given config. The session must be paused. +/// Configures the session with the given config. /// /// @return #AR_SUCCESS or any of: /// - #AR_ERROR_FATAL /// - #AR_ERROR_UNSUPPORTED_CONFIGURATION /// - #AR_ERROR_SESSION_NOT_PAUSED -ArStatus ArSession_configure(ArSession* session, const ArConfig* config); +ArStatus ArSession_configure(ArSession *session, const ArConfig *config); /// Starts or resumes the ARCore Session. /// @@ -574,7 +838,8 @@ ArStatus ArSession_configure(ArSession* session, const ArConfig* config); /// @returns #AR_SUCCESS or any of: /// - #AR_ERROR_FATAL /// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED -ArStatus ArSession_resume(ArSession* session); +/// - #AR_ERROR_CAMERA_NOT_AVAILABLE +ArStatus ArSession_resume(ArSession *session); /// Pause the current session. This method will stop the camera feed and release /// resources. The session can be restarted again by calling ArSession_resume(). @@ -585,14 +850,14 @@ ArStatus ArSession_resume(ArSession* session); /// /// @returns #AR_SUCCESS or any of: /// - #AR_ERROR_FATAL -ArStatus ArSession_pause(ArSession* session); +ArStatus ArSession_pause(ArSession *session); /// Sets the OpenGL texture name (id) that will allow GPU access to the camera /// image. The provided ID should have been created with @c glGenTextures(). The /// resulting texture must be bound to the @c GL_TEXTURE_EXTERNAL_OES target for /// use. Shaders accessing this texture must use a @c samplerExternalOES /// sampler. See sample code for an example. -void ArSession_setCameraTextureName(ArSession* session, uint32_t texture_id); +void ArSession_setCameraTextureName(ArSession *session, uint32_t texture_id); /// Sets the aspect ratio, coordinate scaling, and display rotation. This data /// is used by UV conversion, projection matrix generation, and hit test logic. @@ -606,8 +871,10 @@ void ArSession_setCameraTextureName(ArSession* session, uint32_t texture_id); /// @c ROTATION_270 /// @param[in] width Width of the view, in pixels /// @param[in] height Height of the view, in pixels -void ArSession_setDisplayGeometry(ArSession* session, int rotation, int width, - int height); +void ArSession_setDisplayGeometry(ArSession *session, + int32_t rotation, + int32_t width, + int32_t height); /// Updates the state of the ARCore system. This includes: receiving a new /// camera frame, updating the location of the device, updating the location of @@ -635,7 +902,8 @@ void ArSession_setDisplayGeometry(ArSession* session, int rotation, int width, /// - #AR_ERROR_SESSION_PAUSED /// - #AR_ERROR_TEXTURE_NOT_SET /// - #AR_ERROR_MISSING_GL_CONTEXT -ArStatus ArSession_update(ArSession* session, ArFrame* out_frame); +/// - #AR_ERROR_CAMERA_NOT_AVAILABLE - camera was removed during runtime. +ArStatus ArSession_update(ArSession *session, ArFrame *out_frame); /// Defines a tracked location in the physical world. /// @@ -643,8 +911,9 @@ ArStatus ArSession_update(ArSession* session, ArFrame* out_frame); /// - #AR_ERROR_NOT_TRACKING /// - #AR_ERROR_SESSION_PAUSED /// - #AR_ERROR_RESOURCE_EXHAUSTED -ArStatus ArSession_acquireNewAnchor(ArSession* session, const ArPose* pose, - ArAnchor** out_anchor); +ArStatus ArSession_acquireNewAnchor(ArSession *session, + const ArPose *pose, + ArAnchor **out_anchor); /// Returns all known anchors, including those not currently tracked. Anchors /// forgotten by ARCore due to a call to ArAnchor_detach() or entering the @@ -654,8 +923,8 @@ ArStatus ArSession_acquireNewAnchor(ArSession* session, const ArPose* pose, /// @param[inout] out_anchor_list The list to fill. This list must have already /// been allocated with ArAnchorList_create(). If previously used, the list /// will first be cleared. -void ArSession_getAllAnchors(const ArSession* session, - ArAnchorList* out_anchor_list); +void ArSession_getAllAnchors(const ArSession *session, + ArAnchorList *out_anchor_list); /// Returns the list of all known @ref trackable "trackables". This includes /// ::ArPlane objects if plane detection is enabled, as well as ::ArPoint @@ -668,15 +937,15 @@ void ArSession_getAllAnchors(const ArSession* session, /// @param[inout] out_trackable_list The list to fill. This list must have /// already been allocated with ArTrackableList_create(). If previously /// used, the list will first be cleared. -void ArSession_getAllTrackables(const ArSession* session, +void ArSession_getAllTrackables(const ArSession *session, ArTrackableType filter_type, - ArTrackableList* out_trackable_list); + ArTrackableList *out_trackable_list); /// @} // === ArPose methods === -/// @addtogroup common +/// @addtogroup pose /// @{ /// Allocates and initializes a new pose object. @c pose_raw points to an array @@ -689,19 +958,21 @@ void ArSession_getAllTrackables(const ArSession* session, /// The order of the values is: qx, qy, qz, qw, tx, ty, tz. /// /// If @c pose_raw is null, initializes with the identity pose. -void ArPose_create(const ArSession* session, const float* pose_raw, - ArPose** out_pose); +void ArPose_create(const ArSession *session, + const float *pose_raw, + ArPose **out_pose); /// Releases memory used by a pose object. -void ArPose_destroy(ArPose* pose); +void ArPose_destroy(ArPose *pose); /// Extracts the quaternion rotation and translation from a pose object. /// @param[in] session The ARCore session /// @param[in] pose The pose to extract /// @param[out] out_pose_raw Pointer to an array of 7 floats, to be filled with /// the quaternion rotation and translation as described in ArPose_create(). -void ArPose_getPoseRaw(const ArSession* session, const ArPose* pose, - float* out_pose_raw); +void ArPose_getPoseRaw(const ArSession *session, + const ArPose *pose, + float *out_pose_raw); /// Converts a pose into a 4x4 transformation matrix. /// @param[in] session The ARCore session @@ -709,14 +980,15 @@ void ArPose_getPoseRaw(const ArSession* session, const ArPose* pose, /// @param[out] out_matrix_col_major_4x4 Pointer to an array of 16 floats, to be /// filled with a column-major homogenous transformation matrix, as used by /// OpenGL. -void ArPose_getMatrix(const ArSession* session, const ArPose* pose, - float* out_matrix_col_major_4x4); +void ArPose_getMatrix(const ArSession *session, + const ArPose *pose, + float *out_matrix_col_major_4x4); /// @} // === ArCamera methods === -/// @addtogroup frame +/// @addtogroup camera /// @{ /// Sets @c out_pose to the pose of the user's device in the world coordinate @@ -732,8 +1004,9 @@ void ArPose_getMatrix(const ArSession* session, const ArPose* pose, /// @param[in] camera The session's camera (retrieved from any frame). /// @param[inout] out_pose An already-allocated ArPose object into which the /// pose will be stored. -void ArCamera_getPose(const ArSession* session, const ArCamera* camera, - ArPose* out_pose); +void ArCamera_getPose(const ArSession *session, + const ArCamera *camera, + ArPose *out_pose); /// Sets @c out_pose to the pose of the user's device in the world coordinate /// space at the time of capture of the current camera texture. The position of @@ -751,8 +1024,9 @@ void ArCamera_getPose(const ArSession* session, const ArCamera* camera, /// @param[in] camera The session's camera (retrieved from any frame). /// @param[inout] out_pose An already-allocated ArPose object into which the /// pose will be stored. -void ArCamera_getDisplayOrientedPose(const ArSession* session, - const ArCamera* camera, ArPose* out_pose); +void ArCamera_getDisplayOrientedPose(const ArSession *session, + const ArCamera *camera, + ArPose *out_pose); /// Returns the view matrix for the camera for this frame. This matrix performs /// the inverse transfrom as the pose provided by @@ -763,14 +1037,16 @@ void ArCamera_getDisplayOrientedPose(const ArSession* session, /// @param[inout] out_col_major_4x4 Pointer to an array of 16 floats, to be /// filled with a column-major homogenous transformation matrix, as used by /// OpenGL. -void ArCamera_getViewMatrix(const ArSession* session, const ArCamera* camera, - float* out_col_major_4x4); +void ArCamera_getViewMatrix(const ArSession *session, + const ArCamera *camera, + float *out_col_major_4x4); /// Gets the current state of the pose of this camera. If this state is anything /// other than #AR_TRACKING_STATE_TRACKING the Camera's pose should not be /// considered useful. -void ArCamera_getTrackingState(const ArSession* session, const ArCamera* camera, - ArTrackingState* out_tracking_state); +void ArCamera_getTrackingState(const ArSession *session, + const ArCamera *camera, + ArTrackingState *out_tracking_state); /// Computes a projection matrix for rendering virtual content on top of the /// camera image. Note that the projection matrix reflects the current display @@ -783,40 +1059,48 @@ void ArCamera_getTrackingState(const ArSession* session, const ArCamera* camera, /// @param[inout] dest_col_major_4x4 Pointer to an array of 16 floats, to /// be filled with a column-major homogenous transformation matrix, as used /// by OpenGL. -void ArCamera_getProjectionMatrix(const ArSession* session, - const ArCamera* camera, float near, float far, - float* dest_col_major_4x4); +void ArCamera_getProjectionMatrix(const ArSession *session, + const ArCamera *camera, + float near, + float far, + float *dest_col_major_4x4); /// Releases a reference to the camera. This must match a call to /// ArFrame_acquireCamera(). /// /// This method may safely be called with @c nullptr - it will do nothing. -void ArCamera_release(ArCamera* camera); +void ArCamera_release(ArCamera *camera); + +/// @} // === ArFrame methods === +/// @addtogroup frame +/// @{ + /// Allocates a new ArFrame object, storing the pointer into @c *out_frame. /// /// Note: the same ArFrame can be used repeatedly when calling ArSession_update. -void ArFrame_create(const ArSession* session, ArFrame** out_frame); +void ArFrame_create(const ArSession *session, ArFrame **out_frame); /// Releases an ArFrame and any references it holds. -void ArFrame_destroy(ArFrame* frame); +void ArFrame_destroy(ArFrame *frame); /// Checks if the display rotation or viewport geometry changed since the /// previous call to ArSession_update(). The application should re-query /// ArCamera_getProjectionMatrix() and ArFrame_transformDisplayUvCoords() /// whenever this emits non-zero. -void ArFrame_getDisplayGeometryChanged(const ArSession* session, - const ArFrame* frame, - int32_t* out_geometry_changed); +void ArFrame_getDisplayGeometryChanged(const ArSession *session, + const ArFrame *frame, + int32_t *out_geometry_changed); /// Returns the timestamp in nanoseconds when this image was captured. This can /// be used to detect dropped frames or measure the camera frame rate. The time /// base of this value is specifically not defined, but it is likely /// similar to clock_gettime(CLOCK_BOOTTIME). -void ArFrame_getTimestamp(const ArSession* session, const ArFrame* frame, - int64_t* out_timestamp_ns); +void ArFrame_getTimestamp(const ArSession *session, + const ArFrame *frame, + int64_t *out_timestamp_ns); /// Transform the given texture coordinates to correctly show the background /// image. This will account for the display rotation, and any additional @@ -830,10 +1114,11 @@ void ArFrame_getTimestamp(const ArSession* session, const ArFrame* frame, /// least this many floats. /// @param[in] uvs_in Input UV coordinates in normalized screen space. /// @param[inout] uvs_out Output UV coordinates in texture coordinates. -void ArFrame_transformDisplayUvCoords(const ArSession* session, - const ArFrame* frame, - int32_t num_elements, const float* uvs_in, - float* uvs_out); +void ArFrame_transformDisplayUvCoords(const ArSession *session, + const ArFrame *frame, + int32_t num_elements, + const float *uvs_in, + float *uvs_out); /// Performs a ray cast from the user's device in the direction of the given /// location in the camera view. Intersections with detected scene geometry are @@ -861,9 +1146,11 @@ void ArFrame_transformDisplayUvCoords(const ArSession* session, /// @param[inout] hit_result_list The list to fill. This list must have been /// previously allocated using ArHitResultList_create(). If the list has /// been previously used, it will first be cleared. -void ArFrame_hitTest(const ArSession* session, const ArFrame* frame, - float pixel_x, float pixel_y, - ArHitResultList* hit_result_list); +void ArFrame_hitTest(const ArSession *session, + const ArFrame *frame, + float pixel_x, + float pixel_y, + ArHitResultList *hit_result_list); /// Gets the current ambient light estimate, if light estimation was enabled. /// @@ -871,8 +1158,9 @@ void ArFrame_hitTest(const ArSession* session, const ArFrame* frame, /// @param[in] frame The current frame. /// @param[inout] out_light_estimate The light estimate to fill. This object /// must have been previously created with ArLightEstimate_create(). -void ArFrame_getLightEstimate(const ArSession* session, const ArFrame* frame, - ArLightEstimate* out_light_estimate); +void ArFrame_getLightEstimate(const ArSession *session, + const ArFrame *frame, + ArLightEstimate *out_light_estimate); /// Acquires the current set of estimated 3d points attached to real-world /// geometry. A matching call to PointCloud_release() must be made when the @@ -890,15 +1178,16 @@ void ArFrame_getLightEstimate(const ArSession* session, const ArFrame* frame, /// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from /// by ArSession_update(). /// - #AR_ERROR_RESOURCE_EXHAUSTED if too many point clouds are currently held. -ArStatus ArFrame_acquirePointCloud(const ArSession* session, - const ArFrame* frame, - ArPointCloud** out_point_cloud); +ArStatus ArFrame_acquirePointCloud(const ArSession *session, + const ArFrame *frame, + ArPointCloud **out_point_cloud); /// Returns the camera object for the session. Note that this Camera instance is /// long-lived so the same instance is returned regardless of the frame object /// this method was called on. -void ArFrame_acquireCamera(const ArSession* session, const ArFrame* frame, - ArCamera** out_camera); +void ArFrame_acquireCamera(const ArSession *session, + const ArFrame *frame, + ArCamera **out_camera); /// Gets the camera metadata for the current camera image. /// @@ -906,10 +1195,13 @@ void ArFrame_acquireCamera(const ArSession* session, const ArFrame* frame, /// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from /// by ArSession_update(). /// - #AR_ERROR_RESOURCE_EXHAUSTED if too many metadata objects are currently -/// held. -ArStatus ArFrame_acquireImageMetadata(const ArSession* session, - const ArFrame* frame, - ArImageMetadata** out_metadata); +/// held. +/// - #AR_ERROR_NOT_YET_AVAILABLE if the camera failed to produce metadata for +/// the given frame. Note: this will commonly happen for few frames right +/// after @c ArSession_resume() due to the camera stack bringup. +ArStatus ArFrame_acquireImageMetadata(const ArSession *session, + const ArFrame *frame, + ArImageMetadata **out_metadata); /// Gets the set of anchors that were changed by the ArSession_update() that /// produced this Frame. @@ -919,8 +1211,9 @@ ArStatus ArFrame_acquireImageMetadata(const ArSession* session, /// @param[inout] out_anchor_list The list to fill. This list must have /// already been allocated with ArAnchorList_create(). If previously /// used, the list will first be cleared. -void ArFrame_getUpdatedAnchors(const ArSession* session, const ArFrame* frame, - ArAnchorList* out_anchor_list); +void ArFrame_getUpdatedAnchors(const ArSession *session, + const ArFrame *frame, + ArAnchorList *out_anchor_list); /// Gets the set of trackables of a particular type that were changed by the /// ArSession_update() call that produced this Frame. @@ -932,18 +1225,23 @@ void ArFrame_getUpdatedAnchors(const ArSession* session, const ArFrame* frame, /// @param[inout] out_trackable_list The list to fill. This list must have /// already been allocated with ArTrackableList_create(). If previously /// used, the list will first be cleared. -void ArFrame_getUpdatedTrackables(const ArSession* session, - const ArFrame* frame, +void ArFrame_getUpdatedTrackables(const ArSession *session, + const ArFrame *frame, ArTrackableType filter_type, - ArTrackableList* out_trackable_list); + ArTrackableList *out_trackable_list); + +/// @} // === ArPointCloud methods === +/// @addtogroup pointcloud +/// @{ + /// Retrieves the number of points in the point cloud. /// -void ArPointCloud_getNumberOfPoints(const ArSession* session, - const ArPointCloud* point_cloud, - int32_t* out_number_of_points); +void ArPointCloud_getNumberOfPoints(const ArSession *session, + const ArPointCloud *point_cloud, + int32_t *out_number_of_points); /// Retrieves a pointer to the point cloud data. /// @@ -958,24 +1256,29 @@ void ArPointCloud_getNumberOfPoints(const ArSession* session, /// longer. The points are in world coordinates consistent with the frame it was /// obtained from. If the number of points is zero, then the value of /// @c *out_point_cloud_data should is undefined. -void ArPointCloud_getData(const ArSession* session, - const ArPointCloud* point_cloud, - const float** out_point_cloud_data); +void ArPointCloud_getData(const ArSession *session, + const ArPointCloud *point_cloud, + const float **out_point_cloud_data); /// Returns the timestamp in nanoseconds when this point cloud was observed. /// This timestamp uses the same time base as ArFrame_getTimestamp(). -void ArPointCloud_getTimestamp(const ArSession* session, - const ArPointCloud* point_cloud, - int64_t* out_timestamp_ns); +void ArPointCloud_getTimestamp(const ArSession *session, + const ArPointCloud *point_cloud, + int64_t *out_timestamp_ns); /// Releases a reference to the point cloud. This must match a call to /// ArFrame_acquirePointCloud(). /// /// This method may safely be called with @c nullptr - it will do nothing. -void ArPointCloud_release(ArPointCloud* point_cloud); +void ArPointCloud_release(ArPointCloud *point_cloud); + +/// @} // === Image Metadata methods === +/// @addtogroup image +/// @{ + /// Retrieves the capture metadata for the current camera image. /// /// @c ACameraMetadata is a struct in Android NDK. Include NdkCameraMetadata.h @@ -984,61 +1287,69 @@ void ArPointCloud_release(ArPointCloud* point_cloud); /// Note: that the ACameraMetadata returned from this function will be invalid /// after its ArImageMetadata object is released. void ArImageMetadata_getNdkCameraMetadata( - const ArSession* session, const ArImageMetadata* image_metadata, - const ACameraMetadata** out_ndk_metadata); + const ArSession *session, + const ArImageMetadata *image_metadata, + const ACameraMetadata **out_ndk_metadata); /// Releases a reference to the metadata. This must match a call to /// ArFrame_acquireImageMetadata(). /// /// This method may safely be called with @c nullptr - it will do nothing. -void ArImageMetadata_release(ArImageMetadata* metadata); +void ArImageMetadata_release(ArImageMetadata *metadata); + +/// @} // === ArLightEstimate methods === +/// @addtogroup light +/// @{ + /// Allocates a light estimate object. -void ArLightEstimate_create(const ArSession* session, - ArLightEstimate** out_light_estimate); +void ArLightEstimate_create(const ArSession *session, + ArLightEstimate **out_light_estimate); /// Releases the provided light estimate object. -void ArLightEstimate_destroy(ArLightEstimate* light_estimate); +void ArLightEstimate_destroy(ArLightEstimate *light_estimate); /// Retrieves the validity state of a light estimate. If the resulting value of /// @c *out_light_estimate_state is not #AR_LIGHT_ESTIMATE_STATE_VALID, the /// estimate should not be used for rendering. -void ArLightEstimate_getState(const ArSession* session, - const ArLightEstimate* light_estimate, - ArLightEstimateState* out_light_estimate_state); +void ArLightEstimate_getState(const ArSession *session, + const ArLightEstimate *light_estimate, + ArLightEstimateState *out_light_estimate_state); /// Retrieves the pixel intensity of the current camera view. Values are in the /// range (0.0, 1.0), with zero being black and one being white. -void ArLightEstimate_getPixelIntensity(const ArSession* session, - const ArLightEstimate* light_estimate, - float* out_pixel_intensity); +void ArLightEstimate_getPixelIntensity(const ArSession *session, + const ArLightEstimate *light_estimate, + float *out_pixel_intensity); /// @} // === ArAnchorList methods === -/// @addtogroup trackable +/// @addtogroup anchor /// @{ /// Creates an anchor list object. -void ArAnchorList_create(const ArSession* session, - ArAnchorList** out_anchor_list); +void ArAnchorList_create(const ArSession *session, + ArAnchorList **out_anchor_list); /// Releases the memory used by an anchor list object, along with all the anchor /// references it holds. -void ArAnchorList_destroy(ArAnchorList* anchor_list); +void ArAnchorList_destroy(ArAnchorList *anchor_list); /// Retrieves the number of anchors in this list. -void ArAnchorList_getSize(const ArSession* session, - const ArAnchorList* anchor_list, int32_t* out_size); +void ArAnchorList_getSize(const ArSession *session, + const ArAnchorList *anchor_list, + int32_t *out_size); /// Acquires a reference to an indexed entry in the list. This call must /// eventually be matched with a call to ArAnchor_release(). -void ArAnchorList_acquireItem(const ArSession* session, - const ArAnchorList* anchor_list, int32_t index, - ArAnchor** out_anchor); +void ArAnchorList_acquireItem(const ArSession *session, + const ArAnchorList *anchor_list, + int32_t index, + ArAnchor **out_anchor); // === ArAnchor methods === @@ -1051,45 +1362,53 @@ void ArAnchorList_acquireItem(const ArSession* session, /// @param[in] anchor The anchor to retrieve the pose of. /// @param[inout] out_pose An already-allocated ArPose object into which the /// pose will be stored. -void ArAnchor_getPose(const ArSession* session, const ArAnchor* anchor, - ArPose* out_pose); +void ArAnchor_getPose(const ArSession *session, + const ArAnchor *anchor, + ArPose *out_pose); /// Retrieves the current state of the pose of this anchor. -void ArAnchor_getTrackingState(const ArSession* session, const ArAnchor* anchor, - ArTrackingState* out_tracking_state); +void ArAnchor_getTrackingState(const ArSession *session, + const ArAnchor *anchor, + ArTrackingState *out_tracking_state); /// Tells ARCore to stop tracking and forget this anchor. This call does not /// release the reference to the anchor - that must be done separately using /// ArAnchor_release(). -void ArAnchor_detach(ArSession* session, ArAnchor* anchor); +void ArAnchor_detach(ArSession *session, ArAnchor *anchor); /// Releases a reference to an anchor. This does not mean that the anchor will /// stop tracking, as it will be obtainable from e.g. ArSession_getAllAnchors() /// if any other references exist. /// /// This method may safely be called with @c nullptr - it will do nothing. -void ArAnchor_release(ArAnchor* anchor); +void ArAnchor_release(ArAnchor *anchor); + +/// @} // === ArTrackableList methods === +/// @addtogroup trackable +/// @{ + /// Creates a trackable list object. -void ArTrackableList_create(const ArSession* session, - ArTrackableList** out_trackable_list); +void ArTrackableList_create(const ArSession *session, + ArTrackableList **out_trackable_list); /// Releases the memory used by a trackable list object, along with all the /// anchor references it holds. -void ArTrackableList_destroy(ArTrackableList* trackable_list); +void ArTrackableList_destroy(ArTrackableList *trackable_list); /// Retrieves the number of trackables in this list. -void ArTrackableList_getSize(const ArSession* session, - const ArTrackableList* trackable_list, - int32_t* out_size); +void ArTrackableList_getSize(const ArSession *session, + const ArTrackableList *trackable_list, + int32_t *out_size); /// Acquires a reference to an indexed entry in the list. This call must /// eventually be matched with a call to ArTrackable_release(). -void ArTrackableList_acquireItem(const ArSession* session, - const ArTrackableList* trackable_list, - int32_t index, ArTrackable** out_trackable); +void ArTrackableList_acquireItem(const ArSession *session, + const ArTrackableList *trackable_list, + int32_t index, + ArTrackable **out_trackable); // === ArTrackable methods === @@ -1098,17 +1417,18 @@ void ArTrackableList_acquireItem(const ArSession* session, /// from other calls, for example ArSession_getAllTrackables(). /// /// This method may safely be called with @c nullptr - it will do nothing. -void ArTrackable_release(ArTrackable* trackable); +void ArTrackable_release(ArTrackable *trackable); /// Retrieves the type of the trackable. See ::ArTrackableType for valid types. -void ArTrackable_getType(const ArSession* session, const ArTrackable* trackable, - ArTrackableType* out_trackable_type); +void ArTrackable_getType(const ArSession *session, + const ArTrackable *trackable, + ArTrackableType *out_trackable_type); /// Retrieves the current state of ARCore's knowledge of the pose of this /// trackable. -void ArTrackable_getTrackingState(const ArSession* session, - const ArTrackable* trackable, - ArTrackingState* out_tracking_state); +void ArTrackable_getTrackingState(const ArSession *session, + const ArTrackable *trackable, + ArTrackingState *out_tracking_state); /// Creates aa Anchor at the given pose in the world coordinate space, attached /// to this Trackable, and acquires a reference to it. The type of Trackable @@ -1122,9 +1442,10 @@ void ArTrackable_getTrackingState(const ArSession* session, /// #AR_TRACKING_STATE_TRACKING /// - #AR_ERROR_SESSION_PAUSED if the session was paused /// - #AR_ERROR_RESOURCE_EXHAUSTED if too many anchors exist -ArStatus ArTrackable_acquireNewAnchor(ArSession* session, - ArTrackable* trackable, ArPose* pose, - ArAnchor** out_anchor); +ArStatus ArTrackable_acquireNewAnchor(ArSession *session, + ArTrackable *trackable, + ArPose *pose, + ArAnchor **out_anchor); /// Gets the set of anchors attached to this trackable. /// @@ -1133,12 +1454,17 @@ ArStatus ArTrackable_acquireNewAnchor(ArSession* session, /// @param[inout] out_anchor_list The list to fill. This list must have /// already been allocated with ArAnchorList_create(). If previously /// used, the list will first be cleared. -void ArTrackable_getAnchors(const ArSession* session, - const ArTrackable* trackable, - ArAnchorList* out_anchor_list); +void ArTrackable_getAnchors(const ArSession *session, + const ArTrackable *trackable, + ArAnchorList *out_anchor_list); + +/// @} // === ArPlane methods === +/// @addtogroup plane +/// @{ + /// Acquires a reference to the plane subsuming this plane. /// /// Two or more planes may be automatically merged into a single parent plane, @@ -1152,12 +1478,14 @@ void ArTrackable_getAnchors(const ArSession* session, /// /// Note: this function will set @c *out_subsumed_by to NULL if the plane is not /// subsumed. -void ArPlane_acquireSubsumedBy(const ArSession* session, const ArPlane* plane, - ArPlane** out_subsumed_by); +void ArPlane_acquireSubsumedBy(const ArSession *session, + const ArPlane *plane, + ArPlane **out_subsumed_by); /// Retrieves the type (orientation) of the plane. See ::ArPlaneType. -void ArPlane_getType(const ArSession* session, const ArPlane* plane, - ArPlaneType* out_plane_type); +void ArPlane_getType(const ArSession *session, + const ArPlane *plane, + ArPlaneType *out_plane_type); /// Returns the pose of the center of the detected plane. The pose's transformed /// +Y axis will be point normal out of the plane, with the +X and +Z axes @@ -1167,25 +1495,29 @@ void ArPlane_getType(const ArSession* session, const ArPlane* plane, /// @param[in] plane The plane for which to retrieve center pose. /// @param[inout] out_pose An already-allocated ArPose object into which the /// pose will be stored. -void ArPlane_getCenterPose(const ArSession* session, const ArPlane* plane, - ArPose* out_pose); +void ArPlane_getCenterPose(const ArSession *session, + const ArPlane *plane, + ArPose *out_pose); /// Retrieves the length of this plane's bounding rectangle measured along the /// local X-axis of the coordinate space defined by the output of /// ArPlane_getCenterPose(). -void ArPlane_getExtentX(const ArSession* session, const ArPlane* plane, - float* out_extent_x); +void ArPlane_getExtentX(const ArSession *session, + const ArPlane *plane, + float *out_extent_x); /// Retrieves the length of this plane's bounding rectangle measured along the /// local Z-axis of the coordinate space defined by the output of /// ArPlane_getCenterPose(). -void ArPlane_getExtentZ(const ArSession* session, const ArPlane* plane, - float* out_extent_z); +void ArPlane_getExtentZ(const ArSession *session, + const ArPlane *plane, + float *out_extent_z); /// Retrieves the number of elements (not vertices) in the boundary polygon. /// The number of vertices is 1/2 this size. -void ArPlane_getPolygonSize(const ArSession* session, const ArPlane* plane, - int32_t* out_polygon_size); +void ArPlane_getPolygonSize(const ArSession *session, + const ArPlane *plane, + int32_t *out_polygon_size); /// Returns the 2D vertices of a convex polygon approximating the detected /// plane, in the form [x1, z1, x2, z2, ...]. These X-Z values are in @@ -1196,51 +1528,76 @@ void ArPlane_getPolygonSize(const ArSession* session, const ArPlane* plane, /// @param[in] plane The plane to retrieve the polygon from. /// @param[inout] out_polygon_xz A pointer to an array of floats. The length of /// this array must be at least that reported by ArPlane_getPolygonSize(). -void ArPlane_getPolygon(const ArSession* session, const ArPlane* plane, - float* out_polygon_xz); +void ArPlane_getPolygon(const ArSession *session, + const ArPlane *plane, + float *out_polygon_xz); /// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained /// from a HitResult) is in the plane's rectangular extents. -void ArPlane_isPoseInExtents(const ArSession* session, const ArPlane* plane, - const ArPose* pose, int32_t* out_pose_in_extents); +void ArPlane_isPoseInExtents(const ArSession *session, + const ArPlane *plane, + const ArPose *pose, + int32_t *out_pose_in_extents); /// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained /// from a HitResult) is in the plane's polygon. -void ArPlane_isPoseInPolygon(const ArSession* session, const ArPlane* plane, - const ArPose* pose, int32_t* out_pose_in_polygon); +void ArPlane_isPoseInPolygon(const ArSession *session, + const ArPlane *plane, + const ArPose *pose, + int32_t *out_pose_in_polygon); + +/// @} // === ArPoint methods === -/// Returns the pose of the point. For @c Point objects created by -/// ArFrame_hitTest(), the orientation will follow the behavior described in -/// ArHitResult_getPose(). -/// +/// @addtogroup point +/// @{ + +/// Returns the pose of the point. +/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL, the +/// orientation will follow the behavior described in ArHitResult_getHitPose(). +/// If ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then +/// returns an orientation that is identity or close to identity. /// @param[in] session The ARCore session. /// @param[in] point The point to retrieve the pose of. /// @param[inout] out_pose An already-allocated ArPose object into which the -/// pose will be stored. -void ArPoint_getPose(const ArSession* session, const ArPoint* point, - ArPose* out_pose); +/// pose will be stored. +void ArPoint_getPose(const ArSession *session, + const ArPoint *point, + ArPose *out_pose); + +/// Returns the OrientationMode of the point. For @c Point objects created by +/// ArFrame_hitTest(). +/// If OrientationMode is ESTIMATED_SURFACE_NORMAL, then normal of the surface +/// centered around the ArPoint was estimated succesfully. +/// +/// @param[in] session The ARCore session. +/// @param[in] point The point to retrieve the pose of. +/// @param[inout] out_orientation_mode OrientationMode output result for the +/// the point. +void ArPoint_getOrientationMode(const ArSession *session, + const ArPoint *point, + ArPointOrientationMode *out_orientation_mode); /// @} // === ArHitResultList methods === -/// @addtogroup frame +/// @addtogroup hit /// @{ /// Creates a hit result list object. -void ArHitResultList_create(const ArSession* session, - ArHitResultList** out_hit_result_list); +void ArHitResultList_create(const ArSession *session, + ArHitResultList **out_hit_result_list); /// Releases the memory used by a hit result list object, along with all the /// trackable references it holds. -void ArHitResultList_destroy(ArHitResultList* hit_result_list); +void ArHitResultList_destroy(ArHitResultList *hit_result_list); /// Retrieves the number of hit results in this list. -void ArHitResultList_getSize(const ArSession* session, - const ArHitResultList* hit_result_list, - int32_t* out_size); +void ArHitResultList_getSize(const ArSession *session, + const ArHitResultList *hit_result_list, + int32_t *out_size); /// Copies an indexed entry in the list. This acquires a reference to any /// trackable referenced by the item, and releases any reference currently held @@ -1251,23 +1608,24 @@ void ArHitResultList_getSize(const ArSession* session, /// @param[in] index Index of the entry to copy. /// @param[inout] out_hit_result An already-allocated ArHitResult object into /// which the result will be copied. -void ArHitResultList_getItem(const ArSession* session, - const ArHitResultList* hit_result_list, - int32_t index, ArHitResult* out_hit_result); +void ArHitResultList_getItem(const ArSession *session, + const ArHitResultList *hit_result_list, + int32_t index, + ArHitResult *out_hit_result); // === ArHitResult methods === /// Allocates an empty hit result object. -void ArHitResult_create(const ArSession* session, ArHitResult** out_hit_result); +void ArHitResult_create(const ArSession *session, ArHitResult **out_hit_result); /// Releases the memory used by a hit result object, along with any /// trackable reference it holds. -void ArHitResult_destroy(ArHitResult* hit_result); +void ArHitResult_destroy(ArHitResult *hit_result); /// Returns the distance from the camera to the hit location, in meters. -void ArHitResult_getDistance(const ArSession* session, - const ArHitResult* hit_result, - float* out_distance); +void ArHitResult_getDistance(const ArSession *session, + const ArHitResult *hit_result, + float *out_distance); /// Returns the pose of the intersection between a ray and detected real-world /// geometry. The position is the location in space where the ray intersected @@ -1279,11 +1637,19 @@ void ArHitResult_getDistance(const ArSession* session, /// planes), and Z+ is parallel to the plane, pointing roughly toward the /// user's device. /// -/// ::ArPoint : X+ is perpendicular to the cast ray and points right from the -/// perspective of the user's device, Y+ points up, and Z+ points roughly toward -/// the user's device. -/// -/// If you wish to retain the location of this pose beyond the duration of a +/// ::ArPoint : +/// Attempt to estimate the normal of the surface centered around the hit test. +/// Surface normal estimation is most likely to succeed on textured surfaces +/// and with camera motion. +/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL, +/// then X+ is perpendicular to the cast ray and parallel to the physical +/// surface centered around the hit test, Y+ points along the estimated surface +/// normal, and Z+ points roughly toward the user's device. If +/// ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then X+ is +/// perpendicular to the cast ray and points right from the perspective of the +/// user's device, Y+ points up, and Z+ points roughly toward the user's device. +/// +/// If you wish to retain the location of this pose beyond the duration of aÃŽ /// single frame, create an anchor using ArHitResult_createAnchor() to save the /// pose in a physically consistent way. /// @@ -1291,14 +1657,15 @@ void ArHitResult_getDistance(const ArSession* session, /// @param[in] hit_result The hit result to retrieve the pose of. /// @param[inout] out_pose An already-allocated ArPose object into which the /// pose will be stored. -void ArHitResult_getHitPose(const ArSession* session, - const ArHitResult* hit_result, ArPose* out_pose); +void ArHitResult_getHitPose(const ArSession *session, + const ArHitResult *hit_result, + ArPose *out_pose); -/// Acquires reference to the hit trackable. This call must be paired with a +/// Acquires reference to the hit trackable. This call must be paired with a /// call to ArTrackable_release(). -void ArHitResult_acquireTrackable(const ArSession* session, - const ArHitResult* hit_result, - ArTrackable** out_trackable); +void ArHitResult_acquireTrackable(const ArSession *session, + const ArHitResult *hit_result, + ArTrackable **out_trackable); /// Creates a new anchor at the hit location. See ArHitResult_getHitPose() for /// details. This is equivalent to creating an anchor on the hit trackable at @@ -1310,9 +1677,9 @@ void ArHitResult_acquireTrackable(const ArSession* session, /// - #AR_ERROR_RESOURCE_EXHAUSTED /// - #AR_ERROR_DEADLINE_EXCEEDED - hit result must be used before the next call /// to update(). -ArStatus ArHitResult_acquireNewAnchor(ArSession* session, - ArHitResult* hit_result, - ArAnchor** out_anchor); +ArStatus ArHitResult_acquireNewAnchor(ArSession *session, + ArHitResult *hit_result, + ArAnchor **out_anchor); /// @} @@ -1320,5 +1687,4 @@ ArStatus ArHitResult_acquireNewAnchor(ArSession* session, } #endif - -#endif // ARCORE_C_API_H_ +#endif // THIRD_PARTY_ARCORE_AR_CORE_C_API_ARCORE_C_API_H_ diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar deleted file mode 100644 index 7470bc3e5..000000000 Binary files a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar and /dev/null differ diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.md5 b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.md5 deleted file mode 100644 index 646b1b190..000000000 --- a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.md5 +++ /dev/null @@ -1 +0,0 @@ -91b870506b7963c285147e29523418d1 diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.sha1 b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.sha1 deleted file mode 100644 index b4fd0305f..000000000 --- a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.aar.sha1 +++ /dev/null @@ -1 +0,0 @@ -0da2caba973a305d880d979ab1cda6af1081747e diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom deleted file mode 100644 index 3f81dd4ea..000000000 --- a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom +++ /dev/null @@ -1,17 +0,0 @@ - - 4.0.0 - com.google.ar - core - 0.91.0 - aar - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.md5 b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.md5 deleted file mode 100644 index 5b7b43059..000000000 --- a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -02e1872e7de92d7237f8a4e4157ee797 diff --git a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.sha1 b/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.sha1 deleted file mode 100644 index cbf27a2b0..000000000 --- a/libraries/m2repository/com/google/ar/core/0.91.0/core-0.91.0.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -a5e5a7a3b85f0becb109e0308f0f92ff8c724b8b diff --git a/samples/computervision/app/build.gradle b/samples/computervision/app/build.gradle index c08d32a17..b86ca613f 100644 --- a/samples/computervision/app/build.gradle +++ b/samples/computervision/app/build.gradle @@ -21,7 +21,7 @@ android { dependencies { // ARCore library - implementation 'com.google.ar:core:0.91.0' + implementation 'com.google.ar:core:1.0.0' implementation 'com.android.support:appcompat-v7:27.0.2' implementation 'com.android.support:design:27.0.2' diff --git a/samples/computervision/app/src/main/AndroidManifest.xml b/samples/computervision/app/src/main/AndroidManifest.xml index 1b74db308..890ead496 100644 --- a/samples/computervision/app/src/main/AndroidManifest.xml +++ b/samples/computervision/app/src/main/AndroidManifest.xml @@ -18,27 +18,27 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.ar.core.examples.java.computervision"> - + - + - - - - - - - + + + + + + + diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/MainActivity.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/MainActivity.java index 96481d458..2dbfbff61 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/MainActivity.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/MainActivity.java @@ -16,9 +16,6 @@ package com.google.ar.core.examples.java.computervision; -import static com.google.ar.core.examples.java.computervision.utility.CameraPermissionHelper.CAMERA_PERMISSION_CODE; - -import android.content.pm.PackageManager; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.os.Bundle; @@ -27,9 +24,10 @@ import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MotionEvent; +import android.view.Surface; import android.view.View; import android.view.WindowManager; -import com.google.ar.core.Config; +import android.widget.Toast; import com.google.ar.core.Frame; import com.google.ar.core.Session; import com.google.ar.core.examples.java.computervision.rendering.BackgroundRenderer; @@ -46,267 +44,275 @@ /** This is a simple example that demonstrates texture reading with ARCore. */ public class MainActivity extends AppCompatActivity implements GLSurfaceView.Renderer { - private static final String TAG = MainActivity.class.getSimpleName(); - - // Rendering. The Renderers are created here, and initialized when the GL surface is created. - private GLSurfaceView mSurfaceView; - - private Session mSession; - private Snackbar mMessageSnackbar; - private DisplayRotationHelper mDisplayRotationHelper; - - private final BackgroundRenderer mBackgroundRenderer = new BackgroundRenderer(); - private final CameraImageBuffer mEdgeImage = new CameraImageBuffer(); - private final TextureReader mTextureReader = new TextureReader(); - - // ArCore full resolution texture has a size of 1920 x 1080. - private static final int TEXTURE_WIDTH = 1920; - private static final int TEXTURE_HEIGHT = 1080; - - // We choose a lower sampling resolution. - private static final int IMAGE_WIDTH = 1024; - private static final int IMAGE_HEIGHT = 512; - - private int mFrameBufferIndex = -1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - mSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceview); - mDisplayRotationHelper = new DisplayRotationHelper(/*context=*/ this); - - // Setup a touch listener to control the texture splitter position. - mSurfaceView.setOnTouchListener( - new View.OnTouchListener() { - private static final float SWIPE_SCALING_FACTOR = 1.15f; - private static final float MIN_DELTA = .01f; - private float mStartPosition = 0; - private float mStartCoordinate = 0; - - @Override - public boolean onTouch(View v, MotionEvent e) { - switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: - { - mStartCoordinate = e.getY(); - mStartPosition = mBackgroundRenderer.getSplitterPosition(); - break; - } - case MotionEvent.ACTION_MOVE: - { - float delta = (e.getY() - mStartCoordinate) / mSurfaceView.getHeight(); - - if (Math.abs(delta) > MIN_DELTA) { - float newPosition = mStartPosition + delta * SWIPE_SCALING_FACTOR; - newPosition = Math.min(1.f, Math.max(0.f, newPosition)); - mBackgroundRenderer.setSplitterPosition(newPosition); - } - break; - } - default: - break; - } - - return true; + private static final String TAG = MainActivity.class.getSimpleName(); + + // Rendering. The Renderers are created here, and initialized when the GL surface is created. + private GLSurfaceView surfaceView; + + private Session session; + private Snackbar messageSnackbar; + private DisplayRotationHelper displayRotationHelper; + + private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer(); + private final CameraImageBuffer edgeImage = new CameraImageBuffer(); + private final TextureReader textureReader = new TextureReader(); + + // ArCore full resolution texture has a size of 1920 x 1080. + private static final int TEXTURE_WIDTH = 1920; + private static final int TEXTURE_HEIGHT = 1080; + + // We choose a lower sampling resolution. + private static final int IMAGE_WIDTH = 1024; + private static final int IMAGE_HEIGHT = 512; + + private int frameBufferIndex = -1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + surfaceView = (GLSurfaceView) findViewById(R.id.surfaceview); + displayRotationHelper = new DisplayRotationHelper(/*context=*/ this); + + // Setup a touch listener to control the texture splitter position. + surfaceView.setOnTouchListener( + new View.OnTouchListener() { + private static final float SWIPE_SCALING_FACTOR = 1.15f; + private static final float MIN_DELTA = .01f; + private float startPosition = 0; + private float startCoordX = 0; + private float startCoordY = 0; + private int displayRotation = 0; + + @Override + public boolean onTouch(View v, MotionEvent e) { + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: + { + startCoordX = e.getX(); + startCoordY = e.getY(); + displayRotation = displayRotationHelper.getRotation(); + startPosition = backgroundRenderer.getSplitterPosition(); + break; } - }); - - // Set up renderer. - mSurfaceView.setPreserveEGLContextOnPause(true); - mSurfaceView.setEGLContextClientVersion(2); - mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. - mSurfaceView.setRenderer(this); - mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); - - Exception exception = null; - String message = null; - try { - mSession = new Session(/* context= */ this); - } catch (UnavailableArcoreNotInstalledException e) { - message = "Please install ARCore"; - exception = e; - } catch (UnavailableApkTooOldException e) { - message = "Please update ARCore"; - exception = e; - } catch (UnavailableSdkTooOldException e) { - message = "Please update this app"; - exception = e; - } catch (Exception e) { - message = "This device does not support AR"; - exception = e; - } - - if (message != null) { - showSnackbarMessage(message, true); - Log.e(TAG, "Exception creating session", exception); - return; - } - - // Create default config and check if supported. - Config defaultConfig = new Config(mSession); - if (!mSession.isSupported(defaultConfig)) { - showSnackbarMessage("This device does not support AR", true); - } - } - - @Override - protected void onResume() { - super.onResume(); + case MotionEvent.ACTION_MOVE: + { + float delta = 0; + switch (displayRotation) { + case Surface.ROTATION_90: + delta = (e.getX() - startCoordX) / surfaceView.getWidth(); + break; + case Surface.ROTATION_180: + delta = -(e.getY() - startCoordY) / surfaceView.getHeight(); + break; + case Surface.ROTATION_270: + delta = -(e.getX() - startCoordX) / surfaceView.getWidth(); + break; + case Surface.ROTATION_0: + default: + delta = (e.getY() - startCoordY) / surfaceView.getHeight(); + break; + } + if (Math.abs(delta) > MIN_DELTA) { + float newPosition = startPosition + delta * SWIPE_SCALING_FACTOR; + newPosition = Math.min(1.f, Math.max(0.f, newPosition)); + backgroundRenderer.setSplitterPosition(newPosition); + } + break; + } + default: + break; + } + return true; + } + }); + + // Set up renderer. + surfaceView.setPreserveEGLContextOnPause(true); + surfaceView.setEGLContextClientVersion(2); + surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. + surfaceView.setRenderer(this); + surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } + + @Override + protected void onResume() { + super.onResume(); + + if (session == null) { + Exception exception = null; + String message = null; + try { // ARCore requires camera permissions to operate. If we did not yet obtain runtime // permission on Android M and above, now is a good time to ask the user for it. - if (CameraPermissionHelper.hasCameraPermission(this)) { - if (mSession != null) { - // Note that order matters - see the note in onPause(), the reverse applies here. - mSession.resume(); - } - mSurfaceView.onResume(); - mDisplayRotationHelper.onResume(); - } else { - CameraPermissionHelper.requestCameraPermission(this); + if (!CameraPermissionHelper.hasCameraPermission(this)) { + CameraPermissionHelper.requestCameraPermission(this); + return; } - } - @Override - public void onPause() { - super.onPause(); - // Note that the order matters - GLSurfaceView is paused first so that it does not try - // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may - // still call mSession.update() and get a SessionPausedException. - mDisplayRotationHelper.onPause(); - mSurfaceView.onPause(); - if (mSession != null) { - mSession.pause(); - } + session = new Session(/* context= */ this); + } catch (UnavailableArcoreNotInstalledException e) { + message = "Please install ARCore"; + exception = e; + } catch (UnavailableApkTooOldException e) { + message = "Please update ARCore"; + exception = e; + } catch (UnavailableSdkTooOldException e) { + message = "Please update this app"; + exception = e; + } catch (Exception e) { + message = "This device does not support AR"; + exception = e; + } + + if (message != null) { + showSnackbarMessage(message, true); + Log.e(TAG, "Exception creating session", exception); + return; + } } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { - if (requestCode != CAMERA_PERMISSION_CODE) { - return; - } - if (results.length > 0 && results[0] == PackageManager.PERMISSION_DENIED) { - // Permission denied. - if (CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) { - // Permission denied without checking "Do not ask again." - showSnackbarMessage("Camera permission is needed to run this application", true); - } else if (!CameraPermissionHelper.hasCameraPermission(this)) { - // Permission denied with checking "Do not ask again". - // Show toast and take user to app settings where they can grant the camera - // permission. - CameraPermissionHelper.launchPermissionSettings(this); - } - } + // Note that order matters - see the note in onPause(), the reverse applies here. + session.resume(); + surfaceView.onResume(); + displayRotationHelper.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + // Note that the order matters - GLSurfaceView is paused first so that it does not try + // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may + // still call session.update() and get a SessionPausedException. + displayRotationHelper.onPause(); + surfaceView.onPause(); + if (session != null) { + session.pause(); } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - // Standard Android full-screen functionality. - getWindow() - .getDecorView() - .setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { + if (!CameraPermissionHelper.hasCameraPermission(this)) { + Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG) + .show(); + if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) { + // Permission denied with checking "Do not ask again". + CameraPermissionHelper.launchPermissionSettings(this); + } + finish(); } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + // Standard Android full-screen functionality. + getWindow() + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - - // Create the texture and pass it to ARCore session to be filled during update(). - mBackgroundRenderer.createOnGlThread(/*context=*/ this); + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - if (mSession != null) { - mSession.setCameraTextureName(mBackgroundRenderer.getTextureId()); - } + // Create the texture and pass it to ARCore session to be filled during update(). + backgroundRenderer.createOnGlThread(/*context=*/ this); - // The image format can be either IMAGE_FORMAT_RGBA or IMAGE_FORMAT_I8. - // Set keepAspectRatio to false so that the output image covers the whole viewport. - mTextureReader.create(CameraImageBuffer.IMAGE_FORMAT_I8, IMAGE_WIDTH, IMAGE_HEIGHT, false); + if (session != null) { + session.setCameraTextureName(backgroundRenderer.getTextureId()); } - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - mDisplayRotationHelper.onSurfaceChanged(width, height); - GLES20.glViewport(0, 0, width, height); + // The image format can be either IMAGE_FORMAT_RGBA or IMAGE_FORMAT_I8. + // Set keepAspectRatio to false so that the output image covers the whole viewport. + textureReader.create(CameraImageBuffer.IMAGE_FORMAT_I8, IMAGE_WIDTH, IMAGE_HEIGHT, false); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + displayRotationHelper.onSurfaceChanged(width, height); + GLES20.glViewport(0, 0, width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + // Clear screen to notify driver it should not load any pixels from previous frame. + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + if (session == null) { + return; } + // Notify ARCore session that the view size changed so that the perspective matrix and + // the video background can be properly adjusted. + displayRotationHelper.updateSessionIfNeeded(session); - @Override - public void onDrawFrame(GL10 gl) { - // Clear screen to notify driver it should not load any pixels from previous frame. - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - if (mSession == null) { - return; - } - // Notify ARCore session that the view size changed so that the perspective matrix and - // the video background can be properly adjusted. - mDisplayRotationHelper.updateSessionIfNeeded(mSession); - - try { - Frame frame = mSession.update(); + try { + Frame frame = session.update(); - // If there is a frame being requested previously, acquire the pixels and process it. - if (mFrameBufferIndex >= 0) { - CameraImageBuffer imageBuffer = mTextureReader.acquireFrame(mFrameBufferIndex); + // If there is a frame being requested previously, acquire the pixels and process it. + if (frameBufferIndex >= 0) { + CameraImageBuffer imageBuffer = textureReader.acquireFrame(frameBufferIndex); - // Detect the edges from the captured grayscale image. - if (EdgeDetector.detect(mEdgeImage, imageBuffer)) { - // Set the edge image to renderer as overlay. - mBackgroundRenderer.setOverlayImage(mEdgeImage); - } + // Detect the edges from the captured grayscale image. + if (EdgeDetector.detect(edgeImage, imageBuffer)) { + // Set the edge image to renderer as overlay. + backgroundRenderer.setOverlayImage(edgeImage); + } - // You should always release frame buffer after using. Otherwise the next call to - // submitFrame() may fail. - mTextureReader.releaseFrame(mFrameBufferIndex); - } + // You should always release frame buffer after using. Otherwise the next call to + // submitFrame() may fail. + textureReader.releaseFrame(frameBufferIndex); + } - // Submit request for the texture from the current frame. - mFrameBufferIndex = mTextureReader.submitFrame( - mBackgroundRenderer.getTextureId(), TEXTURE_WIDTH, TEXTURE_HEIGHT); + // Submit request for the texture from the current frame. + frameBufferIndex = + textureReader.submitFrame( + backgroundRenderer.getTextureId(), TEXTURE_WIDTH, TEXTURE_HEIGHT); - // Draw background video. - mBackgroundRenderer.draw(frame); + // Draw background video. + backgroundRenderer.draw(frame); - } catch (Throwable t) { - // Avoid crashing the application due to unhandled exceptions. - Log.e(TAG, "Exception on the OpenGL thread", t); - } + } catch (Throwable t) { + // Avoid crashing the application due to unhandled exceptions. + Log.e(TAG, "Exception on the OpenGL thread", t); } - - private void showSnackbarMessage(String message, boolean finishOnDismiss) { - mMessageSnackbar = - Snackbar.make( - MainActivity.this.findViewById(android.R.id.content), - message, - Snackbar.LENGTH_INDEFINITE); - mMessageSnackbar.getView().setBackgroundColor(0xbf323232); - if (finishOnDismiss) { - mMessageSnackbar.setAction( - "Dismiss", - new View.OnClickListener() { - @Override - public void onClick(View v) { - mMessageSnackbar.dismiss(); - } - }); - mMessageSnackbar.addCallback( - new BaseTransientBottomBar.BaseCallback() { - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - super.onDismissed(transientBottomBar, event); - finish(); - } - }); - } - mMessageSnackbar.show(); + } + + private void showSnackbarMessage(String message, boolean finishOnDismiss) { + messageSnackbar = + Snackbar.make( + MainActivity.this.findViewById(android.R.id.content), + message, + Snackbar.LENGTH_INDEFINITE); + messageSnackbar.getView().setBackgroundColor(0xbf323232); + if (finishOnDismiss) { + messageSnackbar.setAction( + "Dismiss", + new View.OnClickListener() { + @Override + public void onClick(View v) { + messageSnackbar.dismiss(); + } + }); + messageSnackbar.addCallback( + new BaseTransientBottomBar.BaseCallback() { + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + super.onDismissed(transientBottomBar, event); + finish(); + } + }); } + messageSnackbar.show(); + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/package-info.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/package-info.java deleted file mode 100644 index c9e451a39..000000000 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ -/** - * A sample to demonstrate marker detection library. - */ -package com.google.ar.core.examples.java.computervision; diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/BackgroundRenderer.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/BackgroundRenderer.java index 0b04ed27c..9514e61ef 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/BackgroundRenderer.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/BackgroundRenderer.java @@ -33,232 +33,225 @@ * ARCore to be filled with the camera image. */ public class BackgroundRenderer { - private static final String TAG = BackgroundRenderer.class.getSimpleName(); - - private static final int COORDS_PER_VERTEX = 3; - private static final int TEXCOORDS_PER_VERTEX = 2; - private static final int FLOAT_SIZE = 4; - - private FloatBuffer mQuadVertices; - private FloatBuffer mQuadTexCoord; - private FloatBuffer mQuadTexCoordTransformed; - - private int mQuadProgram; - - private int mQuadPositionAttrib; - private int mQuadTexCoordAttrib; - private int mQuadSplitterUniform; - private int mBackgroundTextureId = -1; - private int mOverlayTextureId = -1; - private float mSplitterPosition = 0.5f; - - private CameraImageBuffer mOverlayImageBuffer; - - public int getTextureId() { - return mBackgroundTextureId; - } - - /** - * Allocates and initializes OpenGL resources needed by the background renderer. Must be called - * on the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, - * EGLConfig)}. - * - * @param context Needed to access shader source. - */ - public void createOnGlThread(Context context) { - // Generate the background texture. - int[] textures = new int[2]; - GLES20.glGenTextures(2, textures, 0); - mBackgroundTextureId = textures[0]; - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mBackgroundTextureId); - GLES20.glTexParameteri( - GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri( - GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri( - GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); - GLES20.glTexParameteri( - GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); - - mOverlayTextureId = textures[1]; - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOverlayTextureId); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, - GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, - GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, - GLES20.GL_NEAREST); - - int numVertices = 4; - if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { - throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); - } - - ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); - bbVertices.order(ByteOrder.nativeOrder()); - mQuadVertices = bbVertices.asFloatBuffer(); - mQuadVertices.put(QUAD_COORDS); - mQuadVertices.position(0); - - ByteBuffer bbTexCoords = - ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); - bbTexCoords.order(ByteOrder.nativeOrder()); - mQuadTexCoord = bbTexCoords.asFloatBuffer(); - mQuadTexCoord.put(QUAD_TEXCOORDS); - mQuadTexCoord.position(0); - - ByteBuffer bbTexCoordsTransformed = - ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); - bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); - mQuadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); - - int vertexShader = - ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex); - int fragmentShader = - ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, - R.raw.screenquad_fragment); - - mQuadProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(mQuadProgram, vertexShader); - GLES20.glAttachShader(mQuadProgram, fragmentShader); - GLES20.glLinkProgram(mQuadProgram); - GLES20.glUseProgram(mQuadProgram); - - ShaderUtil.checkGLError(TAG, "Program creation"); - - mQuadPositionAttrib = GLES20.glGetAttribLocation(mQuadProgram, "a_Position"); - mQuadTexCoordAttrib = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord"); - mQuadSplitterUniform = GLES20.glGetUniformLocation(mQuadProgram, "s_SplitterPosition"); - - int texLoc = GLES20.glGetUniformLocation(mQuadProgram, "TexVideo"); - GLES20.glUniform1i(texLoc, 0); - texLoc = GLES20.glGetUniformLocation(mQuadProgram, "TexImage"); - GLES20.glUniform1i(texLoc, 1); - - ShaderUtil.checkGLError(TAG, "Program parameters"); - } - - /** - * Sets new overlay image buffer. This image buffer is used to render side by side with - * background video. - * - * @param imageBuffer the new image buffer for the overlay texture. - */ - public void setOverlayImage(CameraImageBuffer imageBuffer) { - mOverlayImageBuffer = imageBuffer; - } - - /** - * Gets the texture splitter position. - * - * @return the splitter position. - */ - public float getSplitterPosition() { - return mSplitterPosition; + private static final String TAG = BackgroundRenderer.class.getSimpleName(); + + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private FloatBuffer quadTexCoordTransformed; + + private int quadProgram; + + private int quadPositionAttrib; + private int quadTexCoordAttrib; + private int quadSplitterUniform; + private int backgroundTextureId = -1; + private int overlayTextureId = -1; + private float splitterPosition = 0.5f; + + private CameraImageBuffer overlayImageBuffer; + + public int getTextureId() { + return backgroundTextureId; + } + + /** + * Allocates and initializes OpenGL resources needed by the background renderer. Must be called on + * the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, + * EGLConfig)}. + * + * @param context Needed to access shader source. + */ + public void createOnGlThread(Context context) { + // Generate the background texture. + int[] textures = new int[2]; + GLES20.glGenTextures(2, textures, 0); + backgroundTextureId = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, backgroundTextureId); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + overlayTextureId = textures[1]; + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, overlayTextureId); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); } - /** - * Sets the splitter position. This position defines the splitting position between the - * background video and the image. - * - * @param position the new splitter position. - */ - public void setSplitterPosition(float position) { - mSplitterPosition = position; + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + ByteBuffer bbTexCoordsTransformed = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); + quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex); + int fragmentShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + quadPositionAttrib = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordAttrib = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + quadSplitterUniform = GLES20.glGetUniformLocation(quadProgram, "s_SplitterPosition"); + + int texLoc = GLES20.glGetUniformLocation(quadProgram, "TexVideo"); + GLES20.glUniform1i(texLoc, 0); + texLoc = GLES20.glGetUniformLocation(quadProgram, "TexImage"); + GLES20.glUniform1i(texLoc, 1); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + } + + /** + * Sets new overlay image buffer. This image buffer is used to render side by side with background + * video. + * + * @param imageBuffer the new image buffer for the overlay texture. + */ + public void setOverlayImage(CameraImageBuffer imageBuffer) { + overlayImageBuffer = imageBuffer; + } + + /** + * Gets the texture splitter position. + * + * @return the splitter position. + */ + public float getSplitterPosition() { + return splitterPosition; + } + + /** + * Sets the splitter position. This position defines the splitting position between the background + * video and the image. + * + * @param position the new splitter position. + */ + public void setSplitterPosition(float position) { + splitterPosition = position; + } + + /** + * Draws the AR background image. The image will be drawn such that virtual content rendered with + * the matrices provided by {@link Frame#getViewMatrix(float[], int)} and {@link + * Session#getProjectionMatrix(float[], int, float, float)} will accurately follow static physical + * objects. This must be called before drawing virtual content. + * + * @param frame The last {@code Frame} returned by {@link Session#update()}. + */ + public void draw(Frame frame) { + // If display rotation changed (also includes view size change), we need to re-query the uv + // coordinates for the screen rect, as they may have changed as well. + if (frame.hasDisplayGeometryChanged()) { + frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed); } - /** - * Draws the AR background image. The image will be drawn such that virtual content rendered - * with the matrices provided by {@link Frame#getViewMatrix(float[], int)} and {@link - * Session#getProjectionMatrix(float[], int, float, float)} will accurately follow static - * physical objects. This must be called before drawing virtual content. - * - * @param frame The last {@code Frame} returned by {@link Session#update()}. - */ - public void draw(Frame frame) { - // If display rotation changed (also includes view size change), we need to re-query the uv - // coordinates for the screen rect, as they may have changed as well. - if (frame.hasDisplayGeometryChanged()) { - frame.transformDisplayUvCoords(mQuadTexCoord, mQuadTexCoordTransformed); - } - - // No need to test or write depth, the screen quad has arbitrary depth, and is expected - // to be drawn first. - GLES20.glDisable(GLES20.GL_DEPTH_TEST); - GLES20.glDepthMask(false); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mBackgroundTextureId); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE1); - - // Apply overlay image buffer - if (mOverlayImageBuffer != null - && mOverlayImageBuffer.format == CameraImageBuffer.IMAGE_FORMAT_I8) { - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOverlayTextureId); - - ByteBuffer pixelBuffer = mOverlayImageBuffer.buffer; - pixelBuffer.position(0); - GLES20.glTexImage2D( - GLES20.GL_TEXTURE_2D, - 0, - GLES20.GL_LUMINANCE, - mOverlayImageBuffer.width, - mOverlayImageBuffer.height, - 0, - GLES20.GL_LUMINANCE, - GLES20.GL_UNSIGNED_BYTE, - pixelBuffer); - } - - GLES20.glUseProgram(mQuadProgram); - - // Set the vertex positions. - GLES20.glVertexAttribPointer( - mQuadPositionAttrib, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices); - - // Set splitter position. - GLES20.glUniform1f(mQuadSplitterUniform, mSplitterPosition); - - // Set the texture coordinates. - GLES20.glVertexAttribPointer( - mQuadTexCoordAttrib, - TEXCOORDS_PER_VERTEX, - GLES20.GL_FLOAT, - false, - 0, - mQuadTexCoordTransformed); - - // Enable vertex arrays - GLES20.glEnableVertexAttribArray(mQuadPositionAttrib); - GLES20.glEnableVertexAttribArray(mQuadTexCoordAttrib); - - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - - // Disable vertex arrays - GLES20.glDisableVertexAttribArray(mQuadPositionAttrib); - GLES20.glDisableVertexAttribArray(mQuadTexCoordAttrib); - - // Restore the depth state for further drawing. - GLES20.glDepthMask(true); - GLES20.glEnable(GLES20.GL_DEPTH_TEST); - - ShaderUtil.checkGLError(TAG, "Draw"); + // No need to test or write depth, the screen quad has arbitrary depth, and is expected + // to be drawn first. + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthMask(false); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, backgroundTextureId); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE1); + + // Apply overlay image buffer + if (overlayImageBuffer != null + && overlayImageBuffer.format == CameraImageBuffer.IMAGE_FORMAT_I8) { + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, overlayTextureId); + + ByteBuffer pixelBuffer = overlayImageBuffer.buffer; + pixelBuffer.position(0); + GLES20.glTexImage2D( + GLES20.GL_TEXTURE_2D, + 0, + GLES20.GL_LUMINANCE, + overlayImageBuffer.width, + overlayImageBuffer.height, + 0, + GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, + pixelBuffer); } - private static final float[] QUAD_COORDS = - new float[] { - -1.0f, -1.0f, 0.0f, - -1.0f, +1.0f, 0.0f, - +1.0f, -1.0f, 0.0f, - +1.0f, +1.0f, 0.0f, - }; - - private static final float[] QUAD_TEXCOORDS = - new float[] { - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 1.0f, - 1.0f, 0.0f, - }; + GLES20.glUseProgram(quadProgram); + + // Set the vertex positions. + GLES20.glVertexAttribPointer( + quadPositionAttrib, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + // Set splitter position. + GLES20.glUniform1f(quadSplitterUniform, splitterPosition); + + // Set the texture coordinates. + GLES20.glVertexAttribPointer( + quadTexCoordAttrib, + TEXCOORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + 0, + quadTexCoordTransformed); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(quadPositionAttrib); + GLES20.glEnableVertexAttribArray(quadTexCoordAttrib); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(quadPositionAttrib); + GLES20.glDisableVertexAttribArray(quadTexCoordAttrib); + + // Restore the depth state for further drawing. + GLES20.glDepthMask(true); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + + ShaderUtil.checkGLError(TAG, "Draw"); + } + + private static final float[] QUAD_COORDS = + new float[] { + -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[] { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/ShaderUtil.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/ShaderUtil.java index 8fc58b6ae..c401ccfe3 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/ShaderUtil.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/ShaderUtil.java @@ -17,97 +17,94 @@ import android.content.Context; import android.opengl.GLES20; import android.util.Log; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -/** - * Shader helper functions. - */ +/** Shader helper functions. */ public class ShaderUtil { - /** - * Converts a raw text file, saved as a resource, into an OpenGL ES shader. - * - * @param type The type of shader we will be creating. - * @param resId The resource ID of the raw text file about to be turned into a shader. - * @return The shader object handler. - */ - public static int loadGLShader(String tag, Context context, int type, int resId) { - String code = readRawTextFile(context, resId); - return loadGLShader(tag, type, code); - } - - /** - * Converts a string into an OpenGL ES shader. - * - * @param type The type of shader we will be creating. - * @param code The string that contains the code of the shader. - * @return The shader object handler. - */ - public static int loadGLShader(String tag, int type, String code) { - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, code); - GLES20.glCompileShader(shader); + /** + * Converts a raw text file, saved as a resource, into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param resId The resource ID of the raw text file about to be turned into a shader. + * @return The shader object handler. + */ + public static int loadGLShader(String tag, Context context, int type, int resId) { + String code = readRawTextFile(context, resId); + return loadGLShader(tag, type, code); + } - // Get the compilation status. - final int[] compileStatus = new int[1]; - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + /** + * Converts a string into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param code The string that contains the code of the shader. + * @return The shader object handler. + */ + public static int loadGLShader(String tag, int type, String code) { + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); - // If the compilation failed, delete the shader. - if (compileStatus[0] == 0) { - Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); - GLES20.glDeleteShader(shader); - shader = 0; - } + // Get the compilation status. + final int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); - if (shader == 0) { - throw new RuntimeException("Error creating shader."); - } + // If the compilation failed, delete the shader. + if (compileStatus[0] == 0) { + Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } - return shader; + if (shader == 0) { + throw new RuntimeException("Error creating shader."); } - /** - * Checks if we've had an error inside of OpenGL ES, and if so what that error is. - * - * @param label Label to report in case of error. - * @throws RuntimeException If an OpenGL error is detected. - */ - public static void checkGLError(String tag, String label) { - int lastError = GLES20.GL_NO_ERROR; - // Drain the queue of all errors. - int error; - while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { - Log.e(tag, label + ": glError " + error); - lastError = error; - } - if (lastError != GLES20.GL_NO_ERROR) { - throw new RuntimeException(label + ": glError " + lastError); - } + return shader; + } + + /** + * Checks if we've had an error inside of OpenGL ES, and if so what that error is. + * + * @param label Label to report in case of error. + * @throws RuntimeException If an OpenGL error is detected. + */ + public static void checkGLError(String tag, String label) { + int lastError = GLES20.GL_NO_ERROR; + // Drain the queue of all errors. + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(tag, label + ": glError " + error); + lastError = error; + } + if (lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException(label + ": glError " + lastError); } + } - /** - * Converts a raw text file into a string. - * - * @param resId The resource ID of the raw text file about to be turned into a shader. - * @return The context of the text file, or null in case of error. - */ - private static String readRawTextFile(Context context, int resId) { - InputStream inputStream = context.getResources().openRawResource(resId); - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - reader.close(); - return sb.toString(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; + /** + * Converts a raw text file into a string. + * + * @param resId The resource ID of the raw text file about to be turned into a shader. + * @return The context of the text file, or null in case of error. + */ + private static String readRawTextFile(Context context, int resId) { + InputStream inputStream = context.getResources().openRawResource(resId); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); } + return null; + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/package-info.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/package-info.java deleted file mode 100644 index 3695c7862..000000000 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/rendering/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ -/** - * This package contains classes that do the rendering for this example. - */ -package com.google.ar.core.examples.java.computervision.rendering; diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraImageBuffer.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraImageBuffer.java index 094ffcfc8..1c7e63089 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraImageBuffer.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraImageBuffer.java @@ -18,57 +18,56 @@ /** Image Buffer Class. */ public class CameraImageBuffer { - /** The id corresponding to RGBA8888. */ - public static final int IMAGE_FORMAT_RGBA = 0; + /** The id corresponding to RGBA8888. */ + public static final int IMAGE_FORMAT_RGBA = 0; - /** The id corresponding to grayscale. */ - public static final int IMAGE_FORMAT_I8 = 1; + /** The id corresponding to grayscale. */ + public static final int IMAGE_FORMAT_I8 = 1; - /** The width of the image, in pixels. */ - public int width; + /** The width of the image, in pixels. */ + public int width; - /** The height of the image, in pixels. */ - public int height; + /** The height of the image, in pixels. */ + public int height; - /** The image buffer. */ - public ByteBuffer buffer; + /** The image buffer. */ + public ByteBuffer buffer; - /** Pixel format. Can be either IMAGE_FORMAT_RGBA or IMAGE_FORMAT_I8.*/ - public int format; + /** Pixel format. Can be either IMAGE_FORMAT_RGBA or IMAGE_FORMAT_I8. */ + public int format; - /** - * Default constructor. - */ - public CameraImageBuffer() { - width = 1; - height = 1; - format = IMAGE_FORMAT_RGBA; - buffer = ByteBuffer.allocateDirect(4); - } - - /** - * Constructor. - * @param imgWidth the width of the image, in pixels. - * @param imgHeight the height of the image, in pixels. - * @param imgFormat the format of the image. - * @param imgBuffer the buffer of the image pixels. - */ - public CameraImageBuffer(int imgWidth, int imgHeight, int imgFormat, ByteBuffer imgBuffer) { - if (imgWidth == 0 || imgHeight == 0) { - throw new RuntimeException("Invalid image size."); - } + /** Default constructor. */ + public CameraImageBuffer() { + width = 1; + height = 1; + format = IMAGE_FORMAT_RGBA; + buffer = ByteBuffer.allocateDirect(4); + } - if (imgFormat != IMAGE_FORMAT_RGBA && imgFormat != IMAGE_FORMAT_I8) { - throw new RuntimeException("Invalid image format."); - } + /** + * Constructor. + * + * @param imgWidth the width of the image, in pixels. + * @param imgHeight the height of the image, in pixels. + * @param imgFormat the format of the image. + * @param imgBuffer the buffer of the image pixels. + */ + public CameraImageBuffer(int imgWidth, int imgHeight, int imgFormat, ByteBuffer imgBuffer) { + if (imgWidth == 0 || imgHeight == 0) { + throw new RuntimeException("Invalid image size."); + } - if (imgBuffer == null) { - throw new RuntimeException("Pixel buffer cannot be null."); - } + if (imgFormat != IMAGE_FORMAT_RGBA && imgFormat != IMAGE_FORMAT_I8) { + throw new RuntimeException("Invalid image format."); + } - width = imgWidth; - height = imgHeight; - format = imgFormat; - buffer = imgBuffer; + if (imgBuffer == null) { + throw new RuntimeException("Pixel buffer cannot be null."); } + + width = imgWidth; + height = imgHeight; + format = imgFormat; + buffer = imgBuffer; + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraPermissionHelper.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraPermissionHelper.java index b1dd07e0a..19b2b1c62 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraPermissionHelper.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/CameraPermissionHelper.java @@ -22,44 +22,34 @@ import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; -import android.widget.Toast; /** Helper to ask camera permission. */ public final class CameraPermissionHelper { - public static final int CAMERA_PERMISSION_CODE = 0; - private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + private static final int CAMERA_PERMISSION_CODE = 0; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; - /** - * Check to see we have the necessary permissions for this app. - */ - public static boolean hasCameraPermission(Activity activity) { - return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) - == PackageManager.PERMISSION_GRANTED; - } + /** Check to see we have the necessary permissions for this app. */ + public static boolean hasCameraPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } - /** Check to see if we need to show the rationale for this permission. */ - public static boolean shouldShowRequestPermissionRationale(Activity activity) { - return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION); - } + /** Check to see we have the necessary permissions for this app, and ask for them if we don't. */ + public static void requestCameraPermission(Activity activity) { + ActivityCompat.requestPermissions( + activity, new String[] {CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE); + } - /** - * Check to see we have the necessary permissions for this app, and ask for them if we don't. - */ - public static void requestCameraPermission(Activity activity) { - ActivityCompat.requestPermissions(activity, new String[]{CAMERA_PERMISSION}, - CAMERA_PERMISSION_CODE); - } + /** Check to see if we need to show the rationale for this permission. */ + public static boolean shouldShowRequestPermissionRationale(Activity activity) { + return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION); + } - /** - * Launch Application Setting to grant permission, show a toast to explain why. - * This will finish() the activity, as we cannot run an AR activity without camera permissions. - */ - public static void launchPermissionSettings(Activity activity) { - Toast.makeText(activity, "Camera permission is needed", Toast.LENGTH_LONG).show(); - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); - activity.startActivity(intent); - activity.finish(); - } + /** Launch Application Setting to grant permission. */ + public static void launchPermissionSettings(Activity activity) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); + activity.startActivity(intent); + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/DisplayRotationHelper.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/DisplayRotationHelper.java index 1f7a062bd..6c8954ac8 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/DisplayRotationHelper.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/DisplayRotationHelper.java @@ -28,80 +28,78 @@ * events. */ public class DisplayRotationHelper implements DisplayListener { - private boolean mViewportChanged; - private int mViewportWidth; - private int mViewportHeight; - private final Context mContext; - private final Display mDisplay; + private boolean viewportChanged; + private int viewportWidth; + private int viewportHeight; + private final Context context; + private final Display display; - /** - * Constructs the DisplayRotationHelper but does not register the listener yet. - * - * @param context the Android {@link Context}. - */ - public DisplayRotationHelper(Context context) { - mContext = context; - mDisplay = context.getSystemService(WindowManager.class).getDefaultDisplay(); - } + /** + * Constructs the DisplayRotationHelper but does not register the listener yet. + * + * @param context the Android {@link Context}. + */ + public DisplayRotationHelper(Context context) { + this.context = context; + display = context.getSystemService(WindowManager.class).getDefaultDisplay(); + } - /** Registers the display listener. Should be called from {@link Activity#onResume()}. */ - public void onResume() { - mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, null); - } + /** Registers the display listener. Should be called from {@link Activity#onResume()}. */ + public void onResume() { + context.getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } - /** Unregisters the display listener. Should be called from {@link Activity#onPause()}. */ - public void onPause() { - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); - } + /** Unregisters the display listener. Should be called from {@link Activity#onPause()}. */ + public void onPause() { + context.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } - /** - * Records a change in surface dimensions. This will be later used by - * {@link #updateSessionIfNeeded(Session)}. Should be called from - * {@link android.opengl.GLSurfaceView.Renderer - * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. - * - * @param width the updated width of the surface. - * @param height the updated height of the surface. - */ - public void onSurfaceChanged(int width, int height) { - mViewportWidth = width; - mViewportHeight = height; - mViewportChanged = true; - } + /** + * Records a change in surface dimensions. This will be later used by {@link + * #updateSessionIfNeeded(Session)}. Should be called from {@link + * android.opengl.GLSurfaceView.Renderer + * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. + * + * @param width the updated width of the surface. + * @param height the updated height of the surface. + */ + public void onSurfaceChanged(int width, int height) { + viewportWidth = width; + viewportHeight = height; + viewportChanged = true; + } - /** - * Updates the session display geometry if a change was posted either by - * {@link #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system - * callback. This function should be called explicitly before each call to - * {@link Session#update()}. This function will also clear the 'pending update' - * (viewportChanged) flag. - * - * @param session the {@link Session} object to update if display geometry changed. - */ - public void updateSessionIfNeeded(Session session) { - if (mViewportChanged) { - int displayRotation = mDisplay.getRotation(); - session.setDisplayGeometry(displayRotation, mViewportWidth, mViewportHeight); - mViewportChanged = false; - } + /** + * Updates the session display geometry if a change was posted either by {@link + * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This + * function should be called explicitly before each call to {@link Session#update()}. This + * function will also clear the 'pending update' (viewportChanged) flag. + * + * @param session the {@link Session} object to update if display geometry changed. + */ + public void updateSessionIfNeeded(Session session) { + if (viewportChanged) { + int displayRotation = display.getRotation(); + session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight); + viewportChanged = false; } + } - /** - * Returns the current rotation state of android display. - * Same as {@link Display#getRotation()}. - */ - public int getRotation() { - return mDisplay.getRotation(); - } + /** + * Returns the current rotation state of android display. Same as {@link Display#getRotation()}. + */ + public int getRotation() { + return display.getRotation(); + } - @Override - public void onDisplayAdded(int displayId) {} + @Override + public void onDisplayAdded(int displayId) {} - @Override - public void onDisplayRemoved(int displayId) {} + @Override + public void onDisplayRemoved(int displayId) {} - @Override - public void onDisplayChanged(int displayId) { - mViewportChanged = true; - } + @Override + public void onDisplayChanged(int displayId) { + viewportChanged = true; + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/EdgeDetector.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/EdgeDetector.java index 1e0055990..aaeeaa9f1 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/EdgeDetector.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/EdgeDetector.java @@ -18,101 +18,98 @@ import android.util.Log; import java.nio.ByteBuffer; -/** - * Detects edges from input grayscale image. - */ +/** Detects edges from input grayscale image. */ public class EdgeDetector { - private static final String TAG = EdgeDetector.class.getSimpleName(); - - private static byte[] s_ImageBuffer = new byte[0]; - private static int s_ImageBufferSize = 0; - - /** - * Detects edges from the input grayscale image. - * - * @param outputImage Output image buffer, which has a size of width * height. - * @param inputImage Input image. - * @return False if the outputImage buffer is too small, True otherwise. - */ - public static boolean detect(CameraImageBuffer outputImage, CameraImageBuffer inputImage) { - if (inputImage == null - || inputImage.format != CameraImageBuffer.IMAGE_FORMAT_I8) { - Log.e(TAG, "Invalid input image!"); - return false; - } + private static final String TAG = EdgeDetector.class.getSimpleName(); + + private static byte[] s_ImageBuffer = new byte[0]; + private static int s_ImageBufferSize = 0; + + /** + * Detects edges from the input grayscale image. + * + * @param outputImage Output image buffer, which has a size of width * height. + * @param inputImage Input image. + * @return False if the outputImage buffer is too small, True otherwise. + */ + public static boolean detect(CameraImageBuffer outputImage, CameraImageBuffer inputImage) { + if (inputImage == null || inputImage.format != CameraImageBuffer.IMAGE_FORMAT_I8) { + Log.e(TAG, "Invalid input image!"); + return false; + } - if (outputImage == null) { - Log.e(TAG, "Invalid output image!"); - return false; - } + if (outputImage == null) { + Log.e(TAG, "Invalid output image!"); + return false; + } - // Recreate output image buffer if it is different from input image buffer. - if (outputImage.width != inputImage.width - || outputImage.height != inputImage.height - || outputImage.format != inputImage.format - || outputImage.buffer == null) { - outputImage.width = inputImage.width; - outputImage.height = inputImage.height; - outputImage.format = inputImage.format; - outputImage.buffer = ByteBuffer.allocate(inputImage.width * inputImage.height); - } + // Recreate output image buffer if it is different from input image buffer. + if (outputImage.width != inputImage.width + || outputImage.height != inputImage.height + || outputImage.format != inputImage.format + || outputImage.buffer == null) { + outputImage.width = inputImage.width; + outputImage.height = inputImage.height; + outputImage.format = inputImage.format; + outputImage.buffer = ByteBuffer.allocate(inputImage.width * inputImage.height); + } - sobel(outputImage.buffer, inputImage.buffer, inputImage.width, inputImage.height); + sobel(outputImage.buffer, inputImage.buffer, inputImage.width, inputImage.height); - return true; - } + return true; + } - private static void sobel( - ByteBuffer outputBuffer, ByteBuffer inputBuffer, int width, int height) { - // Adjust buffer size if necessary. - final int bufferSize = width * height; - if (s_ImageBuffer.length < bufferSize || bufferSize < s_ImageBufferSize) { - s_ImageBuffer = new byte[bufferSize]; - s_ImageBufferSize = bufferSize; - } + private static void sobel( + ByteBuffer outputBuffer, ByteBuffer inputBuffer, int width, int height) { + // Adjust buffer size if necessary. + final int bufferSize = width * height; + if (s_ImageBuffer.length < bufferSize || bufferSize < s_ImageBufferSize) { + s_ImageBuffer = new byte[bufferSize]; + s_ImageBufferSize = bufferSize; + } - inputBuffer.position(0); - inputBuffer.get(s_ImageBuffer); - - outputBuffer.position(0); - byte[] outputPixel = outputBuffer.array(); - - // Detect edges. - int threshold = 128 * 128; - - for (int j = 1; j < height - 1; j++) { - for (int i = 1; i < width - 1; i++) { - // Offset of the pixel at [i, j] of the input image. - int offset = (j * width) + i; - - // Neighbour pixels around the pixel at [i, j]. - int a00 = s_ImageBuffer[offset - width - 1]; - int a01 = s_ImageBuffer[offset - width]; - int a02 = s_ImageBuffer[offset - width + 1]; - int a10 = s_ImageBuffer[offset - 1]; - int a12 = s_ImageBuffer[offset + 1]; - int a20 = s_ImageBuffer[offset + width - 1]; - int a21 = s_ImageBuffer[offset + width]; - int a22 = s_ImageBuffer[offset + width + 1]; - - // Sobel X filter: - // -1, 0, 1, - // -2, 0, 2, - // -1, 0, 1 - int xSum = -a00 - (2 * a10) - a20 + a02 + (2 * a12) + a22; - - // Sobel Y filter: - // 1, 2, 1, - // 0, 0, 0, - // -1, -2, -1 - int ySum = a00 + (2 * a01) + a02 - a20 - (2 * a21) - a22; - - if ((xSum * xSum) + (ySum * ySum) > threshold) { - outputPixel[(j * width) + i] = (byte) 0xFF; - } else { - outputPixel[(j * width) + i] = (byte) 0x1F; - } - } + inputBuffer.position(0); + inputBuffer.get(s_ImageBuffer); + + outputBuffer.position(0); + byte[] outputPixel = outputBuffer.array(); + + // Detect edges. + int threshold = 128 * 128; + + for (int j = 1; j < height - 1; j++) { + for (int i = 1; i < width - 1; i++) { + // Offset of the pixel at [i, j] of the input image. + int offset = (j * width) + i; + + // Neighbour pixels around the pixel at [i, j]. + int a00 = s_ImageBuffer[offset - width - 1]; + int a01 = s_ImageBuffer[offset - width]; + int a02 = s_ImageBuffer[offset - width + 1]; + int a10 = s_ImageBuffer[offset - 1]; + int a12 = s_ImageBuffer[offset + 1]; + int a20 = s_ImageBuffer[offset + width - 1]; + int a21 = s_ImageBuffer[offset + width]; + int a22 = s_ImageBuffer[offset + width + 1]; + + // Sobel X filter: + // -1, 0, 1, + // -2, 0, 2, + // -1, 0, 1 + int xSum = -a00 - (2 * a10) - a20 + a02 + (2 * a12) + a22; + + // Sobel Y filter: + // 1, 2, 1, + // 0, 0, 0, + // -1, -2, -1 + int ySum = a00 + (2 * a01) + a02 - a20 - (2 * a21) - a22; + + if ((xSum * xSum) + (ySum * ySum) > threshold) { + outputPixel[(j * width) + i] = (byte) 0xFF; + } else { + outputPixel[(j * width) + i] = (byte) 0x1F; } + } } + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/TextureReader.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/TextureReader.java index 83532b3b0..30ca19a19 100644 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/TextureReader.java +++ b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/TextureReader.java @@ -60,434 +60,423 @@ * release the internal resource when you are done with the reader. */ public class TextureReader { - private static final String TAG = TextureReader.class.getSimpleName(); - - // By default, we create only two internal buffers. So you can only hold more than one buffer - // index in your app without releasing it. If you need to hold more than one buffers, you can - // increase the mBufferCount member. - private final int mBufferCount = 2; - private int[] mFrameBuffer; - private int[] mTexture; - private int[] mPBO; - private Boolean[] mBufferUsed; - private int mFrontIndex = -1; - private int mBackIndex = -1; - - // By default, the output image format is set to RGBA. You can also set it to IMAGE_FORMAT_I8. - private int mImageFormat = CameraImageBuffer.IMAGE_FORMAT_RGBA; - private int mImageWidth = 0; - private int mImageHeight = 0; - private int mPixelBufferSize = 0; - private Boolean mKeepAspectRatio = false; - - private FloatBuffer mQuadVertices; - private FloatBuffer mQuadTexCoord; - private int mQuadProgram; - private int mQuadPositionAttrib; - private int mQuadTexCoordAttrib; - private static final int COORDS_PER_VERTEX = 3; - private static final int TEXCOORDS_PER_VERTEX = 2; - private static final int FLOAT_SIZE = 4; - private static final float[] QUAD_COORDS = - new float[] { - -1.0f, -1.0f, 0.0f, - -1.0f, +1.0f, 0.0f, - +1.0f, -1.0f, 0.0f, - +1.0f, +1.0f, 0.0f, - }; - - private static final float[] QUAD_TEXCOORDS = - new float[] { - 0.0f, 0.0f, - 0.0f, 1.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - }; - - private static final String QUAD_RENDERING_VERTEX_SHADER = - "// Vertex shader.\n" - + "attribute vec4 a_Position;\n" - + "attribute vec2 a_TexCoord;\n" - + "varying vec2 v_TexCoord;\n" - + "void main() {\n" - + " gl_Position = a_Position;\n" - + " v_TexCoord = a_TexCoord;\n" - + "}"; - - private static final String QUAD_RENDERING_FRAGMENT_SHADER_RGBA = - "// Fragment shader that renders to a RGBA texture.\n" - + "#extension GL_OES_EGL_image_external : require\n" - + "precision mediump float;\n" - + "varying vec2 v_TexCoord;\n" - + "uniform samplerExternalOES sTexture;\n" - + "void main() {\n" - + " gl_FragColor = texture2D(sTexture, v_TexCoord);\n" - + "}"; - - private static final String QUAD_RENDERING_FRAGMENT_SHADER_I8 = - "// Fragment shader that renders to a grayscale texture.\n" - + "#extension GL_OES_EGL_image_external : require\n" - + "precision mediump float;\n" - + "varying vec2 v_TexCoord;\n" - + "uniform samplerExternalOES sTexture;\n" - + "void main() {\n" - + " vec4 color = texture2D(sTexture, v_TexCoord);\n" - + " gl_FragColor.r = color.r * 0.299 + color.g * 0.587 + color.b * 0.114;\n" - + "}"; - - /** - * Creates the texture reader. - * This function needs to be called from the OpenGL rendering thread. - * - * @param format the format of the output pixel buffer. It can be one of the two values: - * CameraImageBuffer.IMAGE_FORMAT_RGBA or CameraImageBuffer.IMAGE_FORMAT_I8. - * @param width the width of the output image. - * @param height the height of the output image. - * @param keepAspectRatio whether or not to keep aspect ratio. If true, the output image may be - * cropped if the image aspect ratio is different from the texture aspect ratio. If false, - * the output image covers the entire texture scope and no cropping is applied. - */ - public void create(int format, int width, int height, Boolean keepAspectRatio) { - if (format != CameraImageBuffer.IMAGE_FORMAT_RGBA - && format != CameraImageBuffer.IMAGE_FORMAT_I8) { - throw new RuntimeException("Image format not supported."); - } - - mKeepAspectRatio = keepAspectRatio; - mImageFormat = format; - mImageWidth = width; - mImageHeight = height; - mFrontIndex = -1; - mBackIndex = -1; - - if (mImageFormat == CameraImageBuffer.IMAGE_FORMAT_RGBA) { - mPixelBufferSize = mImageWidth * mImageHeight * 4; - } else if (mImageFormat == CameraImageBuffer.IMAGE_FORMAT_I8) { - mPixelBufferSize = mImageWidth * mImageHeight; - } - - // Create framebuffers and PBOs. - mPBO = new int[mBufferCount]; - mFrameBuffer = new int[mBufferCount]; - mTexture = new int[mBufferCount]; - mBufferUsed = new Boolean[mBufferCount]; - GLES30.glGenBuffers(mBufferCount, mPBO, 0); - GLES20.glGenFramebuffers(mBufferCount, mFrameBuffer, 0); - GLES20.glGenTextures(mBufferCount, mTexture, 0); - - for (int i = 0; i < mBufferCount; i++) { - mBufferUsed[i] = false; - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[i]); - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture[i]); - GLES30.glTexImage2D( - GLES30.GL_TEXTURE_2D, - 0, - mImageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_R8 : GLES30.GL_RGBA, - mImageWidth, - mImageHeight, - 0, - mImageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_RED : GLES30.GL_RGBA, - GLES30.GL_UNSIGNED_BYTE, - null); - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, - GLES20.GL_LINEAR); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, - GLES20.GL_LINEAR); - GLES20.glFramebufferTexture2D( - GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, - mTexture[i], 0); - - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { - throw new RuntimeException( - this - + ": Failed to set up render buffer with status " - + status - + " and error " - + GLES20.glGetError()); - } - - // Setup PBOs - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPBO[i]); - GLES30.glBufferData( - GLES30.GL_PIXEL_PACK_BUFFER, mPixelBufferSize, null, GLES30.GL_DYNAMIC_READ); - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); - } - - // Load shader program. - int numVertices = 4; - if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { - throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); - } - - ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); - bbVertices.order(ByteOrder.nativeOrder()); - mQuadVertices = bbVertices.asFloatBuffer(); - mQuadVertices.put(QUAD_COORDS); - mQuadVertices.position(0); - - ByteBuffer bbTexCoords = - ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); - bbTexCoords.order(ByteOrder.nativeOrder()); - mQuadTexCoord = bbTexCoords.asFloatBuffer(); - mQuadTexCoord.put(QUAD_TEXCOORDS); - mQuadTexCoord.position(0); - - int vertexShader = - ShaderUtil.loadGLShader(TAG, GLES20.GL_VERTEX_SHADER, QUAD_RENDERING_VERTEX_SHADER); - int fragmentShader = - ShaderUtil.loadGLShader( - TAG, - GLES20.GL_FRAGMENT_SHADER, - mImageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 - ? QUAD_RENDERING_FRAGMENT_SHADER_I8 - : QUAD_RENDERING_FRAGMENT_SHADER_RGBA); - - mQuadProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(mQuadProgram, vertexShader); - GLES20.glAttachShader(mQuadProgram, fragmentShader); - GLES20.glLinkProgram(mQuadProgram); - GLES20.glUseProgram(mQuadProgram); - - mQuadPositionAttrib = GLES20.glGetAttribLocation(mQuadProgram, "a_Position"); - mQuadTexCoordAttrib = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord"); - int texLoc = GLES20.glGetUniformLocation(mQuadProgram, "sTexture"); - GLES20.glUniform1i(texLoc, 0); + private static final String TAG = TextureReader.class.getSimpleName(); + + // By default, we create only two internal buffers. So you can only hold more than one buffer + // index in your app without releasing it. If you need to hold more than one buffers, you can + // increase the bufferCount member. + private final int bufferCount = 2; + private int[] frameBuffer; + private int[] texture; + private int[] pbo; + private Boolean[] bufferUsed; + private int frontIndex = -1; + private int backIndex = -1; + + // By default, the output image format is set to RGBA. You can also set it to IMAGE_FORMAT_I8. + private int imageFormat = CameraImageBuffer.IMAGE_FORMAT_RGBA; + private int imageWidth = 0; + private int imageHeight = 0; + private int pixelBufferSize = 0; + private Boolean keepAspectRatio = false; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private int quadProgram; + private int quadPositionAttrib; + private int quadTexCoordAttrib; + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + private static final float[] QUAD_COORDS = + new float[] { + -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[] { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + }; + + private static final String QUAD_RENDERING_VERTEX_SHADER = + "// Vertex shader.\n" + + "attribute vec4 a_Position;\n" + + "attribute vec2 a_TexCoord;\n" + + "varying vec2 v_TexCoord;\n" + + "void main() {\n" + + " gl_Position = a_Position;\n" + + " v_TexCoord = a_TexCoord;\n" + + "}"; + + private static final String QUAD_RENDERING_FRAGMENT_SHADER_RGBA = + "// Fragment shader that renders to a RGBA texture.\n" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 v_TexCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, v_TexCoord);\n" + + "}"; + + private static final String QUAD_RENDERING_FRAGMENT_SHADER_I8 = + "// Fragment shader that renders to a grayscale texture.\n" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 v_TexCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " vec4 color = texture2D(sTexture, v_TexCoord);\n" + + " gl_FragColor.r = color.r * 0.299 + color.g * 0.587 + color.b * 0.114;\n" + + "}"; + + /** + * Creates the texture reader. This function needs to be called from the OpenGL rendering thread. + * + * @param format the format of the output pixel buffer. It can be one of the two values: + * CameraImageBuffer.IMAGE_FORMAT_RGBA or CameraImageBuffer.IMAGE_FORMAT_I8. + * @param width the width of the output image. + * @param height the height of the output image. + * @param keepAspectRatio whether or not to keep aspect ratio. If true, the output image may be + * cropped if the image aspect ratio is different from the texture aspect ratio. If false, the + * output image covers the entire texture scope and no cropping is applied. + */ + public void create(int format, int width, int height, Boolean keepAspectRatio) { + if (format != CameraImageBuffer.IMAGE_FORMAT_RGBA + && format != CameraImageBuffer.IMAGE_FORMAT_I8) { + throw new RuntimeException("Image format not supported."); } - /** Destroy the texture reader. */ - public void destroy() { - if (mFrameBuffer != null) { - GLES20.glDeleteFramebuffers(mBufferCount, mFrameBuffer, 0); - mFrameBuffer = null; - } - if (mTexture != null) { - GLES20.glDeleteTextures(mBufferCount, mTexture, 0); - mTexture = null; - } - if (mPBO != null) { - GLES30.glDeleteBuffers(mBufferCount, mPBO, 0); - mPBO = null; - } + this.keepAspectRatio = keepAspectRatio; + imageFormat = format; + imageWidth = width; + imageHeight = height; + frontIndex = -1; + backIndex = -1; + + if (imageFormat == CameraImageBuffer.IMAGE_FORMAT_RGBA) { + pixelBufferSize = imageWidth * imageHeight * 4; + } else if (imageFormat == CameraImageBuffer.IMAGE_FORMAT_I8) { + pixelBufferSize = imageWidth * imageHeight; } - /** - * Submits a frame reading request. This routine does not return the result frame buffer - * immediately. Instead, it returns a frame buffer index, which can be used to acquire the frame - * buffer later through acquireFrame(). - * - *

If there is no enough frame buffer available, an exception will be thrown. - * - * @param textureId the id of the input OpenGL texture. - * @param textureWidth width of the texture in pixels. - * @param textureHeight height of the texture in pixels. - * @return the index to the frame buffer this request is associated to. You should use this - * index to acquire the frame using acquireFrame(); and you should release the frame buffer - * using releaseBuffer() routine after using of the frame. - */ - public int submitFrame(int textureId, int textureWidth, int textureHeight) { - // Find next buffer. - int bufferIndex = -1; - for (int i = 0; i < mBufferCount; i++) { - if (!mBufferUsed[i]) { - bufferIndex = i; - break; - } - } - if (bufferIndex == -1) { - throw new RuntimeException("No buffer available."); - } - - // Bind both read and write to framebuffer. - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[bufferIndex]); - - // Save and setup viewport - IntBuffer viewport = IntBuffer.allocate(4); - GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, viewport); - GLES20.glViewport(0, 0, mImageWidth, mImageHeight); - - // Draw texture to framebuffer. - drawTexture(textureId, textureWidth, textureHeight); - - // Start reading into PBO - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPBO[bufferIndex]); - GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0); - - GLES30.glReadPixels( - 0, - 0, - mImageWidth, - mImageHeight, - mImageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_RED : GLES20.GL_RGBA, - GLES20.GL_UNSIGNED_BYTE, - 0); - - // Restore viewport. - GLES20.glViewport(viewport.get(0), viewport.get(1), viewport.get(2), viewport.get(3)); - - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); - - mBufferUsed[bufferIndex] = true; - return bufferIndex; + // Create framebuffers and PBOs. + pbo = new int[bufferCount]; + frameBuffer = new int[bufferCount]; + texture = new int[bufferCount]; + bufferUsed = new Boolean[bufferCount]; + GLES30.glGenBuffers(bufferCount, pbo, 0); + GLES20.glGenFramebuffers(bufferCount, frameBuffer, 0); + GLES20.glGenTextures(bufferCount, texture, 0); + + for (int i = 0; i < bufferCount; i++) { + bufferUsed[i] = false; + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[i]); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[i]); + GLES30.glTexImage2D( + GLES30.GL_TEXTURE_2D, + 0, + imageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_R8 : GLES30.GL_RGBA, + imageWidth, + imageHeight, + 0, + imageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_RED : GLES30.GL_RGBA, + GLES30.GL_UNSIGNED_BYTE, + null); + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glFramebufferTexture2D( + GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texture[i], 0); + + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException( + this + + ": Failed to set up render buffer with status " + + status + + " and error " + + GLES20.glGetError()); + } + + // Setup PBOs + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo[i]); + GLES30.glBufferData( + GLES30.GL_PIXEL_PACK_BUFFER, pixelBufferSize, null, GLES30.GL_DYNAMIC_READ); + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); } - /** - * Acquires the frame requested earlier. This routine returns a CameraImageBuffer object that - * contains the pixels mapped to the frame buffer requested previously through submitFrame(). - * - *

If input buffer index is invalid, an exception will be thrown. - * - * @param bufferIndex the index to the frame buffer to be acquired. It has to be a frame index - * returned from submitFrame(). - * @return a CameraImageBuffer object if succeed. Null otherwise. - */ - public CameraImageBuffer acquireFrame(int bufferIndex) { - if (bufferIndex < 0 || bufferIndex >= mBufferCount || !mBufferUsed[bufferIndex]) { - throw new RuntimeException("Invalid buffer index."); - } - - // Bind the current PB and acquire the pixel buffer. - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPBO[bufferIndex]); - ByteBuffer mapped = - (ByteBuffer) - GLES30.glMapBufferRange( - GLES30.GL_PIXEL_PACK_BUFFER, 0, mPixelBufferSize, GLES30.GL_MAP_READ_BIT); - - // Wrap the mapped buffer into CameraImageBuffer object. - CameraImageBuffer buffer = - new CameraImageBuffer(mImageWidth, mImageHeight, mImageFormat, mapped); - - return buffer; + // Load shader program. + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); } - /** - * Releases a previously requested frame buffer. If input buffer index is invalid, an exception - * will be thrown. - * - * @param bufferIndex the index to the frame buffer to be acquired. It has to be a frame index - * returned from submitFrame(). - */ - public void releaseFrame(int bufferIndex) { - if (bufferIndex < 0 || bufferIndex >= mBufferCount || !mBufferUsed[bufferIndex]) { - throw new RuntimeException("Invalid buffer index."); - } - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPBO[bufferIndex]); - GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER); - GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); - mBufferUsed[bufferIndex] = false; + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, GLES20.GL_VERTEX_SHADER, QUAD_RENDERING_VERTEX_SHADER); + int fragmentShader = + ShaderUtil.loadGLShader( + TAG, + GLES20.GL_FRAGMENT_SHADER, + imageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 + ? QUAD_RENDERING_FRAGMENT_SHADER_I8 + : QUAD_RENDERING_FRAGMENT_SHADER_RGBA); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + quadPositionAttrib = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordAttrib = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + int texLoc = GLES20.glGetUniformLocation(quadProgram, "sTexture"); + GLES20.glUniform1i(texLoc, 0); + } + + /** Destroy the texture reader. */ + public void destroy() { + if (frameBuffer != null) { + GLES20.glDeleteFramebuffers(bufferCount, frameBuffer, 0); + frameBuffer = null; + } + if (texture != null) { + GLES20.glDeleteTextures(bufferCount, texture, 0); + texture = null; + } + if (pbo != null) { + GLES30.glDeleteBuffers(bufferCount, pbo, 0); + pbo = null; + } + } + + /** + * Submits a frame reading request. This routine does not return the result frame buffer + * immediately. Instead, it returns a frame buffer index, which can be used to acquire the frame + * buffer later through acquireFrame(). + * + *

If there is no enough frame buffer available, an exception will be thrown. + * + * @param textureId the id of the input OpenGL texture. + * @param textureWidth width of the texture in pixels. + * @param textureHeight height of the texture in pixels. + * @return the index to the frame buffer this request is associated to. You should use this index + * to acquire the frame using acquireFrame(); and you should release the frame buffer using + * releaseBuffer() routine after using of the frame. + */ + public int submitFrame(int textureId, int textureWidth, int textureHeight) { + // Find next buffer. + int bufferIndex = -1; + for (int i = 0; i < bufferCount; i++) { + if (!bufferUsed[i]) { + bufferIndex = i; + break; + } + } + if (bufferIndex == -1) { + throw new RuntimeException("No buffer available."); + } + + // Bind both read and write to framebuffer. + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[bufferIndex]); + + // Save and setup viewport + IntBuffer viewport = IntBuffer.allocate(4); + GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, viewport); + GLES20.glViewport(0, 0, imageWidth, imageHeight); + + // Draw texture to framebuffer. + drawTexture(textureId, textureWidth, textureHeight); + + // Start reading into PBO + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo[bufferIndex]); + GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0); + + GLES30.glReadPixels( + 0, + 0, + imageWidth, + imageHeight, + imageFormat == CameraImageBuffer.IMAGE_FORMAT_I8 ? GLES30.GL_RED : GLES20.GL_RGBA, + GLES20.GL_UNSIGNED_BYTE, + 0); + + // Restore viewport. + GLES20.glViewport(viewport.get(0), viewport.get(1), viewport.get(2), viewport.get(3)); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); + + bufferUsed[bufferIndex] = true; + return bufferIndex; + } + + /** + * Acquires the frame requested earlier. This routine returns a CameraImageBuffer object that + * contains the pixels mapped to the frame buffer requested previously through submitFrame(). + * + *

If input buffer index is invalid, an exception will be thrown. + * + * @param bufferIndex the index to the frame buffer to be acquired. It has to be a frame index + * returned from submitFrame(). + * @return a CameraImageBuffer object if succeed. Null otherwise. + */ + public CameraImageBuffer acquireFrame(int bufferIndex) { + if (bufferIndex < 0 || bufferIndex >= bufferCount || !bufferUsed[bufferIndex]) { + throw new RuntimeException("Invalid buffer index."); + } + + // Bind the current PB and acquire the pixel buffer. + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo[bufferIndex]); + ByteBuffer mapped = + (ByteBuffer) + GLES30.glMapBufferRange( + GLES30.GL_PIXEL_PACK_BUFFER, 0, pixelBufferSize, GLES30.GL_MAP_READ_BIT); + + // Wrap the mapped buffer into CameraImageBuffer object. + CameraImageBuffer buffer = new CameraImageBuffer(imageWidth, imageHeight, imageFormat, mapped); + + return buffer; + } + + /** + * Releases a previously requested frame buffer. If input buffer index is invalid, an exception + * will be thrown. + * + * @param bufferIndex the index to the frame buffer to be acquired. It has to be a frame index + * returned from submitFrame(). + */ + public void releaseFrame(int bufferIndex) { + if (bufferIndex < 0 || bufferIndex >= bufferCount || !bufferUsed[bufferIndex]) { + throw new RuntimeException("Invalid buffer index."); + } + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo[bufferIndex]); + GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER); + GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0); + bufferUsed[bufferIndex] = false; + } + + /** + * Reads pixels using dual buffers. This function sends the reading request to GPU and returns the + * result from the previous call. Thus, the first call always returns null. The pixelBuffer member + * in the returned object maps to the internal buffer. This buffer cannot be overrode, and it + * becomes invalid after next call to submitAndAcquire(). + * + * @param textureId the OpenGL texture Id. + * @param textureWidth width of the texture in pixels. + * @param textureHeight height of the texture in pixels. + * @return a CameraImageBuffer object that contains the pixels read from the texture. + */ + public CameraImageBuffer submitAndAcquire(int textureId, int textureWidth, int textureHeight) { + // Release previously used front buffer. + if (frontIndex != -1) { + releaseFrame(frontIndex); } - /** - * Reads pixels using dual buffers. This function sends the reading request to GPU and returns - * the result from the previous call. Thus, the first call always returns null. The pixelBuffer - * member in the returned object maps to the internal buffer. This buffer cannot be overrode, - * and it becomes invalid after next call to submitAndAcquire(). - * - * @param textureId the OpenGL texture Id. - * @param textureWidth width of the texture in pixels. - * @param textureHeight height of the texture in pixels. - * @return a CameraImageBuffer object that contains the pixels read from the texture. - */ - public CameraImageBuffer submitAndAcquire(int textureId, int textureWidth, int textureHeight) { - // Release previously used front buffer. - if (mFrontIndex != -1) { - releaseFrame(mFrontIndex); - } - - // Move previous back buffer to front buffer. - mFrontIndex = mBackIndex; - - // Submit new request on back buffer. - mBackIndex = submitFrame(textureId, textureWidth, textureHeight); - - // Acquire frame from the new front buffer. - if (mFrontIndex != -1) { - return acquireFrame(mFrontIndex); - } - - return null; + // Move previous back buffer to front buffer. + frontIndex = backIndex; + + // Submit new request on back buffer. + backIndex = submitFrame(textureId, textureWidth, textureHeight); + + // Acquire frame from the new front buffer. + if (frontIndex != -1) { + return acquireFrame(frontIndex); } - /** Draws texture to full screen. */ - private void drawTexture(int textureId, int textureWidth, int textureHeight) { - // Disable features that we don't use. - GLES20.glDisable(GLES20.GL_DEPTH_TEST); - GLES20.glDisable(GLES20.GL_CULL_FACE); - GLES20.glDisable(GLES20.GL_SCISSOR_TEST); - GLES20.glDisable(GLES20.GL_STENCIL_TEST); - GLES20.glDisable(GLES20.GL_BLEND); - GLES20.glDepthMask(false); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - GLES30.glBindVertexArray(0); - - // Clear buffers. - GLES20.glClearColor(0, 0, 0, 0); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - - // Set the vertex positions. - GLES20.glVertexAttribPointer( - mQuadPositionAttrib, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices); - - // Calculate the texture coordinates. - if (mKeepAspectRatio) { - int renderWidth = 0; - int renderHeight = 0; - float textureAspectRatio = (float) (textureWidth) / textureHeight; - float imageAspectRatio = (float) (mImageWidth) / mImageHeight; - if (textureAspectRatio < imageAspectRatio) { - renderWidth = mImageWidth; - renderHeight = textureHeight * mImageWidth / textureWidth; - } else { - renderWidth = textureWidth * mImageHeight / textureHeight; - renderHeight = mImageHeight; - } - float offsetU = (float) (renderWidth - mImageWidth) / renderWidth / 2; - float offsetV = (float) (renderHeight - mImageHeight) / renderHeight / 2; - - float[] texCoords = - new float[] { - offsetU, offsetV, - offsetU, 1 - offsetV, - 1 - offsetU, offsetV, - 1 - offsetU, 1 - offsetV - }; - - mQuadTexCoord.put(texCoords); - mQuadTexCoord.position(0); - } else { - mQuadTexCoord.put(QUAD_TEXCOORDS); - mQuadTexCoord.position(0); - } - - // Set the texture coordinates. - GLES20.glVertexAttribPointer( - mQuadTexCoordAttrib, TEXCOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadTexCoord); - - // Enable vertex arrays - GLES20.glEnableVertexAttribArray(mQuadPositionAttrib); - GLES20.glEnableVertexAttribArray(mQuadTexCoordAttrib); - - GLES20.glUseProgram(mQuadProgram); - - // Select input texture. - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); - - // Draw a quad with texture. - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - - // Disable vertex arrays - GLES20.glDisableVertexAttribArray(mQuadPositionAttrib); - GLES20.glDisableVertexAttribArray(mQuadTexCoordAttrib); - - // Reset texture binding. - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); + return null; + } + + /** Draws texture to full screen. */ + private void drawTexture(int textureId, int textureWidth, int textureHeight) { + // Disable features that we don't use. + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDisable(GLES20.GL_CULL_FACE); + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + GLES20.glDisable(GLES20.GL_STENCIL_TEST); + GLES20.glDisable(GLES20.GL_BLEND); + GLES20.glDepthMask(false); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + GLES30.glBindVertexArray(0); + + // Clear buffers. + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + + // Set the vertex positions. + GLES20.glVertexAttribPointer( + quadPositionAttrib, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + // Calculate the texture coordinates. + if (keepAspectRatio) { + int renderWidth = 0; + int renderHeight = 0; + float textureAspectRatio = (float) (textureWidth) / textureHeight; + float imageAspectRatio = (float) (imageWidth) / imageHeight; + if (textureAspectRatio < imageAspectRatio) { + renderWidth = imageWidth; + renderHeight = textureHeight * imageWidth / textureWidth; + } else { + renderWidth = textureWidth * imageHeight / textureHeight; + renderHeight = imageHeight; + } + float offsetU = (float) (renderWidth - imageWidth) / renderWidth / 2; + float offsetV = (float) (renderHeight - imageHeight) / renderHeight / 2; + + float[] texCoords = + new float[] { + offsetU, offsetV, offsetU, 1 - offsetV, 1 - offsetU, offsetV, 1 - offsetU, 1 - offsetV + }; + + quadTexCoord.put(texCoords); + quadTexCoord.position(0); + } else { + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); } + + // Set the texture coordinates. + GLES20.glVertexAttribPointer( + quadTexCoordAttrib, TEXCOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadTexCoord); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(quadPositionAttrib); + GLES20.glEnableVertexAttribArray(quadTexCoordAttrib); + + GLES20.glUseProgram(quadProgram); + + // Select input texture. + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + + // Draw a quad with texture. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(quadPositionAttrib); + GLES20.glDisableVertexAttribArray(quadTexCoordAttrib); + + // Reset texture binding. + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); + } } diff --git a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/package-info.java b/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/package-info.java deleted file mode 100644 index 13e78a856..000000000 --- a/samples/computervision/app/src/main/java/com/google/ar/core/examples/java/computervision/utility/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ -/** - * A sample showing how to build a computer vision application using ARCore. - */ -package com.google.ar.core.examples.java.computervision.utility; diff --git a/samples/computervision/app/src/main/res/layout/activity_main.xml b/samples/computervision/app/src/main/res/layout/activity_main.xml index 8f7cb9aa7..5c8e2fa08 100644 --- a/samples/computervision/app/src/main/res/layout/activity_main.xml +++ b/samples/computervision/app/src/main/res/layout/activity_main.xml @@ -19,10 +19,10 @@ android:layout_height="match_parent" tools:context="com.google.ar.core.examples.java.computervision.MainActivity"> - + diff --git a/samples/computervision/app/src/main/res/values/strings.xml b/samples/computervision/app/src/main/res/values/strings.xml index 1a579d0ae..a980031fb 100644 --- a/samples/computervision/app/src/main/res/values/strings.xml +++ b/samples/computervision/app/src/main/res/values/strings.xml @@ -15,5 +15,5 @@ limitations under the License. --> - CV Java + CV Java diff --git a/samples/computervision/app/src/main/res/values/styles.xml b/samples/computervision/app/src/main/res/values/styles.xml index 0ecfbc537..3a71bd32b 100644 --- a/samples/computervision/app/src/main/res/values/styles.xml +++ b/samples/computervision/app/src/main/res/values/styles.xml @@ -15,21 +15,21 @@ --> + + + - - + + diff --git a/samples/computervision/build.gradle b/samples/computervision/build.gradle index 85691a636..be505f078 100644 --- a/samples/computervision/build.gradle +++ b/samples/computervision/build.gradle @@ -17,9 +17,6 @@ allprojects { google() jcenter() mavenLocal() - maven { - url "${project.rootDir}/../../libraries/m2repository" - } } } diff --git a/samples/computervision/gradle/wrapper/gradle-wrapper.jar b/samples/computervision/gradle/wrapper/gradle-wrapper.jar index 12a08711a..7a3265ee9 100644 Binary files a/samples/computervision/gradle/wrapper/gradle-wrapper.jar and b/samples/computervision/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/hello_ar_c/app/build.gradle b/samples/hello_ar_c/app/build.gradle index 06eeeb06c..018d8980a 100644 --- a/samples/hello_ar_c/app/build.gradle +++ b/samples/hello_ar_c/app/build.gradle @@ -29,7 +29,7 @@ android { } } ndk { - abiFilters "arm64-v8a" + abiFilters "arm64-v8a", "x86" } } buildTypes { @@ -47,8 +47,8 @@ android { dependencies { // ARCore library - implementation 'com.google.ar:core:0.91.0' - natives 'com.google.ar:core:0.91.0' + implementation 'com.google.ar:core:1.0.0' + natives 'com.google.ar:core:1.0.0' implementation 'com.android.support:appcompat-v7:27.0.2' implementation 'com.android.support:design:27.0.2' @@ -57,11 +57,20 @@ dependencies { // Extracts the shared libraries from aars in the natives configuration. // This is done so that NDK builds can access these libraries. task extractNativeLibraries() { - configurations.natives.files.each { f -> - copy { - from zipTree(f) - into arcore_libpath - include "jni/**/*" - } - } + doFirst { + configurations.natives.files.each { f -> + copy { + from zipTree(f) + into arcore_libpath + include "jni/**/*" + } + } + } } + +tasks.whenTaskAdded { + task-> if (task.name.contains("external") && !task.name.contains("Clean")) { + task.dependsOn(extractNativeLibraries) + } +} + diff --git a/samples/hello_ar_c/app/src/main/AndroidManifest.xml b/samples/hello_ar_c/app/src/main/AndroidManifest.xml index 5c57f5a8a..242b25653 100644 --- a/samples/hello_ar_c/app/src/main/AndroidManifest.xml +++ b/samples/hello_ar_c/app/src/main/AndroidManifest.xml @@ -15,28 +15,34 @@ limitations under the License. --> + package="com.google.ar.core.examples.c.helloar"> - + + + - + + + - - - - - - - + + + + + + + diff --git a/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.cc b/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.cc index d2ee96e7f..12d3b960b 100644 --- a/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.cc +++ b/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.cc @@ -24,7 +24,11 @@ namespace hello_ar { namespace { +constexpr size_t kMaxNumberOfAndroidsToRender = 20; constexpr int32_t kPlaneColorRgbaSize = 16; + +const glm::vec3 kWhite = {255, 255, 255}; + constexpr std::array kPlaneColorRgba = { 0xFFFFFFFF, 0xF44336FF, 0xE91E63FF, 0x9C27B0FF, 0x673AB7FF, 0x3F51B5FF, 0x2196F3FF, 0x03A9F4FF, 0x00BCD4FF, 0x009688FF, 0x4CAF50FF, 0x8BC34AFF, @@ -38,46 +42,75 @@ inline glm::vec3 GetRandomPlaneColor() { } } // namespace -HelloArApplication::HelloArApplication(AAssetManager* asset_manager, void* env, - void* context) +HelloArApplication::HelloArApplication(AAssetManager* asset_manager) : asset_manager_(asset_manager) { LOGI("OnCreate()"); - - // === ATTENTION! ATTENTION! ATTENTION! === - // This method can and will fail in user-facing situations. Your application - // must handle these cases at least somewhat gracefully. See HelloAR Java - // sample code for reasonable behavior. - CHECK(ArSession_create(env, context, &ar_session_) == AR_SUCCESS); - CHECK(ar_session_); - - ArConfig* ar_config = nullptr; - ArConfig_create(ar_session_, &ar_config); - CHECK(ar_config); - - const ArStatus status = ArSession_checkSupported(ar_session_, ar_config); - CHECK(status == AR_SUCCESS); - - CHECK(ArSession_configure(ar_session_, ar_config) == AR_SUCCESS); - - ArConfig_destroy(ar_config); - - ArFrame_create(ar_session_, &ar_frame_); - CHECK(ar_frame_); } HelloArApplication::~HelloArApplication() { - ArSession_destroy(ar_session_); - ArFrame_destroy(ar_frame_); + if (ar_session_ != nullptr) { + ArSession_destroy(ar_session_); + ArFrame_destroy(ar_frame_); + } } void HelloArApplication::OnPause() { LOGI("OnPause()"); - ArSession_pause(ar_session_); + if (ar_session_ != nullptr) { + ArSession_pause(ar_session_); + } } -void HelloArApplication::OnResume() { +void HelloArApplication::OnResume(void* env, void* context, void* activity) { LOGI("OnResume()"); + if (ar_session_ == nullptr) { + ArInstallStatus install_status; + // If install was not yet requested, that means that we are resuming the + // activity first time because of explicit user interaction (such as + // launching the application) + bool user_requested_install = !install_requested_; + + // === ATTENTION! ATTENTION! ATTENTION! === + // This method can and will fail in user-facing situations. Your + // application must handle these cases at least somewhat gracefully. See + // HelloAR Java sample code for reasonable behavior. + CHECK(ArCoreApk_requestInstall(env, activity, user_requested_install, + &install_status) == AR_SUCCESS); + + switch (install_status) { + case AR_INSTALL_STATUS_INSTALLED: + break; + case AR_INSTALL_STATUS_INSTALL_REQUESTED: + install_requested_ = true; + return; + } + + // === ATTENTION! ATTENTION! ATTENTION! === + // This method can and will fail in user-facing situations. Your + // application must handle these cases at least somewhat gracefully. See + // HelloAR Java sample code for reasonable behavior. + CHECK(ArSession_create(env, context, &ar_session_) == AR_SUCCESS); + CHECK(ar_session_); + + ArConfig* ar_config = nullptr; + ArConfig_create(ar_session_, &ar_config); + CHECK(ar_config); + + const ArStatus status = ArSession_checkSupported(ar_session_, ar_config); + CHECK(status == AR_SUCCESS); + + CHECK(ArSession_configure(ar_session_, ar_config) == AR_SUCCESS); + + ArConfig_destroy(ar_config); + + ArFrame_create(ar_session_, &ar_frame_); + CHECK(ar_frame_); + + ArSession_setDisplayGeometry(ar_session_, display_rotation_, width_, + height_); + } + const ArStatus status = ArSession_resume(ar_session_); CHECK(status == AR_SUCCESS); } @@ -86,8 +119,6 @@ void HelloArApplication::OnSurfaceCreated() { LOGI("OnSurfaceCreated()"); background_renderer_.InitializeGlContent(); - ArSession_setCameraTextureName(ar_session_, - background_renderer_.GetTextureId()); point_cloud_renderer_.InitializeGlContent(); andy_renderer_.InitializeGlContent(asset_manager_, "andy.obj", "andy.png"); plane_renderer_.InitializeGlContent(asset_manager_); @@ -97,7 +128,12 @@ void HelloArApplication::OnDisplayGeometryChanged(int display_rotation, int width, int height) { LOGI("OnSurfaceChanged(%d, %d)", width, height); glViewport(0, 0, width, height); - ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height); + display_rotation_ = display_rotation; + width_ = width; + height_ = height; + if (ar_session_ != nullptr) { + ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height); + } } void HelloArApplication::OnDrawFrame() { @@ -110,6 +146,11 @@ void HelloArApplication::OnDrawFrame() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (ar_session_ == nullptr) return; + + ArSession_setCameraTextureName(ar_session_, + background_renderer_.GetTextureId()); + // Update session to get current frame and render camera background. if (ArSession_update(ar_session_, ar_frame_) != AR_SUCCESS) { LOGE("HelloArApplication::OnDrawFrame ArSession_update error"); @@ -125,10 +166,17 @@ void HelloArApplication::OnDrawFrame() { /*near=*/0.1f, /*far=*/100.f, glm::value_ptr(projection_mat)); + ArTrackingState camera_tracking_state; + ArCamera_getTrackingState(ar_session_, ar_camera, &camera_tracking_state); ArCamera_release(ar_camera); background_renderer_.Draw(ar_session_, ar_frame_); + // If the camera isn't tracking don't bother rendering other objects. + if (camera_tracking_state != AR_TRACKING_STATE_TRACKING) { + return; + } + // Get light estimation value. ArLightEstimate* ar_light_estimate; ArLightEstimateState ar_light_estimate_state; @@ -176,21 +224,49 @@ void HelloArApplication::OnDrawFrame() { ArTrackable* ar_trackable = nullptr; ArTrackableList_acquireItem(ar_session_, plane_list, i, &ar_trackable); ArPlane* ar_plane = ArAsPlane(ar_trackable); + ArTrackingState out_tracking_state; + ArTrackable_getTrackingState(ar_session_, ar_trackable, + &out_tracking_state); + + ArPlane* subsume_plane; + ArPlane_acquireSubsumedBy(ar_session_, ar_plane, &subsume_plane); + if (subsume_plane != nullptr) { + ArTrackable_release(ArAsTrackable(subsume_plane)); + continue; + } - const auto iter = plane_color_map_.find(ar_plane); - glm::vec3 color; - if (iter != plane_color_map_.end()) { - color = iter->second; - } else { - color = GetRandomPlaneColor(); - plane_color_map_.insert({ar_plane, color}); + if (ArTrackingState::AR_TRACKING_STATE_TRACKING != out_tracking_state) { + continue; } - plane_renderer_.Draw(projection_mat, view_mat, ar_session_, ar_plane, - color); + ArTrackingState plane_tracking_state; + ArTrackable_getTrackingState(ar_session_, ArAsTrackable(ar_plane), + &plane_tracking_state); + if (plane_tracking_state == AR_TRACKING_STATE_TRACKING) { + const auto iter = plane_color_map_.find(ar_plane); + glm::vec3 color; + if (iter != plane_color_map_.end()) { + color = iter->second; + + // If this is an already observed trackable release it so it doesn't + // leave an additional reference dangling. + ArTrackable_release(ar_trackable); + } else { + // The first plane is always white. + if (!first_plane_has_been_found_) { + first_plane_has_been_found_ = true; + color = kWhite; + } else { + color = GetRandomPlaneColor(); + } + plane_color_map_.insert({ar_plane, color}); + } - ArTrackable_release(ar_trackable); + plane_renderer_.Draw(projection_mat, view_mat, ar_session_, ar_plane, + color); + } } + ArTrackableList_destroy(plane_list); plane_list = nullptr; @@ -218,39 +294,50 @@ void HelloArApplication::OnTouched(float x, float y) { // The hitTest method sorts the resulting list by distance from the camera, // increasing. The first hit result will usually be the most relevant when - // responding to user input + // responding to user input. + + ArHitResult* ar_hit_result = nullptr; for (int32_t i = 0; i < hit_result_list_size; ++i) { - ArHitResult* ar_hit_result = nullptr; - ArHitResult_create(ar_session_, &ar_hit_result); - ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit_result); + ArHitResult* ar_hit = nullptr; + ArHitResult_create(ar_session_, &ar_hit); + ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit); - if (ar_hit_result == nullptr) { + if (ar_hit == nullptr) { LOGE("HelloArApplication::OnTouched ArHitResultList_getItem error"); return; } - // Only consider planes for this sample app. ArTrackable* ar_trackable = nullptr; - ArHitResult_acquireTrackable(ar_session_, ar_hit_result, &ar_trackable); + ArHitResult_acquireTrackable(ar_session_, ar_hit, &ar_trackable); ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID; ArTrackable_getType(ar_session_, ar_trackable, &ar_trackable_type); - if (ar_trackable_type != AR_TRACKABLE_PLANE) { - ArTrackable_release(ar_trackable); - continue; - } - - ArPose* ar_pose = nullptr; - ArPose_create(ar_session_, nullptr, &ar_pose); - ArHitResult_getHitPose(ar_session_, ar_hit_result, ar_pose); - int32_t in_polygon = 0; - ArPlane* ar_plane = ArAsPlane(ar_trackable); - ArPlane_isPoseInPolygon(ar_session_, ar_plane, ar_pose, &in_polygon); - ArTrackable_release(ar_trackable); - ArPose_destroy(ar_pose); - if (!in_polygon) { - continue; + // Creates an anchor if a plane or an oriented point was hit. + if (AR_TRACKABLE_PLANE == ar_trackable_type) { + ArPose* ar_pose = nullptr; + ArPose_create(ar_session_, nullptr, &ar_pose); + ArHitResult_getHitPose(ar_session_, ar_hit, ar_pose); + int32_t in_polygon = 0; + ArPlane* ar_plane = ArAsPlane(ar_trackable); + ArPlane_isPoseInPolygon(ar_session_, ar_plane, ar_pose, &in_polygon); + ArPose_destroy(ar_pose); + if (!in_polygon) { + continue; + } + + ar_hit_result = ar_hit; + break; + } else if (AR_TRACKABLE_POINT == ar_trackable_type) { + ArPoint* ar_point = ArAsPoint(ar_trackable); + ArPointOrientationMode mode; + ArPoint_getOrientationMode(ar_session_, ar_point, &mode); + if (AR_POINT_ORIENTATION_ESTIMATED_SURFACE_NORMAL == mode) { + ar_hit_result = ar_hit; + break; + } } + } + if (ar_hit_result) { // Note that the application is responsible for releasing the anchor // pointer after using it. Call ArAnchor_release(anchor) to release. ArAnchor* anchor = nullptr; @@ -265,16 +352,21 @@ void HelloArApplication::OnTouched(float x, float y) { ArAnchor_getTrackingState(ar_session_, anchor, &tracking_state); if (tracking_state != AR_TRACKING_STATE_TRACKING) { ArAnchor_release(anchor); - continue; + return; + } + + if (tracked_obj_set_.size() >= kMaxNumberOfAndroidsToRender) { + ArAnchor_release(tracked_obj_set_[0]); + tracked_obj_set_.erase(tracked_obj_set_.begin()); } - tracked_obj_set_.insert(anchor); + tracked_obj_set_.push_back(anchor); ArHitResult_destroy(ar_hit_result); ar_hit_result = nullptr; - } - ArHitResultList_destroy(hit_result_list); - hit_result_list = nullptr; + ArHitResultList_destroy(hit_result_list); + hit_result_list = nullptr; + } } } diff --git a/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.h b/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.h index aa663d789..7bd5badc1 100644 --- a/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.h +++ b/samples/hello_ar_c/app/src/main/cpp/hello_ar_application.h @@ -22,9 +22,9 @@ #include #include #include +#include #include #include -#include #include "arcore_c_api.h" #include "background_renderer.h" @@ -41,14 +41,14 @@ class HelloArApplication { public: // Constructor and deconstructor. HelloArApplication() = default; - HelloArApplication(AAssetManager* asset_manager, void* env, void* context); + HelloArApplication(AAssetManager* asset_manager); ~HelloArApplication(); // OnPause is called on the UI thread from the Activity's onPause method. void OnPause(); // OnResume is called on the UI thread from the Activity's onResume method. - void OnResume(); + void OnResume(void* env, void* context, void* activity); // OnSurfaceCreated is called on the OpenGL thread when GLSurfaceView // is created. @@ -78,14 +78,23 @@ class HelloArApplication { ArSession* ar_session_ = nullptr; ArFrame* ar_frame_ = nullptr; + bool install_requested_ = false; + int width_ = 1; + int height_ = 1; + int display_rotation_ = 0; + AAssetManager* const asset_manager_; // The anchors at which we are drawing android models - std::unordered_set tracked_obj_set_; + std::vector tracked_obj_set_; // Stores the randomly-selected color each plane is drawn with std::unordered_map plane_color_map_; + // The first plane is always rendered in white, if this is true then a plane + // at some point has been found. + bool first_plane_has_been_found_ = false; + PointCloudRenderer point_cloud_renderer_; BackgroundRenderer background_renderer_; PlaneRenderer plane_renderer_; diff --git a/samples/hello_ar_c/app/src/main/cpp/jni_interface.cc b/samples/hello_ar_c/app/src/main/cpp/jni_interface.cc index ea6aaf42d..6e3d1a054 100644 --- a/samples/hello_ar_c/app/src/main/cpp/jni_interface.cc +++ b/samples/hello_ar_c/app/src/main/cpp/jni_interface.cc @@ -46,9 +46,9 @@ jint JNI_OnLoad(JavaVM *vm, void *) { } JNI_METHOD(jlong, createNativeApplication) -(JNIEnv *env, jclass, jobject j_asset_manager, jobject context) { +(JNIEnv *env, jclass, jobject j_asset_manager) { AAssetManager *asset_manager = AAssetManager_fromJava(env, j_asset_manager); - return jptr(new hello_ar::HelloArApplication(asset_manager, env, context)); + return jptr(new hello_ar::HelloArApplication(asset_manager)); } JNI_METHOD(void, destroyNativeApplication) @@ -62,8 +62,9 @@ JNI_METHOD(void, onPause) } JNI_METHOD(void, onResume) -(JNIEnv *, jclass, jlong native_application) { - native(native_application)->OnResume(); +(JNIEnv *env, jclass, jlong native_application, jobject context, + jobject activity) { + native(native_application)->OnResume(env, context, activity); } JNI_METHOD(void, onGlSurfaceCreated) diff --git a/samples/hello_ar_c/app/src/main/cpp/util.cc b/samples/hello_ar_c/app/src/main/cpp/util.cc index 05effba2d..a02c13c78 100644 --- a/samples/hello_ar_c/app/src/main/cpp/util.cc +++ b/samples/hello_ar_c/app/src/main/cpp/util.cc @@ -286,8 +286,8 @@ bool LoadObjFile(AAssetManager* mgr, const std::string& file_name, break; } [[clang::fallthrough]]; - // Intentionally falling to default error case because vertex - // normal face only has two values. + // Intentionally falling to default error case because vertex + // normal face only has two values. default: // Error formatting. LOGE( diff --git a/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/HelloArActivity.java b/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/HelloArActivity.java index 7bf1c61f2..228d280e4 100644 --- a/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/HelloArActivity.java +++ b/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/HelloArActivity.java @@ -119,7 +119,7 @@ public boolean onTouch(View v, MotionEvent event) { mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); JniInterface.assetManager = getAssets(); - mNativeApplication = JniInterface.createNativeApplication(getAssets(), getApplicationContext()); + mNativeApplication = JniInterface.createNativeApplication(getAssets()); mPlaneStatusCheckingHandler = new Handler(); } @@ -134,7 +134,7 @@ protected void onResume() { return; } - JniInterface.onResume(mNativeApplication); + JniInterface.onResume(mNativeApplication, getApplicationContext(), this); mSurfaceView.onResume(); mLoadingMessageSnackbar = diff --git a/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/JniInterface.java b/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/JniInterface.java index 37933cf63..9ad606bb7 100644 --- a/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/JniInterface.java +++ b/samples/hello_ar_c/app/src/main/java/com/google/ar/core/examples/c/helloar/JniInterface.java @@ -1,5 +1,6 @@ package com.google.ar.core.examples.c.helloar; +import android.app.Activity; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; @@ -17,13 +18,13 @@ public class JniInterface { private static final String TAG = "JniInterface"; static AssetManager assetManager; - public static native long createNativeApplication(AssetManager assetManager, Context context); + public static native long createNativeApplication(AssetManager assetManager); public static native void destroyNativeApplication(long nativeApplication); public static native void onPause(long nativeApplication); - public static native void onResume(long nativeApplication); + public static native void onResume(long nativeApplication, Context context, Activity activity); /** Allocate OpenGL resources for rendering. */ public static native void onGlSurfaceCreated(long nativeApplication); diff --git a/samples/hello_ar_c/app/src/main/res/layout/activity_main.xml b/samples/hello_ar_c/app/src/main/res/layout/activity_main.xml index 9b8502d0c..0e3be9bcc 100644 --- a/samples/hello_ar_c/app/src/main/res/layout/activity_main.xml +++ b/samples/hello_ar_c/app/src/main/res/layout/activity_main.xml @@ -19,10 +19,10 @@ android:layout_height="match_parent" tools:context="com.google.ar.core.examples.c.helloar.HelloArActivity"> - + diff --git a/samples/hello_ar_c/app/src/main/res/values/strings.xml b/samples/hello_ar_c/app/src/main/res/values/strings.xml index f14976234..8b7a8ad74 100644 --- a/samples/hello_ar_c/app/src/main/res/values/strings.xml +++ b/samples/hello_ar_c/app/src/main/res/values/strings.xml @@ -15,5 +15,5 @@ limitations under the License. --> - HelloAR C + HelloAR C diff --git a/samples/hello_ar_c/app/src/main/res/values/styles.xml b/samples/hello_ar_c/app/src/main/res/values/styles.xml index 0ecfbc537..3a71bd32b 100644 --- a/samples/hello_ar_c/app/src/main/res/values/styles.xml +++ b/samples/hello_ar_c/app/src/main/res/values/styles.xml @@ -15,21 +15,21 @@ --> + + + - - + + diff --git a/samples/hello_ar_c/build.gradle b/samples/hello_ar_c/build.gradle index 85691a636..be505f078 100644 --- a/samples/hello_ar_c/build.gradle +++ b/samples/hello_ar_c/build.gradle @@ -17,9 +17,6 @@ allprojects { google() jcenter() mavenLocal() - maven { - url "${project.rootDir}/../../libraries/m2repository" - } } } diff --git a/samples/hello_ar_c/gradle/wrapper/gradle-wrapper.jar b/samples/hello_ar_c/gradle/wrapper/gradle-wrapper.jar index 12a08711a..7a3265ee9 100644 Binary files a/samples/hello_ar_c/gradle/wrapper/gradle-wrapper.jar and b/samples/hello_ar_c/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/hello_ar_java/app/build.gradle b/samples/hello_ar_java/app/build.gradle index 59fecf938..5fbde75a2 100644 --- a/samples/hello_ar_java/app/build.gradle +++ b/samples/hello_ar_java/app/build.gradle @@ -22,7 +22,7 @@ android { dependencies { // ARCore library - implementation 'com.google.ar:core:0.91.0' + implementation 'com.google.ar:core:1.0.0' // Obj - a simple Wavefront OBJ file loader // https://github.com/javagl/Obj diff --git a/samples/hello_ar_java/app/src/main/AndroidManifest.xml b/samples/hello_ar_java/app/src/main/AndroidManifest.xml index 31592256f..1446d1d3c 100644 --- a/samples/hello_ar_java/app/src/main/AndroidManifest.xml +++ b/samples/hello_ar_java/app/src/main/AndroidManifest.xml @@ -15,30 +15,36 @@ limitations under the License. --> - + + + - + - - - - - - - + + + + + + + + + diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java index 397045d19..3403f7cf9 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java @@ -28,20 +28,16 @@ public final class CameraPermissionHelper { private static final int CAMERA_PERMISSION_CODE = 0; private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; - /** - * Check to see we have the necessary permissions for this app. - */ + /** Check to see we have the necessary permissions for this app. */ public static boolean hasCameraPermission(Activity activity) { return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED; } - /** - * Check to see we have the necessary permissions for this app, and ask for them if we don't. - */ + /** Check to see we have the necessary permissions for this app, and ask for them if we don't. */ public static void requestCameraPermission(Activity activity) { - ActivityCompat.requestPermissions(activity, new String[]{CAMERA_PERMISSION}, - CAMERA_PERMISSION_CODE); + ActivityCompat.requestPermissions( + activity, new String[] {CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE); } /** Check to see if we need to show the rationale for this permission. */ diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java index c0fdb16d8..3437d8ccb 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java @@ -28,80 +28,78 @@ * events. */ public class DisplayRotationHelper implements DisplayListener { - private boolean mViewportChanged; - private int mViewportWidth; - private int mViewportHeight; - private final Context mContext; - private final Display mDisplay; + private boolean viewportChanged; + private int viewportWidth; + private int viewportHeight; + private final Context context; + private final Display display; - /** - * Constructs the DisplayRotationHelper but does not register the listener yet. - * - * @param context the Android {@link Context}. - */ - public DisplayRotationHelper(Context context) { - mContext = context; - mDisplay = context.getSystemService(WindowManager.class).getDefaultDisplay(); - } + /** + * Constructs the DisplayRotationHelper but does not register the listener yet. + * + * @param context the Android {@link Context}. + */ + public DisplayRotationHelper(Context context) { + this.context = context; + display = context.getSystemService(WindowManager.class).getDefaultDisplay(); + } - /** Registers the display listener. Should be called from {@link Activity#onResume()}. */ - public void onResume() { - mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, null); - } + /** Registers the display listener. Should be called from {@link Activity#onResume()}. */ + public void onResume() { + context.getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } - /** Unregisters the display listener. Should be called from {@link Activity#onPause()}. */ - public void onPause() { - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); - } + /** Unregisters the display listener. Should be called from {@link Activity#onPause()}. */ + public void onPause() { + context.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } - /** - * Records a change in surface dimensions. This will be later used by - * {@link #updateSessionIfNeeded(Session)}. Should be called from - * {@link android.opengl.GLSurfaceView.Renderer - * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. - * - * @param width the updated width of the surface. - * @param height the updated height of the surface. - */ - public void onSurfaceChanged(int width, int height) { - mViewportWidth = width; - mViewportHeight = height; - mViewportChanged = true; - } + /** + * Records a change in surface dimensions. This will be later used by {@link + * #updateSessionIfNeeded(Session)}. Should be called from {@link + * android.opengl.GLSurfaceView.Renderer + * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. + * + * @param width the updated width of the surface. + * @param height the updated height of the surface. + */ + public void onSurfaceChanged(int width, int height) { + viewportWidth = width; + viewportHeight = height; + viewportChanged = true; + } - /** - * Updates the session display geometry if a change was posted either by - * {@link #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system - * callback. This function should be called explicitly before each call to - * {@link Session#update()}. This function will also clear the 'pending update' - * (viewportChanged) flag. - * - * @param session the {@link Session} object to update if display geometry changed. - */ - public void updateSessionIfNeeded(Session session) { - if (mViewportChanged) { - int displayRotation = mDisplay.getRotation(); - session.setDisplayGeometry(displayRotation, mViewportWidth, mViewportHeight); - mViewportChanged = false; - } + /** + * Updates the session display geometry if a change was posted either by {@link + * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This + * function should be called explicitly before each call to {@link Session#update()}. This + * function will also clear the 'pending update' (viewportChanged) flag. + * + * @param session the {@link Session} object to update if display geometry changed. + */ + public void updateSessionIfNeeded(Session session) { + if (viewportChanged) { + int displayRotation = display.getRotation(); + session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight); + viewportChanged = false; } + } - /** - * Returns the current rotation state of android display. - * Same as {@link Display#getRotation()}. - */ - public int getRotation() { - return mDisplay.getRotation(); - } + /** + * Returns the current rotation state of android display. Same as {@link Display#getRotation()}. + */ + public int getRotation() { + return display.getRotation(); + } - @Override - public void onDisplayAdded(int displayId) {} + @Override + public void onDisplayAdded(int displayId) {} - @Override - public void onDisplayRemoved(int displayId) {} + @Override + public void onDisplayRemoved(int displayId) {} - @Override - public void onDisplayChanged(int displayId) { - mViewportChanged = true; - } + @Override + public void onDisplayChanged(int displayId) { + viewportChanged = true; + } } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/HelloArActivity.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/HelloArActivity.java index 9203b118f..edeffd876 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/HelloArActivity.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/HelloArActivity.java @@ -29,15 +29,18 @@ import android.view.WindowManager; import android.widget.Toast; import com.google.ar.core.Anchor; +import com.google.ar.core.ArCoreApk; import com.google.ar.core.Camera; import com.google.ar.core.Config; import com.google.ar.core.Frame; 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.Session; import com.google.ar.core.Trackable; -import com.google.ar.core.Trackable.TrackingState; +import com.google.ar.core.TrackingState; import com.google.ar.core.examples.java.helloar.rendering.BackgroundRenderer; import com.google.ar.core.examples.java.helloar.rendering.ObjectRenderer; import com.google.ar.core.examples.java.helloar.rendering.ObjectRenderer.BlendMode; @@ -46,6 +49,7 @@ import com.google.ar.core.exceptions.UnavailableApkTooOldException; import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException; import com.google.ar.core.exceptions.UnavailableSdkTooOldException; +import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.ArrayBlockingQueue; @@ -58,353 +62,378 @@ * plane to place a 3d model of the Android robot. */ public class HelloArActivity extends AppCompatActivity implements GLSurfaceView.Renderer { - private static final String TAG = HelloArActivity.class.getSimpleName(); - - // Rendering. The Renderers are created here, and initialized when the GL surface is created. - private GLSurfaceView mSurfaceView; - - private Session mSession; - private GestureDetector mGestureDetector; - private Snackbar mMessageSnackbar; - private DisplayRotationHelper mDisplayRotationHelper; - - private final BackgroundRenderer mBackgroundRenderer = new BackgroundRenderer(); - private final ObjectRenderer mVirtualObject = new ObjectRenderer(); - private final ObjectRenderer mVirtualObjectShadow = new ObjectRenderer(); - private final PlaneRenderer mPlaneRenderer = new PlaneRenderer(); - private final PointCloudRenderer mPointCloud = new PointCloudRenderer(); - - // Temporary matrix allocated here to reduce number of allocations for each frame. - private final float[] mAnchorMatrix = new float[16]; - - // Tap handling and UI. - private final ArrayBlockingQueue mQueuedSingleTaps = new ArrayBlockingQueue<>(16); - private final ArrayList mAnchors = new ArrayList<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - mSurfaceView = findViewById(R.id.surfaceview); - mDisplayRotationHelper = new DisplayRotationHelper(/*context=*/ this); - - // Set up tap listener. - mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapUp(MotionEvent e) { + private static final String TAG = HelloArActivity.class.getSimpleName(); + + // Rendering. The Renderers are created here, and initialized when the GL surface is created. + private GLSurfaceView surfaceView; + + private boolean installRequested; + + private Session session; + private GestureDetector gestureDetector; + private Snackbar messageSnackbar; + private DisplayRotationHelper displayRotationHelper; + + private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer(); + private final ObjectRenderer virtualObject = new ObjectRenderer(); + private final ObjectRenderer virtualObjectShadow = new ObjectRenderer(); + private final PlaneRenderer planeRenderer = new PlaneRenderer(); + private final PointCloudRenderer pointCloud = new PointCloudRenderer(); + + // Temporary matrix allocated here to reduce number of allocations for each frame. + private final float[] anchorMatrix = new float[16]; + + // Tap handling and UI. + private final ArrayBlockingQueue queuedSingleTaps = new ArrayBlockingQueue<>(16); + private final ArrayList anchors = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + surfaceView = findViewById(R.id.surfaceview); + displayRotationHelper = new DisplayRotationHelper(/*context=*/ this); + + // Set up tap listener. + gestureDetector = + new GestureDetector( + this, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { onSingleTap(e); return true; - } + } - @Override - public boolean onDown(MotionEvent e) { + @Override + public boolean onDown(MotionEvent e) { return true; - } - }); - - mSurfaceView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return mGestureDetector.onTouchEvent(event); - } + } + }); + + surfaceView.setOnTouchListener( + new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } }); - // Set up renderer. - mSurfaceView.setPreserveEGLContextOnPause(true); - mSurfaceView.setEGLContextClientVersion(2); - mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. - mSurfaceView.setRenderer(this); - mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); - - Exception exception = null; - String message = null; - try { - mSession = new Session(/* context= */ this); - } catch (UnavailableArcoreNotInstalledException e) { - message = "Please install ARCore"; - exception = e; - } catch (UnavailableApkTooOldException e) { - message = "Please update ARCore"; - exception = e; - } catch (UnavailableSdkTooOldException e) { - message = "Please update this app"; - exception = e; - } catch (Exception e) { - message = "This device does not support AR"; - exception = e; - } - - if (message != null) { - showSnackbarMessage(message, true); - Log.e(TAG, "Exception creating session", exception); + // Set up renderer. + surfaceView.setPreserveEGLContextOnPause(true); + surfaceView.setEGLContextClientVersion(2); + surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. + surfaceView.setRenderer(this); + surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + + installRequested = false; + } + + @Override + protected void onResume() { + super.onResume(); + + if (session == null) { + Exception exception = null; + String message = null; + try { + switch (ArCoreApk.getInstance().requestInstall(this, !installRequested)) { + case INSTALL_REQUESTED: + installRequested = true; return; + case INSTALLED: + break; } - // Create default config and check if supported. - Config config = new Config(mSession); - if (!mSession.isSupported(config)) { - showSnackbarMessage("This device does not support AR", true); - } - mSession.configure(config); - } - - @Override - protected void onResume() { - super.onResume(); - // ARCore requires camera permissions to operate. If we did not yet obtain runtime // permission on Android M and above, now is a good time to ask the user for it. - if (CameraPermissionHelper.hasCameraPermission(this)) { - if (mSession != null) { - showLoadingMessage(); - // Note that order matters - see the note in onPause(), the reverse applies here. - mSession.resume(); - } - mSurfaceView.onResume(); - mDisplayRotationHelper.onResume(); - } else { - CameraPermissionHelper.requestCameraPermission(this); + if (!CameraPermissionHelper.hasCameraPermission(this)) { + CameraPermissionHelper.requestCameraPermission(this); + return; } - } - @Override - public void onPause() { - super.onPause(); - // Note that the order matters - GLSurfaceView is paused first so that it does not try - // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may - // still call mSession.update() and get a SessionPausedException. - mDisplayRotationHelper.onPause(); - mSurfaceView.onPause(); - if (mSession != null) { - mSession.pause(); - } + session = new Session(/* context= */ this); + } catch (UnavailableArcoreNotInstalledException + | UnavailableUserDeclinedInstallationException e) { + message = "Please install ARCore"; + exception = e; + } catch (UnavailableApkTooOldException e) { + message = "Please update ARCore"; + exception = e; + } catch (UnavailableSdkTooOldException e) { + message = "Please update this app"; + exception = e; + } catch (Exception e) { + message = "This device does not support AR"; + exception = e; + } + + if (message != null) { + showSnackbarMessage(message, true); + Log.e(TAG, "Exception creating session", exception); + return; + } + + // Create default config and check if supported. + Config config = new Config(session); + if (!session.isSupported(config)) { + showSnackbarMessage("This device does not support AR", true); + } + session.configure(config); } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { - if (!CameraPermissionHelper.hasCameraPermission(this)) { - Toast.makeText(this, - "Camera permission is needed to run this application", Toast.LENGTH_LONG).show(); - if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) { - // Permission denied with checking "Do not ask again". - CameraPermissionHelper.launchPermissionSettings(this); - } - finish(); - } + showLoadingMessage(); + // Note that order matters - see the note in onPause(), the reverse applies here. + session.resume(); + surfaceView.onResume(); + displayRotationHelper.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + if (session != null) { + // Note that the order matters - GLSurfaceView is paused first so that it does not try + // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may + // still call session.update() and get a SessionPausedException. + displayRotationHelper.onPause(); + surfaceView.onPause(); + session.pause(); } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - // Standard Android full-screen functionality. - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { + if (!CameraPermissionHelper.hasCameraPermission(this)) { + Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG) + .show(); + if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) { + // Permission denied with checking "Do not ask again". + CameraPermissionHelper.launchPermissionSettings(this); + } + finish(); } - - private void onSingleTap(MotionEvent e) { - // Queue tap if there is space. Tap is lost if queue is full. - mQueuedSingleTaps.offer(e); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + // Standard Android full-screen functionality. + getWindow() + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - - // Create the texture and pass it to ARCore session to be filled during update(). - mBackgroundRenderer.createOnGlThread(/*context=*/ this); - if (mSession != null) { - mSession.setCameraTextureName(mBackgroundRenderer.getTextureId()); - } - - // Prepare the other rendering objects. - try { - mVirtualObject.createOnGlThread(/*context=*/this, "andy.obj", "andy.png"); - mVirtualObject.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f); - - mVirtualObjectShadow.createOnGlThread(/*context=*/this, - "andy_shadow.obj", "andy_shadow.png"); - mVirtualObjectShadow.setBlendMode(BlendMode.Shadow); - mVirtualObjectShadow.setMaterialProperties(1.0f, 0.0f, 0.0f, 1.0f); - } catch (IOException e) { - Log.e(TAG, "Failed to read obj file"); - } - try { - mPlaneRenderer.createOnGlThread(/*context=*/this, "trigrid.png"); - } catch (IOException e) { - Log.e(TAG, "Failed to read plane texture"); - } - mPointCloud.createOnGlThread(/*context=*/this); + } + + private void onSingleTap(MotionEvent e) { + // Queue tap if there is space. Tap is lost if queue is full. + queuedSingleTaps.offer(e); + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + + // Create the texture and pass it to ARCore session to be filled during update(). + backgroundRenderer.createOnGlThread(/*context=*/ this); + + // Prepare the other rendering objects. + try { + virtualObject.createOnGlThread(/*context=*/ this, "andy.obj", "andy.png"); + virtualObject.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f); + + virtualObjectShadow.createOnGlThread(/*context=*/ this, "andy_shadow.obj", "andy_shadow.png"); + virtualObjectShadow.setBlendMode(BlendMode.Shadow); + virtualObjectShadow.setMaterialProperties(1.0f, 0.0f, 0.0f, 1.0f); + } catch (IOException e) { + Log.e(TAG, "Failed to read obj file"); } - - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - mDisplayRotationHelper.onSurfaceChanged(width, height); - GLES20.glViewport(0, 0, width, height); + try { + planeRenderer.createOnGlThread(/*context=*/ this, "trigrid.png"); + } catch (IOException e) { + Log.e(TAG, "Failed to read plane texture"); } - - @Override - public void onDrawFrame(GL10 gl) { - // Clear screen to notify driver it should not load any pixels from previous frame. - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - - if (mSession == null) { - return; - } - // Notify ARCore session that the view size changed so that the perspective matrix and - // the video background can be properly adjusted. - mDisplayRotationHelper.updateSessionIfNeeded(mSession); - - try { - // Obtain the current frame from ARSession. When the configuration is set to - // UpdateMode.BLOCKING (it is by default), this will throttle the rendering to the - // camera framerate. - Frame frame = mSession.update(); - Camera camera = frame.getCamera(); - - // Handle taps. Handling only one tap per frame, as taps are usually low frequency - // compared to frame rate. - MotionEvent tap = mQueuedSingleTaps.poll(); - if (tap != null && camera.getTrackingState() == TrackingState.TRACKING) { - for (HitResult hit : frame.hitTest(tap)) { - // Check if any plane was hit, and if it was hit inside the plane polygon - Trackable trackable = hit.getTrackable(); - if (trackable instanceof Plane - && ((Plane) trackable).isPoseInPolygon(hit.getHitPose())) { - // Cap the number of objects created. This avoids overloading both the - // rendering system and ARCore. - if (mAnchors.size() >= 20) { - mAnchors.get(0).detach(); - mAnchors.remove(0); - } - // Adding an Anchor tells ARCore that it should track this position in - // space. This anchor is created on the Plane to place the 3d model - // in the correct position relative both to the world and to the plane. - mAnchors.add(hit.createAnchor()); - - // Hits are sorted by depth. Consider only closest hit on a plane. - break; - } - } - } - - // Draw background. - mBackgroundRenderer.draw(frame); - - // If not tracking, don't draw 3d objects. - if (camera.getTrackingState() == TrackingState.PAUSED) { - return; - } - - // Get projection matrix. - float[] projmtx = new float[16]; - camera.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f); - - // Get camera matrix and draw. - float[] viewmtx = new float[16]; - camera.getViewMatrix(viewmtx, 0); - - // Compute lighting from average intensity of the image. - final float lightIntensity = frame.getLightEstimate().getPixelIntensity(); - - // Visualize tracked points. - PointCloud pointCloud = frame.acquirePointCloud(); - mPointCloud.update(pointCloud); - mPointCloud.draw(viewmtx, projmtx); - - // Application is responsible for releasing the point cloud resources after - // using it. - pointCloud.release(); - - // Check if we detected at least one plane. If so, hide the loading message. - if (mMessageSnackbar != null) { - for (Plane plane : mSession.getAllTrackables(Plane.class)) { - if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING - && plane.getTrackingState() == TrackingState.TRACKING) { - hideLoadingMessage(); - break; - } - } + pointCloud.createOnGlThread(/*context=*/ this); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + displayRotationHelper.onSurfaceChanged(width, height); + GLES20.glViewport(0, 0, width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + // Clear screen to notify driver it should not load any pixels from previous frame. + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + + if (session == null) { + return; + } + // Notify ARCore session that the view size changed so that the perspective matrix and + // the video background can be properly adjusted. + displayRotationHelper.updateSessionIfNeeded(session); + + try { + session.setCameraTextureName(backgroundRenderer.getTextureId()); + + // Obtain the current frame from ARSession. When the configuration is set to + // UpdateMode.BLOCKING (it is by default), this will throttle the rendering to the + // camera framerate. + Frame frame = session.update(); + Camera camera = frame.getCamera(); + + // Handle taps. Handling only one tap per frame, as taps are usually low frequency + // compared to frame rate. + + MotionEvent tap = queuedSingleTaps.poll(); + if (tap != null && camera.getTrackingState() == TrackingState.TRACKING) { + for (HitResult hit : frame.hitTest(tap)) { + // Check if any plane was hit, and if it was hit inside the plane polygon + Trackable trackable = hit.getTrackable(); + // Creates an anchor if a plane or an oriented point was hit. + if ((trackable instanceof Plane && ((Plane) trackable).isPoseInPolygon(hit.getHitPose())) + || (trackable instanceof Point + && ((Point) trackable).getOrientationMode() + == OrientationMode.ESTIMATED_SURFACE_NORMAL)) { + // Hits are sorted by depth. Consider only closest hit on a plane or oriented point. + // Cap the number of objects created. This avoids overloading both the + // rendering system and ARCore. + if (anchors.size() >= 20) { + anchors.get(0).detach(); + anchors.remove(0); } + // Adding an Anchor tells ARCore that it should track this position in + // space. This anchor is created on the Plane to place the 3D model + // in the correct position relative both to the world and to the plane. + anchors.add(hit.createAnchor()); + break; + } + } + } + + // Draw background. + backgroundRenderer.draw(frame); + + // If not tracking, don't draw 3d objects. + if (camera.getTrackingState() == TrackingState.PAUSED) { + return; + } + + // Get projection matrix. + float[] projmtx = new float[16]; + camera.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f); + + // Get camera matrix and draw. + float[] viewmtx = new float[16]; + camera.getViewMatrix(viewmtx, 0); + + // Compute lighting from average intensity of the image. + final float lightIntensity = frame.getLightEstimate().getPixelIntensity(); + + // Visualize tracked points. + PointCloud pointCloud = frame.acquirePointCloud(); + this.pointCloud.update(pointCloud); + this.pointCloud.draw(viewmtx, projmtx); + + // Application is responsible for releasing the point cloud resources after + // using it. + pointCloud.release(); + + // Check if we detected at least one plane. If so, hide the loading message. + if (messageSnackbar != null) { + for (Plane plane : session.getAllTrackables(Plane.class)) { + if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING + && plane.getTrackingState() == TrackingState.TRACKING) { + hideLoadingMessage(); + break; + } + } + } - // Visualize planes. - mPlaneRenderer.drawPlanes( - mSession.getAllTrackables(Plane.class), camera.getDisplayOrientedPose(), projmtx); - - // Visualize anchors created by touch. - float scaleFactor = 1.0f; - for (Anchor anchor : mAnchors) { - if (anchor.getTrackingState() != TrackingState.TRACKING) { - continue; - } - // Get the current pose of an Anchor in world space. The Anchor pose is updated - // during calls to session.update() as ARCore refines its estimate of the world. - anchor.getPose().toMatrix(mAnchorMatrix, 0); - - // Update and draw the model and its shadow. - mVirtualObject.updateModelMatrix(mAnchorMatrix, scaleFactor); - mVirtualObjectShadow.updateModelMatrix(mAnchorMatrix, scaleFactor); - mVirtualObject.draw(viewmtx, projmtx, lightIntensity); - mVirtualObjectShadow.draw(viewmtx, projmtx, lightIntensity); - } + // Visualize planes. + planeRenderer.drawPlanes( + session.getAllTrackables(Plane.class), camera.getDisplayOrientedPose(), projmtx); - } catch (Throwable t) { - // Avoid crashing the application due to unhandled exceptions. - Log.e(TAG, "Exception on the OpenGL thread", t); + // Visualize anchors created by touch. + float scaleFactor = 1.0f; + for (Anchor anchor : anchors) { + if (anchor.getTrackingState() != TrackingState.TRACKING) { + continue; } + // Get the current pose of an Anchor in world space. The Anchor pose is updated + // during calls to session.update() as ARCore refines its estimate of the world. + anchor.getPose().toMatrix(anchorMatrix, 0); + + // Update and draw the model and its shadow. + virtualObject.updateModelMatrix(anchorMatrix, scaleFactor); + virtualObjectShadow.updateModelMatrix(anchorMatrix, scaleFactor); + virtualObject.draw(viewmtx, projmtx, lightIntensity); + virtualObjectShadow.draw(viewmtx, projmtx, lightIntensity); + } + + } catch (Throwable t) { + // Avoid crashing the application due to unhandled exceptions. + Log.e(TAG, "Exception on the OpenGL thread", t); } + } - private void showSnackbarMessage(String message, boolean finishOnDismiss) { - mMessageSnackbar = Snackbar.make( + private void showSnackbarMessage(String message, boolean finishOnDismiss) { + messageSnackbar = + Snackbar.make( HelloArActivity.this.findViewById(android.R.id.content), - message, Snackbar.LENGTH_INDEFINITE); - mMessageSnackbar.getView().setBackgroundColor(0xbf323232); - if (finishOnDismiss) { - mMessageSnackbar.setAction( - "Dismiss", - new View.OnClickListener() { - @Override - public void onClick(View v) { - mMessageSnackbar.dismiss(); - } - }); - mMessageSnackbar.addCallback( - new BaseTransientBottomBar.BaseCallback() { - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - super.onDismissed(transientBottomBar, event); - finish(); - } - }); - } - mMessageSnackbar.show(); - } - - private void showLoadingMessage() { - runOnUiThread(new Runnable() { + message, + Snackbar.LENGTH_INDEFINITE); + messageSnackbar.getView().setBackgroundColor(0xbf323232); + if (finishOnDismiss) { + messageSnackbar.setAction( + "Dismiss", + new View.OnClickListener() { @Override - public void run() { - showSnackbarMessage("Searching for surfaces...", false); + public void onClick(View v) { + messageSnackbar.dismiss(); } - }); - } - - private void hideLoadingMessage() { - runOnUiThread(new Runnable() { + }); + messageSnackbar.addCallback( + new BaseTransientBottomBar.BaseCallback() { @Override - public void run() { - if (mMessageSnackbar != null) { - mMessageSnackbar.dismiss(); - } - mMessageSnackbar = null; + public void onDismissed(Snackbar transientBottomBar, int event) { + super.onDismissed(transientBottomBar, event); + finish(); } - }); + }); } + messageSnackbar.show(); + } + + private void showLoadingMessage() { + runOnUiThread( + new Runnable() { + @Override + public void run() { + showSnackbarMessage("Searching for surfaces...", false); + } + }); + } + + private void hideLoadingMessage() { + runOnUiThread( + new Runnable() { + @Override + public void run() { + if (messageSnackbar != null) { + messageSnackbar.dismiss(); + } + messageSnackbar = null; + } + }); + } } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/package-info.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/package-info.java deleted file mode 100644 index 71ee518a3..000000000 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ -/** - * A sample showing how to build an Augmented Reality app using ARCore. - */ -package com.google.ar.core.examples.java.helloar; diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java index dfae0e29a..0fab1be1a 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java @@ -28,155 +28,159 @@ import javax.microedition.khronos.opengles.GL10; /** - * This class renders the AR background from camera feed. It creates and hosts the texture - * given to ARCore to be filled with the camera image. + * This class renders the AR background from camera feed. It creates and hosts the texture given to + * ARCore to be filled with the camera image. */ public class BackgroundRenderer { - private static final String TAG = BackgroundRenderer.class.getSimpleName(); - - private static final int COORDS_PER_VERTEX = 3; - private static final int TEXCOORDS_PER_VERTEX = 2; - private static final int FLOAT_SIZE = 4; - - private FloatBuffer mQuadVertices; - private FloatBuffer mQuadTexCoord; - private FloatBuffer mQuadTexCoordTransformed; - - private int mQuadProgram; - - private int mQuadPositionParam; - private int mQuadTexCoordParam; - private int mTextureId = -1; - - public BackgroundRenderer() { - } - - public int getTextureId() { - return mTextureId; - } - - /** - * Allocates and initializes OpenGL resources needed by the background renderer. Must be - * called on the OpenGL thread, typically in - * {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. - * - * @param context Needed to access shader source. - */ - public void createOnGlThread(Context context) { - // Generate the background texture. - int[] textures = new int[1]; - GLES20.glGenTextures(1, textures, 0); - mTextureId = textures[0]; - int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; - GLES20.glBindTexture(textureTarget, mTextureId); - GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); - GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); - - int numVertices = 4; - if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { - throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); - } - - ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); - bbVertices.order(ByteOrder.nativeOrder()); - mQuadVertices = bbVertices.asFloatBuffer(); - mQuadVertices.put(QUAD_COORDS); - mQuadVertices.position(0); - - ByteBuffer bbTexCoords = ByteBuffer.allocateDirect( - numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); - bbTexCoords.order(ByteOrder.nativeOrder()); - mQuadTexCoord = bbTexCoords.asFloatBuffer(); - mQuadTexCoord.put(QUAD_TEXCOORDS); - mQuadTexCoord.position(0); - - ByteBuffer bbTexCoordsTransformed = ByteBuffer.allocateDirect( - numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); - bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); - mQuadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); - - int vertexShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex); - int fragmentShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes); - - mQuadProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(mQuadProgram, vertexShader); - GLES20.glAttachShader(mQuadProgram, fragmentShader); - GLES20.glLinkProgram(mQuadProgram); - GLES20.glUseProgram(mQuadProgram); - - ShaderUtil.checkGLError(TAG, "Program creation"); - - mQuadPositionParam = GLES20.glGetAttribLocation(mQuadProgram, "a_Position"); - mQuadTexCoordParam = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord"); - - ShaderUtil.checkGLError(TAG, "Program parameters"); + private static final String TAG = BackgroundRenderer.class.getSimpleName(); + + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private FloatBuffer quadTexCoordTransformed; + + private int quadProgram; + + private int quadPositionParam; + private int quadTexCoordParam; + private int textureId = -1; + + public BackgroundRenderer() {} + + public int getTextureId() { + return textureId; + } + + /** + * Allocates and initializes OpenGL resources needed by the background renderer. Must be called on + * the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, + * EGLConfig)}. + * + * @param context Needed to access shader source. + */ + public void createOnGlThread(Context context) { + // Generate the background texture. + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + textureId = textures[0]; + int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; + GLES20.glBindTexture(textureTarget, textureId); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); } - /** - * Draws the AR background image. The image will be drawn such that virtual content rendered - * with the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} - * and {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will - * accurately follow static physical objects. - * This must be called before drawing virtual content. - * - * @param frame The last {@code Frame} returned by {@link Session#update()}. - */ - public void draw(Frame frame) { - // If display rotation changed (also includes view size change), we need to re-query the uv - // coordinates for the screen rect, as they may have changed as well. - if (frame.hasDisplayGeometryChanged()) { - frame.transformDisplayUvCoords(mQuadTexCoord, mQuadTexCoordTransformed); - } - - // No need to test or write depth, the screen quad has arbitrary depth, and is expected - // to be drawn first. - GLES20.glDisable(GLES20.GL_DEPTH_TEST); - GLES20.glDepthMask(false); - - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId); - - GLES20.glUseProgram(mQuadProgram); - - // Set the vertex positions. - GLES20.glVertexAttribPointer( - mQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices); - - // Set the texture coordinates. - GLES20.glVertexAttribPointer(mQuadTexCoordParam, TEXCOORDS_PER_VERTEX, - GLES20.GL_FLOAT, false, 0, mQuadTexCoordTransformed); - - // Enable vertex arrays - GLES20.glEnableVertexAttribArray(mQuadPositionParam); - GLES20.glEnableVertexAttribArray(mQuadTexCoordParam); - - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - - // Disable vertex arrays - GLES20.glDisableVertexAttribArray(mQuadPositionParam); - GLES20.glDisableVertexAttribArray(mQuadTexCoordParam); - - // Restore the depth state for further drawing. - GLES20.glDepthMask(true); - GLES20.glEnable(GLES20.GL_DEPTH_TEST); - - ShaderUtil.checkGLError(TAG, "Draw"); + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + ByteBuffer bbTexCoordsTransformed = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); + quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex); + int fragmentShader = + ShaderUtil.loadGLShader( + TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + } + + /** + * Draws the AR background image. The image will be drawn such that virtual content rendered with + * the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and + * {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will + * accurately follow static physical objects. This must be called before drawing virtual + * content. + * + * @param frame The last {@code Frame} returned by {@link Session#update()}. + */ + public void draw(Frame frame) { + // If display rotation changed (also includes view size change), we need to re-query the uv + // coordinates for the screen rect, as they may have changed as well. + if (frame.hasDisplayGeometryChanged()) { + frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed); } - private static final float[] QUAD_COORDS = new float[]{ - -1.0f, -1.0f, 0.0f, - -1.0f, +1.0f, 0.0f, - +1.0f, -1.0f, 0.0f, - +1.0f, +1.0f, 0.0f, - }; - - private static final float[] QUAD_TEXCOORDS = new float[]{ - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 1.0f, - 1.0f, 0.0f, - }; + // No need to test or write depth, the screen quad has arbitrary depth, and is expected + // to be drawn first. + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthMask(false); + + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + + GLES20.glUseProgram(quadProgram); + + // Set the vertex positions. + GLES20.glVertexAttribPointer( + quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + // Set the texture coordinates. + GLES20.glVertexAttribPointer( + quadTexCoordParam, + TEXCOORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + 0, + quadTexCoordTransformed); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(quadPositionParam); + GLES20.glEnableVertexAttribArray(quadTexCoordParam); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(quadPositionParam); + GLES20.glDisableVertexAttribArray(quadTexCoordParam); + + // Restore the depth state for further drawing. + GLES20.glDepthMask(true); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + + ShaderUtil.checkGLError(TAG, "Draw"); + } + + private static final float[] QUAD_COORDS = + new float[] { + -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[] { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java index 3117588f6..a4b1288b4 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java @@ -33,335 +33,332 @@ import java.nio.IntBuffer; import java.nio.ShortBuffer; -/** - * Renders an object loaded from an OBJ file in OpenGL. - */ +/** Renders an object loaded from an OBJ file in OpenGL. */ public class ObjectRenderer { - private static final String TAG = ObjectRenderer.class.getSimpleName(); - - /** - * Blend mode. - * - * @see #setBlendMode(BlendMode) - */ - public enum BlendMode { - /** Multiplies the destination color by the source alpha. */ - Shadow, - /** Normal alpha blending. */ - Grid + private static final String TAG = ObjectRenderer.class.getSimpleName(); + + /** + * Blend mode. + * + * @see #setBlendMode(BlendMode) + */ + public enum BlendMode { + /** Multiplies the destination color by the source alpha. */ + Shadow, + /** Normal alpha blending. */ + Grid + } + + private static final int COORDS_PER_VERTEX = 3; + + // Note: the last component must be zero to avoid applying the translational part of the matrix. + private static final float[] LIGHT_DIRECTION = new float[] {0.250f, 0.866f, 0.433f, 0.0f}; + private final float[] viewLightDirection = new float[4]; + + // Object vertex buffer variables. + private int vertexBufferId; + private int verticesBaseAddress; + private int texCoordsBaseAddress; + private int normalsBaseAddress; + private int indexBufferId; + private int indexCount; + + private int program; + private final int[] textures = new int[1]; + + // Shader location: model view projection matrix. + private int modelViewUniform; + private int modelViewProjectionUniform; + + // Shader location: object attributes. + private int positionAttribute; + private int normalAttribute; + private int texCoordAttribute; + + // Shader location: texture sampler. + private int textureUniform; + + // Shader location: environment properties. + private int lightingParametersUniform; + + // Shader location: material properties. + private int materialParametersUniform; + + private BlendMode blendMode = null; + + // Temporary matrices allocated here to reduce number of allocations for each frame. + private final float[] modelMatrix = new float[16]; + private final float[] modelViewMatrix = new float[16]; + private final float[] modelViewProjectionMatrix = new float[16]; + + // Set some default material properties to use for lighting. + private float ambient = 0.3f; + private float diffuse = 1.0f; + private float specular = 1.0f; + private float specularPower = 6.0f; + + public ObjectRenderer() {} + + /** + * Creates and initializes OpenGL resources needed for rendering the model. + * + * @param context Context for loading the shader and below-named model and texture assets. + * @param objAssetName Name of the OBJ file containing the model geometry. + * @param diffuseTextureAssetName Name of the PNG file containing the diffuse texture map. + */ + public void createOnGlThread(Context context, String objAssetName, String diffuseTextureAssetName) + throws IOException { + // Read the texture. + Bitmap textureBitmap = + BitmapFactory.decodeStream(context.getAssets().open(diffuseTextureAssetName)); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glGenTextures(textures.length, textures, 0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0); + GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + textureBitmap.recycle(); + + ShaderUtil.checkGLError(TAG, "Texture loading"); + + // Read the obj file. + InputStream objInputStream = context.getAssets().open(objAssetName); + Obj obj = ObjReader.read(objInputStream); + + // Prepare the Obj so that its structure is suitable for + // rendering with OpenGL: + // 1. Triangulate it + // 2. Make sure that texture coordinates are not ambiguous + // 3. Make sure that normals are not ambiguous + // 4. Convert it to single-indexed data + obj = ObjUtils.convertToRenderable(obj); + + // OpenGL does not use Java arrays. ByteBuffers are used instead to provide data in a format + // that OpenGL understands. + + // Obtain the data from the OBJ, as direct buffers: + IntBuffer wideIndices = ObjData.getFaceVertexIndices(obj, 3); + FloatBuffer vertices = ObjData.getVertices(obj); + FloatBuffer texCoords = ObjData.getTexCoords(obj, 2); + FloatBuffer normals = ObjData.getNormals(obj); + + // Convert int indices to shorts for GL ES 2.0 compatibility + ShortBuffer indices = + ByteBuffer.allocateDirect(2 * wideIndices.limit()) + .order(ByteOrder.nativeOrder()) + .asShortBuffer(); + while (wideIndices.hasRemaining()) { + indices.put((short) wideIndices.get()); } - - private static final int COORDS_PER_VERTEX = 3; - - // Note: the last component must be zero to avoid applying the translational part of the matrix. - private static final float[] LIGHT_DIRECTION = new float[] { 0.250f, 0.866f, 0.433f, 0.0f }; - private float[] mViewLightDirection = new float[4]; - - // Object vertex buffer variables. - private int mVertexBufferId; - private int mVerticesBaseAddress; - private int mTexCoordsBaseAddress; - private int mNormalsBaseAddress; - private int mIndexBufferId; - private int mIndexCount; - - private int mProgram; - private int[] mTextures = new int[1]; - - // Shader location: model view projection matrix. - private int mModelViewUniform; - private int mModelViewProjectionUniform; - - // Shader location: object attributes. - private int mPositionAttribute; - private int mNormalAttribute; - private int mTexCoordAttribute; - - // Shader location: texture sampler. - private int mTextureUniform; - - // Shader location: environment properties. - private int mLightingParametersUniform; - - // Shader location: material properties. - private int mMaterialParametersUniform; - - private BlendMode mBlendMode = null; - - // Temporary matrices allocated here to reduce number of allocations for each frame. - private float[] mModelMatrix = new float[16]; - private float[] mModelViewMatrix = new float[16]; - private float[] mModelViewProjectionMatrix = new float[16]; - - // Set some default material properties to use for lighting. - private float mAmbient = 0.3f; - private float mDiffuse = 1.0f; - private float mSpecular = 1.0f; - private float mSpecularPower = 6.0f; - - public ObjectRenderer() { + indices.rewind(); + + int[] buffers = new int[2]; + GLES20.glGenBuffers(2, buffers, 0); + vertexBufferId = buffers[0]; + indexBufferId = buffers[1]; + + // Load vertex buffer + verticesBaseAddress = 0; + texCoordsBaseAddress = verticesBaseAddress + 4 * vertices.limit(); + normalsBaseAddress = texCoordsBaseAddress + 4 * texCoords.limit(); + final int totalBytes = normalsBaseAddress + 4 * normals.limit(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId); + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, totalBytes, null, GLES20.GL_STATIC_DRAW); + GLES20.glBufferSubData( + GLES20.GL_ARRAY_BUFFER, verticesBaseAddress, 4 * vertices.limit(), vertices); + GLES20.glBufferSubData( + GLES20.GL_ARRAY_BUFFER, texCoordsBaseAddress, 4 * texCoords.limit(), texCoords); + GLES20.glBufferSubData( + GLES20.GL_ARRAY_BUFFER, normalsBaseAddress, 4 * normals.limit(), normals); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + + // Load index buffer + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBufferId); + indexCount = indices.limit(); + GLES20.glBufferData( + GLES20.GL_ELEMENT_ARRAY_BUFFER, 2 * indexCount, indices, GLES20.GL_STATIC_DRAW); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + + ShaderUtil.checkGLError(TAG, "OBJ buffer load"); + + final int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.object_vertex); + final int fragmentShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.object_fragment); + + program = GLES20.glCreateProgram(); + GLES20.glAttachShader(program, vertexShader); + GLES20.glAttachShader(program, fragmentShader); + GLES20.glLinkProgram(program); + GLES20.glUseProgram(program); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + modelViewUniform = GLES20.glGetUniformLocation(program, "u_ModelView"); + modelViewProjectionUniform = GLES20.glGetUniformLocation(program, "u_ModelViewProjection"); + + positionAttribute = GLES20.glGetAttribLocation(program, "a_Position"); + normalAttribute = GLES20.glGetAttribLocation(program, "a_Normal"); + texCoordAttribute = GLES20.glGetAttribLocation(program, "a_TexCoord"); + + textureUniform = GLES20.glGetUniformLocation(program, "u_Texture"); + + lightingParametersUniform = GLES20.glGetUniformLocation(program, "u_LightingParameters"); + materialParametersUniform = GLES20.glGetUniformLocation(program, "u_MaterialParameters"); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + + Matrix.setIdentityM(modelMatrix, 0); + } + + /** + * Selects the blending mode for rendering. + * + * @param blendMode The blending mode. Null indicates no blending (opaque rendering). + */ + public void setBlendMode(BlendMode blendMode) { + this.blendMode = blendMode; + } + + /** + * Updates the object model matrix and applies scaling. + * + * @param modelMatrix A 4x4 model-to-world transformation matrix, stored in column-major order. + * @param scaleFactor A separate scaling factor to apply before the {@code modelMatrix}. + * @see android.opengl.Matrix + */ + public void updateModelMatrix(float[] modelMatrix, float scaleFactor) { + float[] scaleMatrix = new float[16]; + Matrix.setIdentityM(scaleMatrix, 0); + scaleMatrix[0] = scaleFactor; + scaleMatrix[5] = scaleFactor; + scaleMatrix[10] = scaleFactor; + Matrix.multiplyMM(this.modelMatrix, 0, modelMatrix, 0, scaleMatrix, 0); + } + + /** + * Sets the surface characteristics of the rendered model. + * + * @param ambient Intensity of non-directional surface illumination. + * @param diffuse Diffuse (matte) surface reflectivity. + * @param specular Specular (shiny) surface reflectivity. + * @param specularPower Surface shininess. Larger values result in a smaller, sharper specular + * highlight. + */ + public void setMaterialProperties( + float ambient, float diffuse, float specular, float specularPower) { + this.ambient = ambient; + this.diffuse = diffuse; + this.specular = specular; + this.specularPower = specularPower; + } + + /** + * Draws the model. + * + * @param cameraView A 4x4 view matrix, in column-major order. + * @param cameraPerspective A 4x4 projection matrix, in column-major order. + * @param lightIntensity Illumination intensity. Combined with diffuse and specular material + * properties. + * @see #setBlendMode(BlendMode) + * @see #updateModelMatrix(float[], float) + * @see #setMaterialProperties(float, float, float, float) + * @see android.opengl.Matrix + */ + public void draw(float[] cameraView, float[] cameraPerspective, float lightIntensity) { + + ShaderUtil.checkGLError(TAG, "Before draw"); + + // Build the ModelView and ModelViewProjection matrices + // for calculating object position and light. + Matrix.multiplyMM(modelViewMatrix, 0, cameraView, 0, modelMatrix, 0); + Matrix.multiplyMM(modelViewProjectionMatrix, 0, cameraPerspective, 0, modelViewMatrix, 0); + + GLES20.glUseProgram(program); + + // Set the lighting environment properties. + Matrix.multiplyMV(viewLightDirection, 0, modelViewMatrix, 0, LIGHT_DIRECTION, 0); + normalizeVec3(viewLightDirection); + GLES20.glUniform4f( + lightingParametersUniform, + viewLightDirection[0], + viewLightDirection[1], + viewLightDirection[2], + lightIntensity); + + // Set the object material properties. + GLES20.glUniform4f(materialParametersUniform, ambient, diffuse, specular, specularPower); + + // Attach the object texture. + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + GLES20.glUniform1i(textureUniform, 0); + + // Set the vertex attributes. + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId); + + GLES20.glVertexAttribPointer( + positionAttribute, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, verticesBaseAddress); + GLES20.glVertexAttribPointer(normalAttribute, 3, GLES20.GL_FLOAT, false, 0, normalsBaseAddress); + GLES20.glVertexAttribPointer( + texCoordAttribute, 2, GLES20.GL_FLOAT, false, 0, texCoordsBaseAddress); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + + // Set the ModelViewProjection matrix in the shader. + GLES20.glUniformMatrix4fv(modelViewUniform, 1, false, modelViewMatrix, 0); + GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, modelViewProjectionMatrix, 0); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(positionAttribute); + GLES20.glEnableVertexAttribArray(normalAttribute); + GLES20.glEnableVertexAttribArray(texCoordAttribute); + + if (blendMode != null) { + GLES20.glDepthMask(false); + GLES20.glEnable(GLES20.GL_BLEND); + switch (blendMode) { + case Shadow: + // Multiplicative blending function for Shadow. + GLES20.glBlendFunc(GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case Grid: + // Grid, additive blending function. + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + } } - /** - * Creates and initializes OpenGL resources needed for rendering the model. - * - * @param context Context for loading the shader and below-named model and texture assets. - * @param objAssetName Name of the OBJ file containing the model geometry. - * @param diffuseTextureAssetName Name of the PNG file containing the diffuse texture map. - */ - public void createOnGlThread(Context context, String objAssetName, - String diffuseTextureAssetName) throws IOException { - // Read the texture. - Bitmap textureBitmap = BitmapFactory.decodeStream( - context.getAssets().open(diffuseTextureAssetName)); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glGenTextures(mTextures.length, mTextures, 0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]); - - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0); - GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - - textureBitmap.recycle(); - - ShaderUtil.checkGLError(TAG, "Texture loading"); - - // Read the obj file. - InputStream objInputStream = context.getAssets().open(objAssetName); - Obj obj = ObjReader.read(objInputStream); - - // Prepare the Obj so that its structure is suitable for - // rendering with OpenGL: - // 1. Triangulate it - // 2. Make sure that texture coordinates are not ambiguous - // 3. Make sure that normals are not ambiguous - // 4. Convert it to single-indexed data - obj = ObjUtils.convertToRenderable(obj); - - // OpenGL does not use Java arrays. ByteBuffers are used instead to provide data in a format - // that OpenGL understands. - - // Obtain the data from the OBJ, as direct buffers: - IntBuffer wideIndices = ObjData.getFaceVertexIndices(obj, 3); - FloatBuffer vertices = ObjData.getVertices(obj); - FloatBuffer texCoords = ObjData.getTexCoords(obj, 2); - FloatBuffer normals = ObjData.getNormals(obj); - - // Convert int indices to shorts for GL ES 2.0 compatibility - ShortBuffer indices = ByteBuffer.allocateDirect(2 * wideIndices.limit()) - .order(ByteOrder.nativeOrder()).asShortBuffer(); - while (wideIndices.hasRemaining()) { - indices.put((short) wideIndices.get()); - } - indices.rewind(); - - int[] buffers = new int[2]; - GLES20.glGenBuffers(2, buffers, 0); - mVertexBufferId = buffers[0]; - mIndexBufferId = buffers[1]; - - // Load vertex buffer - mVerticesBaseAddress = 0; - mTexCoordsBaseAddress = mVerticesBaseAddress + 4 * vertices.limit(); - mNormalsBaseAddress = mTexCoordsBaseAddress + 4 * texCoords.limit(); - final int totalBytes = mNormalsBaseAddress + 4 * normals.limit(); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferId); - GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, totalBytes, null, GLES20.GL_STATIC_DRAW); - GLES20.glBufferSubData( - GLES20.GL_ARRAY_BUFFER, mVerticesBaseAddress, 4 * vertices.limit(), vertices); - GLES20.glBufferSubData( - GLES20.GL_ARRAY_BUFFER, mTexCoordsBaseAddress, 4 * texCoords.limit(), texCoords); - GLES20.glBufferSubData( - GLES20.GL_ARRAY_BUFFER, mNormalsBaseAddress, 4 * normals.limit(), normals); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - - // Load index buffer - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndexBufferId); - mIndexCount = indices.limit(); - GLES20.glBufferData( - GLES20.GL_ELEMENT_ARRAY_BUFFER, 2 * mIndexCount, indices, GLES20.GL_STATIC_DRAW); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - - ShaderUtil.checkGLError(TAG, "OBJ buffer load"); - - final int vertexShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_VERTEX_SHADER, R.raw.object_vertex); - final int fragmentShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_FRAGMENT_SHADER, R.raw.object_fragment); - - mProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(mProgram, vertexShader); - GLES20.glAttachShader(mProgram, fragmentShader); - GLES20.glLinkProgram(mProgram); - GLES20.glUseProgram(mProgram); - - ShaderUtil.checkGLError(TAG, "Program creation"); - - mModelViewUniform = GLES20.glGetUniformLocation(mProgram, "u_ModelView"); - mModelViewProjectionUniform = - GLES20.glGetUniformLocation(mProgram, "u_ModelViewProjection"); - - mPositionAttribute = GLES20.glGetAttribLocation(mProgram, "a_Position"); - mNormalAttribute = GLES20.glGetAttribLocation(mProgram, "a_Normal"); - mTexCoordAttribute = GLES20.glGetAttribLocation(mProgram, "a_TexCoord"); - - mTextureUniform = GLES20.glGetUniformLocation(mProgram, "u_Texture"); - - mLightingParametersUniform = GLES20.glGetUniformLocation(mProgram, "u_LightingParameters"); - mMaterialParametersUniform = GLES20.glGetUniformLocation(mProgram, "u_MaterialParameters"); - - ShaderUtil.checkGLError(TAG, "Program parameters"); - - Matrix.setIdentityM(mModelMatrix, 0); - } + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBufferId); + GLES20.glDrawElements(GLES20.GL_TRIANGLES, indexCount, GLES20.GL_UNSIGNED_SHORT, 0); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - /** - * Selects the blending mode for rendering. - * - * @param blendMode The blending mode. Null indicates no blending (opaque rendering). - */ - public void setBlendMode(BlendMode blendMode) { - mBlendMode = blendMode; + if (blendMode != null) { + GLES20.glDisable(GLES20.GL_BLEND); + GLES20.glDepthMask(true); } - /** - * Updates the object model matrix and applies scaling. - * - * @param modelMatrix A 4x4 model-to-world transformation matrix, stored in column-major order. - * @param scaleFactor A separate scaling factor to apply before the {@code modelMatrix}. - * @see android.opengl.Matrix - */ - public void updateModelMatrix(float[] modelMatrix, float scaleFactor) { - float[] scaleMatrix = new float[16]; - Matrix.setIdentityM(scaleMatrix, 0); - scaleMatrix[0] = scaleFactor; - scaleMatrix[5] = scaleFactor; - scaleMatrix[10] = scaleFactor; - Matrix.multiplyMM(mModelMatrix, 0, modelMatrix, 0, scaleMatrix, 0); - } + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(positionAttribute); + GLES20.glDisableVertexAttribArray(normalAttribute); + GLES20.glDisableVertexAttribArray(texCoordAttribute); - /** - * Sets the surface characteristics of the rendered model. - * - * @param ambient Intensity of non-directional surface illumination. - * @param diffuse Diffuse (matte) surface reflectivity. - * @param specular Specular (shiny) surface reflectivity. - * @param specularPower Surface shininess. Larger values result in a smaller, sharper - * specular highlight. - */ - public void setMaterialProperties( - float ambient, float diffuse, float specular, float specularPower) { - mAmbient = ambient; - mDiffuse = diffuse; - mSpecular = specular; - mSpecularPower = specularPower; - } + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - /** - * Draws the model. - * - * @param cameraView A 4x4 view matrix, in column-major order. - * @param cameraPerspective A 4x4 projection matrix, in column-major order. - * @param lightIntensity Illumination intensity. Combined with diffuse and specular material - * properties. - * @see #setBlendMode(BlendMode) - * @see #updateModelMatrix(float[], float) - * @see #setMaterialProperties(float, float, float, float) - * @see android.opengl.Matrix - */ - public void draw(float[] cameraView, float[] cameraPerspective, float lightIntensity) { - - ShaderUtil.checkGLError(TAG, "Before draw"); - - // Build the ModelView and ModelViewProjection matrices - // for calculating object position and light. - Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0); - Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0); - - GLES20.glUseProgram(mProgram); - - // Set the lighting environment properties. - Matrix.multiplyMV(mViewLightDirection, 0, mModelViewMatrix, 0, LIGHT_DIRECTION, 0); - normalizeVec3(mViewLightDirection); - GLES20.glUniform4f(mLightingParametersUniform, - mViewLightDirection[0], mViewLightDirection[1], mViewLightDirection[2], lightIntensity); - - // Set the object material properties. - GLES20.glUniform4f(mMaterialParametersUniform, mAmbient, mDiffuse, mSpecular, - mSpecularPower); - - // Attach the object texture. - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]); - GLES20.glUniform1i(mTextureUniform, 0); - - // Set the vertex attributes. - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferId); - - GLES20.glVertexAttribPointer( - mPositionAttribute, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mVerticesBaseAddress); - GLES20.glVertexAttribPointer( - mNormalAttribute, 3, GLES20.GL_FLOAT, false, 0, mNormalsBaseAddress); - GLES20.glVertexAttribPointer( - mTexCoordAttribute, 2, GLES20.GL_FLOAT, false, 0, mTexCoordsBaseAddress); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - - // Set the ModelViewProjection matrix in the shader. - GLES20.glUniformMatrix4fv( - mModelViewUniform, 1, false, mModelViewMatrix, 0); - GLES20.glUniformMatrix4fv( - mModelViewProjectionUniform, 1, false, mModelViewProjectionMatrix, 0); - - // Enable vertex arrays - GLES20.glEnableVertexAttribArray(mPositionAttribute); - GLES20.glEnableVertexAttribArray(mNormalAttribute); - GLES20.glEnableVertexAttribArray(mTexCoordAttribute); - - if (mBlendMode != null) { - GLES20.glDepthMask(false); - GLES20.glEnable(GLES20.GL_BLEND); - switch (mBlendMode) { - case Shadow: - // Multiplicative blending function for Shadow. - GLES20.glBlendFunc(GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA); - break; - case Grid: - // Grid, additive blending function. - GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); - break; - } - } - - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndexBufferId); - GLES20.glDrawElements(GLES20.GL_TRIANGLES, mIndexCount, GLES20.GL_UNSIGNED_SHORT, 0); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - - if (mBlendMode != null) { - GLES20.glDisable(GLES20.GL_BLEND); - GLES20.glDepthMask(true); - } - - // Disable vertex arrays - GLES20.glDisableVertexAttribArray(mPositionAttribute); - GLES20.glDisableVertexAttribArray(mNormalAttribute); - GLES20.glDisableVertexAttribArray(mTexCoordAttribute); - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - - ShaderUtil.checkGLError(TAG, "After draw"); - } + ShaderUtil.checkGLError(TAG, "After draw"); + } - private static void normalizeVec3(float[] v) { - float reciprocalLength = 1.0f / (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - v[0] *= reciprocalLength; - v[1] *= reciprocalLength; - v[2] *= reciprocalLength; - } + private static void normalizeVec3(float[] v) { + float reciprocalLength = 1.0f / (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + v[0] *= reciprocalLength; + v[1] *= reciprocalLength; + v[2] *= reciprocalLength; + } } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java index 636d97160..2935acf0b 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java @@ -21,11 +21,10 @@ import android.opengl.GLSurfaceView; import android.opengl.GLUtils; import android.opengl.Matrix; - import com.google.ar.core.Camera; import com.google.ar.core.Plane; import com.google.ar.core.Pose; -import com.google.ar.core.Trackable.TrackingState; +import com.google.ar.core.TrackingState; import com.google.ar.core.examples.java.helloar.R; import java.io.IOException; import java.nio.ByteBuffer; @@ -42,387 +41,393 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -/** - * Renders the detected AR planes. - */ +/** Renders the detected AR planes. */ public class PlaneRenderer { - private static final String TAG = PlaneRenderer.class.getSimpleName(); - - private static final int BYTES_PER_FLOAT = Float.SIZE / 8; - private static final int BYTES_PER_SHORT = Short.SIZE / 8; - private static final int COORDS_PER_VERTEX = 3; // x, z, alpha - - private static final int VERTS_PER_BOUNDARY_VERT = 2; - private static final int INDICES_PER_BOUNDARY_VERT = 3; - private static final int INITIAL_BUFFER_BOUNDARY_VERTS = 64; - - private static final int INITIAL_VERTEX_BUFFER_SIZE_BYTES = - BYTES_PER_FLOAT * COORDS_PER_VERTEX * VERTS_PER_BOUNDARY_VERT - * INITIAL_BUFFER_BOUNDARY_VERTS; - - private static final int INITIAL_INDEX_BUFFER_SIZE_BYTES = - BYTES_PER_SHORT * INDICES_PER_BOUNDARY_VERT * INDICES_PER_BOUNDARY_VERT - * INITIAL_BUFFER_BOUNDARY_VERTS; - - private static final float FADE_RADIUS_M = 0.25f; - private static final float DOTS_PER_METER = 10.0f; - private static final float EQUILATERAL_TRIANGLE_SCALE = (float) (1 / Math.sqrt(3)); - - // Using the "signed distance field" approach to render sharp lines and circles. - // {dotThreshold, lineThreshold, lineFadeSpeed, occlusionScale} - // dotThreshold/lineThreshold: red/green intensity above which dots/lines are present - // lineFadeShrink: lines will fade in between alpha = 1-(1/lineFadeShrink) and 1.0 - // occlusionShrink: occluded planes will fade out between alpha = 0 and 1/occlusionShrink - private static final float[] GRID_CONTROL = {0.2f, 0.4f, 2.0f, 1.5f}; - - private int mPlaneProgram; - private int[] mTextures = new int[1]; - - private int mPlaneXZPositionAlphaAttribute; - - private int mPlaneModelUniform; - private int mPlaneModelViewProjectionUniform; - private int mTextureUniform; - private int mLineColorUniform; - private int mDotColorUniform; - private int mGridControlUniform; - private int mPlaneUvMatrixUniform; - - private FloatBuffer mVertexBuffer = ByteBuffer.allocateDirect(INITIAL_VERTEX_BUFFER_SIZE_BYTES) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - private ShortBuffer mIndexBuffer = ByteBuffer.allocateDirect(INITIAL_INDEX_BUFFER_SIZE_BYTES) - .order(ByteOrder.nativeOrder()).asShortBuffer(); - - // Temporary lists/matrices allocated here to reduce number of allocations for each frame. - private float[] mModelMatrix = new float[16]; - private float[] mModelViewMatrix = new float[16]; - private float[] mModelViewProjectionMatrix = new float[16]; - private float[] mPlaneColor = new float[4]; - private float[] mPlaneAngleUvMatrix = new float[4]; // 2x2 rotation matrix applied to uv coords. - - private Map mPlaneIndexMap = new HashMap<>(); - - public PlaneRenderer() { + private static final String TAG = PlaneRenderer.class.getSimpleName(); + + private static final int BYTES_PER_FLOAT = Float.SIZE / 8; + private static final int BYTES_PER_SHORT = Short.SIZE / 8; + private static final int COORDS_PER_VERTEX = 3; // x, z, alpha + + private static final int VERTS_PER_BOUNDARY_VERT = 2; + private static final int INDICES_PER_BOUNDARY_VERT = 3; + private static final int INITIAL_BUFFER_BOUNDARY_VERTS = 64; + + private static final int INITIAL_VERTEX_BUFFER_SIZE_BYTES = + BYTES_PER_FLOAT * COORDS_PER_VERTEX * VERTS_PER_BOUNDARY_VERT * INITIAL_BUFFER_BOUNDARY_VERTS; + + private static final int INITIAL_INDEX_BUFFER_SIZE_BYTES = + BYTES_PER_SHORT + * INDICES_PER_BOUNDARY_VERT + * INDICES_PER_BOUNDARY_VERT + * INITIAL_BUFFER_BOUNDARY_VERTS; + + private static final float FADE_RADIUS_M = 0.25f; + private static final float DOTS_PER_METER = 10.0f; + private static final float EQUILATERAL_TRIANGLE_SCALE = (float) (1 / Math.sqrt(3)); + + // Using the "signed distance field" approach to render sharp lines and circles. + // {dotThreshold, lineThreshold, lineFadeSpeed, occlusionScale} + // dotThreshold/lineThreshold: red/green intensity above which dots/lines are present + // lineFadeShrink: lines will fade in between alpha = 1-(1/lineFadeShrink) and 1.0 + // occlusionShrink: occluded planes will fade out between alpha = 0 and 1/occlusionShrink + private static final float[] GRID_CONTROL = {0.2f, 0.4f, 2.0f, 1.5f}; + + private int planeProgram; + private final int[] textures = new int[1]; + + private int planeXZPositionAlphaAttribute; + + private int planeModelUniform; + private int planeModelViewProjectionUniform; + private int textureUniform; + private int lineColorUniform; + private int dotColorUniform; + private int gridControlUniform; + private int planeUvMatrixUniform; + + private FloatBuffer vertexBuffer = + ByteBuffer.allocateDirect(INITIAL_VERTEX_BUFFER_SIZE_BYTES) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + private ShortBuffer indexBuffer = + ByteBuffer.allocateDirect(INITIAL_INDEX_BUFFER_SIZE_BYTES) + .order(ByteOrder.nativeOrder()) + .asShortBuffer(); + + // Temporary lists/matrices allocated here to reduce number of allocations for each frame. + private final float[] modelMatrix = new float[16]; + private final float[] modelViewMatrix = new float[16]; + private final float[] modelViewProjectionMatrix = new float[16]; + private final float[] planeColor = new float[4]; + private final float[] planeAngleUvMatrix = + new float[4]; // 2x2 rotation matrix applied to uv coords. + + private final Map planeIndexMap = new HashMap<>(); + + public PlaneRenderer() {} + + /** + * Allocates and initializes OpenGL resources needed by the plane renderer. Must be called on the + * OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. + * + * @param context Needed to access shader source and texture PNG. + * @param gridDistanceTextureName Name of the PNG file containing the grid texture. + */ + public void createOnGlThread(Context context, String gridDistanceTextureName) throws IOException { + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.plane_vertex); + int passthroughShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.plane_fragment); + + planeProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(planeProgram, vertexShader); + GLES20.glAttachShader(planeProgram, passthroughShader); + GLES20.glLinkProgram(planeProgram); + GLES20.glUseProgram(planeProgram); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + // Read the texture. + Bitmap textureBitmap = + BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName)); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glGenTextures(textures.length, textures, 0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0); + GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + ShaderUtil.checkGLError(TAG, "Texture loading"); + + planeXZPositionAlphaAttribute = GLES20.glGetAttribLocation(planeProgram, "a_XZPositionAlpha"); + + planeModelUniform = GLES20.glGetUniformLocation(planeProgram, "u_Model"); + planeModelViewProjectionUniform = + GLES20.glGetUniformLocation(planeProgram, "u_ModelViewProjection"); + textureUniform = GLES20.glGetUniformLocation(planeProgram, "u_Texture"); + lineColorUniform = GLES20.glGetUniformLocation(planeProgram, "u_lineColor"); + dotColorUniform = GLES20.glGetUniformLocation(planeProgram, "u_dotColor"); + gridControlUniform = GLES20.glGetUniformLocation(planeProgram, "u_gridControl"); + planeUvMatrixUniform = GLES20.glGetUniformLocation(planeProgram, "u_PlaneUvMatrix"); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + } + + /** Updates the plane model transform matrix and extents. */ + private void updatePlaneParameters( + float[] planeMatrix, float extentX, float extentZ, FloatBuffer boundary) { + System.arraycopy(planeMatrix, 0, modelMatrix, 0, 16); + if (boundary == null) { + vertexBuffer.limit(0); + indexBuffer.limit(0); + return; } - /** - * Allocates and initializes OpenGL resources needed by the plane renderer. Must be - * called on the OpenGL thread, typically in - * {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. - * - * @param context Needed to access shader source and texture PNG. - * @param gridDistanceTextureName Name of the PNG file containing the grid texture. - */ - public void createOnGlThread(Context context, String gridDistanceTextureName) - throws IOException { - int vertexShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_VERTEX_SHADER, R.raw.plane_vertex); - int passthroughShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_FRAGMENT_SHADER, R.raw.plane_fragment); - - mPlaneProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(mPlaneProgram, vertexShader); - GLES20.glAttachShader(mPlaneProgram, passthroughShader); - GLES20.glLinkProgram(mPlaneProgram); - GLES20.glUseProgram(mPlaneProgram); - - ShaderUtil.checkGLError(TAG, "Program creation"); - - // Read the texture. - Bitmap textureBitmap = BitmapFactory.decodeStream( - context.getAssets().open(gridDistanceTextureName)); - - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glGenTextures(mTextures.length, mTextures, 0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]); - - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR); - GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0); - GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - - ShaderUtil.checkGLError(TAG, "Texture loading"); - - mPlaneXZPositionAlphaAttribute = GLES20.glGetAttribLocation(mPlaneProgram, - "a_XZPositionAlpha"); - - mPlaneModelUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_Model"); - mPlaneModelViewProjectionUniform = - GLES20.glGetUniformLocation(mPlaneProgram, "u_ModelViewProjection"); - mTextureUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_Texture"); - mLineColorUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_lineColor"); - mDotColorUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_dotColor"); - mGridControlUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_gridControl"); - mPlaneUvMatrixUniform = GLES20.glGetUniformLocation(mPlaneProgram, "u_PlaneUvMatrix"); - - ShaderUtil.checkGLError(TAG, "Program parameters"); + // Generate a new set of vertices and a corresponding triangle strip index set so that + // the plane boundary polygon has a fading edge. This is done by making a copy of the + // boundary polygon vertices and scaling it down around center to push it inwards. Then + // the index buffer is setup accordingly. + boundary.rewind(); + int boundaryVertices = boundary.limit() / 2; + int numVertices; + int numIndices; + + numVertices = boundaryVertices * VERTS_PER_BOUNDARY_VERT; + // drawn as GL_TRIANGLE_STRIP with 3n-2 triangles (n-2 for fill, 2n for perimeter). + numIndices = boundaryVertices * INDICES_PER_BOUNDARY_VERT; + + if (vertexBuffer.capacity() < numVertices * COORDS_PER_VERTEX) { + int size = vertexBuffer.capacity(); + while (size < numVertices * COORDS_PER_VERTEX) { + size *= 2; + } + vertexBuffer = + ByteBuffer.allocateDirect(BYTES_PER_FLOAT * size) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); } - - /** - * Updates the plane model transform matrix and extents. - */ - private void updatePlaneParameters(float[] planeMatrix, float extentX, float extentZ, - FloatBuffer boundary) { - System.arraycopy(planeMatrix, 0, mModelMatrix, 0, 16); - if (boundary == null) { - mVertexBuffer.limit(0); - mIndexBuffer.limit(0); - return; - } - - // Generate a new set of vertices and a corresponding triangle strip index set so that - // the plane boundary polygon has a fading edge. This is done by making a copy of the - // boundary polygon vertices and scaling it down around center to push it inwards. Then - // the index buffer is setup accordingly. - boundary.rewind(); - int boundaryVertices = boundary.limit() / 2; - int numVertices; - int numIndices; - - numVertices = boundaryVertices * VERTS_PER_BOUNDARY_VERT; - // drawn as GL_TRIANGLE_STRIP with 3n-2 triangles (n-2 for fill, 2n for perimeter). - numIndices = boundaryVertices * INDICES_PER_BOUNDARY_VERT; - - if (mVertexBuffer.capacity() < numVertices * COORDS_PER_VERTEX) { - int size = mVertexBuffer.capacity(); - while (size < numVertices * COORDS_PER_VERTEX) { - size *= 2; - } - mVertexBuffer = ByteBuffer.allocateDirect(BYTES_PER_FLOAT * size) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - } - mVertexBuffer.rewind(); - mVertexBuffer.limit(numVertices * COORDS_PER_VERTEX); - - - if (mIndexBuffer.capacity() < numIndices) { - int size = mIndexBuffer.capacity(); - while (size < numIndices) { - size *= 2; - } - mIndexBuffer = ByteBuffer.allocateDirect(BYTES_PER_SHORT * size) - .order(ByteOrder.nativeOrder()).asShortBuffer(); - } - mIndexBuffer.rewind(); - mIndexBuffer.limit(numIndices); - - // Note: when either dimension of the bounding box is smaller than 2*FADE_RADIUS_M we - // generate a bunch of 0-area triangles. These don't get rendered though so it works - // out ok. - float xScale = Math.max((extentX - 2 * FADE_RADIUS_M) / extentX, 0.0f); - float zScale = Math.max((extentZ - 2 * FADE_RADIUS_M) / extentZ, 0.0f); - - while (boundary.hasRemaining()) { - float x = boundary.get(); - float z = boundary.get(); - mVertexBuffer.put(x); - mVertexBuffer.put(z); - mVertexBuffer.put(0.0f); - mVertexBuffer.put(x * xScale); - mVertexBuffer.put(z * zScale); - mVertexBuffer.put(1.0f); - } - - // step 1, perimeter - mIndexBuffer.put((short) ((boundaryVertices - 1) * 2)); - for (int i = 0; i < boundaryVertices; ++i) { - mIndexBuffer.put((short) (i * 2)); - mIndexBuffer.put((short) (i * 2 + 1)); - } - mIndexBuffer.put((short) 1); - // This leaves us on the interior edge of the perimeter between the inset vertices - // for boundary verts n-1 and 0. - - // step 2, interior: - for (int i = 1; i < boundaryVertices / 2; ++i) { - mIndexBuffer.put((short) ((boundaryVertices - 1 - i) * 2 + 1)); - mIndexBuffer.put((short) (i * 2 + 1)); - } - if (boundaryVertices % 2 != 0) { - mIndexBuffer.put((short) ((boundaryVertices / 2) * 2 + 1)); - } + vertexBuffer.rewind(); + vertexBuffer.limit(numVertices * COORDS_PER_VERTEX); + + if (indexBuffer.capacity() < numIndices) { + int size = indexBuffer.capacity(); + while (size < numIndices) { + size *= 2; + } + indexBuffer = + ByteBuffer.allocateDirect(BYTES_PER_SHORT * size) + .order(ByteOrder.nativeOrder()) + .asShortBuffer(); } - - private void draw(float[] cameraView, float[] cameraPerspective) { - // Build the ModelView and ModelViewProjection matrices - // for calculating cube position and light. - Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0); - Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0); - - // Set the position of the plane - mVertexBuffer.rewind(); - GLES20.glVertexAttribPointer( - mPlaneXZPositionAlphaAttribute, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, - BYTES_PER_FLOAT * COORDS_PER_VERTEX, mVertexBuffer); - - // Set the Model and ModelViewProjection matrices in the shader. - GLES20.glUniformMatrix4fv(mPlaneModelUniform, 1, false, mModelMatrix, 0); - GLES20.glUniformMatrix4fv( - mPlaneModelViewProjectionUniform, 1, false, mModelViewProjectionMatrix, 0); - - mIndexBuffer.rewind(); - GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, mIndexBuffer.limit(), - GLES20.GL_UNSIGNED_SHORT, mIndexBuffer); - ShaderUtil.checkGLError(TAG, "Drawing plane"); + indexBuffer.rewind(); + indexBuffer.limit(numIndices); + + // Note: when either dimension of the bounding box is smaller than 2*FADE_RADIUS_M we + // generate a bunch of 0-area triangles. These don't get rendered though so it works + // out ok. + float xScale = Math.max((extentX - 2 * FADE_RADIUS_M) / extentX, 0.0f); + float zScale = Math.max((extentZ - 2 * FADE_RADIUS_M) / extentZ, 0.0f); + + while (boundary.hasRemaining()) { + float x = boundary.get(); + float z = boundary.get(); + vertexBuffer.put(x); + vertexBuffer.put(z); + vertexBuffer.put(0.0f); + vertexBuffer.put(x * xScale); + vertexBuffer.put(z * zScale); + vertexBuffer.put(1.0f); } - static class SortablePlane { - final float mDistance; - final Plane mPlane; - SortablePlane(float distance, Plane plane) { - this.mDistance = distance; - this.mPlane = plane; - } + // step 1, perimeter + indexBuffer.put((short) ((boundaryVertices - 1) * 2)); + for (int i = 0; i < boundaryVertices; ++i) { + indexBuffer.put((short) (i * 2)); + indexBuffer.put((short) (i * 2 + 1)); } - - /** - * Draws the collection of tracked planes, with closer planes hiding more distant ones. - * - * @param allPlanes The collection of planes to draw. - * @param cameraPose The pose of the camera, as returned by {@link Camera#getPose()} - * @param cameraPerspective The projection matrix, as returned by - * {@link Camera#getProjectionMatrix(float[], int, float, float)} - */ - public void drawPlanes(Collection allPlanes, Pose cameraPose, - float[] cameraPerspective) { - // Planes must be sorted by distance from camera so that we draw closer planes first, and - // they occlude the farther planes. - List sortedPlanes = new ArrayList<>(); - float[] normal = new float[3]; - float cameraX = cameraPose.tx(); - float cameraY = cameraPose.ty(); - float cameraZ = cameraPose.tz(); - for (Plane plane : allPlanes) { - if (plane.getTrackingState() != TrackingState.TRACKING - || plane.getSubsumedBy() != null) { - continue; - } - - Pose center = plane.getCenterPose(); - // Get transformed Y axis of plane's coordinate system. - center.getTransformedAxis(1, 1.0f, normal, 0); - // Compute dot product of plane's normal with vector from camera to plane center. - float distance = (cameraX - center.tx()) * normal[0] - + (cameraY - center.ty()) * normal[1] + (cameraZ - center.tz()) * normal[2]; - if (distance < 0) { // Plane is back-facing. - continue; - } - sortedPlanes.add(new SortablePlane(distance, plane)); - } - Collections.sort(sortedPlanes, new Comparator() { - @Override - public int compare(SortablePlane a, SortablePlane b) { - return Float.compare(a.mDistance, b.mDistance); - } - }); - - - float[] cameraView = new float[16]; - cameraPose.inverse().toMatrix(cameraView, 0); - - // Planes are drawn with additive blending, masked by the alpha channel for occlusion. - - // Start by clearing the alpha channel of the color buffer to 1.0. - GLES20.glClearColor(1, 1, 1, 1); - GLES20.glColorMask(false, false, false, true); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - GLES20.glColorMask(true, true, true, true); - - // Disable depth write. - GLES20.glDepthMask(false); - - // Additive blending, masked by alpha channel, clearing alpha channel. - GLES20.glEnable(GLES20.GL_BLEND); - GLES20.glBlendFuncSeparate( - GLES20.GL_DST_ALPHA, GLES20.GL_ONE, // RGB (src, dest) - GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA); // ALPHA (src, dest) - - // Set up the shader. - GLES20.glUseProgram(mPlaneProgram); - - // Attach the texture. - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]); - GLES20.glUniform1i(mTextureUniform, 0); - - // Shared fragment uniforms. - GLES20.glUniform4fv(mGridControlUniform, 1, GRID_CONTROL, 0); - - // Enable vertex arrays - GLES20.glEnableVertexAttribArray(mPlaneXZPositionAlphaAttribute); - - ShaderUtil.checkGLError(TAG, "Setting up to draw planes"); - - for (SortablePlane sortedPlane : sortedPlanes) { - Plane plane = sortedPlane.mPlane; - float[] planeMatrix = new float[16]; - plane.getCenterPose().toMatrix(planeMatrix, 0); - - updatePlaneParameters( - planeMatrix, plane.getExtentX(), plane.getExtentZ(), plane.getPolygon()); - - // Get plane index. Keep a map to assign same indices to same planes. - Integer planeIndex = mPlaneIndexMap.get(plane); - if (planeIndex == null) { - planeIndex = mPlaneIndexMap.size(); - mPlaneIndexMap.put(plane, planeIndex); - } - - // Set plane color. Computed deterministically from the Plane index. - int colorIndex = planeIndex % PLANE_COLORS_RGBA.length; - colorRgbaToFloat(mPlaneColor, PLANE_COLORS_RGBA[colorIndex]); - GLES20.glUniform4fv(mLineColorUniform, 1, mPlaneColor, 0); - GLES20.glUniform4fv(mDotColorUniform, 1, mPlaneColor, 0); - - // Each plane will have its own angle offset from others, to make them easier to - // distinguish. Compute a 2x2 rotation matrix from the angle. - float angleRadians = planeIndex * 0.144f; - float uScale = DOTS_PER_METER; - float vScale = DOTS_PER_METER * EQUILATERAL_TRIANGLE_SCALE; - mPlaneAngleUvMatrix[0] = +(float) Math.cos(angleRadians) * uScale; - mPlaneAngleUvMatrix[1] = -(float) Math.sin(angleRadians) * vScale; - mPlaneAngleUvMatrix[2] = +(float) Math.sin(angleRadians) * uScale; - mPlaneAngleUvMatrix[3] = +(float) Math.cos(angleRadians) * vScale; - GLES20.glUniformMatrix2fv(mPlaneUvMatrixUniform, 1, false, mPlaneAngleUvMatrix, 0); - - draw(cameraView, cameraPerspective); - } - - // Clean up the state we set - GLES20.glDisableVertexAttribArray(mPlaneXZPositionAlphaAttribute); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - GLES20.glDisable(GLES20.GL_BLEND); - GLES20.glDepthMask(true); - - ShaderUtil.checkGLError(TAG, "Cleaning up after drawing planes"); + indexBuffer.put((short) 1); + // This leaves us on the interior edge of the perimeter between the inset vertices + // for boundary verts n-1 and 0. + + // step 2, interior: + for (int i = 1; i < boundaryVertices / 2; ++i) { + indexBuffer.put((short) ((boundaryVertices - 1 - i) * 2 + 1)); + indexBuffer.put((short) (i * 2 + 1)); + } + if (boundaryVertices % 2 != 0) { + indexBuffer.put((short) ((boundaryVertices / 2) * 2 + 1)); } + } + + private void draw(float[] cameraView, float[] cameraPerspective) { + // Build the ModelView and ModelViewProjection matrices + // for calculating cube position and light. + Matrix.multiplyMM(modelViewMatrix, 0, cameraView, 0, modelMatrix, 0); + Matrix.multiplyMM(modelViewProjectionMatrix, 0, cameraPerspective, 0, modelViewMatrix, 0); + + // Set the position of the plane + vertexBuffer.rewind(); + GLES20.glVertexAttribPointer( + planeXZPositionAlphaAttribute, + COORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + BYTES_PER_FLOAT * COORDS_PER_VERTEX, + vertexBuffer); + + // Set the Model and ModelViewProjection matrices in the shader. + GLES20.glUniformMatrix4fv(planeModelUniform, 1, false, modelMatrix, 0); + GLES20.glUniformMatrix4fv( + planeModelViewProjectionUniform, 1, false, modelViewProjectionMatrix, 0); + + indexBuffer.rewind(); + GLES20.glDrawElements( + GLES20.GL_TRIANGLE_STRIP, indexBuffer.limit(), GLES20.GL_UNSIGNED_SHORT, indexBuffer); + ShaderUtil.checkGLError(TAG, "Drawing plane"); + } + + static class SortablePlane { + final float distance; + final Plane plane; + + SortablePlane(float distance, Plane plane) { + this.distance = distance; + this.plane = plane; + } + } + + /** + * Draws the collection of tracked planes, with closer planes hiding more distant ones. + * + * @param allPlanes The collection of planes to draw. + * @param cameraPose The pose of the camera, as returned by {@link Camera#getPose()} + * @param cameraPerspective The projection matrix, as returned by {@link + * Camera#getProjectionMatrix(float[], int, float, float)} + */ + public void drawPlanes(Collection allPlanes, Pose cameraPose, float[] cameraPerspective) { + // Planes must be sorted by distance from camera so that we draw closer planes first, and + // they occlude the farther planes. + List sortedPlanes = new ArrayList<>(); + float[] normal = new float[3]; + float cameraX = cameraPose.tx(); + float cameraY = cameraPose.ty(); + float cameraZ = cameraPose.tz(); + for (Plane plane : allPlanes) { + if (plane.getTrackingState() != TrackingState.TRACKING || plane.getSubsumedBy() != null) { + continue; + } + + Pose center = plane.getCenterPose(); + // Get transformed Y axis of plane's coordinate system. + center.getTransformedAxis(1, 1.0f, normal, 0); + // Compute dot product of plane's normal with vector from camera to plane center. + float distance = + (cameraX - center.tx()) * normal[0] + + (cameraY - center.ty()) * normal[1] + + (cameraZ - center.tz()) * normal[2]; + if (distance < 0) { // Plane is back-facing. + continue; + } + sortedPlanes.add(new SortablePlane(distance, plane)); + } + Collections.sort( + sortedPlanes, + new Comparator() { + @Override + public int compare(SortablePlane a, SortablePlane b) { + return Float.compare(a.distance, b.distance); + } + }); - private static void colorRgbaToFloat(float[] planeColor, int colorRgba) { - planeColor[0] = ((float) ((colorRgba >> 24) & 0xff)) / 255.0f; - planeColor[1] = ((float) ((colorRgba >> 16) & 0xff)) / 255.0f; - planeColor[2] = ((float) ((colorRgba >> 8) & 0xff)) / 255.0f; - planeColor[3] = ((float) ((colorRgba >> 0) & 0xff)) / 255.0f; + float[] cameraView = new float[16]; + cameraPose.inverse().toMatrix(cameraView, 0); + + // Planes are drawn with additive blending, masked by the alpha channel for occlusion. + + // Start by clearing the alpha channel of the color buffer to 1.0. + GLES20.glClearColor(1, 1, 1, 1); + GLES20.glColorMask(false, false, false, true); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glColorMask(true, true, true, true); + + // Disable depth write. + GLES20.glDepthMask(false); + + // Additive blending, masked by alpha channel, clearing alpha channel. + GLES20.glEnable(GLES20.GL_BLEND); + GLES20.glBlendFuncSeparate( + GLES20.GL_DST_ALPHA, GLES20.GL_ONE, // RGB (src, dest) + GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA); // ALPHA (src, dest) + + // Set up the shader. + GLES20.glUseProgram(planeProgram); + + // Attach the texture. + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + GLES20.glUniform1i(textureUniform, 0); + + // Shared fragment uniforms. + GLES20.glUniform4fv(gridControlUniform, 1, GRID_CONTROL, 0); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(planeXZPositionAlphaAttribute); + + ShaderUtil.checkGLError(TAG, "Setting up to draw planes"); + + for (SortablePlane sortedPlane : sortedPlanes) { + Plane plane = sortedPlane.plane; + float[] planeMatrix = new float[16]; + plane.getCenterPose().toMatrix(planeMatrix, 0); + + updatePlaneParameters( + planeMatrix, plane.getExtentX(), plane.getExtentZ(), plane.getPolygon()); + + // Get plane index. Keep a map to assign same indices to same planes. + Integer planeIndex = planeIndexMap.get(plane); + if (planeIndex == null) { + planeIndex = planeIndexMap.size(); + planeIndexMap.put(plane, planeIndex); + } + + // Set plane color. Computed deterministically from the Plane index. + int colorIndex = planeIndex % PLANE_COLORS_RGBA.length; + colorRgbaToFloat(planeColor, PLANE_COLORS_RGBA[colorIndex]); + GLES20.glUniform4fv(lineColorUniform, 1, planeColor, 0); + GLES20.glUniform4fv(dotColorUniform, 1, planeColor, 0); + + // Each plane will have its own angle offset from others, to make them easier to + // distinguish. Compute a 2x2 rotation matrix from the angle. + float angleRadians = planeIndex * 0.144f; + float uScale = DOTS_PER_METER; + float vScale = DOTS_PER_METER * EQUILATERAL_TRIANGLE_SCALE; + planeAngleUvMatrix[0] = +(float) Math.cos(angleRadians) * uScale; + planeAngleUvMatrix[1] = -(float) Math.sin(angleRadians) * vScale; + planeAngleUvMatrix[2] = +(float) Math.sin(angleRadians) * uScale; + planeAngleUvMatrix[3] = +(float) Math.cos(angleRadians) * vScale; + GLES20.glUniformMatrix2fv(planeUvMatrixUniform, 1, false, planeAngleUvMatrix, 0); + + draw(cameraView, cameraPerspective); } - private static final int[] PLANE_COLORS_RGBA = { - 0xFFFFFFFF, - 0xF44336FF, - 0xE91E63FF, - 0x9C27B0FF, - 0x673AB7FF, - 0x3F51B5FF, - 0x2196F3FF, - 0x03A9F4FF, - 0x00BCD4FF, - 0x009688FF, - 0x4CAF50FF, - 0x8BC34AFF, - 0xCDDC39FF, - 0xFFEB3BFF, - 0xFFC107FF, - 0xFF9800FF, - }; + // Clean up the state we set + GLES20.glDisableVertexAttribArray(planeXZPositionAlphaAttribute); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + GLES20.glDisable(GLES20.GL_BLEND); + GLES20.glDepthMask(true); + + ShaderUtil.checkGLError(TAG, "Cleaning up after drawing planes"); + } + + private static void colorRgbaToFloat(float[] planeColor, int colorRgba) { + planeColor[0] = ((float) ((colorRgba >> 24) & 0xff)) / 255.0f; + planeColor[1] = ((float) ((colorRgba >> 16) & 0xff)) / 255.0f; + planeColor[2] = ((float) ((colorRgba >> 8) & 0xff)) / 255.0f; + planeColor[3] = ((float) ((colorRgba >> 0) & 0xff)) / 255.0f; + } + + private static final int[] PLANE_COLORS_RGBA = { + 0xFFFFFFFF, + 0xF44336FF, + 0xE91E63FF, + 0x9C27B0FF, + 0x673AB7FF, + 0x3F51B5FF, + 0x2196F3FF, + 0x03A9F4FF, + 0x00BCD4FF, + 0x009688FF, + 0x4CAF50FF, + 0x8BC34AFF, + 0xCDDC39FF, + 0xFFEB3BFF, + 0xFFC107FF, + 0xFF9800FF, + }; } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java index 9862a743a..35bbeba73 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java @@ -23,107 +23,103 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -/** - * Renders a point cloud. - */ +/** Renders a point cloud. */ public class PointCloudRenderer { - private static final String TAG = PointCloud.class.getSimpleName(); + private static final String TAG = PointCloud.class.getSimpleName(); - private static final int BYTES_PER_FLOAT = Float.SIZE / 8; - private static final int FLOATS_PER_POINT = 4; // X,Y,Z,confidence. - private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * FLOATS_PER_POINT; - private static final int INITIAL_BUFFER_POINTS = 1000; + private static final int BYTES_PER_FLOAT = Float.SIZE / 8; + private static final int FLOATS_PER_POINT = 4; // X,Y,Z,confidence. + private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * FLOATS_PER_POINT; + private static final int INITIAL_BUFFER_POINTS = 1000; - private int mVbo; - private int mVboSize; + private int vbo; + private int vboSize; - private int mProgramName; - private int mPositionAttribute; - private int mModelViewProjectionUniform; - private int mColorUniform; - private int mPointSizeUniform; + private int programName; + private int positionAttribute; + private int modelViewProjectionUniform; + private int colorUniform; + private int pointSizeUniform; - private int mNumPoints = 0; + private int numPoints = 0; - // Keep track of the last point cloud rendered to avoid updating the VBO if point cloud - // was not changed. - private PointCloud mLastPointCloud = null; + // Keep track of the last point cloud rendered to avoid updating the VBO if point cloud + // was not changed. + private PointCloud lastPointCloud = null; - public PointCloudRenderer() { - } + public PointCloudRenderer() {} + + /** + * Allocates and initializes OpenGL resources needed by the plane renderer. Must be called on the + * OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. + * + * @param context Needed to access shader source. + */ + public void createOnGlThread(Context context) { + ShaderUtil.checkGLError(TAG, "before create"); + + int[] buffers = new int[1]; + GLES20.glGenBuffers(1, buffers, 0); + vbo = buffers[0]; + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo); + + vboSize = INITIAL_BUFFER_POINTS * BYTES_PER_POINT; + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vboSize, null, GLES20.GL_DYNAMIC_DRAW); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + + ShaderUtil.checkGLError(TAG, "buffer alloc"); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.point_cloud_vertex); + int passthroughShader = + ShaderUtil.loadGLShader( + TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment); - /** - * Allocates and initializes OpenGL resources needed by the plane renderer. Must be - * called on the OpenGL thread, typically in - * {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. - * - * @param context Needed to access shader source. - */ - public void createOnGlThread(Context context) { - ShaderUtil.checkGLError(TAG, "before create"); - - int[] buffers = new int[1]; - GLES20.glGenBuffers(1, buffers, 0); - mVbo = buffers[0]; - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo); - - mVboSize = INITIAL_BUFFER_POINTS * BYTES_PER_POINT; - GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVboSize, null, GLES20.GL_DYNAMIC_DRAW); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - - ShaderUtil.checkGLError(TAG, "buffer alloc"); - - int vertexShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_VERTEX_SHADER, R.raw.point_cloud_vertex); - int passthroughShader = ShaderUtil.loadGLShader(TAG, context, - GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment); - - mProgramName = GLES20.glCreateProgram(); - GLES20.glAttachShader(mProgramName, vertexShader); - GLES20.glAttachShader(mProgramName, passthroughShader); - GLES20.glLinkProgram(mProgramName); - GLES20.glUseProgram(mProgramName); - - ShaderUtil.checkGLError(TAG, "program"); - - mPositionAttribute = GLES20.glGetAttribLocation(mProgramName, "a_Position"); - mColorUniform = GLES20.glGetUniformLocation(mProgramName, "u_Color"); - mModelViewProjectionUniform = GLES20.glGetUniformLocation( - mProgramName, "u_ModelViewProjection"); - mPointSizeUniform = GLES20.glGetUniformLocation(mProgramName, "u_PointSize"); - - ShaderUtil.checkGLError(TAG, "program params"); + programName = GLES20.glCreateProgram(); + GLES20.glAttachShader(programName, vertexShader); + GLES20.glAttachShader(programName, passthroughShader); + GLES20.glLinkProgram(programName); + GLES20.glUseProgram(programName); + + ShaderUtil.checkGLError(TAG, "program"); + + positionAttribute = GLES20.glGetAttribLocation(programName, "a_Position"); + colorUniform = GLES20.glGetUniformLocation(programName, "u_Color"); + modelViewProjectionUniform = GLES20.glGetUniformLocation(programName, "u_ModelViewProjection"); + pointSizeUniform = GLES20.glGetUniformLocation(programName, "u_PointSize"); + + ShaderUtil.checkGLError(TAG, "program params"); + } + + /** + * Updates the OpenGL buffer contents to the provided point. Repeated calls with the same point + * cloud will be ignored. + */ + public void update(PointCloud cloud) { + if (lastPointCloud == cloud) { + // Redundant call. + return; } - /** - * Updates the OpenGL buffer contents to the provided point. Repeated calls with the same - * point cloud will be ignored. - */ - public void update(PointCloud cloud) { - if (mLastPointCloud == cloud) { - // Redundant call. - return; - } - - ShaderUtil.checkGLError(TAG, "before update"); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo); - mLastPointCloud = cloud; - - // If the VBO is not large enough to fit the new point cloud, resize it. - mNumPoints = mLastPointCloud.getPoints().remaining() / FLOATS_PER_POINT; - if (mNumPoints * BYTES_PER_POINT > mVboSize) { - while (mNumPoints * BYTES_PER_POINT > mVboSize) { - mVboSize *= 2; - } - GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVboSize, null, GLES20.GL_DYNAMIC_DRAW); - } - GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, mNumPoints * BYTES_PER_POINT, - mLastPointCloud.getPoints()); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - - ShaderUtil.checkGLError(TAG, "after update"); + ShaderUtil.checkGLError(TAG, "before update"); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo); + lastPointCloud = cloud; + + // If the VBO is not large enough to fit the new point cloud, resize it. + numPoints = lastPointCloud.getPoints().remaining() / FLOATS_PER_POINT; + if (numPoints * BYTES_PER_POINT > vboSize) { + while (numPoints * BYTES_PER_POINT > vboSize) { + vboSize *= 2; + } + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vboSize, null, GLES20.GL_DYNAMIC_DRAW); } + GLES20.glBufferSubData( + GLES20.GL_ARRAY_BUFFER, 0, numPoints * BYTES_PER_POINT, lastPointCloud.getPoints()); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + + ShaderUtil.checkGLError(TAG, "after update"); + } /** * Renders the point cloud. ArCore point cloud is given in world space. @@ -134,24 +130,23 @@ public void update(PointCloud cloud) { * com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)}. */ public void draw(float[] cameraView, float[] cameraPerspective) { - float[] modelViewProjection = new float[16]; - Matrix.multiplyMM(modelViewProjection, 0, cameraPerspective, 0, cameraView, 0); + float[] modelViewProjection = new float[16]; + Matrix.multiplyMM(modelViewProjection, 0, cameraPerspective, 0, cameraView, 0); - ShaderUtil.checkGLError(TAG, "Before draw"); + ShaderUtil.checkGLError(TAG, "Before draw"); - GLES20.glUseProgram(mProgramName); - GLES20.glEnableVertexAttribArray(mPositionAttribute); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo); - GLES20.glVertexAttribPointer( - mPositionAttribute, 4, GLES20.GL_FLOAT, false, BYTES_PER_POINT, 0); - GLES20.glUniform4f(mColorUniform, 31.0f / 255.0f, 188.0f / 255.0f, 210.0f / 255.0f, 1.0f); - GLES20.glUniformMatrix4fv(mModelViewProjectionUniform, 1, false, modelViewProjection, 0); - GLES20.glUniform1f(mPointSizeUniform, 5.0f); + GLES20.glUseProgram(programName); + GLES20.glEnableVertexAttribArray(positionAttribute); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo); + GLES20.glVertexAttribPointer(positionAttribute, 4, GLES20.GL_FLOAT, false, BYTES_PER_POINT, 0); + GLES20.glUniform4f(colorUniform, 31.0f / 255.0f, 188.0f / 255.0f, 210.0f / 255.0f, 1.0f); + GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, modelViewProjection, 0); + GLES20.glUniform1f(pointSizeUniform, 5.0f); - GLES20.glDrawArrays(GLES20.GL_POINTS, 0, mNumPoints); - GLES20.glDisableVertexAttribArray(mPositionAttribute); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + GLES20.glDrawArrays(GLES20.GL_POINTS, 0, numPoints); + GLES20.glDisableVertexAttribArray(positionAttribute); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - ShaderUtil.checkGLError(TAG, "Draw"); - } + ShaderUtil.checkGLError(TAG, "Draw"); + } } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java index 148ed81fa..fce0f43b9 100644 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java +++ b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java @@ -22,80 +22,78 @@ import java.io.InputStream; import java.io.InputStreamReader; -/** - * Shader helper functions. - */ +/** Shader helper functions. */ public class ShaderUtil { - /** - * Converts a raw text file, saved as a resource, into an OpenGL ES shader. - * - * @param type The type of shader we will be creating. - * @param resId The resource ID of the raw text file about to be turned into a shader. - * @return The shader object handler. - */ - public static int loadGLShader(String tag, Context context, int type, int resId) { - String code = readRawTextFile(context, resId); - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, code); - GLES20.glCompileShader(shader); - - // Get the compilation status. - final int[] compileStatus = new int[1]; - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + /** + * Converts a raw text file, saved as a resource, into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param resId The resource ID of the raw text file about to be turned into a shader. + * @return The shader object handler. + */ + public static int loadGLShader(String tag, Context context, int type, int resId) { + String code = readRawTextFile(context, resId); + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); - // If the compilation failed, delete the shader. - if (compileStatus[0] == 0) { - Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); - GLES20.glDeleteShader(shader); - shader = 0; - } + // Get the compilation status. + final int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); - if (shader == 0) { - throw new RuntimeException("Error creating shader."); - } + // If the compilation failed, delete the shader. + if (compileStatus[0] == 0) { + Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } - return shader; + if (shader == 0) { + throw new RuntimeException("Error creating shader."); } - /** - * Checks if we've had an error inside of OpenGL ES, and if so what that error is. - * - * @param label Label to report in case of error. - * @throws RuntimeException If an OpenGL error is detected. - */ - public static void checkGLError(String tag, String label) { - int lastError = GLES20.GL_NO_ERROR; - // Drain the queue of all errors. - int error; - while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { - Log.e(tag, label + ": glError " + error); - lastError = error; - } - if (lastError != GLES20.GL_NO_ERROR) { - throw new RuntimeException(label + ": glError " + lastError); - } + return shader; + } + + /** + * Checks if we've had an error inside of OpenGL ES, and if so what that error is. + * + * @param label Label to report in case of error. + * @throws RuntimeException If an OpenGL error is detected. + */ + public static void checkGLError(String tag, String label) { + int lastError = GLES20.GL_NO_ERROR; + // Drain the queue of all errors. + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(tag, label + ": glError " + error); + lastError = error; + } + if (lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException(label + ": glError " + lastError); } + } - /** - * Converts a raw text file into a string. - * - * @param resId The resource ID of the raw text file about to be turned into a shader. - * @return The context of the text file, or null in case of error. - */ - private static String readRawTextFile(Context context, int resId) { - InputStream inputStream = context.getResources().openRawResource(resId); - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - reader.close(); - return sb.toString(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; + /** + * Converts a raw text file into a string. + * + * @param resId The resource ID of the raw text file about to be turned into a shader. + * @return The context of the text file, or null in case of error. + */ + private static String readRawTextFile(Context context, int resId) { + InputStream inputStream = context.getResources().openRawResource(resId); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); } + return null; + } } diff --git a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/package-info.java b/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/package-info.java deleted file mode 100644 index 435906b8b..000000000 --- a/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2017 Google Inc. All Rights Reserved. - * - * 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. - */ -/** - * This package contains classes that do the rendering for this example. - */ -package com.google.ar.core.examples.java.helloar.rendering; diff --git a/samples/hello_ar_java/app/src/main/res/layout/activity_main.xml b/samples/hello_ar_java/app/src/main/res/layout/activity_main.xml index 1875feade..2723fc1a8 100644 --- a/samples/hello_ar_java/app/src/main/res/layout/activity_main.xml +++ b/samples/hello_ar_java/app/src/main/res/layout/activity_main.xml @@ -19,10 +19,10 @@ android:layout_height="match_parent" tools:context="com.google.ar.core.examples.java.helloar.HelloArActivity"> - + diff --git a/samples/hello_ar_java/app/src/main/res/values/strings.xml b/samples/hello_ar_java/app/src/main/res/values/strings.xml index 5f8bc36d1..4555298b8 100644 --- a/samples/hello_ar_java/app/src/main/res/values/strings.xml +++ b/samples/hello_ar_java/app/src/main/res/values/strings.xml @@ -15,5 +15,5 @@ limitations under the License. --> - HelloAR Java + HelloAR Java diff --git a/samples/hello_ar_java/app/src/main/res/values/styles.xml b/samples/hello_ar_java/app/src/main/res/values/styles.xml index 59cf7e9ff..68b12b6c4 100644 --- a/samples/hello_ar_java/app/src/main/res/values/styles.xml +++ b/samples/hello_ar_java/app/src/main/res/values/styles.xml @@ -15,21 +15,21 @@ --> + + + - - + + diff --git a/samples/hello_ar_java/build.gradle b/samples/hello_ar_java/build.gradle index 85691a636..be505f078 100644 --- a/samples/hello_ar_java/build.gradle +++ b/samples/hello_ar_java/build.gradle @@ -17,9 +17,6 @@ allprojects { google() jcenter() mavenLocal() - maven { - url "${project.rootDir}/../../libraries/m2repository" - } } } diff --git a/samples/hello_ar_java/gradle/wrapper/gradle-wrapper.jar b/samples/hello_ar_java/gradle/wrapper/gradle-wrapper.jar index 12a08711a..7a3265ee9 100644 Binary files a/samples/hello_ar_java/gradle/wrapper/gradle-wrapper.jar and b/samples/hello_ar_java/gradle/wrapper/gradle-wrapper.jar differ