tweaked curve point dragging thresholds
parent
21ffc99bbb
commit
a6d901d3f0
223
curveEditor.js
223
curveEditor.js
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue