animation length can now be controlled with slider
parent
7a619886c6
commit
e50e5cd7f6
170
curveEditor.js
170
curveEditor.js
|
|
@ -6,6 +6,7 @@ export class CurveEditor {
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
this.timelineLength = timelineLength;
|
this.timelineLength = timelineLength;
|
||||||
|
|
||||||
|
|
||||||
this.scale = 1;
|
this.scale = 1;
|
||||||
this.scaleX = 1;
|
this.scaleX = 1;
|
||||||
this.scaleY = 1;
|
this.scaleY = 1;
|
||||||
|
|
@ -16,6 +17,14 @@ export class CurveEditor {
|
||||||
|
|
||||||
this.currentTime = timelineLength * this.pixelsPerSecond / 2;
|
this.currentTime = timelineLength * this.pixelsPerSecond / 2;
|
||||||
|
|
||||||
|
this.durationHandle = {
|
||||||
|
x: timelineLength, // or maxTimeCS
|
||||||
|
y: 30, // top of canvas
|
||||||
|
radius: 10,
|
||||||
|
type: "duration"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this.motorButtons = {
|
this.motorButtons = {
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
|
@ -25,17 +34,17 @@ export class CurveEditor {
|
||||||
|
|
||||||
|
|
||||||
// COMMENT OUT AND READ FROM EXISTING TIMELINE LATER
|
// COMMENT OUT AND READ FROM EXISTING TIMELINE LATER
|
||||||
const slider = _slider;//document.getElementById('timeSlider');
|
this.slider = _slider;//document.getElementById('timeSlider');
|
||||||
slider.max = this.timelineLength * this.pixelsPerSecond;
|
this.slider.max = this.timelineLength * this.pixelsPerSecond;
|
||||||
|
|
||||||
console.log(slider.max);
|
console.log(this.slider.max);
|
||||||
slider.value = 0;
|
this.slider.value = 0;
|
||||||
slider.addEventListener('input', () => {
|
this.slider.addEventListener('input', () => {
|
||||||
this.currentTime = parseFloat(slider.value);
|
this.currentTime = parseFloat(this.slider.value);
|
||||||
//console.log(slider.value);
|
//console.log(slider.value);
|
||||||
this.draw();
|
this.draw();
|
||||||
});
|
});
|
||||||
|
this.setLength(timelineLength * this.pixelsPerSecond);
|
||||||
|
|
||||||
|
|
||||||
this.curveSets = {}; // motorID → array of curves
|
this.curveSets = {}; // motorID → array of curves
|
||||||
|
|
@ -45,39 +54,6 @@ export class CurveEditor {
|
||||||
this.isPanning = false;
|
this.isPanning = false;
|
||||||
this.panStart = { x: 0, y: 0 };
|
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.initEvents();
|
||||||
this.draw();
|
this.draw();
|
||||||
|
|
@ -150,8 +126,15 @@ export class CurveEditor {
|
||||||
|
|
||||||
setLength(endTime) {
|
setLength(endTime) {
|
||||||
this.timelineLength = endTime / this.pixelsPerSecond;
|
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.currentTime = this.timelineLength * this.pixelsPerSecond / 2;
|
||||||
|
this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCurveSets(curveSets) {
|
loadCurveSets(curveSets) {
|
||||||
|
|
@ -171,6 +154,8 @@ export class CurveEditor {
|
||||||
// Optional: update motor selector UI or redraw timeline
|
// Optional: update motor selector UI or redraw timeline
|
||||||
//this.refreshMotorSelector?.(); // if you have a method for that
|
//this.refreshMotorSelector?.(); // if you have a method for that
|
||||||
//this.drawTimelineMarkers?.();
|
//this.drawTimelineMarkers?.();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -245,17 +230,48 @@ export class CurveEditor {
|
||||||
this.drawGrid(ctx);
|
this.drawGrid(ctx);
|
||||||
|
|
||||||
// Draw inactive curves dimmed
|
// 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)) {
|
for (const [motorID, curves] of Object.entries(this.curveSets)) {
|
||||||
|
if (!curves || curves.length === 0) continue; // skip empty sets
|
||||||
const isSelected = motorID == this.selectedMotorID;
|
const isSelected = motorID == this.selectedMotorID;
|
||||||
this.drawCurves(this.ctx, curves, isSelected ? 1.0 : 0.3, isSelected);
|
this.drawCurves(this.ctx, curves, isSelected ? 1.0 : 0.3, isSelected);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.drawCurves(ctx, this.curves);
|
this.drawCurves(ctx, this.curves);
|
||||||
|
|
||||||
this.drawMotorButtons();
|
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() {
|
drawMotorButtons() {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const canvas = this.canvas;
|
const canvas = this.canvas;
|
||||||
|
|
@ -353,7 +369,9 @@ export class CurveEditor {
|
||||||
drawCurves(ctx, curves, opacity = 1.0, showControlPoints = true) {
|
drawCurves(ctx, curves, opacity = 1.0, showControlPoints = true) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = opacity;
|
ctx.globalAlpha = opacity;
|
||||||
|
if (!curves) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (let curve of curves) {
|
for (let curve of curves) {
|
||||||
const p0 = this.transform(curve.startPoint);
|
const p0 = this.transform(curve.startPoint);
|
||||||
const h0 = this.transform(curve.startPointHandle);
|
const h0 = this.transform(curve.startPointHandle);
|
||||||
|
|
@ -548,6 +566,15 @@ export class CurveEditor {
|
||||||
const mx = e.offsetX;
|
const mx = e.offsetX;
|
||||||
const my = e.offsetY;
|
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) {
|
for (let btn of buttons) {
|
||||||
if (
|
if (
|
||||||
mx >= btn.x &&
|
mx >= btn.x &&
|
||||||
|
|
@ -566,10 +593,6 @@ export class CurveEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const mouse = this.inverseTransform({ x: e.offsetX, y: e.offsetY });
|
const mouse = this.inverseTransform({ x: e.offsetX, y: e.offsetY });
|
||||||
|
|
||||||
if (e.button === 1) {
|
if (e.button === 1) {
|
||||||
|
|
@ -580,7 +603,7 @@ export class CurveEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let curve of this.curves) {
|
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];
|
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 };
|
||||||
|
|
@ -588,6 +611,7 @@ export class CurveEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -608,6 +632,19 @@ export class CurveEditor {
|
||||||
|
|
||||||
if (key === 'startPoint' || key === 'endPoint') {
|
if (key === 'startPoint' || key === 'endPoint') {
|
||||||
this.dragEndpoint(curve, key, mouse.x, mouse.y, index);
|
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 {
|
} else {
|
||||||
this.dragControlPoint(curve, key, mouse.x, mouse.y, index);
|
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 curve’s 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) {
|
dragControlPoint(curve, key, mouseX, mouseY, index) {
|
||||||
curve[key].y = mouseY;
|
curve[key].y = mouseY;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label>Frame: <span id="frameDisplay">0</span></label><br>
|
<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>
|
</div>
|
||||||
|
|
||||||
<label for="filenameInput">Filename:</label>
|
<label for="filenameInput">Filename:</label>
|
||||||
|
|
|
||||||
|
|
@ -663,7 +663,8 @@ window.onload = () => {
|
||||||
const filename = sanitizeFilename(rawInput);
|
const filename = sanitizeFilename(rawInput);
|
||||||
console.log("Sanitized filename:", filename);
|
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 frameRate = 48;
|
||||||
const version = 1;
|
const version = 1;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue