Tweaking path smoothing and simplification.

This commit is contained in:
Yaro Kasear 2026-01-14 13:26:13 -06:00
parent 059d5ee9ba
commit 2561127221

View file

@ -574,10 +574,59 @@ function initGridWidget(root, opts = {}) {
});
}
function totalTurning(points) {
let sum = 0;
for (let i = 1; i < points.length - 1; i++) {
const p0 = points[i - 1];
const p1 = points[i];
const p2 = points[i + 1];
const v1x = p1.x - p0.x;
const v1y = p1.y - p0.y;
const v2x = p2.x - p1.x;
const v2y = p2.y - p1.y;
const len1 = Math.hypot(v1x, v1y);
const len2 = Math.hypot(v2x, v2y);
if (len1 === 0 || len2 === 0) continue;
const cross = Math.abs(v1x * v2y - v1y * v2x);
sum += cross / (len1 * len2);
}
return sum;
}
function pathLength(pts) {
let L = 0;
for (let i = 1; i < pts.length; i++) {
const dx = pts[i].x - pts[i-1].x;
const dy = pts[i].y - pts[i-1].y;
L += Math.hypot(dx, dy);
}
return L;
}
function rebuildPathCaches(list) {
const MIN_PTS_FOR_SMOOTH = 4;
const MIN_LEN = 2;
const MIN_TURN = 0.15;
return list.map(s => {
if (s.type !== 'path') return s;
if (!Array.isArray(s.points) || s.points.length < 2) return s;
const pts = s.points;
if (!Array.isArray(s.points) || pts.length < 2) return s;
if (!pts.every(p => p && Number.isFinite(p.x) && Number.isFinite(p.y))) return s;
if (pathLength(pts) < MIN_LEN) return s;
if (pts.length < MIN_PTS_FOR_SMOOTH) return s;
if (MIN_TURN != null && totalTurning(pts) < MIN_TURN) return s;
const renderPoints = catmullRomResample(s.points, {
alpha: 0.5,
@ -1076,11 +1125,32 @@ function initGridWidget(root, opts = {}) {
ctx.restore();
}
function penAddPoint(shape, clientX, clientY, minStep = 0.02) {
function penAddPoint(shape, clientX, clientY, minStep = 0.02, maxDtMs = 16) {
const p = pxToDocPoint(clientX, clientY);
if (!Array.isArray(shape.points)) shape.points = [];
if (shape._lastAddTime == null) shape._lastAddTime = performance.now();
const pts = shape.points;
const last = pts[pts.length - 1];
if (!last || dist2(p, last) >= minStep * minStep) pts.push(p);
const now = performance.now();
const dt = now - shape._lastAddTime;
if (!last) {
pts.push(p);
shape._lastAddTime = now;
return;
}
const dx = p.x - last.x;
const dy = p.y - last.y;
const d2 = dx * dx + dy * dy;
if (d2 >= minStep * minStep || dt >= maxDtMs) {
pts.push(p);
shape._lastAddTime = now;
}
}
function pxToDocPoint(clientX, clientY) {
@ -1600,6 +1670,7 @@ function initGridWidget(root, opts = {}) {
}
if (finalShape) {
if (finalShape && ('_lastAddTime' in finalShape)) delete finalShape._lastAddTime;
commit([...shapes, finalShape]);
@ -1827,7 +1898,14 @@ function initGridWidget(root, opts = {}) {
// PEN: mutate points and preview the same shape object
if (currentShape.tool === 'pen') {
penAddPoint(currentShape, e.clientX, e.clientY, 0.02);
const minStepPx = 0.75;
const minStep = minStepPx / cellSize;
penAddPoint(currentShape, e.clientX, e.clientY, minStep, 16);
// realtime instrumentation
coordsEl.innerText += ` | pts=${currentShape.points?.length ?? 0}`;
console.log("move", e.pointerId, performance.now());
renderAllWithPreview(currentShape, false);
return;
}
@ -1925,7 +2003,8 @@ function initGridWidget(root, opts = {}) {
points: [p],
color: selectedColor,
strokeWidth: currentStrokeWidth,
strokeOpacity: currentStrokeOpacity
strokeOpacity: currentStrokeOpacity,
_lastAddTime: performance.now()
};
}
});