From 7ddd2f8c66da7ad3800dc16edf5fab37fc6db664 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Thu, 8 Jul 2021 12:01:33 +0100 Subject: [PATCH] Fixes bugs on arrow --- next.config.js | 1 + state/shape-utils/arrow.tsx | 83 +++++++++++++++++++------------------ state/state.ts | 4 +- utils/utils.ts | 11 +++-- utils/vec.ts | 2 +- 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/next.config.js b/next.config.js index 49de7e833..406e96ad9 100644 --- a/next.config.js +++ b/next.config.js @@ -22,6 +22,7 @@ module.exports = withPWA({ pwa: { disable: !isProduction, dest: 'public', + p, }, }) diff --git a/state/shape-utils/arrow.tsx b/state/shape-utils/arrow.tsx index bc53358a5..cd2ffa8ae 100644 --- a/state/shape-utils/arrow.tsx +++ b/state/shape-utils/arrow.tsx @@ -29,8 +29,6 @@ import getStroke from 'perfect-freehand' import React from 'react' import { registerShapeUtils } from './register' -const pathCache = new WeakMap([]) - // A cache for semi-expensive circles calculated from three points function getCtp(shape: ArrowShape) { const { start, end, bend } = shape.handles @@ -94,8 +92,6 @@ const arrow = registerShapeUtils({ }, } - // shape.handles.bend.point = getBendPoint(shape) - return shape }, @@ -107,10 +103,8 @@ const arrow = registerShapeUtils({ const { id, bend, handles, style } = shape const { start, end, bend: _bend } = handles - const isStraightLine = vec.isEqual( - _bend.point, - vec.med(start.point, end.point) - ) + const isStraightLine = + vec.dist(_bend.point, vec.round(vec.med(start.point, end.point))) < 1 const styles = getShapeStyle(style) @@ -126,17 +120,12 @@ const arrow = registerShapeUtils({ if (isStraightLine) { const straight_sw = - strokeWidth * - (style.dash === DashStyle.Draw && bend === 0 ? 0.5 : 1.618) - - if (shape.style.dash === DashStyle.Draw && !pathCache.has(shape)) { - renderFreehandArrowShaft(shape) - } + strokeWidth * (style.dash === DashStyle.Draw ? 0.618 : 1.618) const path = shape.style.dash === DashStyle.Draw - ? pathCache.get(shape) - : 'M' + start.point + 'L' + end.point + ? renderFreehandArrowShaft(shape) + : 'M' + vec.round(start.point) + 'L' + vec.round(end.point) const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( arrowDist, @@ -432,8 +421,6 @@ const arrow = registerShapeUtils({ } } - const midPoint = vec.med(shape.handles.start.point, shape.handles.end.point) - // If the user is moving the bend handle, we want to move the bend point if ('bend' in handles) { const { start, end, bend } = shape.handles @@ -447,28 +434,30 @@ const arrow = registerShapeUtils({ const ap = vec.add(midPoint, vec.mul(vec.per(u), distance / 2)) const bp = vec.sub(midPoint, vec.mul(vec.per(u), distance / 2)) - // Find the nearest point on the line segment to the bend handle - bend.point = vec.round( - vec.nearestPointOnLineSegment(ap, bp, bend.point, true) + // Find the distance between the midpoint and the nearest point on the + // line segment to the bend handle's dragged point + const bendDist = vec.dist( + midPoint, + vec.round(vec.nearestPointOnLineSegment(ap, bp, bend.point, true)) ) - // The "bend" is the distance between this point on the line segment - // and the midpoint, divided by the distance between the start and end points. - shape.bend = vec.dist(bend.point, midPoint) / (distance / 2) + // The shape's "bend" is the ratio of the bend to the distance between + // the start and end points. If the bend is below a certain amount, the + // bend should be zero. + shape.bend = bendDist / (distance / 2) // If the point is to the left of the line segment, we make the bend // negative, otherwise it's positive. const angleToBend = vec.angle(start.point, bend.point) + if (isAngleBetween(angle, angle + Math.PI / 2, angleToBend)) { shape.bend *= -1 } - } else { - shape.handles.bend.point = getBendPoint(shape) } - if (vec.isEqual(shape.handles.bend.point, midPoint)) { - shape.bend = 0 - } + shape.handles.start.point = vec.round(shape.handles.start.point) + shape.handles.end.point = vec.round(shape.handles.end.point) + shape.handles.bend.point = getBendPoint(shape) return this }, @@ -482,9 +471,9 @@ const arrow = registerShapeUtils({ const { start, end, bend } = shape.handles - start.point = vec.sub(start.point, offset) - end.point = vec.sub(end.point, offset) - bend.point = vec.sub(bend.point, offset) + start.point = vec.round(vec.sub(start.point, offset)) + end.point = vec.round(vec.sub(end.point, offset)) + bend.point = vec.round(vec.sub(bend.point, offset)) shape.handles = { ...shape.handles } @@ -548,16 +537,28 @@ function renderFreehandArrowShaft(shape: ArrowShape) { const st = Math.abs(getRandom()) - const stroke = getStroke([...vec.pointsBetween(start.point, end.point)], { - size: strokeWidth / 2, - thinning: 0.5 + getRandom() * 0.3, - easing: (t) => t * t, - end: { taper: 1 }, - start: { taper: 1 + 32 * (st * st * st) }, - simulatePressure: true, - }) + const stroke = getStroke( + [ + ...vec.pointsBetween(start.point, end.point), + end.point, + end.point, + end.point, + end.point, + ], + { + size: strokeWidth / 2, + thinning: 0.5 + getRandom() * 0.3, + easing: (t) => t * t, + end: { taper: 1 }, + start: { taper: 1 + 32 * (st * st * st) }, + simulatePressure: true, + last: true, + } + ) - pathCache.set(shape, getSvgPathFromStroke(stroke)) + const path = getSvgPathFromStroke(stroke) + + return path } function getArrowHeadPath(shape: ArrowShape, point: number[], angle = 0) { diff --git a/state/state.ts b/state/state.ts index 38f350c55..7043f8391 100644 --- a/state/state.ts +++ b/state/state.ts @@ -1347,7 +1347,7 @@ const state = createState({ createShape(data, payload, type: ShapeType) { const style = deepClone(data.currentStyle) - let point = vec.round(tld.screenToWorld(payload.point, data)) + let point = tld.screenToWorld(payload.point, data) if (type === ShapeType.Text) { point = vec.sub(point, vec.mul([0, 1], getFontSize(style.size) * 0.8)) @@ -1356,7 +1356,7 @@ const state = createState({ const shape = createShape(type, { id: uniqueId(), parentId: data.currentPageId, - point, + point: vec.round(point), style: deepClone(data.currentStyle), }) diff --git a/utils/utils.ts b/utils/utils.ts index 9da20e06c..f8f72a951 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1724,14 +1724,17 @@ export function getSvgPathFromStroke(stroke: number[][]): string { const d = stroke.reduce( (acc, [x0, y0], i, arr) => { const [x1, y1] = arr[(i + 1) % arr.length] - acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2) + acc.push(` ${x0},${y0} ${(x0 + x1) / 2},${(y0 + y1) / 2}`) return acc }, - ['M', ...stroke[0], 'Q'] + ['M ', `${stroke[0][0]},${stroke[0][1]}`, ' Q'] ) - d.push('Z') - return d.join(' ').replaceAll(/(\s[0-9]*\.[0-9]{2})([0-9]*)\b/g, '$1') + d.push(' Z') + + return d + .join('') + .replaceAll(/(\s?[A-Z]?,?-?[0-9]*\.[0-9]{0,2})(([0-9]|e|-)*)/g, '$1') } export function debounce unknown>( diff --git a/utils/vec.ts b/utils/vec.ts index 38a3e4971..68bc9cecc 100644 --- a/utils/vec.ts +++ b/utils/vec.ts @@ -515,6 +515,6 @@ export default class Vec { const t = i / steps return t * t * t }) - .map((t) => [...Vec.lrp(a, b, t), (1 - t) / 2]) + .map((t) => Vec.round([...Vec.lrp(a, b, t), (1 - t) / 2])) } }