瀏覽代碼

adds sketchy arrow

main
Steve Ruiz 4 年之前
父節點
當前提交
3629d882cd
共有 2 個文件被更改,包括 119 次插入49 次删除
  1. 118
    38
      lib/shape-utils/arrow.tsx
  2. 1
    11
      lib/shape-utils/ellipse.tsx

+ 118
- 38
lib/shape-utils/arrow.tsx 查看文件

@@ -1,6 +1,13 @@
1 1
 import { v4 as uuid } from 'uuid'
2 2
 import * as vec from 'utils/vec'
3
-import * as svg from 'utils/svg'
3
+import {
4
+  ease,
5
+  getSvgPathFromStroke,
6
+  rng,
7
+  getBoundsFromPoints,
8
+  translateBounds,
9
+  pointsBetween,
10
+} from 'utils/utils'
4 11
 import { ArrowShape, Bounds, ShapeHandle, ShapeType } from 'types'
5 12
 import { registerShapeUtils } from './index'
6 13
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
@@ -9,11 +16,12 @@ import {
9 16
   intersectArcBounds,
10 17
   intersectLineSegmentBounds,
11 18
 } from 'utils/intersections'
12
-import { getBoundsFromPoints, translateBounds } from 'utils/utils'
13 19
 import { pointInCircle } from 'utils/hitTests'
14 20
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
21
+import getStroke from 'perfect-freehand'
15 22
 
16 23
 const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
24
+const pathCache = new WeakMap<ArrowShape, string>([])
17 25
 
18 26
 function getCtp(shape: ArrowShape) {
19 27
   if (!ctpCache.has(shape.handles)) {
@@ -100,7 +108,6 @@ const arrow = registerShapeUtils<ArrowShape>({
100 108
     const style = getShapeStyle(shape.style)
101 109
 
102 110
     let body: JSX.Element
103
-    let endAngle: number
104 111
 
105 112
     if (showCircle) {
106 113
       if (!ctpCache.has(handles)) {
@@ -112,54 +119,48 @@ const arrow = registerShapeUtils<ArrowShape>({
112 119
 
113 120
       const circle = getCtp(shape)
114 121
 
115
-      body = (
116
-        <path
117
-          d={getArrowArcPath(start, end, circle, bend)}
118
-          fill="none"
119
-          strokeLinecap="round"
120
-        />
121
-      )
122
+      if (!pathCache.has(shape)) {
123
+        renderPath(
124
+          shape,
125
+          vec.angle([circle[0], circle[1]], end.point) -
126
+            vec.angle(start.point, end.point) +
127
+            (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
128
+        )
129
+      }
122 130
 
123
-      const CE =
124
-        vec.angle([circle[0], circle[1]], end.point) -
125
-        vec.angle(start.point, end.point) +
126
-        (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
131
+      const path = pathCache.get(shape)
127 132
 
128
-      endAngle = CE
129
-    } else {
130 133
       body = (
131
-        <polyline
132
-          points={[start.point, end.point].join(' ')}
133
-          strokeLinecap="round"
134
-        />
134
+        <>
135
+          <path
136
+            d={getArrowArcPath(start, end, circle, bend)}
137
+            fill="none"
138
+            strokeWidth={+style.strokeWidth * 1.85}
139
+            strokeLinecap="round"
140
+          />
141
+          <path d={path} strokeWidth={+style.strokeWidth * 1.5} />
142
+        </>
135 143
       )
136
-      endAngle = 0
137
-    }
144
+    } else {
145
+      if (!pathCache.has(shape)) {
146
+        renderPath(shape)
147
+      }
138 148
 
139
-    // Arrowhead
140
-    const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
141
-    const u = vec.uni(vec.vec(start.point, end.point))
142
-    const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
143
-    const b = vec.add(end.point, vec.rot(v, Math.PI / 6))
144
-    const c = vec.add(end.point, vec.rot(v, -(Math.PI / 6)))
149
+      const path = pathCache.get(shape)
150
+
151
+      body = <path d={path} />
152
+    }
145 153
 
146 154
     return (
147 155
       <g id={id}>
148 156
         {body}
149
-        <circle
157
+        {/* <circle
150 158
           cx={start.point[0]}
151 159
           cy={start.point[1]}
152
-          r={+style.strokeWidth}
160
+          r={Math.max(4, +style.strokeWidth)}
153 161
           fill={style.stroke}
154 162
           strokeDasharray="none"
155
-        />
156
-        <polyline
157
-          points={[b, end.point, c].join()}
158
-          strokeLinecap="round"
159
-          strokeLinejoin="round"
160
-          fill="none"
161
-          strokeDasharray="none"
162
-        />
163
+        /> */}
163 164
       </g>
164 165
     )
165 166
   },
@@ -426,3 +427,82 @@ function getResizeOffset(a: Bounds, b: Bounds) {
426 427
 
427 428
   return delta
428 429
 }
430
+
431
+function renderPath(shape: ArrowShape, endAngle = 0) {
432
+  const { style, id } = shape
433
+  const { start, end, bend } = shape.handles
434
+
435
+  const getRandom = rng(id)
436
+  const offsetA = getRandom()
437
+
438
+  const strokeWidth = +getShapeStyle(style).strokeWidth * 2
439
+
440
+  const arrowDist = vec.dist(start.point, end.point)
441
+
442
+  const styles = getShapeStyle(shape.style)
443
+
444
+  const sw = +styles.strokeWidth
445
+
446
+  const length = Math.min(arrowDist / 2, 24 + sw * 2)
447
+  const u = vec.uni(vec.vec(start.point, end.point))
448
+  const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
449
+
450
+  // Start
451
+  const a = start.point
452
+
453
+  // Middle
454
+  const m = vec.add(
455
+    vec.lrp(start.point, end.point, 0.25 + Math.abs(getRandom()) / 2),
456
+    [getRandom() * sw, getRandom() * sw]
457
+  )
458
+
459
+  // End
460
+  const b = end.point
461
+
462
+  // Left
463
+  let c = vec.add(
464
+    end.point,
465
+    vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())
466
+  )
467
+
468
+  // Right
469
+  let d = vec.add(
470
+    end.point,
471
+    vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
472
+  )
473
+
474
+  if (getRandom() > 0.5) {
475
+    ;[c, d] = [d, c]
476
+  }
477
+
478
+  const points = endAngle
479
+    ? [
480
+        // Just the arrowhead
481
+        ...pointsBetween(b, c),
482
+        ...pointsBetween(c, b),
483
+        ...pointsBetween(b, d),
484
+        ...pointsBetween(d, b),
485
+      ]
486
+    : [
487
+        // The shaft too
488
+        b,
489
+        a,
490
+        ...pointsBetween(a, m),
491
+        ...pointsBetween(m, b),
492
+        ...pointsBetween(b, c),
493
+        ...pointsBetween(c, b),
494
+        ...pointsBetween(b, d),
495
+        ...pointsBetween(d, b),
496
+      ]
497
+
498
+  const stroke = getStroke(points, {
499
+    size: 1 + strokeWidth,
500
+    thinning: 0.6,
501
+    easing: (t) => t * t * t * t,
502
+    end: { taper: strokeWidth * 20 },
503
+    start: { taper: strokeWidth * 20 },
504
+    simulatePressure: false,
505
+  })
506
+
507
+  pathCache.set(shape, getSvgPathFromStroke(stroke))
508
+}

+ 1
- 11
lib/shape-utils/ellipse.tsx 查看文件

@@ -5,17 +5,7 @@ import { getShapeUtils, registerShapeUtils } from './index'
5 5
 import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
6 6
 import { intersectEllipseBounds } from 'utils/intersections'
7 7
 import { pointInEllipse } from 'utils/hitTests'
8
-import {
9
-  ease,
10
-  getBoundsFromPoints,
11
-  getRotatedCorners,
12
-  getSvgPathFromStroke,
13
-  pointsBetween,
14
-  rng,
15
-  rotateBounds,
16
-  shuffleArr,
17
-  translateBounds,
18
-} from 'utils/utils'
8
+import { ease, getSvgPathFromStroke, rng, translateBounds } from 'utils/utils'
19 9
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
20 10
 import getStroke from 'perfect-freehand'
21 11
 

Loading…
取消
儲存