animation length can now be controlled with slider

node_mode
Jake 2025-11-02 17:48:57 +08:00
parent 7a619886c6
commit e50e5cd7f6
3 changed files with 129 additions and 52 deletions

View File

@ -6,6 +6,7 @@ export class CurveEditor {
this.ctx = canvas.getContext('2d');
this.timelineLength = timelineLength;
this.scale = 1;
this.scaleX = 1;
this.scaleY = 1;
@ -16,6 +17,14 @@ export class CurveEditor {
this.currentTime = timelineLength * this.pixelsPerSecond / 2;
this.durationHandle = {
x: timelineLength, // or maxTimeCS
y: 30, // top of canvas
radius: 10,
type: "duration"
};
this.motorButtons = {
width: 30,
height: 30,
@ -25,17 +34,17 @@ export class CurveEditor {
// COMMENT OUT AND READ FROM EXISTING TIMELINE LATER
const slider = _slider;//document.getElementById('timeSlider');
slider.max = this.timelineLength * this.pixelsPerSecond;
this.slider = _slider;//document.getElementById('timeSlider');
this.slider.max = this.timelineLength * this.pixelsPerSecond;
console.log(slider.max);
slider.value = 0;
slider.addEventListener('input', () => {
this.currentTime = parseFloat(slider.value);
console.log(this.slider.max);
this.slider.value = 0;
this.slider.addEventListener('input', () => {
this.currentTime = parseFloat(this.slider.value);
//console.log(slider.value);
this.draw();
});
this.setLength(timelineLength * this.pixelsPerSecond);
this.curveSets = {}; // motorID → array of curves
@ -45,39 +54,6 @@ export class CurveEditor {
this.isPanning = false;
this.panStart = { x: 0, y: 0 };
// Default curve
// this.setCurves([
// {
// startPoint: { x: this.valueToX(0), y: this.valueToY(0) },
// startPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(0.5) },
// endPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(-0.5) },
// endPoint: { x: this.valueToX(timelineLength), y: this.valueToY(0) }
// }
// ]);
// this.curveSets[this.selectedMotorID] = this.curves;
// let othercurves = [
// {
// startPoint: { x: this.valueToX(0), y: this.valueToY(.3) },
// startPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(0.5) },
// endPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(-0.5) },
// endPoint: { x: this.valueToX(timelineLength), y: this.valueToY(0) }
// }
// ]
// this.curveSets[5] = othercurves;
// othercurves = [
// {
// startPoint: { x: this.valueToX(0), y: this.valueToY(-.5) },
// startPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(0.5) },
// endPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(-0.5) },
// endPoint: { x: this.valueToX(timelineLength), y: this.valueToY(0) }
// }
// ]
// this.curveSets[7] = othercurves;
//this.setSelectedMotor(4);
this.initEvents();
this.draw();
@ -150,8 +126,15 @@ export class CurveEditor {
setLength(endTime) {
this.timelineLength = endTime / this.pixelsPerSecond;
console.log("new endtime: " + endTime);
this.slider.max = this.timelineLength * this.pixelsPerSecond;
console.log(this.slider.max, this.slider.value);
if (this.slider.value > this.slider.max) {
this.slider.value = this.slider.max;
}
this.durationHandle.x = this.timelineLength;
//console.log("new endtime: " + endTime);
//this.currentTime = this.timelineLength * this.pixelsPerSecond / 2;
this.draw();
}
loadCurveSets(curveSets) {
@ -171,6 +154,8 @@ export class CurveEditor {
// Optional: update motor selector UI or redraw timeline
//this.refreshMotorSelector?.(); // if you have a method for that
//this.drawTimelineMarkers?.();
}
@ -245,17 +230,48 @@ export class CurveEditor {
this.drawGrid(ctx);
// Draw inactive curves dimmed
// Draw inactive curves dimmed only if there are curve sets
if (this.curveSets && Object.keys(this.curveSets).length > 0) {
for (const [motorID, curves] of Object.entries(this.curveSets)) {
if (!curves || curves.length === 0) continue; // skip empty sets
const isSelected = motorID == this.selectedMotorID;
this.drawCurves(this.ctx, curves, isSelected ? 1.0 : 0.3, isSelected);
}
}
this.drawCurves(ctx, this.curves);
this.drawMotorButtons();
this.drawDurationHandle(ctx);
}
drawDurationHandle() {
const ctx = this.ctx;
const x = this.valueToX(this.durationHandle.x); // e.g. derived from header.frameCount or maxTime
const y = 20; // top of canvas
ctx.save();
// vertical guide line
ctx.strokeStyle = "red";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, this.canvas.height);
ctx.stroke();
// draggable knob at the top
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, this.durationHandle.y, this.durationHandle.radius, 0, Math.PI * 2); // circle 10px down from top
ctx.fill();
ctx.restore();
}
drawMotorButtons() {
const ctx = this.ctx;
const canvas = this.canvas;
@ -353,7 +369,9 @@ export class CurveEditor {
drawCurves(ctx, curves, opacity = 1.0, showControlPoints = true) {
ctx.save();
ctx.globalAlpha = opacity;
if (!curves) {
return;
}
for (let curve of curves) {
const p0 = this.transform(curve.startPoint);
const h0 = this.transform(curve.startPointHandle);
@ -548,6 +566,15 @@ export class CurveEditor {
const mx = e.offsetX;
const my = e.offsetY;
// Check duration handle
const handleX = this.valueToX(this.durationHandle.x);
const handleY = this.durationHandle.y; // same as draw
if (Math.hypot(e.offsetX - handleX, e.offsetY - handleY) < this.durationHandle.radius) {
this.dragging = { type: "durationHandle" };
return;
}
for (let btn of buttons) {
if (
mx >= btn.x &&
@ -566,10 +593,6 @@ export class CurveEditor {
}
}
const mouse = this.inverseTransform({ x: e.offsetX, y: e.offsetY });
if (e.button === 1) {
@ -580,7 +603,7 @@ export class CurveEditor {
}
for (let curve of this.curves) {
for (let key of ['startPoint', 'startPointHandle', 'endPointHandle', 'endPoint']) {
for (let key of ['startPointHandle', 'endPointHandle', 'startPoint', 'endPoint']) {
const p = curve[key];
if (Math.hypot(p.x - mouse.x, p.y - mouse.y) < 10 / this.scale) {
this.dragging = { curve, key };
@ -588,6 +611,7 @@ export class CurveEditor {
}
}
}
});
@ -608,6 +632,19 @@ export class CurveEditor {
if (key === 'startPoint' || key === 'endPoint') {
this.dragEndpoint(curve, key, mouse.x, mouse.y, index);
} else if (this.dragging.type === "durationHandle") {
// Update duration in timeline units
const newTime = Math.max(0, mouse.x); // timeline units
// Optionally update timeline length
this.setLength(newTime);
this.adjustAllCurvesToDuration(newTime); // truncate/extend curves
//console.log(this.durationHandle.x);
this.draw();
return;
} else {
this.dragControlPoint(curve, key, mouse.x, mouse.y, index);
}
@ -707,6 +744,45 @@ export class CurveEditor {
});
}
adjustAllCurvesToDuration(newTime) {
for (const [motorID, curves] of Object.entries(this.curveSets)) {
if (!curves || curves.length === 0) continue;
for (let i = 0; i < curves.length; i++) {
const curve = curves[i];
// If a curve starts beyond the new duration, drop it
if (curve.startPoint.x >= newTime) {
curves.splice(i);
break;
}
// If a curve ends beyond the new duration, truncate it
if (curve.endPoint.x > newTime) {
curve.endPoint.x = newTime;
curve.endPointHandle.x = newTime;
curves.splice(i + 1);
break;
}
}
// If extending, push the last curves end point out
const last = curves[curves.length - 1];
if (last.endPoint.x < newTime) {
const delta = newTime - last.endPoint.x;
last.endPoint.x = newTime;
last.endPointHandle.x += delta;
}
this.curveSets[motorID] = curves;
}
// Update the active reference too
this.curves = this.curveSets[this.selectedMotorID] || [];
}
dragControlPoint(curve, key, mouseX, mouseY, index) {
curve[key].y = mouseY;

View File

@ -182,7 +182,7 @@
<div>
<label>Frame: <span id="frameDisplay">0</span></label><br>
<input type="range" id="frameSlider" min="0" max="399" value="0" style="width: 80%">
<input type="range" id="frameSlider" min="0" max="100" value="0" style="width: 80%">
</div>
<label for="filenameInput">Filename:</label>

View File

@ -663,7 +663,8 @@ window.onload = () => {
const filename = sanitizeFilename(rawInput);
console.log("Sanitized filename:", filename);
const frameCount = 480; // or whatever your timeline length is
const frameCount = curveEditor.timelineLength * curveEditor.pixelsPerSecond; // or whatever your timeline length is
console.log(frameCount);
const frameRate = 48;
const version = 1;