You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

shape-utils.ts 5.4KB

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