Browse Source

Adds utils, brush selection testing for shapes.

main
Steve Ruiz 4 years ago
parent
commit
7ee3a1ef3d

+ 3
- 0
components/canvas/shape.tsx View File

@@ -2,6 +2,7 @@ import { memo } from "react"
2 2
 import { useSelector } from "state"
3 3
 import { ShapeType } from "types"
4 4
 import Circle from "./shapes/circle"
5
+import Dot from "./shapes/dot"
5 6
 import Rectangle from "./shapes/rectangle"
6 7
 
7 8
 /*
@@ -17,6 +18,8 @@ function Shape({ id }: { id: string }) {
17 18
   })
18 19
 
19 20
   switch (shape.type) {
21
+    case ShapeType.Dot:
22
+      return <Dot {...shape} />
20 23
     case ShapeType.Circle:
21 24
       return <Circle {...shape} />
22 25
     case ShapeType.Rectangle:

+ 46
- 2
components/canvas/shapes/circle.tsx View File

@@ -1,5 +1,49 @@
1
+import state, { useSelector } from "state"
1 2
 import { CircleShape } from "types"
3
+import ShapeGroup from "./shape-group"
4
+import { getPointerEventInfo } from "utils/utils"
2 5
 
3
-export default function Circle({ point, radius }: CircleShape) {
4
-  return <circle cx={point[0]} cy={point[1]} r={radius} fill="black" />
6
+interface BaseCircleProps {
7
+  point: number[]
8
+  radius: number
9
+  fill?: string
10
+  stroke?: string
11
+  strokeWidth?: number
12
+}
13
+
14
+function BaseCircle({
15
+  point,
16
+  radius,
17
+  fill = "#ccc",
18
+  stroke = "none",
19
+  strokeWidth = 0,
20
+}: BaseCircleProps) {
21
+  return (
22
+    <circle
23
+      cx={point[0] + strokeWidth}
24
+      cy={point[1] + strokeWidth}
25
+      r={radius - strokeWidth}
26
+      fill={fill}
27
+      stroke={stroke}
28
+      strokeWidth={strokeWidth}
29
+    />
30
+  )
31
+}
32
+
33
+export default function Circle({ id, point, radius }: CircleShape) {
34
+  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
35
+  return (
36
+    <ShapeGroup id={id}>
37
+      <BaseCircle point={point} radius={radius} />
38
+      {isSelected && (
39
+        <BaseCircle
40
+          point={point}
41
+          radius={radius}
42
+          fill="none"
43
+          stroke="blue"
44
+          strokeWidth={1}
45
+        />
46
+      )}
47
+    </ShapeGroup>
48
+  )
5 49
 }

+ 50
- 0
components/canvas/shapes/dot.tsx View File

@@ -0,0 +1,50 @@
1
+import { useSelector } from "state"
2
+import { DotShape } from "types"
3
+import ShapeGroup from "./shape-group"
4
+
5
+interface BaseCircleProps {
6
+  point: number[]
7
+  fill?: string
8
+  stroke?: string
9
+  strokeWidth?: number
10
+}
11
+
12
+function BaseDot({
13
+  point,
14
+  fill = "#ccc",
15
+  stroke = "none",
16
+  strokeWidth = 0,
17
+}: BaseCircleProps) {
18
+  return (
19
+    <g>
20
+      <circle
21
+        cx={point[0] + strokeWidth}
22
+        cy={point[1] + strokeWidth}
23
+        r={8}
24
+        fill="transparent"
25
+        stroke="none"
26
+        strokeWidth="0"
27
+      />
28
+      <circle
29
+        cx={point[0] + strokeWidth}
30
+        cy={point[1] + strokeWidth}
31
+        r={Math.max(1, 4 - strokeWidth)}
32
+        fill={fill}
33
+        stroke={stroke}
34
+        strokeWidth={strokeWidth}
35
+      />
36
+    </g>
37
+  )
38
+}
39
+
40
+export default function Dot({ id, point }: DotShape) {
41
+  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
42
+  return (
43
+    <ShapeGroup id={id}>
44
+      <BaseDot point={point} />
45
+      {isSelected && (
46
+        <BaseDot point={point} fill="none" stroke="blue" strokeWidth={1} />
47
+      )}
48
+    </ShapeGroup>
49
+  )
50
+}

+ 42
- 6
components/canvas/shapes/rectangle.tsx View File

@@ -1,13 +1,49 @@
1
+import { useSelector } from "state"
1 2
 import { RectangleShape } from "types"
3
+import ShapeGroup from "./shape-group"
2 4
 
3
-export default function Rectangle({ point, size }: RectangleShape) {
5
+interface BaseRectangleProps {
6
+  point: number[]
7
+  size: number[]
8
+  fill?: string
9
+  stroke?: string
10
+  strokeWidth?: number
11
+}
12
+
13
+function BaseRectangle({
14
+  point,
15
+  size,
16
+  fill = "#ccc",
17
+  stroke = "none",
18
+  strokeWidth = 0,
19
+}: BaseRectangleProps) {
4 20
   return (
5 21
     <rect
6
-      x={point[0]}
7
-      y={point[1]}
8
-      width={size[0]}
9
-      height={size[1]}
10
-      fill="black"
22
+      x={point[0] + strokeWidth}
23
+      y={point[1] + strokeWidth}
24
+      width={size[0] - strokeWidth * 2}
25
+      height={size[1] - strokeWidth * 2}
26
+      fill={fill}
27
+      stroke={stroke}
28
+      strokeWidth={strokeWidth}
11 29
     />
12 30
   )
13 31
 }
32
+
33
+export default function Rectangle({ id, point, size }: RectangleShape) {
34
+  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
35
+  return (
36
+    <ShapeGroup id={id}>
37
+      <BaseRectangle point={point} size={size} />
38
+      {isSelected && (
39
+        <BaseRectangle
40
+          point={point}
41
+          size={size}
42
+          fill="none"
43
+          stroke="blue"
44
+          strokeWidth={1}
45
+        />
46
+      )}
47
+    </ShapeGroup>
48
+  )
49
+}

+ 37
- 0
components/canvas/shapes/shape-group.tsx View File

@@ -0,0 +1,37 @@
1
+import React from "react"
2
+import state from "state"
3
+import { Shape } from "types"
4
+import { getPointerEventInfo } from "utils/utils"
5
+
6
+export default function ShapeGroup({
7
+  id,
8
+  children,
9
+}: {
10
+  id: string
11
+  children: React.ReactNode
12
+}) {
13
+  return (
14
+    <g
15
+      onPointerDown={(e) =>
16
+        state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
17
+      }
18
+      onPointerUp={(e) =>
19
+        state.send("STOPPED_POINTING_SHAPE", {
20
+          id,
21
+          ...getPointerEventInfo(e),
22
+        })
23
+      }
24
+      onPointerEnter={(e) =>
25
+        state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) })
26
+      }
27
+      onPointerLeave={(e) =>
28
+        state.send("UNHOVERED_SHAPE", {
29
+          id,
30
+          ...getPointerEventInfo(e),
31
+        })
32
+      }
33
+    >
34
+      {children}
35
+    </g>
36
+  )
37
+}

+ 9
- 0
state/data.ts View File

@@ -38,6 +38,15 @@ export const defaultDocument: Data["document"] = {
38 38
           radius: 25,
39 39
           rotation: 0,
40 40
         },
41
+        shape3: {
42
+          id: "shape3",
43
+          type: ShapeType.Dot,
44
+          name: "Shape 3",
45
+          parentId: "page0",
46
+          childIndex: 3,
47
+          point: [500, 100],
48
+          rotation: 0,
49
+        },
41 50
       },
42 51
     },
43 52
   },

+ 128
- 14
state/sessions/brush-session.ts View File

@@ -1,12 +1,14 @@
1 1
 import { current } from "immer"
2
-import { Bounds, Data, Shape } from "types"
2
+import { Bounds, Data, Shape, ShapeType } from "types"
3 3
 import BaseSession from "./base-session"
4
-import { screenToWorld, getBoundsFromPoints } from "utils/utils"
4
+import shapeUtils from "utils/shapes"
5
+import { getBoundsFromPoints } from "utils/utils"
5 6
 import * as vec from "utils/vec"
7
+import { intersectCircleBounds } from "utils/intersections"
6 8
 
7 9
 interface BrushSnapshot {
8 10
   selectedIds: string[]
9
-  shapes: Shape[]
11
+  shapes: { shape: Shape; bounds: Bounds }[]
10 12
 }
11 13
 
12 14
 export default class BrushSession extends BaseSession {
@@ -24,21 +26,41 @@ export default class BrushSession extends BaseSession {
24 26
   update = (data: Data, point: number[]) => {
25 27
     const { origin, snapshot } = this
26 28
 
27
-    const bounds = getBoundsFromPoints(origin, point)
28
-
29
-    data.brush = bounds
30
-
31
-    const { minX: x, minY: y, width: w, height: h } = bounds
29
+    const brushBounds = getBoundsFromPoints(origin, point)
32 30
 
33 31
     data.selectedIds = [
34 32
       ...snapshot.selectedIds,
35
-      ...snapshot.shapes.map((shape) => {
36
-        return shape.id
37
-      }),
33
+      ...snapshot.shapes
34
+        .filter(({ shape, bounds }) => {
35
+          switch (shape.type) {
36
+            case ShapeType.Circle: {
37
+              return (
38
+                boundsContained(bounds, brushBounds) ||
39
+                intersectCircleBounds(shape.point, shape.radius, brushBounds)
40
+                  .length
41
+              )
42
+            }
43
+            case ShapeType.Dot: {
44
+              return (
45
+                boundsContained(bounds, brushBounds) ||
46
+                intersectCircleBounds(shape.point, 4, brushBounds).length
47
+              )
48
+            }
49
+            case ShapeType.Rectangle: {
50
+              return (
51
+                boundsContained(bounds, brushBounds) ||
52
+                boundsCollide(bounds, brushBounds)
53
+              )
54
+            }
55
+            default: {
56
+              return boundsContained(bounds, brushBounds)
57
+            }
58
+          }
59
+        })
60
+        .map(({ shape }) => shape.id),
38 61
     ]
39 62
 
40
-    // Narrow the  the items on the screen
41
-    data.brush = bounds
63
+    data.brush = brushBounds
42 64
   }
43 65
 
44 66
   cancel = (data: Data) => {
@@ -52,13 +74,105 @@ export default class BrushSession extends BaseSession {
52 74
 
53 75
   static getSnapshot(data: Data) {
54 76
     const {
77
+      selectedIds,
55 78
       document: { pages },
56 79
       currentPageId,
57 80
     } = current(data)
58 81
 
82
+    const currentlySelected = new Set(selectedIds)
83
+
59 84
     return {
60 85
       selectedIds: [...data.selectedIds],
61
-      shapes: Object.values(pages[currentPageId].shapes),
86
+      shapes: Object.values(pages[currentPageId].shapes)
87
+        .filter((shape) => !currentlySelected.has(shape.id))
88
+        .map((shape) => {
89
+          switch (shape.type) {
90
+            case ShapeType.Dot: {
91
+              return {
92
+                shape,
93
+                bounds: shapeUtils[shape.type].getBounds(shape),
94
+              }
95
+            }
96
+            case ShapeType.Circle: {
97
+              return {
98
+                shape,
99
+                bounds: shapeUtils[shape.type].getBounds(shape),
100
+              }
101
+            }
102
+            case ShapeType.Rectangle: {
103
+              return {
104
+                shape,
105
+                bounds: shapeUtils[shape.type].getBounds(shape),
106
+              }
107
+            }
108
+            default: {
109
+              return undefined
110
+            }
111
+          }
112
+        })
113
+        .filter(Boolean),
62 114
     }
63 115
   }
64 116
 }
117
+
118
+/**
119
+ * Get whether two bounds collide.
120
+ * @param a Bounds
121
+ * @param b Bounds
122
+ * @returns
123
+ */
124
+export function boundsCollide(a: Bounds, b: Bounds) {
125
+  return !(
126
+    a.maxX < b.minX ||
127
+    a.minX > b.maxX ||
128
+    a.maxY < b.minY ||
129
+    a.minY > b.maxY
130
+  )
131
+}
132
+
133
+/**
134
+ * Get whether the bounds of A contain the bounds of B. A perfect match will return true.
135
+ * @param a Bounds
136
+ * @param b Bounds
137
+ * @returns
138
+ */
139
+export function boundsContain(a: Bounds, b: Bounds) {
140
+  return (
141
+    a.minX < b.minX && a.minY < b.minY && a.maxY > b.maxY && a.maxX > b.maxX
142
+  )
143
+}
144
+
145
+/**
146
+ * Get whether the bounds of A are contained by the bounds of B.
147
+ * @param a Bounds
148
+ * @param b Bounds
149
+ * @returns
150
+ */
151
+export function boundsContained(a: Bounds, b: Bounds) {
152
+  return boundsContain(b, a)
153
+}
154
+
155
+/**
156
+ * Get whether two bounds are identical.
157
+ * @param a Bounds
158
+ * @param b Bounds
159
+ * @returns
160
+ */
161
+export function boundsAreEqual(a: Bounds, b: Bounds) {
162
+  return !(
163
+    b.maxX !== a.maxX ||
164
+    b.minX !== a.minX ||
165
+    b.maxY !== a.maxY ||
166
+    b.minY !== a.minY
167
+  )
168
+}
169
+
170
+/**
171
+ * Get whether a point is inside of a bounds.
172
+ * @param A
173
+ * @param b
174
+ * @returns
175
+ */
176
+export function pointInBounds(A: number[], b: Bounds) {
177
+  return !(A[0] < b.minX || A[0] > b.maxX || A[1] < b.minY || A[1] > b.maxY)
178
+}

