Windows Community Toolkit 3.0 - Gaze Interaction

概述

Gaze Input & Tracking - 也就是视觉输入和跟踪,是一种和鼠标/触摸屏输入非常不一样的交互方式,利用人类眼球的识别和眼球方向角度的跟踪,来判断人眼的目标和意图,从而非常方便的完成对设备的控制和操作。这种交互方式,应用场景非常广泛,比如 AR/VR/MR 中,利用视觉追踪,来判断 Reaility 中场景物体的方向和展示;再比如阅读中,根据视觉输入和追踪,来自动滚动和翻页等;再比如游戏中依靠视觉追踪来决定人物的走位等,让游戏控制变得非常简单。

Windows 10 秋季创意者更新公布了对视觉追踪的原生支持,而在 Windows 10 四月更新中为开发者增加了 Windows Gaze Input API 来支持视觉追踪开发,让开发者可以在应用中加入视觉追踪的交互方式来处理视觉输入和跟踪。

而在 Windows Community Toolkit 3.0 中,也加入了 Gaze Interaction Library,它基于 Windows Gaze Input API 创建,提供了一系列的开发者帮助类,帮助开发者可以更容易的实现对用户视觉的追踪。它旨在把通过 Windows API 来处理眼球追踪的原始数据流的负责过程封装处理,让开发者可以更方便的在 Windows App 中集成。

下面是 Windows Community Toolkit Sample App 的示例截图和 code/doc 地址:

 

Windows Community Toolkit Doc - Gaze Interaction

Windows Community Toolkit Source Code - Gaze Interaction

Namespace: Microsoft.Toolkit.Uwp.Input.GazeInteraction; Nuget: Microsoft.Toolkit.Uwp.Input.GazeInteraction;

 

开发过程

代码结构分析

首先来看 GazeInteraction 的代码结构,通过类的命名可以看出,开发语言使用的是 C++,而且类结构和数量都比较复杂。可以看到 GazeInteraction 的代码在 Microsoft.Toolkit.Uwp.Input namespace 下,这也意味着 GazeInteraction 会被作为一种 Input 方式来做处理。

 

来看一下在 Visual Studio 中打开的目录,会更清晰一些:

因为是 C++ 语言编写的库,所以可以很清楚的看到,主要功能被划分在 Headers 和 Sources 中,Headers 中主要是 cpp 对应的头文件,以及一些枚举类,变量定义类;Sources 中就是整个 GazeInteraction 的主要代码处理逻辑;

我们挑选其中比较重要的几个类来讲解:

  • GazeInput.cpp - Gaze 输入的主要处理逻辑
  • GazePointer.cpp - Gaze 指针的主要处理逻辑
  • GazePointerProxy.cpp - Gaze 指针的代理处理逻辑
  • GazeTargetItem.cpp - Gaze 操作目标的主要处理逻辑

1. GazeInput.cpp

在 GazeInput.h 中可以看到,定义了很多 public 的依赖属性,主要针对的是 GazeInput 的光标属性,以及很多 get/set 方法,以及 propertychanged 通知事件。

GazeInput 中定义的依赖属性有:

  • Interaction - 获取和设置视觉交互属性,它有三个枚举值:Enabled/Disabled/Inherited;
  • IsCursorVisible - 视觉交互的光标是否显示,布尔值,默认为 false;
  • CursorRadius - 获取和设置视觉光标的半径;
  • GazeElement - 视觉元素,附加到控件的代理对象允许订阅每个视觉事件;
  • FixationDuration - 获取和设置从 Enter 状态到 Fixation 状态的转换所需时间跨度,当 StateChanged 时间被触发,PointerState 被设置为 Fixation,单位是 ms,默认为 350 ms; 
  • DwellDuration - 获取和设置从 Fixation 状态到 DWell 状态的转换所需时间跨度,当 StateChanged 时间被触发,PointerState 被设置为 DWell,单位是 ms,默认为 400 ms;
  • RepeatDelayDuration - 获取和设置第一次重复发生的持续时间,可以防止无意的重复调用;
  • DwellRepeatDuration - 获取和设置 Dwell 重复驻留调用的持续时间;
  • ThresholdDuration - 获取和设置从 Enter 状态到 Exit 状态的转换所需时间跨度,当 StateChanged 时间被触发,PointerState 被设置为 Exit,单位是 ms,默认为 50 ms;
  • MaxDwellRepeatCount - 控件重复调用的最大次数,用户的视觉不需要离开并重新进入控件。默认值为 0,禁用重复调用,开发者可以设置为 >0 的值来启用重复调用;
  • IsSwitchEnabled - 标识切换是否可用,布尔值;

