/** * The $P Point-Cloud Recognizer (.NET Framework 4.0 C# version) * * Radu-Daniel Vatavu, Ph.D. * University Stefan cel Mare of Suceava * Suceava 720229, Romania * vatavu@eed.usv.ro * * Lisa Anthony, Ph.D. * UMBC * Information Systems Department * 1000 Hilltop Circle * Baltimore, MD 21250 * lanthony@umbc.edu * * Jacob O. Wobbrock, Ph.D. * The Information School * University of Washington * Seattle, WA 98195-2840 * wobbrock@uw.edu * * The academic publication for the $P recognizer, and what should be * used to cite it, is: * * Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012). * Gestures as point clouds: A $P recognizer for user interface * prototypes. Proceedings of the ACM Int'l Conference on * Multimodal Interfaces (ICMI '12). Santa Monica, California * (October 22-26, 2012). New York: ACM Press, pp. 273-280. * * This software is distributed under the "New BSD License" agreement: * * Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and * Jacob O. Wobbrock. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the University Stefan cel Mare of Suceava, * University of Washington, nor UMBC, nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony * OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. **/ using System; namespace PDollarGestureRecognizer { /// /// Implements a gesture as a cloud of points (i.e., an unordered set of points). /// Gestures are normalized with respect to scale, translated to origin, and resampled into a fixed number of 32 points. /// public class Gesture { public Point[] Points = null; // gesture points (normalized) public string Name = ""; // gesture class private const int SAMPLING_RESOLUTION = 32; /// /// Constructs a gesture from an array of points /// /// public Gesture(Point[] points, string gestureName = "") { this.Name = gestureName; // normalizes the array of points with respect to scale, origin, and number of points this.Points = Scale(points); this.Points = TranslateTo(Points, Centroid(Points)); this.Points = Resample(Points, SAMPLING_RESOLUTION); } #region gesture pre-processing steps: scale normalization, translation to origin, and resampling /// /// Performs scale normalization with shape preservation into [0..1]x[0..1] /// /// /// private Point[] Scale(Point[] points) { float minx = float.MaxValue, miny = float.MaxValue, maxx = float.MinValue, maxy = float.MinValue; for (int i = 0; i < points.Length; i++) { if (minx > points[i].X) minx = points[i].X; if (miny > points[i].Y) miny = points[i].Y; if (maxx < points[i].X) maxx = points[i].X; if (maxy < points[i].Y) maxy = points[i].Y; } Point[] newPoints = new Point[points.Length]; float scale = Math.Max(maxx - minx, maxy - miny); for (int i = 0; i < points.Length; i++) newPoints[i] = new Point((points[i].X - minx) / scale, (points[i].Y - miny) / scale, points[i].StrokeID); return newPoints; } /// /// Translates the array of points by p /// /// /// /// private Point[] TranslateTo(Point[] points, Point p) { Point[] newPoints = new Point[points.Length]; for (int i = 0; i < points.Length; i++) newPoints[i] = new Point(points[i].X - p.X, points[i].Y - p.Y, points[i].StrokeID); return newPoints; } /// /// Computes the centroid for an array of points /// /// /// private Point Centroid(Point[] points) { float cx = 0, cy = 0; for (int i = 0; i < points.Length; i++) { cx += points[i].X; cy += points[i].Y; } return new Point(cx / points.Length, cy / points.Length, 0); } /// /// Resamples the array of points into n equally-distanced points /// /// /// /// public Point[] Resample(Point[] points, int n) { Point[] newPoints = new Point[n]; newPoints[0] = new Point(points[0].X, points[0].Y, points[0].StrokeID); int numPoints = 1; float I = PathLength(points) / (n - 1); // computes interval length float D = 0; for (int i = 1; i < points.Length; i++) { if (points[i].StrokeID == points[i - 1].StrokeID) { float d = Geometry.EuclideanDistance(points[i - 1], points[i]); if (D + d >= I) { Point firstPoint = points[i - 1]; while (D + d >= I) { // add interpolated point float t = Math.Min(Math.Max((I - D) / d, 0.0f), 1.0f); if (float.IsNaN(t)) t = 0.5f; newPoints[numPoints++] = new Point( (1.0f - t) * firstPoint.X + t * points[i].X, (1.0f - t) * firstPoint.Y + t * points[i].Y, points[i].StrokeID ); // update partial length d = D + d - I; D = 0; firstPoint = newPoints[numPoints - 1]; } D = d; } else D += d; } } if (numPoints == n - 1) // sometimes we fall a rounding-error short of adding the last point, so add it if so newPoints[numPoints++] = new Point(points[points.Length - 1].X, points[points.Length - 1].Y, points[points.Length - 1].StrokeID); return newPoints; } /// /// Computes the path length for an array of points /// /// /// private float PathLength(Point[] points) { float length = 0; for (int i = 1; i < points.Length; i++) if (points[i].StrokeID == points[i - 1].StrokeID) length += Geometry.EuclideanDistance(points[i - 1], points[i]); return length; } #endregion } }