Please Select Your Location
Australia
Österreich
België
Canada
Canada - Français
中国
Česká republika
Denmark
Deutschland
France
HongKong
Iceland
Ireland
Italia
日本
Korea
Latvija
Lietuva
Lëtzebuerg
Malta
المملكة العربية السعودية (Arabic)
Nederland
New Zealand
Norge
Polska
Portugal
Russia
Saudi Arabia
Southeast Asia
España
Suisse
Suomi
Sverige
台灣
Ukraine
United Kingdom
United States
Please Select Your Location
België
Česká republika
Denmark
Iceland
Ireland
Italia
Latvija
Lietuva
Lëtzebuerg
Malta
Nederland
Norge
Polska
Portugal
España
Suisse
Suomi
Sverige

Interact with the Real World: OpenXR Scene Understanding

Overview

1. OpenXR Scene Understanding Plugin Setup
1.1 Enable OpenXR Plugins.
1.2 Enable Scene Understanding extensions.
2. Custom XR Meshing Subsystem.
3. Create an OpenXR Feature to set up the custom Meshing Subsystem..
4. Game objects settings.
4.1 Camera setting.
4.2 Settings for mesh.
4.3 Prefab for mesh.
5. Scripts to draw mesh.
5.1 Add scripts to Start function.
5.2 Add scripts to Update function.
5.3 Results.
6. Let other virtual objects interact with real-environment meshes.
6.1 Game object settings.
6.2 Spawn virtual objects.
6.3 Results.
7. Appendix.
7.1 Custom XR Meshing Subsystem.

1. OpenXR Scene Understanding Plugin Setup

Supported Unity Engine version: 2020.2+

1.1 Enable OpenXR Plugins

Please enable OpenXR plugin in Edit > Project Settings > XR Plug-in Management:

image1.png

Click Exclamation mark next to “OpenXR” then choose “Fix All”.

image2.png
image3.png

Add Interaction profiles for your device. (As following, take Vive Controller as an example.

image4.png

1.2 Enable Scene Understanding extensions

image5.png

2. Custom XR Meshing Subsystem.

The custom Meshing subsystem is native code snippet to supply mesh with OpenXR Sceneunderstanding related functions. Within OpenXR this functionality can be exposed by using `OpenXRFeature` to manage the subsystem. You can use the dll provided by our SceneUnderstanding sample or refer to the appendix to build your custom subsystem.

Assets > MeshingFeaturePlugin > windows > x64 > MeshingFeaturePlugin.dll

3. Create an OpenXR Feature to set up the custom Meshing Subsystem

Defining a feature which override the “SceneUnderstanding_OpenXR_API” class and provide attribute as below when running in the editor.

#if UNITY_EDITOR
    [OpenXRFeature(UiName = "Meshing Subsystem",
        BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA, BuildTargetGroup.Android },
        Company = "HTC",
        Desc = "Example extension showing how to supply a mesh from native code with OpenXR SceneUnderstanding functions.",
        DocumentationLink = ".\\Assets\\Samples\\VIVE Wave OpenXR Plugin - Windows\\1.0.4\\SceneUnderStanding Example\\Documentation",
        OpenxrExtensionStrings = "",
        Version = "0.0.1",
        FeatureId = featureId)]
#endif
    public class MeshingTeapotFeature : SceneUnderstanding_OpenXR_API
    {
        new public const string featureId = "com.unity.openxr.feature.example.meshing";
        private static List<XRMeshSubsystemDescriptor> s_MeshDescriptors = new List<XRMeshSubsystemDescriptor>();
    }

Override functions as below.

Step 1: Manage the lifecycle of Unity subsystems.

protected override void OnSubsystemCreate ()
{
    CreateSubsystem<XRMeshSubsystemDescriptor, XRMeshSubsystem>(s_MeshDescriptors, "Sample Meshing");
}

/// <inheritdoc />
protected override void OnSubsystemStart ()
{
    StartSubsystem<XRMeshSubsystem>();
}

/// <inheritdoc />
protected override void OnSubsystemStop ()
{
    StopSubsystem<XRMeshSubsystem>();
}

/// <inheritdoc />
protected override void OnSubsystemDestroy ()
{
    DestroySubsystem<XRMeshSubsystem>();
}

Step 2: Intercept create session function

protected override void OnSessionCreate(ulong xrSession)
{
    m_XrSession = xrSession;

    NativeApi.SetOpenXRVariables(m_XrInstance, m_XrSession,
        Marshal.GetFunctionPointerForDelegate(m_XrEnumerateReferenceSpaces),
        Marshal.GetFunctionPointerForDelegate(m_XrCreateReferenceSpace),
        Marshal.GetFunctionPointerForDelegate(m_XrDestroySpace),
        Marshal.GetFunctionPointerForDelegate(m_XrEnumerateSceneComputeFeaturesMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrCreateSceneObserverMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrDestroySceneObserverMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrCreateSceneMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrDestroySceneMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrComputeNewSceneMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrGetSceneComputeStateMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrGetSceneComponentsMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrLocateSceneComponentsMSFT),
        Marshal.GetFunctionPointerForDelegate(m_XrGetSceneMeshBuffersMSFT));
    systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
    XrSystemPassThroughPropertiesHTC SystemPassThroughPropertiesHTC;
    SystemPassThroughPropertiesHTC.type = XrStructureType.XR_TYPE_SYSTEM_PASS_THROUGH_PROPERTIES_HTC;
    unsafe
    {
        systemProperties.next = (IntPtr)(&SystemPassThroughPropertiesHTC);
    }
    int res = xrGetSystemProperties(ref systemProperties);
    if (res != (int)XrResult.XR_SUCCESS)
    {
        UnityEngine.Debug.Log("Failed to get systemproperties with error code : " + res);

    }
}

