浏览代码

Improves typing on shapes utils

main
Steve Ruiz 4 年前
父节点
当前提交
d99507de5b
共有 9 个文件被更改,包括 181 次插入158 次删除
  1. 19
    0
      lib/shapes/base-shape.tsx
  2. 23
    7
      lib/shapes/circle.tsx
  3. 16
    5
      lib/shapes/dot.tsx
  4. 3
    0
      lib/shapes/index.tsx
  5. 20
    5
      lib/shapes/polyline.tsx
  6. 17
    7
      lib/shapes/rectangle.tsx
  7. 9
    124
      state/sessions/brush-session.ts
  8. 11
    10
      types.ts
  9. 63
    0
      utils/bounds.ts

+ 19
- 0
lib/shapes/base-shape.tsx 查看文件

@@ -0,0 +1,19 @@
1
+import { Bounds, Shape } from "types"
2
+
3
+export default interface BaseLibShape<K extends Shape> {
4
+  create(props: Partial<K>): K
5
+  getBounds(this: BaseLibShape<K>, shape: K): Bounds
6
+  hitTest(this: BaseLibShape<K>, shape: K, test: number[]): boolean
7
+  hitTestBounds(this: BaseLibShape<K>, shape: K, bounds: Bounds): boolean
8
+  rotate(this: BaseLibShape<K>, shape: K): K
9
+  translate(this: BaseLibShape<K>, shape: K, delta: number[]): K
10
+  scale(this: BaseLibShape<K>, shape: K, scale: number): K
11
+  stretch(this: BaseLibShape<K>, shape: K, scaleX: number, scaleY: number): K
12
+  render(this: BaseLibShape<K>, shape: K): JSX.Element
13
+}
14
+
15
+export function createShape<T extends Shape>(
16
+  shape: BaseLibShape<T>
17
+): BaseLibShape<T> {
18
+  return shape
19
+}

+ 23
- 7
lib/shapes/circle.tsx 查看文件

@@ -1,10 +1,13 @@
1 1
 import { v4 as uuid } from "uuid"
2 2
 import * as vec from "utils/vec"
3
-import { BaseLibShape, CircleShape, ShapeType } from "types"
3
+import { CircleShape, ShapeType } from "types"
4 4
 import { boundsCache } from "./index"
5
+import { boundsContained } from "utils/bounds"
6
+import { intersectCircleBounds } from "utils/intersections"
7
+import { createShape } from "./base-shape"
5 8
 