这些属性的定义让视觉输入可以作为一种输入方式,实现对系统界面元素的操作。

2. GazePointer.cpp

GazePointer 类主要处理的是 GazeInput 的定位和相关功能,代码量比较大,不过每个方法功能都比较容易懂,我们通过几个方法来看一些重要信息:

1). GazePointer 构造方法,看到方法中初始化了 NullFilter 和 GazeCursor,还定义了一段时间接收不到视觉输入的定时处理,以及观察器;

GazePointer::GazePointer()
{
    _nonInvokeGazeTargetItem = ref new NonInvokeGazeTargetItem();

    // Default to not filtering sample data
    Filter = ref new NullFilter();

    _gazeCursor = ref new GazeCursor();

    // timer that gets called back if there gaze samples haven't been received in a while
    _eyesOffTimer = ref new DispatcherTimer();
    _eyesOffTimer->Tick += ref new EventHandler<Object^>(this, &GazePointer::OnEyesOff);

    // provide a default of GAZE_IDLE_TIME microseconds to fire eyes off 
    EyesOffDelay = GAZE_IDLE_TIME;

    InitializeHistogram();

    _watcher = GazeInputSourcePreview::CreateWatcher();
    _watcher->Added += ref new TypedEventHandler<GazeDeviceWatcherPreview^, GazeDeviceWatcherAddedPreviewEventArgs^>(this, &GazePointer::OnDeviceAdded);
    _watcher->Removed += ref new TypedEventHandler<GazeDeviceWatcherPreview^, GazeDeviceWatcherRemovedPreviewEventArgs^>(this, &GazePointer::OnDeviceRemoved);
    _watcher->Start();
}

2). GetProperty 方法,这里我们主要看看 PointerState,主要有 Fixation/DWell/DWellRepeat/Enter 和 Exit;

static DependencyProperty^ GetProperty(PointerState state)
{
    switch (state)
    {
    case PointerState::Fixation: return GazeInput::FixationDurationProperty;
    case PointerState::Dwell: return GazeInput::DwellDurationProperty;
    case PointerState::DwellRepeat: return GazeInput::DwellRepeatDurationProperty;
    case PointerState::Enter: return GazeInput::ThresholdDurationProperty;
    case PointerState::Exit: return GazeInput::ThresholdDurationProperty;
    default: return nullptr;
    }
}

3). GetElementStateDelay 方法,因为 GazePointer 有很多不同的状态,我们看一个典型的获取某个 state delay 的逻辑;根据用户设置或默认设置的值,再根据 pointer state 和是否 repeat 来判断 ticks 的值;  

TimeSpan GazePointer::GetElementStateDelay(UIElement ^element, PointerState pointerState)
{
    auto property = GetProperty(pointerState);
    auto defaultValue = GetDefaultPropertyValue(pointerState);
    auto ticks = GetElementStateDelay(element, property, defaultValue);

    switch (pointerState)
    {
    case PointerState::Dwell:
    case PointerState::DwellRepeat:
        _maxHistoryTime = max(_maxHistoryTime, 2 * ticks);
        break;
    }

    return ticks;
}
TimeSpan GazePointer::GetElementStateDelay(UIElement ^element, DependencyProperty^ property, TimeSpan defaultValue)
{
    UIElement^ walker = element;
    Object^ valueAtWalker = walker->GetValue(property);

    while (GazeInput::UnsetTimeSpan.Equals(valueAtWalker) && walker != nullptr)
    {
        walker = GetInheritenceParent(walker);

        if (walker != nullptr)
        {
            valueAtWalker = walker->GetValue(property);
        }
    }

    auto ticks = GazeInput::UnsetTimeSpan.Equals(valueAtWalker) ? defaultValue : safe_cast<TimeSpan>(valueAtWalker);

    return ticks;
}

4). GetHitTarget 方法,获取击中的目标,根据指针的位置,和每个 target 在视觉树中的位置,以及层级关系,来判断该次击中是否可用,应该产生什么后续事件;

