Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

ellipse.tsx 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { v4 as uuid } from 'uuid'
  2. import * as vec from 'utils/vec'
  3. import { EllipseShape, ShapeType } from 'types'
  4. import { getShapeUtils, registerShapeUtils } from './index'
  5. import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
  6. import { intersectEllipseBounds } from 'utils/intersections'
  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'
  19. import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
  20. import getStroke from 'perfect-freehand'
  21. const pathCache = new WeakMap<EllipseShape, string>([])
  22. const ellipse = registerShapeUtils<EllipseShape>({
  23. boundsCache: new WeakMap([]),
  24. create(props) {
  25. return {
  26. id: uuid(),
  27. seed: Math.random(),
  28. type: ShapeType.Ellipse,
  29. isGenerated: false,
  30. name: 'Ellipse',
  31. parentId: 'page0',
  32. childIndex: 0,
  33. point: [0, 0],
  34. radiusX: 1,
  35. radiusY: 1,
  36. rotation: 0,
  37. isAspectRatioLocked: false,
  38. isLocked: false,
  39. isHidden: false,
  40. style: defaultStyle,
  41. ...props,
  42. }
  43. },
  44. render(shape) {
  45. const { id, radiusX, radiusY, style } = shape
  46. const styles = getShapeStyle(style)
  47. if (!pathCache.has(shape)) {
  48. renderPath(shape)
  49. }
  50. const path = pathCache.get(shape)
  51. return (
  52. <g id={id}>
  53. <ellipse
  54. id={id}
  55. cx={radiusX}
  56. cy={radiusY}
  57. rx={Math.max(0, radiusX - Number(styles.strokeWidth) / 2)}
  58. ry={Math.max(0, radiusY - Number(styles.strokeWidth) / 2)}
  59. stroke="none"
  60. />
  61. <path d={path} fill={styles.stroke} />
  62. </g>
  63. )
  64. // return (
  65. // <ellipse
  66. // id={id}
  67. // cx={radiusX}
  68. // cy={radiusY}
  69. // rx={Math.max(0, radiusX - Number(styles.strokeWidth) / 2)}
  70. // ry={Math.max(0, radiusY - Number(styles.strokeWidth) / 2)}
  71. // />
  72. // )
  73. },
  74. getBounds(shape) {
  75. if (!this.boundsCache.has(shape)) {
  76. const { radiusX, radiusY } = shape
  77. const bounds = {
  78. minX: 0,
  79. minY: 0,
  80. maxX: radiusX * 2,
  81. maxY: radiusY * 2,
  82. width: radiusX * 2,
  83. height: radiusY * 2,
  84. }
  85. this.boundsCache.set(shape, bounds)
  86. }
  87. return translateBounds(this.boundsCache.get(shape), shape.point)
  88. },
  89. getRotatedBounds(shape) {
  90. return getRotatedEllipseBounds(
  91. shape.point[0],
  92. shape.point[1],
  93. shape.radiusX,
  94. shape.radiusY,
  95. shape.rotation
  96. )
  97. },
  98. getCenter(shape) {
  99. return [shape.point[0] + shape.radiusX, shape.point[1] + shape.radiusY]
  100. },
  101. hitTest(shape, point) {
  102. return pointInEllipse(
  103. point,
  104. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  105. shape.radiusX,
  106. shape.radiusY,
  107. shape.rotation
  108. )
  109. },
  110. hitTestBounds(this, shape, brushBounds) {
  111. const shapeBounds = this.getBounds(shape)
  112. return (
  113. boundsContained(shapeBounds, brushBounds) ||
  114. intersectEllipseBounds(
  115. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  116. shape.radiusX,
  117. shape.radiusY,
  118. brushBounds,
  119. shape.rotation
  120. ).length > 0
  121. )
  122. },
  123. transform(shape, bounds, { scaleX, scaleY, initialShape }) {
  124. // TODO: Locked aspect ratio transform
  125. shape.point = [bounds.minX, bounds.minY]
  126. shape.radiusX = bounds.width / 2
  127. shape.radiusY = bounds.height / 2
  128. shape.rotation =
  129. (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
  130. ? -initialShape.rotation
  131. : initialShape.rotation
  132. return this
  133. },
  134. transformSingle(shape, bounds, info) {
  135. return this.transform(shape, bounds, info)
  136. },
  137. })
  138. export default ellipse
  139. function renderPath(shape: EllipseShape) {
  140. const { style, id, radiusX, radiusY, point } = shape
  141. const getRandom = rng(id)
  142. const center = vec.sub(getShapeUtils(shape).getCenter(shape), point)
  143. const strokeWidth = +getShapeStyle(style).strokeWidth
  144. const rx = radiusX + getRandom() * strokeWidth
  145. const ry = radiusY + getRandom() * strokeWidth
  146. const points: number[][] = []
  147. const start = Math.PI + Math.PI * getRandom()
  148. const overlap = Math.PI / 12
  149. for (let i = 2; i < 8; i++) {
  150. const rads = start + overlap * 2 * (i / 8)
  151. const x = rx * Math.cos(rads) + center[0]
  152. const y = ry * Math.sin(rads) + center[1]
  153. points.push([x, y])
  154. }
  155. for (let i = 5; i < 32; i++) {
  156. const rads = start + overlap * 2 + Math.PI * 2.5 * ease(i / 35)
  157. const x = rx * Math.cos(rads) + center[0]
  158. const y = ry * Math.sin(rads) + center[1]
  159. points.push([x, y])
  160. }
  161. for (let i = 0; i < 8; i++) {
  162. const rads = start + overlap * 2 * (i / 4)
  163. const x = rx * Math.cos(rads) + center[0]
  164. const y = ry * Math.sin(rads) + center[1]
  165. points.push([x, y])
  166. }
  167. const stroke = getStroke(points, {
  168. size: 1 + strokeWidth * 2,
  169. thinning: 0.6,
  170. easing: (t) => t * t * t * t,
  171. end: { taper: strokeWidth * 20 },
  172. start: { taper: strokeWidth * 20 },
  173. simulatePressure: false,
  174. })
  175. pathCache.set(shape, getSvgPathFromStroke(stroke))
  176. }