kopia lustrzana https://github.com/Tldraw/Tldraw
188 wiersze
5.0 KiB
TypeScript
188 wiersze
5.0 KiB
TypeScript
import { Vec, VecLike } from '@tldraw/editor'
|
|
import type { StrokeOptions, StrokePoint } from './types'
|
|
|
|
const MIN_START_PRESSURE = 0.025
|
|
const MIN_END_PRESSURE = 0.01
|
|
|
|
/**
|
|
* ## getStrokePoints
|
|
*
|
|
* Get an array of points as objects with an adjusted point, pressure, vector, distance, and
|
|
* runningLength.
|
|
*
|
|
* @param points - An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is
|
|
* optional in both cases.
|
|
* @param options - An object with options.
|
|
* @public
|
|
*/
|
|
export function getStrokePoints(
|
|
rawInputPoints: VecLike[],
|
|
options: StrokeOptions = {}
|
|
): StrokePoint[] {
|
|
const { streamline = 0.5, size = 16, simulatePressure = false } = options
|
|
|
|
// If we don't have any points, return an empty array.
|
|
if (rawInputPoints.length === 0) return []
|
|
|
|
// Find the interpolation level between points.
|
|
const t = 0.15 + (1 - streamline) * 0.85
|
|
|
|
// Whatever the input is, make sure that the points are in number[][].
|
|
let pts = rawInputPoints.map(Vec.From)
|
|
|
|
let pointsRemovedFromNearEnd = 0
|
|
|
|
if (!simulatePressure) {
|
|
// Strip low pressure points from the start of the array.
|
|
let pt = pts[0]
|
|
while (pt) {
|
|
if (pt.z >= MIN_START_PRESSURE) break
|
|
pts.shift()
|
|
pt = pts[0]
|
|
}
|
|
}
|
|
|
|
if (!simulatePressure) {
|
|
// Strip low pressure points from the end of the array.
|
|
let pt = pts[pts.length - 1]
|
|
while (pt) {
|
|
if (pt.z >= MIN_END_PRESSURE) break
|
|
pts.pop()
|
|
pt = pts[pts.length - 1]
|
|
}
|
|
}
|
|
|
|
if (pts.length === 0)
|
|
return [
|
|
{
|
|
point: Vec.From(rawInputPoints[0]),
|
|
input: Vec.From(rawInputPoints[0]),
|
|
pressure: simulatePressure ? 0.5 : 0.15,
|
|
vector: new Vec(1, 1),
|
|
distance: 0,
|
|
runningLength: 0,
|
|
radius: 1,
|
|
},
|
|
]
|
|
|
|
// Strip points that are too close to the first point.
|
|
let pt = pts[1]
|
|
while (pt) {
|
|
if (Vec.Dist2(pt, pts[0]) > (size / 3) ** 2) break
|
|
pts[0].z = Math.max(pts[0].z, pt.z) // Use maximum pressure
|
|
pts.splice(1, 1)
|
|
pt = pts[1]
|
|
}
|
|
|
|
// Strip points that are too close to the last point.
|
|
const last = pts.pop()!
|
|
pt = pts[pts.length - 1]
|
|
while (pt) {
|
|
if (Vec.Dist2(pt, last) > (size / 3) ** 2) break
|
|
pts.pop()
|
|
pt = pts[pts.length - 1]
|
|
pointsRemovedFromNearEnd++
|
|
}
|
|
pts.push(last)
|
|
|
|
const isComplete =
|
|
options.last ||
|
|
!options.simulatePressure ||
|
|
(pts.length > 1 && Vec.Dist2(pts[pts.length - 1], pts[pts.length - 2]) < size ** 2) ||
|
|
pointsRemovedFromNearEnd > 0
|
|
|
|
// Add extra points between the two, to help avoid "dash" lines
|
|
// for strokes with tapered start and ends. Don't mutate the
|
|
// input array!
|
|
if (pts.length === 2 && options.simulatePressure) {
|
|
const last = pts[1]
|
|
pts = pts.slice(0, -1)
|
|
for (let i = 1; i < 5; i++) {
|
|
const next = Vec.Lrp(pts[0], last, i / 4)
|
|
next.z = ((pts[0].z + (last.z - pts[0].z)) * i) / 4
|
|
pts.push(next)
|
|
}
|
|
}
|
|
|
|
// The strokePoints array will hold the points for the stroke.
|
|
// Start it out with the first point, which needs no adjustment.
|
|
const strokePoints: StrokePoint[] = [
|
|
{
|
|
point: pts[0],
|
|
input: pts[0],
|
|
pressure: simulatePressure ? 0.5 : pts[0].z,
|
|
vector: new Vec(1, 1),
|
|
distance: 0,
|
|
runningLength: 0,
|
|
radius: 1,
|
|
},
|
|
]
|
|
|
|
// We use the totalLength to keep track of the total distance
|
|
let totalLength = 0
|
|
|
|
// We're set this to the latest point, so we can use it to calculate
|
|
// the distance and vector of the next point.
|
|
let prev = strokePoints[0]
|
|
|
|
// Iterate through all of the points, creating StrokePoints.
|
|
let point: Vec, distance: number
|
|
|
|
if (isComplete && streamline > 0) {
|
|
pts.push(pts[pts.length - 1].clone())
|
|
}
|
|
|
|
for (let i = 1, n = pts.length; i < n; i++) {
|
|
point =
|
|
!t || (options.last && i === n - 1) ? pts[i].clone() : pts[i].clone().lrp(prev.point, 1 - t)
|
|
|
|
// If the new point is the same as the previous point, skip ahead.
|
|
if (prev.point.equals(point)) continue
|
|
|
|
// How far is the new point from the previous point?
|
|
distance = Vec.Dist(point, prev.point)
|
|
|
|
// Add this distance to the total "running length" of the line.
|
|
totalLength += distance
|
|
|
|
// At the start of the line, we wait until the new point is a
|
|
// certain distance away from the original point, to avoid noise
|
|
|
|
if (i < 4 && totalLength < size) {
|
|
continue
|
|
}
|
|
|
|
// Create a new strokepoint (it will be the new "previous" one).
|
|
prev = {
|
|
input: pts[i],
|
|
// The adjusted point
|
|
point,
|
|
// The input pressure (or .5 if not specified)
|
|
pressure: simulatePressure ? 0.5 : pts[i].z,
|
|
// The vector from the current point to the previous point
|
|
vector: Vec.Sub(prev.point, point).uni(),
|
|
// The distance between the current point and the previous point
|
|
distance,
|
|
// The total distance so far
|
|
runningLength: totalLength,
|
|
// The stroke point's radius
|
|
radius: 1,
|
|
}
|
|
|
|
// Push it to the strokePoints array.
|
|
strokePoints.push(prev)
|
|
}
|
|
|
|
// Set the vector of the first point to be the same as the second point.
|
|
if (strokePoints[1]?.vector) {
|
|
strokePoints[0].vector = strokePoints[1].vector.clone()
|
|
}
|
|
|
|
if (totalLength < 1) {
|
|
const maxPressureAmongPoints = Math.max(0.5, ...strokePoints.map((s) => s.pressure))
|
|
strokePoints.forEach((s) => (s.pressure = maxPressureAmongPoints))
|
|
}
|
|
|
|
return strokePoints
|
|
}
|