aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts4
-rw-r--r--src/client/views/InkingStroke.tsx129
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx91
3 files changed, 105 insertions, 119 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 291d7c799..226051f7d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -348,6 +348,10 @@ export namespace Utils {
return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
}
+ export function ptDistance(p1: { x: number; y: number }, p2: { x: number; y: number }) {
+ return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+ }
+
export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
const r = l + w,
b = t + h;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 1ae96edfd..03acd5393 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -331,87 +331,54 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
};
- coordWithSlope = (distance: number, slope: number) => {
- return Math.sqrt((distance * distance) / (1 + slope * slope))
- }
-
-
- splitByEraser = (startInkCoords: {X: number; Y: number}, inkCoords: {X: number; Y: number}) => {
-
+ splitByEraser = (startInkCoordsIn: { X: number; Y: number }, endInkCoordsIn: { X: number; Y: number }) => {
const radius = ActiveEraserWidth() / 2 + 3; // reduce values to avoid extreme radii
- console.log("radius", radius);
- const ctrlPtDist = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius
-
- var perpSlope = (inkCoords.Y - startInkCoords.Y) / (inkCoords.X - startInkCoords.X);
- if (perpSlope > 500) {
- perpSlope = 500; // avoid ridiculously high/infinity slopes
- } else if (perpSlope < -500) {
- perpSlope = -500
- } else if (perpSlope === 0) {
- perpSlope = 0.002
- }
- const slope = - 1 / perpSlope
- console.log("slope", slope);
- var points: { X: number; Y: number }[] = []
- if (startInkCoords.X > inkCoords.X) {
- points = [
- { X: startInkCoords.X, Y: startInkCoords.Y },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)},
-
- { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) },
- // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- ];
- } else {
- points = [
- { X: startInkCoords.X, Y: startInkCoords.Y },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- // { X: startInkCoords.X + this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y + slope * this.coordWithSlope(ctrlPtDist, slope)},
-
- { X: inkCoords.X + this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X + this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y + slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) + this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) },
- { X: inkCoords.X + this.coordWithSlope(radius, perpSlope), Y: inkCoords.Y + perpSlope * this.coordWithSlope(radius, perpSlope) },
- // { X: inkCoords.X - this.coordWithSlope(radius, perpSlope) - this.coordWithSlope(ctrlPtDist, slope), Y: inkCoords.Y - perpSlope * this.coordWithSlope(radius, perpSlope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- { X: inkCoords.X - this.coordWithSlope(radius, slope) + this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) + perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) },
- { X: inkCoords.X - this.coordWithSlope(radius, slope) - this.coordWithSlope(ctrlPtDist, perpSlope), Y: inkCoords.Y - slope * this.coordWithSlope(radius, slope) - perpSlope * this.coordWithSlope(ctrlPtDist, perpSlope) },
-
- // { X: startInkCoords.X - this.coordWithSlope(ctrlPtDist, slope), Y: startInkCoords.Y - slope * this.coordWithSlope(ctrlPtDist, slope) },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- { X: startInkCoords.X, Y: startInkCoords.Y },
- ];
- }
-
- const eraserCircle = new InkField(points);
- return eraserCircle;
- }
+ const c = 0.551915024494; // circle tangent length to side ratio
+ const movement = { x: endInkCoordsIn.X - startInkCoordsIn.X, y: endInkCoordsIn.Y - startInkCoordsIn.Y };
+ const moveLen = Math.sqrt(movement.x ** 2 + movement.y ** 2);
+ const direction = { x: (movement.x / moveLen) * radius, y: (movement.y / moveLen) * radius };
+ const normal = { x: -direction.y, y: direction.x }; // prettier-ignore
+
+ const startCoords = { X: startInkCoordsIn.X - direction.x, Y: startInkCoordsIn.Y - direction.y };
+ const endCoords = { X: endInkCoordsIn.X + direction.x, Y: endInkCoordsIn.Y + direction.y };
+ return new InkField([
+ // left bot arc
+ { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore
+ { X: startCoords.X + normal.x * c, Y: startCoords.Y + normal.y * c }, // prettier-ignore
+ { X: startCoords.X + direction.x + normal.x - direction.x * c, Y: startCoords.Y + direction.y + normal.y - direction.y * c },
+ { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore
+
+ // bot
+ { X: startCoords.X + direction.x + normal.x, Y: startCoords.Y + direction.y + normal.y }, // prettier-ignore
+ { X: startCoords.X + direction.x + normal.x + direction.x * c, Y: startCoords.Y + direction.y + normal.y + direction.y * c },
+ { X: endCoords.X - direction.x + normal.x - direction.x * c, Y: endCoords.Y - direction.y + normal.y - direction.y * c }, // prettier-ignore
+ { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore
+
+ // right bot arc
+ { X: endCoords.X - direction.x + normal.x, Y: endCoords.Y - direction.y + normal.y }, // prettier-ignore
+ { X: endCoords.X - direction.x + normal.x + direction.x * c, Y: endCoords.Y - direction.y + normal.y + direction.y * c}, // prettier-ignore
+ { X: endCoords.X + normal.x * c, Y: endCoords.Y + normal.y * c }, // prettier-ignore
+ { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore
+
+ // right top arc
+ { X: endCoords.X, Y: endCoords.Y }, // prettier-ignore
+ { X: endCoords.X - normal.x * c, Y: endCoords.Y - normal.y * c }, // prettier-ignore
+ { X: endCoords.X - direction.x - normal.x + direction.x * c, Y: endCoords.Y - direction.y - normal.y + direction.y * c}, // prettier-ignore
+ { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore
+
+ // top
+ { X: endCoords.X - direction.x - normal.x, Y: endCoords.Y - direction.y - normal.y }, // prettier-ignore
+ { X: endCoords.X - direction.x - normal.x - direction.x * c, Y: endCoords.Y - direction.y - normal.y - direction.y * c}, // prettier-ignore
+ { X: startCoords.X + direction.x - normal.x + direction.x * c, Y: startCoords.Y + direction.y - normal.y + direction.y * c },
+ { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore
+
+ // left top arc
+ { X: startCoords.X + direction.x - normal.x, Y: startCoords.Y + direction.y - normal.y }, // prettier-ignore
+ { X: startCoords.X + direction.x - normal.x - direction.x * c, Y: startCoords.Y + direction.y - normal.y - direction.y * c }, // prettier-ignore
+ { X: startCoords.X - normal.x * c, Y: startCoords.Y - normal.y * c }, // prettier-ignore
+ { X: startCoords.X, Y: startCoords.Y }, // prettier-ignore
+ ]);
+ };
_subContentView: ViewBoxInterface | undefined;
setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
@@ -627,4 +594,4 @@ export function ActiveInkBezierApprox(): string {
}
export function ActiveEraserWidth(): number {
return Number(ActiveInkPen()?.eraserWidth);
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 78dae87c3..9df9c5492 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -620,6 +620,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case InkTool.SegmentEraser:
case InkTool.RadiusEraser:
this._batch = UndoManager.StartBatch('collectionErase');
+ this._eraserPts.length = 0;
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
case InkTool.None:
@@ -728,6 +729,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
_eraserLock = 0;
+ _eraserPts: number[][] = []; // keep track of the last few eraserPts to make the eraser circle 'stretch'
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
@@ -737,6 +739,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
const currPoint = { X: e.clientX, Y: e.clientY };
+ this._eraserPts.push([currPoint.X, currPoint.Y]);
+ this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed.
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
@@ -749,7 +753,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const segments =
Doc.ActiveTool === InkTool.SegmentEraser
? this.segmentErase(intersect.inkView, intersect.t) // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
- : this.radiusErase(intersect.inkView, { X: down[0], Y: down[1] }, currPoint);
+ : this.radiusErase(intersect.inkView, { X: this._eraserPts[0][0], Y: this._eraserPts[0][1] }, currPoint);
segments?.forEach(segment =>
this.forceStrokeGesture(
e,
@@ -832,16 +836,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const segments: Segment[] = [];
const startInkCoords = ink.ComponentView?.ptFromScreen?.(startPt);
const inkCoords = ink.ComponentView?.ptFromScreen?.(screenEraserPt); // coordinates in ink space
- if (!inkCoords) return [];
+ if (!inkCoords || !startInkCoords) return [];
var eraseSegment: Segment = []; // for eraser visualization
const inkStroke = ink?.ComponentView as InkingStroke;
const eraserStroke: InkData = inkStroke.splitByEraser(startInkCoords, inkCoords).inkData;
const strokeToTVals: Map<InkingStroke, number[]> = new Map<InkingStroke, number[]>();
- for (var i = 0; i < eraserStroke.length - 3; i +=4) {
+ for (var i = 0; i < eraserStroke.length - 3; i += 4) {
eraseSegment.push(InkField.Segment(eraserStroke, i)); // for eraser visualization
- this.getOtherInkIntersections(i, eraserStroke, strokeToTVals);
+ this.getOtherInkIntersections(ink, i, eraserStroke, strokeToTVals);
}
strokeToTVals.forEach((tVals, inkStroke) => {
var segment1: Segment = [];
@@ -852,7 +856,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
var hasSplit = false;
var continueErasing = false;
-
+
// below is curve splitting logic
if (tVals.length) {
for (var i = 0; i < inkData.length - 3; i += 4) {
@@ -881,12 +885,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
} else if (tVals.length === 1) {
if (tVals[0] > currCurveT && tVals[0] < currCurveT + 1) {
- if (tVals[0] < Math.floor(inkData.length / 4) - tVals[0]) {
+ // this heuristic for determine which segment to keep is not quite right even though it will work most of the time.
+ // We should really store the eraser intersection normal in getOtherInkIntersections,
+ // and then test its dot product with the tangent of the stroke at the intersection point.
+ // if the dot product is positive, then erase the first part of the stroke, otherwise the second.
+ const leftDist = Utils.ptDistance({ x: inkCoords.X, y: inkCoords.Y }, inkSegment.points.lastElement());
+ const rightDist = Utils.ptDistance({ x: inkCoords.X, y: inkCoords.Y }, inkSegment.points[0]);
+ const splits = inkSegment.split(tVals[0] - currCurveT);
+ if (leftDist < rightDist) {
// if it's on the first end
- segment1.push(inkSegment.split(tVals[0] - currCurveT, 1));
+ segment1.push(splits.left);
hasSplit = true;
} else {
- segment1.push(inkSegment.split(0, tVals[0] - currCurveT));
+ segment1.push(splits.right);
hasSplit = true;
}
} else {
@@ -1084,6 +1095,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection
// call in a test for linearity
bintersects = (curve: Bezier, otherCurve: Bezier) => {
+ if ((curve as any)._linear) {
+ // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line
+ const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] });
+ if (intersections.length) {
+ const intPt = otherCurve.get(intersections[0]);
+ const intT = curve.project(intPt).t;
+ return intT ? [intT] : [];
+ }
+ }
if ((otherCurve as any)._linear) {
return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] });
}
@@ -1135,43 +1155,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
/**
- * For radius erase, a function to find an eraser's intersections with all ink strokes on the freeform canvas.
+ * For radius erase, a function to find an eraser's intersections with a single ink stroke
+ * @param otherInkDocVIew the ink document that is being erased
* @param i the index of the eraser's segment
* @param points the eraser's ink data points
* @param strokeToTVals Map of InkingStroke to the tVals of its intersections with the eraser
- * @returns
+ * @returns
*/
- getOtherInkIntersections = (i: number, points: InkData, strokeToTVals: Map<InkingStroke, number[]>): Map<InkingStroke, number[]> => {
- // const tVals: [InkingStroke, number[]] = [];
- // Iterating through all ink strokes in the current freeform collection.
- this.childDocs
- .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
- .forEach(doc => {
- // InkingStroke of other ink strokes
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke;
- // ink Data of other ink strokes
- const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
- for (var j = 0; j < otherInkData.length - 3; j += 4) {
- const curve: Bezier = InkField.Segment(points, i); // eraser curve
- const otherCurve: Bezier = InkField.Segment(otherInkData, j); // other curve
- this.bintersects(otherCurve, curve).forEach((val: string | number, k: number) => {
- // Converting the Bezier.js Split type to a t-value number.
- const t = +val.toString().split('/')[0];
- if (k % 2 === 0) {
- // here, add to the map
- const inkList = strokeToTVals.get(otherInk);
- if (inkList !== undefined) {
- const inList = inkList.some(val => Math.abs(val - (t + Math.floor(j / 4))) <= 0.01);
- if (!inList) {
- inkList.push(t + Math.floor(j/4));
- }
- } else {
- strokeToTVals.set(otherInk, [t + Math.floor(j/4)]);
- }
+ getOtherInkIntersections = (otherInkDocView: DocumentView, i: number, points: InkData, strokeToTVals: Map<InkingStroke, number[]>): Map<InkingStroke, number[]> => {
+ // InkingStroke of other ink strokes
+ const otherInk = otherInkDocView.ComponentView as InkingStroke;
+ // ink Data of other ink strokes
+ const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
+ for (var j = 0; j < otherInkData.length - 3; j += 4) {
+ const curve: Bezier = InkField.Segment(points, i); // eraser curve
+ const otherCurve: Bezier = InkField.Segment(otherInkData, j); // other curve
+ this.bintersects(otherCurve, curve).forEach((val: string | number, k: number) => {
+ // Converting the Bezier.js Split type to a t-value number.
+ const t = +val.toString().split('/')[0];
+ if (k % 2 === 0) {
+ // here, add to the map
+ const inkList = strokeToTVals.get(otherInk);
+ if (inkList !== undefined) {
+ const inList = inkList.some(val => Math.abs(val - (t + Math.floor(j / 4))) <= 0.01);
+ if (!inList) {
+ inkList.push(t + Math.floor(j / 4));
}
- });
+ } else {
+ strokeToTVals.set(otherInk, [t + Math.floor(j / 4)]);
+ }
}
});
+ }
return strokeToTVals;
};