Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { current } from 'immer'
  2. import { Data, DrawShape } from 'types'
  3. import BaseSession from './base-session'
  4. import { getShapeUtils } from 'lib/shape-utils'
  5. import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
  6. import * as vec from 'utils/vec'
  7. import commands from 'state/commands'
  8. let prevEndPoint: number[]
  9. export default class BrushSession extends BaseSession {
  10. origin: number[]
  11. previous: number[]
  12. last: number[]
  13. points: number[][]
  14. snapshot: DrawSnapshot
  15. isLocked: boolean
  16. lockedDirection: 'horizontal' | 'vertical'
  17. constructor(data: Data, id: string, point: number[], isLocked = false) {
  18. super(data)
  19. this.origin = point
  20. this.previous = point
  21. this.last = point
  22. this.snapshot = getDrawSnapshot(data, id)
  23. // Add a first point but don't update the shape yet. We'll update
  24. // when the draw session ends; if the user hasn't added additional
  25. // points, this single point will be interpreted as a "dot" shape.
  26. this.points = [[0, 0]]
  27. const shape = getPage(data).shapes[id]
  28. getShapeUtils(shape).translateTo(shape, point)
  29. updateParents(data, [shape.id])
  30. }
  31. update = (
  32. data: Data,
  33. point: number[],
  34. pressure: number,
  35. isLocked = false
  36. ) => {
  37. const { snapshot } = this
  38. const delta = vec.vec(this.origin, point)
  39. // Drawing while holding shift will "lock" the pen to either the
  40. // x or y axis, depending on which direction has the greater
  41. // delta. Pressing shift will also add more points to "return"
  42. // the pen to the axis.
  43. if (isLocked) {
  44. if (!this.isLocked && this.points.length > 1) {
  45. this.isLocked = true
  46. const returning = [...this.previous]
  47. const isVertical = Math.abs(delta[0]) < Math.abs(delta[1])
  48. if (isVertical) {
  49. this.lockedDirection = 'vertical'
  50. returning[0] = this.origin[0]
  51. } else {
  52. this.lockedDirection = 'horizontal'
  53. returning[1] = this.origin[1]
  54. }
  55. this.previous = returning
  56. this.points.push(vec.sub(returning, this.origin))
  57. }
  58. } else if (this.isLocked) {
  59. this.isLocked = false
  60. }
  61. if (this.isLocked) {
  62. if (this.lockedDirection === 'vertical') {
  63. point[0] = this.origin[0]
  64. } else {
  65. point[1] = this.origin[1]
  66. }
  67. }
  68. // Low pass the current input point against the previous one
  69. const nextPrev = vec.med(this.previous, point)
  70. // A delta to project the projected point
  71. const offset = vec.sub(nextPrev, this.previous)
  72. this.previous = nextPrev
  73. // Generate some temporary points towards a projected point
  74. const temporaryPoints = [0.7, 0.9, 0.95, 1].map((v) =>
  75. vec.round([
  76. ...vec.sub(
  77. vec.lrp(this.previous, vec.add(point, offset), v),
  78. this.origin
  79. ),
  80. pressure,
  81. ])
  82. )
  83. // Don't add duplicate points. It's important to test against the
  84. // adjusted (low-passed) point rather than the input point.
  85. const newPoint = vec.round([
  86. ...vec.sub(this.previous, this.origin),
  87. pressure,
  88. ])
  89. if (vec.isEqual(this.last, newPoint)) return
  90. this.last = newPoint
  91. this.points.push(newPoint)
  92. // We draw a dot when the number of points is 1 or 2, so this guard
  93. // prevents a "flash" of a dot when a user begins drawing a line.
  94. if (this.points.length <= 2) return
  95. // If the delta between the averaged point and the real point is
  96. // too great, skip the temporary points. This avoids "sawblading".
  97. const tooFarForTemporaryPoints =
  98. !isMobile() && vec.dist(newPoint, temporaryPoints[3]) > 32
  99. // Update the points and update the shape's parents.
  100. const shape = getShape(data, snapshot.id) as DrawShape
  101. getShapeUtils(shape).setProperty(
  102. shape,
  103. 'points',
  104. tooFarForTemporaryPoints
  105. ? [...this.points]
  106. : [...this.points, ...temporaryPoints]
  107. )
  108. updateParents(data, [shape.id])
  109. }
  110. cancel = (data: Data) => {
  111. const { snapshot } = this
  112. const shape = getShape(data, snapshot.id) as DrawShape
  113. getShapeUtils(shape).setProperty(shape, 'points', snapshot.points)
  114. updateParents(data, [shape.id])
  115. }
  116. complete = (data: Data) => {
  117. const { snapshot } = this
  118. const page = getPage(data)
  119. const shape = page.shapes[snapshot.id] as DrawShape
  120. if (shape.points.length < this.points.length) {
  121. getShapeUtils(shape).setProperty(shape, 'points', this.points)
  122. }
  123. getShapeUtils(shape).onSessionComplete(shape)
  124. updateParents(data, [shape.id])
  125. commands.draw(data, this.snapshot.id)
  126. }
  127. }
  128. export function getDrawSnapshot(data: Data, shapeId: string) {
  129. const page = getPage(current(data))
  130. const { points } = page.shapes[shapeId] as DrawShape
  131. return {
  132. id: shapeId,
  133. points,
  134. }
  135. }
  136. export type DrawSnapshot = ReturnType<typeof getDrawSnapshot>