Line simplification!!!
This commit is contained in:
parent
a1cf260072
commit
429e993009
3 changed files with 141 additions and 45 deletions
|
|
@ -13,7 +13,6 @@
|
|||
.grid-widget .grid-wrap {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 375px;
|
||||
|
|
@ -47,28 +46,28 @@
|
|||
}
|
||||
|
||||
/* WIDE: 1 row */
|
||||
@container (min-width: 725px){
|
||||
.grid-widget [data-toolbar].toolbar{
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
@container (min-width: 750px) {
|
||||
.grid-widget [data-toolbar].toolbar {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.grid-widget [data-toolbar] .toolbar-row{
|
||||
display: contents;
|
||||
}
|
||||
.grid-widget [data-toolbar] .toolbar-row {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.grid-widget [data-toolbar] .toolbar-row--primary,
|
||||
.grid-widget [data-toolbar] .toolbar-row--secondary{
|
||||
overflow: visible;
|
||||
}
|
||||
.grid-widget [data-toolbar] .toolbar-row--primary,
|
||||
.grid-widget [data-toolbar] .toolbar-row--secondary {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-widget [data-grid] {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
cursor: crosshair;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -76,6 +75,13 @@
|
|||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.grid-widget [data-canvas],
|
||||
.grid-widget [data-coords],
|
||||
.grid-widget [data-dot] {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.grid-widget [data-toolbar]::-webkit-scrollbar {
|
||||
|
|
@ -97,6 +103,11 @@
|
|||
z-index: 1;
|
||||
pointer-events: none;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid-widget [data-dot] {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,11 @@ function initGridWidget(root, opts = {}) {
|
|||
activeGridWidget = api;
|
||||
}, { capture: true });
|
||||
|
||||
function isInsideRect(clientX, clientY, rect) {
|
||||
return clientX >= rect.left && clientX <= rect.right &&
|
||||
clientY >= rect.top && clientY <= rect.bottom;
|
||||
}
|
||||
|
||||
function bindRangeWithLabel(inputEl, labelEl, format = (v) => v) {
|
||||
const sync = () => { labelEl.textContent = format(inputEl.value); };
|
||||
inputEl.addEventListener('input', sync);
|
||||
|
|
@ -187,7 +192,62 @@ function initGridWidget(root, opts = {}) {
|
|||
|
||||
function dist2(a, b) {
|
||||
const dx = a.x - b.x, dy = a.y - b.y;
|
||||
return dx * dx + dy * dy
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
function pointToSegmentDist2(p, a, b) {
|
||||
const vx = b.x - a.x, vy = b.y - a.y;
|
||||
const wx = p.x - a.x, wy = p.y - a.y;
|
||||
|
||||
const c1 = vx * wx + vy * wy;
|
||||
if (c1 <= 0) return dist2(p, a);
|
||||
|
||||
const c2 = vx * vx + vy * vy;
|
||||
if (c2 <= c1) return dist2(p, b);
|
||||
|
||||
const t = c1 / c2;
|
||||
const proj = { x: a.x + t * vx, y: a.y + t * vy };
|
||||
return dist2(p, proj);
|
||||
}
|
||||
|
||||
function simplifyRDP(points, epsilon) {
|
||||
if (!Array.isArray(points) || points.length < 3) return points || [];
|
||||
const eps2 = epsilon * epsilon;
|
||||
|
||||
function rdp(first, last, out) {
|
||||
let maxD2 = 0;
|
||||
let idx = -1;
|
||||
|
||||
const a = points[first];
|
||||
const b = points[last];
|
||||
|
||||
for (let i = first + 1; i < last; ++i) {
|
||||
const d2 = pointToSegmentDist2(points[i], a, b);
|
||||
if (d2 > maxD2) {
|
||||
maxD2 = d2;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxD2 > eps2 && idx !== -1) {
|
||||
rdp(first, idx, out);
|
||||
out.pop();
|
||||
rdp(idx, last, out);
|
||||
} else {
|
||||
out.push(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
const out = [];
|
||||
rdp(0, points.length - 1, out);
|
||||
|
||||
const deduped = [out[0]];
|
||||
for (let i = 1; i < out.length; i++) {
|
||||
const prev = deduped[deduped.length - 1];
|
||||
const cur = out[i];
|
||||
if (prev.x !== cur.x || prev.y !== cur.y) deduped.push(cur);
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
function undo() {
|
||||
|
|
@ -315,18 +375,29 @@ function initGridWidget(root, opts = {}) {
|
|||
const snappedW = snapDown(w, grid);
|
||||
const snappedH = snapDown(h, grid);
|
||||
|
||||
if (snappedW === lastApplied.w && snappedH === lastApplied.h) return;
|
||||
// Only touch width-related CSS if width changed
|
||||
const wChanged = snappedW !== lastApplied.w;
|
||||
const hChanged = snappedH !== lastApplied.h;
|
||||
if (!wChanged && !hChanged) return;
|
||||
|
||||
lastApplied = { w: snappedW, h: snappedH };
|
||||
|
||||
// critical: don't let observer see our own updates as layout input
|
||||
ro.disconnect();
|
||||
|
||||
gridEl.style.width = `${snappedW}px`;
|
||||
gridEl.style.height = `${snappedH}px`;
|
||||
|
||||
toolBarEl.style.setProperty('--grid-maxw', `${snappedW}px`);
|
||||
if (wChanged) {
|
||||
root.style.setProperty('--grid-maxw', `${snappedW}px`);
|
||||
}
|
||||
|
||||
ro.observe(gridWrapEl);
|
||||
|
||||
gridEl.getBoundingClientRect();
|
||||
resizeAndSetupCanvas();
|
||||
}
|
||||
|
||||
|
||||
function scheduleSnappedCellSize() {
|
||||
if (sizingRAF) return;
|
||||
sizingRAF = requestAnimationFrame(applySnappedCellSize);
|
||||
|
|
@ -536,6 +607,8 @@ function initGridWidget(root, opts = {}) {
|
|||
const y2 = toPx(shape.y2);
|
||||
|
||||
ctx.globalAlpha = clamp01(shape.strokeOpacity, SHAPE_DEFAULTS.strokeOpacity);
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
|
|
@ -633,15 +706,22 @@ function initGridWidget(root, opts = {}) {
|
|||
const pts = currentShape.points;
|
||||
|
||||
if (pts.length >= 2) {
|
||||
const simplified = [pts[0]];
|
||||
const minStep = 0.03;
|
||||
const coarse = [pts[0]];
|
||||
const minStep = 0.02;
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (dist2(pts[i], simplified[simplified.length - 1]) >= minStep * minStep) {
|
||||
simplified.push(pts[i]);
|
||||
if (dist2(pts[i], coarse[coarse.length - 1]) >= minStep * minStep) {
|
||||
coarse.push(pts[i]);
|
||||
}
|
||||
}
|
||||
if (simplified.length >= 2) {
|
||||
finalShape = { ...currentShape, points: simplified };
|
||||
|
||||
if (coarse.length >= 2) {
|
||||
const epsilon = Math.max(0.01, (currentShape.strokeWidth ?? 0.12) * 0.75);
|
||||
|
||||
const simplified = simplifyRDP(coarse, epsilon);
|
||||
|
||||
if (simplified.length >= 2) {
|
||||
finalShape = { ...currentShape, points: simplified };
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (currentShape.tool === 'line') {
|
||||
|
|
@ -797,23 +877,31 @@ function initGridWidget(root, opts = {}) {
|
|||
gridEl.addEventListener('pointermove', (e) => {
|
||||
if (!ctx) return;
|
||||
|
||||
const rect = gridEl.getBoundingClientRect();
|
||||
const inside = isInsideRect(e.clientX, e.clientY, rect);
|
||||
const drawing = !!currentShape;
|
||||
|
||||
const { ix, iy, x: snapX, y: snapY, localX, localY } = snapToGrid(e.clientX, e.clientY);
|
||||
const tool = getActiveTool();
|
||||
|
||||
coordsEl.classList.remove('d-none');
|
||||
|
||||
if (getActiveType() !== 'noGrid' && tool !== 'pen') {
|
||||
dotEl.classList.remove('d-none');
|
||||
|
||||
const gridRect = gridEl.getBoundingClientRect();
|
||||
const wrapRect = gridWrapEl.getBoundingClientRect();
|
||||
const offsetX = gridRect.left - wrapRect.left;
|
||||
const offsetY = gridRect.top - wrapRect.top;
|
||||
|
||||
dotEl.style.left = `${offsetX + snapX}px`;
|
||||
dotEl.style.top = `${offsetY + snapY}px`;
|
||||
} else {
|
||||
if (!drawing && !inside) {
|
||||
coordsEl.classList.add('d-none');
|
||||
dotEl.classList.add('d-none');
|
||||
} else {
|
||||
coordsEl.classList.remove('d-none');
|
||||
|
||||
if (getActiveType() !== 'noGrid' && tool !== 'pen') {
|
||||
dotEl.classList.remove('d-none');
|
||||
|
||||
const wrapRect = gridWrapEl.getBoundingClientRect();
|
||||
const offsetX = rect.left - wrapRect.left;
|
||||
const offsetY = rect.top - wrapRect.top;
|
||||
|
||||
dotEl.style.left = `${offsetX + snapX}px`;
|
||||
dotEl.style.top = `${offsetY + snapY}px`;
|
||||
} else {
|
||||
dotEl.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
if (getActiveType() == 'noGrid') {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,9 @@
|
|||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="col" style="min-height: 80vh">
|
||||
{{ draw.drawWidget('test1') }}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{ draw.drawWidget('test2') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue