Browse Source

Improves drawing

main
Steve Ruiz 4 years ago
parent
commit
c41def99d3
4 changed files with 80 additions and 5 deletions
  1. 1
    1
      components/canvas/brush.tsx
  2. 31
    2
      lib/shape-utils/draw.tsx
  3. 10
    2
      state/sessions/draw-session.ts
  4. 38
    0
      utils/utils.ts

+ 1
- 1
components/canvas/brush.tsx View File

@@ -12,7 +12,6 @@ export default function Brush() {
12 12
       y={brush.minY}
13 13
       width={brush.width}
14 14
       height={brush.height}
15
-      className="brush"
16 15
     />
17 16
   )
18 17
 }
@@ -20,4 +19,5 @@ export default function Brush() {
20 19
 const BrushRect = styled("rect", {
21 20
   fill: "$brushFill",
22 21
   stroke: "$brushStroke",
22
+  zStrokeWidth: 1,
23 23
 })

+ 31
- 2
lib/shape-utils/draw.tsx View File

@@ -5,6 +5,9 @@ import { registerShapeUtils } from "./index"
5 5
 import { intersectPolylineBounds } from "utils/intersections"
6 6
 import { boundsContainPolygon } from "utils/bounds"
7 7
 import { getBoundsFromPoints, translateBounds } from "utils/utils"
8
+import { DotCircle } from "components/canvas/misc"
9
+
10
+const pathCache = new WeakMap<DrawShape, string>([])
8 11
 
9 12
 const draw = registerShapeUtils<DrawShape>({
10 13
   boundsCache: new WeakMap([]),
@@ -31,8 +34,34 @@ const draw = registerShapeUtils<DrawShape>({
31 34
     }
32 35
   },
33 36
 
34
-  render({ id, points }) {
35
-    return <polyline id={id} points={points.toString()} />
37
+  render(shape) {
38
+    const { id, point, points } = shape
39
+
40
+    if (points.length < 2) {
41
+      return <DotCircle cx={point[0]} cy={point[1]} r={3} />
42
+    }
43
+
44
+    if (!pathCache.has(shape)) {
45
+      pathCache.set(
46
+        shape,
47
+        points
48
+          .reduce(
49
+            (acc, [x0, y0], i, arr) => {
50
+              if (i === points.length - 1) {
51
+                acc.push("L", x0, y0)
52
+              } else {
53
+                const [x1, y1] = arr[i + 1]
54
+                acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
55
+              }
56
+              return acc
57
+            },
58
+            ["M", ...points[0], "Q"]
59
+          )
60
+          .join(" ")
61
+      )
62
+    }
63
+
64
+    return <path id={id} d={pathCache.get(shape)} />
36 65
   },
37 66
 
38 67
   applyStyles(shape, style) {

+ 10
- 2
state/sessions/draw-session.ts View File

@@ -2,7 +2,7 @@ import { current } from "immer"
2 2
 import { Data, DrawShape } from "types"
3 3
 import BaseSession from "./base-session"
4 4
 import { getShapeUtils } from "lib/shape-utils"
5
-import { getPage } from "utils/utils"
5
+import { getPage, simplify } from "utils/utils"
6 6
 import * as vec from "utils/vec"
7 7
 import commands from "state/commands"
8 8
 
@@ -42,7 +42,15 @@ export default class BrushSession extends BaseSession {
42 42
   }
43 43
 
44 44
   complete = (data: Data) => {
45
-    commands.points(data, this.shapeId, this.snapshot.points, this.points)
45
+    commands.points(
46
+      data,
47
+      this.shapeId,
48
+      this.snapshot.points,
49
+      simplify(this.points, 1).map(([x, y]) => [
50
+        Math.trunc(x * 100) / 100,
51
+        Math.trunc(y * 100) / 100,
52
+      ])
53
+    )
46 54
   }
47 55
 }
48 56
 

+ 38
- 0
utils/utils.ts View File

@@ -1492,3 +1492,41 @@ export function getCurrent<T extends object>(source: T): T {
1492 1492
     Object.entries(source).map(([key, value]) => [key, value])
1493 1493
   ) as T
1494 1494
 }
1495
+
1496
+/**
1497
+ * Simplify a line (using Ramer-Douglas-Peucker algorithm).
1498
+ * @param points An array of points as [x, y, ...][]
1499
+ * @param tolerance The minimum line distance (also called epsilon).
1500
+ * @returns Simplified array as [x, y, ...][]
1501
+ */
1502
+export function simplify(points: number[][], tolerance = 1) {
1503
+  const len = points.length,
1504
+    a = points[0],
1505
+    b = points[len - 1],
1506
+    [x1, y1] = a,
1507
+    [x2, y2] = b
1508
+
1509
+  if (len > 2) {
1510
+    let distance = 0,
1511
+      index = 0,
1512
+      max = Math.hypot(y2 - y1, x2 - x1)
1513
+
1514
+    for (let i = 1; i < len - 1; i++) {
1515
+      const [x0, y0] = points[i],
1516
+        d = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / max
1517
+
1518
+      if (distance > d) continue
1519
+
1520
+      distance = d
1521
+      index = i
1522
+    }
1523
+
1524
+    if (distance > tolerance) {
1525
+      let l0 = simplify(points.slice(0, index + 1), tolerance)
1526
+      let l1 = simplify(points.slice(index + 1), tolerance)
1527
+      return l0.concat(l1.slice(1))
1528
+    }
1529
+  }
1530
+
1531
+  return [a, b]
1532
+}

Loading…
Cancel
Save