Wave MR Scene And Anchor Native Development Guide

Overview

To develop MR applications using Wave SDK, please refer to the following documents:

Contents

Note

  • You can download the Wave SDK from here. And check Native MR sample in SDK/samples/wvr_native_anchor.
  • To build the project, please reference Native SDK Getting Started.

1. Permission & Manifest

In order to use scene perception API, developer should add the feature in AndroidManifest.xml. If developer wants to use scene mesh API, permission is also needed.

//andoirManifest.xml
//scene perception
<uses-feature android:name="wave.feature.sceneperception" android:required="true"/>
//scene mesh
<uses-permission android:name="wave.permission.GET_SCENE_MESH"/>
//MainActivity.java
int hasPermission=checkSelfPermission(SCENE_PERMISSION);
if(hasPermission!=PackageManager.PERMISSION_GRANTED){
    Log.i(TAG, "Permission not granted try to aquire: "+SCENE_PERMISSION);
    requestPermissions(new String[]{SCENE_PERMISSION},REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode,  String[] permissions,  int[] grantResults) {
    if (requestCode == REQUEST_CODE && grantResults != null && grantResults.length > 0) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            mHasPermission = true;
            Log.i(TAG, "Permission granted");
        }
        else{
            Log.i(TAG, "Permission not granted");
        }
    }
}

2. Show passthrouh

Show Passthrough underlay by calling WVR_ShowPassthroughUnderlay() after WVR_Init().

static bool showUnderlay = true;
WVR_ShowPassthroughUnderlay(showUnderlay);
_images/passThrough.png

3. Anchor

3-1. Init

In order to use anchor API, developer should call WVR_StartScene() before calling any anchor API.

WVR_Result result = WVR_StartScene();
if (result != WVR_Success)
{
    LOGE("WVR_StartScene init fail %d", result);
    return false;
}

3-2. Spatial Anchors

Spatial anchor will be destroyed after closing app.

3-2-1. Create spatial anchors - WVR_CreateSpatialAnchor()

Application can create a spatial anchor by calling WVR_CreateSpatialAnchor(). The created spatial anchor will be recorded by the device service. After the device service records the added spatial anchor successfully, the device service will send WVR_EventType_SpatialAnchor_Changed event to WVR_Event_t. Note that the last char of WVR_SpatialAnchorCreateInfo.anchorName.name must be an ending char.

// this example anchor name should be "s/c/p=time=scale.x=scale.y=scale.z="
WVR_SpatialAnchorCreateInfo info;
info.originModel = WVR_PoseOriginModel_OriginOnHead;
info.pose = convertToWVRPose(pose);

// anchor name()
std::string dash("=");
// type tag
std::string spatialName;
switch (type)
{
case spatial:
    // represent spatial anchor
    spatialName = {"s"};
    break;
case cached:
    // represent cached anchor
    spatialName = {"c"};
    break;
case persisted:
    // represent persisted anchor
    spatialName = {"p"};
    break;
default:
    LOGE("createCubeAnchor type get error");
    break;
}
spatialName += dash;

// time tag
static int preventSameName = 0;
preventSameName = (preventSameName + 1) % 10; // this allow create 10 times in one second
uint64_t sec = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::string tempString = std::to_string(sec);
tempString = std::to_string(preventSameName) + tempString;
spatialName += tempString;
spatialName += dash;

// scale tag
spatialName += std::to_string(round(scale.x * 10000) / 10000);
spatialName += dash;
spatialName += std::to_string(round(scale.y * 10000) / 10000);
spatialName += dash;
spatialName += std::to_string(round(scale.z * 10000) / 10000);
spatialName += dash;

// add ending char
spatialName.push_back('\0');

std::copy(spatialName.begin(), spatialName.end(), info.anchorName.name);
LOGI("addAnchor , scale:(%f, %f, %f)", round(scale.x * 10000) / 10000, round(scale.y * 10000) / 10000, round(scale.z * 10000) / 10000);