GazeTargetItem^ GazePointer::GetHitTarget(Point gazePoint)
{
    GazeTargetItem^ invokable;

    switch (Window::Current->CoreWindow->ActivationMode)
    {
    default:
        invokable = _nonInvokeGazeTargetItem;
        break;

    case CoreWindowActivationMode::ActivatedInForeground:
    case CoreWindowActivationMode::ActivatedNotForeground:
        auto elements = VisualTreeHelper::FindElementsInHostCoordinates(gazePoint, nullptr, false);
        auto first = elements->First();
        auto element = first->HasCurrent ? first->Current : nullptr;

        invokable = nullptr;

        if (element != nullptr)
        {
            invokable = GazeTargetItem::GetOrCreate(element);

            while (element != nullptr && !invokable->IsInvokable)
            {
                element = dynamic_cast<UIElement^>(VisualTreeHelper::GetParent(element));

                if (element != nullptr)
                {
                    invokable = GazeTargetItem::GetOrCreate(element);
                }
            }
        }
        ...break;
    }

    return invokable;
}

GazePointer 类中处理方法非常多,这里不一一列举,大家可以详细阅读源代码去理解每一个方法的书写方法。

3. GazePointerProxy.cpp

GazePointerProxy 类主要是为 GazePointer 设立的代理,包括 Loaded 和 UnLoaded 事件的代理,以及 Enable 状态和处理的代理;比较典型的 OnLoaded 事件处理:

void GazePointerProxy::OnLoaded(Object^ sender, RoutedEventArgs^ args)
{
    assert(IsLoadedHeuristic(safe_cast<FrameworkElement^>(sender)));

    if (!_isLoaded)
    {
        // Record that we are now loaded.
        _isLoaded = true;

        // If we were previously enabled...
        if (_isEnabled)
        {
            // ...we can now be counted as actively enabled.
            GazePointer::Instance->AddRoot(sender);
        }
    }
    else
    {
        Debug::WriteLine(L"Unexpected Load");
    }
}

4. GazeTargetItem.cpp

Gaze 视觉输入的 Target Item 类,针对不同类型的 Target,进行不同的交互和逻辑处理,比较典型的 PivotItemGazeTargetItem 类,会根据 PivotItem 的组成:headerItem 和 headerPanel,设置选中的 Index;

ref class PivotItemGazeTargetItem sealed : GazeTargetItem
{
internal:

    PivotItemGazeTargetItem(UIElement^ element)
        : GazeTargetItem(element)
    {
    }

    void Invoke() override
    {
        auto headerItem = safe_cast<PivotHeaderItem^>(TargetElement);
        auto headerPanel = safe_cast<PivotHeaderPanel^>(VisualTreeHelper::GetParent(headerItem));
        unsigned index;
        headerPanel->Children->IndexOf(headerItem, &index);

        DependencyObject^ walker = headerPanel;
        Pivot^ pivot;
        do
        {
            walker = VisualTreeHelper::GetParent(walker);
            pivot = dynamic_cast<Pivot^>(walker);
        } while (pivot == nullptr);

        pivot->SelectedIndex = index;
    }
};

调用示例

<Page   
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
    mc:Ignorable="d"
    xmlns:g="using:Microsoft.Toolkit.Uwp.Input.GazeInteraction" 
    g:GazeInput.Interaction="Enabled"
    g:GazeInput.IsCursorVisible="True"
    g:GazeInput.CursorRadius="5">
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">             
        <Button x:Name="TargetButton" HorizontalAlignment="Center" BorderBrush="#7FFFFFFF"                            
                    g:GazeInput.ThresholdDuration="00:00:00.0500000"
                    g:GazeInput.FixationDuration="00:00:00.3500000"
                    g:GazeInput.DwellDuration="00:00:00.4000000"
                    g:GazeInput.RepeatDelayDuration="00:00:00.4000000"
                    g:GazeInput.DwellRepeatDuration="00:00:00.4000000"
                    g:GazeInput.MaxDwellRepeatCount="0"
                    Width="100"
                    Height="100"
                    />
  </Grid>