6
-const Circle: BaseLibShape<ShapeType.Circle> = {
7
-  create(props): CircleShape {
9
+const circle = createShape<CircleShape>({
10
+  create(props) {
8 11
     return {
9 12
       id: uuid(),
10 13
       type: ShapeType.Circle,
@@ -52,6 +55,19 @@ const Circle: BaseLibShape<ShapeType.Circle> = {
52 55
     )
53 56
   },
54 57
 
58
+  hitTestBounds(shape, bounds) {
59
+    const shapeBounds = this.getBounds(shape)
60
+
61
+    return (
62
+      boundsContained(shapeBounds, bounds) ||
63
+      intersectCircleBounds(
64
+        vec.addScalar(shape.point, shape.radius),
65
+        shape.radius,
66
+        bounds
67
+      ).length > 0
68
+    )
69
+  },
70
+
55 71
   rotate(shape) {
56 72
     return shape
57 73
   },
@@ -61,13 +77,13 @@ const Circle: BaseLibShape<ShapeType.Circle> = {
61 77
     return shape
62 78
   },
63 79
 
64
-  scale(shape, scale: number) {
80
+  scale(shape, scale) {
65 81
     return shape
66 82
   },
67 83
 
68
-  stretch(shape, scaleX: number, scaleY: number) {
84
+  stretch(shape, scaleX, scaleY) {
69 85
     return shape
70 86
   },
71
-}
87
+})
72 88
 
73
-export default Circle
89
+export default circle

+ 16
- 5
lib/shapes/dot.tsx 查看文件

@@ -1,10 +1,13 @@
1 1
 import { v4 as uuid } from "uuid"
2 2
 import * as vec from "utils/vec"
3
-import { BaseLibShape, DotShape, ShapeType } from "types"
3
+import { DotShape, ShapeType } from "types"
4 4
 import { boundsCache } from "./index"
5
+import { boundsContained } from "utils/bounds"
6
+import { intersectCircleBounds } from "utils/intersections"
7
+import { createShape } from "./base-shape"
5 8
 
6
-const Dot: BaseLibShape<ShapeType.Dot> = {
7
-  create(props): DotShape {
9
+const dot = createShape<DotShape>({
10
+  create(props) {
8 11
     return {
9 12
       id: uuid(),
10 13
       type: ShapeType.Dot,
@@ -48,6 +51,14 @@ const Dot: BaseLibShape<ShapeType.Dot> = {
48 51
     return vec.dist(shape.point, test) < 4
49 52
   },
50 53
 
54
+  hitTestBounds(this, shape, brushBounds) {
55
+    const shapeBounds = this.getBounds(shape)
56
+    return (
57
+      boundsContained(shapeBounds, brushBounds) ||
58
+      intersectCircleBounds(shape.point, 4, brushBounds).length > 0
59
+    )
60
+  },
61
+
51 62
   rotate(shape) {
52 63
     return shape
53 64
   },
@@ -64,6 +75,6 @@ const Dot: BaseLibShape<ShapeType.Dot> = {
64 75
   stretch(shape, scaleX: number, scaleY: number) {
65 76
     return shape
66 77
   },
67
-}
78
+})
68 79
 
69
-export default Dot
80
+export default dot

+ 3
- 0
lib/shapes/index.tsx 查看文件

@@ -12,6 +12,9 @@ const shapes = {
12 12
   [ShapeType.Dot]: Dot,
13 13
   [ShapeType.Polyline]: Polyline,
14 14
   [ShapeType.Rectangle]: Rectangle,
15
+  [ShapeType.Ellipse]: Rectangle,
16
+  [ShapeType.Line]: Rectangle,
17
+  [ShapeType.Ray]: Rectangle,
15 18
 }
16 19
 
17 20
 export default shapes

+ 20
- 5
lib/shapes/polyline.tsx 查看文件

@@ -1,10 +1,13 @@
1 1
 import { v4 as uuid } from "uuid"
2 2
 import * as vec from "utils/vec"
3
-import { BaseLibShape, PolylineShape, ShapeType } from "types"
3
+import { PolylineShape, ShapeType } from "types"
4 4
 import { boundsCache } from "./index"
5
+import { intersectPolylineBounds } from "utils/intersections"
6
+import { boundsCollide, boundsContained } from "utils/bounds"
7
+import { createShape } from "./base-shape"
5 8
 
6
-const Polyline: BaseLibShape<ShapeType.Polyline> = {
7
-  create(props): PolylineShape {
9
+const polyline = createShape<PolylineShape>({
10
+  create(props) {
8 11
     return {
9 12
       id: uuid(),
10 13
       type: ShapeType.Polyline,
@@ -57,6 +60,18 @@ const Polyline: BaseLibShape<ShapeType.Polyline> = {
57 60
     return true
58 61
   },
59 62
 
63
+  hitTestBounds(this, shape, bounds) {
64
+    const shapeBounds = this.getBounds(shape)
65
+    return (
66
+      boundsContained(shapeBounds, bounds) ||
67
+      (boundsCollide(shapeBounds, bounds) &&
68
+        intersectPolylineBounds(
69
+          shape.points.map((point) => vec.add(point, shape.point)),
70
+          bounds
71
+        ).length > 0)
72
+    )
73
+  },
74
+
60 75
   rotate(shape) {
61 76
     return shape
62 77
   },
@@ -73,6 +88,6 @@ const Polyline: BaseLibShape<ShapeType.Polyline> = {
73 88
   stretch(shape, scaleX: number, scaleY: number) {
74 89
     return shape
75 90
   },
76
-}
91
+})
77 92
 
78
-export default Polyline
93
+export default polyline

+ 17
- 7
lib/shapes/rectangle.tsx 查看文件

@@ -1,10 +1,12 @@
1 1
 import { v4 as uuid } from "uuid"
2 2
 import * as vec from "utils/vec"
3
-import { BaseLibShape, RectangleShape, ShapeType } from "types"
3
+import { RectangleShape, ShapeType } from "types"
4 4
 import { boundsCache } from "./index"
5
+import { boundsContained, boundsCollide } from "utils/bounds"
6
+import { createShape } from "./base-shape"
5 7
 
6
-const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
7
-  create(props): RectangleShape {
8
+const rectangle = createShape<RectangleShape>({
9
+  create(props) {
8 10
     return {
9 11
       id: uuid(),
10 12
       type: ShapeType.Rectangle,
@@ -50,6 +52,14 @@ const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
50 52
     return true
51 53
   },
52 54
 
55
+  hitTestBounds(shape, brushBounds) {
56
+    const shapeBounds = this.getBounds(shape)
57
+    return (
58
+      boundsContained(shapeBounds, brushBounds) ||
59
+      boundsCollide(shapeBounds, brushBounds)
60
+    )
61
+  },
62
+
53 63
   rotate(shape) {
54 64
     return shape
55 65
   },
@@ -59,13 +69,13 @@ const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
59 69
     return shape
60 70
   },
61 71
 
62
-  scale(shape, scale: number) {
72
+  scale(shape, scale) {
63 73
     return shape
64 74
   },
65 75
 
66
-  stretch(shape, scaleX: number, scaleY: number) {
76
+  stretch(shape, scaleX, scaleY) {
67 77
     return shape
68 78
   },
69
-}
79
+})
70 80
 
71
-export default Rectangle
81
+export default rectangle

+ 9
- 124
state/sessions/brush-session.ts 查看文件

@@ -1,13 +1,9 @@
1 1
 import { current } from "immer"
2
-import { Bounds, Data, ShapeType } from "types"
2
+import { BaseLibShape, Bounds, Data, Shapes } from "types"
3 3
 import BaseSession from "./base-session"
4
-import Shapes from "lib/shapes"
4
+import shapes from "lib/shapes"
5 5
 import { getBoundsFromPoints } from "utils/utils"
6 6
 import * as vec from "utils/vec"
7
-import {
8
-  intersectCircleBounds,
9
-  intersectPolylineBounds,
10
-} from "utils/intersections"
11 7
 
12 8
 interface BrushSnapshot {
13 9
   selectedIds: Set<string>
@@ -69,124 +65,13 @@ export default class BrushSession extends BaseSession {
69 65
       selectedIds: new Set(data.selectedIds),
70 66
       shapes: Object.values(pages[currentPageId].shapes)
71 67
         .filter((shape) => !selectedIds.has(shape.id))
72
-        .map((shape) => {
73
-          switch (shape.type) {
74
-            case ShapeType.Dot: {
75
-              const bounds = Shapes[shape.type].getBounds(shape)
76
-
77
-              return {
78
-                id: shape.id,
79
-                test: (brushBounds: Bounds) =>
80
-                  boundsContained(bounds, brushBounds) ||
81
-                  intersectCircleBounds(shape.point, 4, brushBounds).length > 0,
82
-              }
83
-            }
84
-            case ShapeType.Circle: {
85
-              const bounds = Shapes[shape.type].getBounds(shape)
86
-
87
-              return {
88
-                id: shape.id,
89
-                test: (brushBounds: Bounds) =>
90
-                  boundsContained(bounds, brushBounds) ||
91
-                  intersectCircleBounds(
92
-                    vec.addScalar(shape.point, shape.radius),
93
-                    shape.radius,
94
-                    brushBounds
95
-                  ).length > 0,
96
-              }
97
-            }
98
-            case ShapeType.Rectangle: {
99
-              const bounds = Shapes[shape.type].getBounds(shape)
100
-
101
-              return {
102
-                id: shape.id,
103
-                test: (brushBounds: Bounds) =>
104
-                  boundsContained(bounds, brushBounds) ||
105
-                  boundsCollide(bounds, brushBounds),
106
-              }
107
-            }
108
-            case ShapeType.Polyline: {
109
-              const bounds = Shapes[shape.type].getBounds(shape)
110
-              const points = shape.points.map((point) =>
111
-                vec.add(point, shape.point)
112
-              )
113
-
114
-              return {
115
-                id: shape.id,
116
-                test: (brushBounds: Bounds) =>
117
-                  boundsContained(bounds, brushBounds) ||
118
-                  (boundsCollide(bounds, brushBounds) &&
119
-                    intersectPolylineBounds(points, brushBounds).length > 0),
120
-              }
121
-            }
122
-            default: {
123
-              return undefined
124
-            }
125
-          }
126
-        })
127
-        .filter(Boolean),
68
+        .map((shape) => ({
69
+          id: shape.id,
70
+          test: (brushBounds: Bounds): boolean =>
71
+            (shapes[shape.type] as BaseLibShape<
72
+              Shapes[typeof shape.type]
73
+            >).hitTestBounds(shape, brushBounds),
74
+        })),
128 75
     }
129 76
   }
130 77
 }
131
-
132
-/**
133
- * Get whether two bounds collide.
134
- * @param a Bounds
135
- * @param b Bounds
136
- * @returns
137
- */
138
-export function boundsCollide(a: Bounds, b: Bounds) {
139
-  return !(
140
-    a.maxX < b.minX ||
141
-    a.minX > b.maxX ||
142
-    a.maxY < b.minY ||
143
-    a.minY > b.maxY
144
-  )
145
-}
146
-
147
-/**
148
- * Get whether the bounds of A contain the bounds of B. A perfect match will return true.
149
- * @param a Bounds
150
- * @param b Bounds
151
- * @returns
152
- */
153
-export function boundsContain(a: Bounds, b: Bounds) {
154
-  return (
155
-    a.minX < b.minX && a.minY < b.minY && a.maxY > b.maxY && a.maxX > b.maxX
156
-  )
157
-}
158
-
159
-/**
160
- * Get whether the bounds of A are contained by the bounds of B.
161
- * @param a Bounds
162
- * @param b Bounds
163
- * @returns
164
- */
165
-export function boundsContained(a: Bounds, b: Bounds) {
166
-  return boundsContain(b, a)
167
-}
168
-
169
-/**
170
- * Get whether two bounds are identical.
171
- * @param a Bounds
172
- * @param b Bounds
173
- * @returns
174
- */
175
-export function boundsAreEqual(a: Bounds, b: Bounds) {
176
-  return !(
177
-    b.maxX !== a.maxX ||
178
-    b.minX !== a.minX ||
179
-    b.maxY !== a.maxY ||
180
-    b.minY !== a.minY
181
-  )
182
-}
183
-
184
-/**
185
- * Get whether a point is inside of a bounds.
186
- * @param A
187
- * @param b
188
- * @returns
189
- */
190
-export function pointInBounds(A: number[], b: Bounds) {
191
-  return !(A[0] < b.minX || A[0] > b.maxX || A[1] < b.minY || A[1] > b.maxY)
192
-}

+ 11
- 10
types.ts 查看文件

@@ -44,7 +44,7 @@ export interface BaseShape {
44 44
   childIndex: number
45 45
   name: string
46 46
   point: number[]
47
-  rotation: 0
47
+  rotation: number
48 48
   style: Partial<React.SVGProps<SVGUseElement>>
49 49
 }
50 50
 
@@ -120,15 +120,16 @@ export type ShapeSpecificProps<T extends Shape> = Pick<
120 120
 
121 121
 export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
122 122
 
123
-export type BaseLibShape<K extends ShapeType> = {
124
-  create(props: Partial<Shapes[K]>): Shapes[K]
125
-  getBounds(shape: Shapes[K]): Bounds
126
-  hitTest(shape: Shapes[K], test: number[]): boolean
127
-  rotate(shape: Shapes[K]): Shapes[K]
128
-  translate(shape: Shapes[K], delta: number[]): Shapes[K]
129
-  scale(shape: Shapes[K], scale: number): Shapes[K]
130
-  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
131
-  render(shape: Shapes[K]): JSX.Element
123
+export type BaseLibShape<K extends Shape> = {
124
+  create(props: Partial<K>): K
125
+  getBounds(shape: K): Bounds
126
+  hitTest(shape: K, test: number[]): boolean
127
+  hitTestBounds(shape: K, bounds: Bounds): boolean
128
+  rotate(shape: K): K
129
+  translate(shape: K, delta: number[]): K
130
+  scale(shape: K, scale: number): K
131
+  stretch(shape: K, scaleX: number, scaleY: number): K
132
+  render(shape: K): JSX.Element
132 133
 }
133 134
 
134 135
 export interface PointerInfo {

+ 63
- 0
utils/bounds.ts 查看文件

@@ -0,0 +1,63 @@
1
+import { Bounds } from "types"
2
+
3
+/**
4
+ * Get whether two bounds collide.
5
+ * @param a Bounds
6
+ * @param b Bounds
7
+ * @returns
8
+ */
9
+export function boundsCollide(a: Bounds, b: Bounds) {
10
+  return !(
11
+    a.maxX < b.minX ||
12
+    a.minX > b.maxX ||
13
+    a.maxY < b.minY ||
14
+    a.minY > b.maxY
15
+  )
16
+}
17
+
18
+/**
19
+ * Get whether the bounds of A contain the bounds of B. A perfect match will return true.
20
+ * @param a Bounds
21
+ * @param b Bounds
22
+ * @returns
23
+ */
24
+export function boundsContain(a: Bounds, b: Bounds) {
25
+  return (
26
+    a.minX < b.minX && a.minY < b.minY && a.maxY > b.maxY && a.maxX > b.maxX
27
+  )
28
+}
29
+
30
+/**
31
+ * Get whether the bounds of A are contained by the bounds of B.
32
+ * @param a Bounds
33
+ * @param b Bounds
34
+ * @returns
35
+ */
36
+export function boundsContained(a: Bounds, b: Bounds) {
37
+  return boundsContain(b, a)
38
+}
39
+
40
+/**
41
+ * Get whether two bounds are identical.
42
+ * @param a Bounds
43
+ * @param b Bounds
44
+ * @returns
45
+ */
46
+export function boundsAreEqual(a: Bounds, b: Bounds) {
47
+  return !(
48
+    b.maxX !== a.maxX ||
49
+    b.minX !== a.minX ||
50
+    b.maxY !== a.maxY ||
51
+    b.minY !== a.minY
52
+  )
53
+}
54
+
55
+/**
56
+ * Get whether a point is inside of a bounds.
57
+ * @param A
58
+ * @param b
59
+ * @returns
60
+ */
61
+export function pointInBounds(A: number[], b: Bounds) {
62
+  return !(A[0] < b.minX || A[0] > b.maxX || A[1] < b.minY || A[1] > b.maxY)
63
+}

正在加载...
取消
保存