diff --git a/OpenXR/meta_openxr_preview/meta_colocation_discovery.h b/OpenXR/meta_openxr_preview/meta_colocation_discovery.h new file mode 100755 index 0000000..1e5870e --- /dev/null +++ b/OpenXR/meta_openxr_preview/meta_colocation_discovery.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#ifndef META_COLOCATION_DISCOVERY_H_ +#define META_COLOCATION_DISCOVERY_H_ 1 + +/********************** +This file is @generated from the OpenXR XML API registry. +Language : C99 +Copyright : (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +***********************/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef XR_META_colocation_discovery + +// XR_META_colocation_discovery is a preprocessor guard. Do not pass it to API calls. +#define XR_META_colocation_discovery 1 +#define XR_MAX_COLOCATION_DISCOVERY_BUFFER_SIZE_META 1024 +#define XR_META_colocation_discovery_SPEC_VERSION 1 +#define XR_META_COLOCATION_DISCOVERY_EXTENSION_NAME "XR_META_colocation_discovery" +// The network request failed. +static const XrResult XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META = (XrResult) -1000571001; +// The runtime does not have any methods available to perform discovery. +static const XrResult XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META = (XrResult) -1000571002; +// Colocation advertisement has already been enabled +static const XrResult XR_COLOCATION_DISCOVERY_ALREADY_ADVERTISING_META = (XrResult) 1000571003; +// Colocation discovery has already been enabled +static const XrResult XR_COLOCATION_DISCOVERY_ALREADY_DISCOVERING_META = (XrResult) 1000571004; +static const XrStructureType XR_TYPE_COLOCATION_DISCOVERY_START_INFO_META = (XrStructureType) 1000571010; +static const XrStructureType XR_TYPE_COLOCATION_DISCOVERY_STOP_INFO_META = (XrStructureType) 1000571011; +static const XrStructureType XR_TYPE_COLOCATION_ADVERTISEMENT_START_INFO_META = (XrStructureType) 1000571012; +static const XrStructureType XR_TYPE_COLOCATION_ADVERTISEMENT_STOP_INFO_META = (XrStructureType) 1000571013; +static const XrStructureType XR_TYPE_EVENT_DATA_START_COLOCATION_ADVERTISEMENT_COMPLETE_META = (XrStructureType) 1000571020; +static const XrStructureType XR_TYPE_EVENT_DATA_STOP_COLOCATION_ADVERTISEMENT_COMPLETE_META = (XrStructureType) 1000571021; +static const XrStructureType XR_TYPE_EVENT_DATA_COLOCATION_ADVERTISEMENT_COMPLETE_META = (XrStructureType) 1000571022; +static const XrStructureType XR_TYPE_EVENT_DATA_START_COLOCATION_DISCOVERY_COMPLETE_META = (XrStructureType) 1000571023; +static const XrStructureType XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_RESULT_META = (XrStructureType) 1000571024; +static const XrStructureType XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_COMPLETE_META = (XrStructureType) 1000571025; +static const XrStructureType XR_TYPE_EVENT_DATA_STOP_COLOCATION_DISCOVERY_COMPLETE_META = (XrStructureType) 1000571026; +static const XrStructureType XR_TYPE_SYSTEM_COLOCATION_DISCOVERY_PROPERTIES_META = (XrStructureType) 1000571030; +typedef struct XrColocationDiscoveryStartInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationDiscoveryStartInfoMETA; + +typedef struct XrColocationDiscoveryStopInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationDiscoveryStopInfoMETA; + +typedef struct XrColocationAdvertisementStartInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t bufferSize; + uint8_t* buffer; +} XrColocationAdvertisementStartInfoMETA; + +typedef struct XrColocationAdvertisementStopInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationAdvertisementStopInfoMETA; + +typedef struct XrEventDataStartColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB advertisementRequestId; + XrResult result; + XrUuid advertisementUuid; +} XrEventDataStartColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataStopColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataStopColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB advertisementRequestId; + XrResult result; +} XrEventDataColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataStartColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrResult result; +} XrEventDataStartColocationDiscoveryCompleteMETA; + +typedef struct XrEventDataColocationDiscoveryResultMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrUuid advertisementUuid; + uint32_t bufferSize; + uint8_t buffer[XR_MAX_COLOCATION_DISCOVERY_BUFFER_SIZE_META]; +} XrEventDataColocationDiscoveryResultMETA; + +typedef struct XrEventDataColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrResult result; +} XrEventDataColocationDiscoveryCompleteMETA; + +typedef struct XrEventDataStopColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataStopColocationDiscoveryCompleteMETA; + +typedef struct XrSystemColocationDiscoveryPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsColocationDiscovery; +} XrSystemColocationDiscoveryPropertiesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrStartColocationDiscoveryMETA)(XrSession session, const XrColocationDiscoveryStartInfoMETA* info, XrAsyncRequestIdFB* discoveryRequestId); +typedef XrResult (XRAPI_PTR *PFN_xrStopColocationDiscoveryMETA)(XrSession session, const XrColocationDiscoveryStopInfoMETA* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrStartColocationAdvertisementMETA)(XrSession session, const XrColocationAdvertisementStartInfoMETA* info, XrAsyncRequestIdFB* advertisementRequestId); +typedef XrResult (XRAPI_PTR *PFN_xrStopColocationAdvertisementMETA)(XrSession session, const XrColocationAdvertisementStopInfoMETA* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrStartColocationDiscoveryMETA( + XrSession session, + const XrColocationDiscoveryStartInfoMETA* info, + XrAsyncRequestIdFB* discoveryRequestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopColocationDiscoveryMETA( + XrSession session, + const XrColocationDiscoveryStopInfoMETA* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStartColocationAdvertisementMETA( + XrSession session, + const XrColocationAdvertisementStartInfoMETA* info, + XrAsyncRequestIdFB* advertisementRequestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopColocationAdvertisementMETA( + XrSession session, + const XrColocationAdvertisementStopInfoMETA* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_META_colocation_discovery */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/OpenXR/meta_openxr_preview/meta_hand_tracking_microgestures.h b/OpenXR/meta_openxr_preview/meta_hand_tracking_microgestures.h new file mode 100755 index 0000000..4432e98 --- /dev/null +++ b/OpenXR/meta_openxr_preview/meta_hand_tracking_microgestures.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#ifndef META_HAND_TRACKING_MICROGESTURES_H_ +#define META_HAND_TRACKING_MICROGESTURES_H_ 1 + +/********************** +This file is @generated from the OpenXR XML API registry. +Language : C99 +Copyright : (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +***********************/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef XR_META_hand_tracking_microgestures + +// XR_META_hand_tracking_microgestures is a preprocessor guard. Do not pass it to API calls. +#define XR_META_hand_tracking_microgestures 1 +#define XR_META_hand_tracking_microgestures_SPEC_VERSION 1 +#define XR_META_HAND_TRACKING_MICROGESTURES_EXTENSION_NAME "XR_META_hand_tracking_microgestures" +static const XrStructureType XR_TYPE_SYSTEM_MICROGESTURE_PROPERTIES_META = (XrStructureType) 1000252000; +typedef struct XrSystemMicrogesturePropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsMicrogestures; +} XrSystemMicrogesturePropertiesMETA; + +#endif /* XR_META_hand_tracking_microgestures */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/OpenXR/meta_openxr_preview/meta_spatial_entity_group_sharing.h b/OpenXR/meta_openxr_preview/meta_spatial_entity_group_sharing.h new file mode 100755 index 0000000..ce06089 --- /dev/null +++ b/OpenXR/meta_openxr_preview/meta_spatial_entity_group_sharing.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#ifndef META_SPATIAL_ENTITY_GROUP_SHARING_H_ +#define META_SPATIAL_ENTITY_GROUP_SHARING_H_ 1 + +/********************** +This file is @generated from the OpenXR XML API registry. +Language : C99 +Copyright : (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +***********************/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef XR_META_spatial_entity_group_sharing + +// XR_META_spatial_entity_group_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_group_sharing 1 +#define XR_META_spatial_entity_group_sharing_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_GROUP_SHARING_EXTENSION_NAME "XR_META_spatial_entity_group_sharing" +// The group uuid could not be found within the runtime +static const XrResult XR_ERROR_SPACE_GROUP_NOT_FOUND_META = (XrResult) -1000572002; +static const XrStructureType XR_TYPE_SHARE_SPACES_RECIPIENT_GROUPS_META = (XrStructureType) 1000572000; +static const XrStructureType XR_TYPE_SPACE_GROUP_UUID_FILTER_INFO_META = (XrStructureType) 1000572001; +static const XrStructureType XR_TYPE_SYSTEM_SPATIAL_ENTITY_GROUP_SHARING_PROPERTIES_META = (XrStructureType) 1000572100; +typedef struct XrSystemSpatialEntityGroupSharingPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialEntityGroupSharing; +} XrSystemSpatialEntityGroupSharingPropertiesMETA; + +typedef struct XrShareSpacesRecipientGroupsMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t groupCount; + XrUuid* groups; +} XrShareSpacesRecipientGroupsMETA; + +typedef struct XrSpaceGroupUuidFilterInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuid groupUuid; +} XrSpaceGroupUuidFilterInfoMETA; + +#endif /* XR_META_spatial_entity_group_sharing */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/OpenXR/meta_openxr_preview/meta_spatial_entity_sharing.h b/OpenXR/meta_openxr_preview/meta_spatial_entity_sharing.h new file mode 100755 index 0000000..8e44e3f --- /dev/null +++ b/OpenXR/meta_openxr_preview/meta_spatial_entity_sharing.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#ifndef META_SPATIAL_ENTITY_SHARING_H_ +#define META_SPATIAL_ENTITY_SHARING_H_ 1 + +/********************** +This file is @generated from the OpenXR XML API registry. +Language : C99 +Copyright : (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +***********************/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef XR_META_spatial_entity_sharing + +// XR_META_spatial_entity_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_sharing 1 +#define XR_META_spatial_entity_sharing_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_SHARING_EXTENSION_NAME "XR_META_spatial_entity_sharing" +static const XrStructureType XR_TYPE_SYSTEM_SPATIAL_ENTITY_SHARING_PROPERTIES_META = (XrStructureType) 1000290000; +static const XrStructureType XR_TYPE_SHARE_SPACES_INFO_META = (XrStructureType) 1000290001; +static const XrStructureType XR_TYPE_EVENT_DATA_SHARE_SPACES_COMPLETE_META = (XrStructureType) 1000290002; +typedef struct XrSystemSpatialEntitySharingPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialEntitySharing; +} XrSystemSpatialEntitySharingPropertiesMETA; + +typedef struct XR_MAY_ALIAS XrShareSpacesRecipientBaseHeaderMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrShareSpacesRecipientBaseHeaderMETA; + +typedef struct XrShareSpacesInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; + const XrShareSpacesRecipientBaseHeaderMETA* recipientInfo; +} XrShareSpacesInfoMETA; + +typedef struct XrEventDataShareSpacesCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataShareSpacesCompleteMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrShareSpacesMETA)(XrSession session, const XrShareSpacesInfoMETA* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrShareSpacesMETA( + XrSession session, + const XrShareSpacesInfoMETA* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_META_spatial_entity_sharing */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/OpenXR/meta_openxr_preview/metax1_dynamic_object_tracker.h b/OpenXR/meta_openxr_preview/metax1_dynamic_object_tracker.h new file mode 100755 index 0000000..994e5a5 --- /dev/null +++ b/OpenXR/meta_openxr_preview/metax1_dynamic_object_tracker.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#ifndef METAX1_DYNAMIC_OBJECT_TRACKER_H_ +#define METAX1_DYNAMIC_OBJECT_TRACKER_H_ 1 + +/********************** +This file is @generated from the OpenXR XML API registry. +Language : C99 +Copyright : (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +***********************/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef XR_METAX1_dynamic_object_tracker + +// XR_METAX1_dynamic_object_tracker is a preprocessor guard. Do not pass it to API calls. +#define XR_METAX1_dynamic_object_tracker 1 +XR_DEFINE_HANDLE(XrDynamicObjectTrackerMETAX1) +#define XR_METAX1_dynamic_object_tracker_SPEC_VERSION 1 +#define XR_METAX1_DYNAMIC_OBJECT_TRACKER_EXTENSION_NAME "XR_METAX1_dynamic_object_tracker" +// XrDynamicObjectTrackerMETAX1 +static const XrObjectType XR_OBJECT_TYPE_DYNAMIC_OBJECT_TRACKER_METAX1 = (XrObjectType) 1000288000; +static const XrStructureType XR_TYPE_SYSTEM_DYNAMIC_OBJECT_TRACKER_PROPERTIES_METAX1 = (XrStructureType) 1000288000; +static const XrStructureType XR_TYPE_DYNAMIC_OBJECT_TRACKER_CREATE_INFO_METAX1 = (XrStructureType) 1000288001; +static const XrStructureType XR_TYPE_DYNAMIC_OBJECT_TRACKED_CLASSES_SET_INFO_METAX1 = (XrStructureType) 1000288002; +static const XrStructureType XR_TYPE_DYNAMIC_OBJECT_DATA_METAX1 = (XrStructureType) 1000288003; +static const XrStructureType XR_TYPE_EVENT_DATA_DYNAMIC_OBJECT_TRACKER_CREATE_RESULT_METAX1 = (XrStructureType) 1000288004; +static const XrStructureType XR_TYPE_EVENT_DATA_DYNAMIC_OBJECT_SET_TRACKED_CLASSES_RESULT_METAX1 = (XrStructureType) 1000288005; +static const XrSpaceComponentTypeFB XR_SPACE_COMPONENT_TYPE_DYNAMIC_OBJECT_DATA_METAX1 = (XrSpaceComponentTypeFB) 1000288006; + +typedef enum XrDynamicObjectClassMETAX1 { + XR_DYNAMIC_OBJECT_CLASS_KEYBOARD_METAX1 = 0, + XR_DYNAMIC_OBJECT_CLASS_METAX1_MAX_ENUM = 0x7FFFFFFF +} XrDynamicObjectClassMETAX1; +typedef struct XrSystemDynamicObjectTrackerPropertiesMETAX1 { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsDynamicObjectTracker; +} XrSystemDynamicObjectTrackerPropertiesMETAX1; + +typedef struct XrDynamicObjectTrackerCreateInfoMETAX1 { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrDynamicObjectTrackerCreateInfoMETAX1; + +typedef struct XrDynamicObjectTrackedClassesSetInfoMETAX1 { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t classCount; + const XrDynamicObjectClassMETAX1* classes; +} XrDynamicObjectTrackedClassesSetInfoMETAX1; + +typedef struct XrDynamicObjectDataMETAX1 { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrDynamicObjectClassMETAX1 classType; +} XrDynamicObjectDataMETAX1; + +typedef struct XrEventDataDynamicObjectTrackerCreateResultMETAX1 { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDynamicObjectTrackerMETAX1 handle; + XrResult result; +} XrEventDataDynamicObjectTrackerCreateResultMETAX1; + +typedef struct XrEventDataDynamicObjectSetTrackedClassesResultMETAX1 { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDynamicObjectTrackerMETAX1 handle; + XrResult result; +} XrEventDataDynamicObjectSetTrackedClassesResultMETAX1; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateDynamicObjectTrackerMETAX1)(XrSession session, const XrDynamicObjectTrackerCreateInfoMETAX1* createInfo, XrDynamicObjectTrackerMETAX1* dynamicObjectTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyDynamicObjectTrackerMETAX1)(XrDynamicObjectTrackerMETAX1 dynamicObjectTracker); +typedef XrResult (XRAPI_PTR *PFN_xrSetDynamicObjectTrackedClassesMETAX1)(XrDynamicObjectTrackerMETAX1 dynamicObjectTracker, const XrDynamicObjectTrackedClassesSetInfoMETAX1* setInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceDynamicObjectDataMETAX1)(XrSession session, XrSpace space, XrDynamicObjectDataMETAX1* dynamicObjectDataOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateDynamicObjectTrackerMETAX1( + XrSession session, + const XrDynamicObjectTrackerCreateInfoMETAX1* createInfo, + XrDynamicObjectTrackerMETAX1* dynamicObjectTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyDynamicObjectTrackerMETAX1( + XrDynamicObjectTrackerMETAX1 dynamicObjectTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetDynamicObjectTrackedClassesMETAX1( + XrDynamicObjectTrackerMETAX1 dynamicObjectTracker, + const XrDynamicObjectTrackedClassesSetInfoMETAX1* setInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceDynamicObjectDataMETAX1( + XrSession session, + XrSpace space, + XrDynamicObjectDataMETAX1* dynamicObjectDataOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_METAX1_dynamic_object_tracker */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/README.md b/README.md index 3a9812b..9e8dde5 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,16 @@ As these API definitions reach their final stages, they will be incorporated int |Sample Name |OpenXR features / extensions shown |Target devices| Extra notes |--|--|--|--| |[`XrBodyFaceEyeSocial`](Samples/XrSamples/XrBodyFaceEyeSocial/) |`XR_FB_body_tracking`, `XR_FB_eye_tracking_social`, `XR_FB_face_tracking` |Meta Quest Pro +|[`XrColocationDiscovery`](Samples/XrSamples/XrColocationDiscovery/) |`XR_META_colocation_discovery`, `XR_META_spatial_entity_group_sharing`, and `XR_META_spatial_entity_sharing` |Meta Quest 2 and later devices |[`XrColorSpaceFB`](Samples/XrSamples/XrColorSpaceFB/) |`XR_FB_color_space` |All Meta Quest devices -|[`XrCompositor_NativeActivity`](Samples/XrSamples/XrColorSpaceFB/) |`XR_KHR_composition_layer_cube`, `XR_KHR_composition_layer_cylinder`, `XR_KHR_composition_layer_equirect2`, `XR_FB_foveation` |All Meta Quest devices |Single file `C` sample -|[`XrControllers`](Samples/XrSamples/XrColorSpaceFB/) |`XR_FB_haptic_amplitude_envelope`, `XR_FB_haptic_pcm`|Meta Quest 2 and later devices -|[`XrHandDataSource`](Samples/XrSamples/XrColorSpaceFB/) |`XR_EXT_hand_tracking_data_source`|Meta Quest 2 and later devices -|[`XrHandsAndControllers`](Samples/XrSamples/XrColorSpaceFB/) |`XR_META_detached_controllers`,`XR_META_simultaneous_hands_and_controllers`|Meta Quest 3 and later| -|[`XrHandsFB`](Samples/XrSamples/XrColorSpaceFB/) |`XR_FB_hand_tracking_mesh`, `XR_FB_hand_tracking_capsules`,`XR_FB_hand_tracking_aim`|All Meta Quest devices| +|[`XrCompositor_NativeActivity`](Samples/XrSamples/XrCompositor_NativeActivity/) |`XR_KHR_composition_layer_cube`, `XR_KHR_composition_layer_cylinder`, `XR_KHR_composition_layer_equirect2`, `XR_FB_foveation` |All Meta Quest devices |Single file `C` sample +|[`XrControllers`](Samples/XrSamples/XrControllers/) |`XR_FB_haptic_amplitude_envelope`, `XR_FB_haptic_pcm`|Meta Quest 2 and later devices +|[`XrDynamicObjects`](Samples/XrSamples/XrDynamicObjects/) |`XR_META_dynamic_object_tracker`|Meta Quest 3 and later devices +|[`XrHandDataSource`](Samples/XrSamples/XrHandDataSource/) |`XR_EXT_hand_tracking_data_source`|Meta Quest 2 and later devices +|[`XrHandsAndControllers`](Samples/XrSamples/XrHandsAndControllers/) |`XR_META_detached_controllers`,`XR_META_simultaneous_hands_and_controllers`|Meta Quest 3 and later| +|[`XrHandsFB`](Samples/XrSamples/XrHandsFB/) |`XR_FB_hand_tracking_mesh`, `XR_FB_hand_tracking_capsules`,`XR_FB_hand_tracking_aim`|All Meta Quest devices| |[`XrHandTrackingWideMotionMode`](Samples/XrSamples/XrHandTrackingWideMotionMode/) |`XR_META_hand_tracking_wide_motion_mode`|Meta Quest 3 and later| +|[`XrMicrogestures`](Samples/XrSamples/XrMicrogestures/) |`XR_META_hand_tracking_microgestures`|Meta Quest 2 and later devices| |[`XrInput`](Samples/XrSamples/XrInput/) |OpenXR Action System|All Meta Quest devices| |[`XrKeyboard`](Samples/XrSamples/XrKeyboard/) |`XR_FB_keyboard_tracking`,`XR_FB_passthrough_keyboard_hands`,`XR_FB_render_model`|Meta Quest 2 and later| |[`XrPassthrough`](Samples/XrSamples/XrPassthrough/) |`XR_FB_passthrough`|All Meta Quest devices|Demonstrates the use of still and animated styles, selective and projected passthrough. diff --git a/Samples/1stParty/OVR/Include/JniUtils-inl.h b/Samples/1stParty/OVR/Include/JniUtils-inl.h index 6350012..ce1951c 100755 --- a/Samples/1stParty/OVR/Include/JniUtils-inl.h +++ b/Samples/1stParty/OVR/Include/JniUtils-inl.h @@ -91,8 +91,15 @@ inline jint ovr_AttachCurrentThread(JavaVM* vm, JNIEnv** jni, void* args) { } } + // Propagate the thread name into the JVM, unless args are set explicitly. + JavaVMAttachArgs defaultArgs = { + .version = JNI_VERSION_1_2, + .name = threadName, + .group = nullptr, + }; + // Attach the thread to the JVM. - const jint rtn = attachCurrentThread(vm, jni, args); + const jint rtn = attachCurrentThread(vm, jni, args != nullptr ? args : &defaultArgs); if (rtn != JNI_OK) { OVR_FAIL("AttachCurrentThread returned %i", rtn); } diff --git a/Samples/1stParty/OVR/Include/JniUtils.h b/Samples/1stParty/OVR/Include/JniUtils.h index 7f146eb..b24a8ea 100755 --- a/Samples/1stParty/OVR/Include/JniUtils.h +++ b/Samples/1stParty/OVR/Include/JniUtils.h @@ -225,10 +225,13 @@ class JavaString : public JavaObject { public: JavaString(JNIEnv* jni_, char const* string_) : JavaObject(jni_, NULL) { #if defined(OVR_OS_ANDROID) + OVR_ASSERT(string_); SetJObject(GetJNI()->NewStringUTF(string_)); if (GetJNI()->ExceptionOccurred()) { + OVR_ASSERT(!(bool)"JNI exception occurred calling NewStringUTF!"); OVR_LOG("JNI exception occurred calling NewStringUTF!"); } + OVR_ASSERT(GetJObject()); #else OVR_UNUSED(string_); #endif @@ -239,6 +242,9 @@ class JavaString : public JavaObject { } jstring GetJString() const { +#if defined(OVR_OS_ANDROID) + OVR_ASSERT(GetJObject()); +#endif return static_cast(GetJObject()); } }; @@ -255,11 +261,14 @@ class JavaString : public JavaObject { class JavaUTFChars : public JavaString { public: JavaUTFChars(JNIEnv* jni_, jstring const string_) : JavaString(jni_, string_), UTFString(NULL) { + OVR_ASSERT(string_); #if defined(OVR_OS_ANDROID) UTFString = GetJNI()->GetStringUTFChars(GetJString(), NULL); if (GetJNI()->ExceptionOccurred()) { + OVR_ASSERT(!(bool)"JNI exception occurred calling GetStringUTFChars!"); OVR_LOG("JNI exception occurred calling GetStringUTFChars!"); } + OVR_ASSERT(UTFString); #endif } @@ -268,15 +277,18 @@ class JavaUTFChars : public JavaString { OVR_ASSERT(UTFString != NULL); GetJNI()->ReleaseStringUTFChars(GetJString(), UTFString); if (GetJNI()->ExceptionOccurred()) { + OVR_ASSERT(!(bool)"JNI exception occurred calling ReleaseStringUTFChars!"); OVR_LOG("JNI exception occurred calling ReleaseStringUTFChars!"); } #endif } char const* ToStr() const { + OVR_ASSERT(UTFString); return UTFString; } operator char const*() const { + OVR_ASSERT(UTFString); return UTFString; } diff --git a/Samples/1stParty/OVR/Include/OVR_Math.h b/Samples/1stParty/OVR/Include/OVR_Math.h index 32b764e..7975e07 100755 --- a/Samples/1stParty/OVR/Include/OVR_Math.h +++ b/Samples/1stParty/OVR/Include/OVR_Math.h @@ -482,14 +482,14 @@ inline bool EssentiallyEqual(float a, float b) { ((fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits::epsilon()); } -inline bool DefinitelyGreaterThan(float a, float b) -{ - return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits::epsilon()); +inline bool DefinitelyGreaterThan(float a, float b) { + return (a - b) > + ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits::epsilon()); } -inline bool DefinitelyLessThan(float a, float b) -{ - return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits::epsilon()); +inline bool DefinitelyLessThan(float a, float b) { + return (b - a) > + ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits::epsilon()); } // Conversion functions between degrees and radians @@ -2106,11 +2106,11 @@ class Quat { return *this * s; } - inline void ChangeHemisphere(){ - x = -x; - y = -y; - z = -z; - w = -w; + inline void ChangeHemisphere() { + x = -x; + y = -y; + z = -z; + w = -w; } inline void EnsureSameHemisphere(const Quat& o) { @@ -2396,7 +2396,7 @@ class Quat { if (c) *c = S * D * static_cast( - atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm)); + atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm)); } else if (c2 > T(1) - singularityRadius) { // North pole singularity if (a) *a = T(0); @@ -2405,12 +2405,12 @@ class Quat { if (c) *c = S * D * static_cast( - atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm)); + atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm)); } else { if (a) *a = S * D * static_cast( - atan2(psign * w * Q[m] + Q[A1] * Q[A2], w * Q[A2] - psign * Q[A1] * Q[m])); + atan2(psign * w * Q[m] + Q[A1] * Q[A2], w * Q[A2] - psign * Q[A1] * Q[m])); if (b) *b = S * D * static_cast(acos(c2)); if (c) @@ -2424,30 +2424,30 @@ class Quat { return isnan(x) || isnan(y) || isnan(z) || isnan(w); } - Quat GetTwistQuaternion(const Vector3& twistAxis) const{ + Quat GetTwistQuaternion(const Vector3& twistAxis) const { Vector3 rotAxis(x, y, z); float dotProd = rotAxis.Dot(twistAxis); auto projected = twistAxis * dotProd; Quat twist(projected.x, projected.y, projected.z, w); twist.Normalize(); - if(dotProd < 0){ + if (dotProd < 0) { twist.ChangeHemisphere(); } return twist; } - float GetTwistAngle(const Vector3& twistAxis) const{ + float GetTwistAngle(const Vector3& twistAxis) const { Vector3 rotAxis(x, y, z); float dotProd = rotAxis.Dot(twistAxis); auto projected = twistAxis * dotProd; Quat twist(projected.x, projected.y, projected.z, w); twist.Normalize(); - return dotProd < 0? -twist.Angle():twist.Angle(); + return dotProd < 0 ? -twist.Angle() : twist.Angle(); } - void GetSwingTwist(Quat* swing, Quat* twist, const Vector3& twistAxis) const{ - (*twist) = GetTwistQuaternion(twistAxis); - (*swing) = (*this) * twist->Inverse(); + void GetSwingTwist(Quat* swing, Quat* twist, const Vector3& twistAxis) const { + (*twist) = GetTwistQuaternion(twistAxis); + (*swing) = (*this) * twist->Inverse(); } }; @@ -3177,9 +3177,15 @@ class Matrix4 { scale->y = Vector3(M[0][1], M[1][1], M[2][1]).Length(); scale->z = Vector3(M[0][2], M[1][2], M[2][2]).Length(); Matrix3 rotationM( - M[0][0]/scale->x, M[0][1]/scale->y, M[0][2]/scale->z, - M[1][0]/scale->x, M[1][1]/scale->y, M[1][2]/scale->z, - M[2][0]/scale->x, M[2][1]/scale->y, M[2][2]/scale->z); + M[0][0] / scale->x, + M[0][1] / scale->y, + M[0][2] / scale->z, + M[1][0] / scale->x, + M[1][1] / scale->y, + M[1][2] / scale->z, + M[2][0] / scale->x, + M[2][1] / scale->y, + M[2][2] / scale->z); // Because we may have flipY applied to modelMatrix, we may have a matrix which has both TRS // and reflection. If we found there are odd number of reflections (i.e. determinate is -1), // we arbitrarily flip one axis. @@ -3208,7 +3214,6 @@ class Matrix4 { return Matrix4(Pose(r_lerped, t_lerped)) * Matrix4::Scaling(s_lerped); } - // Creates a matrix that converts the vertices from one coordinate system // to another. static Matrix4 AxisConversion(const WorldAxes& to, const WorldAxes& from) { @@ -4857,15 +4862,6 @@ typedef MapRange MapRanged; } // Namespace OVR -namespace std { - template - struct hash> { - size_t operator()(const OVR::Vector2& v) const { - return hash()(v.x) ^ hash()(v.y); - } - }; -} - #if defined(_MSC_VER) #pragma warning(pop) #endif diff --git a/Samples/3rdParty/CMakeLists.txt b/Samples/3rdParty/CMakeLists.txt index 5ce3a04..d31f22c 100755 --- a/Samples/3rdParty/CMakeLists.txt +++ b/Samples/3rdParty/CMakeLists.txt @@ -114,7 +114,7 @@ function(find_openxr_loader) FetchContent_Declare( openxr GIT_REPOSITORY https://github.com/KhronosGroup/OpenXR-SDK.git - GIT_TAG release-1.1.38 + GIT_TAG release-1.1.40 SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/openxr ) diff --git a/Samples/3rdParty/khronos/openxr/OpenXR-SDK/src/common/xr_linear.h b/Samples/3rdParty/khronos/openxr/OpenXR-SDK/src/common/xr_linear.h index 2b295ed..cc525d3 100755 --- a/Samples/3rdParty/khronos/openxr/OpenXR-SDK/src/common/xr_linear.h +++ b/Samples/3rdParty/khronos/openxr/OpenXR-SDK/src/common/xr_linear.h @@ -134,7 +134,7 @@ static const XrColor4f XrColorCyan = {0.0f, 1.0f, 1.0f, 1.0f}; static const XrColor4f XrColorLightGrey = {0.7f, 0.7f, 0.7f, 1.0f}; static const XrColor4f XrColorDarkGrey = {0.3f, 0.3f, 0.3f, 1.0f}; -typedef enum GraphicsAPI { GRAPHICS_VULKAN, GRAPHICS_OPENGL, GRAPHICS_OPENGL_ES, GRAPHICS_D3D } GraphicsAPI; +typedef enum GraphicsAPI { GRAPHICS_VULKAN, GRAPHICS_OPENGL, GRAPHICS_OPENGL_ES, GRAPHICS_D3D, GRAPHICS_METAL } GraphicsAPI; // Column-major, pre-multiplied. This type does not exist in the OpenXR API and is provided for convenience. typedef struct XrMatrix4x4f { diff --git a/Samples/SampleXrFramework/Src/XrApp.cpp b/Samples/SampleXrFramework/Src/XrApp.cpp index 7061441..94ba3cc 100755 --- a/Samples/SampleXrFramework/Src/XrApp.cpp +++ b/Samples/SampleXrFramework/Src/XrApp.cpp @@ -216,6 +216,10 @@ void XrApp::HandleSessionStateChanges(XrSessionState state) { } } +XrResult XrApp::PollXrEvent(XrEventDataBuffer* eventDataBuffer) { + return xrPollEvent(Instance, eventDataBuffer); +} + void XrApp::HandleXrEvents() { XrEventDataBuffer eventDataBuffer = {}; @@ -225,7 +229,7 @@ void XrApp::HandleXrEvents() { baseEventHeader->type = XR_TYPE_EVENT_DATA_BUFFER; baseEventHeader->next = NULL; XrResult r; - OXR(r = xrPollEvent(Instance, &eventDataBuffer)); + OXR(r = PollXrEvent(&eventDataBuffer)); if (r != XR_SUCCESS) { break; } @@ -567,9 +571,7 @@ void XrApp::GetInitialSceneUri(std::string& sceneUri) const { sceneUri = "apk:///assets/box.ovrscene"; } -// Called one time when the application process starts. -// Returns true if the application initialized successfully. -bool XrApp::Init(const xrJava& context) { +XrInstance XrApp::CreateInstance(const xrJava& context) { #if defined(ANDROID) // Loader PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; @@ -683,16 +685,23 @@ bool XrApp::Init(const xrJava& context) { instanceCreateInfo.enabledExtensionCount = extensions.size(); instanceCreateInfo.enabledExtensionNames = extensions.data(); + XrInstance instance; XrResult initResult; - OXR(initResult = xrCreateInstance(&instanceCreateInfo, &Instance)); + OXR(initResult = xrCreateInstance(&instanceCreateInfo, &instance)); if (initResult != XR_SUCCESS) { ALOGE("Failed to create XR instance: %d.", initResult); exit(1); } FreeInstanceCreateInfoNextChain(nextChain); - /// + return instance; +} + +// Called one time when the application process starts. +// Returns true if the application initialized successfully. +bool XrApp::Init(const xrJava& context) { + Instance = CreateInstance(context); XrInstanceProperties instanceInfo = {XR_TYPE_INSTANCE_PROPERTIES}; OXR(xrGetInstanceProperties(Instance, &instanceInfo)); ALOGV( @@ -705,6 +714,7 @@ bool XrApp::Init(const xrJava& context) { XrSystemGetInfo systemGetInfo = {XR_TYPE_SYSTEM_GET_INFO}; systemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + XrResult initResult; XrSystemId systemId; OXR(initResult = xrGetSystem(Instance, &systemGetInfo, &systemId)); if (initResult != XR_SUCCESS) { @@ -1090,10 +1100,14 @@ void XrApp::EndSession() { ovrEgl_DestroyContext(&Egl); } +void XrApp::DestroyInstance() { + OXR(xrDestroyInstance(Instance)); +} + // Called one time when the applicatoin process exits void XrApp::Shutdown(const xrJava& context) { AppShutdown(&context); - OXR(xrDestroyInstance(Instance)); + DestroyInstance(); Clear(); } diff --git a/Samples/SampleXrFramework/Src/XrApp.h b/Samples/SampleXrFramework/Src/XrApp.h index c0aad64..07da839 100755 --- a/Samples/SampleXrFramework/Src/XrApp.h +++ b/Samples/SampleXrFramework/Src/XrApp.h @@ -491,8 +491,9 @@ class XrApp { return CurrentSpace; } - XrActionSet CreateActionSet(uint32_t priority, const char* name, const char* localizedName); - XrAction CreateAction( + virtual XrActionSet + CreateActionSet(uint32_t priority, const char* name, const char* localizedName); + virtual XrAction CreateAction( XrActionSet actionSet, XrActionType type, const char* actionName, @@ -522,6 +523,11 @@ class XrApp { // Called to deal with lifetime void HandleSessionStateChanges(XrSessionState state); + protected: + virtual XrInstance CreateInstance(const xrJava& context); + virtual void DestroyInstance(); + virtual XrResult PollXrEvent(XrEventDataBuffer* eventDataBuffer); + private: // Called one time when the application process starts. // Returns true if the application initialized successfully. diff --git a/Samples/XrSamples/XrBodyFaceEyeSocial/Projects/Android/build.gradle b/Samples/XrSamples/XrBodyFaceEyeSocial/Projects/Android/build.gradle index bf48162..de70b6a 100755 --- a/Samples/XrSamples/XrBodyFaceEyeSocial/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrBodyFaceEyeSocial/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrColocationDiscovery/CMakeLists.txt b/Samples/XrSamples/XrColocationDiscovery/CMakeLists.txt new file mode 100755 index 0000000..2b7302b --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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. +project(xrcolocationdiscovery) + +if(NOT TARGET OpenXR::openxr_loader) + find_package(OpenXR REQUIRED) +endif() + +file(GLOB_RECURSE SRC_FILES + Src/*.c + Src/*.cpp +) + +if(ANDROID) + add_library(${PROJECT_NAME} MODULE ${SRC_FILES}) + target_include_directories(${PROJECT_NAME} PUBLIC ${ANDROID_NDK}/sources/android/native_app_glue) + target_link_libraries(${PROJECT_NAME} PRIVATE + android + EGL + GLESv3 + log + ktx + ) + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-u ANativeActivity_onCreate") +elseif(WIN32) + add_definitions(-D_USE_MATH_DEFINES) + add_executable(${PROJECT_NAME} ${SRC_FILES}) + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/assets" + "$/assets" + VERBATIM) + + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/SampleXrFramework/res/raw" + "$/font/res/raw" + VERBATIM) +endif() + +# Common across platforms +target_include_directories(${PROJECT_NAME} PRIVATE Src) +target_link_libraries(${PROJECT_NAME} PRIVATE samplexrframework) diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/AndroidManifest.xml b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/AndroidManifest.xml new file mode 100755 index 0000000..8fa849b --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/AndroidManifest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.bat b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.bat new file mode 100755 index 0000000..b9bbb04 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.bat @@ -0,0 +1,45 @@ +REM Copyright (c) Meta Platforms, Inc. and affiliates. +REM All rights reserved. +REM +REM Licensed under the Oculus SDK License Agreement (the "License"); +REM you may not use the Oculus SDK except in compliance with the License, +REM which is provided at the time of installation or download, or which +REM otherwise accompanies this software in either electronic or hard copy form. +REM +REM You may obtain a copy of the License at +REM https://developer.oculus.com/licenses/oculussdk/ +REM +REM Unless required by applicable law or agreed to in writing, the Oculus SDK +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +@rem Only edit the master copy of this file in SDK_ROOT/bin/scripts/build/perproject + +@setlocal enableextensions enabledelayedexpansion + +@if not exist "build.gradle" @echo Build script must be executed from project directory. & goto :Abort + +@set P=.. + +:TryAgain + +@rem @echo P = %P% + +@if exist "%P%\bin\scripts\build\build.py.bat" goto :Found + +@if exist "%P%\bin\scripts\build" @echo "Could not find build.py.bat" & goto :Abort + +@set P=%P%\.. + +@goto :TryAgain + +:Found + +@set P=%P%\bin\scripts\build +@call %P%\build.py.bat %1 %2 %3 %4 %5 +@goto :End + +:Abort + +:End diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.gradle b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.gradle new file mode 100755 index 0000000..6d9a124 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.gradle @@ -0,0 +1,96 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.3" + } +} + +repositories { + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.oculus.sdk.xrcolocationdiscovery" + minSdk 26 + targetSdk 32 + versionCode 1 + versionName "1.0" + + // override app plugin abiFilters for 64-bit support + externalNativeBuild { + ndk { + abiFilters 'arm64-v8a' + } + ndkBuild { + abiFilters 'arm64-v8a' + } + cmake { + targets "xrcolocationdiscovery" + } + } + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['../../java'] + assets.srcDirs = ['../../assets'] + res.srcDirs = ['../../res'] + } + } + + buildTypes { + debug { + debuggable true + } + + release { + debuggable false + } + } + + externalNativeBuild { + cmake { + path file('../../../../CMakeLists.txt') + } + } + + // Enable prefab support for the OpenXR AAR + buildFeatures { + prefab true + } +} + +dependencies { + // Package/application AndroidManifest.xml properties, plus headers and libraries + // exposed to CMake + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' +} diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.py b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.py new file mode 100755 index 0000000..82c0793 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/build.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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 first bit of code is common bootstrapping code +# to determine the SDK root, and to set up the import +# path for additional python code. + +# begin bootstrap +import os +import sys + + +def init(): + root = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) + os.chdir(root) # make sure we are always executing from the project directory + while os.path.isdir(os.path.join(root, "bin/scripts/build")) == False: + root = os.path.realpath(os.path.join(root, "..")) + if ( + len(root) <= 5 + ): # Should catch both Posix and Windows root directories (e.g. '/' and 'C:\') + print("Unable to find SDK root. Exiting.") + sys.exit(1) + root = os.path.abspath(root) + os.environ["OCULUS_SDK_PATH"] = root + sys.path.append(root + "/bin/scripts/build") + + +init() +import ovrbuild + +ovrbuild.init() +# end bootstrap + + +ovrbuild.build() diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle.properties b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle.properties new file mode 100644 index 0000000..3e927b1 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.jar b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.properties b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew.bat b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew.bat new file mode 100755 index 0000000..f127cfd --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/XrSamples/XrColocationDiscovery/Projects/Android/settings.gradle b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/settings.gradle new file mode 100755 index 0000000..08f9e7f --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Projects/Android/settings.gradle @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +rootProject.name = "XrColocationDiscovery" diff --git a/Samples/XrSamples/XrColocationDiscovery/README.md b/Samples/XrSamples/XrColocationDiscovery/README.md new file mode 100644 index 0000000..a680f90 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/README.md @@ -0,0 +1,11 @@ +# OpenXR Colocation Discovery Sample + +## Overview +Colocation Discovery allows apps to discover physically colocated devices running the same app. + +The `XR_META_colocation_discovery` extension allows an app to: +* Start/stop advertising a unique colocation advertisement UUID + an optional buffer of metadata +* Start/stop discoverying nearby colocation advertisements started by the same app. + +## The Sample +The sample shows a simple workflow of how `XR_META_colocation_discovery` can be used to discovery nearby devices. Additionally, the sample shows how this can be used with the `XR_META_spatial_entity_group_sharing` extension to share a Spatial Anchor with these nearby devices to create common reference frames. diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/Anchor.h b/Samples/XrSamples/XrColocationDiscovery/Src/Anchor.h new file mode 100755 index 0000000..aee99e9 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/Anchor.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include +#include "OVR_Math.h" +#include + +struct Anchor { + std::string Uuid; + std::optional Space; + OVR::Vector4f Color; +}; diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.cpp b/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.cpp new file mode 100755 index 0000000..80aa410 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include "CubesRenderer.h" +#include + +void CubesRenderer::Add(const OVR::Vector4f& colorOptions) { + // Add cube at the pose to render + OVRFW::GeometryBuilder templateCubeGeometry; + templateCubeGeometry.Add( + OVRFW::BuildUnitCubeDescriptor(), OVRFW::GeometryBuilder::kInvalidIndex, colorOptions); + + OVRFW::GeometryRenderer cubeRenderer; + CubesRenderer.push_back(cubeRenderer); + + CubesRenderer.back().ChannelControl = OVR::Vector4f(1, 1, 1, 1); + CubesRenderer.back().Init(templateCubeGeometry.ToGeometryDescriptor()); + + const float cubeLength = 0.05; + CubesRenderer.back().SetScale({cubeLength, cubeLength, cubeLength}); +} + +void CubesRenderer::Shutdown() { + for (auto& cube : CubesRenderer) { + cube.Shutdown(); + } +} + +void CubesRenderer::Render(std::vector& surfaces) { + for (auto& cube : CubesRenderer) { + cube.Render(surfaces); + } +} + +void CubesRenderer::UpdatePoses(const std::vector& poses) { + while (!CubesRenderer.empty()) { + auto cube = CubesRenderer.back(); + CubesRenderer.pop_back(); + cube.Shutdown(); + } + + for (size_t i = 0; i < poses.size(); i++) { + this->Add(poses[i].Color); + CubesRenderer[i].SetPose(poses[i].Pose); + CubesRenderer[i].Update(); + } +} diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.h b/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.h new file mode 100755 index 0000000..6cf63c0 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/CubesRenderer.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +#include +#include +#include "Render/GeometryRenderer.h" +#include "Render/SurfaceRender.h" +#include "OVR_Math.h" +#include "IAnchorsRenderer.h" + +class CubesRenderer : public IAnchorsRenderer { + public: + ~CubesRenderer() override{}; + void Shutdown() override; + void Render(std::vector& surfaces) override; + void UpdatePoses(const std::vector& poses) override; + + private: + void Add(const OVR::Vector4f& colorOptions) override; + + std::deque CubesRenderer; +}; diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/HexStringHelper.h b/Samples/XrSamples/XrColocationDiscovery/Src/HexStringHelper.h new file mode 100755 index 0000000..82e964d --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/HexStringHelper.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +#include + +#include + +class HexStringHelper { + public: + static bool HexStringToUuid(const std::string& hexStr, XrUuidEXT& uuid) { + if (hexStr.size() != 36) { + return false; + } + if (hexStr[8] != '-' || hexStr[13] != '-' || hexStr[18] != '-' || hexStr[23] != '-') { + return false; + } + for (int i = 0; i < 4; i++) { + if (!ParseByte(hexStr, (2 * i), uuid.data[i])) { + return false; + } + } + for (int i = 4; i < 6; i++) { + if (!ParseByte(hexStr, (2 * i) + 1, uuid.data[i])) { + return false; + } + } + for (int i = 6; i < 8; i++) { + if (!ParseByte(hexStr, (2 * i) + 2, uuid.data[i])) { + return false; + } + } + for (int i = 8; i < 10; ++i) { + if (!ParseByte(hexStr, (2 * i) + 3, uuid.data[i])) { + return false; + } + } + for (int i = 10; i < 16; ++i) { + if (!ParseByte(hexStr, (2 * i) + 4, uuid.data[i])) { + return false; + } + } + return true; + } + + static std::string UuidToHexString(const XrUuidEXT& uuid) { + std::string output; + output.reserve(36); + for (int i = 0; i < 16; i++) { + AppendByte(uuid.data[i], output); + if (i == 3 || i == 5 || i == 7 || i == 9) { + output += '-'; + } + } + return output; + } + + private: + inline static bool ParseDigit(std::uint8_t digit, std::uint8_t& dst) { + if (digit >= '0' && digit <= '9') { + dst = digit - '0'; + return true; + } else if (digit >= 'a' && digit <= 'f') { + dst = digit - 'a' + 10; + return true; + } else if (digit >= 'A' && digit <= 'F') { + dst = digit - 'A' + 10; + return true; + } else { + return false; + } + } + + inline static bool ParseByte(const std::string& str, int pos, std::uint8_t& dst) { + std::uint8_t hi; + std::uint8_t lo; + if (!ParseDigit(str[pos + 0], hi)) { + return false; + } + if (!ParseDigit(str[pos + 1], lo)) { + return false; + } + dst = (hi << 4) | lo; + return true; + } + + inline static void AppendDigit(std::uint8_t byte, std::string& str) { + if (byte < 10) { + str += static_cast('0' + byte); + } else { + str += static_cast('a' + (byte - 10)); + } + } + + inline static void AppendByte(std::uint8_t byte, std::string& str) { + AppendDigit((byte >> 4) & 0x0f, str); + AppendDigit((byte >> 0) & 0x0f, str); + } +}; diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/IAnchorsRenderer.h b/Samples/XrSamples/XrColocationDiscovery/Src/IAnchorsRenderer.h new file mode 100755 index 0000000..5e02a30 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/IAnchorsRenderer.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +#include +#include "OVR_Math.h" +#include "Render/SurfaceRender.h" + +struct AnchorPose { + OVR::Posef Pose; + OVR::Vector4f Color; +}; + +class IAnchorsRenderer { + public: + virtual ~IAnchorsRenderer(){}; + virtual void Shutdown() = 0; + virtual void Render(std::vector& surfaces) = 0; + virtual void UpdatePoses(const std::vector& poses) = 0; + + private: + virtual void Add(const OVR::Vector4f& colorOptions) = 0; +}; diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/SimpleListUI.h b/Samples/XrSamples/XrColocationDiscovery/Src/SimpleListUI.h new file mode 100755 index 0000000..08c0de6 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/SimpleListUI.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +/******************************************************************************* + +Filename : SimpleListUI.h +Content : +Created : +Authors : +Language : C++ +Copyright: Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved. + +*******************************************************************************/ + +#include +#include +#include + +#include "XrApp.h" + +#include "Input/SkeletonRenderer.h" +#include "Input/ControllerRenderer.h" +#include "Input/TinyUI.h" + +class SimpleListUI { + public: + SimpleListUI( + OVRFW::TinyUI& ui, + const OVR::Vector3f& position, + const OVR::Vector2f& size, + const size_t numRows, + const std::string& header) { + const float rowHeight = size[1] / numRows; + for (size_t i = 0; i < numRows; i++) { + Rows.push_back(ui.AddLabel( + "", + OVR::Vector3f( + position[0], + position[1] + ((numRows - i - 1) * rowHeight / 1000.0), + position[2]), + OVR::Vector2f(size[0], rowHeight / 2.0))); + } + Header = ui.AddLabel( + header, + OVR::Vector3f(position[0], position[1] + (numRows * rowHeight / 1000.0), position[2]), + OVR::Vector2f(size[0], rowHeight / 2.0)); + } + + void SetHeader(const std::string& header) { + Header->SetText(header.c_str()); + } + + void AppendRow(const std::string& text) { + ALOGV("Appending row to list: %s", text.c_str()); + if (NumRowsFilled == Rows.size()) { + for (size_t i = 0; i < Rows.size() - 1; i++) { + Rows[i]->SetText(Rows[i + 1]->GetText().c_str()); + } + } else { + NumRowsFilled++; + } + Rows[NumRowsFilled - 1]->SetText(text.c_str()); + } + + void Clear() { + for (auto& row : Rows) { + row->SetText(""); + } + NumRowsFilled = 0; + } + + void SetVisible(bool visible = false) { + Header->SetVisible(visible); + for (auto& row : Rows) { + row->SetVisible(visible); + } + } + + private: + std::vector Rows; + size_t NumRowsFilled = 0; + OVRFW::VRMenuObject* Header; +}; diff --git a/Samples/XrSamples/XrColocationDiscovery/Src/main.cpp b/Samples/XrSamples/XrColocationDiscovery/Src/main.cpp new file mode 100755 index 0000000..2ec46b8 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/Src/main.cpp @@ -0,0 +1,982 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/******************************************************************************* + +Filename : Main.cpp +Created : +Authors : +Language : C++ +Copyright: Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved. + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Anchor.h" +#include "CubesRenderer.h" +#include "HexStringHelper.h" +#include "OVR_JSON.h" +#include "SimpleListUI.h" +#include "XrApp.h" +#include "Input/TinyUI.h" +#include "Input/ControllerRenderer.h" +#include "Misc/Log.h" +#include "Render/SimpleBeamRenderer.h" + +#include +#include +#include +#include +#include + +#define DECLARE_FUNCTION_PTR(functionName) PFN_##functionName functionName = nullptr; +#define HOOKUP_FUNCTION_PTR(functionName) \ + OXR(xrGetInstanceProcAddr(GetInstance(), #functionName, (PFN_xrVoidFunction*)(&functionName))); +#define VALIDATE_FUNCTION_PTR(functionName) \ + if (functionName == nullptr) { \ + Logs->AppendRow("Skipping: this app does not currently support " #functionName); \ + return; \ + } +#define K_MAX_PERSISTENT_SPACES 32 + +class XrColocationDiscoveryApp : public OVRFW::XrApp { + public: + XrColocationDiscoveryApp() : OVRFW::XrApp() { + BackgroundColor = OVR::Vector4f(0.0f, 0.0f, 0.0f, 0.0f); + } + + // Must return true if the application initializes successfully. + virtual bool AppInit(const xrJava* context) override { + if (false == Ui.Init(context, GetFileSys())) { + ALOG("TinyUI::Init FAILED."); + return false; + } + AnchorsRenderer = std::make_unique(); + + HookExtensions(); + /// Build UI + Logs = std::make_unique( + Ui, OVR::Vector3f(0.2f, 1.0f, -2.0f), OVR::Vector2f(700.0f, 1000.0f), 10, "Logs"); + + Results = std::make_unique( + Ui, OVR::Vector3f(1.7f, 1.0f, -2.0f), OVR::Vector2f(700.0f, 1000.0f), 10, ""); + + Banner = Ui.AddLabel( + "Click \"A\" on your right controller to place a shared anchor.\n" + "Once the shared anchor is created, you may start an advertisement to other nearby devices.\n" + "When nearby devices discover you, they will attempt to localize your shared anchor.", + OVR::Vector3f(0.2f, 2.2f, -2.0f), + OVR::Vector2f(1400.0f, 150.0f)); + + const OVR::Vector2f buttonSize = {400.0f, 150.0f}; + const float buttonXPos = -1.0f; + const float buttonZPos = -2.0f; + + StartDiscoveryButton = Ui.AddButton( + "Start Colocation Discovery", {buttonXPos, 1.9f, buttonZPos}, buttonSize, [this]() { + StartDiscovery(); + }); + StopDiscoveryButton = Ui.AddButton( + "Stop Colocation Discovery", {buttonXPos, 1.9f, buttonZPos}, buttonSize, [this]() { + StopDiscovery(); + }); + StartAdvertisementButton = Ui.AddButton( + "Start Colocation Advertisement", {buttonXPos, 1.6f, buttonZPos}, buttonSize, [this]() { + StartColocationAdvertisement(); + }); + StopAdvertisementButton = Ui.AddButton( + "Stop Colocation Advertisement", {buttonXPos, 1.6f, buttonZPos}, buttonSize, [this]() { + StopColocationAdvertisement(); + }); + + return true; + } + + virtual void AppShutdown(const xrJava* context) override { + OVRFW::XrApp::AppShutdown(context); + Ui.Shutdown(); + AnchorsRenderer->Shutdown(); + } + + virtual void PreProjectionAddLayer(xrCompositorLayerUnion* layers, int& layerCount) override { + AddPassthroughLayer(layers, layerCount); + } + + // Returns a list of OpenXR extensions requested for this app + // Note that the sample framework will filter out any extension + // that is not listed as supported. + virtual std::vector GetExtensions() override { + std::vector extensions = XrApp::GetExtensions(); + extensions.push_back(XR_FB_PASSTHROUGH_EXTENSION_NAME); + extensions.push_back(XR_META_COLOCATION_DISCOVERY_EXTENSION_NAME); + extensions.push_back(XR_FB_SPATIAL_ENTITY_EXTENSION_NAME); + extensions.push_back(XR_FB_SPATIAL_ENTITY_QUERY_EXTENSION_NAME); + extensions.push_back(XR_META_SPATIAL_ENTITY_SHARING_EXTENSION_NAME); + extensions.push_back(XR_META_SPATIAL_ENTITY_GROUP_SHARING_EXTENSION_NAME); + + return extensions; + } + + void AddPassthroughLayer(xrCompositorLayerUnion* layers, int& layerCount) { + if (PassthroughLayer != XR_NULL_HANDLE) { + XrCompositionLayerPassthroughFB passthroughLayer = { + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB}; + passthroughLayer.layerHandle = PassthroughLayer; + passthroughLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; + passthroughLayer.space = XR_NULL_HANDLE; + layers[layerCount++].Passthrough = passthroughLayer; + } + } + + virtual bool SessionInit() override { + /// Init session bound objects + if (false == ControllerRenderL.Init(true)) { + ALOG("AppInit::Init L controller renderer FAILED."); + return false; + } + if (false == ControllerRenderR.Init(false)) { + ALOG("AppInit::Init R controller renderer FAILED."); + return false; + } + BeamRenderer.Init(GetFileSys(), nullptr, OVR::Vector4f(1.0f), 1.0f); + + // Create and start passthrough + { + XrPassthroughCreateInfoFB ptci{XR_TYPE_PASSTHROUGH_CREATE_INFO_FB}; + XrResult result; + OXR(result = xrCreatePassthroughFB(GetSession(), &ptci, &Passthrough)); + + if (XR_SUCCEEDED(result)) { + ALOGV("Creating passthrough layer"); + XrPassthroughLayerCreateInfoFB plci{XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB}; + plci.passthrough = Passthrough; + plci.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB; + OXR(xrCreatePassthroughLayerFB(GetSession(), &plci, &PassthroughLayer)); + + ALOGV("Setting passthrough style"); + XrPassthroughStyleFB style{XR_TYPE_PASSTHROUGH_STYLE_FB}; + OXR(xrPassthroughLayerResumeFB(PassthroughLayer)); + style.textureOpacityFactor = 0.5f; + style.edgeColor = {0.0f, 0.0f, 0.0f, 0.0f}; + OXR(xrPassthroughLayerSetStyleFB(PassthroughLayer, &style)); + } else { + ALOGV("Create passthrough failed"); + } + if (result != XR_ERROR_FEATURE_UNSUPPORTED) { + OXR(result = xrPassthroughStartFB(Passthrough)); + } + } + return true; + } + + virtual void SessionEnd() override { + ControllerRenderL.Shutdown(); + ControllerRenderR.Shutdown(); + BeamRenderer.Shutdown(); + } + + // Update state + virtual void Update(const OVRFW::ovrApplFrameIn& in) override { + Ui.HitTestDevices().clear(); + + if (in.LeftRemoteTracked) { + ControllerRenderL.Update(in.LeftRemotePose); + const bool didPinch = in.LeftRemoteIndexTrigger > 0.5f; + Ui.AddHitTestRay(in.LeftRemotePointPose, didPinch); + } + if (in.RightRemoteTracked) { + ControllerRenderR.Update(in.RightRemotePose); + const bool didPinch = in.RightRemoteIndexTrigger > 0.5f; + Ui.AddHitTestRay(in.RightRemotePointPose, didPinch); + } + + Ui.Update(in); + BeamRenderer.Update(in, Ui.HitTestDevices()); + + if (in.Clicked(in.kButtonA)) { + ALOG("\'A\' button is clicked, creating a shared anchor"); + CreateAndShareAnchor(in.RightRemotePose, ToXrTime(in.PredictedDisplayTime)); + } + + StartAdvertisementButton->SetVisible(false); + StopAdvertisementButton->SetVisible(false); + + StartDiscoveryButton->SetVisible(IsStartDiscoveryButtonEnabled); + StopDiscoveryButton->SetVisible(!IsStartDiscoveryButtonEnabled); + + if (MyAnchor.has_value() && IsAnchorShared) { + StartAdvertisementButton->SetVisible(IsStartAdvertisementButtonEnabled); + StopAdvertisementButton->SetVisible(!IsStartAdvertisementButtonEnabled); + } + + UpdateAnchorPoses(in); + } + + void UpdateAnchorPoses(const OVRFW::ovrApplFrameIn& in) { + // Locate the space + // If anchor was placed, just update the anchor location + // Updating it regularly will prevent drift + std::vector poses; + if (MyAnchor.has_value() && MyAnchor.value().Space.has_value()) { + const auto& [uuid, space, color] = MyAnchor.value(); + XrSpaceLocation persistedAnchorLoc = {XR_TYPE_SPACE_LOCATION}; + XrResult res = XR_SUCCESS; + OXR(res = xrLocateSpace( + space.value(), + GetCurrentSpace(), + ToXrTime(in.PredictedDisplayTime), + &persistedAnchorLoc)); + if (res == XR_SUCCESS) { + if (ValidateLocationFlags(persistedAnchorLoc.locationFlags)) { + const OVR::Posef localFromPersistedAnchor = + FromXrPosef(persistedAnchorLoc.pose); + poses.push_back(AnchorPose{localFromPersistedAnchor, color}); + } else { + ALOGE( + "Failed to locate anchor pose of uuid %s : invalid locationFlags %#lx", + uuid.c_str(), + (long)persistedAnchorLoc.locationFlags); + } + } else { + ALOGE("Failed locate anchor pose of uuid: %s", uuid.c_str()); + } + } + + for (const auto& [uuid, anchor] : ReceivedAnchors) { + const auto& [_, space, color] = anchor; + if (!space.has_value()) + continue; + XrSpaceLocation persistedAnchorLoc = {XR_TYPE_SPACE_LOCATION}; + XrResult res = XR_SUCCESS; + OXR(res = xrLocateSpace( + space.value(), + GetCurrentSpace(), + ToXrTime(in.PredictedDisplayTime), + &persistedAnchorLoc)); + if (res == XR_SUCCESS) { + if (ValidateLocationFlags(persistedAnchorLoc.locationFlags)) { + const OVR::Posef localFromPersistedAnchor = + FromXrPosef(persistedAnchorLoc.pose); + poses.push_back(AnchorPose{localFromPersistedAnchor, color}); + } else { + ALOGE( + "Failed to locate anchor pose of uuid %s : invalid locationFlags %#lx", + uuid.c_str(), + (long)persistedAnchorLoc.locationFlags); + } + } else { + ALOGE("Failed locate anchor pose of uuid: %s", uuid.c_str()); + } + } + AnchorsRenderer->UpdatePoses(poses); + } + + // Render eye buffers while running + virtual void Render(const OVRFW::ovrApplFrameIn& in, OVRFW::ovrRendererOutput& out) override { + /// Render UI + Ui.Render(in, out); + + /// Render controllers + if (in.LeftRemoteTracked) { + ControllerRenderL.Render(out.Surfaces); + } + if (in.RightRemoteTracked) { + ControllerRenderR.Render(out.Surfaces); + } + + /// Render beams last, since they render with transparency (alpha blending) + BeamRenderer.Render(in, out); + + /// Render placed anchors + AnchorsRenderer->Render(out.Surfaces); + } + + bool IsComponentSupported(XrSpace space, XrSpaceComponentTypeFB type) { + uint32_t numComponents = 0; + OXR(xrEnumerateSpaceSupportedComponentsFB(space, 0, &numComponents, nullptr)); + std::vector components(numComponents); + OXR(xrEnumerateSpaceSupportedComponentsFB( + space, numComponents, &numComponents, components.data())); + + bool supported = false; + for (uint32_t c = 0; c < numComponents; ++c) { + if (components[c] == type) { + supported = true; + break; + } + } + return supported; + } + + virtual void HandleXrEvents() override { + XrEventDataBuffer eventDataBuffer = {}; + + // Poll for events + for (;;) { + XrEventDataBaseHeader* baseEventHeader = (XrEventDataBaseHeader*)(&eventDataBuffer); + baseEventHeader->type = XR_TYPE_EVENT_DATA_BUFFER; + baseEventHeader->next = NULL; + XrResult r; + OXR(r = xrPollEvent(Instance, &eventDataBuffer)); + if (r != XR_SUCCESS) { + break; + } + + switch (baseEventHeader->type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST event"); + break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event"); + break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED event"); + break; + case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT: { + const XrEventDataPerfSettingsEXT* perfSettingsEvent = + (XrEventDataPerfSettingsEXT*)(baseEventHeader); + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT event: type %d subdomain %d : level %d -> level %d", + perfSettingsEvent->type, + perfSettingsEvent->subDomain, + perfSettingsEvent->fromLevel, + perfSettingsEvent->toLevel); + } break; + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING event"); + break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + const XrEventDataSessionStateChanged* sessionStateChangedEvent = + (XrEventDataSessionStateChanged*)(baseEventHeader); + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: %d for session %p at time %f", + sessionStateChangedEvent->state, + (void*)sessionStateChangedEvent->session, + FromXrTime(sessionStateChangedEvent->time)); + + switch (sessionStateChangedEvent->state) { + case XR_SESSION_STATE_FOCUSED: + Focused = true; + break; + case XR_SESSION_STATE_VISIBLE: + Focused = false; + break; + case XR_SESSION_STATE_READY: + HandleSessionStateChanges(sessionStateChangedEvent->state); + break; + case XR_SESSION_STATE_STOPPING: + HandleSessionStateChanges(sessionStateChangedEvent->state); + break; + case XR_SESSION_STATE_EXITING: + ShouldExit = true; + break; + default: + break; + } + } break; + + case XR_TYPE_EVENT_DATA_START_COLOCATION_DISCOVERY_COMPLETE_META: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_START_COLOCATION_DISCOVERY_COMPLETE_META"); + const XrEventDataStartColocationDiscoveryCompleteMETA* started = + (XrEventDataStartColocationDiscoveryCompleteMETA*)(baseEventHeader); + if (started->result == XR_SUCCESS) { + Logs->AppendRow("Start Colocation Discovery complete"); + } else { + Logs->AppendRow("Start Colocation Discovery failed with code: " + std::to_string(started->result)); + IsStartDiscoveryButtonEnabled = true; + } + } break; + case XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_RESULT_META: { + ALOGV("xrPollEvent: received XR_TYPE_EVENT_COLOCATION_DISCOVERY_RESULT_META"); + const XrEventDataColocationDiscoveryResultMETA* result = + (XrEventDataColocationDiscoveryResultMETA*)(baseEventHeader); + auto advertisementUuid = + HexStringHelper::UuidToHexString(result->advertisementUuid); + Logs->AppendRow("Advertisement UUID: " + advertisementUuid); + auto metadata = std::string((char*)(result->buffer), result->bufferSize); + + const auto root = OVR::JSON::Parse(metadata.c_str()); + + std::string groupUuidStr = root->GetItemByName("group_uuid")->GetStringValue(); + Results->AppendRow("Discovered Group: " + groupUuidStr); + std::string anchorUuidStr = root->GetItemByName("anchor_uuid")->GetStringValue(); + Results->AppendRow(std::string("Discovered Anchor: ").append(anchorUuidStr)); + const auto anchorColor = root->GetItemByName("anchor_color"); + OVR::Vector4f colorVec = OVR::Vector4f( + anchorColor->GetItemByName("x")->GetDoubleValue(), + anchorColor->GetItemByName("y")->GetDoubleValue(), + anchorColor->GetItemByName("z")->GetDoubleValue(), + anchorColor->GetItemByName("w")->GetDoubleValue()); + + ReceivedAnchors[anchorUuidStr] = Anchor{anchorUuidStr, std::nullopt, colorVec}; + XrUuidEXT groupUuid; + HexStringHelper::HexStringToUuid(groupUuidStr, groupUuid); + QueryAnchors(groupUuid); + } break; + case XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_COMPLETE_META: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_COMPLETE_META"); + const XrEventDataColocationDiscoveryCompleteMETA* complete = + (XrEventDataColocationDiscoveryCompleteMETA*)(baseEventHeader); + if (complete->result == XR_SUCCESS) { + Logs->AppendRow("Colocation Discovery Complete"); + } else { + Logs->AppendRow( + "Colocation Discovery Complete with code: " + + std::to_string(complete->result)); + } + IsStartDiscoveryButtonEnabled = true; + } break; + case XR_TYPE_EVENT_DATA_STOP_COLOCATION_DISCOVERY_COMPLETE_META: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_STOP_COLOCATION_DISCOVERY_COMPLETE_META"); + const XrEventDataStopColocationDiscoveryCompleteMETA* complete = + (XrEventDataStopColocationDiscoveryCompleteMETA*)(baseEventHeader); + if (complete->result == XR_SUCCESS) { + Logs->AppendRow("Stop Colocation Discovery Complete"); + IsStartDiscoveryButtonEnabled = true; + } else { + Logs->AppendRow("Stop Colocation Discovery Failed to Complete"); + IsStartDiscoveryButtonEnabled = false; + } + } break; + case XR_TYPE_EVENT_DATA_START_COLOCATION_ADVERTISEMENT_COMPLETE_META: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_ENABLE_COLOCATION_ADVERTISEMENT_COMPLETE_META"); + const XrEventDataStartColocationAdvertisementCompleteMETA* complete = + (XrEventDataStartColocationAdvertisementCompleteMETA*)(baseEventHeader); + if (complete->result == XR_SUCCESS) { + Logs->AppendRow("Start Colocation Advertisement complete"); + Logs->AppendRow( + "Advertisement UUID: " + + HexStringHelper::UuidToHexString(complete->advertisementUuid)); + IsStartAdvertisementButtonEnabled = false; + } else { + Logs->AppendRow("Start Colocation Advertisement failed with code: " + std::to_string(complete->result)); + IsStartAdvertisementButtonEnabled = true; + } + } break; + case XR_TYPE_EVENT_DATA_COLOCATION_ADVERTISEMENT_COMPLETE_META: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_COLOCATION_ADVERTISEMENT_COMPLETE_META"); + const XrEventDataColocationAdvertisementCompleteMETA* complete = + (XrEventDataColocationAdvertisementCompleteMETA*)(baseEventHeader); + if (complete->result == XR_SUCCESS) { + Logs->AppendRow("Colocation Advertisement Complete"); + } else { + Logs->AppendRow( + "Colocation Advertisement Complete with code: " + + std::to_string(complete->result)); + } + IsStartAdvertisementButtonEnabled = true; + } break; + case XR_TYPE_EVENT_DATA_STOP_COLOCATION_ADVERTISEMENT_COMPLETE_META: { + ALOG( + "xrPollEvent: received XR_TYPE_EVENT_DATA_STOP_COLOCATION_ADVERTISEMENT_COMPLETE_META"); + const XrEventDataStopColocationAdvertisementCompleteMETA* complete = + (XrEventDataStopColocationAdvertisementCompleteMETA*)(baseEventHeader); + if (complete->result == XR_SUCCESS) { + Logs->AppendRow("Stop Colocation Advertisement complete"); + IsStartAdvertisementButtonEnabled = true; + } else { + Logs->AppendRow("Stop Colocation Advertisement failed with code: " + std::to_string(complete->result)); + IsStartAdvertisementButtonEnabled = false; + } + } break; + case XR_TYPE_EVENT_DATA_SPATIAL_ANCHOR_CREATE_COMPLETE_FB: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_SPATIAL_ANCHOR_CREATE_COMPLETE_FB"); + const XrEventDataSpatialAnchorCreateCompleteFB* createAnchorResult = + (XrEventDataSpatialAnchorCreateCompleteFB*)(baseEventHeader); + if (createAnchorResult->result != XR_SUCCESS) { + Logs->AppendRow("Failed to create spatial anchor!!!"); + break; + } + ALOGV("Success to create spatial anchor"); + XrSpace space = createAnchorResult->space; + if (space == XR_NULL_HANDLE) { + ALOG("Failed to create spatial anchor: Generated anchor's space is null"); + Logs->AppendRow( + "Failed to create spatial anchor: Generated anchor's space is null"); + break; + } + auto spaceUuid = HexStringHelper::UuidToHexString(createAnchorResult->uuid); + auto color = GenerateRandomColor(); + MyAnchor = Anchor{spaceUuid, space, color}; + + if (IsComponentSupported(space, XR_SPACE_COMPONENT_TYPE_STORABLE_FB)) { + XrSpaceComponentStatusSetInfoFB request = { + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, + nullptr, + XR_SPACE_COMPONENT_TYPE_STORABLE_FB, + XR_TRUE, + 0}; + + XrAsyncRequestIdFB requestId; + XrResult res = xrSetSpaceComponentStatusFB(space, &request, &requestId); + if (res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) { + ALOGV("SpatialAnchorXr: Space component STORABLE already set!"); + } + } else { + Logs->AppendRow("Error: Anchor is not storable"); + MyAnchor = std::nullopt; + return; + } + + if (IsComponentSupported(space, XR_SPACE_COMPONENT_TYPE_SHARABLE_FB)) { + XrSpaceComponentStatusSetInfoFB request = { + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, + nullptr, + XR_SPACE_COMPONENT_TYPE_SHARABLE_FB, + XR_TRUE, + 0}; + + XrAsyncRequestIdFB requestId; + XrResult res = xrSetSpaceComponentStatusFB(space, &request, &requestId); + if (res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) { + ALOGV("SpatialAnchorXr: Space component SHARABLE already set!"); + } + } else { + Logs->AppendRow("Error: Anchor is not sharable"); + MyAnchor = std::nullopt; + return; + } + + ShareAnchor(space); + } break; + + case XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB: { + const XrEventDataSpaceSetStatusCompleteFB* enableResult = + (XrEventDataSpaceSetStatusCompleteFB*)(baseEventHeader); + if (enableResult->result == XR_SUCCESS) { + ALOGV( + "xrPollEvent: XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB success"); + return; + } + ALOGV("xrPollEvent: XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB failed"); + + } break; + + case XR_TYPE_EVENT_DATA_SHARE_SPACES_COMPLETE_META: { + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_SHARE_SPACES_COMPLETE_META"); + const XrEventDataShareSpacesCompleteMETA* shareResult = + (XrEventDataShareSpacesCompleteMETA*)(baseEventHeader); + if (shareResult->result == XR_SUCCESS) { + Logs->AppendRow("Successfully shared anchor!"); + IsAnchorShared = true; + } else { + Logs->AppendRow( + "Share Space failed with code: " + std::to_string(shareResult->result)); + MyAnchor = std::nullopt; + GroupUuid = std::nullopt; + } + } break; + + case XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB: { + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB"); + const auto resultsAvailable = + (XrEventDataSpaceQueryResultsAvailableFB*)baseEventHeader; + + XrResult res = XR_SUCCESS; + XrSpaceQueryResultsFB queryResults{XR_TYPE_SPACE_QUERY_RESULTS_FB}; + queryResults.resultCapacityInput = 0; + queryResults.resultCountOutput = 0; + queryResults.results = nullptr; + + VALIDATE_FUNCTION_PTR(xrRetrieveSpaceQueryResultsFB) + res = xrRetrieveSpaceQueryResultsFB( + Session, resultsAvailable->requestId, &queryResults); + if (res != XR_SUCCESS) { + Logs->AppendRow( + "Retrieve query results failed with code: " + std::to_string(res)); + break; + } else { + ALOGV("xrRetrieveSpaceQueryResultsFB: success"); + } + + std::vector results(queryResults.resultCountOutput); + queryResults.resultCapacityInput = results.size(); + queryResults.results = results.data(); + + res = xrRetrieveSpaceQueryResultsFB( + Session, resultsAvailable->requestId, &queryResults); + if (res != XR_SUCCESS) { + Logs->AppendRow( + "Retrieve query results failed with code: " + std::to_string(res)); + break; + } + + for (uint32_t i = 0; i < queryResults.resultCountOutput; ++i) { + auto& result = results[i]; + + if (IsComponentSupported( + result.space, XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB)) { + XrSpaceComponentStatusSetInfoFB request = { + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, + nullptr, + XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB, + XR_TRUE, + 0}; + XrAsyncRequestIdFB requestId; + res = xrSetSpaceComponentStatusFB(result.space, &request, &requestId); + if (res == XR_SUCCESS || + res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) { + auto spaceUuid = HexStringHelper::UuidToHexString(result.uuid); + ReceivedAnchors[spaceUuid].Space = result.space; + } + } + + if (IsComponentSupported( + result.space, XR_SPACE_COMPONENT_TYPE_STORABLE_FB)) { + XrSpaceComponentStatusSetInfoFB request = { + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, + nullptr, + XR_SPACE_COMPONENT_TYPE_STORABLE_FB, + XR_TRUE, + 0}; + XrAsyncRequestIdFB requestId; + res = xrSetSpaceComponentStatusFB(result.space, &request, &requestId); + if (res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) { + ALOGV( + "xrPollEvent: Storable component was already enabled for Space uuid: %s", + HexStringHelper::UuidToHexString(result.uuid).c_str()); + } + } + + if (IsComponentSupported( + result.space, XR_SPACE_COMPONENT_TYPE_SHARABLE_FB)) { + XrSpaceComponentStatusSetInfoFB request = { + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, + nullptr, + XR_SPACE_COMPONENT_TYPE_SHARABLE_FB, + XR_TRUE, + 0}; + XrAsyncRequestIdFB requestId; + res = xrSetSpaceComponentStatusFB(result.space, &request, &requestId); + if (res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) { + ALOGV( + "xrPollEvent: Sharable component was already enabled for Space uuid: %s", + HexStringHelper::UuidToHexString(result.uuid).c_str()); + } + } + } + } break; + case XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB: { + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB"); + } break; + + default: + ALOGV("xrPollEvent: Unknown event"); + break; + } + } + } + + void ClearResults() { + Results->SetHeader(""); + Results->Clear(); + } + + std::string GetLastUpdatedJsonStr() { + auto json = OVR::JSON::CreateObject(); + json->AddStringItem("group_uuid", HexStringHelper::UuidToHexString(GroupUuid.value()).c_str()); + json->AddStringItem("anchor_uuid", MyAnchor.value().Uuid.c_str()); + auto anchorColor = OVR::JSON::CreateObject(); + anchorColor->AddNumberItem("x", MyAnchor.value().Color.x); + anchorColor->AddNumberItem("y", MyAnchor.value().Color.y); + anchorColor->AddNumberItem("z", MyAnchor.value().Color.z); + anchorColor->AddNumberItem("w", MyAnchor.value().Color.w); + json->AddItem("anchor_color", anchorColor); + return json->PrintValue(0, false); + } + + void StartColocationAdvertisement() { + VALIDATE_FUNCTION_PTR(xrStartColocationAdvertisementMETA) + IsStartAdvertisementButtonEnabled = false; + ALOGV("Start Colocation Advertisement"); + XrColocationAdvertisementStartInfoMETA info{ + XR_TYPE_COLOCATION_ADVERTISEMENT_START_INFO_META}; + auto metadataJson = GetLastUpdatedJsonStr(); + std::vector metadata(metadataJson.size()); + std::memcpy(metadata.data(), metadataJson.data(), sizeof(uint8_t) * metadataJson.size()); + info.buffer = metadata.data(); + info.bufferSize = metadata.size(); + XrAsyncRequestIdFB requestId; + XrResult r; + OXR(r = xrStartColocationAdvertisementMETA(GetSession(), &info, &requestId)); + + if (r == XR_SUCCESS) { + Logs->AppendRow("Enabling Colocation Advertisement..."); + } else { + Logs->AppendRow("Failed to call Start Colocation Advertisement: " + std::to_string(r)); + IsStartAdvertisementButtonEnabled = true; + } + } + + void StopColocationAdvertisement() { + VALIDATE_FUNCTION_PTR(xrStopColocationAdvertisementMETA) + IsStartAdvertisementButtonEnabled = true; + ALOGV("StopColocationAdvertisement"); + XrAsyncRequestIdFB requestId; + XrResult r; + OXR(r = xrStopColocationAdvertisementMETA(GetSession(), nullptr, &requestId)); + + if (r == XR_SUCCESS) { + Logs->AppendRow("Disabling Colocation Advertisement..."); + } else { + Logs->AppendRow("Failed to call Stop Colocation Advertisement: " + std::to_string(r)); + IsStartAdvertisementButtonEnabled = false; + } + } + + void StartDiscovery() { + VALIDATE_FUNCTION_PTR(xrStartColocationDiscoveryMETA) + IsStartDiscoveryButtonEnabled = false; + XrAsyncRequestIdFB requestId; + XrResult r; + OXR(r = xrStartColocationDiscoveryMETA(GetSession(), nullptr, &requestId)); + if (r == XR_SUCCESS) { + Logs->AppendRow("Starting Colocation Discovery..."); + ClearResults(); + Results->SetHeader("Discovered Advertisements"); + } else { + Logs->AppendRow("Failed to start Colocation Discovery: " + std::to_string(r)); + IsStartDiscoveryButtonEnabled = true; + } + } + + void StopDiscovery() { + VALIDATE_FUNCTION_PTR(xrStopColocationDiscoveryMETA) + IsStartDiscoveryButtonEnabled = true; + XrAsyncRequestIdFB requestId; + XrResult r; + OXR(r = xrStopColocationDiscoveryMETA(GetSession(), nullptr, &requestId)); + if (r == XR_SUCCESS) { + Logs->AppendRow("Stopping Colocation Discovery..."); + } else { + Logs->AppendRow("Failed to stop Colocation Discovery: " + std::to_string(r)); + IsStartDiscoveryButtonEnabled = false; + } + } + + void CreateAndShareAnchor(const OVR::Posef& pose, XrTime predictedDisplayTime) { + if (MyAnchor.has_value()) { + Logs->AppendRow("Skipping: Already created anchor."); + return; + } + + VALIDATE_FUNCTION_PTR(xrCreateSpatialAnchorFB) + Logs->AppendRow("Creating shared anchor"); + + XrSpatialAnchorCreateInfoFB anchorCreateInfo = {XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_FB}; + anchorCreateInfo.space = GetCurrentSpace(); + anchorCreateInfo.poseInSpace = ToXrPosef(pose); + anchorCreateInfo.time = predictedDisplayTime; + XrAsyncRequestIdFB createRequest; + XrResult r; + OXR(r = xrCreateSpatialAnchorFB(GetSession(), &anchorCreateInfo, &createRequest)); + if (r != XR_SUCCESS) { + Logs->AppendRow("Failed to share anchors: " + std::to_string(r)); + return; + } + ALOGV("Place Spatial Anchor initiated."); + } + + void ShareAnchor(XrSpace anchor) { + VALIDATE_FUNCTION_PTR(xrShareSpacesMETA) + + XrUuidEXT groupuuid = GenerateRandomGroupUUID(); + Logs->AppendRow( + "Sharing anchor to group UUID: " + HexStringHelper::UuidToHexString(groupuuid)); + + XrShareSpacesRecipientGroupsMETA recipientGroupInfo = { + XR_TYPE_SHARE_SPACES_RECIPIENT_GROUPS_META, nullptr}; + recipientGroupInfo.groupCount = 1; + recipientGroupInfo.groups = &groupuuid; + + XrShareSpacesInfoMETA info = {XR_TYPE_SHARE_SPACES_INFO_META}; + info.spaceCount = 1; + info.spaces = &anchor; + + info.recipientInfo = (const XrShareSpacesRecipientBaseHeaderMETA*)&recipientGroupInfo; + XrAsyncRequestIdFB shareRequestId; + + XrResult r; + OXR(r = xrShareSpacesMETA(GetSession(), &info, &shareRequestId)); + if (r == XR_SUCCESS) { + GroupUuid = groupuuid; + } else { + Logs->AppendRow("Failed to share anchors: " + std::to_string(r)); + MyAnchor = std::nullopt; + } + } + + void QueryAnchors(XrUuidEXT groupUuid) { + VALIDATE_FUNCTION_PTR(xrQuerySpacesFB) + + Logs->AppendRow( + "Querying anchors from group: " + HexStringHelper::UuidToHexString(groupUuid)); + + XrSpaceStorageLocationFilterInfoFB locationFilterInfo = { + XR_TYPE_SPACE_STORAGE_LOCATION_FILTER_INFO_FB, + nullptr, + XR_SPACE_STORAGE_LOCATION_CLOUD_FB}; + XrSpaceGroupUuidFilterInfoMETA filterInfo = {XR_TYPE_SPACE_GROUP_UUID_FILTER_INFO_META}; + filterInfo.groupUuid = groupUuid; + filterInfo.next = &locationFilterInfo; + + XrSpaceQueryInfoFB info = { + XR_TYPE_SPACE_QUERY_INFO_FB, + nullptr, + XR_SPACE_QUERY_ACTION_LOAD_FB, + K_MAX_PERSISTENT_SPACES, + 0, + (XrSpaceFilterInfoBaseHeaderFB*)&filterInfo, + nullptr}; + + XrAsyncRequestIdFB requestId; + XrResult r; + OXR(r = xrQuerySpacesFB(GetSession(), (XrSpaceQueryInfoBaseHeaderFB*)&info, &requestId)); + + if (r == XR_SUCCESS) { + ALOGV("Query Spatial Anchor initiated."); + } else { + Logs->AppendRow("Failed to Query anchors: " + std::to_string(r)); + } + } + + void HookExtensions() { + /// Passthrough + HOOKUP_FUNCTION_PTR(xrCreatePassthroughFB) + HOOKUP_FUNCTION_PTR(xrDestroyPassthroughFB) + HOOKUP_FUNCTION_PTR(xrCreatePassthroughLayerFB) + HOOKUP_FUNCTION_PTR(xrDestroyPassthroughLayerFB) + HOOKUP_FUNCTION_PTR(xrPassthroughLayerResumeFB) + HOOKUP_FUNCTION_PTR(xrPassthroughLayerPauseFB) + HOOKUP_FUNCTION_PTR(xrPassthroughLayerSetStyleFB) + HOOKUP_FUNCTION_PTR(xrPassthroughStartFB) + HOOKUP_FUNCTION_PTR(xrPassthroughPauseFB) + + // Spatial anchor + HOOKUP_FUNCTION_PTR(xrEnumerateSpaceSupportedComponentsFB) + HOOKUP_FUNCTION_PTR(xrSetSpaceComponentStatusFB) + HOOKUP_FUNCTION_PTR(xrCreateSpatialAnchorFB) + HOOKUP_FUNCTION_PTR(xrGetSpaceComponentStatusFB) + + // Colocation Discovery + HOOKUP_FUNCTION_PTR(xrStopColocationAdvertisementMETA) + HOOKUP_FUNCTION_PTR(xrStartColocationAdvertisementMETA) + HOOKUP_FUNCTION_PTR(xrStartColocationDiscoveryMETA) + HOOKUP_FUNCTION_PTR(xrStopColocationDiscoveryMETA) + HOOKUP_FUNCTION_PTR(xrShareSpacesMETA) + HOOKUP_FUNCTION_PTR(xrRetrieveSpaceQueryResultsFB) + HOOKUP_FUNCTION_PTR(xrQuerySpacesFB) + } + + bool ValidateLocationFlags(XrSpaceLocationFlags flags) { + return (flags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0 && + (flags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) != 0; + } + + XrUuidEXT GenerateRandomGroupUUID() { + XrUuidEXT groupuuid; + + for (uint8_t i = 0; i < XR_UUID_SIZE; i++) { + groupuuid.data[i] = rand(); + } + + return groupuuid; + } + + OVR::Vector4f GenerateRandomColor() { + return { + (float)rand() / (float)RAND_MAX, + (float)rand() / (float)RAND_MAX, + (float)rand() / (float)RAND_MAX, + 1.0f}; + } + + private: + // Passthrough layer + XrPassthroughFB Passthrough = XR_NULL_HANDLE; + XrPassthroughLayerFB PassthroughLayer = XR_NULL_HANDLE; + + // Passthrough + DECLARE_FUNCTION_PTR(xrCreatePassthroughFB) + DECLARE_FUNCTION_PTR(xrDestroyPassthroughFB) + DECLARE_FUNCTION_PTR(xrCreatePassthroughLayerFB) + DECLARE_FUNCTION_PTR(xrDestroyPassthroughLayerFB) + DECLARE_FUNCTION_PTR(xrPassthroughLayerResumeFB) + DECLARE_FUNCTION_PTR(xrPassthroughLayerPauseFB) + DECLARE_FUNCTION_PTR(xrPassthroughLayerSetStyleFB) + DECLARE_FUNCTION_PTR(xrPassthroughStartFB) + DECLARE_FUNCTION_PTR(xrPassthroughPauseFB) + + // Spatial anchor + DECLARE_FUNCTION_PTR(xrEnumerateSpaceSupportedComponentsFB) + DECLARE_FUNCTION_PTR(xrSetSpaceComponentStatusFB) + DECLARE_FUNCTION_PTR(xrCreateSpatialAnchorFB) + DECLARE_FUNCTION_PTR(xrGetSpaceComponentStatusFB) + + // Colocation Discovery + DECLARE_FUNCTION_PTR(xrStopColocationAdvertisementMETA) + DECLARE_FUNCTION_PTR(xrStartColocationAdvertisementMETA) + DECLARE_FUNCTION_PTR(xrStartColocationDiscoveryMETA) + DECLARE_FUNCTION_PTR(xrStopColocationDiscoveryMETA) + DECLARE_FUNCTION_PTR(xrShareSpacesMETA) + DECLARE_FUNCTION_PTR(xrRetrieveSpaceQueryResultsFB) + DECLARE_FUNCTION_PTR(xrQuerySpacesFB) + + std::unique_ptr AnchorsRenderer; + OVRFW::ControllerRenderer ControllerRenderL; + OVRFW::ControllerRenderer ControllerRenderR; + OVRFW::TinyUI Ui; + OVRFW::SimpleBeamRenderer BeamRenderer; + std::vector Beams; + + std::optional MyAnchor = std::nullopt; + std::unordered_map ReceivedAnchors; + std::optional GroupUuid; + + // UI Menu + std::unique_ptr Logs; + std::unique_ptr Results; + OVRFW::VRMenuObject* Banner; + OVRFW::VRMenuObject* StartDiscoveryButton; + OVRFW::VRMenuObject* StopDiscoveryButton; + OVRFW::VRMenuObject* StartAdvertisementButton; + OVRFW::VRMenuObject* StopAdvertisementButton; + + bool IsStartDiscoveryButtonEnabled = true; + bool IsStartAdvertisementButtonEnabled = true; + bool IsAnchorShared = false; +}; + +ENTRY_POINT(XrColocationDiscoveryApp) diff --git a/Samples/XrSamples/XrColocationDiscovery/assets/assets.txt b/Samples/XrSamples/XrColocationDiscovery/assets/assets.txt new file mode 100644 index 0000000..2cc30f7 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/assets/assets.txt @@ -0,0 +1 @@ +This file is a placeholder. diff --git a/Samples/XrSamples/XrColocationDiscovery/assets/panel.ktx b/Samples/XrSamples/XrColocationDiscovery/assets/panel.ktx new file mode 100644 index 0000000..deb13e8 Binary files /dev/null and b/Samples/XrSamples/XrColocationDiscovery/assets/panel.ktx differ diff --git a/Samples/XrSamples/XrColocationDiscovery/java/MainActivity.java b/Samples/XrSamples/XrColocationDiscovery/java/MainActivity.java new file mode 100644 index 0000000..2a2a216 --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/java/MainActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// Copyright (c) Facebook Technologies, LLC and its affiliates. All Rights reserved. +package com.oculus.sdk.xrcolocationdiscovery; + +public class MainActivity extends android.app.NativeActivity {} diff --git a/Samples/XrSamples/XrColocationDiscovery/res/values/strings.xml b/Samples/XrSamples/XrColocationDiscovery/res/values/strings.xml new file mode 100644 index 0000000..23943fe --- /dev/null +++ b/Samples/XrSamples/XrColocationDiscovery/res/values/strings.xml @@ -0,0 +1,4 @@ + + + XrColocationDiscovery Sample + diff --git a/Samples/XrSamples/XrColorSpaceFB/Projects/Android/build.gradle b/Samples/XrSamples/XrColorSpaceFB/Projects/Android/build.gradle index 4d5e8af..b4174d2 100755 --- a/Samples/XrSamples/XrColorSpaceFB/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrColorSpaceFB/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrCompositor_NativeActivity/Projects/Android/build.gradle b/Samples/XrSamples/XrCompositor_NativeActivity/Projects/Android/build.gradle index 62c4409..81ef42d 100755 --- a/Samples/XrSamples/XrCompositor_NativeActivity/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrCompositor_NativeActivity/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrCompositor_NativeActivity/Src/XrCompositor_NativeActivity.c b/Samples/XrSamples/XrCompositor_NativeActivity/Src/XrCompositor_NativeActivity.c index 3b80bea..927c288 100755 --- a/Samples/XrSamples/XrCompositor_NativeActivity/Src/XrCompositor_NativeActivity.c +++ b/Samples/XrSamples/XrCompositor_NativeActivity/Src/XrCompositor_NativeActivity.c @@ -110,6 +110,7 @@ typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)( #define OVR_LOG_TAG "XrCompositor_NativeActivity" #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, OVR_LOG_TAG, __VA_ARGS__) +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, OVR_LOG_TAG, __VA_ARGS__) #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, OVR_LOG_TAG, __VA_ARGS__) #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, OVR_LOG_TAG, __VA_ARGS__) @@ -324,7 +325,8 @@ static void ovrEgl_CreateContext(ovrEgl* egl, const ovrEgl* shareEgl) { ALOGE(" eglGetConfigs() failed: %s", EglErrorString(eglGetError())); return; } - const EGLint configAttribs[] = { + + const EGLint defaultConfigAttribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, @@ -339,39 +341,64 @@ static void ovrEgl_CreateContext(ovrEgl* egl, const ovrEgl* shareEgl) { 0, EGL_SAMPLES, 0, - EGL_NONE}; + EGL_NONE + }; + + const EGLint fallbackConfigAttribs[] = { + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, // need alpha for the multi-pass timewarp compositor + EGL_NONE + }; + + const EGLint* configAttribs[2] = { + defaultConfigAttribs, + fallbackConfigAttribs, + }; + egl->Config = 0; ALOGD(" Queried %d EGL configs, evaluating ...", numConfigs); - for (int i = 0; i < numConfigs; i++) { - EGLint value = 0; - - ALOGD(" Evaluating EGL config %d.", i); - eglGetConfigAttrib(egl->Display, configs[i], EGL_RENDERABLE_TYPE, &value); - if (!checkFlagAndLog(value, EGL_OPENGL_ES3_BIT_KHR, "renderable type")) - { - continue; - } + for (int attempt = 0 ; attempt < 2; attempt++) { + for (int i = 0; i < numConfigs; i++) { + EGLint value = 0; + + ALOGD(" Evaluating EGL config %d.", i); + eglGetConfigAttrib(egl->Display, configs[i], EGL_RENDERABLE_TYPE, &value); + if (!checkFlagAndLog(value, EGL_OPENGL_ES3_BIT_KHR, "renderable type")) + { + continue; + } - // The pbuffer config also needs to be compatible with normal window rendering - // so it can share textures with the window context. - eglGetConfigAttrib(egl->Display, configs[i], EGL_SURFACE_TYPE, &value); - if (!checkFlagAndLog(value, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, "surface type")) - { - continue; - } + // The pbuffer config also needs to be compatible with normal window rendering + // so it can share textures with the window context. + eglGetConfigAttrib(egl->Display, configs[i], EGL_SURFACE_TYPE, &value); + if (!checkFlagAndLog(value, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, "surface type")) + { + continue; + } - int j = 0; - ALOGD(" Checking EGL config attributes to make sure they match what we want..."); - for (; configAttribs[j] != EGL_NONE; j += 2) { - eglGetConfigAttrib(egl->Display, configs[i], configAttribs[j], &value); - if (value != configAttribs[j + 1]) { - ALOGD(" Skipping EGL config due to mismatch in config attribute %d: expected %d, got %d", j / 2, configAttribs[j + 1], value); + int j = 0; + ALOGD(" Checking EGL config attributes to make sure they match what we want..."); + for (; configAttribs[attempt][j] != EGL_NONE; j += 2) { + eglGetConfigAttrib(egl->Display, configs[i], configAttribs[attempt][j], &value); + if (value != configAttribs[attempt][j + 1]) { + ALOGD(" Skipping EGL config due to mismatch in config attribute %d: expected %d, got %d", j / 2, configAttribs[attempt][j + 1], value); + break; + } + } + if (configAttribs[attempt][j] == EGL_NONE) { + ALOGD(" Successfully picked EGL config %d!", i); + egl->Config = configs[i]; break; } } - if (configAttribs[j] == EGL_NONE) { - ALOGD(" Successfully picked EGL config %d!", i); - egl->Config = configs[i]; + if (egl->Config != 0) { + ALOGW(" Failed to pick EGL config! Fallback to simpler color config."); break; } } diff --git a/Samples/XrSamples/XrControllers/Projects/Android/build.gradle b/Samples/XrSamples/XrControllers/Projects/Android/build.gradle index 8d4c386..c3ab687 100755 --- a/Samples/XrSamples/XrControllers/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrControllers/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrControllers/Src/main.cpp b/Samples/XrSamples/XrControllers/Src/main.cpp index 9ded14d..e2053bb 100755 --- a/Samples/XrSamples/XrControllers/Src/main.cpp +++ b/Samples/XrSamples/XrControllers/Src/main.cpp @@ -1198,6 +1198,9 @@ Function to create PCM samples from an array of amplitudes, frequency and durati const float* buffer, const size_t bufferSize, float sampleRate) { + // stream and sleep on a separate thread, + // so that we don't lock up the entire app + std::thread t([action, subactionPath, buffer, bufferSize, sampleRate, this]() { /// fill in the amplitude buffer std::vector pcmBuffer(bufferSize); for (size_t i = 0; i < bufferSize; ++i) { @@ -1238,6 +1241,8 @@ Function to create PCM samples from an array of amplitudes, frequency and durati totalSamplesUsed += samplesUsed; ALOG("Haptics PCM Buffer Count Output: %d", *(v.samplesConsumed)); } + }); + t.detach(); } void StopHapticEffect(const XrAction& action, const XrPath& subactionPath) { diff --git a/Samples/XrSamples/XrDynamicObjects/CMakeLists.txt b/Samples/XrSamples/XrDynamicObjects/CMakeLists.txt new file mode 100755 index 0000000..f0a7318 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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. +project(xrdynamicobjects) + +if(NOT TARGET OpenXR::openxr_loader) + find_package(OpenXR REQUIRED) +endif() + +file(GLOB_RECURSE SRC_FILES + Src/*.c + Src/*.cpp +) + +if(ANDROID) + add_library(${PROJECT_NAME} MODULE ${SRC_FILES}) + target_include_directories(${PROJECT_NAME} PUBLIC ${ANDROID_NDK}/sources/android/native_app_glue) + target_link_libraries(${PROJECT_NAME} PRIVATE + android + EGL + GLESv3 + log + ktx + ) + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-u ANativeActivity_onCreate") +elseif(WIN32) + add_definitions(-D_USE_MATH_DEFINES) + add_executable(${PROJECT_NAME} ${SRC_FILES}) + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/assets" + "$/assets" + VERBATIM) + + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/SampleXrFramework/res/raw" + "$/font/res/raw" + VERBATIM) +endif() + +# Common across platforms +target_include_directories(${PROJECT_NAME} PRIVATE Src) +target_link_libraries(${PROJECT_NAME} PRIVATE samplexrframework) diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/AndroidManifest.xml b/Samples/XrSamples/XrDynamicObjects/Projects/Android/AndroidManifest.xml new file mode 100755 index 0000000..b5d2834 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/AndroidManifest.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.bat b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.bat new file mode 100755 index 0000000..b9bbb04 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.bat @@ -0,0 +1,45 @@ +REM Copyright (c) Meta Platforms, Inc. and affiliates. +REM All rights reserved. +REM +REM Licensed under the Oculus SDK License Agreement (the "License"); +REM you may not use the Oculus SDK except in compliance with the License, +REM which is provided at the time of installation or download, or which +REM otherwise accompanies this software in either electronic or hard copy form. +REM +REM You may obtain a copy of the License at +REM https://developer.oculus.com/licenses/oculussdk/ +REM +REM Unless required by applicable law or agreed to in writing, the Oculus SDK +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +@rem Only edit the master copy of this file in SDK_ROOT/bin/scripts/build/perproject + +@setlocal enableextensions enabledelayedexpansion + +@if not exist "build.gradle" @echo Build script must be executed from project directory. & goto :Abort + +@set P=.. + +:TryAgain + +@rem @echo P = %P% + +@if exist "%P%\bin\scripts\build\build.py.bat" goto :Found + +@if exist "%P%\bin\scripts\build" @echo "Could not find build.py.bat" & goto :Abort + +@set P=%P%\.. + +@goto :TryAgain + +:Found + +@set P=%P%\bin\scripts\build +@call %P%\build.py.bat %1 %2 %3 %4 %5 +@goto :End + +:Abort + +:End diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.gradle b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.gradle new file mode 100755 index 0000000..ec67801 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.gradle @@ -0,0 +1,96 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.3" + } +} + +repositories { + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.oculus.sdk.xrdynamicobjects" + minSdk 26 + targetSdk 32 + versionCode 1 + versionName "1.0" + + // override app plugin abiFilters for 64-bit support + externalNativeBuild { + ndk { + abiFilters 'arm64-v8a' + } + ndkBuild { + abiFilters 'arm64-v8a' + } + cmake { + targets "xrdynamicobjects" + } + } + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['../../java'] + assets.srcDirs = ['../../assets'] + res.srcDirs = ['../../res'] + } + } + + buildTypes { + debug { + debuggable true + } + + release { + debuggable false + } + } + + externalNativeBuild { + cmake { + path file('../../../../CMakeLists.txt') + } + } + + // Enable prefab support for the OpenXR AAR + buildFeatures { + prefab true + } +} + +dependencies { + // Package/application AndroidManifest.xml properties, plus headers and libraries + // exposed to CMake + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' +} diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.py b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.py new file mode 100755 index 0000000..82c0793 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/build.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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 first bit of code is common bootstrapping code +# to determine the SDK root, and to set up the import +# path for additional python code. + +# begin bootstrap +import os +import sys + + +def init(): + root = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) + os.chdir(root) # make sure we are always executing from the project directory + while os.path.isdir(os.path.join(root, "bin/scripts/build")) == False: + root = os.path.realpath(os.path.join(root, "..")) + if ( + len(root) <= 5 + ): # Should catch both Posix and Windows root directories (e.g. '/' and 'C:\') + print("Unable to find SDK root. Exiting.") + sys.exit(1) + root = os.path.abspath(root) + os.environ["OCULUS_SDK_PATH"] = root + sys.path.append(root + "/bin/scripts/build") + + +init() +import ovrbuild + +ovrbuild.init() +# end bootstrap + + +ovrbuild.build() diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle.properties b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle.properties new file mode 100644 index 0000000..3e927b1 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.jar b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.properties b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew.bat b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew.bat new file mode 100755 index 0000000..f127cfd --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/XrSamples/XrDynamicObjects/Projects/Android/settings.gradle b/Samples/XrSamples/XrDynamicObjects/Projects/Android/settings.gradle new file mode 100755 index 0000000..004f4c0 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Projects/Android/settings.gradle @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +rootProject.name = "XrDynamicObjects" diff --git a/Samples/XrSamples/XrDynamicObjects/README.md b/Samples/XrSamples/XrDynamicObjects/README.md new file mode 100644 index 0000000..8e86964 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/README.md @@ -0,0 +1,8 @@ +# OpenXR Dynamic Object Tracker Sample + +## Overview +The extension `XR_META_dynamic_object_tracker` provides the ability for the headset to detect and track an object class, such as a keyboard. + +## The Sample +This sample opens to a panel that displays the state of the internal API calls. If the extension is enabled successfully and a keyboard is detected, +the application will display a passthrough window showing the user's keyboard. diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.cpp b/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.cpp new file mode 100755 index 0000000..ac0a9c1 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrDynamicObjectTrackerHelper.cpp +Content : Helper inteface for openxr XR_META_dynamic_object_tracker extension +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#include "XrDynamicObjectTrackerHelper.h" + +#include +#include + +std::vector XrDynamicObjectTrackerHelper::RequiredExtensionNames() { + return {XR_METAX1_DYNAMIC_OBJECT_TRACKER_EXTENSION_NAME}; +} + +XrDynamicObjectTrackerHelper::XrDynamicObjectTrackerHelper(XrInstance instance) + : XrHelper(instance) { + OXR(xrGetInstanceProcAddr( + instance, + "xrCreateDynamicObjectTrackerMETAX1", + reinterpret_cast(&XrCreateDynamicObjectTrackerMETAX1))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyDynamicObjectTrackerMETAX1", + reinterpret_cast(&XrDestroyDynamicObjectTrackerMETAX1))); + OXR(xrGetInstanceProcAddr( + instance, + "xrSetDynamicObjectTrackedClassesMETAX1", + reinterpret_cast(&XrSetDynamicObjectTrackedClassesMETAX1))); + OXR(xrGetInstanceProcAddr( + instance, + "xrGetSpaceDynamicObjectDataMETAX1", + reinterpret_cast(&XrGetSpaceDynamicObjectDataMETAX1))); +} + +void XrDynamicObjectTrackerHelper::SessionInit(XrSession session) { + Session = session; +} + +void XrDynamicObjectTrackerHelper::SessionEnd() { + Session = XR_NULL_HANDLE; +} + +void XrDynamicObjectTrackerHelper::Update(XrSpace currentSpace, XrTime predictedDisplayTime) {} + +bool XrDynamicObjectTrackerHelper::HandleXrEvents(const XrEventDataBaseHeader* baseEventHeader) { + bool handled = false; + switch (baseEventHeader->type) { + case XR_TYPE_EVENT_DATA_DYNAMIC_OBJECT_TRACKER_CREATE_RESULT_METAX1: { + const auto createResult = + reinterpret_cast( + baseEventHeader); + // Make sure this belongs to our tracker + if (createResult->handle == DynamicObjectTracker) { + if (createResult->result != XR_SUCCESS) { + ALOGE("Dynamic object tracker create result: %d", createResult->result); + DynamicObjectTracker = XR_NULL_HANDLE; + State = TrackerState::Empty; + } else { + State = TrackerState::Created; + } + handled = true; + } + } break; + case XR_TYPE_EVENT_DATA_DYNAMIC_OBJECT_SET_TRACKED_CLASSES_RESULT_METAX1: { + const auto setTrackedClassesResult = + reinterpret_cast( + baseEventHeader); + // Make sure this belongs to our tracker + if (setTrackedClassesResult->handle == DynamicObjectTracker) { + if (setTrackedClassesResult->result != XR_SUCCESS) { + ALOGE( + "Dynamic object set tracked classes result: %d", + setTrackedClassesResult->result); + State = TrackerState::Created; + } else { + State = TrackerState::Initialized; + } + handled = true; + } + } break; + default: { + } + } + + return handled; +} + +XrDynamicObjectTrackerHelper::TrackerState XrDynamicObjectTrackerHelper::GetTrackerState() const { + return State; +} + +bool XrDynamicObjectTrackerHelper::CreateDynamicObjectTracker() { + if (State != TrackerState::Empty) { + ALOGE("Cannot create tracker more than once"); + return false; + } + + XrDynamicObjectTrackerCreateInfoMETAX1 createInfo{ + XR_TYPE_DYNAMIC_OBJECT_TRACKER_CREATE_INFO_METAX1}; + XrResult result = + XrCreateDynamicObjectTrackerMETAX1(Session, &createInfo, &DynamicObjectTracker); + if (result != XR_SUCCESS) { + ALOGE("xrCreateDynamicObjectTrackerMETAX1 failed, error %d", result); + return false; + } + + State = TrackerState::Creating; + return true; +} + +bool XrDynamicObjectTrackerHelper::DestroyDynamicObjectTracker() { + if (State == TrackerState::Empty) { + ALOGE("No tracker available"); + return false; + } + + XrResult result = XrDestroyDynamicObjectTrackerMETAX1(DynamicObjectTracker); + if (result != XR_SUCCESS) { + ALOGE("xrDestroyDynamicObjectTrackerMETAX1 failed, error %d", result); + return false; + } + + DynamicObjectTracker = XR_NULL_HANDLE; + State = TrackerState::Empty; + return true; +} + +bool XrDynamicObjectTrackerHelper::SetDynamicObjectTrackedClasses( + const std::vector& trackedClasses) { + if (State != TrackerState::Created) { + ALOGE("No tracker available"); + return false; + } + + XrDynamicObjectTrackedClassesSetInfoMETAX1 setInfo{ + XR_TYPE_DYNAMIC_OBJECT_TRACKED_CLASSES_SET_INFO_METAX1}; + setInfo.classCount = trackedClasses.size(); + setInfo.classes = trackedClasses.data(); + XrResult result = XrSetDynamicObjectTrackedClassesMETAX1(DynamicObjectTracker, &setInfo); + if (result != XR_SUCCESS) { + ALOGE("xrSetDynamicObjectTrackedClassesMETAX1 failed, error %d", result); + return false; + } + + State = TrackerState::Initializing; + return true; +} + +bool XrDynamicObjectTrackerHelper::GetDynamicObjectClass( + XrSpace space, + XrDynamicObjectClassMETAX1& classType) const { + XrDynamicObjectDataMETAX1 dynamicObjectData{XR_TYPE_DYNAMIC_OBJECT_DATA_METAX1}; + XrResult result = XrGetSpaceDynamicObjectDataMETAX1(Session, space, &dynamicObjectData); + if (XR_FAILED(result)) { + ALOGE("xrGetSpaceDynamicObjectDataMETAX1 failed, error %d", result); + return false; + } + + classType = dynamicObjectData.classType; + return true; +} + +bool XrDynamicObjectTrackerHelper::IsSupported() const { + XrSystemGetInfo systemGetInfo{XR_TYPE_SYSTEM_GET_INFO}; + systemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + XrSystemId systemId; + XrResult result = xrGetSystem(Instance, &systemGetInfo, &systemId); + if (result != XR_SUCCESS) { + if (result == XR_ERROR_FORM_FACTOR_UNAVAILABLE) { + ALOGE( + "Failed to get system; the specified form factor is not available. Is your headset connected?"); + } else { + ALOGE("xrGetSystem failed, error %d", result); + } + exit(1); + } + + XrSystemProperties systemProperties{XR_TYPE_SYSTEM_PROPERTIES}; + XrSystemDynamicObjectTrackerPropertiesMETAX1 dynamicObjectTrackerProps{ + XR_TYPE_SYSTEM_DYNAMIC_OBJECT_TRACKER_PROPERTIES_METAX1}; + systemProperties.next = &dynamicObjectTrackerProps; + result = xrGetSystemProperties(Instance, systemId, &systemProperties); + if (result != XR_SUCCESS) { + ALOGE("xrGetSystemProperties failed, error %d", result); + return false; + } + + return (dynamicObjectTrackerProps.supportsDynamicObjectTracker == XR_TRUE); +} diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.h b/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.h new file mode 100755 index 0000000..a3ac5fa --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrDynamicObjectTrackerHelper.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrDynamicObjectTrackerHelper.h +Content : Helper inteface for openxr XR_META_dynamic_object_tracker extension +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#pragma once + +#include + +#include "XrHelper.h" + +class XrDynamicObjectTrackerHelper : public XrHelper { + public: + static std::vector RequiredExtensionNames(); + + public: + enum class TrackerState { + Empty, + Creating, + Created, + Initializing, + Initialized, + }; + + public: + explicit XrDynamicObjectTrackerHelper(XrInstance instance); + + void SessionInit(XrSession session) override; + void SessionEnd() override; + void Update(XrSpace currentSpace, XrTime predictedDisplayTime) override; + + bool HandleXrEvents(const XrEventDataBaseHeader* baseEventHeader); + + TrackerState GetTrackerState() const; + + bool CreateDynamicObjectTracker(); + bool DestroyDynamicObjectTracker(); + bool SetDynamicObjectTrackedClasses( + const std::vector& trackedClasses); + bool GetDynamicObjectClass(XrSpace space, XrDynamicObjectClassMETAX1& classType) const; + + bool IsSupported() const; + + private: + XrSession Session = XR_NULL_HANDLE; + XrDynamicObjectTrackerMETAX1 DynamicObjectTracker = XR_NULL_HANDLE; + + PFN_xrCreateDynamicObjectTrackerMETAX1 XrCreateDynamicObjectTrackerMETAX1 = nullptr; + PFN_xrDestroyDynamicObjectTrackerMETAX1 XrDestroyDynamicObjectTrackerMETAX1 = nullptr; + PFN_xrSetDynamicObjectTrackedClassesMETAX1 XrSetDynamicObjectTrackedClassesMETAX1 = nullptr; + PFN_xrGetSpaceDynamicObjectDataMETAX1 XrGetSpaceDynamicObjectDataMETAX1 = nullptr; + + TrackerState State = TrackerState::Empty; +}; diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.cpp b/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.cpp new file mode 100755 index 0000000..aecb988 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrHandHelper.cpp +Content : Helper Inteface for openxr hand extensions +Created : September 2023 +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#include "XrHandHelper.h" + +std::vector XrHandHelper::RequiredExtensionNames() { + return { + XR_EXT_HAND_TRACKING_EXTENSION_NAME, + XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME, + XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME}; +} + +XrHandHelper::XrHandHelper(XrInstance instance, bool isLeft) : XrHelper(instance), IsLeft(isLeft) { + /// Hook up extensions for hand tracking + OXR(xrGetInstanceProcAddr( + instance, + "xrCreateHandTrackerEXT", + reinterpret_cast(&XrCreateHandTrackerExt))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyHandTrackerEXT", + reinterpret_cast(&XrDestroyHandTrackerExt))); + OXR(xrGetInstanceProcAddr( + instance, + "xrLocateHandJointsEXT", + reinterpret_cast(&XrLocateHandJointsExt))); + /// Hook up extensions for hand rendering + OXR(xrGetInstanceProcAddr( + instance, "xrGetHandMeshFB", reinterpret_cast(&XrGetHandMeshFb))); +} + +XrHandHelper::~XrHandHelper() { + XrCreateHandTrackerExt = nullptr; + XrDestroyHandTrackerExt = nullptr; + XrLocateHandJointsExt = nullptr; + XrGetHandMeshFb = nullptr; +}; + +void XrHandHelper::SessionInit(XrSession session) { + if (XrCreateHandTrackerExt) { + XrHandTrackerCreateInfoEXT createInfo{XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT}; + createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT; + createInfo.hand = IsLeft ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT; + OXR(XrCreateHandTrackerExt(session, &createInfo, &HandTracker)); + + if (XrGetHandMeshFb) { + Mesh.type = XR_TYPE_HAND_TRACKING_MESH_FB; + Mesh.next = nullptr; + Mesh.jointCapacityInput = 0; + Mesh.jointCountOutput = 0; + Mesh.vertexCapacityInput = 0; + Mesh.vertexCountOutput = 0; + Mesh.indexCapacityInput = 0; + Mesh.indexCountOutput = 0; + OXR(XrGetHandMeshFb(HandTracker, &Mesh)); + + /// update sizes + Mesh.jointCapacityInput = Mesh.jointCountOutput; + Mesh.vertexCapacityInput = Mesh.vertexCountOutput; + Mesh.indexCapacityInput = Mesh.indexCountOutput; + VertexPositions.resize(Mesh.vertexCapacityInput); + VertexNormals.resize(Mesh.vertexCapacityInput); + VertexUVs.resize(Mesh.vertexCapacityInput); + VertexBlendIndices.resize(Mesh.vertexCapacityInput); + VertexBlendWeights.resize(Mesh.vertexCapacityInput); + Indices.resize(Mesh.indexCapacityInput); + + /// skeleton + Mesh.jointBindPoses = JointBindPoses; + Mesh.jointParents = JointParents; + Mesh.jointRadii = JointRadii; + /// mesh + Mesh.vertexPositions = VertexPositions.data(); + Mesh.vertexNormals = VertexNormals.data(); + Mesh.vertexUVs = VertexUVs.data(); + Mesh.vertexBlendIndices = VertexBlendIndices.data(); + Mesh.vertexBlendWeights = VertexBlendWeights.data(); + Mesh.indices = Indices.data(); + /// get mesh + OXR(XrGetHandMeshFb(HandTracker, &Mesh)); + } + } +} + +void XrHandHelper::SessionEnd() { + if (XrDestroyHandTrackerExt) { + OXR(XrDestroyHandTrackerExt(HandTracker)); + } +} + +void XrHandHelper::Update(XrSpace currentSpace, XrTime predictedDisplayTime) { + if (XrLocateHandJointsExt) { + /// aim + AimState.next = nullptr; + /// scale + Scale.next = &AimState; + Scale.sensorOutput = 1.0f; + Scale.currentOutput = 1.0f; + Scale.overrideHandScale = XR_TRUE; + Scale.overrideValueInput = 1.00f; + /// locations + Locations.next = &Scale; + Locations.jointCount = XR_HAND_JOINT_COUNT_EXT; + Locations.jointLocations = JointLocations; + XrHandJointsLocateInfoEXT locateInfo{XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT}; + locateInfo.baseSpace = currentSpace; + locateInfo.time = predictedDisplayTime; + + MatJointScaledFromUnscaled = OVR::Matrix4f::Identity(); + + OXR(XrLocateHandJointsExt(HandTracker, &locateInfo, &Locations)); + OVR::Matrix4f rootMat = OVR::Matrix4f(FromXrPosef(GetWristRootPose())); + OVR::Matrix4f scaleMat = OVR::Matrix4f::Scaling(GetRenderScale()); + MatJointScaledFromUnscaled = rootMat * scaleMat * rootMat.Inverted(); + } +} + +XrPosef XrHandHelper::GetScaledJointPose(XrHandJointEXT joint) const { + OVR::Posef jointPoseWorldUnscaled = FromXrPosef(JointLocations[joint].pose); + OVR::Matrix4f scaled = MatJointScaledFromUnscaled * OVR::Matrix4f(jointPoseWorldUnscaled); + return ToXrPosef(OVR::Posef(scaled)); +} + +void XrHandHelper::ModifyWristRoot(const XrPosef& wristRootPose) { + auto rootPose = FromXrPosef(GetWristRootPose()); + auto modifiedPose = FromXrPosef(wristRootPose); + if (!rootPose.IsEqual(modifiedPose)) { + const OVR::Matrix4f rootMatrix = OVR::Matrix4f(rootPose); + const OVR::Matrix4f rootMatrixOffset = OVR::Matrix4f(modifiedPose); + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + // Apply offset to hand joints + auto j = OVR::Matrix4f(FromXrPosef(JointLocations[i].pose)); + JointLocations[i].pose = ToXrPosef(OVR::Posef(rootMatrixOffset + (j - rootMatrix))); + } + } +} diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.h b/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.h new file mode 100755 index 0000000..7d25380 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrHandHelper.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrHandHelper.h +Content : Helper Inteface for openxr hand extensions +Created : September 2023 +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ +#pragma once + +#include "XrHelper.h" + +class XrHandHelper : public XrHelper { + public: + static std::vector RequiredExtensionNames(); + + public: + XrHandHelper(XrInstance instance, bool isLeft); + ~XrHandHelper() override; + + void SessionInit(XrSession session) override; + void SessionEnd() override; + void Update(XrSpace currentSpace, XrTime predictedDisplayTime) override; + + public: + bool IsLeftHand() const { + return IsLeft; + } + const XrHandTrackingMeshFB& GetMesh() const { + return Mesh; + } + const XrHandTrackingScaleFB& GetScale() const { + return Scale; + } + const XrPosef* GetJointBindPoses() const { + return JointBindPoses; + } + const XrHandJointEXT* GetJointParents() const { + return JointParents; + } + const XrHandJointLocationEXT* GetJoints() const { + return JointLocations; + } + float GetRenderScale() const { + return Scale.sensorOutput; + } + bool IsTracking() const { + return (HandTracker != XR_NULL_HANDLE); + } + bool AreLocationsActive() const { + return IsTracking() && (Locations.isActive); + } + bool IsPositionValid() const { + return JointLocations[XR_HAND_JOINT_PALM_EXT].locationFlags & + XR_SPACE_LOCATION_POSITION_VALID_BIT; + } + const XrPosef& GetAimPose() const { + return AimState.aimPose; + } + const XrPosef& GetWristRootPose() const { + return JointLocations[XR_HAND_JOINT_WRIST_EXT].pose; + } + bool GetIndexPinching() const { + return (AimState.status & XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB) != 0; + } + const XrHandTrackingAimStateFB& GetAimState() const { + return AimState; + } + + XrPosef GetScaledJointPose(XrHandJointEXT joint) const; + void ModifyWristRoot(const XrPosef& wristRootPose); + + private: + bool IsLeft = false; + /// Hands - extension functions + PFN_xrCreateHandTrackerEXT XrCreateHandTrackerExt = nullptr; + PFN_xrDestroyHandTrackerEXT XrDestroyHandTrackerExt = nullptr; + PFN_xrLocateHandJointsEXT XrLocateHandJointsExt = nullptr; + /// Hands - FB mesh rendering extensions + PFN_xrGetHandMeshFB XrGetHandMeshFb = nullptr; + /// Hands - tracker handles + XrHandTrackerEXT HandTracker = XR_NULL_HANDLE; + /// Hands - data buffers + XrHandJointLocationEXT JointLocations[XR_HAND_JOINT_COUNT_EXT]; + XrPosef JointBindPoses[XR_HAND_JOINT_COUNT_EXT]; + XrHandJointEXT JointParents[XR_HAND_JOINT_COUNT_EXT]; + float JointRadii[XR_HAND_JOINT_COUNT_EXT]; + /// mesh storage + XrHandTrackingMeshFB Mesh{XR_TYPE_HAND_TRACKING_MESH_FB}; + std::vector VertexPositions; + std::vector VertexNormals; + std::vector VertexUVs; + std::vector VertexBlendIndices; + std::vector VertexBlendWeights; + std::vector Indices; + /// extension nodes + /// scale + XrHandTrackingScaleFB Scale{XR_TYPE_HAND_TRACKING_SCALE_FB}; + /// aim + XrHandTrackingAimStateFB AimState{XR_TYPE_HAND_TRACKING_AIM_STATE_FB}; + /// joint locations *before* applying hand scale + XrHandJointLocationsEXT Locations{XR_TYPE_HAND_JOINT_LOCATIONS_EXT}; + // cached matrix for applying hand scale to joint locations + OVR::Matrix4f MatJointScaledFromUnscaled; +}; diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrHelper.h b/Samples/XrSamples/XrDynamicObjects/Src/XrHelper.h new file mode 100755 index 0000000..342d94f --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrHelper.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrHelper.h +Content : Base interface to wrap openxr extension functionality +Created : September 2023 +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ +#pragma once + +#include + +class XrHelper { + public: + explicit XrHelper(XrInstance instance) : Instance(instance) {} + virtual ~XrHelper() = default; + + virtual void SessionInit(XrSession session) = 0; + virtual void SessionEnd() = 0; + virtual void Update(XrSpace currentSpace, XrTime predictedDisplayTime) = 0; + + protected: + XrInstance Instance; +}; diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.cpp b/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.cpp new file mode 100755 index 0000000..547077d --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrPassthroughHelper.cpp +Content : Helper inteface for openxr XR_FB_spatial_entity and related extensions +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#include "XrPassthroughHelper.h" + +#include + +std::vector XrPassthroughHelper::RequiredExtensionNames() { + return {XR_FB_PASSTHROUGH_EXTENSION_NAME, XR_FB_TRIANGLE_MESH_EXTENSION_NAME}; +} + +XrPassthroughHelper::XrPassthroughHelper(XrInstance instance) : XrHelper(instance) { + // XR_FB_passthrough + OXR(xrGetInstanceProcAddr( + instance, + "xrCreatePassthroughFB", + reinterpret_cast(&XrCreatePassthroughFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyPassthroughFB", + reinterpret_cast(&XrDestroyPassthroughFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrPassthroughStartFB", + reinterpret_cast(&XrPassthroughStartFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrPassthroughPauseFB", + reinterpret_cast(&XrPassthroughPauseFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrCreatePassthroughLayerFB", + reinterpret_cast(&XrCreatePassthroughLayerFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyPassthroughLayerFB", + reinterpret_cast(&XrDestroyPassthroughLayerFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrPassthroughLayerSetStyleFB", + reinterpret_cast(&XrPassthroughLayerSetStyleFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrCreateGeometryInstanceFB", + reinterpret_cast(&XrCreateGeometryInstanceFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyGeometryInstanceFB", + reinterpret_cast(&XrDestroyGeometryInstanceFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrGeometryInstanceSetTransformFB", + reinterpret_cast(&XrGeometryInstanceSetTransformFB))); + + // XR_FB_triangle_mesh + OXR(xrGetInstanceProcAddr( + instance, + "xrCreateTriangleMeshFB", + reinterpret_cast(&XrCreateTriangleMeshFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrDestroyTriangleMeshFB", + reinterpret_cast(&XrDestroyTriangleMeshFB))); +} + +void XrPassthroughHelper::SessionInit(XrSession session) { + Session = session; + + XrPassthroughCreateInfoFB passthroughInfo{XR_TYPE_PASSTHROUGH_CREATE_INFO_FB}; + OXR(XrCreatePassthroughFB(Session, &passthroughInfo, &Passthrough)); +} + +void XrPassthroughHelper::SessionEnd() { + OXR(XrDestroyPassthroughFB(Passthrough)); + + Session = XR_NULL_HANDLE; +} + +void XrPassthroughHelper::Update(XrSpace currentSpace, XrTime predictedDisplayTime) {} + +XrPassthroughLayerFB XrPassthroughHelper::CreateProjectedLayer() const { + XrPassthroughLayerFB layer = XR_NULL_HANDLE; + + XrPassthroughLayerCreateInfoFB layerInfo{XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB}; + layerInfo.passthrough = Passthrough; + layerInfo.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB; + layerInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB; + XrResult result = XrCreatePassthroughLayerFB(Session, &layerInfo, &layer); + if (result != XR_SUCCESS) { + ALOGE("xrCreatePassthroughLayerFB failed, error %d", result); + return XR_NULL_HANDLE; + } + + XrPassthroughStyleFB style{XR_TYPE_PASSTHROUGH_STYLE_FB}; + style.textureOpacityFactor = 1.0f; + style.edgeColor = {0.0f, 0.0f, 0.0f, 0.0f}; + result = XrPassthroughLayerSetStyleFB(layer, &style); + if (result != XR_SUCCESS) { + ALOGE("xrPassthroughLayerSetStyleFBfailed, error %d", result); + return XR_NULL_HANDLE; + } + + return layer; +} + +void XrPassthroughHelper::DestroyLayer(XrPassthroughLayerFB layer) const { + OXR(XrDestroyPassthroughLayerFB(layer)); +} + +XrGeometryInstanceFB XrPassthroughHelper::CreateGeometryInstance( + XrPassthroughLayerFB layer, + XrSpace baseSpace, + const std::vector& vertices, + const std::vector& indices) { + Geometry geometry; + + XrTriangleMeshCreateInfoFB meshInfo{XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB}; + meshInfo.vertexCount = vertices.size(); + meshInfo.vertexBuffer = vertices.data(); + meshInfo.triangleCount = indices.size() / 3; + meshInfo.indexBuffer = indices.data(); + meshInfo.windingOrder = XR_WINDING_ORDER_UNKNOWN_FB; + XrResult result = XrCreateTriangleMeshFB(Session, &meshInfo, &geometry.Mesh); + if (result != XR_SUCCESS) { + ALOGE("xrCreateTriangleMeshFB, error %d", result); + return XR_NULL_HANDLE; + } + + geometry.Transform.type = XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB; + geometry.Transform.baseSpace = baseSpace; + geometry.Transform.pose.orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + geometry.Transform.pose.position = {0.0f, 0.0f, 0.0f}; + geometry.Transform.scale = {1.0f, 1.0f, 1.0f}; + + XrGeometryInstanceCreateInfoFB geometryInfo{XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB}; + geometryInfo.layer = layer; + geometryInfo.mesh = geometry.Mesh; + geometryInfo.scale = geometry.Transform.scale; + geometryInfo.baseSpace = baseSpace; + geometryInfo.pose = geometry.Transform.pose; + result = XrCreateGeometryInstanceFB(Session, &geometryInfo, &geometry.Instance); + if (result != XR_SUCCESS) { + ALOGE("xrCreateGeometryInstanceFB, error %d", result); + OXR(XrDestroyTriangleMeshFB(geometry.Mesh)); + return XR_NULL_HANDLE; + } + + Geometries.emplace_back(geometry); + return geometry.Instance; +} + +void XrPassthroughHelper::DestroyGeometryInstance(XrGeometryInstanceFB instance) { + auto iter = std::find_if(Geometries.begin(), Geometries.end(), [instance](const auto& geo) { + return geo.Instance == instance; + }); + if (iter == Geometries.end()) { + return; + } + + OXR(XrDestroyTriangleMeshFB(iter->Mesh)); + OXR(XrDestroyGeometryInstanceFB(iter->Instance)); + Geometries.erase(iter); +} + +void XrPassthroughHelper::SetGeometryInstanceTransform( + XrGeometryInstanceFB instance, + XrTime time, + const XrPosef& pose, + const XrVector3f& scale) { + auto iter = std::find_if(Geometries.begin(), Geometries.end(), [instance](const auto& geo) { + return geo.Instance == instance; + }); + if (iter == Geometries.end()) { + return; + } + + auto& transform = iter->Transform; + transform.time = time; + transform.pose = pose; + transform.scale = scale; + OXR(XrGeometryInstanceSetTransformFB(instance, &transform)); +} + +void XrPassthroughHelper::Start() const { + OXR(XrPassthroughStartFB(Passthrough)); +} + +void XrPassthroughHelper::Pause() const { + OXR(XrPassthroughPauseFB(Passthrough)); +} diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.h b/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.h new file mode 100755 index 0000000..b554956 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrPassthroughHelper.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrPassthroughHelper.h +Content : Helper inteface for openxr XR_FB_passthrough and related extensions +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#pragma once + +#include + +#include "XrHelper.h" + +class XrPassthroughHelper : public XrHelper { + public: + static std::vector RequiredExtensionNames(); + + public: + explicit XrPassthroughHelper(XrInstance instance); + + void SessionInit(XrSession session) override; + void SessionEnd() override; + void Update(XrSpace currentSpace, XrTime predictedDisplayTime) override; + + XrPassthroughLayerFB CreateProjectedLayer() const; + void DestroyLayer(XrPassthroughLayerFB layer) const; + + XrGeometryInstanceFB CreateGeometryInstance( + XrPassthroughLayerFB layer, + XrSpace baseSpace, + const std::vector& vertices, + const std::vector& indices); + void DestroyGeometryInstance(XrGeometryInstanceFB instance); + void SetGeometryInstanceTransform( + XrGeometryInstanceFB instance, + XrTime time, + const XrPosef& pose, + const XrVector3f& scale); + + void Start() const; + void Pause() const; + + private: + XrSession Session = XR_NULL_HANDLE; + + // XR_FB_passthrough + PFN_xrCreatePassthroughFB XrCreatePassthroughFB = nullptr; + PFN_xrDestroyPassthroughFB XrDestroyPassthroughFB = nullptr; + PFN_xrPassthroughStartFB XrPassthroughStartFB = nullptr; + PFN_xrPassthroughPauseFB XrPassthroughPauseFB = nullptr; + PFN_xrCreatePassthroughLayerFB XrCreatePassthroughLayerFB = nullptr; + PFN_xrDestroyPassthroughLayerFB XrDestroyPassthroughLayerFB = nullptr; + PFN_xrPassthroughLayerSetStyleFB XrPassthroughLayerSetStyleFB = nullptr; + PFN_xrCreateGeometryInstanceFB XrCreateGeometryInstanceFB = nullptr; + PFN_xrDestroyGeometryInstanceFB XrDestroyGeometryInstanceFB = nullptr; + PFN_xrGeometryInstanceSetTransformFB XrGeometryInstanceSetTransformFB = nullptr; + + // XR_FB_triangle_mesh + PFN_xrCreateTriangleMeshFB XrCreateTriangleMeshFB = nullptr; + PFN_xrDestroyTriangleMeshFB XrDestroyTriangleMeshFB = nullptr; + + struct Geometry { + XrGeometryInstanceFB Instance = XR_NULL_HANDLE; + XrTriangleMeshFB Mesh = XR_NULL_HANDLE; + XrGeometryInstanceTransformFB Transform{XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB}; + }; + + XrPassthroughFB Passthrough = XR_NULL_HANDLE; + std::vector Geometries; +}; diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.cpp b/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.cpp new file mode 100755 index 0000000..351877a --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrSpatialAnchorHelper.cpp +Content : Helper inteface for openxr XR_FB_spatial_entity and related extensions +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#include "XrSpatialAnchorHelper.h" + +#include + +std::vector XrSpatialAnchorHelper::RequiredExtensionNames() { + return { + XR_FB_SCENE_EXTENSION_NAME, + XR_FB_SPATIAL_ENTITY_EXTENSION_NAME, + XR_FB_SPATIAL_ENTITY_QUERY_EXTENSION_NAME, + XR_FB_SPATIAL_ENTITY_STORAGE_EXTENSION_NAME}; +} + +XrSpatialAnchorHelper::XrSpatialAnchorHelper(XrInstance instance) : XrHelper(instance) { + // XR_FB_scene + OXR(xrGetInstanceProcAddr( + instance, + "xrGetSpaceBoundingBox3DFB", + reinterpret_cast(&XrGetSpaceBoundingBox3DFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrGetSpaceSemanticLabelsFB", + reinterpret_cast(&XrGetSpaceSemanticLabelsFB))); + + // XR_FB_spatial_entity + OXR(xrGetInstanceProcAddr( + instance, + "xrEnumerateSpaceSupportedComponentsFB", + reinterpret_cast(&XrEnumerateSpaceSupportedComponentsFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrSetSpaceComponentStatusFB", + reinterpret_cast(&XrSetSpaceComponentStatusFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrGetSpaceComponentStatusFB", + reinterpret_cast(&XrGetSpaceComponentStatusFB))); + + // XR_FB_spatial_entity_query + OXR(xrGetInstanceProcAddr( + instance, "xrQuerySpacesFB", reinterpret_cast(&XrQuerySpacesFB))); + OXR(xrGetInstanceProcAddr( + instance, + "xrRetrieveSpaceQueryResultsFB", + reinterpret_cast(&XrRetrieveSpaceQueryResultsFB))); +} + +void XrSpatialAnchorHelper::SessionInit(XrSession session) { + Session = session; +} + +void XrSpatialAnchorHelper::SessionEnd() { + Session = XR_NULL_HANDLE; +} + +void XrSpatialAnchorHelper::Update(XrSpace currentSpace, XrTime predictedDisplayTime) {} + +bool XrSpatialAnchorHelper::HandleXrEvents(const XrEventDataBaseHeader* baseEventHeader) { + bool handled = false; + switch (baseEventHeader->type) { + case XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB: { + const auto resultsAvailable = + reinterpret_cast(baseEventHeader); + if (resultsAvailable->requestId != QuerySpacesRequestId) { + ALOGE( + "Unexpected request id %ull for xrQuerySpacesFB results, ignoring...", + resultsAvailable->requestId); + break; + } + + // This is for our request, mark event handled regardless of whether retrieval succeeds + handled = true; + + XrSpaceQueryResultsFB queryResults{XR_TYPE_SPACE_QUERY_RESULTS_FB}; + XrResult result = + XrRetrieveSpaceQueryResultsFB(Session, resultsAvailable->requestId, &queryResults); + if (result != XR_SUCCESS) { + ALOGE("xrRetrieveSpaceQueryResultsFB failed, error %d", result); + break; + } + + Anchors.clear(); + Anchors.resize(queryResults.resultCountOutput); + + queryResults.resultCapacityInput = Anchors.size(); + queryResults.resultCountOutput = 0; + queryResults.results = Anchors.data(); + + result = + XrRetrieveSpaceQueryResultsFB(Session, resultsAvailable->requestId, &queryResults); + if (result != XR_SUCCESS) { + ALOGE("xrRetrieveSpaceQueryResultsFB failed, error %d", result); + break; + } + + ALOG( + "xrQuerySpacesFB results received: %u, request id %ull", + queryResults.resultCountOutput, + resultsAvailable->requestId); + } break; + case XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB: { + const auto queryComplete = + reinterpret_cast(baseEventHeader); + if (queryComplete->requestId != QuerySpacesRequestId) { + ALOGE( + "Unexpected request id %ull for xrQuerySpacesFB complete, ignoring...", + queryComplete->requestId); + return false; + } + ALOG("xrQuerySpacesFB completed, request id %ull", queryComplete->requestId); + QuerySpacesRequestId = 0; + handled = true; + } break; + case XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB: { + const auto setStatusComplete = + reinterpret_cast(baseEventHeader); + ALOG( + "xrSetSpaceComponentStatusFB completed, type %d, result %s, request id %ull", + setStatusComplete->componentType, + setStatusComplete->result == XR_SUCCESS ? "success" : "fail", + setStatusComplete->requestId); + RequestIds.erase(setStatusComplete->requestId); + handled = true; + } break; + default: { + } + } + + return handled; +} + +void XrSpatialAnchorHelper::SetSupportedSemanticLabels(std::string supportedLabels) { + SupportedLabels = std::move(supportedLabels); +} + +bool XrSpatialAnchorHelper::RequestQueryAnchors(XrAsyncRequestIdFB& requestId) { + if (QuerySpacesRequestId != 0) { + // Only allow one query in-flight at a time + ALOGW("xrQuerySpacesFB request already sent."); + return false; + } + + XrSpaceQueryInfoFB queryInfo{ + XR_TYPE_SPACE_QUERY_INFO_FB, + nullptr, + XR_SPACE_QUERY_ACTION_LOAD_FB, + 100 /*maxResultCount*/, + XR_NO_DURATION, + nullptr, + nullptr}; + XrResult result = XrQuerySpacesFB( + Session, + reinterpret_cast(&queryInfo), + &QuerySpacesRequestId); + if (result != XR_SUCCESS) { + ALOGE("xrQuerySpacesFB failed, error %d", result); + return false; + } + + ALOG("xrQuerySpacesFB request sent, request id %ull", QuerySpacesRequestId); + + requestId = QuerySpacesRequestId; + return true; +} + +bool XrSpatialAnchorHelper::RequestQueryAnchorsWithComponentEnabled( + XrSpaceComponentTypeFB componentType, + XrAsyncRequestIdFB& requestId) { + if (QuerySpacesRequestId != 0) { + // Only allow one query in-flight at a time + ALOGW("xrQuerySpacesFB request already sent."); + return false; + } + + XrSpaceStorageLocationFilterInfoFB storageLocationFilterInfo{ + XR_TYPE_SPACE_STORAGE_LOCATION_FILTER_INFO_FB, nullptr, XR_SPACE_STORAGE_LOCATION_LOCAL_FB}; + XrSpaceComponentFilterInfoFB componentFilterInfo{ + XR_TYPE_SPACE_COMPONENT_FILTER_INFO_FB, &storageLocationFilterInfo, componentType}; + XrSpaceQueryInfoFB queryInfo{ + XR_TYPE_SPACE_QUERY_INFO_FB, + nullptr, + XR_SPACE_QUERY_ACTION_LOAD_FB, + 100 /*maxResultCount*/, + XR_NO_DURATION, + reinterpret_cast(&componentFilterInfo), + nullptr}; + XrResult result = XrQuerySpacesFB( + Session, + reinterpret_cast(&queryInfo), + &QuerySpacesRequestId); + if (result != XR_SUCCESS) { + ALOGE("xrQuerySpacesFB failed, error %d", result); + return false; + } + + ALOG( + "xrQuerySpacesFB request sent with component filter %d, request id %ull", + static_cast(componentType), + QuerySpacesRequestId); + + requestId = QuerySpacesRequestId; + return true; +} + +bool XrSpatialAnchorHelper::RequestSetComponentStatus( + XrSpace space, + XrSpaceComponentTypeFB type, + bool enabled, + XrAsyncRequestIdFB& requestId) { + if (!IsComponentSupported(space, type)) { + ALOGE("Anchor does not support component %d.", type); + return false; + } + + XrSpaceComponentStatusSetInfoFB setInfo{ + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB, nullptr, type, enabled, 0}; + XrAsyncRequestIdFB reqId; + XrResult result = XrSetSpaceComponentStatusFB(space, &setInfo, &reqId); + if (XR_FAILED(result)) { + ALOGE("xrSetSpaceComponentStatusFB failed, error %d", result); + return false; + } + + ALOG("xrSetSpaceComponentStatusFB request sent for component %d, request id %ull", type, reqId); + + RequestIds.insert(reqId); + requestId = reqId; + return true; +} + +bool XrSpatialAnchorHelper::IsRequestCompleted(XrAsyncRequestIdFB requestId) const { + if (QuerySpacesRequestId == requestId) { + return false; + } + if (RequestIds.find(requestId) != RequestIds.end()) { + return false; + } + return true; +} + +std::vector XrSpatialAnchorHelper::GetSpatialAnchors( + std::function filter) const { + std::vector results; + for (const auto& anchor : Anchors) { + if (filter && filter(anchor.space)) { + results.emplace_back(anchor.space); + } + } + return results; +} + +bool XrSpatialAnchorHelper::IsComponentSupported(XrSpace space, XrSpaceComponentTypeFB type) const { + uint32_t numComponents = 0; + OXR(XrEnumerateSpaceSupportedComponentsFB(space, 0, &numComponents, nullptr)); + std::vector components(numComponents); + OXR(XrEnumerateSpaceSupportedComponentsFB( + space, numComponents, &numComponents, components.data())); + const bool supported = + std::find(components.begin(), components.end(), type) != components.end(); + return supported; +} + +bool XrSpatialAnchorHelper::GetComponentStatus( + XrSpace space, + XrSpaceComponentTypeFB type, + bool& enabled) const { + if (!IsComponentSupported(space, type)) { + ALOGE("Anchor does not support component %d.", type); + return false; + } + + XrSpaceComponentStatusFB status{XR_TYPE_SPACE_COMPONENT_STATUS_FB}; + XrResult result = XrGetSpaceComponentStatusFB(space, type, &status); + if (XR_FAILED(result)) { + ALOGE("XrGetSpaceComponentStatusFB failed, error %d", result); + return false; + } + + enabled = (status.enabled && !status.changePending); + return true; +} + +bool XrSpatialAnchorHelper::GetSemanticLabels(XrSpace space, std::string& labels) const { + if (!IsComponentSupported(space, XR_SPACE_COMPONENT_TYPE_SEMANTIC_LABELS_FB)) { + ALOGE("Anchor does not support semantic label component."); + return false; + } + + XrSemanticLabelsSupportInfoFB supportInfo{ + XR_TYPE_SEMANTIC_LABELS_SUPPORT_INFO_FB, + nullptr, + XR_SEMANTIC_LABELS_SUPPORT_MULTIPLE_SEMANTIC_LABELS_BIT_FB, + SupportedLabels.c_str()}; + XrSemanticLabelsFB semanticLabels{XR_TYPE_SEMANTIC_LABELS_FB}; + if (!SupportedLabels.empty()) { + semanticLabels.next = &supportInfo; + } + XrResult result = XrGetSpaceSemanticLabelsFB(Session, space, &semanticLabels); + if (XR_FAILED(result)) { + ALOGE("xrGetSpaceSemanticLabelsFB failed, error %d", result); + return false; + } + + std::vector labelData(semanticLabels.bufferCountOutput); + semanticLabels.bufferCapacityInput = labelData.size(); + semanticLabels.buffer = labelData.data(); + + result = XrGetSpaceSemanticLabelsFB(Session, space, &semanticLabels); + if (XR_FAILED(result)) { + ALOGE("xrGetSpaceSemanticLabelsFB failed, error %d", result); + return false; + } + + labels.assign(semanticLabels.buffer, semanticLabels.bufferCountOutput); + return true; +} + +bool XrSpatialAnchorHelper::GetBoundingBox3D(XrSpace space, XrRect3DfFB& boundingBox3D) const { + if (!IsComponentSupported(space, XR_SPACE_COMPONENT_TYPE_BOUNDED_3D_FB)) { + ALOGE("Anchor does not support bounded 3D component."); + return false; + } + + XrResult result = XrGetSpaceBoundingBox3DFB(Session, space, &boundingBox3D); + if (XR_FAILED(result)) { + ALOGE("xrGetSpaceBoundingBox3DFB failed, error %d", result); + return false; + } + return true; +} diff --git a/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.h b/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.h new file mode 100755 index 0000000..83e2646 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/XrSpatialAnchorHelper.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : XrSpatialAnchorHelper.h +Content : Helper inteface for openxr XR_FB_spatial_entity and related extensions +Created : September 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#pragma once + +#include + +#include + +#include "XrHelper.h" + +class XrSpatialAnchorHelper : public XrHelper { + public: + static std::vector RequiredExtensionNames(); + + public: + explicit XrSpatialAnchorHelper(XrInstance instance); + + void SessionInit(XrSession session) override; + void SessionEnd() override; + void Update(XrSpace currentSpace, XrTime predictedDisplayTime) override; + + bool HandleXrEvents(const XrEventDataBaseHeader* baseEventHeader); + + void SetSupportedSemanticLabels(std::string supportedLabels); + + bool RequestQueryAnchors(XrAsyncRequestIdFB& requestId); + bool RequestQueryAnchorsWithComponentEnabled( + XrSpaceComponentTypeFB componentType, + XrAsyncRequestIdFB& requestId); + bool RequestSetComponentStatus( + XrSpace space, + XrSpaceComponentTypeFB type, + bool enabled, + XrAsyncRequestIdFB& requestId); + + bool IsRequestCompleted(XrAsyncRequestIdFB requestId) const; + std::vector GetSpatialAnchors(std::function filter = {}) const; + bool IsComponentSupported(XrSpace space, XrSpaceComponentTypeFB type) const; + bool GetComponentStatus(XrSpace space, XrSpaceComponentTypeFB type, bool& enabled) const; + bool GetSemanticLabels(XrSpace space, std::string& labels) const; + bool GetBoundingBox3D(XrSpace space, XrRect3DfFB& boundingBox3D) const; + + private: + XrSession Session = XR_NULL_HANDLE; + + // XR_FB_scene + PFN_xrGetSpaceBoundingBox3DFB XrGetSpaceBoundingBox3DFB = nullptr; + PFN_xrGetSpaceSemanticLabelsFB XrGetSpaceSemanticLabelsFB = nullptr; + + // XR_FB_spatial_entity + PFN_xrEnumerateSpaceSupportedComponentsFB XrEnumerateSpaceSupportedComponentsFB = nullptr; + PFN_xrSetSpaceComponentStatusFB XrSetSpaceComponentStatusFB = nullptr; + PFN_xrGetSpaceComponentStatusFB XrGetSpaceComponentStatusFB = nullptr; + + // XR_FB_spatial_entity_query + PFN_xrQuerySpacesFB XrQuerySpacesFB = nullptr; + PFN_xrRetrieveSpaceQueryResultsFB XrRetrieveSpaceQueryResultsFB = nullptr; + + std::string SupportedLabels; + + XrAsyncRequestIdFB QuerySpacesRequestId = 0; + std::unordered_set RequestIds; + + std::vector Anchors; +}; diff --git a/Samples/XrSamples/XrDynamicObjects/Src/main.cpp b/Samples/XrSamples/XrDynamicObjects/Src/main.cpp new file mode 100755 index 0000000..154018c --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/Src/main.cpp @@ -0,0 +1,813 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +/************************************************************************************************ +Filename : main.cpp +Content : Sample test app to test openxr XR_META_dynamic_object_tracker extension +Created : August 2023 +Authors : Adam Bengis, Peter Chan +Language : C++ +Copyright : Copyright (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. +************************************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "XrDynamicObjectTrackerHelper.h" +#include "XrHandHelper.h" +#include "XrPassthroughHelper.h" +#include "XrSpatialAnchorHelper.h" + +class XrDynamicObjectsApp : public OVRFW::XrApp { + public: + XrDynamicObjectsApp() = default; + + // Returns a list of OpenXr extensions needed for this app + std::vector GetExtensions() override { + std::vector extensions = XrApp::GetExtensions(); + // Add hand extensions + for (const auto& handExtension : XrHandHelper::RequiredExtensionNames()) { + extensions.push_back(handExtension); + } + // Add dynamic object tracker extension + for (const auto& dynamicObjectTrackerExtension : + XrDynamicObjectTrackerHelper::RequiredExtensionNames()) { + extensions.push_back(dynamicObjectTrackerExtension); + } + // Add spatial anchor extensions + for (const auto& spatialAnchorExtension : XrSpatialAnchorHelper::RequiredExtensionNames()) { + extensions.push_back(spatialAnchorExtension); + } + // Add passthrough extensions + for (const auto& passthroughExtension : XrPassthroughHelper::RequiredExtensionNames()) { + extensions.push_back(passthroughExtension); + } + + // Log all extensions + ALOG("XrDynamicObjectsApp requesting extensions:"); + for (const auto& e : extensions) { + ALOG(" --> %s", e); + } + + return extensions; + } + + // Must return true if the application initializes successfully + bool AppInit(const xrJava* context) override { + if (!UI.Init(context, GetFileSys(), false)) { + ALOGE("TinyUI::Init FAILED."); + return false; + } + + // Hand tracking + HandsExtensionAvailable = ExtensionsArePresent(XrHandHelper::RequiredExtensionNames()); + if (HandsExtensionAvailable) { + HandL = std::make_unique(GetInstance(), true); + HandR = std::make_unique(GetInstance(), false); + } + + // Dynamic object tracking + if (ExtensionsArePresent(XrDynamicObjectTrackerHelper::RequiredExtensionNames())) { + DynamicObjectTracker = std::make_unique(GetInstance()); + } + + // Spatial anchor + if (ExtensionsArePresent(XrSpatialAnchorHelper::RequiredExtensionNames())) { + SpatialAnchor = std::make_unique(GetInstance()); + } + + // Passthrough + if (ExtensionsArePresent(XrPassthroughHelper::RequiredExtensionNames())) { + Passthrough = std::make_unique(GetInstance()); + } + + return true; + } + + void AppShutdown(const xrJava* context) override { + Passthrough.reset(); + SpatialAnchor.reset(); + DynamicObjectTracker.reset(); + HandL.reset(); + HandR.reset(); + + UIInitialized = false; + HandsExtensionAvailable = false; + + UI.Shutdown(); + OVRFW::XrApp::AppShutdown(context); + } + + bool SessionInit() override { + // Use LocalSpace instead of Stage Space + CurrentSpace = LocalSpace; + + // Init session bound objects + if (!ControllerRenderL.Init(true)) { + ALOGE("SessionInit::Init L controller renderer FAILED."); + return false; + } + if (!ControllerRenderR.Init(false)) { + ALOGE("SessionInit::Init R controller renderer FAILED."); + return false; + } + BeamRenderer.Init(GetFileSys(), nullptr, OVR::Vector4f(1.0f), 1.0f); + ParticleSystem.Init(10, nullptr, OVRFW::ovrParticleSystem::GetDefaultGpuState(), false); + + if (HandsExtensionAvailable) { + HandL->SessionInit(GetSession()); + HandR->SessionInit(GetSession()); + HandRendererL.Init(&HandL->GetMesh(), HandL->IsLeftHand()); + HandRendererR.Init(&HandR->GetMesh(), HandR->IsLeftHand()); + } + if (DynamicObjectTracker) { + DynamicObjectTracker->SessionInit(GetSession()); + } + if (SpatialAnchor) { + SpatialAnchor->SessionInit(GetSession()); + } + if (Passthrough) { + Passthrough->SessionInit(GetSession()); + PassthroughLayer = Passthrough->CreateProjectedLayer(); + Passthrough->Start(); + } + + AxisRenderer.Init(); + + GeometryRenderer.Init(OVRFW::BuildTesselatedQuadDescriptor( + 4, 4, false /*twoSided*/)); // quad in XY plane, facing +Z + + GeometryRenderer.DiffuseColor = {1.0f, 1.0f, 1.0f, 1.0f}; + GeometryRenderer.ChannelControl = {0, 1, 0, 1}; + GeometryRenderer.AmbientLightColor = {1, 1, 1}; + GeometryRenderer.BlendMode = GL_FUNC_REVERSE_SUBTRACT; + + return true; + } + + void SessionEnd() override { + GeometryRenderer.Shutdown(); + AxisRenderer.Shutdown(); + + if (Passthrough) { + Passthrough->DestroyGeometryInstance(PassthroughWindow); + Passthrough->DestroyLayer(PassthroughLayer); + Passthrough->SessionEnd(); + } + if (SpatialAnchor) { + SpatialAnchor->SessionEnd(); + } + if (DynamicObjectTracker) { + DynamicObjectTracker->DestroyDynamicObjectTracker(); + DynamicObjectTracker->SessionEnd(); + } + if (HandsExtensionAvailable) { + HandL->SessionEnd(); + HandR->SessionEnd(); + HandRendererL.Shutdown(); + HandRendererR.Shutdown(); + } + + ControllerRenderL.Shutdown(); + ControllerRenderR.Shutdown(); + ParticleSystem.Shutdown(); + BeamRenderer.Shutdown(); + } + + void HandleXrEvents() override { + XrEventDataBuffer eventDataBuffer = {}; + + // Poll for events + for (;;) { + XrEventDataBaseHeader* baseEventHeader = (XrEventDataBaseHeader*)(&eventDataBuffer); + baseEventHeader->type = XR_TYPE_EVENT_DATA_BUFFER; + baseEventHeader->next = NULL; + XrResult r; + OXR(r = xrPollEvent(Instance, &eventDataBuffer)); + if (r != XR_SUCCESS) { + break; + } + + if (DynamicObjectTracker && DynamicObjectTracker->HandleXrEvents(baseEventHeader)) { + continue; + } + if (SpatialAnchor && SpatialAnchor->HandleXrEvents(baseEventHeader)) { + continue; + } + + switch (baseEventHeader->type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST event"); + break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: + ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event"); + break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED event"); + break; + case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT: { + const auto perfSettingsEvent = + reinterpret_cast(baseEventHeader); + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT event: type %d subdomain %d : level %d -> level %d", + perfSettingsEvent->type, + perfSettingsEvent->subDomain, + perfSettingsEvent->fromLevel, + perfSettingsEvent->toLevel); + } break; + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING event"); + break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + const auto sessionStateChangedEvent = + reinterpret_cast(baseEventHeader); + ALOGV( + "xrPollEvent: received XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: %d for session %p at time %f", + sessionStateChangedEvent->state, + reinterpret_cast(sessionStateChangedEvent->session), + FromXrTime(sessionStateChangedEvent->time)); + + switch (sessionStateChangedEvent->state) { + case XR_SESSION_STATE_FOCUSED: + Focused = true; + break; + case XR_SESSION_STATE_VISIBLE: + Focused = false; + break; + case XR_SESSION_STATE_READY: + case XR_SESSION_STATE_STOPPING: + HandleSessionStateChanges(sessionStateChangedEvent->state); + break; + case XR_SESSION_STATE_EXITING: + ShouldExit = true; + break; + default: + break; + } + } break; + default: + ALOGV("xrPollEvent: Unknown event"); + break; + } + } + } + + void Update(const OVRFW::ovrApplFrameIn& in) override { + InitializeUI(); + + XrSpace currentSpace = GetCurrentSpace(); + XrTime predictedDisplayTime = ToXrTime(in.PredictedDisplayTime); + + if (HandsExtensionAvailable) { + HandL->Update(currentSpace, predictedDisplayTime); + HandR->Update(currentSpace, predictedDisplayTime); + } + + UpdateDynamicObjectTracker(currentSpace, predictedDisplayTime); + UpdateSpatialAnchor(currentSpace, predictedDisplayTime); + UpdatePassthrough(currentSpace, predictedDisplayTime); + UpdateUIHitTests(in); + + // Hands + if (HandsExtensionAvailable) { + if (HandL->AreLocationsActive()) { + HandRendererL.Update(HandL->GetJoints(), HandL->GetRenderScale()); + } + if (HandR->AreLocationsActive()) { + HandRendererR.Update(HandR->GetJoints(), HandR->GetRenderScale()); + } + } + + // Controllers + if (in.LeftRemoteTracked) { + ControllerRenderL.Update(in.LeftRemotePose); + } + if (in.RightRemoteTracked) { + ControllerRenderR.Update(in.RightRemotePose); + } + } + + void Render(const OVRFW::ovrApplFrameIn& in, OVRFW::ovrRendererOutput& out) override { + if (KeyboardTracking) { + AxisRenderer.Render(OVR::Matrix4f(), in, out); + GeometryRenderer.Render(out.Surfaces); + } + + UI.Render(in, out); + + if (HandsExtensionAvailable && HandL->AreLocationsActive() && HandL->IsPositionValid()) { + const float transparency = ComputeHandTransparency( + FromXrPosef(HandL->GetJoints()[XR_HAND_JOINT_MIDDLE_TIP_EXT].pose).Translation); + HandRendererL.Confidence = transparency; + HandRendererL.Render(out.Surfaces); + } else if (in.LeftRemoteTracked) { + ControllerRenderL.Render(out.Surfaces); + } + + if (HandsExtensionAvailable && HandR->AreLocationsActive() && HandR->IsPositionValid()) { + const float transparency = ComputeHandTransparency( + FromXrPosef(HandR->GetJoints()[XR_HAND_JOINT_MIDDLE_TIP_EXT].pose).Translation); + HandRendererR.Confidence = transparency; + HandRendererR.Render(out.Surfaces); + } else if (in.RightRemoteTracked) { + ControllerRenderR.Render(out.Surfaces); + } + + // Render beams last for proper blending + ParticleSystem.Frame(in, nullptr, out.FrameMatrices.CenterView); + ParticleSystem.RenderEyeView( + out.FrameMatrices.CenterView, out.FrameMatrices.EyeProjection[0], out.Surfaces); + BeamRenderer.Render(in, out); + } + + void PreProjectionAddLayer(xrCompositorLayerUnion* layers, int& layerCount) override { + static XrCompositionLayerAlphaBlendFB alphaBlend{XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB}; + alphaBlend.next = nullptr; + alphaBlend.srcFactorColor = XR_BLEND_FACTOR_ONE_FB; + alphaBlend.dstFactorColor = XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB; + alphaBlend.srcFactorAlpha = XR_BLEND_FACTOR_ONE_FB; + alphaBlend.dstFactorAlpha = XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB; + + // Add the passthrough layer + if (PassthroughLayer != XR_NULL_HANDLE) { + XrCompositionLayerPassthroughFB passthroughLayer{ + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB}; + passthroughLayer.next = &alphaBlend; + passthroughLayer.layerHandle = PassthroughLayer; + layers[layerCount++].Passthrough = passthroughLayer; + } + } + + private: + enum class HitTestRayDeviceNums { + LeftHand, + LeftRemote, + RightHand, + RightRemote, + }; + + bool ExtensionsArePresent(const std::vector& extensionList) const { + const auto extensionProperties = GetXrExtensionProperties(); + bool foundAllExtensions = true; + for (const auto& extension : extensionList) { + bool foundExtension = false; + for (const auto& extensionProperty : extensionProperties) { + if (!strcmp(extension, extensionProperty.extensionName)) { + foundExtension = true; + break; + } + } + if (!foundExtension) { + foundAllExtensions = false; + break; + } + } + return foundAllExtensions; + } + + void InitializeUI() { + if (UIInitialized) { + return; + } + + UIInitialized = true; + TextLabel = UI.AddLabel("Dynamic Objects Sample", {0.0f, 0.5f, -1.5f}, {600.0f, 50.0f}); + + StatusTextLabel = UI.AddLabel("", {0.0f, 0.1f, -1.5f}, {600.0f, 320.0f}); + OVRFW::VRMenuFontParms fontParms = StatusTextLabel->GetFontParms(); + fontParms.AlignHoriz = OVRFW::HORIZONTAL_LEFT; + fontParms.AlignVert = OVRFW::VERTICAL_BASELINE; + fontParms.WrapWidth = 1.1f; + fontParms.MaxLines = 10; + StatusTextLabel->SetFontParms(fontParms); + StatusTextLabel->SetTextLocalPosition({-0.55f, 0.25f, 0.0f}); + + StatusText += "DynamicObjectTracker "; + if (DynamicObjectTracker) { + StatusText += DynamicObjectTracker->IsSupported() ? "supported\n" : "unsupported\n"; + } else { + StatusText += "unavailable\n"; + } + StatusText += + "SpatialAnchor " + std::string(SpatialAnchor ? "available\n" : "unavailable\n"); + StatusText += "Passthrough " + std::string(Passthrough ? "available\n" : "unavailable\n"); + StatusTextLabel->SetText(StatusText.c_str()); + } + + void EnableButton(OVRFW::VRMenuObject* button) { + button->SetSurfaceColor(0, UI.BackgroundColor); + button->RemoveFlags(OVRFW::VRMENUOBJECT_DONT_HIT_ALL); + } + + void DisableButton(OVRFW::VRMenuObject* button) { + button->SetSurfaceColor(0, {0.1f, 0.1f, 0.1f, 1.0f}); + button->AddFlags(OVRFW::VRMENUOBJECT_DONT_HIT_ALL); + } + + OVRFW::ovrParticleSystem::handle_t AddParticle( + const OVRFW::ovrApplFrameIn& in, + const OVR::Vector3f& position) { + return ParticleSystem.AddParticle( + in, + position, + 0.0f, + OVR::Vector3f(0.0f), + OVR::Vector3f(0.0f), + BeamRenderer.PointerParticleColor, + OVRFW::ovrEaseFunc::NONE, + 0.0f, + 0.03f, + 0.1f, + 0); + } + + void UpdateUIHitTests(const OVRFW::ovrApplFrameIn& in) { + UI.HitTestDevices().clear(); + ParticleSystem.RemoveParticle(LeftControllerPoint); + ParticleSystem.RemoveParticle(RightControllerPoint); + + // The controller actions are still triggered with hand tracking + if (HandsExtensionAvailable && HandL->IsPositionValid()) { + UpdateRemoteTrackedUIHitTest( + FromXrPosef(HandL->GetAimPose()), + HandL->GetIndexPinching() ? 1.0f : 0.0f, + HandL.get(), + HitTestRayDeviceNums::LeftHand); + } else if (in.LeftRemoteTracked) { + UpdateRemoteTrackedUIHitTest( + in.LeftRemotePointPose, + in.LeftRemoteIndexTrigger, + HandL.get(), + HitTestRayDeviceNums::LeftRemote); + LeftControllerPoint = AddParticle(in, in.LeftRemotePointPose.Translation); + } + + if (HandsExtensionAvailable && HandR->IsPositionValid()) { + UpdateRemoteTrackedUIHitTest( + FromXrPosef(HandR->GetAimPose()), + HandR->GetIndexPinching() ? 1.0f : 0.0f, + HandR.get(), + HitTestRayDeviceNums::RightHand); + } else if (in.RightRemoteTracked) { + UpdateRemoteTrackedUIHitTest( + in.RightRemotePointPose, + in.RightRemoteIndexTrigger, + HandR.get(), + HitTestRayDeviceNums::RightRemote); + RightControllerPoint = AddParticle(in, in.RightRemotePointPose.Translation); + } + + UI.Update(in); + BeamRenderer.Update(in, UI.HitTestDevices()); + } + + void UpdateRemoteTrackedUIHitTest( + const OVR::Posef& remotePose, + float remoteIndexTrigger, + XrHandHelper* hand, + HitTestRayDeviceNums device) { + const bool didPinch = remoteIndexTrigger > 0.25f; + UI.AddHitTestRay(remotePose, didPinch, static_cast(device)); + } + + void UpdateDynamicObjectTracker(XrSpace currentSpace, XrTime predictedDisplayTime) { + if (!DynamicObjectTracker) { + return; + } + + DynamicObjectTracker->Update(currentSpace, predictedDisplayTime); + + // Check if dynamic object tracker is supported + if (!DynamicObjectTracker->IsSupported()) { + DynamicObjectTracker.reset(); + return; + } + + const auto trackerState = DynamicObjectTracker->GetTrackerState(); + if (trackerState == XrDynamicObjectTrackerHelper::TrackerState::Empty) { + // Create tracker if it is uninitialized + if (DynamicObjectTracker->CreateDynamicObjectTracker()) { + StatusText += "Creating DynamicObjectTracker...\n"; + StatusTextLabel->SetText(StatusText.c_str()); + return; + } else { + StatusText += "Failed to create DynamicObjectTracker!\n"; + StatusTextLabel->SetText(StatusText.c_str()); + DynamicObjectTracker.reset(); + return; + } + } + + if (trackerState == XrDynamicObjectTrackerHelper::TrackerState::Created) { + // Set object class that we want to track + const std::vector trackedClasses = { + XR_DYNAMIC_OBJECT_CLASS_KEYBOARD_METAX1}; + if (DynamicObjectTracker->SetDynamicObjectTrackedClasses(trackedClasses)) { + StatusText += "Looking for: KEYBOARD\n"; + StatusTextLabel->SetText(StatusText.c_str()); + return; + } else { + StatusText += "Failed to set tracked classes: KEYBOARD\n"; + StatusTextLabel->SetText(StatusText.c_str()); + DynamicObjectTracker.reset(); + return; + } + } + + if (trackerState == XrDynamicObjectTrackerHelper::TrackerState::Initialized) { + // Tracker ready, if we don't have a keyboard then try looking for objects + // Currently we update our anchor at interval to avoid the bounding box miscmatching + if (SpatialAnchor && predictedDisplayTime > NextQueryAnchorTime) { + if (SpatialAnchor->RequestQueryAnchorsWithComponentEnabled( + XR_SPACE_COMPONENT_TYPE_DYNAMIC_OBJECT_DATA_METAX1, QueryAnchorRequestId)) { + StatusText += NextQueryAnchorTime == 0 ? "Querying spatial anchors" : "."; + StatusTextLabel->SetText(StatusText.c_str()); + } else { + StatusText += "Failed to query spatial anchors!\n"; + StatusTextLabel->SetText(StatusText.c_str()); + } + // If no anchor is found, retry after a short delay. + NextQueryAnchorTime = predictedDisplayTime + ToXrTime(1.0); + } + } + } + + void UpdateSpatialAnchor(XrSpace currentSpace, XrTime predictedDisplayTime) { + if (!SpatialAnchor) { + return; + } + + SpatialAnchor->Update(currentSpace, predictedDisplayTime); + + // Wait for pending requests to finish + if (QueryAnchorRequestId != 0 && SpatialAnchor->IsRequestCompleted(QueryAnchorRequestId)) { + QueryAnchorRequestId = 0; + } + if (EnableLocatableRequestId != 0 && + SpatialAnchor->IsRequestCompleted(EnableLocatableRequestId)) { + EnableLocatableRequestId = 0; + } + if (QueryAnchorRequestId != 0 || EnableLocatableRequestId != 0) { + return; + } + + // Check if we have a detected keyboard + KeyboardSpace = DetectKeyboard(); + if (KeyboardSpace == XR_NULL_HANDLE) { + return; + } + + // We have a keyboard, make sure it's locatable + bool keyboardLocatable = false; + if (!SpatialAnchor->GetComponentStatus( + KeyboardSpace, XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB, keyboardLocatable)) { + ALOGE("KeyboardSpace is not locatable!"); + return; + } + + // Start tracking the keyboard if it is untracked + if (!keyboardLocatable) { + StatusText += "Locating KeyboardSpace...\n"; + StatusTextLabel->SetText(StatusText.c_str()); + SpatialAnchor->RequestSetComponentStatus( + KeyboardSpace, + XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB, + true, + EnableLocatableRequestId); + return; + } + + XrSpaceLocation spaceLocation{XR_TYPE_SPACE_LOCATION}; + XrResult result = + xrLocateSpace(KeyboardSpace, GetLocalSpace(), predictedDisplayTime, &spaceLocation); + if (XR_FAILED(result)) { + ALOGE("xrLocateSpace failed for KeyboardSpace, error %d", result); + return; + } + + static bool logKeyboardTracking = true; + if (spaceLocation.locationFlags == 0) { + // we didn't get a valid pose, reset state and keep trying! + logKeyboardTracking = true; + KeyboardTracking = false; + KeyboardSpace = XR_NULL_HANDLE; + return; + } + + if (logKeyboardTracking) { + StatusText += "Tracking KeyboardSpace...\n"; + StatusTextLabel->SetText(StatusText.c_str()); + logKeyboardTracking = false; + } + + KeyboardTracking = true; + KeyboardPose = FromXrPosef(spaceLocation.pose); + SpatialAnchor->GetBoundingBox3D(KeyboardSpace, KeyboardBoundingBox3D); + } + + void UpdatePassthrough(XrSpace currentSpace, XrTime predictedDisplayTime) { + if (!Passthrough) { + return; + } + + Passthrough->Update(currentSpace, predictedDisplayTime); + + // If keyboard is not tracking, we are done + if (!KeyboardTracking) { + return; + } + + // Create passthrough window if we don't already have one + if (PassthroughWindow == XR_NULL_HANDLE) { + const std::vector kSquareVertices = { + XrVector3f{-0.5f, -0.5f, 0.0f}, + XrVector3f{-0.5f, 0.5f, 0.0f}, + XrVector3f{0.5f, 0.5f, 0.0f}, + XrVector3f{0.5f, -0.5f, 0.0f}}; + const std::vector kSquareIndices = {0, 1, 2, 2, 3, 0}; + PassthroughWindow = Passthrough->CreateGeometryInstance( + PassthroughLayer, GetCurrentSpace(), kSquareVertices, kSquareIndices); + } + + // Update cutoff transform + if (PassthroughWindow != XR_NULL_HANDLE) { + // multiplying by 1.25 because when we generated the Tesselated Quad, we used a 4x4 grid + // of verts. and BuildTesselatedQuadDescriptor will transition from opaque on the outer + // ring of verts to transparent on the next inner ring of verts. i.e. 25% of each + // dimension will be feathered, so we need to scale the quad by 1.25 so that the actual + // keyboard part is unfeathered. Note: this is highly dependant on the implementation of + // and the call to BuildTesselatedQuadDescriptor, so if that changes this will break. + constexpr float kPassThroughScaleFeatheringMultiplier = 1.25f; + const OVR::Vector3f passthroughScale = OVR::Vector3f( + KeyboardBoundingBox3D.extent.width, + KeyboardBoundingBox3D.extent.height, + KeyboardBoundingBox3D.extent.depth) * + kPassThroughScaleFeatheringMultiplier; + + Passthrough->SetGeometryInstanceTransform( + PassthroughWindow, + predictedDisplayTime, + ToXrPosef(KeyboardPose), + ToXrVector3f(passthroughScale)); + + const OVR::Vector3f halfSize = passthroughScale * 0.5f; + std::vector poses; + // Center + poses.push_back(KeyboardPose); + // XY plane corners + OVR::Posef point = OVR::Posef::Identity(); + point.Translation.x = +halfSize.x; + point.Translation.y = +halfSize.y; + poses.push_back(KeyboardPose * point); + point.Translation.x = +halfSize.x; + point.Translation.y = -halfSize.y; + poses.push_back(KeyboardPose * point); + point.Translation.x = -halfSize.x; + point.Translation.y = +halfSize.y; + poses.push_back(KeyboardPose * point); + point.Translation.x = -halfSize.x; + point.Translation.y = -halfSize.y; + poses.push_back(KeyboardPose * point); + AxisRenderer.Update(poses); + + GeometryRenderer.SetPose(KeyboardPose); + GeometryRenderer.SetScale({halfSize.x, halfSize.y, 1.0f}); + GeometryRenderer.Update(); + } + } + + XrSpace DetectKeyboard() { + if (KeyboardSpace != XR_NULL_HANDLE) { + // Already have a keyboard, we are done + return KeyboardSpace; + } + + if (!SpatialAnchor) { + // Can't detect if spatial anchor is not available + return XR_NULL_HANDLE; + } + + // Filter by dynamic object class KEYBOARD + const auto& results = SpatialAnchor->GetSpatialAnchors([&](XrSpace space) { + if (SpatialAnchor->IsComponentSupported( + space, XR_SPACE_COMPONENT_TYPE_DYNAMIC_OBJECT_DATA_METAX1)) { + XrDynamicObjectClassMETAX1 dynamicObjectClass; + if (DynamicObjectTracker->GetDynamicObjectClass(space, dynamicObjectClass) && + dynamicObjectClass == XR_DYNAMIC_OBJECT_CLASS_KEYBOARD_METAX1) { + return true; + } + } + return false; + }); + if (results.empty()) { + // Found nothing + return XR_NULL_HANDLE; + } + + KeyboardSpace = results.at(0); + StatusText += "\nKeyboard detected!\n"; + StatusTextLabel->SetText(StatusText.c_str()); + return KeyboardSpace; + } + + float ComputeHandTransparency(const OVR::Vector3f& handPoint) { + // Define test box to use XY from bounding box, but cutout plane + // sits on max Z so define a custom range from that + const float boxMinX = KeyboardBoundingBox3D.offset.x; + const float boxMinY = KeyboardBoundingBox3D.offset.y; + const float boxMaxX = boxMinX + KeyboardBoundingBox3D.extent.width; + const float boxMaxY = boxMinY + KeyboardBoundingBox3D.extent.height; + const float boxMinZ = KeyboardBoundingBox3D.offset.z + KeyboardBoundingBox3D.extent.depth; + const float boxMaxZ = boxMinZ + 0.1f; + + // Closest point to test box + const OVR::Vector3f localHandPoint = KeyboardPose.InverseTransform(handPoint); + const OVR::Vector3f closestHandPoint{ + OVR::OVRMath_Clamp(localHandPoint.x, boxMinX, boxMaxX), + OVR::OVRMath_Clamp(localHandPoint.y, boxMinY, boxMaxY), + OVR::OVRMath_Clamp(localHandPoint.z, boxMinZ, boxMaxZ)}; + + // Apply falloff based on distance + const OVR::Vector3f v = localHandPoint - closestHandPoint; + const float distance = v.Length(); + const float kHiThreshold = 0.1f; + const float kLoThreshold = 0.0f; + if (distance < kLoThreshold) { + return 0.0f; + } + if (distance < kHiThreshold) { + return 1.0f - ((kHiThreshold - distance) / (kHiThreshold - kLoThreshold)); + } + return 1.0f; + } + + private: + bool HandsExtensionAvailable = false; + bool UIInitialized = false; + + // XR interface + std::unique_ptr HandL; + std::unique_ptr HandR; + std::unique_ptr DynamicObjectTracker; + std::unique_ptr SpatialAnchor; + std::unique_ptr Passthrough; + + // hands/controllers - rendering + OVRFW::HandRenderer HandRendererL; + OVRFW::HandRenderer HandRendererR; + OVRFW::ControllerRenderer ControllerRenderL; + OVRFW::ControllerRenderer ControllerRenderR; + + // UI + OVRFW::TinyUI UI; + OVRFW::SimpleBeamRenderer BeamRenderer; + std::vector Beams; + OVRFW::ovrParticleSystem ParticleSystem; + OVRFW::ovrParticleSystem::handle_t LeftControllerPoint; + OVRFW::ovrParticleSystem::handle_t RightControllerPoint; + + OVRFW::VRMenuObject* TextLabel = nullptr; + OVRFW::VRMenuObject* StatusTextLabel = nullptr; + std::string StatusText; + + XrTime NextQueryAnchorTime = 0; + bool KeyboardTracking = false; + + XrAsyncRequestIdFB QueryAnchorRequestId = 0; + XrAsyncRequestIdFB EnableLocatableRequestId = 0; + XrSpace KeyboardSpace = XR_NULL_HANDLE; + OVR::Posef KeyboardPose = OVR::Posef::Identity(); + XrRect3DfFB KeyboardBoundingBox3D = {}; + + XrPassthroughLayerFB PassthroughLayer = XR_NULL_HANDLE; + XrGeometryInstanceFB PassthroughWindow = XR_NULL_HANDLE; + + OVRFW::ovrAxisRenderer AxisRenderer; + OVRFW::GeometryRenderer GeometryRenderer; +}; + +ENTRY_POINT(XrDynamicObjectsApp) diff --git a/Samples/XrSamples/XrDynamicObjects/assets/assets.txt b/Samples/XrSamples/XrDynamicObjects/assets/assets.txt new file mode 100644 index 0000000..2cc30f7 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/assets/assets.txt @@ -0,0 +1 @@ +This file is a placeholder. diff --git a/Samples/XrSamples/XrDynamicObjects/assets/panel.ktx b/Samples/XrSamples/XrDynamicObjects/assets/panel.ktx new file mode 100644 index 0000000..deb13e8 Binary files /dev/null and b/Samples/XrSamples/XrDynamicObjects/assets/panel.ktx differ diff --git a/Samples/XrSamples/XrDynamicObjects/java/MainActivity.java b/Samples/XrSamples/XrDynamicObjects/java/MainActivity.java new file mode 100644 index 0000000..5dd2dae --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/java/MainActivity.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// (c) Meta Platforms, Inc. and affiliates. +package com.oculus.sdk.xrdynamicobjects; + +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; + +public class MainActivity extends android.app.NativeActivity { + private static final String PERMISSION_USE_SCENE = "com.oculus.permission.USE_SCENE"; + private static final int REQUEST_CODE_PERMISSION_USE_SCENE = 1; + private static final String TAG = "XrDynamicObjects"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestScenePermissionIfNeeded(); + } + + private void requestScenePermissionIfNeeded() { + Log.d(TAG, "requestScenePermissionIfNeeded"); + if (checkSelfPermission(PERMISSION_USE_SCENE) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Permission has not been granted, request " + PERMISSION_USE_SCENE); + requestPermissions( + new String[] {PERMISSION_USE_SCENE}, REQUEST_CODE_PERMISSION_USE_SCENE); + } + } +} diff --git a/Samples/XrSamples/XrDynamicObjects/res/values/strings.xml b/Samples/XrSamples/XrDynamicObjects/res/values/strings.xml new file mode 100644 index 0000000..67fb1e7 --- /dev/null +++ b/Samples/XrSamples/XrDynamicObjects/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Xr Dynamic Objects Sample + diff --git a/Samples/XrSamples/XrHandDataSource/Projects/Android/build.gradle b/Samples/XrSamples/XrHandDataSource/Projects/Android/build.gradle index 3bdbcd3..16fdfa0 100755 --- a/Samples/XrSamples/XrHandDataSource/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrHandDataSource/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrHandTrackingWideMotionMode/Projects/Android/build.gradle b/Samples/XrSamples/XrHandTrackingWideMotionMode/Projects/Android/build.gradle index 07d34b9..8e388e4 100755 --- a/Samples/XrSamples/XrHandTrackingWideMotionMode/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrHandTrackingWideMotionMode/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrHandsAndControllers/Projects/Android/build.gradle b/Samples/XrSamples/XrHandsAndControllers/Projects/Android/build.gradle index b83f5a2..22602e4 100755 --- a/Samples/XrSamples/XrHandsAndControllers/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrHandsAndControllers/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrHandsAndControllers/Src/ActionSetDisplayPanel.cpp b/Samples/XrSamples/XrHandsAndControllers/Src/ActionSetDisplayPanel.cpp index 4e829bf..cda0211 100755 --- a/Samples/XrSamples/XrHandsAndControllers/Src/ActionSetDisplayPanel.cpp +++ b/Samples/XrSamples/XrHandsAndControllers/Src/ActionSetDisplayPanel.cpp @@ -242,7 +242,8 @@ std::string ActionSetDisplayPanel::ListBoundSources(XrAction action) { OXR(xrGetInputSourceLocalizedName( Session, &sni, sourceName.size(), &sourceNameLength, sourceName.data())); - bindingText << "\nBinding: " << pathString.data() << "\n(" << sourceName.data() << ")\n"; + bindingText << "\nBinding: " << (pathString.empty() ? "UNKNOWN" : pathString.data()) + << "\n(" << (sourceName.empty() ? "UNKNOWN" : sourceName.data()) << ")\n"; } return std::string(bindingText.str()); } diff --git a/Samples/XrSamples/XrHandsFB/Projects/Android/build.gradle b/Samples/XrSamples/XrHandsFB/Projects/Android/build.gradle index 9ccacde..49a062c 100755 --- a/Samples/XrSamples/XrHandsFB/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrHandsFB/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrInput/Projects/Android/build.gradle b/Samples/XrSamples/XrInput/Projects/Android/build.gradle index 3bb10f8..d1c8dc1 100755 --- a/Samples/XrSamples/XrInput/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrInput/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrInput/Src/ActionSetDisplayPanel.cpp b/Samples/XrSamples/XrInput/Src/ActionSetDisplayPanel.cpp index 112fb82..08c62e4 100755 --- a/Samples/XrSamples/XrInput/Src/ActionSetDisplayPanel.cpp +++ b/Samples/XrSamples/XrInput/Src/ActionSetDisplayPanel.cpp @@ -202,7 +202,8 @@ std::string ActionSetDisplayPanel::ListBoundSources(XrAction action) { OXR(xrGetInputSourceLocalizedName( Session, &sni, sourceName.size(), &sourceNameLength, sourceName.data())); - bindingText << "\nBinding: " << pathString.data() << "\n(" << sourceName.data() << ")\n"; + bindingText << "\nBinding: " << (pathString.empty() ? "UNKNOWN" : pathString.data()) + << "\n(" << (sourceName.empty() ? "UNKNOWN" : sourceName.data()) << ")\n"; } return std::string(bindingText.str()); } diff --git a/Samples/XrSamples/XrKeyboard/Projects/Android/build.gradle b/Samples/XrSamples/XrKeyboard/Projects/Android/build.gradle index 9f76c1c..d708e65 100755 --- a/Samples/XrSamples/XrKeyboard/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrKeyboard/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrMicrogestures/CMakeLists.txt b/Samples/XrSamples/XrMicrogestures/CMakeLists.txt new file mode 100755 index 0000000..ead4e1a --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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. +project(xrmicrogestures) + +if(NOT TARGET OpenXR::openxr_loader) + find_package(OpenXR REQUIRED) +endif() + +file(GLOB_RECURSE SRC_FILES + Src/*.c + Src/*.cpp +) + +if(ANDROID) + add_library(${PROJECT_NAME} MODULE ${SRC_FILES}) + target_include_directories(${PROJECT_NAME} PUBLIC ${ANDROID_NDK}/sources/android/native_app_glue) + target_link_libraries(${PROJECT_NAME} PRIVATE + android + EGL + GLESv3 + log + ktx + ) + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-u ANativeActivity_onCreate") +elseif(WIN32) + add_definitions(-D_USE_MATH_DEFINES) + add_executable(${PROJECT_NAME} ${SRC_FILES}) + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/assets" + "$/assets" + VERBATIM) + + add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/SampleXrFramework/res/raw" + "$/font/res/raw" + VERBATIM) +endif() + +# Common across platforms +target_include_directories(${PROJECT_NAME} PRIVATE Src) +target_link_libraries(${PROJECT_NAME} PRIVATE samplexrframework) diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/AndroidManifest.xml b/Samples/XrSamples/XrMicrogestures/Projects/Android/AndroidManifest.xml new file mode 100755 index 0000000..f70b002 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/build.bat b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.bat new file mode 100755 index 0000000..b9bbb04 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.bat @@ -0,0 +1,45 @@ +REM Copyright (c) Meta Platforms, Inc. and affiliates. +REM All rights reserved. +REM +REM Licensed under the Oculus SDK License Agreement (the "License"); +REM you may not use the Oculus SDK except in compliance with the License, +REM which is provided at the time of installation or download, or which +REM otherwise accompanies this software in either electronic or hard copy form. +REM +REM You may obtain a copy of the License at +REM https://developer.oculus.com/licenses/oculussdk/ +REM +REM Unless required by applicable law or agreed to in writing, the Oculus SDK +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +@rem Only edit the master copy of this file in SDK_ROOT/bin/scripts/build/perproject + +@setlocal enableextensions enabledelayedexpansion + +@if not exist "build.gradle" @echo Build script must be executed from project directory. & goto :Abort + +@set P=.. + +:TryAgain + +@rem @echo P = %P% + +@if exist "%P%\bin\scripts\build\build.py.bat" goto :Found + +@if exist "%P%\bin\scripts\build" @echo "Could not find build.py.bat" & goto :Abort + +@set P=%P%\.. + +@goto :TryAgain + +:Found + +@set P=%P%\bin\scripts\build +@call %P%\build.py.bat %1 %2 %3 %4 %5 +@goto :End + +:Abort + +:End diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/build.gradle b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.gradle new file mode 100755 index 0000000..c299505 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.gradle @@ -0,0 +1,96 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.3" + } +} + +repositories { + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.oculus.sdk.xrmicrogestures" + minSdk 26 + targetSdk 32 + versionCode 1 + versionName "1.0" + + // override app plugin abiFilters for 64-bit support + externalNativeBuild { + ndk { + abiFilters 'arm64-v8a' + } + ndkBuild { + abiFilters 'arm64-v8a' + } + cmake { + targets "xrmicrogestures" + } + } + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['../../java'] + assets.srcDirs = ['../../assets'] + res.srcDirs = ['../../res'] + } + } + + buildTypes { + debug { + debuggable true + } + + release { + debuggable false + } + } + + externalNativeBuild { + cmake { + path file('../../../../CMakeLists.txt') + } + } + + // Enable prefab support for the OpenXR AAR + buildFeatures { + prefab true + } +} + +dependencies { + // Package/application AndroidManifest.xml properties, plus headers and libraries + // exposed to CMake + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' +} diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/build.py b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.py new file mode 100755 index 0000000..82c0793 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/build.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# Licensed under the Oculus SDK License Agreement (the "License"); +# you may not use the Oculus SDK except in compliance with the License, +# which is provided at the time of installation or download, or which +# otherwise accompanies this software in either electronic or hard copy form. +# +# You may obtain a copy of the License at +# https://developer.oculus.com/licenses/oculussdk/ +# +# Unless required by applicable law or agreed to in writing, the Oculus SDK +# 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 first bit of code is common bootstrapping code +# to determine the SDK root, and to set up the import +# path for additional python code. + +# begin bootstrap +import os +import sys + + +def init(): + root = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) + os.chdir(root) # make sure we are always executing from the project directory + while os.path.isdir(os.path.join(root, "bin/scripts/build")) == False: + root = os.path.realpath(os.path.join(root, "..")) + if ( + len(root) <= 5 + ): # Should catch both Posix and Windows root directories (e.g. '/' and 'C:\') + print("Unable to find SDK root. Exiting.") + sys.exit(1) + root = os.path.abspath(root) + os.environ["OCULUS_SDK_PATH"] = root + sys.path.append(root + "/bin/scripts/build") + + +init() +import ovrbuild + +ovrbuild.init() +# end bootstrap + + +ovrbuild.build() diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle.properties b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle.properties new file mode 100644 index 0000000..3e927b1 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.jar b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.properties b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew.bat b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew.bat new file mode 100755 index 0000000..f127cfd --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/XrSamples/XrMicrogestures/Projects/Android/settings.gradle b/Samples/XrSamples/XrMicrogestures/Projects/Android/settings.gradle new file mode 100755 index 0000000..2afbc3e --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Projects/Android/settings.gradle @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +rootProject.name = "XrMicrogestures" diff --git a/Samples/XrSamples/XrMicrogestures/README.md b/Samples/XrSamples/XrMicrogestures/README.md new file mode 100644 index 0000000..74ebe09 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/README.md @@ -0,0 +1,43 @@ +# OpenXR Hand Tracking Microgestures Sample + +## Overview + +Microgestures expand the capabilities of hand tracking by enabling low-calorie thumb tap and swipe motions to trigger discrete D-pad-like directional commands. + +The hand pose and motion of the thumb is as follows: initially, the thumb must be raised above the index finger (not touching the index finger). The other fingers should be slightly curled as in the picture below for best performance: i.e. not too extended, nor completely curled into a fist. + +A tap is performed by touching the middle segment of the index finger with the thumb, and then lifting the thumb. +The four directional thumb swipes performed on the surface of the index finger are: +1) Left swipe: a swipe towards the index fingertip on the right hand, and away from the index fingertip on the left hand. On the right hand for example, the motion is as follows: the thumb starts raised above the index finger, touches the middle segment of the index finger, slides towards the index fingertip, and lifts. +2) Right swipe: the same motion as the left swipe, but in the opposite direction. On the right hand for example, the thumb starts raised above the index finger, touches the middle segment of the index, slides away from the index fingertip, and lifts. +3) Forward swipe: the thumb starts raised above the index finger, touches the middle segment of the index finger, slides forward, and lifts. +4) Backward swipe: the thumb starts raised above the index finger, touches the middle segment of the index finger, slides backward/downward, and lifts. +Note that the motions can be performed at moderate to quick speeds, and should be performed in one smooth motion. The detection of the gesture happens at the end of the motion, regardless of speed. + +![Microgestures](./images/openxr-hand-tracking-microgestures.png) + +## Using XrMicrogestures Native Sample App + +As a user, when you open the sample app, the scene will contain the hand tracking overlay. There are 5 panels for each hand corresponding to: +* Swipe Left +* Swipe Right +* Swipe Forward +* Swipe Backward +* Tap Thumb + +Each panel contains information about the OpenXR action state for the associated microgesture. The action state has the following data (for a complete description, please refer to the [Boolean Actions](https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#_boolean_actions) section in the OpenXR Specification). +* `currentState` ⎼ boolean value, set to `True` if a valid microgesture has been detected +* `isActive` ⎼ boolean value, set to `True` if the Microgesture extension is enabled +* `changedSinceLastSync` ⎼ boolean value, set to `True`, if the Microgesture state has changed sync the last sync call +* `lastChangeTime` ⎼ `XrTime` value, indicating the last time the state of the current gesture has changed +* `num events` ⎼ (not in the action state) integer value, indicating how many times the associated microgesture has been detected + +![Using the XrMicrogestures Native Sample App](./images/openxr-hand-tracking-microgestures-native.png) + +## Link Support + +Microgestures are supported over Link. Please refer to [Quest Link for App Development](https://developer.oculus.com/documentation/unity/unity-link/) for setup details. + +The following software is required: +* Meta Quest build v71.0 or later +* Oculus PC app with version v71.0 or later, which you can download from the [Meta Quest website](https://www.meta.com/quest/setup/) diff --git a/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.cpp b/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.cpp new file mode 100755 index 0000000..447ba75 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include "ActionSetDisplayPanel.h" +#include "Render/BitmapFont.h" + + +#include +#include +#include + +ActionSetDisplayPanel::ActionSetDisplayPanel( + std::string title, + XrSession session, + XrInstance instance, + OVRFW::TinyUI* ui, + OVR::Vector3f topLeftLocation) + : Session{session}, Instance{instance}, Ui{ui}, TopLeftLocation{topLeftLocation} { + auto label = Ui->AddLabel( + title, GetNextLabelLocation() + OVR::Vector3f{0, kHeaderHeight, 0.00}, {kWidthPx, 45.0f}); + Labels.push_back(label); +} + +void ActionSetDisplayPanel::AddBoolAction(XrAction action, const char* actionName) { + auto labelPair = CreateActionLabel(actionName); + auto headerLabel = labelPair.first; + auto actionStateLabel = labelPair.second; + BoolActions.push_back({action, headerLabel, actionStateLabel, actionName}); + Labels.push_back(headerLabel); +} + +std::pair ActionSetDisplayPanel::CreateActionLabel( + const char* actionName) { + auto label = Ui->AddLabel(actionName, GetNextLabelLocation(), {kWidthPx, 45.0f}); + auto stateLabel = Ui->AddLabel("state", GetNextStateLabelLocation(), {kWidthPx, 250.0f}); + + OVRFW::VRMenuFontParms fontParams{}; + fontParams.Scale = 0.5f; + fontParams.AlignVert = OVRFW::VERTICAL_CENTER; + fontParams.AlignHoriz = OVRFW::HORIZONTAL_LEFT; + label->SetFontParms(fontParams); + label->SetTextLocalPosition({-0.45f * kWidth, 0, 0}); + stateLabel->SetFontParms(fontParams); + stateLabel->SetTextLocalPosition({-0.47f * kWidth, -0.02f * kHeight, 0}); + + Elements++; + return std::make_pair(label, stateLabel); +} + +OVR::Vector3f ActionSetDisplayPanel::GetNextLabelLocation() { + return TopLeftLocation + + OVR::Vector3f{kWidth * 0.5f, -Elements * kElementGap - kHeaderHeight, 0.01}; +} + +OVR::Vector3f ActionSetDisplayPanel::GetNextStateLabelLocation() { + return GetNextLabelLocation() + OVR::Vector3f{0.0, -kElementGap * 0.5f, 0.0}; +} + +void ActionSetDisplayPanel::Update() { + for (auto& boolAction : BoolActions) { + XrAction action = boolAction.Action; + VRMenuObject* header = boolAction.HeaderMenu; + VRMenuObject* label = boolAction.StateMenu; + auto& totalCount = boolAction.TotalCount; + auto& showActiveStateCountdown = boolAction.ShowActiveStateCountdown; + + XrActionStateGetInfo getInfo{XR_TYPE_ACTION_STATE_GET_INFO}; + getInfo.action = action; + getInfo.subactionPath = XR_NULL_PATH; + XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN}; + OXR(xrGetActionStateBoolean(Session, &getInfo, &state)); + + const bool microgestureEventDetected = state.changedSinceLastSync && state.currentState; + if (microgestureEventDetected) { + ++totalCount; + } + showActiveStateCountdown = microgestureEventDetected + ? kShowActiveState + : (showActiveStateCountdown > 0 ? showActiveStateCountdown - 1 : 0); + + label->SetText( + "currentState: %s\n" + "isActive: %s\n" + "changedSinceLastSync: %s\n" + "lastChangeTime: %ldms\n\n" + "num events: %d\n", + state.currentState ? "True" : "False", + state.isActive ? "True" : "False", + state.changedSinceLastSync ? "True" : "False", + state.lastChangeTime / (1000 * 1000), // convert from ns to ms + totalCount); + label->SetSurfaceColor( + 0, + showActiveStateCountdown > 0 ? OVR::Vector4f(0.05f, 0.5f, 0.05f, 0.6f) + : OVR::Vector4f(0.05f, 0.05f, 0.05f, 1.0f)); + label->SetSelected(state.currentState); + + header->SetSurfaceColor( + 0, + showActiveStateCountdown > 0 ? OVR::Vector4f(0.2f, 0.5f, 0.2f, 0.6f) + : OVR::Vector4f(0.2f, 0.2f, 0.2f, 1.0f)); + header->SetSelected(state.currentState); + } +} diff --git a/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.h b/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.h new file mode 100755 index 0000000..a1b979a --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/ActionSetDisplayPanel.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include +#include "XrApp.h" +#include "Input/TinyUI.h" +#include "GUI/VRMenuObject.h" + +#include + +using OVRFW::VRMenuObject; + +class ActionSetDisplayPanel { + public: + ActionSetDisplayPanel( + std::string title, + XrSession session, + XrInstance instance, + OVRFW::TinyUI* ui, + OVR::Vector3f topLeftLocation); + + void AddBoolAction(XrAction action, const char* actionName); + void AddPinch(const char* pinchName); + + void UpdatePinch(const char* pinchName, bool state, float value); + void Update(); + + private: + static constexpr float kHeaderHeight{0.15}; + static constexpr float kElementGap{0.65}; + static constexpr float kWidthPx{600}; + static constexpr float kHeightPx{500}; + static constexpr float kWidth{kWidthPx * VRMenuObject::DEFAULT_TEXEL_SCALE}; + static constexpr float kHeight{kHeightPx * VRMenuObject::DEFAULT_TEXEL_SCALE}; + static constexpr uint32_t kShowActiveState{10}; + + std::pair CreateActionLabel(const char* actionName); + OVR::Vector3f GetNextLabelLocation(); + OVR::Vector3f GetNextStateLabelLocation(); + + struct BoolAction { + XrAction Action; + VRMenuObject* HeaderMenu; + VRMenuObject* StateMenu; + std::string ActionName; + uint32_t TotalCount = 0; + XrTime LastStateUpdateTime = 0; + uint32_t ShowActiveStateCountdown = 0; + }; + + std::vector BoolActions{}; + std::vector Labels{}; + XrSession Session; + XrInstance Instance; + OVRFW::TinyUI* Ui; + + OVR::Vector3f TopLeftLocation; + int Elements{0}; +}; diff --git a/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.cpp b/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.cpp new file mode 100755 index 0000000..6397228 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include "EnvironmentRenderer.h" +#include "Model/ModelFile.h" +#include "Model/ModelFileLoading.h" +#include "XrApp.h" + +#include + +using OVR::Matrix4f; +using OVR::Posef; +using OVR::Quatf; +using OVR::Vector3f; +using OVR::Vector4f; + +namespace OVRFW { +namespace EnvironmentShaders { + +/// clang-format off +static const char* vertexShaderSrc = R"glsl( +attribute highp vec4 Position; +attribute highp vec3 Normal; +attribute highp vec2 TexCoord; +attribute lowp vec4 VertexColor; + +varying lowp vec3 oEye; +varying lowp vec3 oNormal; +varying lowp vec2 oTexCoord; +varying lowp vec4 oVertexColor; + +vec3 multiply( mat4 m, vec3 v ) +{ + return vec3( + m[0].x * v.x + m[1].x * v.y + m[2].x * v.z, + m[0].y * v.x + m[1].y * v.y + m[2].y * v.z, + m[0].z * v.x + m[1].z * v.y + m[2].z * v.z ); +} + +vec3 transposeMultiply( mat4 m, vec3 v ) +{ + return vec3( + m[0].x * v.x + m[0].y * v.y + m[0].z * v.z, + m[1].x * v.x + m[1].y * v.y + m[1].z * v.z, + m[2].x * v.x + m[2].y * v.y + m[2].z * v.z ); +} + +void main() +{ + gl_Position = TransformVertex( Position ); + vec3 eye = transposeMultiply( sm.ViewMatrix[VIEW_ID], -vec3( sm.ViewMatrix[VIEW_ID][3] ) ); + oEye = eye - vec3( ModelMatrix * Position ); + vec3 iNormal = Normal * 100.0f; + oNormal = multiply( ModelMatrix, iNormal ); + oTexCoord = TexCoord; + oVertexColor = VertexColor; +} +)glsl"; + +/// This shader uses vertex color.r for a fog, fading to a fog color as vertex color decreases to 0. +/// This gives behaviour consistent with our unity samples. +static const char* fragmentShaderSrc = R"glsl( +precision lowp float; + +uniform sampler2D Texture0; +uniform sampler2D Texture1; +uniform lowp vec3 SpecularLightDirection; +uniform lowp vec3 SpecularLightColor; +uniform lowp vec3 AmbientLightColor; +uniform lowp float FogStrength; +uniform lowp vec3 FogColor; + +varying lowp vec3 oEye; +varying lowp vec3 oNormal; +varying lowp vec2 oTexCoord; +varying lowp vec4 oVertexColor; + +lowp vec3 multiply( lowp mat3 m, lowp vec3 v ) +{ + return vec3( + m[0].x * v.x + m[1].x * v.y + m[2].x * v.z, + m[0].y * v.x + m[1].y * v.y + m[2].y * v.z, + m[0].z * v.x + m[1].z * v.y + m[2].z * v.z ); +} + +void main() +{ + lowp vec3 eyeDir = normalize( oEye.xyz ); + lowp vec3 Normal = normalize( oNormal ); + + lowp vec3 reflectionDir = dot( eyeDir, Normal ) * 2.0 * Normal - eyeDir; + lowp vec4 diffuse = texture2D( Texture0, oTexCoord ); + lowp vec4 detail = texture2D( Texture1, oTexCoord * 20.0 ); + lowp vec4 res = 0.5 * (diffuse + detail); + lowp vec3 ambientValue = res.xyz * AmbientLightColor; + + lowp float nDotL = max( dot( Normal , SpecularLightDirection ), 0.0 ); + lowp vec3 diffuseValue = res.xyz * SpecularLightColor * nDotL; + + lowp float specularPower = 1.0f - res.a; + specularPower = specularPower * specularPower; + + lowp vec3 H = normalize( SpecularLightDirection + eyeDir ); + lowp float nDotH = max( dot( Normal, H ), 0.0 ); + lowp float specularIntensity = pow( nDotH, 64.0f * ( specularPower ) ) * specularPower; + lowp vec3 specularValue = specularIntensity * SpecularLightColor; + + lowp vec3 controllerColor = diffuseValue + ambientValue + specularValue; + + lowp float fog = FogStrength * (1.0 - oVertexColor.r); + controllerColor = fog * FogColor + (1.0 - fog) * controllerColor; + + gl_FragColor.w = 1.0; + gl_FragColor.xyz = controllerColor; +} +)glsl"; + +/// clang-format on + +} // namespace EnvironmentShaders + +bool EnvironmentRenderer::Init(std::string modelPath, OVRFW::ovrFileSys* fileSys) { + /// Shader + ovrProgramParm uniformParms[] = { + {"Texture0", ovrProgramParmType::TEXTURE_SAMPLED}, + {"Texture1", ovrProgramParmType::TEXTURE_SAMPLED}, // An optional detail texture. + {"SpecularLightDirection", ovrProgramParmType::FLOAT_VECTOR3}, + {"SpecularLightColor", ovrProgramParmType::FLOAT_VECTOR3}, + {"AmbientLightColor", ovrProgramParmType::FLOAT_VECTOR3}, + {"FogStrength", ovrProgramParmType::FLOAT}, + {"FogColor", ovrProgramParmType::FLOAT_VECTOR3}, + }; + ProgRenderModel = GlProgram::Build( + "", + EnvironmentShaders::vertexShaderSrc, + "", + EnvironmentShaders::fragmentShaderSrc, + uniformParms, + sizeof(uniformParms) / sizeof(ovrProgramParm)); + + MaterialParms materials = {}; + ModelGlPrograms programs = {}; + programs.ProgSingleTexture = &ProgRenderModel; + programs.ProgBaseColorPBR = &ProgRenderModel; + programs.ProgSkinnedBaseColorPBR = &ProgRenderModel; + programs.ProgLightMapped = &ProgRenderModel; + programs.ProgBaseColorEmissivePBR = &ProgRenderModel; + programs.ProgSkinnedBaseColorEmissivePBR = &ProgRenderModel; + programs.ProgSimplePBR = &ProgRenderModel; + programs.ProgSkinnedSimplePBR = &ProgRenderModel; + + if (fileSys) { + OVRFW::ovrFileSys& fs = *fileSys; + RenderModel = LoadModelFile(fs, modelPath.c_str(), programs, materials); + } else { + ALOGE("Couldn't load model, we didn't get a valid filesystem"); + return false; + } + + if (RenderModel == nullptr || static_cast(RenderModel->Models.size()) < 1) { + ALOGE("Couldn't load modelrenderer model!"); + return false; + } + + FogStrengths = new OVR::Size[RenderModel->Models.size()]; + int modelIndex = 0; + for (auto& model : RenderModel->Models) { + auto& gc = model.surfaces[0].surfaceDef.graphicsCommand; + gc.UniformData[0].Data = &gc.Textures[0]; + gc.UniformData[1].Data = &gc.Textures[1]; + gc.UniformData[2].Data = &SpecularLightDirection; + gc.UniformData[3].Data = &SpecularLightColor; + gc.UniformData[4].Data = &AmbientLightColor; + FogStrengths[modelIndex] = + OVR::Size(gc.GpuState.blendEnable == ovrGpuState::BLEND_ENABLE ? 1.0f : 0.0f); + gc.UniformData[5].Data = &FogStrengths[modelIndex]; + gc.UniformData[6].Data = &FogColor; + gc.GpuState.depthEnable = gc.GpuState.depthMaskEnable = true; + modelIndex++; + } + + /// Set defaults + SpecularLightDirection = Vector3f(1.0f, 1.0f, 0.0f); + SpecularLightColor = Vector3f(1.0f, 0.95f, 0.8f) * 0.75f; + AmbientLightColor = Vector3f(1.0f, 1.0f, 1.0f) * 0.15f; + FogColor = Vector3f(0.3372549f, 0.345098f, 0.3686275f); + + /// all good + Initialized = true; + return true; +} + +void EnvironmentRenderer::Shutdown() { + OVRFW::GlProgram::Free(ProgRenderModel); + if (RenderModel != nullptr) { + delete RenderModel; + RenderModel = nullptr; + } + if (FogStrengths != nullptr) { + delete FogStrengths; + FogStrengths = nullptr; + } +} + +void EnvironmentRenderer::Render(std::vector& surfaceList) { + /// toggle alpha override + if (RenderModel != nullptr) { + for (int i = 0; i < static_cast(RenderModel->Models.size()); i++) { + auto& model = RenderModel->Models[i]; + auto& node = RenderModel->Nodes[i + 1]; + ovrDrawSurface controllerSurface; + for (int j = 0; j < static_cast(model.surfaces.size()); j++) { + controllerSurface.surface = &(model.surfaces[j].surfaceDef); + controllerSurface.modelMatrix = node.GetGlobalTransform(); + surfaceList.push_back(controllerSurface); + } + } + } +} + +} // namespace OVRFW diff --git a/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.h b/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.h new file mode 100755 index 0000000..bf5f4ab --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/EnvironmentRenderer.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +#include "Misc/Log.h" +#include "Model/SceneView.h" +#include "Render/GlProgram.h" +#include "Render/SurfaceRender.h" +#include "OVR_FileSys.h" +#include "OVR_Math.h" + +#include +#include +#include + +#if defined(ANDROID) +#define XR_USE_GRAPHICS_API_OPENGL_ES 1 +#define XR_USE_PLATFORM_ANDROID 1 +#else +#include "unknwn.h" +#define XR_USE_GRAPHICS_API_OPENGL 1 +#define XR_USE_PLATFORM_WIN32 1 +#endif + +#include +#include +#include + +namespace OVRFW { + +class EnvironmentRenderer { + public: + EnvironmentRenderer() = default; + ~EnvironmentRenderer() = default; + + bool Init(std::vector& modelBuffer); + bool Init(std::string modelPath, OVRFW::ovrFileSys* fileSys); + void Shutdown(); + void Render(std::vector& surfaceList); + bool IsInitialized() const { + return Initialized; + } + + public: + OVR::Vector3f SpecularLightDirection; + OVR::Vector3f SpecularLightColor; + OVR::Vector3f AmbientLightColor; + OVR::Vector3f FogColor; + + private: + bool Initialized = false; + GlProgram ProgRenderModel; + ModelFile* RenderModel = nullptr; + GlTexture RenderModelTextureSolid; + OVR::Matrix4f Transform; + OVR::Size* FogStrengths; +}; + +} // namespace OVRFW diff --git a/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.cpp b/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.cpp new file mode 100755 index 0000000..3c65360 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include "SkyboxRenderer.h" +#include "Model/ModelFile.h" +#include "Model/ModelFileLoading.h" +#include "XrApp.h" + +#include + +using OVR::Matrix4f; +using OVR::Posef; +using OVR::Quatf; +using OVR::Vector3f; +using OVR::Vector4f; + +namespace OVRFW { +namespace SkyboxShaders { + +/// clang-format off +static const char* vertexShaderSrc = R"glsl( +attribute highp vec4 Position; +attribute highp vec3 Normal; +attribute highp vec2 TexCoord; + +varying lowp vec3 oEye; +varying lowp vec3 oNormal; +varying lowp vec2 oTexCoord; + +vec3 multiply( mat4 m, vec3 v ) +{ + return vec3( + m[0].x * v.x + m[1].x * v.y + m[2].x * v.z, + m[0].y * v.x + m[1].y * v.y + m[2].y * v.z, + m[0].z * v.x + m[1].z * v.y + m[2].z * v.z ); +} + +vec3 transposeMultiply( mat4 m, vec3 v ) +{ + return vec3( + m[0].x * v.x + m[0].y * v.y + m[0].z * v.z, + m[1].x * v.x + m[1].y * v.y + m[1].z * v.z, + m[2].x * v.x + m[2].y * v.y + m[2].z * v.z ); +} + +void main() +{ + gl_Position = TransformVertex( Position ); + oTexCoord = TexCoord; +} +)glsl"; + +static const char* fragmentShaderSrc = R"glsl( +precision lowp float; + +uniform lowp vec3 TopColor; +uniform lowp vec3 MiddleColor; +uniform lowp vec3 BottomColor; + +varying lowp vec2 oTexCoord; + +lowp vec3 multiply( lowp mat3 m, lowp vec3 v ) +{ + return vec3( + m[0].x * v.x + m[1].x * v.y + m[2].x * v.z, + m[0].y * v.x + m[1].y * v.y + m[2].y * v.z, + m[0].z * v.x + m[1].z * v.y + m[2].z * v.z ); +} + +lowp float saturate(lowp float v) { + return clamp(v, 0.0f, 1.0f); +} + +void main() +{ + lowp float val = oTexCoord.y; + lowp float topVal = saturate(-3.0 + ( 4.0 * val )); + lowp float middleVal = saturate( 1.0 - 4.0 * abs(0.75 - val )); + lowp float bottomVal = saturate( 4.0 * ( 0.75 - val)); + + lowp vec3 finalColor = BottomColor.rgb * bottomVal + MiddleColor.rgb * middleVal + TopColor.rgb * topVal; + + gl_FragColor.w = 1.0f; + gl_FragColor.xyz = finalColor; +} +)glsl"; + +/// clang-format on + +} // namespace SkyboxShaders + +bool SkyboxRenderer::Init(std::string modelPath, OVRFW::ovrFileSys* fileSys) { + /// Shader + ovrProgramParm uniformParms[] = { + {"TopColor", ovrProgramParmType::FLOAT_VECTOR3}, + {"MiddleColor", ovrProgramParmType::FLOAT_VECTOR3}, + {"BottomColor", ovrProgramParmType::FLOAT_VECTOR3}, + }; + ProgRenderModel = GlProgram::Build( + "", + SkyboxShaders::vertexShaderSrc, + "", + SkyboxShaders::fragmentShaderSrc, + uniformParms, + sizeof(uniformParms) / sizeof(ovrProgramParm)); + + MaterialParms materials = {}; + ModelGlPrograms programs = {}; + programs.ProgSingleTexture = &ProgRenderModel; + programs.ProgBaseColorPBR = &ProgRenderModel; + programs.ProgSkinnedBaseColorPBR = &ProgRenderModel; + programs.ProgLightMapped = &ProgRenderModel; + programs.ProgBaseColorEmissivePBR = &ProgRenderModel; + programs.ProgSkinnedBaseColorEmissivePBR = &ProgRenderModel; + programs.ProgSimplePBR = &ProgRenderModel; + programs.ProgSkinnedSimplePBR = &ProgRenderModel; + + if (fileSys) { + OVRFW::ovrFileSys& fs = *fileSys; + RenderModel = LoadModelFile(fs, modelPath.c_str(), programs, materials); + } else { + ALOGE("Couldn't load model, we didn't get a valid filesystem"); + return false; + } + + if (RenderModel == nullptr || static_cast(RenderModel->Models.size()) < 1) { + ALOGE("Couldn't load modelrenderer model!"); + return false; + } + + TopColor = OVR::Vector3f(0.937f, 0.9236477f, 0.883591f); + MiddleColor = OVR::Vector3f(0.6705883f, 0.6909091f, 0.7450981f); + BottomColor = OVR::Vector3f(0.3372549f, 0.345098f, 0.3686275f); + + for (auto& model : RenderModel->Models) { + auto& gc = model.surfaces[0].surfaceDef.graphicsCommand; + gc.UniformData[0].Data = &TopColor; + gc.UniformData[1].Data = &MiddleColor; + gc.UniformData[2].Data = &BottomColor; + gc.GpuState.depthMaskEnable = false; + gc.GpuState.depthEnable = false; + gc.GpuState.blendEnable = ovrGpuState::BLEND_DISABLE; + } + + /// all good + Initialized = true; + return true; +} + +void SkyboxRenderer::Shutdown() { + OVRFW::GlProgram::Free(ProgRenderModel); + if (RenderModel != nullptr) { + delete RenderModel; + RenderModel = nullptr; + } +} + +void SkyboxRenderer::Render(std::vector& surfaceList) { + if (RenderModel != nullptr) { + for (int i = 0; i < static_cast(RenderModel->Models.size()); i++) { + auto& model = RenderModel->Models[i]; + auto& node = RenderModel->Nodes[i]; + ovrDrawSurface controllerSurface; + for (int j = 0; j < static_cast(model.surfaces.size()); j++) { + controllerSurface.surface = &(model.surfaces[j].surfaceDef); + controllerSurface.modelMatrix = node.GetGlobalTransform(); + surfaceList.push_back(controllerSurface); + } + } + } +} + +bool SkyboxRenderer::IsInitialized() const { + return Initialized; +} + +} // namespace OVRFW diff --git a/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.h b/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.h new file mode 100755 index 0000000..9a4b844 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/SkyboxRenderer.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#pragma once + +#include "Misc/Log.h" +#include "Model/SceneView.h" +#include "Render/GlProgram.h" +#include "Render/SurfaceRender.h" +#include "OVR_FileSys.h" +#include "OVR_Math.h" + +#include +#include +#include + +#if defined(ANDROID) +#define XR_USE_GRAPHICS_API_OPENGL_ES 1 +#define XR_USE_PLATFORM_ANDROID 1 +#else +#include "unknwn.h" +#define XR_USE_GRAPHICS_API_OPENGL 1 +#define XR_USE_PLATFORM_WIN32 1 +#endif + +#include +#include +#include + +namespace OVRFW { + +class SkyboxRenderer { + public: + SkyboxRenderer() = default; + + ~SkyboxRenderer() = default; + + bool Init(std::string modelPath, OVRFW::ovrFileSys* fileSys); + + void Shutdown(); + + void Render(std::vector& surfaceList); + + bool IsInitialized() const; + + public: + OVR::Vector3f TopColor; + OVR::Vector3f MiddleColor; + OVR::Vector3f BottomColor; + OVR::Vector3f Direction; + + private: + bool Initialized = false; + GlProgram ProgRenderModel; + ModelFile* RenderModel = nullptr; + GlTexture RenderModelTextureSolid; + OVR::Matrix4f Transform; +}; + +} // namespace OVRFW diff --git a/Samples/XrSamples/XrMicrogestures/Src/main.cpp b/Samples/XrSamples/XrMicrogestures/Src/main.cpp new file mode 100755 index 0000000..1023bf8 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/Src/main.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +#include + +#include "GUI/VRMenuObject.h" +#include "XrApp.h" +#include "ActionSetDisplayPanel.h" +#include "SkyboxRenderer.h" +#include "EnvironmentRenderer.h" +#include "Input/TinyUI.h" +#include "Render/SimpleBeamRenderer.h" +#include "Input/HandRenderer.h" + +#include + +// For expressiveness; use _m rather than f literals when we mean meters +constexpr float operator"" _m(long double meters) { + return static_cast(meters); +} +constexpr float operator"" _m(unsigned long long meters) { + return static_cast(meters); +} + +class XrMicrogesturesApp : public OVRFW::XrApp { + public: + XrMicrogesturesApp() : OVRFW::XrApp() { + BackgroundColor = OVR::Vector4f(0.75f, 0.75f, 0.75f, 1.0f); + + // Disable framework input management, letting this sample explicitly + // call xrSyncActions() every frame; which includes control over which + // ActionSet to set as active each frame + SkipInputHandling = true; + } + + // Return a list of OpenXR extensions needed for this app + virtual std::vector GetExtensions() override { + std::vector extensions = XrApp::GetExtensions(); + extensions.push_back(XR_EXT_HAND_TRACKING_EXTENSION_NAME); + extensions.push_back(XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME); + extensions.push_back(XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME); + extensions.push_back(XR_EXT_HAND_INTERACTION_EXTENSION_NAME); + extensions.push_back(XR_META_HAND_TRACKING_MICROGESTURES_EXTENSION_NAME); + return extensions; + } + + std::unordered_map> GetSuggestedBindings( + XrInstance instance) override { + OXR(xrStringToPath(instance, "/user/hand/left", &LeftHandPath)); + OXR(xrStringToPath(instance, "/user/hand/right", &RightHandPath)); + + // Get the default bindings suggested by XrApp framework + auto suggestedBindings = XrApp::GetSuggestedBindings(instance); + ActionLeftHandSetMicrogestures = + CreateActionSet(0, "left_hand_microgesture_action_set", "Left hand microgestures"); + ActionRightHandSetMicrogestures = + CreateActionSet(0, "right_hand_microgesture_action_set", "Right hand microgestures"); + + ActionLeftHandSwipeLeft = CreateAction( + ActionLeftHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_swipe_left", + "Swipe Left"); + ActionLeftHandSwipeRight = CreateAction( + ActionLeftHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_swipe_right", + "Swipe Right"); + ActionLeftHandSwipeForward = CreateAction( + ActionLeftHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "swipe_forward", + "Swipe Forward"); + ActionLeftHandSwipeBackward = CreateAction( + ActionLeftHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "swipe_backward", + "Swipe Backward"); + ActionLeftHandTapThumb = CreateAction( + ActionLeftHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_tap_thumb", + "Thumb Tap"); + + ActionRightHandSwipeLeft = CreateAction( + ActionRightHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_swipe_left", + "Swipe Left"); + ActionRightHandSwipeRight = CreateAction( + ActionRightHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_swipe_right", + "Swipe Right"); + ActionRightHandSwipeForward = CreateAction( + ActionRightHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "swipe_forward", + "Swipe Forward"); + ActionRightHandSwipeBackward = CreateAction( + ActionRightHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "swipe_backward", + "Swipe Backward"); + ActionRightHandTapThumb = CreateAction( + ActionRightHandSetMicrogestures, + XR_ACTION_TYPE_BOOLEAN_INPUT, + "action_tap_thumb", + "Thumb Tap"); + + // EXT_hand_interaction suggested bindings + OXR(xrStringToPath( + instance, + "/interaction_profiles/ext/hand_interaction_ext", + &ExtHandInteractionProfile)); + + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionLeftHandSwipeLeft, "/user/hand/left/input/swipe_left_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionLeftHandSwipeRight, "/user/hand/left/input/swipe_right_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionLeftHandSwipeForward, "/user/hand/left/input/swipe_forward_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionLeftHandSwipeBackward, "/user/hand/left/input/swipe_backward_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionLeftHandTapThumb, "/user/hand/left/input/tap_thumb_meta/click")); + + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionRightHandSwipeLeft, "/user/hand/right/input/swipe_left_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionRightHandSwipeRight, "/user/hand/right/input/swipe_right_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionRightHandSwipeForward, "/user/hand/right/input/swipe_forward_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionRightHandSwipeBackward, "/user/hand/right/input/swipe_backward_meta/click")); + suggestedBindings[ExtHandInteractionProfile].emplace_back(ActionSuggestedBinding( + ActionRightHandTapThumb, "/user/hand/right/input/tap_thumb_meta/click")); + + return suggestedBindings; + } + + // Must return true if the application initializes successfully. + virtual bool AppInit(const xrJava* context) override { + int fontVertexBufferSize = 32 * 1024; // Custom large text buffer size for all the text + bool updateColors = true; // Update UI colors on interaction + if (false == Ui.Init(context, GetFileSys(), updateColors, fontVertexBufferSize)) { + ALOG("TinyUI::Init FAILED."); + return false; + } + + auto fileSys = std::unique_ptr(OVRFW::ovrFileSys::Create(*context)); + if (fileSys) { + std::string environmentPath = "apk:///assets/SmallRoom.gltf.ovrscene"; + EnvironmentRenderer.Init(environmentPath, fileSys.get()); + std::string skyboxPath = "apk:///assets/Skybox.gltf.ovrscene"; + SkyboxRenderer.Init(skyboxPath, fileSys.get()); + } + + // Inspect hand tracking system properties + XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties{ + XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT}; + XrSystemProperties systemProperties{ + XR_TYPE_SYSTEM_PROPERTIES, &handTrackingSystemProperties}; + XrSystemMicrogesturePropertiesMETA microgestureSystemProperties{ + XR_TYPE_SYSTEM_MICROGESTURE_PROPERTIES_META}; + reinterpret_cast(&handTrackingSystemProperties)->next = + reinterpret_cast(µgestureSystemProperties); + OXR(xrGetSystemProperties(GetInstance(), GetSystemId(), &systemProperties)); + if (!handTrackingSystemProperties.supportsHandTracking || + !microgestureSystemProperties.supportsMicrogestures) { + // The system does not support hand tracking + ALOG("[XrMicrogestures] System does not support microgestures"); + return false; + } else { + ALOG( + "[XrMicrogestures] " + "xrGetSystemProperties XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT OK, " + "xrGetSystemProperties XR_TYPE_SYSTEM_MICROGESTURE_PROPERTIES_META OK, " + "Initializing hand tracking..."); + } + + // Hook up extensions for hand tracking + OXR(xrGetInstanceProcAddr( + GetInstance(), + "xrCreateHandTrackerEXT", + (PFN_xrVoidFunction*)(&XrCreateHandTrackerExt))); + OXR(xrGetInstanceProcAddr( + GetInstance(), + "xrDestroyHandTrackerEXT", + (PFN_xrVoidFunction*)(&XrDestroyHandTrackerExt))); + OXR(xrGetInstanceProcAddr( + GetInstance(), "xrLocateHandJointsEXT", (PFN_xrVoidFunction*)(&XrLocateHandJointsExt))); + + // Hook up extensions for hand rendering + OXR(xrGetInstanceProcAddr( + GetInstance(), "xrGetHandMeshFB", (PFN_xrVoidFunction*)(&XrGetHandMeshFb))); + + return true; + } + + virtual void AppShutdown(const xrJava* context) override { + // unhook extensions for hand tracking + XrCreateHandTrackerExt = nullptr; + XrDestroyHandTrackerExt = nullptr; + XrLocateHandJointsExt = nullptr; + XrGetHandMeshFb = nullptr; + + OVRFW::XrApp::AppShutdown(context); + Ui.Shutdown(); + } + + virtual bool SessionInit() override { + BeamRenderer.Init(GetFileSys(), nullptr, OVR::Vector4f(1.0f), 1.0f); + + { + // Attach ActionSets to session + // This is required before any call to xrSyncActions for these action sets + std::vector actionSets{ + ActionLeftHandSetMicrogestures, ActionRightHandSetMicrogestures}; + XrSessionActionSetsAttachInfo attachInfo{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attachInfo.countActionSets = actionSets.size(); + attachInfo.actionSets = actionSets.data(); + OXR(xrAttachSessionActionSets(Session, &attachInfo)); + // After this point all actions and bindings are final for the session + // (calls to xrSuggestInteractionProfileBindings and xrAttachSessionActionSets fail) + } + + { + // Setup all the UI panels to display the state of each action + MgActionSetPanels.insert( + {ActionLeftHandSetMicrogestures, + ActionSetDisplayPanel( + "LEFT HAND", Session, Instance, &Ui, {-1.7_m, 2.9_m, -3.5_m})}); + MgActionSetPanels.at(ActionLeftHandSetMicrogestures) + .AddBoolAction(ActionLeftHandSwipeLeft, "Left Hand Swipe Left"); + MgActionSetPanels.at(ActionLeftHandSetMicrogestures) + .AddBoolAction(ActionLeftHandSwipeRight, "Left Hand Swipe Right"); + MgActionSetPanels.at(ActionLeftHandSetMicrogestures) + .AddBoolAction(ActionLeftHandSwipeForward, "Left Hand Swipe Forward"); + MgActionSetPanels.at(ActionLeftHandSetMicrogestures) + .AddBoolAction(ActionLeftHandSwipeBackward, "Left Hand Swipe Backward"); + MgActionSetPanels.at(ActionLeftHandSetMicrogestures) + .AddBoolAction(ActionLeftHandTapThumb, "Left Hand Tap Thumb"); + + MgActionSetPanels.insert( + {ActionRightHandSetMicrogestures, + ActionSetDisplayPanel( + "RIGHT HAND", Session, Instance, &Ui, {0.2_m, 2.9_m, -3.5_m})}); + MgActionSetPanels.at(ActionRightHandSetMicrogestures) + .AddBoolAction(ActionRightHandSwipeLeft, "Right Hand Swipe Left"); + MgActionSetPanels.at(ActionRightHandSetMicrogestures) + .AddBoolAction(ActionRightHandSwipeRight, "Right Hand Swipe Right"); + MgActionSetPanels.at(ActionRightHandSetMicrogestures) + .AddBoolAction(ActionRightHandSwipeForward, "Right Hand Swipe Forward"); + MgActionSetPanels.at(ActionRightHandSetMicrogestures) + .AddBoolAction(ActionRightHandSwipeBackward, "Right Hand Swipe Backward"); + MgActionSetPanels.at(ActionRightHandSetMicrogestures) + .AddBoolAction(ActionRightHandTapThumb, "Right Hand Tap Thumb"); + } + + // Hand Trackers + if (XrCreateHandTrackerExt) { + XrHandTrackerCreateInfoEXT createInfo{XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT}; + createInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT; + createInfo.hand = XR_HAND_LEFT_EXT; + OXR(XrCreateHandTrackerExt(GetSession(), &createInfo, &HandTrackerL)); + createInfo.hand = XR_HAND_RIGHT_EXT; + OXR(XrCreateHandTrackerExt(GetSession(), &createInfo, &HandTrackerR)); + + ALOG("xrCreateHandTrackerExt HandTrackerL=%llx", (long long)HandTrackerL); + ALOG("xrCreateHandTrackerExt HandTrackerR=%llx", (long long)HandTrackerR); + + // Setup skinning meshes for both hands + if (XrGetHandMeshFb) { + for (int handIndex = 0; handIndex < 2; ++handIndex) { + // Alias everything for initialization + const bool isLeft = (handIndex == 0); + auto& handTracker = isLeft ? HandTrackerL : HandTrackerR; + auto& handRenderer = isLeft ? HandRendererL : HandRendererR; + + // two-call pattern for mesh data + // call 1 - figure out sizes + + // mesh + XrHandTrackingMeshFB mesh{XR_TYPE_HAND_TRACKING_MESH_FB}; + mesh.next = nullptr; + // mesh - skeleton + mesh.jointCapacityInput = 0; + mesh.jointCountOutput = 0; + mesh.jointBindPoses = nullptr; + mesh.jointRadii = nullptr; + mesh.jointParents = nullptr; + // mesh - vertex + mesh.vertexCapacityInput = 0; + mesh.vertexCountOutput = 0; + mesh.vertexPositions = nullptr; + mesh.vertexNormals = nullptr; + mesh.vertexUVs = nullptr; + mesh.vertexBlendIndices = nullptr; + mesh.vertexBlendWeights = nullptr; + // mesh - index + mesh.indexCapacityInput = 0; + mesh.indexCountOutput = 0; + mesh.indices = nullptr; + // get mesh sizes + OXR(XrGetHandMeshFb(handTracker, &mesh)); + + // mesh storage - update sizes + mesh.jointCapacityInput = mesh.jointCountOutput; + mesh.vertexCapacityInput = mesh.vertexCountOutput; + mesh.indexCapacityInput = mesh.indexCountOutput; + // skeleton + std::vector jointBindLocations; + std::vector parentData; + std::vector jointRadii; + jointBindLocations.resize(mesh.jointCountOutput); + parentData.resize(mesh.jointCountOutput); + jointRadii.resize(mesh.jointCountOutput); + mesh.jointBindPoses = jointBindLocations.data(); + mesh.jointParents = parentData.data(); + mesh.jointRadii = jointRadii.data(); + // vertex + std::vector vertexPositions; + std::vector vertexNormals; + std::vector vertexUVs; + std::vector vertexBlendIndices; + std::vector vertexBlendWeights; + vertexPositions.resize(mesh.vertexCountOutput); + vertexNormals.resize(mesh.vertexCountOutput); + vertexUVs.resize(mesh.vertexCountOutput); + vertexBlendIndices.resize(mesh.vertexCountOutput); + vertexBlendWeights.resize(mesh.vertexCountOutput); + mesh.vertexPositions = vertexPositions.data(); + mesh.vertexNormals = vertexNormals.data(); + mesh.vertexUVs = vertexUVs.data(); + mesh.vertexBlendIndices = vertexBlendIndices.data(); + mesh.vertexBlendWeights = vertexBlendWeights.data(); + // index + std::vector indices; + indices.resize(mesh.indexCountOutput); + mesh.indices = indices.data(); + + mesh.next = nullptr; + + // get mesh data + OXR(XrGetHandMeshFb(handTracker, &mesh)); + // init renderer + handRenderer.Init(&mesh, true); + } + } + } + + return true; + } + + virtual void SessionEnd() override { + EnvironmentRenderer.Shutdown(); + SkyboxRenderer.Shutdown(); + + // Hand Tracker + if (XrDestroyHandTrackerExt) { + OXR(XrDestroyHandTrackerExt(HandTrackerL)); + OXR(XrDestroyHandTrackerExt(HandTrackerR)); + } + + BeamRenderer.Shutdown(); + HandRendererL.Shutdown(); + HandRendererR.Shutdown(); + } + + // Update state + virtual void Update(const OVRFW::ovrApplFrameIn& in) override { + { + // xrSyncAction + std::vector activeActionSets = { + {ActionLeftHandSetMicrogestures}, {ActionRightHandSetMicrogestures}}; + + XrActionsSyncInfo syncInfo = {XR_TYPE_ACTIONS_SYNC_INFO}; + syncInfo.countActiveActionSets = activeActionSets.size(); + syncInfo.activeActionSets = activeActionSets.data(); + OXR(xrSyncActions(Session, &syncInfo)); + } + + Ui.HitTestDevices().clear(); + + // Hands + if (XrLocateHandJointsExt) { + // L + XrHandTrackingScaleFB scaleL{XR_TYPE_HAND_TRACKING_SCALE_FB}; + scaleL.next = nullptr; + scaleL.sensorOutput = 1.0f; + scaleL.currentOutput = 1.0f; + scaleL.overrideValueInput = 1.00f; + scaleL.overrideHandScale = XR_FALSE; + XrHandTrackingAimStateFB aimStateL{XR_TYPE_HAND_TRACKING_AIM_STATE_FB}; + aimStateL.next = &scaleL; + XrHandJointVelocitiesEXT velocitiesL{XR_TYPE_HAND_JOINT_VELOCITIES_EXT}; + velocitiesL.next = &aimStateL; + velocitiesL.jointCount = XR_HAND_JOINT_COUNT_EXT; + velocitiesL.jointVelocities = JointVelocitiesL; + XrHandJointLocationsEXT locationsL{XR_TYPE_HAND_JOINT_LOCATIONS_EXT}; + locationsL.next = &velocitiesL; + locationsL.jointCount = XR_HAND_JOINT_COUNT_EXT; + locationsL.jointLocations = JointLocationsL; + // R + XrHandTrackingScaleFB scaleR{XR_TYPE_HAND_TRACKING_SCALE_FB}; + scaleR.next = nullptr; + scaleR.sensorOutput = 1.0f; + scaleR.currentOutput = 1.0f; + scaleR.overrideValueInput = 1.00f; + scaleR.overrideHandScale = XR_FALSE; + XrHandTrackingAimStateFB aimStateR{XR_TYPE_HAND_TRACKING_AIM_STATE_FB}; + aimStateR.next = &scaleR; + XrHandJointVelocitiesEXT velocitiesR{XR_TYPE_HAND_JOINT_VELOCITIES_EXT}; + velocitiesR.next = &aimStateR; + velocitiesR.jointCount = XR_HAND_JOINT_COUNT_EXT; + velocitiesR.jointVelocities = JointVelocitiesR; + XrHandJointLocationsEXT locationsR{XR_TYPE_HAND_JOINT_LOCATIONS_EXT}; + locationsR.next = &velocitiesR; + locationsR.jointCount = XR_HAND_JOINT_COUNT_EXT; + locationsR.jointLocations = JointLocationsR; + + XrHandJointsLocateInfoEXT locateInfoL{XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT}; + locateInfoL.baseSpace = GetStageSpace(); + locateInfoL.time = ToXrTime(in.PredictedDisplayTime); + OXR(XrLocateHandJointsExt(HandTrackerL, &locateInfoL, &locationsL)); + + XrHandJointsLocateInfoEXT locateInfoR{XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT}; + locateInfoR.baseSpace = GetStageSpace(); + locateInfoR.time = ToXrTime(in.PredictedDisplayTime); + OXR(XrLocateHandJointsExt(HandTrackerR, &locateInfoR, &locationsR)); + + std::vector handJointsL; + std::vector handJointsR; + + // Tracked joints and computed joints can all be valid + XrSpaceLocationFlags isValid = + XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_POSITION_VALID_BIT; + + HandTrackedL = false; + HandTrackedR = false; + + if (locationsL.isActive) { + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) { + if ((JointLocationsL[i].locationFlags & isValid) != 0) { + const auto p = FromXrPosef(JointLocationsL[i].pose); + handJointsL.push_back(p); + HandTrackedL = true; + } + } + HandRendererL.Update(&JointLocationsL[0]); + } + if (locationsR.isActive) { + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) { + if ((JointLocationsR[i].locationFlags & isValid) != 0) { + const auto p = FromXrPosef(JointLocationsR[i].pose); + handJointsR.push_back(p); + HandTrackedR = true; + } + } + HandRendererR.Update(&JointLocationsR[0]); + } + } + + for (auto& panelPair : MgActionSetPanels) { + panelPair.second.Update(); + } + + Ui.Update(in); + BeamRenderer.Update(in, Ui.HitTestDevices()); + } + + // Render eye buffers while running + virtual void Render(const OVRFW::ovrApplFrameIn& in, OVRFW::ovrRendererOutput& out) override { + SkyboxRenderer.Render(out.Surfaces); + EnvironmentRenderer.Render(out.Surfaces); + Ui.Render(in, out); + + // Render hand axes + if (HandTrackedL) { + HandRendererL.Render(out.Surfaces); + } + if (HandTrackedR) { + HandRendererR.Render(out.Surfaces); + } + // Render beams last, since they render with transparency (alpha blending) + BeamRenderer.Render(in, out); + } + + public: + // Hands - extension functions + PFN_xrCreateHandTrackerEXT XrCreateHandTrackerExt = nullptr; + PFN_xrDestroyHandTrackerEXT XrDestroyHandTrackerExt = nullptr; + PFN_xrLocateHandJointsEXT XrLocateHandJointsExt = nullptr; + // Hands - FB mesh rendering extensions + PFN_xrGetHandMeshFB XrGetHandMeshFb = nullptr; + // Hands - tracker handles + XrHandTrackerEXT HandTrackerL = XR_NULL_HANDLE; + XrHandTrackerEXT HandTrackerR = XR_NULL_HANDLE; + // Hands - data buffers + XrHandJointLocationEXT JointLocationsL[XR_HAND_JOINT_COUNT_EXT]; + XrHandJointLocationEXT JointLocationsR[XR_HAND_JOINT_COUNT_EXT]; + XrHandJointVelocityEXT JointVelocitiesL[XR_HAND_JOINT_COUNT_EXT]; + XrHandJointVelocityEXT JointVelocitiesR[XR_HAND_JOINT_COUNT_EXT]; + + private: + OVRFW::EnvironmentRenderer EnvironmentRenderer; + OVRFW::SkyboxRenderer SkyboxRenderer; + OVRFW::TinyUI Ui; + OVRFW::SimpleBeamRenderer BeamRenderer; + + OVRFW::HandRenderer HandRendererL; + OVRFW::HandRenderer HandRendererR; + bool HandTrackedL{false}; + bool HandTrackedR{false}; + + XrPath ExtHandInteractionProfile{XR_NULL_PATH}; + XrPath LeftHandPath{XR_NULL_PATH}; + XrPath RightHandPath{XR_NULL_PATH}; + + XrActionSet ActionLeftHandSetMicrogestures{XR_NULL_HANDLE}; + XrAction ActionLeftHandSwipeLeft{XR_NULL_HANDLE}; + XrAction ActionLeftHandSwipeRight{XR_NULL_HANDLE}; + XrAction ActionLeftHandSwipeForward{XR_NULL_HANDLE}; + XrAction ActionLeftHandSwipeBackward{XR_NULL_HANDLE}; + XrAction ActionLeftHandTapThumb{XR_NULL_HANDLE}; + + XrActionSet ActionRightHandSetMicrogestures{XR_NULL_HANDLE}; + XrAction ActionRightHandSwipeLeft{XR_NULL_HANDLE}; + XrAction ActionRightHandSwipeRight{XR_NULL_HANDLE}; + XrAction ActionRightHandSwipeForward{XR_NULL_HANDLE}; + XrAction ActionRightHandSwipeBackward{XR_NULL_HANDLE}; + XrAction ActionRightHandTapThumb{XR_NULL_HANDLE}; + + std::unordered_map MgActionSetPanels{}; +}; + +ENTRY_POINT(XrMicrogesturesApp) diff --git a/Samples/XrSamples/XrMicrogestures/assets/Skybox.gltf.ovrscene b/Samples/XrSamples/XrMicrogestures/assets/Skybox.gltf.ovrscene new file mode 100644 index 0000000..a098497 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/assets/Skybox.gltf.ovrscene differ diff --git a/Samples/XrSamples/XrMicrogestures/assets/SmallRoom.gltf.ovrscene b/Samples/XrSamples/XrMicrogestures/assets/SmallRoom.gltf.ovrscene new file mode 100644 index 0000000..2183af3 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/assets/SmallRoom.gltf.ovrscene differ diff --git a/Samples/XrSamples/XrMicrogestures/assets/assets.txt b/Samples/XrSamples/XrMicrogestures/assets/assets.txt new file mode 100644 index 0000000..2cc30f7 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/assets/assets.txt @@ -0,0 +1 @@ +This file is a placeholder. diff --git a/Samples/XrSamples/XrMicrogestures/assets/panel.ktx b/Samples/XrSamples/XrMicrogestures/assets/panel.ktx new file mode 100644 index 0000000..deb13e8 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/assets/panel.ktx differ diff --git a/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures-native.png b/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures-native.png new file mode 100644 index 0000000..1facb59 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures-native.png differ diff --git a/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures.png b/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures.png new file mode 100644 index 0000000..e4ecde2 Binary files /dev/null and b/Samples/XrSamples/XrMicrogestures/images/openxr-hand-tracking-microgestures.png differ diff --git a/Samples/XrSamples/XrMicrogestures/java/MainActivity.java b/Samples/XrSamples/XrMicrogestures/java/MainActivity.java new file mode 100644 index 0000000..63a3ad9 --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/java/MainActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * 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. + */ +// Copyright (c) Facebook Technologies, LLC and its affiliates. All Rights reserved. +package com.oculus.sdk.xrmicrogestures; + +///--BEGIN_SDK_REMOVE +import java.io.PrintWriter; +import java.io.FileDescriptor; +///--END_SDK_REMOVE + +public class MainActivity extends android.app.NativeActivity { +///--BEGIN_SDK_REMOVE + private static native String nativeGetDumpStr(); + + static { + System.loadLibrary("xrmicrogestures"); + } + + @Override + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println(); + writer.println("========== MICROGESTURES =========="); + writer.println("Microgesture Events Begin"); + writer.println(nativeGetDumpStr()); + writer.println("Microgesture Events End"); + } +///--END_SDK_REMOVE +} diff --git a/Samples/XrSamples/XrMicrogestures/res/values/strings.xml b/Samples/XrSamples/XrMicrogestures/res/values/strings.xml new file mode 100644 index 0000000..cacf9cf --- /dev/null +++ b/Samples/XrSamples/XrMicrogestures/res/values/strings.xml @@ -0,0 +1,4 @@ + + + XrMicrogestures Sample + diff --git a/Samples/XrSamples/XrPassthrough/Projects/Android/build.gradle b/Samples/XrSamples/XrPassthrough/Projects/Android/build.gradle index 3bbe15c..67b5913 100755 --- a/Samples/XrSamples/XrPassthrough/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrPassthrough/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrPassthroughOcclusion/Projects/Android/build.gradle b/Samples/XrSamples/XrPassthroughOcclusion/Projects/Android/build.gradle index 81ce61d..7f613ce 100755 --- a/Samples/XrSamples/XrPassthroughOcclusion/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrPassthroughOcclusion/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrSceneModel/Projects/Android/build.gradle b/Samples/XrSamples/XrSceneModel/Projects/Android/build.gradle index 947ecc1..03662a5 100755 --- a/Samples/XrSamples/XrSceneModel/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrSceneModel/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrSceneModel/Src/SceneModelGl.cpp b/Samples/XrSamples/XrSceneModel/Src/SceneModelGl.cpp index c68099e..a3d2884 100755 --- a/Samples/XrSamples/XrSceneModel/Src/SceneModelGl.cpp +++ b/Samples/XrSamples/XrSceneModel/Src/SceneModelGl.cpp @@ -961,7 +961,7 @@ void ovrScene::Clear() { } Meshes.clear(); -} + } bool ovrScene::IsCreated() { return CreatedScene; @@ -1026,7 +1026,7 @@ void ovrScene::Create() { ALOGE("Failed to compile mesh program!"); } - + CreatedScene = true; CreateVAOs(); @@ -1405,6 +1405,6 @@ void ovrAppRenderer::RenderFrame(const FrameIn& frameIn) { GL(glDisable(GL_BLEND)); GL(glUseProgram(0)); - + Framebuffer.Unbind(); } diff --git a/Samples/XrSamples/XrSceneModel/Src/SceneModelXr.cpp b/Samples/XrSamples/XrSceneModel/Src/SceneModelXr.cpp index 2269862..3397266 100755 --- a/Samples/XrSamples/XrSceneModel/Src/SceneModelXr.cpp +++ b/Samples/XrSamples/XrSceneModel/Src/SceneModelXr.cpp @@ -56,7 +56,6 @@ Authors : #include - #if defined(_WIN32) // Favor the high performance NVIDIA or AMD GPUs extern "C" { @@ -212,7 +211,6 @@ std::string BoundaryVisibilityToString(const XrBoundaryVisibilityMETA boundaryVi } } - /* ================================================================================ @@ -500,7 +498,7 @@ struct ovrExtensionFunctionPointers { PFN_xrRequestSceneCaptureFB xrRequestSceneCaptureFB = nullptr; #endif PFN_xrRequestBoundaryVisibilityMETA xrRequestBoundaryVisibilityMETA = nullptr; - }; +}; struct ovrApp { void Clear(); @@ -581,8 +579,7 @@ struct ovrApp { XrPassthroughLayerFB PassthroughLayer = XR_NULL_HANDLE; XrBoundaryVisibilityMETA CurrentBoundaryVisibility = XR_BOUNDARY_VISIBILITY_NOT_SUPPRESSED_META; - - }; +}; void ovrApp::Clear() { #if defined(XR_USE_PLATFORM_ANDROID) @@ -1083,8 +1080,7 @@ void ovrApp::HandleXrEvents() { CollectRoomLayoutUuids(result.space, UuidSet); } } - - } + } } break; case XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB: { ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB"); @@ -1116,7 +1112,7 @@ void ovrApp::HandleXrEvents() { "xrPollEvent: Boundary visibility changed to %s", BoundaryVisibilityToString(CurrentBoundaryVisibility).c_str()); } break; - default: + default: ALOGV("xrPollEvent: Unknown event"); break; } @@ -1477,7 +1473,7 @@ int main() { XR_FB_SPATIAL_ENTITY_CONTAINER_EXTENSION_NAME, XR_META_SPATIAL_ENTITY_MESH_EXTENSION_NAME, XR_META_BOUNDARY_VISIBILITY_EXTENSION_NAME, - XR_FB_SCENE_EXTENSION_NAME, + XR_FB_SCENE_EXTENSION_NAME, #if defined(ANDROID) XR_FB_SCENE_CAPTURE_EXTENSION_NAME, #endif @@ -1940,7 +1936,7 @@ int main() { instance, "xrRequestBoundaryVisibilityMETA", (PFN_xrVoidFunction*)(&app.FunPtrs.xrRequestBoundaryVisibilityMETA))); - + CreatePassthrough(app); // Two values for left and right controllers. @@ -2032,7 +2028,6 @@ int main() { app.UuidSet.clear(); - app.IsQueryComplete = true; } @@ -2143,7 +2138,6 @@ int main() { CycleSceneVisualizationMode(app); } - #if defined(ANDROID) // Right Index Trigger: Request scene capture. if (input->IsTriggerPressed(SimpleXrInput::Side_Right)) { diff --git a/Samples/XrSamples/XrSpaceWarp/Projects/Android/build.gradle b/Samples/XrSamples/XrSpaceWarp/Projects/Android/build.gradle index 7243166..0c280d4 100755 --- a/Samples/XrSamples/XrSpaceWarp/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrSpaceWarp/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrSpatialAnchor/Projects/Android/build.gradle b/Samples/XrSamples/XrSpatialAnchor/Projects/Android/build.gradle index 1909129..2a75a56 100755 --- a/Samples/XrSamples/XrSpatialAnchor/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrSpatialAnchor/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' } diff --git a/Samples/XrSamples/XrVirtualKeyboard/Projects/Android/build.gradle b/Samples/XrSamples/XrVirtualKeyboard/Projects/Android/build.gradle index 8e59759..d7af42a 100755 --- a/Samples/XrSamples/XrVirtualKeyboard/Projects/Android/build.gradle +++ b/Samples/XrSamples/XrVirtualKeyboard/Projects/Android/build.gradle @@ -92,5 +92,5 @@ android { dependencies { // Package/application AndroidManifest.xml properties, plus headers and libraries // exposed to CMake - implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.38' + implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.40' }