aboutsummaryrefslogtreecommitdiff
path: root/src/pen-gestures/ndollar.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pen-gestures/ndollar.ts')
-rw-r--r--src/pen-gestures/ndollar.ts252
1 files changed, 137 insertions, 115 deletions
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 3ee9506cb..ff7f7310b 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -1,4 +1,5 @@
-import { GestureUtils } from './GestureUtils';
+/* eslint-disable no-use-before-define */
+import { Gestures } from './GestureTypes';
/**
* The $N Multistroke Recognizer (JavaScript version)
@@ -69,20 +70,34 @@ import { GestureUtils } from './GestureUtils';
* 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.
- **/
+ * */
//
// Point class
//
export class Point {
- constructor(public X: number, public Y: number) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public X: number,
+ public Y: number
+ ) {
+ /* empty */
+ }
}
//
// Rectangle class
//
export class Rectangle {
- constructor(public X: number, public Y: number, public Width: number, public Height: number) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public X: number,
+ public Y: number,
+ public Width: number,
+ public Height: number
+ ) {
+ /* empty */
+ }
}
//
@@ -93,7 +108,11 @@ export class Unistroke {
public StartUnitVector: Point;
public Vector: number[];
- constructor(public Name: string, useBoundedRotationInvariance: boolean, points: Point[]) {
+ constructor(
+ public Name: string,
+ useBoundedRotationInvariance: boolean,
+ points: Point[]
+ ) {
this.Points = Resample(points, NumPoints);
const radians = IndicativeAngle(this.Points);
this.Points = RotateBy(this.Points, -radians);
@@ -121,15 +140,15 @@ export class Multistroke {
this.NumStrokes = strokes.length; // number of individual strokes
const order = new Array(strokes.length); // array of integer indices
- for (var i = 0; i < strokes.length; i++) {
+ for (let i = 0; i < strokes.length; i++) {
order[i] = i; // initialize
}
- const orders = new Array(); // array of integer arrays
- HeapPermute(strokes.length, order, /*out*/ orders);
+ const orders = [] as any[]; // array of integer arrays
+ HeapPermute(strokes.length, order, /* out */ orders);
const unistrokes = MakeUnistrokes(strokes, orders); // returns array of point arrays
this.Unistrokes = new Array(unistrokes.length); // unistrokes for this multistroke
- for (var j = 0; j < unistrokes.length; j++) {
+ for (let j = 0; j < unistrokes.length; j++) {
this.Unistrokes[j] = new Unistroke(this.Name, useBoundedRotationInvariance, unistrokes[j]);
}
}
@@ -139,7 +158,14 @@ export class Multistroke {
// Result class
//
export class Result {
- constructor(public Name: string, public Score: any, public Time: any) {}
+ // eslint-disable-next-line no-useless-constructor
+ constructor(
+ public Name: string,
+ public Score: any,
+ public Time: any
+ ) {
+ /* empty */
+ }
}
//
@@ -172,46 +198,42 @@ export class NDollarRecognizer {
//
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Rectangle,
+ Gestures.Rectangle,
useBoundedRotationInvariance,
- new Array(
- new Array(
- new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200),
- new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230),
- new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160),
- new Point(106, 146), //new Point(80, 150), new Point(50, 146),
- new Point(30, 143)
- )
- )
+ new Array([
+ new Point(30, 146), // new Point(29, 160), new Point(30, 180), new Point(31, 200),
+ new Point(30, 222), // new Point(50, 219), new Point(70, 225), new Point(90, 230),
+ new Point(106, 225), // new Point(100, 200), new Point(106, 180), new Point(110, 160),
+ new Point(106, 146), // new Point(80, 150), new Point(50, 146),
+ new Point(30, 143),
+ ])
)
);
- this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))));
+ this.Multistrokes.push(new Multistroke(Gestures.Line, useBoundedRotationInvariance, [[new Point(12, 347), new Point(119, 347)]]));
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Triangle, // equilateral
+ Gestures.Triangle, // equilateral
useBoundedRotationInvariance,
- new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
+ new Array([new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)])
)
);
this.Multistrokes.push(
new Multistroke(
- GestureUtils.Gestures.Circle,
+ Gestures.Circle,
useBoundedRotationInvariance,
- new Array(
- new Array(
- new Point(200, 250),
- new Point(240, 230),
- new Point(248, 210),
- new Point(248, 190),
- new Point(240, 170),
- new Point(200, 150),
- new Point(160, 170),
- new Point(151, 190),
- new Point(151, 210),
- new Point(160, 230),
- new Point(201, 250)
- )
- )
+ new Array([
+ new Point(200, 250),
+ new Point(240, 230),
+ new Point(248, 210),
+ new Point(248, 190),
+ new Point(240, 170),
+ new Point(200, 150),
+ new Point(160, 170),
+ new Point(151, 190),
+ new Point(151, 210),
+ new Point(160, 230),
+ new Point(201, 250),
+ ])
)
);
NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes
@@ -287,25 +309,26 @@ export class NDollarRecognizer {
//
}
- Recognize = (strokes: any[], useBoundedRotationInvariance: boolean = false, requireSameNoOfStrokes: boolean = false, useProtractor: boolean = true) => {
+ Recognize = (strokes: { X: number; Y: number }[][], useBoundedRotationInvariance: boolean = false, requireSameNoOfStrokes: boolean = false, useProtractor: boolean = true) => {
const t0 = Date.now();
const points = CombineStrokes(strokes); // make one connected unistroke from the given strokes
const candidate = new Unistroke('', useBoundedRotationInvariance, points);
- var u = -1;
- var b = +Infinity;
+ let u = -1;
+ let b = +Infinity;
for (
- var i = 0;
+ let i = 0;
i < this.Multistrokes.length;
i++ // for each multistroke template
) {
if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) {
// optional -- only attempt match when same # of component strokes
- for (const unistroke of this.Multistrokes[i].Unistrokes) {
+ // eslint-disable-next-line no-loop-func
+ this.Multistrokes[i].Unistrokes.forEach(unistroke => {
// for each unistroke within this multistroke
if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) {
// strokes start in the same direction
- var d;
+ let d;
if (useProtractor) {
d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor
} else {
@@ -316,7 +339,7 @@ export class NDollarRecognizer {
u = i; // multistroke owner of unistroke
}
}
- }
+ });
}
}
const t1 = Date.now();
@@ -325,12 +348,12 @@ export class NDollarRecognizer {
AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
- var num = 0;
- for (const multistroke of this.Multistrokes) {
+ let num = 0;
+ this.Multistrokes.forEach(multistroke => {
if (multistroke.Name === name) {
num++;
}
- }
+ });
return num;
};
@@ -343,11 +366,11 @@ export class NDollarRecognizer {
//
// Private helper functions from here on down
//
-function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
+function HeapPermute(n: number, order: any[], /* out */ orders: any[]) {
if (n === 1) {
orders[orders.length] = order.slice(); // append copy
} else {
- for (var i = 0; i < n; i++) {
+ for (let i = 0; i < n; i++) {
HeapPermute(n - 1, order, orders);
if (n % 2 === 1) {
// swap 0, n-1
@@ -364,47 +387,44 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
}
}
-function MakeUnistrokes(strokes: any, orders: any) {
- const unistrokes = new Array(); // array of point arrays
- for (const order of orders) {
+function MakeUnistrokes(strokes: any, orders: any[]) {
+ const unistrokes = [] as any[]; // array of point arrays
+ orders.forEach(order => {
for (
- var b = 0;
- b < Math.pow(2, order.length);
+ let b = 0;
+ b < order.length ** 2;
b++ // use b's bits for directions
) {
- const unistroke = new Array(); // array of points
- for (var i = 0; i < order.length; i++) {
- var pts;
+ const unistroke = [] as any[]; // array of points
+ for (let i = 0; i < order.length; i++) {
+ let pts;
+ // eslint-disable-next-line no-bitwise
if (((b >> i) & 1) === 1) {
// is b's bit at index i on?
pts = strokes[order[i]].slice().reverse(); // copy and reverse
} else {
pts = strokes[order[i]].slice(); // copy
}
- for (const point of pts) {
+ pts.forEach((point: any) => {
unistroke[unistroke.length] = point; // append points
- }
+ });
}
unistrokes[unistrokes.length] = unistroke; // add one unistroke to set
}
- }
+ });
return unistrokes;
}
-function CombineStrokes(strokes: any) {
- const points = new Array();
- for (const stroke of strokes) {
- for (const { X, Y } of stroke) {
- points[points.length] = new Point(X, Y);
- }
- }
+function CombineStrokes(strokes: { X: number; Y: number }[][]) {
+ const points: Point[] = [];
+ strokes.forEach(stroke => stroke.forEach(({ X, Y }) => points.push(new Point(X, Y))));
return points;
}
function Resample(points: any, n: any) {
const I = PathLength(points) / (n - 1); // interval length
- var D = 0.0;
+ let D = 0.0;
const newpoints = new Array(points[0]);
- for (var i = 1; i < points.length; i++) {
+ for (let i = 1; i < points.length; i++) {
const d = Distance(points[i - 1], points[i]);
if (D + d >= I) {
const qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
@@ -425,55 +445,55 @@ function IndicativeAngle(points: any) {
const c = Centroid(points);
return Math.atan2(c.Y - points[0].Y, c.X - points[0].X);
}
-function RotateBy(points: any, radians: any) {
+function RotateBy(points: Point[], radians: any) {
// rotates points around centroid
const c = Centroid(points);
const cos = Math.cos(radians);
const sin = Math.sin(radians);
- const newpoints = new Array();
- for (const point of points) {
+ const newpoints: Point[] = [];
+ points.forEach(point => {
const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X;
const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y;
- newpoints[newpoints.length] = new Point(qx, qy);
- }
+ newpoints.push(new Point(qx, qy));
+ });
return newpoints;
}
function ScaleDimTo(points: any, size: any, ratio1D: any) {
// scales bbox uniformly for 1D, non-uniformly for 2D
const B = BoundingBox(points);
const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
- const newpoints = new Array();
- for (const { X, Y } of points) {
+ const newpoints: Point[] = [];
+ points.forEach(({ X, Y }) => {
const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width);
const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height);
newpoints[newpoints.length] = new Point(qx, qy);
- }
+ });
return newpoints;
}
function TranslateTo(points: any, pt: any) {
// translates points' centroid
const c = Centroid(points);
- const newpoints = new Array();
- for (const { X, Y } of points) {
+ const newpoints: Point[] = [];
+ points.forEach(({ X, Y }) => {
const qx = X + pt.X - c.X;
const qy = Y + pt.Y - c.Y;
newpoints[newpoints.length] = new Point(qx, qy);
- }
+ });
return newpoints;
}
function Vectorize(points: any, useBoundedRotationInvariance: any) {
// for Protractor
- var cos = 1.0;
- var sin = 0.0;
+ let cos = 1.0;
+ let sin = 0.0;
if (useBoundedRotationInvariance) {
const iAngle = Math.atan2(points[0].Y, points[0].X);
const baseOrientation = (Math.PI / 4.0) * Math.floor((iAngle + Math.PI / 8.0) / (Math.PI / 4.0));
cos = Math.cos(baseOrientation - iAngle);
sin = Math.sin(baseOrientation - iAngle);
}
- var sum = 0.0;
- const vector = new Array<number>();
- for (var i = 0; i < points.length; i++) {
+ let sum = 0.0;
+ const vector: number[] = [];
+ for (let i = 0; i < points.length; i++) {
const newX = points[i].X * cos - points[i].Y * sin;
const newY = points[i].Y * cos + points[i].X * sin;
vector[vector.length] = newX;
@@ -481,16 +501,16 @@ function Vectorize(points: any, useBoundedRotationInvariance: any) {
sum += newX * newX + newY * newY;
}
const magnitude = Math.sqrt(sum);
- for (var i = 0; i < vector.length; i++) {
+ for (let i = 0; i < vector.length; i++) {
vector[i] /= magnitude;
}
return vector;
}
function OptimalCosineDistance(v1: any, v2: any) {
// for Protractor
- var a = 0.0;
- var b = 0.0;
- for (var i = 0; i < v1.length; i += 2) {
+ let a = 0.0;
+ let b = 0.0;
+ for (let i = 0; i < v1.length; i += 2) {
a += v1[i] * v2[i] + v1[i + 1] * v2[i + 1];
b += v1[i] * v2[i + 1] - v1[i + 1] * v2[i];
}
@@ -498,18 +518,20 @@ function OptimalCosineDistance(v1: any, v2: any) {
return Math.acos(a * Math.cos(angle) + b * Math.sin(angle));
}
function DistanceAtBestAngle(points: any, T: any, a: any, b: any, threshold: any) {
- var x1 = Phi * a + (1.0 - Phi) * b;
- var f1 = DistanceAtAngle(points, T, x1);
- var x2 = (1.0 - Phi) * a + Phi * b;
- var f2 = DistanceAtAngle(points, T, x2);
+ let x1 = Phi * a + (1.0 - Phi) * b;
+ let f1 = DistanceAtAngle(points, T, x1);
+ let x2 = (1.0 - Phi) * a + Phi * b;
+ let f2 = DistanceAtAngle(points, T, x2);
while (Math.abs(b - a) > threshold) {
if (f1 < f2) {
+ // eslint-disable-next-line no-param-reassign
b = x2;
x2 = x1;
f2 = f1;
x1 = Phi * a + (1.0 - Phi) * b;
f1 = DistanceAtAngle(points, T, x1);
} else {
+ // eslint-disable-next-line no-param-reassign
a = x1;
x1 = x2;
f1 = f2;
@@ -523,34 +545,34 @@ function DistanceAtAngle(points: any, T: any, radians: any) {
const newpoints = RotateBy(points, radians);
return PathDistance(newpoints, T.Points);
}
-function Centroid(points: any) {
- var x = 0.0,
- y = 0.0;
- for (const point of points) {
- x += point.X;
- y += point.Y;
- }
+function Centroid(points: Point[]) {
+ let x = 0.0;
+ let y = 0.0;
+ points.forEach(({ X, Y }) => {
+ x += X;
+ y += Y;
+ });
x /= points.length;
y /= points.length;
return new Point(x, y);
}
-function BoundingBox(points: any) {
- var minX = +Infinity,
- maxX = -Infinity,
- minY = +Infinity,
- maxY = -Infinity;
- for (const { X, Y } of points) {
+function BoundingBox(points: Point[]) {
+ let minX = +Infinity;
+ let maxX = -Infinity;
+ let minY = +Infinity;
+ let maxY = -Infinity;
+ points.forEach(({ X, Y }) => {
minX = Math.min(minX, X);
minY = Math.min(minY, Y);
maxX = Math.max(maxX, X);
maxY = Math.max(maxY, Y);
- }
+ });
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
function PathDistance(pts1: any, pts2: any) {
// average distance between corresponding points in two paths
- var d = 0.0;
- for (var i = 0; i < pts1.length; i++) {
+ let d = 0.0;
+ for (let i = 0; i < pts1.length; i++) {
// assumes pts1.length == pts2.length
d += Distance(pts1[i], pts2[i]);
}
@@ -558,8 +580,8 @@ function PathDistance(pts1: any, pts2: any) {
}
function PathLength(points: any) {
// length traversed by a point path
- var d = 0.0;
- for (var i = 1; i < points.length; i++) {
+ let d = 0.0;
+ for (let i = 1; i < points.length; i++) {
d += Distance(points[i - 1], points[i]);
}
return d;
@@ -570,9 +592,9 @@ function Distance(p1: any, p2: any) {
const dy = p2.Y - p1.Y;
return Math.sqrt(dx * dx + dy * dy);
}
-function CalcStartUnitVector(points: any, index: any) {
+function CalcStartUnitVector(points: Point[], index: any) {
// start angle from points[0] to points[index] normalized as a unit vector
- const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y);
+ const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
return new Point(v.X / len, v.Y / len);
}