Create class which contains function from custom XR Meshing Subsystem.

class NativeApi
{
    const string dll_path = "MeshingFeaturePlugin";
    [DllImport(dll_path)]
    public static extern void SetOpenXRVariables(ulong instance, ulong session,
        IntPtr PFN_XrEnumerateReferenceSpaces,
        IntPtr PFN_XrCreateReferenceSpace,
        IntPtr PFN_XrDestroySpace,
        IntPtr PFN_XrEnumerateSceneComputeFeaturesMSFT,
        IntPtr PFN_XrCreateSceneObserverMSFT,
        IntPtr PFN_XrDestroySceneObserverMSFT,
        IntPtr PFN_XrCreateSceneMSFT,
        IntPtr PFN_XrDestroySceneMSFT,
        IntPtr PFN_XrComputeNewSceneMSFT,
        IntPtr PFN_XrGetSceneComputeStateMSFT,
        IntPtr PFN_XrGetSceneComponentsMSFT,
        IntPtr PFN_XrLocateSceneComponentsMSFT,
        IntPtr PFN_XrGetSceneMeshBuffersMSFT);
    [DllImport(dll_path)]
    public static extern void SetSceneComputeOrientedBoxBound(Vector4 rotation, Vector3 position, Vector3 extent);
}

Add function to convert right-handed transform.

public void SetSceneComputeOrientedBoxBound(Transform transform, Vector3 extent)
{
    Vector4 rotation;
    Vector3 position;
    ConvertTransform(transform, out rotation, out position);
    NativeApi.SetSceneComputeOrientedBoxBound(rotation, position, extent);
}

Enabled the created OpenXR feature.

image6.png

4. Game objects settings

Create empty game object named MeshingSample here.

image7.png

Create cube for setting scene understanding computation box bound and set options in inspector.

image8.png image9.png

4.1 Camera setting

To display mesh in the correct position, add “TrackedPoseDriver” script to your VR render camera.

image10.png

Adjust background

image11.png

4.2 Settings for mesh

Create empty game object named Meshes here.

image12.png

4.3 Prefab for mesh

Create prefab for mesh named emptyMesh and add Mesh Renderer and setting materials.

image13.png

Add Mesh Collider

image14.png

Add Mesh filter.

image15.png

5. Scripts to draw mesh

Create new script to draw mesh with scene understanding results and add following namespaces to your script.

using UnityEngine.SubsystemsImplementation;
using UnityEngine.XR;
using UnityEngine.InputSystem;
using VIVE.SceneUnderstanding;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Samples.MeshingFeature;

Add the following properties:

public GameObject emptyMeshPrefab_Default;
public Transform target;
private XRMeshSubsystem s_MeshSubsystem;
private List<MeshInfo> s_MeshInfos = new List<MeshInfo>();
private Dictionary<MeshId, GameObject> m_MeshIdToGo = new Dictionary<MeshId, GameObject>();
private MeshingTeapotFeature m_MeshingFeature;
//Scene compute bound variables
// A default cube game object
public GameObject m_BoxBoundObject

5.1 Add scripts to Start function

Get and check XRMeshSubsystem

m_MeshingFeature = OpenXRSettings.Instance.GetFeature<MeshingTeapotFeature>();
if (m_MeshingFeature == null || m_MeshingFeature.enabled == false)
{
    enabled = false;
    return;
}

var meshSubsystems = new List<XRMeshSubsystem>();
SubsystemManager.GetInstances(meshSubsystems);
if (meshSubsystems.Count == 1)
{
    s_MeshSubsystem = meshSubsystems[0];
    textMesh.gameObject.SetActive(false);
}
else
{
    enabled = false;
}

5.2 Add scripts to Update function

Step 1: Get the states of all tracked meshes.

