Log in

Leap Motion でアプリ開発 - 手のひらを描画する

  • Public
By 山下 雅也 1555 days ago

目次へ戻る

今回は WPF の 3D 機能を使った Leap Motion のサンプルアプリケーションの続きです。

WPF の 3D 空間に Leap Motion で認識した手のひらを描画してみます。


認識できた手の情報は、Frame オブジェクトの Hands プロパティが持っており、複数の手の情報が格納されています。
Leap Motion では手の検出が不可能になったり、新しく検出できたりすることがあるため、この Hands プロパティから Hand オブジェクトが突然消えたり、増えたりすることも考慮しておかなければなりません。

Hand オブジェクトは Id を持っていますので、前回処理時と同じかどうかは Id で調べることができます。
今回は Hashtable を使って Hand オブジェクトと WPF のモデルとを管理することにしました。

手のひらを描画するために必要な情報は3つです。
PalmPosition プロパティは手のひらの中央の位置を表しています。
PalmNormal プロパティは手のひらの法線ベクトルです。
Direction プロパティは手のひらの前方向のベクトルです。

Leap_Palm_Vectors



手のひらのモデルは、XZ平面に沿った薄い直方体としました。
描画時は

  1. PalmNormal プロパティに合わせるようにモデルを回転
  2. Direction プロパティに合わせるようにモデルを回転
  3. PalmPosition プロパティに合わせるようにモデルを平行移動

としています。