</Page>
private void GazeButtonControl_StateChanged(object sender, GazePointerEventArgs ea)
{
    if (ea.PointerState == GazePointerState.Enter)
    {
    }
    if (ea.PointerState == GazePointerState.Fixation)
    {
    }
    if (ea.PointerState == GazePointerState.Dwell)
    {
        if (dwellCount == 0)
        {
            dwellCount = 1;
        }
        else
        {
            dwellCount += 1;
        }
    }
    if (ea.PointerState == GazePointerState.Exit)
    { 
    }
}

// You can respond to dwell progress in the ProgressFeedback handler
private void OnProgressFeedback(object sender, GazeProgressEventArgs e){}private void OnGazeInvoked(object sender, GazeInvokedRoutedEventArgs e){}

 

总结

到这里我们就把 Windows Community Toolkit 3.0 中的 Gaze Interation 的源代码实现过程讲解完成了,希望能对大家更好的理解和使用这个功能有所帮助。同时这一功能,对于开发 AR/VR/MR 和基于其他视觉追踪设备的应用,会非常有想象空间,希望大家能有很多很好玩的想法,也欢迎和我们交流。

最后,再跟大家安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490大家可以通过微博关注最新动态。

衷心感谢 WindowsCommunityToolkit 的作者们杰出的工作,感谢每一位贡献者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!

转载于:https://www.cnblogs.com/shaomeng/p/9281073.html

### 回答1: Soap Toolkit 3.0 是一个用于在各种平台上构建和部署 Web 服务的工具集。它提供了一种简单且可扩展的方式来创建和使用 Web 服务,能够帮助开发者轻松地构建跨平台的分布式应用程序。 在 CSDN(中国最大的开发者社区)上,可以找到并下载 Soap Toolkit 3.0。CSDN提供了一个集成的开发者平台,为开发人员提供了软件、代码和技术文章等资源。CSDN的下载中心是一个开放的资源平台,提供各种开发工具和库,开发者可以在这里搜索并下载自己需要的软件。 要下载 Soap Toolkit 3.0,你可以在CSDN的搜索框中输入关键词 "Soap Toolkit 3.0" 并点击搜索按钮。结果页面将显示与Soap Toolkit 3.0相关的文章、下载链接和相关资源。选择合适的链接,进入下载页面,按照指示完成下载过程。这样,你就可以在本地计算机上获得 Soap Toolkit 3.0 的安装文件,并按照指南进行安装和配置。 下载 Soap Toolkit 3.0 后,你可以使用它来开发和部署 Web 服务。它支持 SOAP(Simple Object Access Protocol)协议,使得不同平台之间的应用程序可以通过 Web 服务进行通信。Soap Toolkit 3.0 提供了多种编程语言的支持,例如 C++、Java、VBScript 等,使得开发者可以使用自己熟悉的语言来开发 Web 服务。 总之,通过在CSDN上下载和使用 Soap Toolkit 3.0,开发者可以方便地构建和部署跨平台的 Web 服务应用程序。CSDN作为一个技术交流平台,为开发者们提供了丰富的资源,使得他们可以更加轻松地获取他们需要的技术工具和知识。 ### 回答2: 要在CSDN上下载Soap Toolkit 3.0,您可以按照以下步骤进行操作: 1. 打开浏览器,并在地址栏中输入“www.csdn.net”以访问CSDN网站主页。 2. 在网站主页的搜索栏中输入“Soap Toolkit 3.0”并点击搜索按钮。 3. 在搜索结果页面中,您可以看到与Soap Toolkit 3.0相关的文章、教程和下载链接。 4. 点击其中一个下载链接来访问Soap Toolkit 3.0的下载页面。 5. 在下载页面中,您可能需要先登录您的CSDN账户。如果您没有账户,可以选择注册一个新账户。 6. 在下载页面中,您可以看到有关Soap Toolkit 3.0的详细信息,例如版本、文件大小和发布日期。 7. 点击下载按钮或链接,将开始下载Soap Toolkit 3.0的安装文件。 8. 选择保存安装文件的目标文件夹,并等待下载完成。 9. 下载完成后,您可以通过点击安装文件来开始安装Soap Toolkit 3.0。 10. 按照安装向导的提示,完成Soap Toolkit 3.0的安装过程。 11. 安装完成后,您就可以在您的计算机上使用Soap Toolkit 3.0进行开发和集成相关工作了。 请注意,以上步骤仅为参考,具体步骤可能会根据CSDN网站的更新和变化而稍有不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值