diff options
Diffstat (limited to 'src/client/views/InkStrokeProperties.ts')
-rw-r--r-- | src/client/views/InkStrokeProperties.ts | 104 |
1 files changed, 60 insertions, 44 deletions
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 33e25bbbb..6687b2bc7 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,6 +1,6 @@ import { Bezier } from "bezier-js"; import { action, observable, reaction } from "mobx"; -import { Doc, Opt } from "../../fields/Doc"; +import { Doc, Opt, DocListCast } from "../../fields/Doc"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; @@ -10,6 +10,7 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { undoBatch } from "../util/UndoManager"; import { InkingStroke } from "./InkingStroke"; import { DocumentView } from "./nodes/DocumentView"; +import { DocumentManager } from "../util/DocumentManager"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -155,29 +156,24 @@ export class InkStrokeProperties { }, true) /** - * Rotates the entire selected ink instance. + * Rotates ink stroke(s) about a point + * @param inkStrokes set of ink documentViews to rotate * @param angle The angle at which to rotate the ink in radians. + * @param scrpt The center point of the rotation in screen coordinates */ @undoBatch @action rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => { - const oldXrangeMin = Math.min(...ink.map(p => p.X)); - const oldYrangeMin = Math.min(...ink.map(p => p.Y)); - const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y); - const inkCenterPt = { - X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin, - Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin - }; - const newPoints = ink.map(i => { - const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; - const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; - return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; - }); - const doc = view.rootDoc; - doc.rotation = NumCast(doc.rotation) + angle; - return newPoints; + view.rootDoc.rotation = NumCast(view.rootDoc.rotation) + angle; + const inkCenterPt = view.ComponentView?.ptFromScreen?.({ X: scrpt.x, Y: scrpt.y }); + return !inkCenterPt ? ink : + ink.map(i => { + const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; + const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; + return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; + }); }); } @@ -203,7 +199,7 @@ 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, Y: pt.Y + deltaY }); } return pt; }); @@ -211,7 +207,7 @@ export class InkStrokeProperties { }) - public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) { + public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refInkSpacePt: { X: number, Y: number }, excludeSegs?: number[]) { var distance = Number.MAX_SAFE_INTEGER; var nearestT = -1; var nearestSeg = -1; @@ -219,9 +215,9 @@ export class InkStrokeProperties { 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 }); + const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refInkSpacePt.X, y: refInkSpacePt.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)); + const dist = Math.sqrt((point.x - refInkSpacePt.X) * (point.x - refInkSpacePt.X) + (point.y - refInkSpacePt.Y) * (point.y - refInkSpacePt.Y)); if (dist < distance) { distance = dist; nearestT = point.t; @@ -238,34 +234,54 @@ export class InkStrokeProperties { */ snapControl = (inkView: DocumentView, controlIndex: number) => { const inkDoc = inkView.rootDoc; - const ink = Cast(inkDoc.data, InkField)?.inkData; - if (ink) { - const closed = InkingStroke.IsClosed(ink); - - // 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]); + const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData; - // 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) - NumCast(inkDoc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; - const ptsYscale = ((NumCast(inkDoc._height) - NumCast(inkDoc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; - 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 / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControlPtHandle(inkView, (nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + if (ink) { + const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]); + if (screenDragPt) { + const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex); + if (snapData.distance < 10) { + const deltaX = (snapData.nearestPt.X - ink[controlIndex].X); + const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y); + const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex); + console.log("X= " + snapData.nearestPt.X + " " + snapData.nearestPt.Y); + return res; + } } } return false; } + excludeSelfSnapSegs = (ink: InkData, controlIndex: number) => { + const closed = InkingStroke.IsClosed(ink); + + // 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; + return [thisseg, prevseg, nextseg]; + } + + snapToAllCurves = (screenDragPt: { X: number, Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number, Y: number }, distance: number }, ink: InkData, controlIndex: number) => { + const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + containingCollection?.childDocs + .filter(doc => doc.type === DocumentType.INK) + .forEach(doc => { + const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView); + const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []); + if (snapped && snapped.distance < snapData.distance) { + const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : + inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space + + if (snappedInkPt) { + snapData = { nearestPt: snappedInkPt, distance: snapped.distance }; + } + } + }); + return snapData; + } + /** * Snaps a control point with broken tangency back to synced rotation. * @param handleIndexA The handle point that retains its current position. |