• 【Magic Leap开发指南(8)-眼球追踪(Lumin Runtime)】-开发者专栏-【游戏蛮牛】-游戏出海,ar增强现实,虚拟现实,yzc88޳,yzc88޳教程下载首选޳,yzc88޳官网 - Powered by Discuz!

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

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

    34

    主题

    261

    帖子

    1505

    积分

    Rank: 9Rank: 9Rank: 9

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

    专栏作家

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

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

    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四处流浪
    472/500
    排名
    10596
    昨日变化

    2

    主题

    237

    帖子

    472

    积分

    Rank: 4

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

    本版积分规则