From 9808c01be554ab210083af9c1ef7fd45fbd84c19 Mon Sep 17 00:00:00 2001
From: Zachary Zhang <zacharyzhang7@gmail.com>
Date: Wed, 25 Sep 2024 21:26:31 -0400
Subject: fix scribble gesture to use bezier curve intersections

---
 src/client/views/GestureOverlay.tsx | 196 +++++++++++++++++-------------------
 1 file changed, 91 insertions(+), 105 deletions(-)

(limited to 'src/client/views/GestureOverlay.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 935eeb251..7e396d475 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,4 +1,5 @@
 import * as fitCurve from 'fit-curve';
+import { Bezier } from 'bezier-js';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -95,7 +96,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     componentDidMount() {
         GestureOverlay.Instance = this;
     }
-
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
@@ -139,52 +139,52 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * to be deleted and then it will delete them.
      * @returns
      */
-    isScribble() {
+    isScribble(inkData: InkData) {
         const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
-        const cuspArray = this.getCusps();
-        const cuspBooleanArray: boolean[] = [];
+        console.log(inkData.map(ink => ({ x: ink.X, y: ink.Y })));
+        let intersectArray: boolean[] = [];
+        const cuspArray = this.getCusps(inkData);
+        console.log(cuspArray.length);
+        for (let i = 0; i < cuspArray.length; i++) {
+            intersectArray[i] = false;
+        }
         const docsToDelete: Doc[] = [];
-        const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs;
+        const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs.slice(0, -1);
         childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc)));
         if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) {
             //how many cusps the scribble hsa
-            if (cuspArray.length > 4) {
-                for (let i = 0; i < cuspArray.length - 2; i++) {
-                    let hasDocInTriangle = false;
-                    for (const doc of childDocs) {
-                        const point1 = cuspArray[i];
-                        const point2 = cuspArray[i + 1];
-                        const point3 = cuspArray[i + 2];
-                        const triangleObject = { p1: { X: point1.X, Y: point1.Y }, p2: { X: point2.X, Y: point2.Y }, p3: { X: point3.X, Y: point3.Y } };
-                        const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
-                        if (otherInk instanceof InkingStroke) {
-                            const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
-                            const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
-                            if (doc.title === 'line') {
-                                if (this.doesLineIntersectTriangle(otherScreenPts, triangleObject)) {
-                                    docsToDelete.push(doc);
-                                    hasDocInTriangle = true;
-                                    cuspBooleanArray.push(true);
-                                }
+            for (const doc of childDocs) {
+                const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
+                if (otherInk instanceof InkingStroke) {
+                    const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
+                    const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
+                    if (this.isRectangleOverlap(this.getExtremeCoordinates(otherScreenPts), this.getExtremeCoordinates(inkData))) {
+                        console.log('somethings in');
+                        const intersects = this.doInksIntersect(inkData, otherScreenPts);
+                        console.log(intersects);
+                        intersects.forEach(intersect => {
+                            let percentage = '';
+                            if (intersect.includes('/')) {
+                                const leftOfSlash = intersect.split('/')[0];
+                                percentage = leftOfSlash;
                             } else {
-                                if (this.isAnyPointInTriangle(triangleObject, otherScreenPts)) {
-                                    docsToDelete.push(doc);
-                                    hasDocInTriangle = true;
-                                    cuspBooleanArray.push(true);
-                                }
+                                percentage = intersect;
                             }
-                        }
+                            intersectArray[Math.floor((percentage as unknown as number) * cuspArray.length)] = true;
+                            docsToDelete.push(doc);
+                        });
                     }
-                    cuspBooleanArray.push(hasDocInTriangle);
-                }
-                if (this.determineIfScribble(cuspBooleanArray)) {
-                    docsToDelete.forEach(doc => {
-                        ffView?.ComponentView?.removeDocument?.(doc);
-                    });
-                    this._points = [];
-                    return true;
                 }
             }
+            console.log(intersectArray);
+            if (intersectArray.length > 3 && this.determineIfScribble(intersectArray)) {
+                console.log('is a scribble');
+                docsToDelete.forEach(doc => {
+                    ffView?.ComponentView?.removeDocument?.(doc);
+                });
+                this._points = [];
+                return true;
+            }
         }
         return false;
     }
@@ -198,7 +198,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         if (!cuspBooleanArray) {
             return false;
         }
-        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.25);
+        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.3);
         let hasObjectInFirstAndLast25 = true;
         for (let i = 0; i < quarterArrayLength; i++) {
             if (cuspBooleanArray[i] == false || cuspBooleanArray[cuspBooleanArray.length - 1 - i] == false) {
@@ -207,7 +207,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         }
         const trueCount = cuspBooleanArray.filter(value => value).length;
         const percentageTrues = trueCount / cuspBooleanArray.length;
-        return percentageTrues > 0.65 || hasObjectInFirstAndLast25;
+        return percentageTrues > 0.5 || hasObjectInFirstAndLast25;
     }
     /**
      * determines if two rectangles are overlapping each other
@@ -221,38 +221,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
         return !noOverlap;
     }
-    /**
-     * determines if there is a point in a triangle used to determine what triangles in the scribble have an object
-     * @param pt the point in question
-     * @param triangle the triangle with 3 points
-     * @returns true or false if point is in triangle
-     */
-    isPointInTriangle(pt: { X: number; Y: number }, triangle: { p1: { X: number; Y: number }; p2: { X: number; Y: number }; p3: { X: number; Y: number } }): boolean {
-        const area = (v1: { X: number; Y: number }, v2: { X: number; Y: number }, v3: { X: number; Y: number }) => Math.abs((v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y)) / 2.0);
-
-        const A = area(triangle.p1, triangle.p2, triangle.p3);
-
-        const A1 = area(pt, triangle.p2, triangle.p3);
-        const A2 = area(triangle.p1, pt, triangle.p3);
-        const A3 = area(triangle.p1, triangle.p2, pt);
-
-        return A === A1 + A2 + A3;
-    }
-    /**
-     * determines if any points in an array are in a triangle
-     * @param triangle the triangle with 3 points
-     * @param points the point in question
-     * @returns true or false if point is in triangle
-     */
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    isAnyPointInTriangle(triangle: any, points: any[]): boolean {
-        for (const point of points) {
-            if (this.isPointInTriangle(point, triangle)) {
-                return true;
-            }
-        }
-        return false;
-    }
     /**
      * determines if a line intersects a triangle. used for scribble gesture since the line doesnt have a lot
      * of points across is so isPointInTriangle will not work for it.
@@ -261,20 +229,25 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * @returns true or false if it intersects
      */
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    doesLineIntersectTriangle(line: any, triangle: any): boolean {
-        const edges = [
-            { start: triangle.p1, end: triangle.p2 },
-            { start: triangle.p2, end: triangle.p3 },
-            { start: triangle.p3, end: triangle.p1 },
-        ];
-
-        for (const edge of edges) {
-            if (this.doLinesIntersect(line, edge)) {
-                return true;
+    doInksIntersect(scribble: InkData, inkStroke: InkData): string[] {
+        let intersectArray: string[] = [];
+        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+        if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+            for (let i = 0; i < scribble.length - 3; i += 4) {
+                // iterate over each segment of bezier curve
+                for (let j = 0; j < inkStroke.length - 3; j += 4) {
+                    const intersectCurve: Bezier = InkField.Segment(scribble, i); // other curve
+                    const eraserCurve: Bezier = InkField.Segment(inkStroke, j); // eraser curve
+                    if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+                        const result = (ffView.ComponentView as CollectionFreeFormView).bintersects(intersectCurve, eraserCurve)[0];
+                        if (result !== undefined) {
+                            intersectArray.push(result.toString());
+                        }
+                    }
+                }
             }
         }
-
-        return false;
+        return intersectArray;
     }
     /**
      * used in doesLineIntersectTriangle, splits up the triangle into 3 lines and runs this method
@@ -284,20 +257,21 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * @returns
      */
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    doLinesIntersect(line1: any, line2: any): boolean {
-        const A = line1[0];
-        const B = line1[line1.length - 1];
-        const { start: C, end: D } = line2;
-        const denominator = (B.X - A.X) * (D.Y - C.Y) - (B.Y - A.Y) * (D.X - C.X);
-        if (denominator === 0) return false;
-
-        const numerator1 = (A.Y - C.Y) * (D.X - C.X) - (A.X - C.X) * (D.Y - C.Y);
-        const numerator2 = (A.Y - C.Y) * (B.X - A.X) - (A.X - C.X) * (B.Y - A.Y);
-
-        const r = numerator1 / denominator;
-        const s = numerator2 / denominator;
-
-        return r >= 0 && r <= 1 && s >= 0 && s <= 1;
+    doLinesIntersect(points: any, line: any): boolean {
+        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+        if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+            for (let i = 0; i < points.length - 3; i += 4) {
+                // iterate over each segment of bezier curve
+                for (let j = 0; j < line.length - 3; j += 4) {
+                    const intersectCurve: Bezier = InkField.Segment(points, i); // other curve
+                    const eraserCurve: Bezier = InkField.Segment(line, j); // eraser curve
+                    if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) {
+                        //(ffView.ComponentView as CollectionFreeFormView).bintersects();
+                    }
+                }
+            }
+        }
+        return false;
     }
 
     dryInk = () => {
@@ -343,18 +317,31 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     case Gestures.Rectangle:
                     case Gestures.Circle:
                         this.makeBezierPolygon(Name, true);
+                        setTimeout(() => {
+                            const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+                            const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1];
+                            //this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData);
+                        }, 1);
                         return this.dispatchGesture(name);
                     case Gestures.RightAngle:
                         return this.convertToText().length > 0;
                     default:
-                    case Gestures.Stroke:
-                        return this.isScribble();
                 }
             })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures));
             // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
-            if (!actionPerformed) this.dryInk();
+            if (!actionPerformed) {
+                this.dryInk();
+                setTimeout(() => {
+                    const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
+                    const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1];
+                    if (this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData)) {
+                        ffView?.ComponentView?.removeDocument?.(scribbleInk);
+                    }
+                }, 1);
+            }
         }
         this._points.length = 0;
+        //console.log((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData());
     };
     /**
      * used in the rightAngle gesture to convert handwriting into text. will only work on collections
@@ -374,7 +361,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     const bounds = DocumentView.getDocumentView(doc)?.getBounds;
                     if (bounds) {
                         const rect1 = { minX: bounds.left, maxX: bounds.right, minY: bounds.top, maxY: bounds.bottom };
-                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates())) {
+                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates(this._points))) {
                             if (doc.x < minX) {
                                 minX = doc.x;
                             }
@@ -405,8 +392,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * used to determine how many cusps and where the cusps are in order
      * @returns will return an array containing the coordinates of the sharp cusps
      */
-    getCusps() {
-        const points = this._points.map(p => ({ X: p.X, Y: p.Y }));
+    getCusps(points: InkData) {
         const arrayOfPoints: { X: number; Y: number }[] = [];
         arrayOfPoints.push(points[0]);
         for (let i = 0; i < points.length - 2; i++) {
@@ -424,8 +410,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
      * will look through an array of point data and return the coordinates of the smallest box that can fit all the points
      * @returns the minX,maxX,minY,maxY of the box
      */
-    getExtremeCoordinates() {
-        const coordinates = this._points;
+    getExtremeCoordinates(points: { X: number; Y: number }[]) {
+        const coordinates = points;
         if (coordinates.length === 0) {
             throw new Error('Coordinates array is empty');
         }
-- 
cgit v1.2.3-70-g09d2

