tweaked curve point dragging thresholds

master
Jake Wilkinson 2025-11-24 14:24:11 +08:00
parent 21ffc99bbb
commit a6d901d3f0
2 changed files with 111 additions and 114 deletions

View File

@ -663,26 +663,26 @@ export class CurveEditor {
} }
for (let curve of this.curves) { for (let curve of this.curves) {
// Pass 1: check handles first // Pass 1: check handles first
for (let key of ['startPointHandle', 'endPointHandle']) { for (let key of ['startPointHandle', 'endPointHandle']) {
const p = curve[key]; const p = curve[key];
if (Math.hypot(p.x - mouse.x, p.y - mouse.y) < 10 / this.scale) { if (Math.hypot(p.x - mouse.x, p.y - mouse.y) < 10 / this.scale) {
this.dragging = { curve, key }; this.dragging = { curve, key };
return; return;
} }
} }
} }
// If no handle was hit, then check points // If no handle was hit, then check points
for (let curve of this.curves) { for (let curve of this.curves) {
for (let key of ['startPoint', 'endPoint']) { for (let key of ['startPoint', 'endPoint']) {
const p = curve[key]; const p = curve[key];
if (Math.hypot(p.x - mouse.x, p.y - mouse.y) < 10 / this.scale) { if (Math.hypot(p.x - mouse.x, p.y - mouse.y) < 10 / this.scale) {
this.dragging = { curve, key }; this.dragging = { curve, key };
return; return;
} }
} }
} }
}); });
@ -771,106 +771,103 @@ for (let curve of this.curves) {
} }
splitCurveAtTime(motorID, timeX, yPosition = null) { splitCurveAtTime(motorID, timeX, yPosition = null) {
const curves = this.curveSets[motorID]; const curves = this.curveSets[motorID];
if (!curves) return; if (!curves) return;
const tolerance = 2; // pixels/frames tolerance for matching existing points const tolerance = 4; // pixels/frames tolerance for matching existing points
for (let i = 0; i < curves.length; i++) { for (let i = 0; i < curves.length; i++) {
const curve = curves[i]; const curve = curves[i];
// --- check startPoint --- // --- check startPoint ---
if (Math.abs(curve.startPoint.x - timeX) < tolerance) { if (Math.abs(curve.startPoint.x - timeX) < tolerance) {
if (yPosition !== null) { if (yPosition !== null) {
// move existing point // move existing point
const yEditor = (yPosition * 800 / 4095); const yEditor = (yPosition * 800 / 4095);
curve.startPoint.y = yEditor; curve.startPoint.y = yEditor;
if (curve.startPointHandle) curve.startPointHandle.y = yEditor; if (curve.startPointHandle) curve.startPointHandle.y = yEditor;
} else { } else {
// remove point by merging with previous // 🚫 don't allow deleting the very first control point
if (i > 0) { if (i > 0) {
const prev = curves[i - 1]; const prev = curves[i - 1];
const merged = { const merged = {
startPoint: prev.startPoint, startPoint: prev.startPoint,
startPointHandle: prev.startPointHandle, startPointHandle: prev.startPointHandle,
endPointHandle: curve.endPointHandle, endPointHandle: curve.endPointHandle,
endPoint: curve.endPoint endPoint: curve.endPoint
}; };
curves.splice(i - 1, 2, merged); curves.splice(i - 1, 2, merged);
} else {
curves.splice(i, 1); // drop if it's the very first
}
}
this.curveSets[motorID] = curves;
this.draw();
return;
}
// --- check endPoint ---
if (Math.abs(curve.endPoint.x - timeX) < tolerance) {
if (yPosition !== null) {
// move existing point
const yEditor = (yPosition * 800 / 4095);
curve.endPoint.y = yEditor;
if (curve.endPointHandle) curve.endPointHandle.y = yEditor;
} else {
// remove point by merging with next
if (i < curves.length - 1) {
const next = curves[i + 1];
const merged = {
startPoint: curve.startPoint,
startPointHandle: curve.startPointHandle,
endPointHandle: next.endPointHandle,
endPoint: next.endPoint
};
curves.splice(i, 2, merged);
} else {
curves.splice(i, 1); // drop if it's the very last
}
}
this.curveSets[motorID] = curves;
this.draw();
return;
}
// --- split inside curve if timeX lies between start and end ---
const x0 = curve.startPoint.x;
const x3 = curve.endPoint.x;
if (timeX > x0 && timeX < x3) {
// binary search for t where curve.x ≈ timeX
let t = 0.5, minT = 0, maxT = 1;
for (let j = 0; j < 10; j++) {
const pt = this.cubicBezier(
t,
curve.startPoint,
curve.startPointHandle,
curve.endPointHandle,
curve.endPoint
);
if (pt.x < timeX) minT = t;
else maxT = t;
t = (minT + maxT) / 2;
}
let [left, right] = this.splitCurve(curve, t);
if (yPosition !== null) {
const yEditor = (yPosition * 800 / 4095);
left.endPoint.y = yEditor;
right.startPoint.y = yEditor;
if (left.endPointHandle) left.endPointHandle.y = yEditor;
if (right.startPointHandle) right.startPointHandle.y = yEditor;
}
curves.splice(i, 1, left, right);
this.curveSets[motorID] = curves;
this.draw();
return;
}
} }
}
this.curveSets[motorID] = curves;
this.draw();
return;
} }
// --- check endPoint ---
if (Math.abs(curve.endPoint.x - timeX) < tolerance) {
if (yPosition !== null) {
// move existing point
const yEditor = (yPosition * 800 / 4095);
curve.endPoint.y = yEditor;
if (curve.endPointHandle) curve.endPointHandle.y = yEditor;
} else {
// 🚫 don't allow deleting the very last control point
if (i < curves.length - 1) {
const next = curves[i + 1];
const merged = {
startPoint: curve.startPoint,
startPointHandle: curve.startPointHandle,
endPointHandle: next.endPointHandle,
endPoint: next.endPoint
};
curves.splice(i, 2, merged);
}
}
this.curveSets[motorID] = curves;
this.draw();
return;
}
// --- split inside curve if timeX lies between start and end ---
const x0 = curve.startPoint.x;
const x3 = curve.endPoint.x;
if (timeX > x0 && timeX < x3) {
// binary search for t where curve.x ≈ timeX
let t = 0.5, minT = 0, maxT = 1;
for (let j = 0; j < 10; j++) {
const pt = this.cubicBezier(
t,
curve.startPoint,
curve.startPointHandle,
curve.endPointHandle,
curve.endPoint
);
if (pt.x < timeX) minT = t;
else maxT = t;
t = (minT + maxT) / 2;
}
let [left, right] = this.splitCurve(curve, t);
if (yPosition !== null) {
const yEditor = (yPosition * 800 / 4095);
left.endPoint.y = yEditor;
right.startPoint.y = yEditor;
if (left.endPointHandle) left.endPointHandle.y = yEditor;
if (right.startPointHandle) right.startPointHandle.y = yEditor;
}
curves.splice(i, 1, left, right);
this.curveSets[motorID] = curves;
this.draw();
return;
}
}
}
adjustAllCurvesToDuration(newTime) { adjustAllCurvesToDuration(newTime) {
for (const [motorID, curves] of Object.entries(this.curveSets)) { for (const [motorID, curves] of Object.entries(this.curveSets)) {

View File

@ -43,7 +43,7 @@ export class SerialManager {
} }
const message = [HEADER1, HEADER2, commandCode, lengthHigh, lengthLow, ...payload, checksum]; const message = [HEADER1, HEADER2, commandCode, lengthHigh, lengthLow, ...payload, checksum];
//console.log(new Uint8Array(message)); console.log(new Uint8Array(message));
await this.writer.write(new Uint8Array(message)); await this.writer.write(new Uint8Array(message));
} }