AR酱 Magic Leap开发指南(8)-- 眼球追踪(Lumin Runtime)

1
回复
280
查看
打印 上一主题 下一主题
[ 复制链接 ]
排名
2299
昨日变化

30

主题

251

帖子

1464

积分

Rank: 9Rank: 9Rank: 9

UID
156756
好友
11
蛮牛币
1343
威望
0
注册时间
2016-7-13
在线时间
491 小时
最后登录
2019-9-13

专栏作家

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

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内容平台)
3偶尔光临
299/300
排名
19338
昨日变化
610

2

主题

189

帖子

299

积分

Rank: 3Rank: 3Rank: 3

UID
328743
好友
0
蛮牛币
50
威望
0
注册时间
2019-8-5
在线时间
76 小时
最后登录
2019-9-17
沙发
2019-8-30 11:44:48 只看该作者
66666666666666
您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则