if (s_MeshSubsystem.running && s_MeshSubsystem.TryGetMeshInfos(s_MeshInfos))
{
    foreach (var meshInfo in s_MeshInfos)
    {
        switch (meshInfo.ChangeState)
        {
            case MeshChangeState.Added:
            case MeshChangeState.Updated:
                if (!m_MeshIdToGo.TryGetValue(meshInfo.MeshId, out var go))
                {
                    go = Instantiate(emptyMeshPrefab, target, false);
                    m_MeshIdToGo[meshInfo.MeshId] = go;
                }

                var mesh = go.GetComponent<MeshFilter>().mesh;
                var col = go.GetComponent<MeshCollider>();

                s_MeshSubsystem.GenerateMeshAsync(meshInfo.MeshId, mesh, col, MeshVertexAttributes.None,
                    result =>
                    {
                        result.Mesh.RecalculateNormals();
                    });
                break;
            case MeshChangeState.Removed:
                if (m_MeshIdToGo.TryGetValue(meshInfo.MeshId, out var meshGo))
                {
                    Destroy(meshGo);
                    m_MeshIdToGo.Remove(meshInfo.MeshId);
                }
                break;
            default:
                break;
        }
    }
}

When state is “Updated”, generate mesh if the existence of the mesh that Unity doesn’t know. When state is “Removed”, destroy mesh if Unity know s the existence of the mesh.

Step 2: Set scene computation box bound.

if (m_BoxBoundObject == null) return;
m_MeshingFeature.SetSceneComputeOrientedBoxBound(m_BoxBoundObject.transform, m_BoxBoundObject.transform.localScale); // The widths of a default cube is 1.0f.

Attach the script to MeshingSample object and set options in inspector.

image16.png image17.png

5.3 Results

image18.png

The blue part is the background, the white part is the created meshes, the upper horizontal plane is the ceiling, and the front vertical plane is the wall.

6. Let other virtual objects interact with real-environment meshes

6.1 Game object settings

Create empty prefab named sphere here.

Add Mesh Renderer and setting materials.

image19.png

Add Sphere Collider

image20.png

Add Rigidbody.

image21.png

Add Mesh filter.

image22.png

Add DestrySelf script

image23.png

6.2 Spawn virtual objects

Create new script to spawn sphere from main camera.

Step 1: Add properties to script.

public GameObject sphere;
public float shootVelocity = 5f;
float time;

Step 2: Set options in inspector and attach the script to camera.

image24.png

Step 3: Add below codes to spawn targets.

private void Start()
{
    time = Time.time;
}
void Spawn()
{
    GameObject ball = Instantiate(sphere, transform);
    Rigidbody rb = ball.GetComponent<Rigidbody>();
    rb.velocity = ball.transform.parent.forward * shootVelocity;
    rb.isKinematic = false;
    ball.transform.parent = null;
}

// Update is called once per frame
void Update()
{
    if(Time.time - time > 1)
    {
        Spawn();
        time = Time.time;
    }

}

6.3 Results

image25.png

The balls that shoot forward will collide with the vertical plane then fall and stay on the horizontal plane.

7. Appendix

7.1 Custom XR Meshing Subsystem

Add following namespaces.

#include <openxr/openxr.h>
#include "IUnityInterface.h"
#include "XR/IUnityXRMeshing.h"
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <fstream>
#include <vector>

Add following function pointers.

// XrSpace related function pointers
static PFN_xrEnumerateReferenceSpaces s_xrEnumerateReferenceSpaces = nullptr;
static PFN_xrCreateReferenceSpace s_xrCreateReferenceSpace = nullptr;
static PFN_xrDestroySpace s_xrDestroySpace = nullptr;

// XR_MSFT_scene_understanding function pointers
static PFN_xrEnumerateSceneComputeFeaturesMSFT s_xrEnumerateSceneComputeFeaturesMSFT = nullptr;
static PFN_xrCreateSceneObserverMSFT s_xrCreateSceneObserverMSFT = nullptr;
static PFN_xrDestroySceneObserverMSFT s_xrDestroySceneObserverMSFT = nullptr;
static PFN_xrCreateSceneMSFT s_xrCreateSceneMSFT = nullptr;
static PFN_xrDestroySceneMSFT s_xrDestroySceneMSFT = nullptr;
static PFN_xrComputeNewSceneMSFT s_xrComputeNewSceneMSFT = nullptr;
static PFN_xrGetSceneComputeStateMSFT s_xrGetSceneComputeStateMSFT = nullptr;
static PFN_xrGetSceneComponentsMSFT s_xrGetSceneComponentsMSFT = nullptr;
static PFN_xrLocateSceneComponentsMSFT s_xrLocateSceneComponentsMSFT = nullptr;
static PFN_xrGetSceneMeshBuffersMSFT s_xrGetSceneMeshBuffersMSFT = nullptr;

Define class to make an XRSceneMSFT can be managed by shared pointers

void Log(const std::string& line)
{
    std::ofstream file("meshing_plugin.log", std::ios::app);
    if (!file.is_open()) return;
    file << line << "\n";
}

void CheckResult(XrResult result, const std::string& funcName)
{
    if (result != XrResult::XR_SUCCESS)
    {
        Log(funcName + " failure: " + std::to_string(result));
    }
}
class SharedOpenXRScene
{
public:
    /**
     * @param[in] scene A valid scene, which is created by xrCreateSceneMSFT.
     */
    SharedOpenXRScene(XrSceneMSFT scene) : m_Scene(scene) {}
    ~SharedOpenXRScene()
    {
        if (s_xrDestroySceneMSFT != nullptr && s_SceneObserver != nullptr)
        {
            CheckResult(s_xrDestroySceneMSFT(m_Scene), "xrDestroySceneMSFT");
        }
    }

