aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx192
-rw-r--r--src/fields/InkField.ts9
2 files changed, 121 insertions, 80 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ce4acb7ad..4aac49469 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { DateField } from "../../../../fields/DateField";
import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData, Intersection } from "../../../../fields/InkField";
+import { InkData, InkField, InkTool, PointData, Intersection, Segment } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -53,6 +53,7 @@ import React = require("react");
import { ColorScheme } from "../../../util/SettingsManager";
import { Bezier } from "bezier-js";
import { GestureOverlay } from "../../GestureOverlay";
+import { constants } from "perf_hooks";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -648,6 +649,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
if (CurrentUserUtils.SelectedTool !== InkTool.None) {
this._currPoint = { X: e.clientX, Y: e.clientY };
+ // Erasing ink strokes if intersections occur.
const eraserIntersections: Intersection[] = this.getEraserIntersections();
if (eraserIntersections) this.eraseInkStrokes(eraserIntersections);
}
@@ -666,68 +668,28 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
- @action
+ /**
+ * Iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
+ * and deletes the original stroke.
+ * @param eraserIntersections The intersections made by the eraser.
+ */
eraseInkStrokes = (eraserIntersections: Intersection[]) => {
eraserIntersections.forEach(intersect => {
- var t1, t2;
- var distT1: number, distT2: number;
- distT1 = distT2 = Number.MAX_SAFE_INTEGER;
- const t = intersect.t;
const ink = intersect.ink;
- const ctrlPoints = Cast(ink?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? [];
- for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
- const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
- const curve = new Bezier(array.map(p => ({ x: p.X, y: p.Y })));
- const inkIntersections: number[] = this.getInkIntersections(curve);
- inkIntersections.forEach(currentT => {
- const diff = t - currentT;
- if (diff > 0 && diff < distT1) {
- distT1 = diff;
- t1 = currentT;
- } else if (diff < 0 && diff < distT2) {
- distT2 = -diff;
- t2 = currentT;
- }
- });
-
- // Normal case --> deletion of entire stroke
- if (!t1 && !t2) {
- if (!this._deleteList.includes(ink)) {
- this._deleteList.push(ink);
- // Lowering ink opacity to give the user a visual indicator of deletion.
- ink.Document.opacity = 0.5;
- }
- } else if (t1 && !t2) {
- const splitCurve = curve.split(t1);
- const leftInkData: PointData[] = splitCurve.left.points.map(p => ({ X: p.x, Y: p.y }));
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, leftInkData);
- if (!this._deleteList.includes(ink)) {
- this._deleteList.push(ink);
- // Lowering ink opacity to give the user a visual indicator of deletion.
- ink.Document.opacity = 0.5;
- }
- } else if (!t1 && t2) {
- const splitCurve = curve.split(t2);
- const rightInkData: PointData[] = splitCurve.right.points.map(p => ({ X: p.x, Y: p.y }));
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, rightInkData);
- if (!this._deleteList.includes(ink)) {
- this._deleteList.push(ink);
- // Lowering ink opacity to give the user a visual indicator of deletion.
- ink.Document.opacity = 0.5;
- }
- } else if (t1 && t2) {
- const splitCurve1 = curve.split(t1);
- const leftInkData: PointData[] = splitCurve1.left.points.map(p => ({ X: p.x, Y: p.y }));
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, leftInkData);
- const splitCurve2 = curve.split(t2);
- const rightInkData: PointData[] = splitCurve2.right.points.map(p => ({ X: p.x, Y: p.y }));
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, rightInkData);
-
- if (!this._deleteList.includes(ink)) {
- this._deleteList.push(ink);
- // Lowering ink opacity to give the user a visual indicator of deletion.
- ink.Document.opacity = 0.5;
- }
+ if (ink) {
+ if (!this._deleteList.includes(ink)) {
+ const segments: Segment[] = this.segmentInkStroke(ink, intersect.index ?? 0);
+ segments.forEach(segment => {
+ const newInkData: PointData[] = [];
+ // Appending all curves of the current segment together in order to render a single new stroke.
+ segment.forEach(curve => {
+ newInkData.push(...curve.points.map(p => ({ X: p.x, Y: p.y })));
+ });
+ GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, newInkData);
+ });
+ this._deleteList.push(ink);
+ // Lowering ink opacity to give the user a visual indicator of deletion.
+ ink.Document.opacity = 0.5;
}
}
});
@@ -756,9 +718,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }
});
if (inkView && intersects) {
- intersects.forEach(t => {
- intersections.push({ t: +t.toString(), ink: inkView });
- });
+ for (const val of intersects) {
+ // Casting t-value from type: (string | number) to number for comparisons.
+ const t = +val.toString();
+ var unique: boolean = true;
+ // Ensuring there are no duplicate intersections in the list returned.
+ for (const prevIntersect of intersections) {
+ if (prevIntersect.t === t) {
+ unique = false;
+ break;
+ }
+ }
+ if (unique) intersections.push({ t: +t.toString(), ink: inkView, curve: curve, index: i });
+ }
}
}
}
@@ -766,36 +738,100 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return intersections;
}
- getInkIntersections = (curve: Bezier): number[] => {
- const intersections: number[] = [];
- const selfIntersect = curve.selfintersects();
- if (selfIntersect) {
- selfIntersect.forEach(t => {
- intersections.push(+t.toString());
- });
+ /**
+ * Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the
+ * ink stroke intersects any other ink stroke (including itself).
+ * @param ink The ink DocumentView intersected by the eraser.
+ * @param excludeCurve The index of the curve in the ink document that the eraser intersection occurred.
+ * @returns The ink stroke represented as a list of segments, excluding the segment in which the eraser intersection occurred.
+ */
+ @action
+ segmentInkStroke = (ink: DocumentView, excludeCurve: number): Segment[] => {
+ const segments: Segment[] = [];
+ var segment: Segment = [];
+ var excludeSegment: boolean = false;
+ const ctrlPoints = Cast(ink?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? [];
+ // Iterating through all of the curves of the intersected ink stroke.
+ for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
+ // Performing "deletion" of a segment by excluding the segment in which intersection with the eraser occurred.
+ if (i === excludeCurve) excludeSegment = true;
+ const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
+ const curve = new Bezier(array.map(p => ({ x: p.X, y: p.Y })));
+ // Getting all t-value intersections of the current curve with all other curves.
+ const tVals: number[] = this.getInkIntersections(i, ink, curve);
+ if (tVals.length > 0) {
+ tVals.sort();
+ const curveCopy = new Bezier(curve.points);
+ // Splitting only by the first t-value.
+ const pair = curveCopy.split(tVals[0]);
+ segment.push(pair.left);
+ if (!excludeSegment) {
+ segments.push(segment);
+ segment = [pair.right];
+ excludeSegment = false;
+ }
+ // Splitting by the number of t-values returned.
+ // for (var index = 0; index < tVals.length; index++) {
+ // const curveCopy = new Bezier(curve.points);
+ // if (index === 0) {
+ // const pair = curveCopy.split(tVals[index]);
+ // segment.push(pair.left);
+ // segments.push(segment);
+ // segment = [pair.right];
+ // } else if (index === tVals.length - 1) {
+ // segments.push(segment);
+ // const pair = curveCopy.split(tVals[index]);
+ // segment = [pair.right];
+ // } else {
+ // segments.push(segment);
+ // const curve = curveCopy.split(index, index + 1);
+ // segment = [curve];
+ // }
+ // }
+ } else {
+ segment.push(curve);
+ }
}
+ if (!excludeSegment) segments.push(segment);
+ return segments;
+ }
+ /**
+ * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all
+ * ink strokes in the current collection.
+ * @param i The index of the current curve within the inkData of the intersected ink stroke.
+ * @param ink The intersected DocumentView of the ink stroke.
+ * @param curve The current curve of the intersected ink stroke.
+ * @returns A list of all t-values at which intersections occur at the current curve of the intersected ink stroke.
+ */
+ getInkIntersections = (i: number, ink: DocumentView, curve: Bezier): number[] => {
+ const tVals: number[] = [];
+ // Iterating through all ink strokes in the current freeform collection.
this.childDocs
.filter(doc => doc.type === DocumentType.INK)
.forEach(doc => {
const currInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView);
const currCtrls = Cast(currInk?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? [];
- for (var i = 0; i < currCtrls.length - 3; i += 4) {
- const currArray = [currCtrls[i], currCtrls[i + 1], currCtrls[i + 2], currCtrls[i + 3]];
+ for (var j = 0; j < currCtrls.length - 3; j += 4) {
+ const exclude = i === j || i === j - 4 || i === j + 4;
+ const sameDoc = ink?.Document === currInk?.Document;
+ // Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
+ if (sameDoc && exclude) continue;
+ const currArray = [currCtrls[j], currCtrls[j + 1], currCtrls[j + 2], currCtrls[j + 3]];
const currCurve = new Bezier(currArray.map(p => ({ x: p.X, y: p.Y })));
const intersect = curve.intersects(currCurve);
- if (intersect) {
- intersect.forEach(t => {
- intersections.push(+this.parseTValue(t.toString()));
+ if (intersect.length > 0) {
+ intersect.forEach(val => {
+ // Converting the Bezier.js Split type to a t-value number.
+ const t = +val.toString().split("/")[0];
+ if (!tVals.includes(t)) tVals.push(t);
});
}
}
});
- return intersections;
+ return tVals;
}
- parseTValue = (t: string) => t.split("/", 1)[0];
-
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
if (!e.cancelBubble) {
const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 1d50b5e0d..f61313674 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -4,6 +4,7 @@ import { Deserializable } from "../client/util/SerializationHelper";
import { Copy, ToScriptString, ToString } from "./FieldSymbols";
import { ObjectField } from "./ObjectField";
import { DocumentView } from "../client/views/nodes/DocumentView";
+import { Bezier } from "bezier-js";
// Helps keep track of the current ink tool in use.
export enum InkTool {
@@ -22,10 +23,14 @@ export interface PointData {
}
export interface Intersection {
- t: number;
- ink: DocumentView;
+ t?: number;
+ ink?: DocumentView;
+ curve?: Bezier;
+ index?: number;
}
+export type Segment = Array<Bezier>;
+
// Defines an ink as an array of points.
export type InkData = Array<PointData>;