MainWindows.xaml.cs を以下のように書き換えます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace LeapSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private Leap.Controller controller = new Leap.Controller();
        private ModelVisual3D trackingAreaModel = null;
        private Hashtable hands = new Hashtable();
 
        public MainWindow()
        {
            InitializeComponent();
 
            var root = this.Content as Grid;
            root.Children.Add(new TextBlock());
 
            Init();
 
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }
 
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var root = this.Content as Grid;
            var textBlock = root.Children[0] as TextBlock;
 
            Leap.Frame frame = controller.Frame();
            string msg = string.Format("IsValid={0}\n", frame.IsValid);
            msg += string.Format("Id={0}\n", frame.Id);
            msg += string.Format("Timestamp={0}\n", frame.Timestamp);
            msg += string.Format("InteractionBox={0}\n", frame.InteractionBox);
            msg += string.Format("CurrentFramesPerSecond={0}\n", frame.CurrentFramesPerSecond);
            msg += string.Format("Hands.Count={0}\n", frame.Hands.Count);
            msg += string.Format("Pointables.Count={0}\n", frame.Pointables.Count);
            msg += string.Format("Fingers.Count={0}\n", frame.Fingers.Count);
            msg += string.Format("Tools.Count={0}\n", frame.Tools.Count);
            textBlock.Text = msg;
 
            var td = root.Children[1] as _3DTools.TrackballDecorator;
            var viewport = td.Content as Viewport3D;
 
            if (frame.IsValid)
            {
                if (trackingAreaModel == null)
                {
                    trackingAreaModel = CreateTrackingAreaModel(frame.InteractionBox);
                    viewport.Children.Add(trackingAreaModel);
                }
            }
            else
            {
                viewport.Children.Remove(trackingAreaModel);
                trackingAreaModel = null;
            }
 
            Hashtable unusedHands = (Hashtable)hands.Clone();
 
            foreach (var hand in frame.Hands)
            {
                if (!hand.IsValid) continue;
                drawHand(hand);
                unusedHands.Remove(hand.Id);
            }
 
            foreach (var id in unusedHands.Keys)
            {
                viewport.Children.Remove((ModelVisual3D)unusedHands[id]);
                hands.Remove(id);
            }
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(0, 1000, 1200);
            camera.UpDirection = new Vector3D(0, 1, 0);
            camera.LookDirection = (new Point3D(0, 100, 0) - camera.Position);
            viewport.Camera = camera;
 
            // light
            var model = new ModelVisual3D();
            var light = new DirectionalLight { Color = Colors.White, Direction = new Vector3D(-1, -1, -1) };
            model.Content = light;
            viewport.Children.Add(model);
 
            // axis
            viewport.Children.Add(CreateAxisModel());
        }
 
        private ModelVisual3D CreateAxisModel()
        {
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-2000, 0, 0));
            xAxis.Points.Add(new Point3D(2000, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -2000, 0));
            yAxis.Points.Add(new Point3D(0, 2000, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -2000));
            zAxis.Points.Add(new Point3D(0, 0, 2000));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            return axis;
        }
 
        private ModelVisual3D CreateTrackingAreaModel(Leap.InteractionBox ib)
        {
            double k = (ib.Center.y + ib.Height / 2) / (ib.Center.y - ib.Height / 2);
            var lb = new Point3D((ib.Center.x - ib.Width / 2) * k, ib.Center.y + ib.Height / 2, (ib.Center.z - ib.Depth / 2) * k);    // left back
            var rb = new Point3D((ib.Center.x + ib.Width / 2) * k, ib.Center.y + ib.Height / 2, (ib.Center.z - ib.Depth / 2) * k);    // right back
            var rf = new Point3D((ib.Center.x + ib.Width / 2) * k, ib.Center.y + ib.Height / 2, (ib.Center.z + ib.Depth / 2) * k);    // right front
            var lf = new Point3D((ib.Center.x - ib.Width / 2) * k, ib.Center.y + ib.Height / 2, (ib.Center.z + ib.Depth / 2) * k);    // left front
            var origin = new Point3D(0, 0, 0);
 
            var m = new _3DTools.ScreenSpaceLines3D();
            m.Points.Add(lb);
            m.Points.Add(rb);
            m.Points.Add(rb);
            m.Points.Add(rf);
            m.Points.Add(rf);
            m.Points.Add(lf);
            m.Points.Add(lf);
            m.Points.Add(lb);
            m.Points.Add(origin);
            m.Points.Add(lb);
            m.Points.Add(origin);
            m.Points.Add(rb);
            m.Points.Add(origin);
            m.Points.Add(rf);
            m.Points.Add(origin);
            m.Points.Add(lf);
            m.Color = Colors.SlateGray;
            return m;
        }
 
        public ModelVisual3D GetCube(Color colour, Point3D nearPoint, Point3D farPoint)
        {
            var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
            var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
            var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
            var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
            var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
            var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
            var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
            var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
 
            var materialGroup = new MaterialGroup();
            materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
            var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
            materialGroup.Children.Add(specMat);
 
            var cube = new Model3DGroup();
            cube.Children.Add(CreateRectangleModel(materialGroup, p7, p6, p2, p3));        // front side rectangle
            cube.Children.Add(CreateRectangleModel(materialGroup, p6, p5, p1, p2));        // right side rectangle
            cube.Children.Add(CreateRectangleModel(materialGroup, p5, p4, p0, p1));        // back side rectangle
            cube.Children.Add(CreateRectangleModel(materialGroup, p4, p7, p3, p0));        // left side rectangle
            cube.Children.Add(CreateRectangleModel(materialGroup, p4, p5, p6, p7));        // top side rectangle
            cube.Children.Add(CreateRectangleModel(materialGroup, p1, p0, p3, p2));        // bottom side rectangle
 
            var model = new ModelVisual3D();
            model.Content = cube;
            return model;
        }
 
        private GeometryModel3D CreateRectangleModel(MaterialGroup material, Point3D p1, Point3D p2, Point3D p3, Point3D p4)
        {
            var upperGeometry = new GeometryModel3D();
            var mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(new Point3D[] { p1, p2, p3, p4, });
            mesh.TriangleIndices = new Int32Collection(new int[] { 0, 2, 1, 0, 3, 2 });
            mesh.TextureCoordinates = new PointCollection(new Point[] { new Point(0, 0), new Point(1, 0), new Point(1, 1), new Point(0, 1) });
            upperGeometry.Geometry = mesh;
            upperGeometry.Material = material;
            return upperGeometry;
        }
 
        private void drawHand(Leap.Hand hand)
        {
            ModelVisual3D model = null;
            if (!hands.Contains(hand.Id))
            {
                var root = this.Content as Grid;
                var td = root.Children[1] as _3DTools.TrackballDecorator;
                var viewport = td.Content as Viewport3D;
 
                model = GetCube(Colors.Red, new Point3D(30, 3, 30), new Point3D(-30, -3, -30));
                hands.Add(hand.Id, model);
                viewport.Children.Add(model);
            }
            else
            {
                model = (ModelVisual3D)hands[hand.Id];
            }
 
            var u1 = new Vector3D(0, -1, 0);
            var n1 = new Vector3D(hand.PalmNormal.x, hand.PalmNormal.y, hand.PalmNormal.z);
            var a1 = Vector3D.AngleBetween(u1, n1);
            Quaternion q1 = new Quaternion(Vector3D.CrossProduct(u1, n1), a1);
 
            var u2 = new Vector3D(0, 0, -1);
            Matrix3D m2 = new Matrix3D();
            m2.Rotate(q1);
            m2.Transform(u2);
            var d1 = new Vector3D(hand.Direction.x, hand.Direction.y, hand.Direction.z);
            var a2 = Vector3D.AngleBetween(u2, d1);
            Quaternion q2 = new Quaternion(Vector3D.CrossProduct(u2, d1), a2);
 
            var q = Quaternion.Multiply(q2, q1);
 
            Matrix3D m = new Matrix3D();
            m.Rotate(q);
            m.OffsetX = hand.PalmPosition.x;
            m.OffsetY = hand.PalmPosition.y;
            m.OffsetZ = hand.PalmPosition.z;
            model.Transform = new MatrixTransform3D(m);
        }
    }
}



実行結果は以下となります。

leapsample-4



Leap Motion デバイスの上で手を動かすと、それにあわせて赤い直方体が動きます。


今回はここまでです。

目次へ戻る