diff options
Diffstat (limited to 'src/pen-gestures/ndollar.ts')
-rw-r--r-- | src/pen-gestures/ndollar.ts | 252 |
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); } |