+ 19
- 1
state/state.ts View File

@@ -35,7 +35,10 @@ const state = createState({
35 35
       },
36 36
     },
37 37
     brushSelecting: {
38
-      onEnter: "startBrushSession",
38
+      onEnter: [
39
+        { unless: "isPressingShiftKey", do: "clearSelection" },
40
+        "startBrushSession",
41
+      ],
39 42
       on: {
40 43
         MOVED_POINTER: "updateBrushSession",
41 44
         PANNED_CAMERA: "updateBrushSession",
@@ -44,6 +47,11 @@ const state = createState({
44 47
       },
45 48
     },
46 49
   },
50
+  conditions: {
51
+    isPressingShiftKey(data, payload: { shiftKey: boolean }) {
52
+      return payload.shiftKey
53
+    },
54
+  },
47 55
   actions: {
48 56
     cancelSession(data) {
49 57
       session.cancel(data)
@@ -62,6 +70,11 @@ const state = createState({
62 70
     updateBrushSession(data, payload: { point: number[] }) {
63 71
       session.update(data, screenToWorld(payload.point, data))
64 72
     },
73
+    // Selection
74
+    clearSelection(data) {
75
+      data.selectedIds = []
76
+    },
77
+    // Camera
65 78
     zoomCamera(data, payload: { delta: number; point: number[] }) {
66 79
       const { camera } = data
67 80
       const p0 = screenToWorld(payload.point, data)
@@ -81,6 +94,11 @@ const state = createState({
81 94
       )
82 95
     },
83 96
   },
97
+  values: {
98
+    selectedIds(data) {
99
+      return new Set(data.selectedIds)
100
+    },
101
+  },
84 102
 })
85 103
 
86 104
 let session: Sessions.BaseSession

+ 18
- 9
types.ts View File

@@ -21,18 +21,17 @@ export interface Page {
21 21
 }
22 22
 
23 23
 export enum ShapeType {
24
+  Dot = "dot",
24 25
   Circle = "circle",
25 26
   Ellipse = "ellipse",
26
-  Square = "square",
27
-  Rectangle = "rectangle",
28 27
   Line = "line",
29
-  LineSegment = "lineSegment",
30
-  Dot = "dot",
31 28
   Ray = "ray",
32
-  Glob = "glob",
33
-  Spline = "spline",
34
-  Cubic = "cubic",
35
-  Conic = "conic",
29
+  LineSegment = "lineSegment",
30
+  Rectangle = "rectangle",
31
+  // Glob = "glob",
32
+  // Spline = "spline",
33
+  // Cubic = "cubic",
34
+  // Conic = "conic",
36 35
 }
37 36
 
38 37
 export interface BaseShape {
@@ -87,9 +86,9 @@ export interface RectangleShape extends BaseShape {
87 86
 }
88 87
 
89 88
 export type Shape =
89
+  | DotShape
90 90
   | CircleShape
91 91
   | EllipseShape
92
-  | DotShape
93 92
   | LineShape
94 93
   | RayShape
95 94
   | LineSegmentShape
@@ -103,3 +102,13 @@ export interface Bounds {
103 102
   width: number
104 103
   height: number
105 104
 }
105
+
106
+export interface Shapes extends Record<ShapeType, Shape> {
107
+  [ShapeType.Dot]: DotShape
108
+  [ShapeType.Circle]: CircleShape
109
+  [ShapeType.Ellipse]: EllipseShape
110
+  [ShapeType.Line]: LineShape
111
+  [ShapeType.Ray]: RayShape
112
+  [ShapeType.LineSegment]: LineSegmentShape
113
+  [ShapeType.Rectangle]: RectangleShape
114
+}

+ 103
- 0
utils/intersections.ts View File

@@ -0,0 +1,103 @@
1
+import { Bounds } from "types"
2
+import * as vec from "utils/vec"
3
+
4
+interface Intersection {
5
+  didIntersect: boolean
6
+  message: string
7
+  points: number[][]
8
+}
9
+
10
+export function intersectCircleLine(
11
+  c: number[],
12
+  r: number,
13
+  a1: number[],
14
+  a2: number[]
15
+): Intersection {
16
+  const a =
17
+    (a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
18
+  const b =
19
+    2 * ((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
20
+  const cc =
21
+    c[0] * c[0] +
22
+    c[1] * c[1] +
23
+    a1[0] * a1[0] +
24
+    a1[1] * a1[1] -
25
+    2 * (c[0] * a1[0] + c[1] * a1[1]) -
26
+    r * r
27
+
28
+  const deter = b * b - 4 * a * cc
29
+
30
+  if (deter < 0) {
31
+    return { didIntersect: false, message: "outside", points: [] }
32
+  }
33
+
34
+  if (deter === 0) {
35
+    return { didIntersect: false, message: "tangent", points: [] }
36
+  }
37
+
38
+  var e = Math.sqrt(deter)
39
+  var u1 = (-b + e) / (2 * a)
40
+  var u2 = (-b - e) / (2 * a)
41
+  if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
42
+    if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
43
+      return { didIntersect: false, message: "outside", points: [] }
44
+    } else {
45
+      return { didIntersect: false, message: "inside", points: [] }
46
+    }
47
+  }
48
+
49
+  const result = { didIntersect: true, message: "intersection", points: [] }
50
+  if (0 <= u1 && u1 <= 1) result.points.push(vec.lrp(a1, a2, u1))
51
+  if (0 <= u2 && u2 <= 1) result.points.push(vec.lrp(a1, a2, u2))
52
+
53
+  return result
54
+}
55
+
56
+export function intersectCircleRectangle(
57
+  c: number[],
58
+  r: number,
59
+  point: number[],
60
+  size: number[]
61
+): Intersection[] {
62
+  const tl = point
63
+  const tr = vec.add(point, [size[0], 0])
64
+  const br = vec.add(point, size)
65
+  const bl = vec.add(point, [0, size[1]])
66
+
67
+  const intersections: Intersection[] = []
68
+
69
+  const topIntersection = intersectCircleLine(c, r, tl, tr)
70
+  if (topIntersection.didIntersect) {
71
+    intersections.push({ ...topIntersection, message: "top" })
72
+  }
73
+  const rightIntersection = intersectCircleLine(c, r, tr, br)
74
+  if (rightIntersection.didIntersect) {
75
+    intersections.push({ ...rightIntersection, message: "right" })
76
+  }
77
+  const bottomIntersection = intersectCircleLine(c, r, bl, br)
78
+  if (bottomIntersection.didIntersect) {
79
+    intersections.push({ ...bottomIntersection, message: "bottom" })
80
+  }
81
+  const leftIntersection = intersectCircleLine(c, r, tl, bl)
82
+  if (leftIntersection.didIntersect) {
83
+    intersections.push({ ...leftIntersection, message: "left" })
84
+  }
85
+
86
+  return intersections
87
+}
88
+
89
+export function intersectCircleBounds(
90
+  c: number[],
91
+  r: number,
92
+  bounds: Bounds
93
+): Intersection[] {
94
+  const { minX, minY, width, height } = bounds
95
+  const intersections = intersectCircleRectangle(
96
+    c,
97
+    r,
98
+    [minX, minY],
99
+    [width, height]
100
+  )
101
+
102
+  return intersections
103
+}

+ 310
- 0
utils/shapes.ts View File

@@ -0,0 +1,310 @@
1
+import {
2
+  boundsCollide,
3
+  boundsContain,
4
+  pointInBounds,
5
+} from "state/sessions/brush-session"
6
+import {
7
+  Shape,
8
+  Bounds,
9
+  ShapeType,
10
+  CircleShape,
11
+  DotShape,
12
+  RectangleShape,
13
+  Shapes,
14
+  EllipseShape,
15
+  LineShape,
16
+  RayShape,
17
+  LineSegmentShape,
18
+} from "types"
19
+import { intersectCircleBounds } from "./intersections"
20
+import * as vec from "./vec"
21
+
22
+type BaseShapeUtils<K extends ShapeType> = {
23
+  getBounds(shape: Shapes[K]): Bounds
24
+  hitTest(shape: Shapes[K], test: number[] | Bounds): boolean
25
+  rotate(shape: Shapes[K]): Shapes[K]
26
+  translate(shape: Shapes[K]): Shapes[K]
27
+  scale(shape: Shapes[K], scale: number): Shapes[K]
28
+  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
29
+}
30
+
31
+/* ----------------------- Dot ---------------------- */
32
+
33
+const DotUtils: BaseShapeUtils<ShapeType.Dot> = {
34
+  getBounds(shape) {
35
+    const {
36
+      point: [cx, cy],
37
+    } = shape
38
+
39
+    return {
40
+      minX: cx - 2,
41
+      maxX: cx + 2,
42
+      minY: cy - 2,
43
+      maxY: cy + 2,
44
+      width: 4,
45
+      height: 4,
46
+    }
47
+  },
48
+
49
+  hitTest(shape, test) {
50
+    if ("minX" in test) {
51
+      return pointInBounds(shape.point, test)
52
+    }
53
+    return vec.dist(shape.point, test) < 4
54
+  },
55
+
56
+  rotate(shape) {
57
+    return shape
58
+  },
59
+
60
+  translate(shape) {
61
+    return shape
62
+  },
63
+
64
+  scale(shape, scale: number) {
65
+    return shape
66
+  },
67
+
68
+  stretch(shape, scaleX: number, scaleY: number) {
69
+    return shape
70
+  },
71
+}
72
+
73
+/* --------------------- Circle --------------------- */
74
+
75
+const CircleUtils: BaseShapeUtils<ShapeType.Circle> = {
76
+  getBounds(shape) {
77
+    const {
78
+      point: [cx, cy],
79
+      radius,
80
+    } = shape
81
+
82
+    return {
83
+      minX: cx - radius,
84
+      maxX: cx + radius,
85
+      minY: cy - radius,
86
+      maxY: cy + radius,
87
+      width: radius * 2,
88
+      height: radius * 2,
89
+    }
90
+  },
91
+
92
+  hitTest(shape, test) {
93
+    if ("minX" in test) {
94
+      const bounds = CircleUtils.getBounds(shape)
95
+      return (
96
+        boundsContain(bounds, test) ||
97
+        intersectCircleBounds(shape.point, shape.radius, bounds).length > 0
98
+      )
99
+    }
100
+    return vec.dist(shape.point, test) < 4
101
+  },
102
+
103
+  rotate(shape) {
104
+    return shape
105
+  },
106
+
107
+  translate(shape) {
108
+    return shape
109
+  },
110
+
111
+  scale(shape, scale: number) {
112
+    return shape
113
+  },
114
+
115
+  stretch(shape, scaleX: number, scaleY: number) {
116
+    return shape
117
+  },
118
+}
119
+
120
+/* --------------------- Ellipse -------------------- */
121
+
122
+const EllipseUtils: BaseShapeUtils<ShapeType.Ellipse> = {
123
+  getBounds(shape) {
124
+    return {
125
+      minX: 0,
126
+      minY: 0,
127
+      maxX: 0,
128
+      maxY: 0,
129
+      width: 0,
130
+      height: 0,
131
+    }
132
+  },
133
+
134
+  hitTest(shape) {
135
+    return true
136
+  },
137
+
138
+  rotate(shape) {
139
+    return shape
140
+  },
141
+
142
+  translate(shape) {
143
+    return shape
144
+  },
145
+
146
+  scale(shape, scale: number) {
147
+    return shape
148
+  },
149
+
150
+  stretch(shape, scaleX: number, scaleY: number) {
151
+    return shape
152
+  },
153
+}
154
+
155
+/* ---------------------- Line ---------------------- */
156
+
157
+const LineUtils: BaseShapeUtils<ShapeType.Line> = {
158
+  getBounds(shape) {
159
+    return {
160
+      minX: 0,
161
+      minY: 0,
162
+      maxX: 0,
163
+      maxY: 0,
164
+      width: 0,
165
+      height: 0,
166
+    }
167
+  },
168
+
169
+  hitTest(shape) {
170
+    return true
171
+  },
172
+
173
+  rotate(shape) {
174
+    return shape
175
+  },
176
+
177
+  translate(shape) {
178
+    return shape
179
+  },
180
+
181
+  scale(shape, scale: number) {
182
+    return shape
183
+  },
184
+
185
+  stretch(shape, scaleX: number, scaleY: number) {
186
+    return shape
187
+  },
188
+}
189
+
190
+/* ----------------------- Ray ---------------------- */
191
+
192
+const RayUtils: BaseShapeUtils<ShapeType.Ray> = {
193
+  getBounds(shape) {
194
+    return {
195
+      minX: Infinity,
196
+      minY: Infinity,
197
+      maxX: Infinity,
198
+      maxY: Infinity,
199
+      width: Infinity,
200
+      height: Infinity,
201
+    }
202
+  },
203
+
204
+  hitTest(shape) {
205
+    return true
206
+  },
207
+
208
+  rotate(shape) {
209
+    return shape
210
+  },
211
+
212
+  translate(shape) {
213
+    return shape
214
+  },
215
+
216
+  scale(shape, scale: number) {
217
+    return shape
218
+  },
219
+
220
+  stretch(shape, scaleX: number, scaleY: number) {
221
+    return shape
222
+  },
223
+}
224
+
225
+/* ------------------ Line Segment ------------------ */
226
+
227
+const LineSegmentUtils: BaseShapeUtils<ShapeType.LineSegment> = {
228
+  getBounds(shape) {
229
+    return {
230
+      minX: 0,
231
+      minY: 0,
232
+      maxX: 0,
233
+      maxY: 0,
234
+      width: 0,
235
+      height: 0,
236
+    }
237
+  },
238
+
239
+  hitTest(shape) {
240
+    return true
241
+  },
242
+
243
+  rotate(shape) {
244
+    return shape
245
+  },
246
+
247
+  translate(shape) {
248
+    return shape
249
+  },
250
+
251
+  scale(shape, scale: number) {
252
+    return shape
253
+  },
254
+
255
+  stretch(shape, scaleX: number, scaleY: number) {
256
+    return shape
257
+  },
258
+}
259
+
260
+/* -------------------- Rectangle ------------------- */
261
+
262
+const RectangleUtils: BaseShapeUtils<ShapeType.Rectangle> = {
263
+  getBounds(shape) {
264
+    const {
265
+      point: [x, y],
266
+      size: [width, height],
267
+    } = shape
268
+
269
+    return {
270
+      minX: x,
271
+      maxX: x + width,
272
+      minY: y,
273
+      maxY: y + height,
274
+      width,
275
+      height,
276
+    }
277
+  },
278
+
279
+  hitTest(shape) {
280
+    return true
281
+  },
282
+
283
+  rotate(shape) {
284
+    return shape
285
+  },
286
+
287
+  translate(shape) {
288
+    return shape
289
+  },
290
+
291
+  scale(shape, scale: number) {
292
+    return shape
293
+  },
294
+
295
+  stretch(shape, scaleX: number, scaleY: number) {
296
+    return shape
297
+  },
298
+}
299
+
300
+const shapeUtils: { [K in ShapeType]: BaseShapeUtils<K> } = {
301
+  [ShapeType.Dot]: DotUtils,
302
+  [ShapeType.Circle]: CircleUtils,
303
+  [ShapeType.Ellipse]: EllipseUtils,
304
+  [ShapeType.Line]: LineUtils,
305
+  [ShapeType.Ray]: RayUtils,
306
+  [ShapeType.LineSegment]: LineSegmentUtils,
307
+  [ShapeType.Rectangle]: RectangleUtils,
308
+}
309
+
310
+export default shapeUtils

Loading…
Cancel
Save