找回密碼
 注冊帳號

掃一掃,訪問微社區

AR醬 Magic Leap開發指南(8)-- 眼球追蹤(Lumin Runtime)

1
回復
678
查看
打印 上一主題 下一主題
[ 復制鏈接 ]
排名
2299
昨日變化

34

主題

262

帖子

1514

積分

Rank: 9Rank: 9Rank: 9

UID
156756
好友
11
蠻牛幣
998
威望
0
注冊時間
2016-7-13
在線時間
527 小時
最后登錄
2019-12-2

專欄作家

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

x
本帖最后由 geekli 于 2019-8-29 21:35 編輯

上一篇教程Magic Leap開發指南(7)-- 眼球追蹤(Unity))我們了解了如何在Unity中使用Eye Tracking來完成一些小項目,這篇我們繼續通過Lumin Runtime來運用眼球追蹤功能。

實現目標:
  • 使用Eye Tracking API檢測用戶的注視位置。
  • 控制兩個數字對象的位置(兩個字母:'A'和'B')。
  • 使用簡單的控制按鈕點擊鎖定它們的位置。
  • 計算并向用戶顯示點A和點B之間的距離。


實現效果:



Step 1: 在Lumin Runtime Editor設置項目

1.從Package Manager啟動 Lumin Runtime Editor。
2.在Lumin Runtime Editor中創建一個新項目。
3.命名為EyeTracking,然后單擊Create Project。如下圖:

4.在場景層次結構下,右鍵單擊根節點。
5.點擊Insert > Core > Text。

6.將Text2d的名稱更改為A并應用以下設置。

7.在場景層次結構下,右鍵單擊A節點,然后單擊Duplicate。

8.更新Text2d節點的Id和文本。

9.在場景層次結構下,再次右鍵單擊A節點,然后單擊Duplicate。
10.更新Text2d節點的Id、文本和顏色用于顯示A和B之間距離。

保存Scene。

場景示例如下圖:


Step 2:在Visual Studio (Windows)設置項目

1.啟動Microsoft Visual Studio。

2.點擊File > New > Import Magic Leap Mabu Projects。

3.在導入Magic Leap項目中單擊Browse,并選擇Lumin Runtime Editor的項目文件夾。


4.點擊Import

EyeTracking項目應該如下圖所示:


Step 3:在Visual Studio Code (Windows / macOS)設置項目

1.打開Visual Studio Code。
2.點擊左側的圖標。
3.在Lumin SDK窗口標題中,單擊圖標,設置Lumin SDK的路徑(如果還沒有設置)。通常是:/Users/user/MagicLeap/mlsdk/v0.x.x。
4.在簽名證書窗口標題中,單擊圖標,然后設置.cert包簽名證書文件的路徑(如果還沒有設置)。
5.回到Lumin Runtime Editor,在項目菜單上,單擊Code Generation > Open code in External Editor。
6.出現下面窗口時單擊OK即可。


項目文件夾如下圖:


Step 4:腳本處理

EyeTracking.h header file

在文件夾(Visual Studio code中的code/inc文件夾)下,打開EyeTracking.h文件,Lumin運行時已經生成了大量的代碼行。在最后一個私有變量下添加以下行:
[AppleScript] 純文本查看 復制代碼
std::shared_ptr<lumin::PrismDataHandle> eyetrackingRetainer_;


EyeTracking.cpp Script

在Source Files文件夾(或Visual Studio code中的code/src文件夾)下,打開EyeTracking.cpp腳本。下面我們將重點討論需要修改的部分:

Directives, Namespaces and Globals

在最后一個#include指令之后,在腳本頂部添加以下指令:
[AppleScript] 純文本查看 復制代碼
#include <lumin/ui/UiKit.h>
#include "glm/ext.hpp"
#include <lumin/event/EyeTrackingEventData.h>
#include <lumin/event/KeyInputEventData.h>

接下來,添加以下指令和聲明:
[AppleScript] 純文本查看 復制代碼
using namespace lumin;
using namespace lumin::ui;

