aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts8
-rw-r--r--src/client/util/InteractionUtils.tsx26
-rw-r--r--src/client/util/SettingsManager.tsx4
-rw-r--r--src/client/views/GestureOverlay.tsx49
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx6
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx9
-rw-r--r--src/pen-gestures/GestureUtils.ts48
-rw-r--r--src/pen-gestures/ndollar.ts230
8 files changed, 204 insertions, 176 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1c9f89fa0..bfa868dbc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -12,6 +12,7 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
+import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
@@ -631,9 +632,9 @@ export class CurrentUserUtils {
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} },
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }},
// { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
- { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
- // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
- { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }},
+ { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", _readOnly_);}`} },
+ { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", _readOnly_);}`} },
+ { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
{ title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
@@ -797,6 +798,7 @@ export class CurrentUserUtils {
doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true);
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
+ doc.activeTool = InkTool.None;
doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");;
doc.activeInkWidth ?? (doc.activeInkWidth = 1);
doc.activeInkBezier ?? (doc.activeInkBezier = "0");
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 85700da37..6622b498d 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,4 +1,5 @@
import React = require('react');
+import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Utils } from '../../Utils';
import './InteractionUtils.scss';
@@ -186,7 +187,7 @@ export namespace InteractionUtils {
export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
//pointer is up (first and last points are the same)
- if (shape === 'arrow' || shape === 'line' || shape === 'circle') {
+ if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) {
//if arrow or line, the two end points should be the starting and the ending point
var left = points[0].X;
var top = points[0].Y;
@@ -208,7 +209,7 @@ export namespace InteractionUtils {
left = points[0].X;
bottom = points[points.length - 1].Y;
top = points[0].Y;
- if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') {
+ if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
//switch left/right and top/bottom if needed
if (left > right) {
const temp = right;
@@ -224,20 +225,20 @@ export namespace InteractionUtils {
}
points = [];
switch (shape) {
- case 'rectangle':
+ case GestureUtils.Gestures.Rectangle:
points.push({ X: left, Y: top });
points.push({ X: right, Y: top });
points.push({ X: right, Y: bottom });
points.push({ X: left, Y: bottom });
points.push({ X: left, Y: top });
break;
- case 'triangle':
+ case GestureUtils.Gestures.Triangle:
points.push({ X: left, Y: bottom });
points.push({ X: right, Y: bottom });
points.push({ X: (right + left) / 2, Y: top });
points.push({ X: left, Y: bottom });
break;
- case 'circle':
+ case GestureUtils.Gestures.Circle:
const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
@@ -253,7 +254,7 @@ export namespace InteractionUtils {
points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(left, right) - centerX, 2)) + centerY });
break;
- case 'line':
+ case GestureUtils.Gestures.Line:
points.push({ X: left, Y: top });
points.push({ X: right, Y: bottom });
break;
@@ -266,17 +267,14 @@ export namespace InteractionUtils {
* @param type - InteractionUtils.(PENTYPE | ERASERTYPE | MOUSETYPE | TOUCHTYPE)
*/
export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+ // prettier-ignore
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
- case PENTYPE:
- return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
- case ERASERTYPE:
- return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
- case TOUCHTYPE:
- return e.pointerType === TOUCHTYPE;
- default:
- return e.pointerType === type;
+ case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+ case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
+ case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
}
+ return e.pointerType === type;
}
/**
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index a185c8936..92032a8f9 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -186,6 +186,10 @@ export class SettingsManager extends React.Component<{}> {
<input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
<div className="preferences-check">Show button labels</div>
</div>
+ <div>
+ <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGesturs(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} />
+ <div className="preferences-check">Recognize ink Gesturs</div>
+ </div>
</div>
);
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 850688e7e..23b03bc50 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -684,7 +684,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
}
//if any of the shape is activated in the CollectionFreeFormViewChrome
else if (this.InkShape) {
- this.makePolygon(this.InkShape, false);
+ this.makeBezierPolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points.length = 0;
if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
@@ -699,27 +699,17 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
let actionPerformed = false;
if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Box:
- actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box);
- break;
+ case GestureUtils.Gestures.Triangle:
+ case GestureUtils.Gestures.Rectangle:
+ case GestureUtils.Gestures.Circle:
+ this.makeBezierPolygon(result.Name, true);
case GestureUtils.Gestures.StartBracket:
- actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket);
- break;
case GestureUtils.Gestures.EndBracket:
- actionPerformed = this.dispatchGesture('endbracket');
+ actionPerformed = this.dispatchGesture(result.Name);
break;
case GestureUtils.Gestures.Line:
actionPerformed = this.handleLineGesture();
break;
- case GestureUtils.Gestures.Triangle:
- actionPerformed = this.makePolygon('triangle', true);
- break;
- case GestureUtils.Gestures.Circle:
- actionPerformed = this.makePolygon('circle', true);
- break;
- case GestureUtils.Gestures.Rectangle:
- actionPerformed = this.makePolygon('rectangle', true);
- break;
case GestureUtils.Gestures.Scribble:
console.log('scribble');
break;
@@ -760,11 +750,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
CollectionFreeFormViewChrome.Instance?.primCreated();
};
- makePolygon = (shape: string, gesture: boolean) => {
- //take off gesture recognition for now
- if (gesture) {
- return false;
- }
+ makeBezierPolygon = (shape: string, gesture: boolean) => {
const xs = this._points.map(p => p.X);
const ys = this._points.map(p => p.Y);
var right = Math.max(...xs);
@@ -794,7 +780,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
left = this._points[0].X;
bottom = this._points[this._points.length - 2].Y;
top = this._points[0].Y;
- if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') {
+ if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
if (left > right) {
const temp = right;
right = left;
@@ -809,9 +795,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
}
this._points.length = 0;
switch (shape) {
- //must push an extra point in the end so InteractionUtils knows pointer is up.
- //must be (points[0].X,points[0]-1)
- case 'rectangle':
+ case GestureUtils.Gestures.Rectangle:
this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: top });
@@ -834,7 +818,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
break;
- case 'triangle':
+ case GestureUtils.Gestures.Triangle:
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -852,7 +836,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
this._points.push({ X: left, Y: bottom });
break;
- case 'circle':
+ case GestureUtils.Gestures.Circle:
// Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
// making the curves indistinguishable from a circle.
// Source: https://spencermortensen.com/articles/bezier-circle/
@@ -884,7 +868,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
break;
- case 'line':
+ case GestureUtils.Gestures.Line:
if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
lastx = firstx;
}
@@ -897,7 +881,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
this._points.push({ X: lastx, Y: lasty });
this._points.push({ X: lastx, Y: lasty });
break;
- case 'arrow':
+ case GestureUtils.Gestures.Arrow:
const x1 = left;
const y1 = top;
const x2 = right;
@@ -914,12 +898,11 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
this._points.push({ X: x3, Y: y3 });
this._points.push({ X: x4, Y: y4 });
this._points.push({ X: x2, Y: y2 });
- // this._points.push({ X: x1, Y: y1 - 1 });
}
- return true;
+ return false;
};
- dispatchGesture = (gesture: 'box' | 'line' | 'startbracket' | 'endbracket' | 'stroke' | 'scribble' | 'text', stroke?: InkData, data?: any) => {
+ dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
return (
target?.dispatchEvent(
@@ -1136,7 +1119,7 @@ ScriptingGlobals.add(function resetPen() {
}, 'resets the pen tool');
ScriptingGlobals.add(
function createText(text: any, x: any, y: any) {
- GestureOverlay.Instance.dispatchGesture('text', [{ X: x, Y: y }], text);
+ GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: x, Y: y }], text);
},
'creates a text document with inputted text and coordinates',
'(text: any, x: any, y: any)'
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d4e1c999..daf69d4f6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -568,6 +568,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
+ default:
+ case GestureUtils.Gestures.Circle:
+ case GestureUtils.Gestures.Rectangle:
+ case GestureUtils.Gestures.Triangle:
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
@@ -597,7 +601,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.addDocument(inkDoc);
e.stopPropagation();
break;
- case GestureUtils.Gestures.Box:
+ case GestureUtils.Gestures.Rectangle:
const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 6d1751b25..6eaf3c31a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -11,6 +11,7 @@ import { InkTool } from '../../../../fields/InkField';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
+import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
import { aggregateBounds, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -85,6 +86,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
static SetShowLabels(show: boolean) {
Doc.UserDoc()._showLabel = show;
}
+ static GetRecognizeGestures() {
+ return BoolCast(Doc.UserDoc()._recognizeGestures);
+ }
+ static SetRecognizeGesturs(show: boolean) {
+ Doc.UserDoc()._recognizeGestures = show;
+ }
// Determining UI Specs
@computed get label() {
@@ -775,7 +782,7 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean)
if (checkResult) {
return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent';
}
- if (['circle', 'square', 'line'].includes(tool)) {
+ if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) {
if (GestureOverlay.Instance.InkShape === tool) {
Doc.ActiveTool = InkTool.None;
GestureOverlay.Instance.InkShape = InkTool.None;
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 65f2bf80c..2d3b1fdb8 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,40 +1,34 @@
-import { Rect } from "react-measure";
-import { PointData } from "../fields/InkField";
-import { NDollarRecognizer } from "./ndollar";
+import { Rect } from 'react-measure';
+import { PointData } from '../fields/InkField';
+import { NDollarRecognizer } from './ndollar';
export namespace GestureUtils {
export class GestureEvent {
- constructor(
- readonly gesture: Gestures,
- readonly points: PointData[],
- readonly bounds: Rect,
- readonly text?: any
- ) { }
+ constructor(readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, readonly text?: any) {}
}
- export interface GestureEventDisposer { (): void; }
+ export interface GestureEventDisposer {
+ (): void;
+ }
- export function MakeGestureTarget(
- element: HTMLElement,
- func: (e: Event, ge: GestureEvent) => void
- ): GestureEventDisposer {
+ export function MakeGestureTarget(element: HTMLElement, func: (e: Event, ge: GestureEvent) => void): GestureEventDisposer {
const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
- element.addEventListener("dashOnGesture", handler);
- return () => element.removeEventListener("dashOnGesture", handler);
+ element.addEventListener('dashOnGesture', handler);
+ return () => element.removeEventListener('dashOnGesture', handler);
}
export enum Gestures {
- Box = "box",
- Line = "line",
- StartBracket = "startbracket",
- EndBracket = "endbracket",
- Stroke = "stroke",
- Scribble = "scribble",
- Text = "text",
- Triangle = "triangle",
- Circle = "circle",
- Rectangle = "rectangle",
+ Line = 'line',
+ StartBracket = 'startbracket',
+ EndBracket = 'endbracket',
+ Stroke = 'stroke',
+ Scribble = 'scribble',
+ Text = 'text',
+ Triangle = 'triangle',
+ Circle = 'circle',
+ Rectangle = 'rectangle',
+ Arrow = 'arrow',
}
export const GestureRecognizer = new NDollarRecognizer(false);
-} \ No newline at end of file
+}
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index ecd8df3e7..b10a9da17 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -1,4 +1,4 @@
-import { GestureUtils } from "./GestureUtils";
+import { GestureUtils } from './GestureUtils';
/**
* The $N Multistroke Recognizer (JavaScript version)
@@ -69,20 +69,20 @@ 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) { }
+ constructor(public X: number, public Y: number) {}
}
//
// Rectangle class
//
export class Rectangle {
- constructor(public X: number, public Y: number, public Width: number, public Height: number) { }
+ constructor(public X: number, public Y: number, public Width: number, public Height: number) {}
}
//
@@ -113,8 +113,11 @@ export class Multistroke {
public NumStrokes: number;
public Unistrokes: Unistroke[];
- constructor(public Name: string, useBoundedRotationInvariance: boolean, strokes: any[]) // constructor
- {
+ constructor(
+ public Name: string,
+ useBoundedRotationInvariance: boolean,
+ strokes: any[] // constructor
+ ) {
this.NumStrokes = strokes.length; // number of individual strokes
const order = new Array(strokes.length); // array of integer indices
@@ -136,13 +139,13 @@ export class Multistroke {
// Result class
//
export class Result {
- constructor(public Name: string, public Score: any, public Time: any) { }
+ constructor(public Name: string, public Score: any, public Time: any) {}
}
//
// NDollarRecognizer constants
//
-const NumMultistrokes = 7;
+const NumMultistrokes = 6;
const NumPoints = 96;
const SquareSize = 250.0;
const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35)
@@ -152,7 +155,7 @@ const HalfDiagonal = 0.5 * Diagonal;
const AngleRange = Deg2Rad(45.0);
const AnglePrecision = Deg2Rad(2.0);
const Phi = 0.5 * (-1.0 + Math.sqrt(5.0)); // Golden Ratio
-const StartAngleIndex = (NumPoints / 8); // eighth of gesture length
+const StartAngleIndex = NumPoints / 8; // eighth of gesture length
const AngleSimilarityThreshold = Deg2Rad(30.0);
//
@@ -164,48 +167,68 @@ export class NDollarRecognizer {
/**
* @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS.
*/
- constructor(useBoundedRotationInvariance: boolean) // constructor
- {
+ constructor(
+ useBoundedRotationInvariance: boolean // constructor
+ ) {
//
// one predefined multistroke for each multistroke type
//
this.Multistrokes = new Array(NumMultistrokes);
- this.Multistrokes[0] = new Multistroke(GestureUtils.Gestures.Box, useBoundedRotationInvariance, new Array(
+ this.Multistrokes[0] = new Multistroke(
+ GestureUtils.Gestures.Rectangle,
+ useBoundedRotationInvariance,
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[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(
- new Array(new Point(12, 347), new Point(119, 347))
- ));
- this.Multistrokes[2] = new Multistroke(GestureUtils.Gestures.StartBracket, useBoundedRotationInvariance, new Array(
- // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150))
- new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150))
- ));
- this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
- // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
- // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
- new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
- ));
- this.Multistrokes[4] = new Multistroke(GestureUtils.Gestures.Triangle, useBoundedRotationInvariance, new Array(
- new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))
- ));
- this.Multistrokes[5] = new Multistroke(GestureUtils.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))
- ));
- this.Multistrokes[6] = new Multistroke(GestureUtils.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)
+ )
+ )
+ );
+ this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))));
+ this.Multistrokes[2] = new Multistroke(
+ GestureUtils.Gestures.StartBracket,
+ useBoundedRotationInvariance,
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 Point(29, 220))
- ));
-
+ // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150))
+ new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150))
+ )
+ );
+ this.Multistrokes[3] = new Multistroke(
+ GestureUtils.Gestures.EndBracket,
+ useBoundedRotationInvariance,
+ new Array(
+ // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
+ // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
+ new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
+ )
+ );
+ this.Multistrokes[4] = new Multistroke(
+ GestureUtils.Gestures.Triangle, // equilateral
+ useBoundedRotationInvariance,
+ new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
+ );
+ this.Multistrokes[5] = new Multistroke(
+ GestureUtils.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)
+ )
+ )
+ );
//
// PREDEFINED STROKES
//
@@ -281,23 +304,25 @@ export class NDollarRecognizer {
Recognize = (strokes: any[], 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);
+ const candidate = new Unistroke('', useBoundedRotationInvariance, points);
var u = -1;
var b = +Infinity;
- for (var 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) // for each unistroke within this multistroke
- {
- if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction
- {
+ for (
+ var 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) {
+ // for each unistroke within this multistroke
+ if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) {
+ // strokes start in the same direction
var d;
if (useProtractor) {
d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor
- }
- else {
+ } else {
d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N)
}
if (d < b) {
@@ -309,8 +334,8 @@ export class NDollarRecognizer {
}
}
const t1 = Date.now();
- return (u === -1) ? null : new Result(this.Multistrokes[u].Name, useProtractor ? (1.0 - b) : (1.0 - b / HalfDiagonal), t1 - t0);
- }
+ return u === -1 ? null : new Result(this.Multistrokes[u].Name, useProtractor ? 1.0 - b : 1.0 - b / HalfDiagonal, t1 - t0);
+ };
AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
@@ -321,15 +346,14 @@ export class NDollarRecognizer {
}
}
return num;
- }
+ };
DeleteUserGestures = () => {
this.Multistrokes.length = NumMultistrokes; // clear any beyond the original set
return NumMultistrokes;
- }
+ };
}
-
//
// Private helper functions from here on down
//
@@ -339,11 +363,13 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
} else {
for (var i = 0; i < n; i++) {
HeapPermute(n - 1, order, orders);
- if (n % 2 === 1) { // swap 0, n-1
+ if (n % 2 === 1) {
+ // swap 0, n-1
const tmp = order[0];
order[0] = order[n - 1];
order[n - 1] = tmp;
- } else { // swap i, n-1
+ } else {
+ // swap i, n-1
const tmp = order[i];
order[i] = order[n - 1];
order[n - 1] = tmp;
@@ -355,15 +381,18 @@ 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) {
- for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions
- {
+ for (
+ var b = 0;
+ b < Math.pow(2, order.length);
+ b++ // use b's bits for directions
+ ) {
const unistroke = new Array(); // array of points
for (var i = 0; i < order.length; i++) {
var pts;
- if (((b >> i) & 1) === 1) {// is b's bit at index i on?
+ if (((b >> i) & 1) === 1) {
+ // is b's bit at index i on?
pts = strokes[order[i]].slice().reverse(); // copy and reverse
- }
- else {
+ } else {
pts = strokes[order[i]].slice(); // copy
}
for (const point of pts) {
@@ -391,17 +420,17 @@ function Resample(points: any, n: any) {
const newpoints = new Array(points[0]);
for (var i = 1; i < points.length; i++) {
const d = Distance(points[i - 1], points[i]);
- if ((D + d) >= I) {
+ if (D + d >= I) {
const qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
const qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y);
const q = new Point(qx, qy);
newpoints[newpoints.length] = q; // append new point 'q'
points.splice(i, 0, q); // insert 'q' at position i in points s.t. 'q' will be the next i
D = 0.0;
- }
- else D += d;
+ } else D += d;
}
- if (newpoints.length === n - 1) {// sometimes we fall a rounding-error short of adding the last point, so add it if so
+ if (newpoints.length === n - 1) {
+ // sometimes we fall a rounding-error short of adding the last point, so add it if so
newpoints[newpoints.length] = new Point(points[points.length - 1].X, points[points.length - 1].Y);
}
return newpoints;
@@ -410,8 +439,8 @@ 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) // rotates points around centroid
-{
+function RotateBy(points: any, radians: any) {
+ // rotates points around centroid
const c = Centroid(points);
const cos = Math.cos(radians);
const sin = Math.sin(radians);
@@ -423,8 +452,8 @@ function RotateBy(points: any, radians: any) // rotates points around centroid
}
return newpoints;
}
-function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniformly for 1D, non-uniformly for 2D
-{
+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();
@@ -435,8 +464,8 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform
}
return newpoints;
}
-function TranslateTo(points: any, pt: any) // translates points' centroid
-{
+function TranslateTo(points: any, pt: any) {
+ // translates points' centroid
const c = Centroid(points);
const newpoints = new Array();
for (const { X, Y } of points) {
@@ -446,8 +475,8 @@ function TranslateTo(points: any, pt: any) // translates points' centroid
}
return newpoints;
}
-function Vectorize(points: any, useBoundedRotationInvariance: any) // for Protractor
-{
+function Vectorize(points: any, useBoundedRotationInvariance: any) {
+ // for Protractor
var cos = 1.0;
var sin = 0.0;
if (useBoundedRotationInvariance) {
@@ -471,8 +500,8 @@ function Vectorize(points: any, useBoundedRotationInvariance: any) // for Protra
}
return vector;
}
-function OptimalCosineDistance(v1: any, v2: any) // for Protractor
-{
+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) {
@@ -509,7 +538,8 @@ function DistanceAtAngle(points: any, T: any, radians: any) {
return PathDistance(newpoints, T.Points);
}
function Centroid(points: any) {
- var x = 0.0, y = 0.0;
+ var x = 0.0,
+ y = 0.0;
for (const point of points) {
x += point.X;
y += point.Y;
@@ -519,7 +549,10 @@ function Centroid(points: any) {
return new Point(x, y);
}
function BoundingBox(points: any) {
- var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity;
+ var minX = +Infinity,
+ maxX = -Infinity,
+ minY = +Infinity,
+ maxY = -Infinity;
for (const { X, Y } of points) {
minX = Math.min(minX, X);
minY = Math.min(minY, Y);
@@ -528,38 +561,41 @@ function BoundingBox(points: any) {
}
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
-function PathDistance(pts1: any, pts2: any) // average distance between corresponding points in two paths
-{
+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++) {// assumes pts1.length == pts2.length
+ for (var i = 0; i < pts1.length; i++) {
+ // assumes pts1.length == pts2.length
d += Distance(pts1[i], pts2[i]);
}
return d / pts1.length;
}
-function PathLength(points: any) // length traversed by a point path
-{
+function PathLength(points: any) {
+ // length traversed by a point path
var d = 0.0;
for (var i = 1; i < points.length; i++) {
d += Distance(points[i - 1], points[i]);
}
return d;
}
-function Distance(p1: any, p2: any) // distance between two points
-{
+function Distance(p1: any, p2: any) {
+ // distance between two points
const dx = p2.X - p1.X;
const dy = p2.Y - p1.Y;
return Math.sqrt(dx * dx + dy * dy);
}
-function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector
-{
+function CalcStartUnitVector(points: any, 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 len = Math.sqrt(v.X * v.X + v.Y * v.Y);
return new Point(v.X / len, v.Y / len);
}
-function AngleBetweenUnitVectors(v1: any, v2: any) // gives acute angle between unit vectors from (0,0) to v1, and (0,0) to v2
-{
- const n = (v1.X * v2.X + v1.Y * v2.Y);
+function AngleBetweenUnitVectors(v1: any, v2: any) {
+ // gives acute angle between unit vectors from (0,0) to v1, and (0,0) to v2
+ const n = v1.X * v2.X + v1.Y * v2.Y;
const c = Math.max(-1.0, Math.min(1.0, n)); // ensure [-1,+1]
return Math.acos(c); // arc cosine of the vector dot product
}
-function Deg2Rad(d: any) { return (d * Math.PI / 180.0); } \ No newline at end of file
+function Deg2Rad(d: any) {
+ return (d * Math.PI) / 180.0;
+}