diff --git a/Runtime/Scripts/DollarRecognizer.cs b/Runtime/Scripts/DollarRecognizer.cs index caca96b2ac8925c54f759315926ee151046726eb..a4df17164d00bdba6d4b4dfbc4f13dc9b5d58dee 100644 --- a/Runtime/Scripts/DollarRecognizer.cs +++ b/Runtime/Scripts/DollarRecognizer.cs @@ -93,359 +93,360 @@ using UnityEngine; using System.Collections.Generic; -public class DollarRecognizer +namespace PDollarGestureRecognizer { - public class Unistroke + public class DollarRecognizer { - public string Name { get; set; } - public Vector2[] Points { get; set; } - public float Angle { get; set; } - public List<float> Vector { get; set; } - - public Unistroke() + public class Unistroke { - - } + public string Name { get; set; } + public Vector2[] Points { get; set; } + public float Angle { get; set; } + public List<float> Vector { get; set; } - public Unistroke(string name, IEnumerable<Vector2> points) - { - Name = string.Intern(name); - Points = DollarRecognizer.resample(points, _kNormalizedPoints); - Angle = DollarRecognizer.indicativeAngle(Points); - DollarRecognizer.rotateBy(Points, -Angle); - DollarRecognizer.scaleTo(Points, _kNormalizedSize); - DollarRecognizer.translateTo(Points, Vector2.zero); - Vector = DollarRecognizer.vectorize(Points); - } - - public override string ToString() - { - return string.Format("{0} #{1}", Name); - } - } + public Unistroke() + { - public struct Result - { - public Unistroke Match; - public float Score; - public float Angle; + } - public Result(Unistroke match, float score, float angle) - { - Match = match; - Score = score; - Angle = angle; + public Unistroke(string name, IEnumerable<Vector2> points) + { + Name = string.Intern(name); + Points = DollarRecognizer.resample(points, _kNormalizedPoints); + Angle = DollarRecognizer.indicativeAngle(Points); + DollarRecognizer.rotateBy(Points, -Angle); + DollarRecognizer.scaleTo(Points, _kNormalizedSize); + DollarRecognizer.translateTo(Points, Vector2.zero); + Vector = DollarRecognizer.vectorize(Points); + } + + public override string ToString() + { + return string.Format("{0} #{1}", Name); + } } - public static Result None + public struct Result { - get + public Unistroke Match; + public float Score; + public float Angle; + + public Result(Unistroke match, float score, float angle) + { + Match = match; + Score = score; + Angle = angle; + } + + public static Result None + { + get { return new Result(null, 0, 0); } + } + + public override string ToString() { - return new Result(null, 0, 0); + return string.Format("{0} @{2} ({1})", Match, Score, Angle); } } - public override string ToString() + public string[] EnumerateGestures() { - return string.Format("{0} @{2} ({1})", Match, Score, Angle); + List<string> result = new List<string>(); + + for (int i = 0; i < trainingSet.Count; i++) + { + if (!result.Contains(trainingSet[i].Name)) + result.Add(trainingSet[i].Name); + } + + return result.ToArray(); } - } - public string[] EnumerateGestures() - { - List<string> result = new List<string>(); - for (int i = 0; i < trainingSet.Count; i++) + protected const int _kNormalizedPoints = 64; + protected const float _kNormalizedSize = 256.0f; + protected const float _kAngleRange = 45.0f * Mathf.Deg2Rad; + protected const float _kAnglePrecision = 2.0f * Mathf.Deg2Rad; + protected static readonly float _kDiagonal = (Vector2.one * _kNormalizedSize).magnitude; + protected static readonly float _kHalfDiagonal = _kDiagonal * 0.5f; + + public List<Unistroke> trainingSet; + private List<Vector2> points = new(); + + public DollarRecognizer() { - if (!result.Contains(trainingSet[i].Name)) - result.Add(trainingSet[i].Name); + trainingSet = new List<Unistroke>(); } - return result.ToArray(); - } + public void ClearPoints() + { + points.Clear(); + } + public void AddPoints(Vector3[] points) + { + foreach (var point in points) + AddPoint(point.x, point.z); + } - protected const int _kNormalizedPoints = 64; - protected const float _kNormalizedSize = 256.0f; - protected const float _kAngleRange = 45.0f * Mathf.Deg2Rad; - protected const float _kAnglePrecision = 2.0f * Mathf.Deg2Rad; - protected static readonly float _kDiagonal = (Vector2.one * _kNormalizedSize).magnitude; - protected static readonly float _kHalfDiagonal = _kDiagonal * 0.5f; + private void AddPoint(float x, float z) + { + points.Add(new Vector2(x, z)); + } - public List<Unistroke> trainingSet; - private List<Vector2> points = new(); + public Unistroke SaveGesture(string name) + { + Unistroke stroke = new Unistroke(name, points); - public DollarRecognizer() - { - trainingSet = new List<Unistroke>(); - } - - public void ClearPoints() - { - points.Clear(); - } - - public void AddPoints(Vector3[] points) - { - foreach (var point in points) - AddPoint(point.x, point.z); - } - - private void AddPoint(float x, float z) - { - points.Add(new Vector2(x, z)); - } - - public Unistroke SaveGesture(string name) - { - Unistroke stroke = new Unistroke(name, points); + int index = trainingSet.Count; + trainingSet.Add(stroke); - int index = trainingSet.Count; - trainingSet.Add(stroke); + return stroke; + } - return stroke; - } + public Result Recognize() + { + return Recognize(trainingSet); + } - public Result Recognize() - { - return Recognize(trainingSet); - } - - public Result Recognize(List<Unistroke> trainingSetFiltered) - { - return Classify(trainingSetFiltered); - } + public Result Recognize(List<Unistroke> trainingSetFiltered) + { + return Classify(trainingSetFiltered); + } - private Result Classify(List<Unistroke> trainingSetRecognize) - { - Vector2[] working = resample(points, _kNormalizedPoints); - float angle = indicativeAngle(working); - rotateBy(working, -angle); - scaleTo(working, _kNormalizedSize); - translateTo(working, Vector2.zero); + private Result Classify(List<Unistroke> trainingSetRecognize) + { + Vector2[] working = resample(points, _kNormalizedPoints); + float angle = indicativeAngle(working); + rotateBy(working, -angle); + scaleTo(working, _kNormalizedSize); + translateTo(working, Vector2.zero); - List<float> v = vectorize(working); + List<float> v = vectorize(working); - float bestDist = float.PositiveInfinity; - int bestIndex = -1; + float bestDist = float.PositiveInfinity; + int bestIndex = -1; - for (int i = 0; i < trainingSetRecognize.Count; i++) - { - float dist = optimalCosineDistance(trainingSetRecognize[i].Vector, v); - if (dist < bestDist) + for (int i = 0; i < trainingSetRecognize.Count; i++) { - bestDist = dist; - bestIndex = i; + float dist = optimalCosineDistance(trainingSetRecognize[i].Vector, v); + if (dist < bestDist) + { + bestDist = dist; + bestIndex = i; + } } - } - if (bestIndex < 0) - return Result.None; - else - return new Result(trainingSetRecognize[bestIndex], 1.0f / bestDist, (trainingSetRecognize[bestIndex].Angle - angle) * Mathf.Rad2Deg); - } + if (bestIndex < 0) + return Result.None; + else + return new Result(trainingSetRecognize[bestIndex], 1.0f / bestDist, + (trainingSetRecognize[bestIndex].Angle - angle) * Mathf.Rad2Deg); + } - protected static Vector2[] resample(IEnumerable<Vector2> points, int targetCount) - { - List<Vector2> result = new List<Vector2>(); + protected static Vector2[] resample(IEnumerable<Vector2> points, int targetCount) + { + List<Vector2> result = new List<Vector2>(); - float interval = pathLength(points) / (targetCount - 1); - float accumulator = 0; + float interval = pathLength(points) / (targetCount - 1); + float accumulator = 0; - Vector2 previous = Vector2.zero; + Vector2 previous = Vector2.zero; - IEnumerator<Vector2> stepper = points.GetEnumerator(); - bool more = stepper.MoveNext(); - Vector2 point = stepper.Current; - result.Add(point); - previous = point; + IEnumerator<Vector2> stepper = points.GetEnumerator(); + bool more = stepper.MoveNext(); + Vector2 point = stepper.Current; + result.Add(point); + previous = point; - while (more) - { - Vector2 delta = point - previous; - float dist = delta.magnitude; - if ((accumulator + dist) >= interval) + while (more) { - float span = ((interval - accumulator) / dist); - Vector2 q = previous + (span * delta); - result.Add(q); - previous = q; - accumulator = 0; + Vector2 delta = point - previous; + float dist = delta.magnitude; + if ((accumulator + dist) >= interval) + { + float span = ((interval - accumulator) / dist); + Vector2 q = previous + (span * delta); + result.Add(q); + previous = q; + accumulator = 0; + } + else + { + accumulator += dist; + previous = point; + more = stepper.MoveNext(); + point = stepper.Current; + } } - else + + if (result.Count < targetCount) { - accumulator += dist; - previous = point; - more = stepper.MoveNext(); - point = stepper.Current; + // sometimes we fall a rounding-error short of adding the last point, so add it if so + result.Add(previous); } + + return result.ToArray(); } - - if (result.Count < targetCount) + + protected static Vector2 centroid(Vector2[] points) { - // sometimes we fall a rounding-error short of adding the last point, so add it if so - result.Add(previous); - } + Vector2 result = Vector2.zero; - return result.ToArray(); - } + for (int i = 0; i < points.Length; i++) + { + result += points[i]; + } - protected static Vector2 centroid(Vector2[] points) - { - Vector2 result = Vector2.zero; + result = result / (float)points.Length; + return result; + } - for (int i = 0; i < points.Length; i++) + protected static float indicativeAngle(Vector2[] points) { - result += points[i]; + Vector2 delta = centroid(points) - points[0]; + return Mathf.Atan2(delta.y, delta.x); } - result = result / (float)points.Length; - return result; - } - - protected static float indicativeAngle(Vector2[] points) - { - Vector2 delta = centroid(points) - points[0]; - return Mathf.Atan2(delta.y, delta.x); - } - - protected static void rotateBy(Vector2[] points, float angle) - { - Vector2 c = centroid(points); - float cos = Mathf.Cos(angle); - float sin = Mathf.Sin(angle); - - for (int i = 0; i < points.Length; i++) + protected static void rotateBy(Vector2[] points, float angle) { - Vector2 delta = points[i] - c; - points[i].x = (delta.x * cos) - (delta.y * sin); - points[i].y = (delta.x * sin) + (delta.y * cos); - points[i] += c; - } - } + Vector2 c = centroid(points); + float cos = Mathf.Cos(angle); + float sin = Mathf.Sin(angle); - protected static Rect boundingBox(Vector2[] points) - { - Rect result = new Rect(); - result.xMin = float.PositiveInfinity; - result.xMax = float.NegativeInfinity; - result.yMin = float.PositiveInfinity; - result.yMax = float.NegativeInfinity; + for (int i = 0; i < points.Length; i++) + { + Vector2 delta = points[i] - c; + points[i].x = (delta.x * cos) - (delta.y * sin); + points[i].y = (delta.x * sin) + (delta.y * cos); + points[i] += c; + } + } - for (int i = 0; i < points.Length; i++) + protected static Rect boundingBox(Vector2[] points) { - result.xMin = Mathf.Min(result.xMin, points[i].x); - result.xMax = Mathf.Max(result.xMax, points[i].x); - result.yMin = Mathf.Min(result.yMin, points[i].y); - result.yMax = Mathf.Max(result.yMax, points[i].y); - } + Rect result = new Rect(); + result.xMin = float.PositiveInfinity; + result.xMax = float.NegativeInfinity; + result.yMin = float.PositiveInfinity; + result.yMax = float.NegativeInfinity; - return result; - } + for (int i = 0; i < points.Length; i++) + { + result.xMin = Mathf.Min(result.xMin, points[i].x); + result.xMax = Mathf.Max(result.xMax, points[i].x); + result.yMin = Mathf.Min(result.yMin, points[i].y); + result.yMax = Mathf.Max(result.yMax, points[i].y); + } - protected static void scaleTo(Vector2[] points, float normalizedSize) - { - Rect bounds = boundingBox(points); - Vector2 scale = new Vector2(bounds.width, bounds.height) * (1.0f / normalizedSize); - for (int i = 0; i < points.Length; i++) - { - points[i].x = points[i].x * scale.x; - points[i].y = points[i].y * scale.y; + return result; } - } - protected static void translateTo(Vector2[] points, Vector2 newCentroid) - { - Vector2 c = centroid(points); - Vector2 delta = newCentroid - c; - - for (int i = 0; i < points.Length; i++) + protected static void scaleTo(Vector2[] points, float normalizedSize) { - points[i] = points[i] + delta; + Rect bounds = boundingBox(points); + Vector2 scale = new Vector2(bounds.width, bounds.height) * (1.0f / normalizedSize); + for (int i = 0; i < points.Length; i++) + { + points[i].x = points[i].x * scale.x; + points[i].y = points[i].y * scale.y; + } } - } - - protected static List<float> vectorize(Vector2[] points) - { - float sum = 0; - List<float> result = new List<float>(); - for (int i = 0; i < points.Length; i++) + protected static void translateTo(Vector2[] points, Vector2 newCentroid) { - result.Add(points[i].x); - result.Add(points[i].y); - sum += points[i].sqrMagnitude; + Vector2 c = centroid(points); + Vector2 delta = newCentroid - c; + + for (int i = 0; i < points.Length; i++) + { + points[i] = points[i] + delta; + } } - float mag = Mathf.Sqrt(sum); - for (int i = 0; i < result.Count; i++) + protected static List<float> vectorize(Vector2[] points) { - result[i] /= mag; - } + float sum = 0; + List<float> result = new List<float>(); - return result; - } + for (int i = 0; i < points.Length; i++) + { + result.Add(points[i].x); + result.Add(points[i].y); + sum += points[i].sqrMagnitude; + } - protected static float optimalCosineDistance(List<float> v1, List<float> v2) - { - if (v1.Count != v2.Count) - { - return float.NaN; + float mag = Mathf.Sqrt(sum); + for (int i = 0; i < result.Count; i++) + { + result[i] /= mag; + } + + return result; } - - float a = 0; - float b = 0; - for (int i = 0; i < v1.Count; i += 2) + protected static float optimalCosineDistance(List<float> v1, List<float> v2) { - a += (v1[i] * v2[i]) + (v1[i+1] * v2[i+1]); - b += (v1[i] * v2[i+1]) - (v1[i+1] * v2[i]); - } + if (v1.Count != v2.Count) + { + return float.NaN; + } - float angle = Mathf.Atan(b / a); - float result = Mathf.Acos((a * Mathf.Cos(angle)) + (b * Mathf.Sin(angle))); - return result; - } + float a = 0; + float b = 0; - protected static float distanceAtAngle(Vector2[] points, Unistroke test, float angle) - { - Vector2[] rotated = new Vector2[points.Length]; - rotateBy(rotated, angle); - return pathDistance(rotated, test.Points); - } + for (int i = 0; i < v1.Count; i += 2) + { + a += (v1[i] * v2[i]) + (v1[i + 1] * v2[i + 1]); + b += (v1[i] * v2[i + 1]) - (v1[i + 1] * v2[i]); + } - protected static float pathDistance(Vector2[] pts1, Vector2[] pts2) - { - if (pts1.Length != pts2.Length) - return float.NaN; + float angle = Mathf.Atan(b / a); + float result = Mathf.Acos((a * Mathf.Cos(angle)) + (b * Mathf.Sin(angle))); + return result; + } - float result = 0; - for (int i = 0; i < pts1.Length; i++) + protected static float distanceAtAngle(Vector2[] points, Unistroke test, float angle) { - result += (pts2[i] - pts1[i]).magnitude; + Vector2[] rotated = new Vector2[points.Length]; + rotateBy(rotated, angle); + return pathDistance(rotated, test.Points); } - return result / (float)pts1.Length; - } + protected static float pathDistance(Vector2[] pts1, Vector2[] pts2) + { + if (pts1.Length != pts2.Length) + return float.NaN; - protected static float pathLength(IEnumerable<Vector2> points) - { - float result = 0; - Vector2 previous = default; + float result = 0; + for (int i = 0; i < pts1.Length; i++) + { + result += (pts2[i] - pts1[i]).magnitude; + } - bool first = true; - foreach (Vector2 point in points) + return result / (float)pts1.Length; + } + + protected static float pathLength(IEnumerable<Vector2> points) { - if (first) - first = false; - else + float result = 0; + Vector2 previous = default; + + bool first = true; + foreach (Vector2 point in points) { - result += (point - previous).magnitude; + if (first) + first = false; + else + { + result += (point - previous).magnitude; + } + + previous = point; } - previous = point; + return result; } - - return result; } -} +} \ No newline at end of file diff --git a/Runtime/Scripts/Gesture.cs b/Runtime/Scripts/Gesture.cs index 15a5b56c703fe1d9eecf6a3920a39174edc2ed59..0dedc7415718fa7c9b3be4f907d9a3adea9a49ba 100644 --- a/Runtime/Scripts/Gesture.cs +++ b/Runtime/Scripts/Gesture.cs @@ -58,7 +58,6 @@ * SUCH DAMAGE. **/ using System; -using UnityEngine; namespace PDollarGestureRecognizer { diff --git a/Runtime/Scripts/GestureIO.cs b/Runtime/Scripts/GestureIO.cs index 8579cfa40c997be5de68ad451a1ae29a5059e959..b602fd37b37fa37e143ad50191ad96599871f85b 100644 --- a/Runtime/Scripts/GestureIO.cs +++ b/Runtime/Scripts/GestureIO.cs @@ -2,7 +2,6 @@ using System.IO; using System.Collections.Generic; using System.Xml; using PDollarGestureRecognizer; -using UnityEngine; namespace PDollarDemo { diff --git a/Runtime/Scripts/PRecognizer.cs b/Runtime/Scripts/PRecognizer.cs index cb0e8fc518ee77e376e149074c7cfe34ba6d6080..bebf83aba540c52924da977a7dcc7971645f9179 100644 --- a/Runtime/Scripts/PRecognizer.cs +++ b/Runtime/Scripts/PRecognizer.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Collections.Generic; using PDollarGestureRecognizer; using QDollarGestureRecognizer; using UnityEngine;