namespace {
  Text2dNode* Text;
  Text2dNode* A;
  Text2dNode* B;
  TransformNode* transformNodeA;
  TransformNode* transformNodeB;
  TransformNode* transformNodeDist;
  EyeTrackingEventData* result;
  bool updated = false;   
  std::string state = "PlaceA";
  glm::vec3 posA, posB, posDist, eyeFixation, EyeFixation_prismPosition;
  glm::mat4 prismWorldTransform;
}


  • Text2dNode* Text, A and B 是我們場景的三個節點。
  • TransformNode* transformNodeA,transformNodeB and transformNodeDist用于改變三個節點的位置。
  • EyeTrackingEventData* result存儲每個眼球跟蹤事件的數據。
  • bool update將在第一個眼球跟蹤事件后變為true。
  • std::string state存儲應用程序的狀態(“PlaceA”、“PlaceB”、“Wait”)。
  • glm::vec3 posA, posB, posDist存儲了三個轉換節點的位置。
  • glm::vec3 eyeFixation存儲用戶的眼睛注視位置。
  • glm::vec3 EyeFixation_prismPosition以Prism坐標存儲用戶的眼睛注視位置。
  • glm::mat4 prismWorldTransform將世界坐標轉換為Prism坐標。



glm::vec3 EyeTracking::getInitialPrismSize()改為:
[AppleScript] 純文本查看 復制代碼
const glm::vec3 EyeTracking::getInitialPrismSize() const {
  return glm::vec3(5.0f, 5.0f, 5.0f);
}


初始化

int EyeTracking::init() 方法初始化Prism(默認情況下prism_)。
[AppleScript] 純文本查看 復制代碼
int EyeTracking::init() {

  ML_LOG(Debug, "EyeTracking Initializing.");

  createInitialPrism();
  lumin::ui::Cursor::SetScale(prism_, 0.03f);
  spawnInitialScenes();

  eyetrackingRetainer_ = prism_->retainEyeTrackingUpdates();
  Text = static_cast<Text2dNode*>(prism_->findNode("text", prism_->getRootNode()));
  A = static_cast<Text2dNode*>(prism_->findNode("A", prism_->getRootNode()));
  B = static_cast<Text2dNode*>(prism_->findNode("B", prism_->getRootNode()));
  transformNodeA = static_cast<lumin::TransformNode*>(A);
  transformNodeB = static_cast<lumin::TransformNode*>(B);
  transformNodeDist = static_cast<lumin::TransformNode*>(Text);
  transformNodeB->setVisible(false);
  transformNodeDist->setVisible(false);

  return 0;
}


  • createInitialPrism() 創建Prism。
  • ui::Cursor::SetScale(prism_, 0.03f)設置Prism (prism_)光標的比例。
  • spawnInitialScenes()實例化唯一的場景。
  • eyetrackingRetainer_包含所有眼球跟蹤事件的更新。
  • Text = static_cast<Text2dNode*>(prism_->findNode("text", prism_->getRootNode())) 表示并轉換場景的Text2d文本節點。
  • A和B表示并轉換場景的Text2d A和B節點。
  • transformNodeA, transformNodeB, transformNodeDist分別表示和轉換場景的A、B和文本節點。
  • transformNodeB and transformNodeDist成為invisible。


Event Listener

bool EyeTracking::eventListener(lumin::ServerEvent* event) 方法在運行時監聽眼球跟蹤事件,并接收緩沖器和觸發按鈕點擊。
[AppleScript] 純文本查看 復制代碼
bool EyeTracking::eventListener(lumin::ServerEvent* event) {

  if (event->isInputEventType()) {
  
    //Check for Control button taps
                if (updated) {
      InputEventData* inputEventData = static_cast<InputEventData*>(event);
      KeyInputEventData* keyEventData = static_cast<KeyInputEventData*>(inputEventData);
      
      if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_BUMPER) {
        prismWorldTransform = prism_->getTransform();
        EyeFixation_prismPosition = glm::vec3(glm::inverse(prismWorldTransform) * 
                                    glm::vec4(eyeFixation, 1));
        if (state == "PlaceA") {
          state = "PlaceB";
          transformNodeA->setLocalPosition(EyeFixation_prismPosition);
          posA = EyeFixation_prismPosition;
          transformNodeB->setVisible(true);
        }
      }
      
      else if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_TRIGGER) {
        if (state == "PlaceB") {
          state = "Wait";
          transformNodeDist->setVisible(true);
        }
      }
    }
  }
  
  else {
  
    //Get the eye tracking data
      result = static_cast<EyeTrackingEventData*>(event);
      eyeFixation = result->getEyeTrackingFixationPosition();
      updated = true;
  }
  return false;
}


此方法將每個事件捕獲為ServerEvent*事件。眼球跟蹤事件屬于EyeTrackingEventData類,可以通過引用ServerEvent類型直接捕獲。但是,控件按鈕事件屬于KeyInputEventData類,它依賴于InputEventData類。InputEventData類依賴于ServerEvent類。


因此,我們使用一個event->isInputEventType() 調用來區分眼球跟蹤和控制按鈕事件。

