Tweaking path smoothing and simplification.
This commit is contained in:
parent
059d5ee9ba
commit
2561127221
1 changed files with 84 additions and 5 deletions
|
|
@ -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) {
|
function rebuildPathCaches(list) {
|
||||||
|
const MIN_PTS_FOR_SMOOTH = 4;
|
||||||
|
const MIN_LEN = 2;
|
||||||
|
const MIN_TURN = 0.15;
|
||||||
|
|
||||||
return list.map(s => {
|
return list.map(s => {
|
||||||
if (s.type !== 'path') return 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, {
|
const renderPoints = catmullRomResample(s.points, {
|
||||||
alpha: 0.5,
|
alpha: 0.5,
|
||||||
|
|
@ -1076,11 +1125,32 @@ function initGridWidget(root, opts = {}) {
|
||||||
ctx.restore();
|
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);
|
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 pts = shape.points;
|
||||||
const last = pts[pts.length - 1];
|
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) {
|
function pxToDocPoint(clientX, clientY) {
|
||||||
|
|
@ -1600,6 +1670,7 @@ function initGridWidget(root, opts = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalShape) {
|
if (finalShape) {
|
||||||
|
if (finalShape && ('_lastAddTime' in finalShape)) delete finalShape._lastAddTime;
|
||||||
commit([...shapes, finalShape]);
|
commit([...shapes, finalShape]);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1827,7 +1898,14 @@ function initGridWidget(root, opts = {}) {
|
||||||
|
|
||||||
// PEN: mutate points and preview the same shape object
|
// PEN: mutate points and preview the same shape object
|
||||||
if (currentShape.tool === 'pen') {
|
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);
|
renderAllWithPreview(currentShape, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1925,7 +2003,8 @@ function initGridWidget(root, opts = {}) {
|
||||||
points: [p],
|
points: [p],
|
||||||
color: selectedColor,
|
color: selectedColor,
|
||||||
strokeWidth: currentStrokeWidth,
|
strokeWidth: currentStrokeWidth,
|
||||||
strokeOpacity: currentStrokeOpacity
|
strokeOpacity: currentStrokeOpacity,
|
||||||
|
_lastAddTime: performance.now()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue