ソースを参照

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
 import { v4 as uuid } from 'uuid'
1
 import { v4 as uuid } from 'uuid'
2
 import * as vec from 'utils/vec'
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
 import { ArrowShape, Bounds, ShapeHandle, ShapeType } from 'types'
11
 import { ArrowShape, Bounds, ShapeHandle, ShapeType } from 'types'
5
 import { registerShapeUtils } from './index'
12
 import { registerShapeUtils } from './index'
6
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
13
 import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
9
   intersectArcBounds,
16
   intersectArcBounds,
10
   intersectLineSegmentBounds,
17
   intersectLineSegmentBounds,
11
 } from 'utils/intersections'
18
 } from 'utils/intersections'
12
-import { getBoundsFromPoints, translateBounds } from 'utils/utils'
13
 import { pointInCircle } from 'utils/hitTests'
19
 import { pointInCircle } from 'utils/hitTests'
14
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
20
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
21
+import getStroke from 'perfect-freehand'
15
 
22
 
16
 const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
23
 const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
24
+const pathCache = new WeakMap<ArrowShape, string>([])
17
 
25
 
18
 function getCtp(shape: ArrowShape) {
26
 function getCtp(shape: ArrowShape) {
19
   if (!ctpCache.has(shape.handles)) {
27
   if (!ctpCache.has(shape.handles)) {
100
     const style = getShapeStyle(shape.style)
108
     const style = getShapeStyle(shape.style)
101
 
109
 
102
     let body: JSX.Element
110
     let body: JSX.Element
103
-    let endAngle: number
104
 
111
 
105
     if (showCircle) {
112
     if (showCircle) {
106
       if (!ctpCache.has(handles)) {
113
       if (!ctpCache.has(handles)) {
112
 
119
 
113
       const circle = getCtp(shape)
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
       body = (
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
     return (
154
     return (
147
       <g id={id}>
155
       <g id={id}>
148
         {body}
156
         {body}
149
-        <circle
157
+        {/* <circle
150
           cx={start.point[0]}
158
           cx={start.point[0]}
151
           cy={start.point[1]}
159
           cy={start.point[1]}
152
-          r={+style.strokeWidth}
160
+          r={Math.max(4, +style.strokeWidth)}
153
           fill={style.stroke}
161
           fill={style.stroke}
154
           strokeDasharray="none"
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
       </g>
164
       </g>
164
     )
165
     )
165
   },
166
   },
426
 
427
 
427
   return delta
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
 import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
5
 import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
6
 import { intersectEllipseBounds } from 'utils/intersections'
6
 import { intersectEllipseBounds } from 'utils/intersections'
7
 import { pointInEllipse } from 'utils/hitTests'
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
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
9
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
20
 import getStroke from 'perfect-freehand'
10
 import getStroke from 'perfect-freehand'
21
 
11
 

読み込み中…
キャンセル
保存