旬も過ぎ去った感もあるLeap Motionを触り始めました。とりあえずね。センサーから上がってくる実際の値を処理してみたいじゃないですか…!!
こちらのbuildinsiderの記事を参考に画面に指先の点をプロットするアプリを書いてみました。
早速やってみた
Listenerを実装してオーバーライドしたメソッドで処理を…とやるのが嫌だったので、以下のようなListenerのOnFrameをIO<Controller>に変換するだけのListenerを作りました。
class RxListener : Listener { private Subject<Controller> onFrameProxy = new Subject<Controller>(); publicoverridevoid OnFrame(Controller c) { this.onFrameProxy.OnNext(c); } public IObservable<Controller> OnFrameAsObservable() { returnthis.onFrameProxy.AsObservable(); } }
そして、Leapからのデータの受信を開始する処理では、以下のようにRxで50ms間隔でデータを貰うようにしてからUIスレッド上で現在のポイントを割り出しています。ここらへんは、記事のサンプルコードをC++からC#に焼き直しただけです。
this.leapController = new Controller(); this.leapListener = new RxListener(); this.leapController.AddListener(this.leapListener); this.leapListener.OnFrameAsObservable() .Sample(TimeSpan.FromMilliseconds(50)) .ObserveOn(SynchronizationContext.Current) .Subscribe(c => { this.Points.Clear(); var frame = c.Frame(); foreach (var p in frame.Pointables) { var normalizedPoint = frame.InteractionBox.NormalizePoint(p.StabilizedTipPosition); var x = normalizedPoint.x * this.WindowWidth; var y = this.WindowHeight - normalizedPoint.y * this.WindowHeight; var color = default(Color); if (p.TouchDistance > 0&& p.TouchZone != Pointable.Zone.ZONENONE) { color = Colors.Azure; } elseif (p.TouchDistance <= 0) { color = Colors.Red; } else { color = Colors.LightGray; } this.Points.Add(Tuple.Create((int)x, (int)y, new SolidColorBrush(color))); } });
PointsプロパティはObservableCollection<Tuple<int, int, SolidColorBrush>という手抜きにしてます。本当はちゃんとクラス作ったほうがいいんですけど、なんだかめんどくさくなって。
画面側
画面側は、ItemsControlにPointsをバインドしてTuple<int, int, SolidColorBrush>の値をもとに丸を画面上に配置してます。ItemsPanelTemplateでCanvasを指定してItemsContainerStyleでCanvas.TopとCanvas.Leftを指定して任意の場所に丸を出してるのがポイントかな。
<ItemsControl Grid.RowSpan="2"ItemsSource="{Binding Points}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><Canvas /></ItemsPanelTemplate></ItemsControl.ItemsPanel><ItemsControl.ItemContainerStyle><Style TargetType="ContentPresenter"><Setter Property="Canvas.Left"Value="{Binding Item1}" /><Setter Property="Canvas.Top"Value="{Binding Item2}" /></Style></ItemsControl.ItemContainerStyle><ItemsControl.ItemTemplate><DataTemplate><Ellipse Width="100"Height="100"Fill="{Binding Item3}" /></DataTemplate></ItemsControl.ItemTemplate></ItemsControl>
動かしてみると以下のようになります。
いい感じ!!
コード全体
以下のライブラリを参照したWPFアプリケーションです。
- MVVM Light PCL
- Rx-Main
AppModel.cs
using GalaSoft.MvvmLight; using Leap; using System; using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using System.Windows.Media; namespace WpfApplication4 { publicclass AppModel : ViewModelBase { privateint windowHeight; publicint WindowHeight { get { returnthis.windowHeight; } set { this.Set(refthis.windowHeight, value); } } privateint windowWidth; publicint WindowWidth { get { returnthis.windowWidth; } set { this.Set(refthis.windowWidth, value); } } public ObservableCollection<Tuple<int, int, SolidColorBrush>> Points { get; private set; } public AppModel() { this.WindowHeight = 800; this.WindowWidth = 800; this.Points = new ObservableCollection<Tuple<int, int, SolidColorBrush>>(); } private Controller leapController; private RxListener leapListener; publicvoid Start() { this.leapController = new Controller(); this.leapListener = new RxListener(); this.leapController.AddListener(this.leapListener); this.leapListener.OnFrameAsObservable() .Sample(TimeSpan.FromMilliseconds(50)) .ObserveOn(SynchronizationContext.Current) .Subscribe(c => { this.Points.Clear(); var frame = c.Frame(); foreach (var p in frame.Pointables) { var normalizedPoint = frame.InteractionBox.NormalizePoint(p.StabilizedTipPosition); var x = normalizedPoint.x * this.WindowWidth; var y = this.WindowHeight - normalizedPoint.y * this.WindowHeight; var color = default(Color); if (p.TouchDistance > 0&& p.TouchZone != Pointable.Zone.ZONENONE) { color = Colors.Azure; } elseif (p.TouchDistance <= 0) { color = Colors.Red; } else { color = Colors.LightGray; } this.Points.Add(Tuple.Create((int)x, (int)y, new SolidColorBrush(color))); } }); } publicvoid Stop() { this.leapController.RemoveListener(this.leapListener); this.leapController.Dispose(); this.leapListener = null; this.leapController = null; } } class RxListener : Listener { private Subject<Controller> onFrameProxy = new Subject<Controller>(); publicoverridevoid OnFrame(Controller c) { this.onFrameProxy.OnNext(c); } public IObservable<Controller> OnFrameAsObservable() { returnthis.onFrameProxy.AsObservable(); } } }
MainWindow.xaml
<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"x:Class="WpfApplication4.MainWindow"xmlns:local="clr-namespace:WpfApplication4"Title="MainWindow"Height="{Binding WindowHeight, Mode=TwoWay}"Width="{Binding WindowWidth, Mode=TwoWay}"><Window.DataContext><local:AppModel /></Window.DataContext><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition/></Grid.RowDefinitions><Menu><MenuItem Header="Start"><i:Interaction.Triggers><i:EventTrigger EventName="Click"><ei:CallMethodAction TargetObject="{Binding Mode=OneWay}"MethodName="Start"/></i:EventTrigger></i:Interaction.Triggers></MenuItem><MenuItem Header="Stop"><i:Interaction.Triggers><i:EventTrigger EventName="Click"><ei:CallMethodAction TargetObject="{Binding Mode=OneWay}"MethodName="Stop"/></i:EventTrigger></i:Interaction.Triggers></MenuItem></Menu><ItemsControl Grid.RowSpan="2"ItemsSource="{Binding Points}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><Canvas /></ItemsPanelTemplate></ItemsControl.ItemsPanel><ItemsControl.ItemContainerStyle><Style TargetType="ContentPresenter"><Setter Property="Canvas.Left"Value="{Binding Item1}" /><Setter Property="Canvas.Top"Value="{Binding Item2}" /></Style></ItemsControl.ItemContainerStyle><ItemsControl.ItemTemplate><DataTemplate><Ellipse Width="100"Height="100"Fill="{Binding Item3}" /></DataTemplate></ItemsControl.ItemTemplate></ItemsControl></Grid></Window>