aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/InkStrokeProperties.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-09-29 15:15:21 -0400
committerbobzel <zzzman@gmail.com>2021-09-29 15:15:21 -0400
commit5f95911a504a47c867198fccc32a75bf22d26056 (patch)
treed98ff4a6243de2d2bc615540db5b040793e82496 /src/client/views/InkStrokeProperties.ts
parente6451eda7c7a5be73922b302627c53db5e22d474 (diff)
added snapping to close curve or to self-snap a vertex to its curve. fixed ink decorations from being clipped when zoomed. fixed crash with zero-length tangent
Diffstat (limited to 'src/client/views/InkStrokeProperties.ts')
-rw-r--r--src/client/views/InkStrokeProperties.ts61
1 files changed, 59 insertions, 2 deletions
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 3770eb7c1..ac5cdfee2 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -189,7 +189,7 @@ export class InkStrokeProperties {
const order = controlIndex % 4;
const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
- return ink.map((pt, i) => {
+ const newpts = ink.map((pt, i) => {
const leftHandlePoint = order === 0 && i === controlIndex + 1;
const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
if (controlIndex === i ||
@@ -201,12 +201,68 @@ export class InkStrokeProperties {
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) {
- return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale });
+ return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale });
}
return pt;
});
+ return newpts;
})
+
+ public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) {
+ var distance = Number.MAX_SAFE_INTEGER;
+ var nearestT = -1;
+ var nearestSeg = -1;
+ var nearestPt = { X: 0, Y: 0 };
+ for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
+ if (excludeSegs?.includes(i)) continue;
+ const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
+ const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y });
+ if (point.t !== undefined) {
+ const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y));
+ if (dist < distance) {
+ distance = dist;
+ nearestT = point.t;
+ nearestSeg = i;
+ nearestPt = { X: point.x, Y: point.y };
+ }
+ }
+ }
+ return { distance, nearestT, nearestSeg, nearestPt };
+ }
+
+ /**
+ * Handles the movement/scaling of a control point.
+ */
+ snapControl = (inkDoc: Doc, controlIndex: number) => {
+ const ink = Cast(inkDoc.data, InkField)?.inkData;
+ if (ink) {
+ const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
+
+ // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve)
+ const thisseg = Math.floor(controlIndex / 4) * 4;
+ const which = controlIndex % 4;
+ const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1;
+ const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1;
+ const refPt = ink[controlIndex];
+ const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]);
+
+ // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates.
+ // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself.
+ const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
+ const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
+ const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min);
+ const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min);
+ const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
+ (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
+
+ if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) {
+ return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex)
+ }
+ }
+ return false;
+ }
+
/**
* Snaps a control point with broken tangency back to synced rotation.
* @param handleIndexA The handle point that retains its current position.
@@ -247,6 +303,7 @@ export class InkStrokeProperties {
angleBetweenTwoVectors = (vectorA: PointData, vectorB: PointData) => {
const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y);
const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
+ if (magnitudeA === 0 || magnitudeB === 0) return 0;
// Normalizing the vectors.
vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };