2021-05-28 14:37:23 +00:00
|
|
|
import { current } from 'immer'
|
|
|
|
import { Data, DrawShape } from 'types'
|
|
|
|
import BaseSession from './base-session'
|
|
|
|
import { getShapeUtils } from 'lib/shape-utils'
|
2021-06-04 16:08:43 +00:00
|
|
|
import { getPage, getShape, updateParents } from 'utils/utils'
|
2021-05-28 14:37:23 +00:00
|
|
|
import * as vec from 'utils/vec'
|
|
|
|
import commands from 'state/commands'
|
2021-05-27 17:59:40 +00:00
|
|
|
|
2021-05-30 21:24:31 +00:00
|
|
|
let prevEndPoint: number[]
|
|
|
|
|
2021-05-27 17:59:40 +00:00
|
|
|
export default class BrushSession extends BaseSession {
|
|
|
|
origin: number[]
|
2021-05-27 20:11:48 +00:00
|
|
|
previous: number[]
|
2021-06-06 07:33:30 +00:00
|
|
|
last: number[]
|
2021-05-27 17:59:40 +00:00
|
|
|
points: number[][]
|
|
|
|
snapshot: DrawSnapshot
|
2021-05-30 21:24:31 +00:00
|
|
|
isLocked: boolean
|
|
|
|
lockedDirection: 'horizontal' | 'vertical'
|
2021-05-27 17:59:40 +00:00
|
|
|
|
2021-05-30 21:24:31 +00:00
|
|
|
constructor(data: Data, id: string, point: number[], isLocked = false) {
|
2021-05-27 17:59:40 +00:00
|
|
|
super(data)
|
|
|
|
this.origin = point
|
2021-05-27 20:11:48 +00:00
|
|
|
this.previous = point
|
2021-06-06 07:33:30 +00:00
|
|
|
this.last = point
|
2021-05-27 17:59:40 +00:00
|
|
|
this.snapshot = getDrawSnapshot(data, id)
|
|
|
|
|
2021-06-12 08:25:04 +00:00
|
|
|
// Add a first point but don't update the shape yet. We'll update
|
|
|
|
// when the draw session ends; if the user hasn't added additional
|
|
|
|
// points, this single point will be interpreted as a "dot" shape.
|
|
|
|
this.points = [[0, 0]]
|
|
|
|
|
|
|
|
const shape = getPage(data).shapes[id]
|
|
|
|
|
2021-06-04 16:08:43 +00:00
|
|
|
getShapeUtils(shape).translateTo(shape, point)
|
2021-06-12 08:10:12 +00:00
|
|
|
|
|
|
|
updateParents(data, [shape.id])
|
2021-05-27 17:59:40 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 07:33:30 +00:00
|
|
|
update = (
|
|
|
|
data: Data,
|
|
|
|
point: number[],
|
|
|
|
pressure: number,
|
|
|
|
isLocked = false
|
|
|
|
) => {
|
2021-05-29 12:54:33 +00:00
|
|
|
const { snapshot } = this
|
2021-05-27 17:59:40 +00:00
|
|
|
|
2021-05-30 21:24:31 +00:00
|
|
|
const delta = vec.vec(this.origin, point)
|
|
|
|
|
2021-06-12 08:25:04 +00:00
|
|
|
// Drawing while holding shift will "lock" the pen to either the
|
|
|
|
// x or y axis, depending on which direction has the greater
|
|
|
|
// delta. Pressing shift will also add more points to "return"
|
|
|
|
// the pen to the axis.
|
2021-05-30 21:24:31 +00:00
|
|
|
if (isLocked) {
|
|
|
|
if (!this.isLocked && this.points.length > 1) {
|
|
|
|
this.isLocked = true
|
|
|
|
const returning = [...this.previous]
|
|
|
|
|
2021-06-12 08:25:04 +00:00
|
|
|
const isVertical = Math.abs(delta[0]) < Math.abs(delta[1])
|
|
|
|
|
|
|
|
if (isVertical) {
|
2021-05-30 21:24:31 +00:00
|
|
|
this.lockedDirection = 'vertical'
|
|
|
|
returning[0] = this.origin[0]
|
|
|
|
} else {
|
|
|
|
this.lockedDirection = 'horizontal'
|
|
|
|
returning[1] = this.origin[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
this.previous = returning
|
|
|
|
this.points.push(vec.sub(returning, this.origin))
|
|
|
|
}
|
2021-06-12 08:25:04 +00:00
|
|
|
} else if (this.isLocked) {
|
|
|
|
this.isLocked = false
|
2021-05-30 21:24:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.isLocked) {
|
|
|
|
if (this.lockedDirection === 'vertical') {
|
|
|
|
point[0] = this.origin[0]
|
|
|
|
} else {
|
|
|
|
point[1] = this.origin[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-12 16:34:38 +00:00
|
|
|
// Low pass the current input point against the previous one
|
|
|
|
const nextPrev = vec.med(this.previous, point)
|
|
|
|
|
|
|
|
// A delta to project the projected point
|
2021-06-12 16:41:38 +00:00
|
|
|
const offset = vec.sub(nextPrev, this.previous)
|
2021-06-12 16:34:38 +00:00
|
|
|
|
|
|
|
this.previous = nextPrev
|
|
|
|
|
|
|
|
// Generate some temporary points towards a projected point
|
2021-06-12 16:04:11 +00:00
|
|
|
const temporaryPoints = [0.7, 0.9, 0.95, 1].map((v) =>
|
|
|
|
vec.round([
|
2021-06-12 16:34:38 +00:00
|
|
|
...vec.sub(
|
|
|
|
vec.lrp(this.previous, vec.add(point, offset), v),
|
|
|
|
this.origin
|
|
|
|
),
|
2021-06-12 16:04:11 +00:00
|
|
|
pressure,
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
2021-06-12 08:25:04 +00:00
|
|
|
// Don't add duplicate points. It's important to test against the
|
|
|
|
// adjusted (low-passed) point rather than the input point.
|
2021-05-30 21:24:31 +00:00
|
|
|
|
2021-06-12 16:04:11 +00:00
|
|
|
const newPoint = vec.round([
|
|
|
|
...vec.sub(this.previous, this.origin),
|
|
|
|
pressure,
|
|
|
|
])
|
|
|
|
|
|
|
|
if (vec.isEqual(this.last, newPoint)) return
|
2021-06-12 08:25:04 +00:00
|
|
|
|
2021-06-12 16:04:11 +00:00
|
|
|
this.last = newPoint
|
|
|
|
|
|
|
|
this.points.push(newPoint)
|
2021-06-06 07:33:30 +00:00
|
|
|
|
2021-06-12 08:25:04 +00:00
|
|
|
// We draw a dot when the number of points is 1 or 2, so this guard
|
|
|
|
// prevents a "flash" of a dot when a user begins drawing a line.
|
|
|
|
if (this.points.length <= 2) return
|
2021-05-27 17:59:40 +00:00
|
|
|
|
2021-06-12 16:04:11 +00:00
|
|
|
// If the delta between the averaged point and the real point is
|
|
|
|
// too great, skip the temporary points. This avoids "sawblading".
|
|
|
|
const tooFarForTemporaryPoints = vec.dist(newPoint, temporaryPoints[3]) > 32
|
|
|
|
|
|
|
|
// Update the points and update the shape's parents.
|
2021-06-04 16:08:43 +00:00
|
|
|
const shape = getShape(data, snapshot.id) as DrawShape
|
2021-06-12 16:04:11 +00:00
|
|
|
|
|
|
|
getShapeUtils(shape).setProperty(
|
|
|
|
shape,
|
|
|
|
'points',
|
|
|
|
tooFarForTemporaryPoints
|
|
|
|
? [...this.points]
|
|
|
|
: [...this.points, ...temporaryPoints]
|
|
|
|
)
|
|
|
|
|
2021-06-04 16:14:01 +00:00
|
|
|
updateParents(data, [shape.id])
|
2021-05-27 17:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cancel = (data: Data) => {
|
2021-05-29 12:54:33 +00:00
|
|
|
const { snapshot } = this
|
2021-06-04 16:08:43 +00:00
|
|
|
const shape = getShape(data, snapshot.id) as DrawShape
|
2021-05-29 12:54:33 +00:00
|
|
|
getShapeUtils(shape).setProperty(shape, 'points', snapshot.points)
|
2021-06-04 16:14:01 +00:00
|
|
|
updateParents(data, [shape.id])
|
2021-05-27 17:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
complete = (data: Data) => {
|
2021-06-07 11:18:50 +00:00
|
|
|
const { snapshot } = this
|
|
|
|
const page = getPage(data)
|
|
|
|
const shape = page.shapes[snapshot.id] as DrawShape
|
2021-05-29 22:27:19 +00:00
|
|
|
|
2021-06-12 16:04:11 +00:00
|
|
|
if (shape.points.length < this.points.length) {
|
|
|
|
getShapeUtils(shape).setProperty(shape, 'points', this.points)
|
|
|
|
}
|
|
|
|
|
|
|
|
getShapeUtils(shape).onSessionComplete(shape)
|
2021-06-12 08:10:12 +00:00
|
|
|
|
|
|
|
updateParents(data, [shape.id])
|
|
|
|
|
2021-06-07 11:18:50 +00:00
|
|
|
commands.draw(data, this.snapshot.id)
|
2021-05-27 17:59:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getDrawSnapshot(data: Data, shapeId: string) {
|
|
|
|
const page = getPage(current(data))
|
2021-06-02 11:50:34 +00:00
|
|
|
const { points } = page.shapes[shapeId] as DrawShape
|
|
|
|
|
2021-05-27 17:59:40 +00:00
|
|
|
return {
|
2021-05-29 12:54:33 +00:00
|
|
|
id: shapeId,
|
2021-05-27 17:59:40 +00:00
|
|
|
points,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export type DrawSnapshot = ReturnType<typeof getDrawSnapshot>
|