    XrSceneMSFT GetScene() const { return m_Scene; };

private:
    XrSceneMSFT m_Scene;
};

Define class to store mesh data belonging to a UnityXRMeshId.

class MeshData
{
public:
    MeshData() : m_UpdateTime(0) {}

    UnityXRMeshInfo m_UnityXRMeshInfo;

    /**
     * Point to a shared OpenXR scene.
     * When there is no mesh data pointing to a scene,
     * the scene will be destroyed by an OpenXR function (xrDestroySceneMSFT).
     */
    std::shared_ptr<SharedOpenXRScene> m_SharedOpenXRScene;

    XrSceneMeshMSFT m_OpenXRSceneMesh;

    long long m_UpdateTime;
};

Add following variables.

static XrReferenceSpaceType s_ReferenceSpaceType = XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_STAGE;
static XrSpace s_XrSpace = nullptr;

static XrSceneObserverMSFT s_SceneObserver = nullptr;
static bool s_OpenXRReady = false;

static XrSceneComputeConsistencyMSFT s_SceneComputeConsistency = XrSceneComputeConsistencyMSFT::XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_INCOMPLETE_FAST_MSFT;

// User specified scene computation boundaries.

static std::vector<XrSceneSphereBoundMSFT> s_SceneSphereBounds;
static std::vector<XrSceneOrientedBoxBoundMSFT> s_SceneOrientedBoxBounds;
static std::vector&ly;XrSceneFrustumBoundMSFT> s_SceneFrustumBounds;

static XrMeshComputeLodMSFT s_MeshComputeLod = XrMeshComputeLodMSFT::XR_MESH_COMPUTE_LOD_COARSE_MSFT;
static std::map<UnityXRMeshId, MeshData, MeshIdLessThanComparator> s_MeshDataByMeshId;

Add following codes to handle main Unity events, which must export UnityPluginLoad and UnityPluginUnload functions. IUnityInterfaces enables the plug-in to access these functions.

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginLoad(IUnityInterfaces* interfaces)
{
    s_Meshing = interfaces->Get<IUnityXRMeshInterface>();
    if (s_Meshing == nullptr)
        return;

    UnityLifecycleProvider meshingLifecycleHandler{};
    s_Meshing->RegisterLifecycleProvider("OpenXR Extension Sample", "Sample Meshing", &meshingLifecycleHandler);

}
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginUnload()
{
}

Implement subsystem specific lifecycle events in UnityPluginLoad function as follows

Step 1: Define Initialize function for the subsystem. (Reset scene computation bounds and define mesh provider here.)