如果結果是false:
  • 存儲結果EyeTrackingEventData
  • 檢索眼球固定位置并將其存儲到glm::vec3 eyeFixation(以世界坐標表示)。
  • 將bool更新為true。


如果事件是InputEvent(引用控件輸入的事件),則執行以下操作:
  • 檢查bool更新是否為真。
  • 如果是,將InputEventData轉換為InputEventData
  • 將相關的KeyInputEventData轉換為keyEventData
  • 檢查keyEventData是否引用AKEYCODE_EX_BUMPER事件。


如果bumper被點擊,則:
  • 將Prism的世界位置存儲在prismWorldTransform中。
  • 將眼睛注視位置從World轉換為Prism坐標,并將新坐標存儲為EyeFixation_prismPosition
  • 檢查state是否設置為Place A。
  • 如果是,則將狀態更改為Place B。
  • 將A的位置鎖定在眼睛注視位置。
  • 使B節點可見,并讓用戶控制其位置。


如果trigger被點擊,則:
  • 檢查state是否設置為Place B。
  • 如果是,將狀態更改為Wait。
  • 使A和B之間的距離對用戶可見。


The bool EyeTracking::updateLoop(float fDelta) 在每一幀中運行。

[AppleScript] 純文本查看 復制代碼
bool EyeTracking::updateLoop(float fDelta) {
  if (updated) {
    prismWorldTransform = prism_->getTransform();
    EyeFixation_prismPosition = glm::vec3(glm::inverse(prismWorldTransform) * glm::vec4(eyeFixation, 1));
    if (state == "PlaceA") {
      transformNodeA->setLocalPosition(EyeFixation_prismPosition);
    }
    else if (state == "PlaceB") {
      transformNodeB->setLocalPosition(EyeFixation_prismPosition);
      posB = EyeFixation_prismPosition;
      float len = glm::length(posA - posB);
      Text->setText("Distance AB is "+std::to_string(len) + " m");
      posDist = posB;
      posDist.y += float(0.02);
      transformNodeDist->setLocalPosition(posDist);
    }
  }
  return true;
}


如果我們已經收到一個眼球跟蹤事件,更新的bool應該為true:

  • 計算EyeFixation_prismPosition
  • 檢查狀態是否設置為Place A,如果設置為Place A,則讓用戶用眼睛控制節點A。
  • 檢查狀態是否設置為放置B,如果設置為Place B,則讓用戶用眼睛控制節點B,計算A與B之間的距離,并將距離消息(文本節點)放置在B節點上方。


Step 5:Build

構建簽名.mpk文件并將其安裝到設備上的過程取決于使用的IDE。
Visual Studio (Windows)
  • 使用USB-C電纜將Magic Leap One連接到計算機。
  • 選擇調試配置和ML目標。
  • 切換到Magic Leap調試器。
  • 運行該應用程序。
  • 如果需要調試,單擊Continue繼續執行。



Visual Studio Code (Windows / macOS)

  • 把你的設備插入電腦。
  • 單擊左邊的圖標。
  • 將調試目標設置為Lumin OS Debug
  • 單擊三角形圖標開始調試。

完整代碼參考:
[AppleScript] 純文本查看 復制代碼
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%

// %SRC_VERSION%: 1

#include <EyeTracking.h>
#include <lumin/node/RootNode.h>
#include <lumin/ui/Cursor.h>
#include <ml_logging.h>
#include <scenes.h>
#include <PrismSceneManager.h>
#include <lumin/ui/UiKit.h>
#include "glm/ext.hpp"
#include <lumin/event/EyeTrackingEventData.h>
#include <lumin/event/KeyInputEventData.h>

using namespace lumin;
using namespace lumin::ui;

namespace {
  Text2dNode* Text;
  Text2dNode* A;
  Text2dNode* B;
  TransformNode* transformNodeA;
  TransformNode* transformNodeB;
  TransformNode* transformNodeDist;
  EyeTrackingEventData* result;
  bool updated = false;
  std::string state = "PlaceA";
  glm::vec3 posA, posB, posDist, eyeFixation, EyeFixation_prismPosition;
  glm::mat4 prismWorldTransform;
}

EyeTracking::EyeTracking() {
  ML_LOG(Debug, "EyeTracking Constructor.");

  // Place your constructor implementation here.
}

EyeTracking::~EyeTracking() {
  ML_LOG(Debug, "EyeTracking Destructor.");

  // Place your destructor implementation here.
}

const glm::vec3 EyeTracking::getInitialPrismSize() const {
  return glm::vec3(5.0f, 5.0f, 5.0f);
}