// create anchor
WVR_SpatialAnchor anchor;
LOGI("create spatial anchor name(%d)=%s", spatialName.size(), spatialName.c_str());
switch (type)
{
case spatial:
    // create spatical anchor
    if (WVR_CreateSpatialAnchor(&info, &anchor) != WVR_Success)
    {
        LOGE("create spatial anchor fail");
    }
    break;
case cached:
    if (WVR_CreateSpatialAnchor(&info, &anchor) != WVR_Success)
    {
        LOGE("create spatial anchor fail");
        return false;
    }
    // create cached anchor
    WVR_SpatialAnchorCacheInfo cacheInfo;
    cacheInfo.spatialAnchor = anchor;
    cacheInfo.cachedSpatialAnchorName = info.anchorName;
    LOGI("create cached anchor");
    if (WVR_CacheSpatialAnchor(&cacheInfo) != WVR_Success)
        LOGE("create cached anchor fail");

    /*not necessary on every app*/
    // destroy spatial anchor. In this sample, we will refrsh all spatial anchor by
    // using WVR_EnumerateCachedSpatialAnchorNames and WVR_CreateSpatialAnchorFromCacheName
    WVR_DestroySpatialAnchor(anchor);
    break;
case persisted:
    // check if persisted anchor is full
    WVR_PersistedSpatialAnchorCountGetInfo countInfo;
    WVR_GetPersistedSpatialAnchorCount(&countInfo);
    if (countInfo.currentTrackingCount < countInfo.maximumTrackingCount)
    {
        if (WVR_CreateSpatialAnchor(&info, &anchor) != WVR_Success)
        {
            LOGE("create spatial anchor fail");
            return false;
        }
        // create persisted anchor
        WVR_SpatialAnchorPersistInfo persistInfo;
        persistInfo.spatialAnchor = anchor;
        persistInfo.persistedSpatialAnchorName = info.anchorName;
        LOGI("create persisted anchor");
        if (WVR_PersistSpatialAnchor(&persistInfo) != WVR_Success)
            LOGE("create persisted anchor fail");

        /*not necessary on every app*/
        // destroy spatial anchor. In this sample, we will refrsh all spatial anchor by
        // using WVR_EnumerateCachedSpatialAnchorNames and WVR_CreateSpatialAnchorFromCacheName
        WVR_DestroySpatialAnchor(anchor);
    }
    else
    {
        LOGE("persist anchor is full");
    }
    break;
default:
    LOGE("type get error");
    break;
}

3-2-2. Get the existing spatial anchor - WVR_EnumerateSpatialAnchors()

Application should call WVR_EnumerateSpatialAnchors() twice to get the number of the existing spatial anchors and WVR_SpatialAnchor object. Application should implement this process in onCreate and onResume to get WVR_EventType_SpatialAnchor_Changed event.

WVR_SpatialAnchor *allSpatialAnchor;
uint32_t allSpatialAnchorCount;

while (WVR_PollEventQueue(&event))
{
    ...
    // anchor event
    if (event.common.type == WVR_EventType_SpatialAnchor_Changed)
    {
        LOGI("receive WVR_EventType_SpatialAnchor_Changed event");
        mAnchorManager->getExistedSpatialAnchor(mCubes);
    }
    ...
}