meshingLifecycleHandler.Initialize = [](UnitySubsystemHandle handle, void* userData) -> UnitySubsystemErrorCode {
    // Reset scene computation bounds.
    // Use an axis-aligned bounding box by default.
    s_SceneSphereBounds.clear();
    s_SceneOrientedBoxBounds.clear();
    s_SceneFrustumBounds.clear();

    s_MeshDataByMeshId.clear();
    UnityXRMeshProvider meshProvider{};
    meshProvider.GetMeshInfos = [](UnitySubsystemHandle handle, void* userData, UnityXRMeshInfoAllocator* allocator) -> UnitySubsystemErrorCode {
        if (s_SceneObserver == nullptr) return kUnitySubsystemErrorCodeFailure;

        // Set existing mesh infos as not updated.
        for (auto&& pair : s_MeshDataByMeshId)
        {
            pair.second.m_UnityXRMeshInfo.updated = false;
        }

        bool canComputeNewScene = false;

        // Check the scene compute state.
        XrSceneComputeStateMSFT computeState;
        CheckResult(s_xrGetSceneComputeStateMSFT(s_SceneObserver, &computeState), "xrGetSceneComputeStateMSFT");
        switch (computeState)
        {
        case XrSceneComputeStateMSFT::XR_SCENE_COMPUTE_STATE_NONE_MSFT:
        {
            // Compute a new scene at the end of the function.
            canComputeNewScene = true;
            break;
        }
        case XrSceneComputeStateMSFT::XR_SCENE_COMPUTE_STATE_UPDATING_MSFT:
            // Wait for scene computation.
            canComputeNewScene = false;
            break;
        case XrSceneComputeStateMSFT::XR_SCENE_COMPUTE_STATE_COMPLETED_MSFT:
        {
            // Compute a new scene at the end of the function.
            canComputeNewScene = true;

            // Create a scene of the computation result.
            XrSceneCreateInfoMSFT sceneCreateInfo;
            sceneCreateInfo.type = XrStructureType::XR_TYPE_SCENE_CREATE_INFO_MSFT;
            sceneCreateInfo.next = NULL;
            XrSceneMSFT scene;
            CheckResult(s_xrCreateSceneMSFT(s_SceneObserver, &sceneCreateInfo, &scene), "xrCreateSceneMSFT");

            // Create a shared scene to be stored in mesh data.
            auto sharedScene = std::make_shared<SharedOpenXRScene>(scene);

            // Stage 1: Get scene visual mesh components.

            XrSceneComponentsGetInfoMSFT sceneComponentsGetInfo;
            sceneComponentsGetInfo.type = XrStructureType::XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT;
            sceneComponentsGetInfo.next = NULL;
            sceneComponentsGetInfo.componentType = XrSceneComponentTypeMSFT::XR_SCENE_COMPONENT_TYPE_VISUAL_MESH_MSFT;
            // First get the buffer capacity.
            XrSceneComponentsMSFT sceneComponents;
            sceneComponents.type = XrStructureType::XR_TYPE_SCENE_COMPONENTS_MSFT;
            sceneComponents.next = NULL;
            sceneComponents.componentCapacityInput = 0;
            sceneComponents.components = NULL;
            CheckResult(s_xrGetSceneComponentsMSFT(scene, &sceneComponentsGetInfo, &sceneComponents), "xrGetSceneComponentsMSFT");
            // Create scene components by the provided capacity.
            std::vector<XrSceneComponentMSFT> sceneComponentsVector(sceneComponents.componentCountOutput);
            sceneComponents.componentCapacityInput = sceneComponents.componentCountOutput;
            sceneComponents.components = sceneComponentsVector.data();
            // Also add an instance in the structure chain for getting scene visual mesh components.
            std::vector<XrSceneMeshMSFT> sceneMeshesVector(sceneComponents.componentCountOutput);
            XrSceneMeshesMSFT sceneMeshes;
            sceneMeshes.type = XrStructureType::XR_TYPE_SCENE_MESHES_MSFT;
            sceneMeshes.next = NULL;
            sceneMeshes.sceneMeshCount = sceneComponents.componentCountOutput;
            sceneMeshes.sceneMeshes = sceneMeshesVector.data();
            sceneComponents.next = &sceneMeshes;
            // Call xrGetSceneComponentsMSFT() again to fill out the scene components and scene visual mesh components.
            CheckResult(s_xrGetSceneComponentsMSFT(scene, &sceneComponentsGetInfo, &sceneComponents), "xrGetSceneComponentsMSFT");

            // Fill out mesh info from the scene visual mesh components.
            for (size_t componentIndex = 0; componentIndex < sceneComponents.componentCountOutput; ++componentIndex)
            {
                auto& sceneComponent = sceneComponentsVector[componentIndex];
                auto& sceneMesh = sceneMeshesVector[componentIndex];

                // Create a Unity mesh id by the OpenXR component id.
                // If OpenXR scene components of different time have the same component id,
                // they represent the same physical object. Thus use component id
                // as Unity mesh id.
                UnityXRMeshId meshId;
                memcpy(&meshId, &sceneComponent.id, sizeof(UnityXRMeshId));

                // Prepare to store mesh data of the mesh id.
                // The mesh data can be an existing one from the previous scene,
                // or a new one of the current scene.
                auto& meshData = s_MeshDataByMeshId[meshId];

                // Set the mesh info of the mesh id.
                // If the current update time is larger than the stored value,
                // set the mesh as updated.
                auto& meshInfo = meshData.m_UnityXRMeshInfo;
                meshInfo.meshId = meshId;
                meshInfo.updated = sceneComponent.updateTime > meshData.m_UpdateTime;
                meshInfo.priorityHint = 0;

                // Store the shared scene in order to manage the destruction of scene.
                meshData.m_SharedOpenXRScene = sharedScene;

                // Store the OpenXR scene mesh.
                meshData.m_OpenXRSceneMesh = sceneMesh;

                // Store the update time.
                meshData.m_UpdateTime = sceneComponent.updateTime;
            }

            // After setting data of the current scene, remove mesh data
            // not belonging to the current scene.
            for (auto iterator = s_MeshDataByMeshId.cbegin(); iterator != s_MeshDataByMeshId.cend();)
            {
                if (iterator->second.m_SharedOpenXRScene != sharedScene)
                {
                    // The mesh data does not exist in the current scene.
                    // Erase it from the container, and get the iterator
                    // after the erased position.
                    iterator = s_MeshDataByMeshId.erase(iterator);
                }
                else
                {
                    // The mesh data exist in the current scene.
                    // Do nothing and increment the iterator.
                    ++iterator;
                }
            }
        }
            break;
        case XrSceneComputeStateMSFT::XR_SCENE_COMPUTE_STATE_COMPLETED_WITH_ERROR_MSFT:
            Log("Scene computation failed");
            // Compute a new scene at the end of the function.
            canComputeNewScene = true;
            break;
        default:
            Log("Invalid scene compute state: " + std::to_string(computeState));
            // Compute a new scene at the end of the function.
            canComputeNewScene = true;
            break;
        }

        if (canComputeNewScene)
        {
            // Compute a new scene.
            XrVisualMeshComputeLodInfoMSFT visualMeshComputeLodInfo;
            visualMeshComputeLodInfo.type = XrStructureType::XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT;
            visualMeshComputeLodInfo.next = NULL;
            visualMeshComputeLodInfo.lod = s_MeshComputeLod;
            std::vector<XrSceneComputeFeatureMSFT> sceneComputeFeatures = {XrSceneComputeFeatureMSFT::XR_SCENE_COMPUTE_FEATURE_VISUAL_MESH_MSFT};
            XrNewSceneComputeInfoMSFT newSceneComputeInfo;
            newSceneComputeInfo.type = XrStructureType::XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT;
            newSceneComputeInfo.next = &visualMeshComputeLodInfo;
            newSceneComputeInfo.requestedFeatureCount = (uint32_t) sceneComputeFeatures.size();
            newSceneComputeInfo.requestedFeatures = sceneComputeFeatures.data();
            newSceneComputeInfo.consistency = s_SceneComputeConsistency;
            newSceneComputeInfo.bounds.sphereCount = (uint32_t) s_SceneSphereBounds.size();
            newSceneComputeInfo.bounds.spheres = s_SceneSphereBounds.data();
            newSceneComputeInfo.bounds.boxCount =  (uint32_t) s_SceneOrientedBoxBounds.size();
            newSceneComputeInfo.bounds.boxes = s_SceneOrientedBoxBounds.data();
            newSceneComputeInfo.bounds.frustumCount = (uint32_t) s_SceneFrustumBounds.size();
            newSceneComputeInfo.bounds.frustums = s_SceneFrustumBounds.data();
            CheckResult(s_xrComputeNewSceneMSFT(s_SceneObserver, &newSceneComputeInfo), "xrComputeNewSceneMSFT");
        }

        // Allocate an output array and copy mesh infos to it.
        auto pMeshInfos = s_Meshing->MeshInfoAllocator_Allocate(allocator, s_MeshDataByMeshId.size());
        size_t meshInfoIndex = 0;
        for (auto&& pair : s_MeshDataByMeshId)
        {
            pMeshInfos[meshInfoIndex] = pair.second.m_UnityXRMeshInfo;
            ++meshInfoIndex;
        }

        return kUnitySubsystemErrorCodeSuccess;
    };
    meshProvider.AcquireMesh = [](UnitySubsystemHandle handle, void* userData, const UnityXRMeshId* meshId, UnityXRMeshDataAllocator* allocator) -> UnitySubsystemErrorCode {
        // Get mesh data from the input mesh id.
        MeshData* pMeshData = nullptr;
        try
        {
            pMeshData = &s_MeshDataByMeshId.at(*meshId);
        }
        catch(const std::exception& e)
        {
            Log("Mesh id not found: " + std::string(e.what()));
            return UnitySubsystemErrorCode::kUnitySubsystemErrorCodeFailure;
        }

        // Check if the shared OpenXR scene is not null.
        if (pMeshData->m_SharedOpenXRScene == nullptr)
        {
            // Mesh data with null shared scene implies that a mesh
            // of the mesh data has been acquired before.
            return UnitySubsystemErrorCode::kUnitySubsystemErrorCodeFailure;
        }

        // Stage 2: Get mesh buffers of the first scene visual mesh component.

        XrSceneMeshBuffersGetInfoMSFT sceneMeshBuffersGetInfo;
        sceneMeshBuffersGetInfo.type = XrStructureType::XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT;
        sceneMeshBuffersGetInfo.next = NULL;
        sceneMeshBuffersGetInfo.meshBufferId = pMeshData->m_OpenXRSceneMesh.meshBufferId;
        // Create buffers on the structure chain of XrSceneMeshBuffersMSFT.
        // Set input capacity to zero to get buffer capacity.
        XrSceneMeshBuffersMSFT sceneMeshBuffers;
        sceneMeshBuffers.type = XrStructureType::XR_TYPE_SCENE_MESH_BUFFERS_MSFT;
        XrSceneMeshVertexBufferMSFT sceneMeshVerticesBuffer;
        sceneMeshVerticesBuffer.type = XrStructureType::XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT;
        sceneMeshVerticesBuffer.vertexCapacityInput = 0;
        sceneMeshVerticesBuffer.vertices = NULL;
        XrSceneMeshIndicesUint32MSFT sceneMeshIndicesUint32Buffer;
        sceneMeshIndicesUint32Buffer.type = XrStructureType::XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT;
        sceneMeshIndicesUint32Buffer.indexCapacityInput = 0;
        sceneMeshIndicesUint32Buffer.indices = NULL;
        // Chain the structure instances.
        sceneMeshBuffers.next = &sceneMeshVerticesBuffer;
        sceneMeshVerticesBuffer.next = &sceneMeshIndicesUint32Buffer;
        sceneMeshIndicesUint32Buffer.next = NULL;
        // Call xrGetSceneMeshBuffersMSFT() to get buffer capacity.
        CheckResult(s_xrGetSceneMeshBuffersMSFT(pMeshData->m_SharedOpenXRScene->GetScene(),
            &sceneMeshBuffersGetInfo, &sceneMeshBuffers), "xrGetSceneMeshBuffersMSFT");
        // Create buffers by the capacity.
        std::vector<XrVector3f> vertices(sceneMeshVerticesBuffer.vertexCountOutput);
        std::vector<uint32_t> indices(sceneMeshIndicesUint32Buffer.indexCountOutput);
        sceneMeshVerticesBuffer.vertexCapacityInput = sceneMeshVerticesBuffer.vertexCountOutput;
        sceneMeshVerticesBuffer.vertices = vertices.data();
        sceneMeshIndicesUint32Buffer.indexCapacityInput = sceneMeshIndicesUint32Buffer.indexCountOutput;
        sceneMeshIndicesUint32Buffer.indices = indices.data();
        // Call xrGetSceneMeshBuffersMSFT() again to fill out buffers.
        CheckResult(s_xrGetSceneMeshBuffersMSFT(pMeshData->m_SharedOpenXRScene->GetScene(),
            &sceneMeshBuffersGetInfo, &sceneMeshBuffers), "xrGetSceneMeshBuffersMSFT");

        // After getting OpenXR mesh buffers, reset the shared pointer to the scene
        // to release the scene. The scene will be destroyed when no shared pointers
        // pointing to it.
        pMeshData->m_SharedOpenXRScene = nullptr;

        // Now the buffers are filled with mesh data.
        // Copy data to the Unity XR mesh descriptor.
        auto& verticesCount = sceneMeshVerticesBuffer.vertexCountOutput;
        auto& indicesCount = sceneMeshIndicesUint32Buffer.indexCountOutput;
        auto* meshDesc = s_Meshing->MeshDataAllocator_AllocateMesh(allocator,
            verticesCount, indicesCount, kUnityXRIndexFormat32Bit,
            (UnityXRMeshVertexAttributeFlags) 0,kUnityXRMeshTopologyTriangles);
        memcpy(meshDesc->positions, sceneMeshVerticesBuffer.vertices, verticesCount * sizeof(float) * 3);
        memcpy(meshDesc->indices32, sceneMeshIndicesUint32Buffer.indices, indicesCount * sizeof(uint32_t));

        // Convert meshes from right-handed to left-handed.
        for (size_t i = 0; i < verticesCount; ++i)
        {
            // Multiply the z value by -1.
            meshDesc->positions[i].z *= -1.0f;
        }
        for (size_t i = 0; i < indicesCount; i += 3)
        {
            // Swap the second and the third index in a triangle.
            std::swap(meshDesc->indices32[i + 1],  meshDesc->indices32[i + 2]);
        }
        return kUnitySubsystemErrorCodeSuccess;
    };
    meshProvider.ReleaseMesh = [](UnitySubsystemHandle handle, void* userData, const UnityXRMeshId* meshId, const UnityXRMeshDescriptor* mesh, void* pluginData) -> UnitySubsystemErrorCode {
        return kUnitySubsystemErrorCodeSuccess;
    };
    meshProvider.SetMeshDensity = [](UnitySubsystemHandle handle, void* userData, float density) -> UnitySubsystemErrorCode {
        return kUnitySubsystemErrorCodeSuccess;
    };
    meshProvider.SetBoundingVolume = [](UnitySubsystemHandle handle, void* userData, const UnityXRBoundingVolume* boundingVolume) -> UnitySubsystemErrorCode {
        return kUnitySubsystemErrorCodeSuccess;
    };
    s_Meshing->RegisterMeshProvider(handle, &meshProvider);
    return kUnitySubsystemErrorCodeSuccess;
};

