diff --git a/src/canvas.js b/src/canvas.js index 99294e5c6c49bac839c86b897330a50db12f911d..7ff97f7615b0d0054a02c3f9fdb5a23534acaad4 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -20,6 +20,58 @@ let stroke_colour = "blue" export const MIN_STROKE_RADIUS = 0.1 export const MAX_STROKE_RADIUS = 3.9 +const MAX_POINT_DISTANCE = 5 +const MAX_PRESSURE_DELTA = 0.05 + +// Interpolate a path so that: +// - The distance between two adjacent points is capped at MAX_POINT_DISTANCE. +// - The pressure delta between two adjacent points is capped at +// MAX_PRESSURE_DELTA +// If paths are too choppy, try decreasing these constants. +const smoothPath = ([...path]) => { + // Apply MAX_POINT_DISTANCE. + for (let i = 1; i < path.length; i++) { + const dx = path[i][0] - path[i - 1][0] + const dy = path[i][1] - path[i - 1][1] + const dw = path[i][2] - path[i - 1][2] + const distance = Math.hypot(dx, dy) + const segmentsToSplit = Math.ceil(distance / MAX_POINT_DISTANCE) + const newPoints = [] + for (let j = 1; j < segmentsToSplit; j++) { + newPoints.push([ + path[i - 1][0] + (dx / segmentsToSplit) * j, + path[i - 1][1] + (dy / segmentsToSplit) * j, + path[i - 1][2] + (dw / segmentsToSplit) * j, + path[i - 1][3], + path[i - 1][4], + ]) + } + path.splice(i, 0, ...newPoints) + i += newPoints.length + } + + // Apply MAX_PRESSURE_DELTA. + for (let i = 1; i < path.length; i++) { + const dx = path[i][0] - path[i - 1][0] + const dy = path[i][1] - path[i - 1][1] + const dw = path[i][2] - path[i - 1][2] + const segmentsToSplit = Math.ceil(dw / MAX_PRESSURE_DELTA) + const newPoints = [] + for (let j = 1; j < segmentsToSplit; j++) { + newPoints.push([ + path[i - 1][0] + (dx / segmentsToSplit) * j, + path[i - 1][1] + (dy / segmentsToSplit) * j, + path[i - 1][2] + (dw / segmentsToSplit) * j, + path[i - 1][3], + path[i - 1][4], + ]) + } + path.splice(i, 0, ...newPoints) + i += newPoints.length + } + return path +} + const getStrokeRadius = (pressure) => { return MIN_STROKE_RADIUS + pressure * (MAX_STROKE_RADIUS - MIN_STROKE_RADIUS) } @@ -82,7 +134,7 @@ export const renderPath = (id, points) => { pathGroupElem.appendChild(circleElem) } else { // Further split up segments based on thickness. - const subpath_ = subpath.slice() + const subpath_ = smoothPath(subpath) let w = subpath_[0][2] for (let i = 1; i < subpath_.length; i++) { if (subpath_[i][2] != w) {