bool anchorManager::getExistedSpatialAnchor(CubeManager *cm)
{
    // WVR API get all spatial anchor num and allocate memory to get data
    allSpatialAnchorCount = 0;
    WVR_EnumerateSpatialAnchors(0, &allSpatialAnchorCount, nullptr);
    LOGI("get all spatial anchor anchorCount=%u", allSpatialAnchorCount);

    // clear memory
    if (allSpatialAnchor != nullptr)
        free(allSpatialAnchor);
    cm->clearAllTargetCubes();

    // assign memory space for spatial anchor
    allSpatialAnchor = (WVR_SpatialAnchor *)malloc(sizeof(WVR_SpatialAnchor) * allSpatialAnchorCount);
    WVR_EnumerateSpatialAnchors(allSpatialAnchorCount, &allSpatialAnchorCount, allSpatialAnchor);

    // classified different spatial anchors
    for (int a = 0; a < allSpatialAnchorCount; a++)
    {
        Matrix4 pose;
        Vector3 scale;
        WVR_SpatialAnchorState anchorState;
        WVR_GetSpatialAnchorState(allSpatialAnchor[a], WVR_PoseOriginModel_OriginOnHead, &anchorState);
        std::string mName(anchorState.anchorName.name);

        switch (mName[0])
        {
        // pure spatial anchor
        case 's':
            pose = convertToMatrix4(anchorState.pose);
            scale = getScaleFromName(mName);
            cm->addCubeFromAnchor(spatial, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
            break;
        // spatial anchor which has been cached
        case 'c':
            pose = convertToMatrix4(anchorState.pose);
            scale = getScaleFromName(mName);
            cm->addCubeFromAnchor(cached, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
            break;
        // spatial anchor which has been persisted
        case 'p':
            pose = convertToMatrix4(anchorState.pose);
            scale = getScaleFromName(mName);
            cm->addCubeFromAnchor(persisted, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
            break;
        default:
            LOGE("Wrong anchor name exist: %s", mName.c_str());
            break;
        }
    }
    return true;
}

3-2-3. Get the existing spatial anchor data - WVR_GetSpatialAnchorState()

Application can send WVR_SpatialAnchor object to WVR_GetSpatialAnchorState() to update the anchor status which includes pose, name and tracking state. If the anchor’s trackingState is not equal to WVR_SpatialAnchorTrackingState_Tracking, developer should not use this anchor pose to update model pose.

// WVR API get all spatial anchor num and allocate memory to get data
allSpatialAnchorCount = 0;
WVR_EnumerateSpatialAnchors(0, &allSpatialAnchorCount, nullptr);
LOGI("get all spatial anchor anchorCount=%u", allSpatialAnchorCount);

// clear memory
if (allSpatialAnchor != nullptr)
    free(allSpatialAnchor);
cm->clearAllTargetCubes();

// assign memory space for anchor
allSpatialAnchor = (WVR_SpatialAnchor *)malloc(sizeof(WVR_SpatialAnchor) * allSpatialAnchorCount);
WVR_EnumerateSpatialAnchors(allSpatialAnchorCount, &allSpatialAnchorCount, allSpatialAnchor);

// classified different spatial anchors
for (int a = 0; a < allSpatialAnchorCount; a++)
{
    Matrix4 pose;
    Vector3 scale;
    WVR_SpatialAnchorState anchorState;
    WVR_GetSpatialAnchorState(allSpatialAnchor[a], WVR_PoseOriginModel_OriginOnHead, &anchorState);
    std::string mName(anchorState.anchorName.name);

    switch (mName[0])
    {
    // update spatial anchor pose to render
    case 's':
        pose = convertToMatrix4(anchorState.pose);
        scale = getScaleFromName(mName);
        cm->addCubeFromAnchor(spatial, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
        break;
    // update catched anchor pose to render
    case 'c':
        pose = convertToMatrix4(anchorState.pose);
        scale = getScaleFromName(mName);
        cm->addCubeFromAnchor(cached, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
        break;
    // update persisted anchor pose to render
    case 'p':
        pose = convertToMatrix4(anchorState.pose);
        scale = getScaleFromName(mName);
        cm->addCubeFromAnchor(persisted, a, pose, scale, anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking ? false : true);
        break;
    default:
        LOGE("Wrong anchor name exist: %s", mName.c_str());
        break;
    }
}

3-2-4. Delete spatial anchor - WVR_DestroySpatialAnchor()

Application can delete spatial anchors by calling WVR_DestroySpatialAnchor(). Note that the device service sends WVR_EventType_SpatialAnchor_Changed after removing this spatial anchor.

// delete spatial anchor
WVR_DestroySpatialAnchor(*(allSpatialAnchor + cm->leftTouchVector[a].anchorIndex))
// will get WVR_EventType_SpatialAnchor_Changed event soon

3-3. Cached Anchors

Cached anchor will be destroyed when device can not recognize the surrounding area.

3-3-1. Cache spatial anchor - WVR_CacheSpatialAnchor()

Application can cache spatial anchor by calling WVR_CacheSpatialAnchor(). After the device service records a anchor is cached successfully, it will send WVR_EventType_CachedAnchor_Changed event to WVR_Event_t. Note that, in order to cache anchor successfully, WVR_SpatialAnchorCacheInfo.cachedSpatialAnchorName must be a unique name.

code as 3-2-1

3-3-2. Get the existing cached anchor - WVR_EnumerateCachedSpatialAnchorNames()

Application should call WVR_EnumerateCachedSpatialAnchorNames() twice to get the number of the cached anchors and their names. Application should implement this process in onCreate and onResume to get WVR_EventType_CachedAnchor_Changed event.

uint32_t cachedAnchorCount;

while (WVR_PollEventQueue(&event))
{
    ...
    // anchor event
    if (event.common.type == WVR_EventType_CachedAnchor_Changed)
    {
        LOGI("receive WVR_EventType_CachedAnchor_Changed event");
        mAnchorManager->getExistedCachedAnchor();
    }
    ...
}

bool anchorManager::getExistedCachedAnchor()
{
    // get cache anchor name
    cachedAnchorCount = 0;
    WVR_EnumerateCachedSpatialAnchorNames(0, &cachedAnchorCount, nullptr);
    LOGI("Enumerate cached count=%d", cachedAnchorCount);
    WVR_SpatialAnchorName *allCachedName;
    allCachedName = (WVR_SpatialAnchorName *)malloc(sizeof(WVR_SpatialAnchorName) * cachedAnchorCount);
    WVR_EnumerateCachedSpatialAnchorNames(cachedAnchorCount, &cachedAnchorCount, allCachedName);
    // Let all cached anchor as spatial anchor
    WVR_SpatialAnchorFromCacheNameCreateInfo cacheInfo;
    for (int a = 0; a < cachedAnchorCount; a++)
    {
        cacheInfo.cachedSpatialAnchorName = allCachedName[a];
        cacheInfo.spatialAnchorName = allCachedName[a];
        WVR_SpatialAnchor *ptr; // will get this anchor address later(via WVR_EnumerateSpatialAnchors)
        LOGI("create spatial anchor from cached anchor");
        if (WVR_CreateSpatialAnchorFromCacheName(&cacheInfo, ptr) != WVR_Success)
            LOGI("already have same cached anchor as spatial anchor");
    }
    free(allCachedName);
    return true;
}

3-3-3. Create/restore the spatial anchor by the cached name - WVR_CreateSpatialAnchorFromCacheName()

Application can also use cached anchor name to create and restore the spatial anchor. After device service records added spatial anchor successfully, it will send WVR_EventType_SpatialAnchor_Changed event to WVR_Event_t.

code as 3-3-2

3-3-4. uncache cached anchor - WVR_UncacheSpatialAnchor()/WVR_ClearCachedSpatialAnchors()

Application can call WVR_UncacheSpatialAnchor() to uncache anchor, or call WVR_ClearCachedSpatialAnchors() to uncache all cached anchor in the device service. After the device service records a cached anchor is uncached successfully, it will send WVR_EventType_CachedAnchor_Changed event to WVR_Event_t.

...
/*uncache one cached anchor*/
WVR_SpatialAnchorState anchorState;
WVR_GetSpatialAnchorState(allSpatialAnchor[cm->leftTouchVector[a].anchorIndex], WVR_PoseOriginModel_OriginOnHead, &anchorState);
if (type == cached)
{
    // in this sample, cachedSpatialAnchorName equal to spatialAnchorName
    // Therefore, we can uncache anchor via spatialAnchorName
    WVR_UncacheSpatialAnchor(&anchorState.anchorName);
}
...

/*uncache all cached anchor*/
WVR_ClearCachedSpatialAnchors()

3-3-5. Get the existing cached anchor data - WVR_GetSpatialAnchorState()

In order to get cached anchor pose, application should update its status.

code as 3-2-3

3-4. Persisted Anchors

Persisted anchor can be shared betweem devices.

3-4-1. Persisted spatial anchor - WVR_PersistSpatialAnchor()

Application should create a persisted spatial anchor by calling WVR_PersistSpatialAnchor() after checking the maximum number by WVR_PersistedSpatialAnchorCountGetInfo.maximumTrackingCount. After the device service records a persisted anchor is added successfully, it will send WVR_EventType_PersistedAnchor_Changed event to WVR_Event_t. Note that to create a persisted anchor successfully, WVR_SpatialAnchorPersistInfo.persistedSpatialAnchorName should be a unique name.

code as 3-2-1

3-4-2. Get the existing persisted anchor - WVR_EnumeratePersistedSpatialAnchorNames()

Application should call WVR_EnumeratePersistedSpatialAnchorNames() twice to get the number of the persisted anchors and their name. Application should implement this process in onCreate and onResume to get WVR_EventType_PersistedAnchor_Changed event.

uint32_t cachedAnchorCount;

while (WVR_PollEventQueue(&event))
{
    ...
    // anchor event
    if (event.common.type == WVR_EventType_PersistedAnchor_Changed)
    {
        LOGI("receive WVR_EventType_PersistedAnchor_Changed event");
        mAnchorManager->getExistedPersistedAnchor();
    }
    ...
}

bool anchorManager::getExistedPersistedAnchor()
{
    // get persisted anchor name
    persistedAnchorCount = 0;
    WVR_EnumeratePersistedSpatialAnchorNames(0, &persistedAnchorCount, nullptr);
    LOGI("Enumerate persisted count=%d", persistedAnchorCount);
    WVR_SpatialAnchorName *allPersistedName;
    allPersistedName = (WVR_SpatialAnchorName *)malloc(sizeof(WVR_SpatialAnchorName) * persistedAnchorCount);
    WVR_EnumeratePersistedSpatialAnchorNames(persistedAnchorCount, &persistedAnchorCount, allPersistedName);
    // restore spatial anchor via persisted anchor name
    WVR_SpatialAnchorFromPersistenceNameCreateInfo persistInfo;
    for (int a = 0; a < persistedAnchorCount; a++)
    {
        persistInfo.persistedSpatialAnchorName = allPersistedName[a];
        persistInfo.spatialAnchorName = allPersistedName[a];
        WVR_SpatialAnchor *ptr; // will get this anchor address later(via WVR_EnumerateSpatialAnchors)
        LOGI("create spatial anchor from persisted anchor");
        if (WVR_CreateSpatialAnchorFromPersistenceName(&persistInfo, ptr) != WVR_Success)
            LOGI("already have same persisted anchor as spatial anchor");
    }
    free(allPersistedName);
    return true;
}

3-4-3. Create/restore the spatial anchor by persisted anchor name - WVR_CreateSpatialAnchorFromPersistenceName()

Application can also use persisted anchor name to create or restore the spatial anchor. After the device service records a spatial anchor is added successfully, it will send WVR_EventType_SpatialAnchor_Changed event to WVR_Event_t.

code as 3-4-2

3-4-4. Unpersist persisted anchor - WVR_UnpersistSpatialAnchor()/WVR_ClearPersistedSpatialAnchors()

Application can call WVR_UnpersistSpatialAnchor() to unpersist one persisted anchor, or call WVR_ClearPersistedSpatialAnchors() to unpersist all existing persisted anchors in the device service. After the device service records an unpersist anchor is cached successfully, it will send WVR_EventType_PersistedAnchor_Changed event to WVR_Event_t.

...
/*uncache one cached anchor*/
WVR_SpatialAnchorState anchorState;
WVR_GetSpatialAnchorState(allSpatialAnchor[cm->leftTouchVector[a].anchorIndex], WVR_PoseOriginModel_OriginOnHead, &anchorState);
if (type == persisted)
{
    // in this sample, persistedSpatialAnchorName equal to spatialAnchorName
    // Therefore, we can unpersist anchor via spatialAnchorName
    WVR_UnpersistSpatialAnchor(&anchorState.anchorName);
}

...
/*unpersist all persisted anchor*/
WVR_ClearPersistedSpatialAnchors()

3-4-5. Get the existing persisted anchor data - WVR_GetSpatialAnchorState()

In order to get persisted anchor pose, application should update its status.

code as 3-2-3

3-4-6. Export the persisted anchor data - WVR_ExportPersistedSpatialAnchor()

Persisted anchor can be shared between different devices. In order to share persisted anchor data, application should call WVR_ExportPersistedSpatialAnchor() twice to get the size and data of the persisted anchor. Note that this API is always called in the background thread because it may block the thread for a log time.

// do this work in background thread
if (taskPtr->workType == EXPORT_ANCHOR)
{
    LOGI("BGThread EXPORT_ANCHOR...");
    if (taskPtr->persistData != nullptr)
    {
        free(taskPtr->persistData);
        taskPtr->persistDataSize = 0;
    }

    if (WVR_ExportPersistedSpatialAnchor(&taskPtr->persistedName, 0, &taskPtr->persistDataSize, nullptr) == WVR_Success)
    {
        LOGI("export persisted anchor persistDataSize=%d", taskPtr->persistDataSize);
        taskPtr->persistData = (char *)malloc(sizeof(char) * taskPtr->persistDataSize);
    }

    if (WVR_ExportPersistedSpatialAnchor(&taskPtr->persistedName, taskPtr->persistDataSize, &taskPtr->persistDataSize, taskPtr->persistData) != WVR_Success)
    {
        LOGE("export persisted anchor fail");
        free(taskPtr->persistData);
        taskPtr->persistDataSize = 0;
    }
    else
    {
        // store persisted anchor in device. Can push this file to other device
        std::ofstream binFile("/data/user/0/com.htc.vr.samples.wvr_native_anchor/files/export.bin", std::ios::out | std::ios::binary);
        binFile.write(taskPtr->persistData, taskPtr->persistDataSize);
        binFile.close();
    }
    LOGI("BGThread EXPORT_ANCHOR done");
}

3-4-7. Import persisted anchor data - WVR_ImportPersistedSpatialAnchor()

Persisted anchor can be shared between different devices. In order to get the persisted anchors from another devices, application should call WVR_ImportPersistedSpatialAnchor() to import the persisted anchors. After the device service records a persisted anchor is imported successfully, it send WVR_EventType_PersistedAnchor_Changed event to WVR_Event_t. Note that this API is always be called in background thread because it may block the thread for quite a log time.

if (taskPtr->workType == IMPORT_ANCHOR)
{
    LOGI("BGThread IMPORT_ANCHOR...");
    // open persisted anchor in device. This file is pushed by other device
    std::ifstream importFile("/data/user/0/com.htc.vr.samples.wvr_native_anchor/files/import.bin", std::ios::in);
    if (importFile.is_open())
    {
        LOGI("import anchor file exist, replace import data");
        if (taskPtr->persistData != nullptr)
        {
            free(taskPtr->persistData);
            taskPtr->persistDataSize = 0;
        }

        importFile.seekg(0, std::ifstream::end);
        taskPtr->persistDataSize = importFile.tellg();
        LOGI("import persisted anchor Size=%d", taskPtr->persistDataSize);
        importFile.seekg(0, std::ifstream::beg);
        taskPtr->persistData = (char *)malloc(sizeof(char) * taskPtr->persistDataSize);
        importFile.read(taskPtr->persistData, taskPtr->persistDataSize);
        importFile.close();
    }

    if (taskPtr->persistDataSize > 0)
    {
        WVR_ImportPersistedSpatialAnchor(taskPtr->persistDataSize, taskPtr->persistData);
    }
    else
    {
        LOGW("no persisted data can be import");
    }
    LOGI("BGThread IMPORT_ANCHOR done");
}

3-5. Render anchor object

In order to render a model with anchor, developer should use the anchor’s pose as the model pose matrix and sent it to OpenGL shader program.

//update anchor pose
WVR_GetSpatialAnchorState(mAnchor[cm->cubeVector[a]->anchorIndex], WVR_PoseOriginModel_OriginOnHead, &anchorState);
if (anchorState.trackingState == WVR_SpatialAnchorTrackingState_Tracking)
{
    allAnchorCanTrack = true;
    pose = convertToMatrix4(anchorState.pose);
    cm->cubeVector[a]->mPose = pose;
    cm->cubeVector[a]->anchorLost = false;
}
else
{
    cm->cubeVector[a]->anchorLost = true;
    LOGI("Anchor cubeVector(%d) lost tracking", a);
}

//render
Matrix4 matrix = projection * eye * view * cubeVector[a]->mPose;
glUniformMatrix4fv(mMatrixLocation, 1, false, matrix.get());
glUniform3f(mScale, cubeVector[a]->mScale.x, cubeVector[a]->mScale.y, cubeVector[a]->mScale.z);

mVAO->bindVAO();
glActiveTexture(GL_TEXTURE0);
mTexture->bindTexture();
glDrawElements(GL_TRIANGLES, singleIndexArray.size(), GL_UNSIGNED_INT, 0);
mTexture->unbindTexture();
mVAO->unbindVAO();
_images/renderAnchor.png

4. Scene Plane & Plane Mesh

4-1. Init

In order to use plane API, developer should call WVR_StartScene() and WVR_StartScenePerception() before calling any plane API.

// Loading the WVR scene
WVR_Result result = WVR_StartScene();
if (result != WVR_Success)
{
    LOGE("WVR_StartScene init fail %d", result);
    return false;
}

// start perception before get scene plane (need permission)
// can call WVR_StopScenePerception when you won't need to update data
// WVR_StopScenePerception will be automatic called when app is closed
WVR_Result startPerceptionResult_2d = WVR_StartScenePerception(WVR_ScenePerceptionTarget_2dPlane);
if (startPerceptionResult_2d == WVR_Error_PermissionNotGranted)
{
    LOGE("Permission not granted. Need permission to start scene perception");
    perceptionPermissionGrant = false;
}
else{
    perceptionPermissionGrant = true;
}

if (WVR_Success != startPerceptionResult_2d)
    LOGE("WVR_StartScenePerception init fail");

4-2. Get the Plane data - WVR_GetScenePlanes()

Application should call WVR_GetScenePlanes() twice in each frame to get the number of the planes and planes data.

// WVR API get scene num and allocate memory to get data
WVR_GetScenePlanes(nullptr, 0, &planeCount, WVR_PoseOriginModel_OriginOnHead, nullptr);
mPlane = (WVR_ScenePlane *)malloc(sizeof(WVR_ScenePlane) * planeCount);
WVR_GetScenePlanes(nullptr, planeCount, &planeCount, WVR_PoseOriginModel_OriginOnHead, mPlane);
//get planes data
planesVector.push_back(Plane{convertToMatrix4(mPlane[a].pose), Vector3(mPlane[a].extent.width, mPlane[a].extent.height),1});

4-3. Render Plane object

Application can use the pose and extent to the render rectangles.

glUniform4f(mColor, 0.0f, 0.8f, 0.0f, 1.0f);
matrix.push_back(projection * eye * view * planesVector[a].mPose);
glUniformMatrix4fv(mMatrixLocation, 1, false, matrix[a].get());
glUniform3f(mScale, planesVector[a].mScale.x, planesVector[a].mScale.y, planesVector[a].mScale.z);//scale is define by plane extent
mVAO->bindVAO();
glDrawElements(GL_TRIANGLES, singleIndexArray.size(), GL_UNSIGNED_INT, 0);
mVAO->unbindVAO();
_images/renderPlane.png

4-4. Get plane mesh data - WVR_GetSceneMeshBuffer()

Plane mesh vertex and element index can be got via sending meshBufferId to WVR_GetSceneMeshBuffer.

WVR_SceneMeshBuffer buffer0 = {0, 0, nullptr, 0, 0, nullptr};

// get sceneMesh size
if (WVR_GetSceneMeshBuffer(mPlane[planes[b].scenePlaneIndex].meshBufferId, WVR_PoseOriginModel_OriginOnHead, &buffer0) != WVR_Success)
{
    LOGE("Fail to get plane sceneMesh size");
    sceneMesh2D->clearAllData();
}
else
    LOGI("Get sceneMesh index size=%d", buffer0.indexCountOutput);

std::vector<WVR_Vector3f> vertexBuffer(buffer0.vertexCountOutput);
std::vector<uint32_t> indexBuffer(buffer0.indexCountOutput);
WVR_SceneMeshBuffer buffer1 = {buffer0.vertexCountOutput, buffer0.vertexCountOutput, vertexBuffer.data(), buffer0.indexCountOutput, buffer0.indexCountOutput, indexBuffer.data()};

// get sceneMesh data
if (WVR_GetSceneMeshBuffer(mPlane[planes[b].scenePlaneIndex].meshBufferId, WVR_PoseOriginModel_OriginOnHead, &buffer1) != WVR_Success)
{
    LOGE("Fail to get sceneMesh data");
    sceneMesh2D->clearAllData();
}
sceneMesh2D->mMeshIndex.push_back(indexBuffer);
sceneMesh2D->mMeshVertex.push_back(vertexBuffer);

4-5. Render plane mesh data

Developer can simply render plane meshes by the vertex and the index with glDrawElement().

//populate vertex buffer
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * mMeshVertex[a].size()*3, mMeshVertex[a].data() , GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0);

// populate the index buffer
mVAO->bindElementArrayBuffer();
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * mMeshIndex[a].size(), mMeshIndex[a].data(), GL_STATIC_DRAW);

Matrix4 matrix = projection * eye * view;
glUniformMatrix4fv(mMatrixLocation, 1, false, matrix.get());
mVAO->bindVAO();
glDrawElements(GL_TRIANGLES, index[a].size(), GL_UNSIGNED_INT, 0);
mVAO->unbindVAO();
_images/renderPlaneMesh.png

5. Collider Mesh & Visual Mesh

5-1. Init

In order to use mesh API, developer should call WVR_StartScene() and WVR_StartScenePerception() before calling any mesh APIs.

// Loading the WVR scene
WVR_Result result = WVR_StartScene();
if (result != WVR_Success)
{
    LOGE("WVR_StartScene init fail %d", result);
    return false;
}

// start perception before get scene mesh (need permission)
// can call WVR_StopScenePerception when you won't need to update data
// WVR_StopScenePerception will be automatic called when app is closed
WVR_Result startPerceptionResult_3d = WVR_StartScenePerception(WVR_ScenePerceptionTarget_SceneMesh);
if (startPerceptionResult_3d == WVR_Error_PermissionNotGranted)
{
    LOGE("Permission not granted. Need permission to start scene perception");
    perceptionPermissionGrant = false;
}
else{
    perceptionPermissionGrant = true;
}

if (WVR_Success != startPerceptionResult_3d)
    LOGE("WVR_StartScenePerception init fail");

5-2. Check the scene perception state - WVR_GetScenePerceptionState()

Application should check if HMD device can recognize the corresponding mesh. If the state is not equal to WVR_ScenePerceptionState_Complete, application should not use WVR_GetSceneMeshBuffer() to get the mesh data.

WVR_GetScenePerceptionState(WVR_ScenePerceptionTarget_SceneMesh,&spstate);
if(spstate==WVR_ScenePerceptionState_Completed){
    // get mesh data
    ...
}
else
{
    LOGI("Scene perception not complete");
    mSceneMeshBufferVisual_index.clear();
    mSceneMeshBufferVisual_vertex.clear();
}

5-3. Get the existing collider/visual meshBufferId - WVR_GetSceneMeshes()

Application should call WVR_GetSceneMeshes() to get the newest existing meshBufferId before calling WVR_GetSceneMeshBuffer(). If any scene mesh vertex is changed, all meshBufferId will be changed as well.

//ColliderMesh
uint32_t  count_mesh;
//get number of sceneMeshes
if(WVR_GetSceneMeshes(WVR_SceneMeshType_ColliderMesh, 0, &count_mesh, nullptr)!=WVR_Success)
    LOGE("Fail to get sceneMeshes count");
else
    LOGI("SceneMesh ID size=%d", count_mesh);

//Get sceneMeshes ID
std::vector<WVR_SceneMesh> meshes(count_mesh);
if(WVR_GetSceneMeshes(WVR_SceneMeshType_ColliderMesh, count_mesh, &count_mesh, meshes.data())!=WVR_Success)
    LOGE("Fail to get sceneMeshes ID");

//visualMesh
if(WVR_GetSceneMeshes(WVR_SceneMeshType_VisualMesh, 0, &count_mesh, nullptr)!=WVR_Success)
/*same as collider mesh...*/

5-4. Get the collider/visual mesh data - WVR_GetSceneMeshBuffer ()

Application could use meshBufferId and WVR_GetSceneMeshBuffer() to get the mesh vertex and element index. Owing to performance concerns, we strongly recommend only using WVR_GetSceneMeshBuffer() when the scene mesh vertex is changed.

//only update data when any vertex had changed
//If any mesh data had changed, all buffer id get from WVR_GetSceneMeshes() would be change
if(lastVisualMeshID!=meshes[0].meshBufferId){
    LOGI("visual sceneMesh data should be updated");
    WVR_SceneMeshBuffer buffer0={0, 0, nullptr, 0, 0, nullptr};
    //get sceneMesh size
    if(WVR_GetSceneMeshBuffer(meshes[a].meshBufferId, WVR_PoseOriginModel_OriginOnHead, &buffer0)!=WVR_Success){
        LOGE("Fail to get visual sceneMesh size");
        mSceneMeshBufferVisual_index.clear();
        mSceneMeshBufferVisual_vertex.clear();
        lastVisualMeshID=-1;
        return;
    }
    else
        LOGI("Get sceneMesh index size=%d", buffer0.indexCountOutput);

    std::vector<WVR_Vector3f> vertexBuffer(buffer0.vertexCountOutput);
    std::vector<uint32_t> indexBuffer(buffer0.indexCountOutput);
    WVR_SceneMeshBuffer buffer1 = {buffer0.vertexCountOutput, buffer0.vertexCountOutput,vertexBuffer.data(), buffer0.indexCountOutput, buffer0.indexCountOutput, indexBuffer.data()};

    //get sceneMesh data
    if(WVR_GetSceneMeshBuffer(meshes[a].meshBufferId, WVR_PoseOriginModel_OriginOnHead, &buffer1)!=WVR_Success){
        LOGE("Fail to get sceneMesh data");
        mSceneMeshBufferVisual_index.clear();
        mSceneMeshBufferVisual_vertex.clear();
        lastVisualMeshID=-1;
        return;
    }
    mSceneMeshBufferVisual_index.push_back(indexBuffer);
    mSceneMeshBufferVisual_vertex.push_back(vertexBuffer);
}

5-5. Render collider/visual mesh

Developer can render 3D mesh simply by vertex and index with glDrawElement(). In addition, developer can also modify the vertex and index to create a better visual effect. Owing to the large vertex and element data, we strongly recommend not to push the vertex to GPU in each frame. Please push the vertex to GPU when the the vertex data is changed.

//Modify color
Vector3 selectColor(int num){
    Vector3 color;
    switch (num) {
    case 1:
        color.x = 238.0 / 255;
        color.y = 132.0 / 255;
        color.z = 117.0 / 255;
        break;
    /*skip ...*/
    case 10:
        color.x = 157.0 / 255;
        color.y = 28.0 / 255;
        color.z = 200.0 / 255;
        break;
    }
    return color;
}
//modify vertex and element for better visual effect
void getOrderMesh(){
    int count;
    Vector3 color;
    mMeshOrder.clear();
    std::vector<float> buffer;
    for (int a = 0; a < mMeshIndex.size(); a++) {
        count = 0;
        buffer.clear();
        for (int b = 0; b < mMeshIndex[a].size(); b = b + 3){
            count = count % 10 + 1;
            color = selectColor(count);
            buffer.push_back(mMeshVertex[a][mMeshIndex[a][b]].v[0]);
            buffer.push_back(mMeshVertex[a][mMeshIndex[a][b]].v[1]);
            buffer.push_back(mMeshVertex[a][mMeshIndex[a][b]].v[2]);
            buffer.push_back(color.x);
            buffer.push_back(color.y);
            buffer.push_back(color.z);
        }
        mMeshOrder.push_back(buffer);
    }
}
// push data to GPU (only when the vertice were changed)
mVAO->bindVAO();
mVAO->bindArrayBuffer();
int stride = 6 * sizeof(float);
GLuint offset = 0;
float *ptr = (float *)meshData->mMeshOrder[a].data();
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * meshData->mMeshOrder[a].size(), ptr, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); // populate vertex
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, (const void *)offset);
offset += sizeof(float) * 3;
glEnableVertexAttribArray(1); // populate color
glVertexAttribPointer(1, 3, GL_FLOAT, false, stride, (const void *)offset);
mVAO->unbindVAO();
mVAO->unbindArrayBuffer();

// render
meshData->mVertexSize.push_back(meshData->mMeshOrder[a].size() / 6);
LOGI("render %d vertex", meshData->mVertexSize[a]);
glUniformMatrix4fv(mMatrixLocation, 1, false, matrix.get());
mVAO->bindVAO();
glDrawArrays(GL_TRIANGLES, 0, meshData->mVertexSize[a]);
mVAO->unbindVAO();
  • Render Visual Mesh
_images/visualMesh.png
  • Render Collider Mesh
_images/colliderMesh.png

6. Others

While developer use the last section features, developer should align WVR_PoseOriginModel in all WAVE API. If nothing is incorrect, developer can obtain the MR content with the correct position as below.

_images/renderAll.png