Step 2: Define Start function for the subsystem.

meshingLifecycleHandler.Start = [](UnitySubsystemHandle handle, void* userData) -> UnitySubsystemErrorCode {
    XrSceneObserverCreateInfoMSFT sceneObserverCreateInfo;
    sceneObserverCreateInfo.type = XrStructureType::XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT;
    sceneObserverCreateInfo.next = NULL;
    CheckResult(s_xrCreateSceneObserverMSFT(s_XrSession, &sceneObserverCreateInfo, &s_SceneObserver), "xrCreateSceneObserverMSFT");

    return kUnitySubsystemErrorCodeSuccess;
};

Step 3: Define Stop function for the subsystem.

meshingLifecycleHandler.Stop = [](UnitySubsystemHandle handle, void* userData) -> void {
    // Clear mesh data.
    // All pointed shared OpenXR scenes are also destroyed.
    s_MeshDataByMeshId.clear();

    // Destroy the scene observer.
    CheckResult(s_xrDestroySceneObserverMSFT(s_SceneObserver), "xrDestroySceneObserverMSFT");
};

Step 4: Define Shutdown function for the subsystem.

meshingLifecycleHandler.Shutdown = [](UnitySubsystemHandle handle, void* userData) -> void {
    s_OpenXRReady = false;

    CheckResult(s_xrDestroySpace(s_XrSpace), "xrDestroySpace");
};

