diff --git a/inventory/static/js/components/draw.js b/inventory/static/js/components/draw.js index 8b8969f..a2a9a57 100644 --- a/inventory/static/js/components/draw.js +++ b/inventory/static/js/components/draw.js @@ -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() }; } });