Tldraw/packages/tldraw/src/lib/shapes/shared/freehand/setStrokePointRadii.ts

128 wiersze
3.7 KiB
TypeScript

import { EASINGS } from '@tldraw/editor'
import { StrokeOptions, StrokePoint } from './types'
const { min } = Math
// This is the rate of change for simulated pressure. It could be an option.
const RATE_OF_PRESSURE_CHANGE = 0.275
/** @public */
export function setStrokePointRadii(strokePoints: StrokePoint[], options: StrokeOptions) {
const {
size = 16,
thinning = 0.5,
simulatePressure = true,
easing = (t) => t,
start = {},
end = {},
} = options
const { easing: taperStartEase = EASINGS.easeOutQuad } = start
const { easing: taperEndEase = EASINGS.easeOutCubic } = end
const totalLength = strokePoints[strokePoints.length - 1].runningLength
let firstRadius: number | undefined
let prevPressure = strokePoints[0].pressure
let strokePoint: StrokePoint
if (!simulatePressure && totalLength < size) {
const max = strokePoints.reduce((max, curr) => Math.max(max, curr.pressure), 0.5)
strokePoints.forEach((sp) => {
sp.pressure = max
sp.radius = size * easing(0.5 - thinning * (0.5 - sp.pressure))
})
return strokePoints
} else {
// Calculate initial pressure based on the average of the first
// n number of points. This prevents "dots" at the start of the
// line. Drawn lines almost always start slow!
let p: number
for (let i = 0, n = strokePoints.length; i < n; i++) {
strokePoint = strokePoints[i]
if (strokePoint.runningLength > size * 5) break
const sp = min(1, strokePoint.distance / size)
if (simulatePressure) {
const rp = min(1, 1 - sp)
p = min(1, prevPressure + (rp - prevPressure) * (sp * RATE_OF_PRESSURE_CHANGE))
} else {
p = min(1, prevPressure + (strokePoint.pressure - prevPressure) * 0.5)
}
prevPressure = prevPressure + (p - prevPressure) * 0.5
}
// Now calculate pressure and radius for each point
for (let i = 0; i < strokePoints.length; i++) {
strokePoint = strokePoints[i]
if (thinning) {
let { pressure } = strokePoint
const sp = min(1, strokePoint.distance / size)
if (simulatePressure) {
// If we're simulating pressure, then do so based on the distance
// between the current point and the previous point, and the size
// of the stroke.
const rp = min(1, 1 - sp)
pressure = min(1, prevPressure + (rp - prevPressure) * (sp * RATE_OF_PRESSURE_CHANGE))
} else {
// Otherwise, use the input pressure slightly smoothed based on the
// distance between the current point and the previous point.
pressure = min(
1,
prevPressure + (pressure - prevPressure) * (sp * RATE_OF_PRESSURE_CHANGE)
)
}
strokePoint.radius = size * easing(0.5 - thinning * (0.5 - pressure))
prevPressure = pressure
} else {
strokePoint.radius = size / 2
}
if (firstRadius === undefined) {
firstRadius = strokePoint.radius
}
}
}
const taperStart =
start.taper === false
? 0
: start.taper === true
? Math.max(size, totalLength)
: (start.taper as number)
const taperEnd =
end.taper === false
? 0
: end.taper === true
? Math.max(size, totalLength)
: (end.taper as number)
if (taperStart || taperEnd) {
for (let i = 0; i < strokePoints.length; i++) {
strokePoint = strokePoints[i]
/*
Apply tapering
If the current length is within the taper distance at either the
start or the end, calculate the taper strengths. Apply the smaller
of the two taper strengths to the radius.
*/
const { runningLength } = strokePoint
const ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1
const te =
totalLength - runningLength < taperEnd
? taperEndEase((totalLength - runningLength) / taperEnd)
: 1
strokePoint.radius = Math.max(0.01, strokePoint.radius * Math.min(ts, te))
}
}
return strokePoints
}