Define function to get OpenXR function pointers and create reference space.

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
SetOpenXRVariables(unsigned long long instance, unsigned long long session,
    void* xrEnumerateReferenceSpaces,
    void* xrCreateReferenceSpace,
    void* xrDestroySpace,
    void* xrEnumerateSceneComputeFeaturesMSFTptr,
    void* xrCreateSceneObserverMSFTptr,
    void* xrDestroySceneObserverMSFTptr,
    void* xrCreateSceneMSFTptr,
    void* xrDestroySceneMSFTptr,
    void* xrComputeNewSceneMSFTptr,
    void* xrGetSceneComputeStateMSFTptr,
    void* xrGetSceneComponentsMSFTptr,
    void* xrLocateSceneComponentsMSFTptr,
    void* xrGetSceneMeshBuffersMSFTptr)
{
    s_XrInstance = (XrInstance)instance;
    s_XrSession = (XrSession)session;
    
    s_xrEnumerateReferenceSpaces = (PFN_xrEnumerateReferenceSpaces)xrEnumerateReferenceSpaces;
    s_xrCreateReferenceSpace = (PFN_xrCreateReferenceSpace)xrCreateReferenceSpace;
    s_xrDestroySpace = (PFN_xrDestroySpace)xrDestroySpace;
    s_xrEnumerateSceneComputeFeaturesMSFT = (PFN_xrEnumerateSceneComputeFeaturesMSFT)xrEnumerateSceneComputeFeaturesMSFTptr;
    s_xrCreateSceneObserverMSFT = (PFN_xrCreateSceneObserverMSFT)xrCreateSceneObserverMSFTptr;
    s_xrDestroySceneObserverMSFT = (PFN_xrDestroySceneObserverMSFT)xrDestroySceneObserverMSFTptr;
    s_xrCreateSceneMSFT = (PFN_xrCreateSceneMSFT)xrCreateSceneMSFTptr;
    s_xrDestroySceneMSFT = (PFN_xrDestroySceneMSFT)xrDestroySceneMSFTptr;
    s_xrComputeNewSceneMSFT = (PFN_xrComputeNewSceneMSFT)xrComputeNewSceneMSFTptr;
    s_xrGetSceneComputeStateMSFT = (PFN_xrGetSceneComputeStateMSFT)xrGetSceneComputeStateMSFTptr;
    s_xrGetSceneComponentsMSFT = (PFN_xrGetSceneComponentsMSFT)xrGetSceneComponentsMSFTptr;
    s_xrLocateSceneComponentsMSFT = (PFN_xrLocateSceneComponentsMSFT)xrLocateSceneComponentsMSFTptr;
    s_xrGetSceneMeshBuffersMSFT = (PFN_xrGetSceneMeshBuffersMSFT)xrGetSceneMeshBuffersMSFTptr;
    s_XrInstance = (XrInstance) instance;
    s_XrSession = (XrSession) session;
 
    // Enumerate supported reference space types.
    std::vector<XrReferenceSpaceType> supportedReferenceSpaceTypes;
    uint32_t supportedReferenceSpaceTypeCount = 0;
    CheckResult(s_xrEnumerateReferenceSpaces(s_XrSession, 0, &supportedReferenceSpaceTypeCount,
        supportedReferenceSpaceTypes.data()), "xrEnumerateReferenceSpaces");
    supportedReferenceSpaceTypes.resize(supportedReferenceSpaceTypeCount);
    CheckResult(s_xrEnumerateReferenceSpaces(s_XrSession, (uint32_t) supportedReferenceSpaceTypes.size(), &supportedReferenceSpaceTypeCount,
        supportedReferenceSpaceTypes.data()), "xrEnumerateReferenceSpaces");
    // Get a supported reference space type. Prefer the stage space type.
    for (auto&& type : supportedReferenceSpaceTypes)
    {
        s_ReferenceSpaceType = type;
        if (type == XrReferenceSpaceType::XR_REFERENCE_SPACE_TYPE_STAGE)
        {
            break;
        }
    }
    // Create a reference space.
    XrReferenceSpaceCreateInfo referenceSpaceCreateInfo;
    referenceSpaceCreateInfo.type = XrStructureType::XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
    referenceSpaceCreateInfo.next = NULL;
    referenceSpaceCreateInfo.referenceSpaceType = s_ReferenceSpaceType;
    referenceSpaceCreateInfo.poseInReferenceSpace.orientation = {0, 0, 0, 1};
    referenceSpaceCreateInfo.poseInReferenceSpace.position = {0, 0, 0};
    CheckResult(s_xrCreateReferenceSpace(s_XrSession, &referenceSpaceCreateInfo, &s_XrSpace), "xrCreateReferenceSpace");
    s_OpenXRReady = true;
}


Define function to set a scene compute oriented box bound in a right-handed world space.

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
SetSceneComputeOrientedBoxBound(XrQuaternionf orientation, XrVector3f position, XrVector3f extents)
{
    s_SceneOrientedBoxBounds.resize(1);
    auto& bound = s_SceneOrientedBoxBounds[0];
    bound.pose.orientation = orientation;
    bound.pose.position = position;
    bound.extents = extents;
}