void EyeTracking::createInitialPrism() {
  prism_ = requestNewPrism(getInitialPrismSize());
  if (!prism_) {
    ML_LOG(Error, "EyeTracking Error creating default prism.");
    abort();
  }
  prismSceneManager_ = new PrismSceneManager(prism_);
}

int EyeTracking::init() {

  ML_LOG(Debug, "EyeTracking Initializing.");

  createInitialPrism();
  lumin::ui::Cursor::SetScale(prism_, 0.03f);
  spawnInitialScenes();

  eyetrackingRetainer_ = prism_->retainEyeTrackingUpdates();
  Text = static_cast<Text2dNode*>(prism_->findNode("text", prism_->getRootNode()));
  A = static_cast<Text2dNode*>(prism_->findNode("A", prism_->getRootNode()));
  B = static_cast<Text2dNode*>(prism_->findNode("B", prism_->getRootNode()));
  transformNodeA = static_cast<lumin::TransformNode*>(A);
  transformNodeB = static_cast<lumin::TransformNode*>(B);
  transformNodeDist = static_cast<lumin::TransformNode*>(Text);
  transformNodeB->setVisible(false);
  transformNodeDist->setVisible(false);

  return 0;
}

int EyeTracking::deInit() {
  ML_LOG(Debug, "EyeTracking Deinitializing.");

  // Place your deinitialization here.

  return 0;
}

void EyeTracking::spawnInitialScenes() {

  // Iterate over all the exported scenes
  for (auto& exportedSceneEntry : scenes::externalScenes ) {

    // If this scene was marked to be instanced at app initialization, do it
    const SceneDescriptor &sd = exportedSceneEntry.second;
    if (sd.getInitiallySpawned()) {
      lumin::Node* const spawnedRoot = prismSceneManager_->spawn(sd);
      if (spawnedRoot) {
        if (!prism_->getRootNode()->addChild(spawnedRoot)) {
          ML_LOG(Error, "EyeTracking Failed to add spawnedRoot to the prism root node");
          abort();
        }
      }
    }
  }
}

bool EyeTracking::updateLoop(float fDelta) {
  if (updated) {
    prismWorldTransform = prism_->getTransform();
    EyeFixation_prismPosition = glm::vec3(glm::inverse(prismWorldTransform) * glm::vec4(eyeFixation, 1));
    if (state == "PlaceA") {
      transformNodeA->setLocalPosition(EyeFixation_prismPosition);
    }
    else if (state == "PlaceB") {
      transformNodeB->setLocalPosition(EyeFixation_prismPosition);
      posB = EyeFixation_prismPosition;
      float len = glm::length(posA - posB);
      Text->setText("Distance AB is " + std::to_string(len) + " m");
      posDist = posB;
      posDist.y += float(0.02);
      transformNodeDist->setLocalPosition(posDist);
    }
  }
  return true;
}

bool EyeTracking::eventListener(lumin::ServerEvent* event) {

  if (event->isInputEventType()) {
    if (updated) {
      InputEventData* inputEventData = static_cast<InputEventData*>(event);
      KeyInputEventData* keyEventData = static_cast<KeyInputEventData*>(inputEventData);
      if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_BUMPER) {
        prismWorldTransform = prism_->getTransform();
        EyeFixation_prismPosition = glm::vec3(glm::inverse(prismWorldTransform) * glm::vec4(eyeFixation, 1));
        if (state == "PlaceA") {
          state = "PlaceB";
          transformNodeA->setLocalPosition(EyeFixation_prismPosition);
          posA = EyeFixation_prismPosition;
          transformNodeB->setVisible(true);
        }
      }
      else if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_TRIGGER) {
        if (state == "PlaceB") {
          state = "Wait";
          transformNodeDist->setVisible(true);
        }
      }
    }
  }
  else {
      result = static_cast<EyeTrackingEventData*>(event);
      eyeFixation = result->getEyeTrackingFixationPosition();
      updated = true;
  }
  return false;
}




------AR Portal(AR開發者社區)整理
關注微信公眾號:AR開發者社區  (國內領先的AR開發者交流學習社區和AR內容平臺)
回復

使用道具 舉報

4四處流浪
481/500
排名
10596
昨日變化

2

主題

237

帖子

481

積分

Rank: 4

UID
328743
好友
0
蠻牛幣
348
威望
0
注冊時間
2019-8-5
在線時間
142 小時
最后登錄
2019-12-2
沙發
2019-8-30 11:44:48 只看該作者
66666666666666
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

法甲球队主场名称