diff --git a/curveEditor.js b/curveEditor.js index 98f6659..c443cff 100644 --- a/curveEditor.js +++ b/curveEditor.js @@ -1,5 +1,5 @@ export class CurveEditor { - constructor(canvas, timelineLength = 10) { + constructor(canvas, timelineLength = 10, _slider) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.timelineLength = timelineLength; @@ -21,7 +21,7 @@ export class CurveEditor { // COMMENT OUT AND READ FROM EXISTING TIMELINE LATER - const slider = document.getElementById('timeSlider'); + const slider = _slider;//document.getElementById('timeSlider'); slider.max = this.timelineLength * this.pixelsPerSecond; console.log(slider.max); @@ -80,7 +80,7 @@ export class CurveEditor { console.log(this.curveSets); } - addChannel(motorID){ + addChannel(motorID) { this.setCurves([ { startPoint: { x: this.valueToX(0), y: this.valueToY(0) }, @@ -259,22 +259,52 @@ export class CurveEditor { return t; } - getPositionAtTime(timeInSeconds, curve, valueToX, transform) { - // Convert time to pixel X position - const targetX = valueToX(timeInSeconds); - - // Transform control points - const p0 = transform(curve.startPoint); - const h0 = transform(curve.startPointHandle); - const h1 = transform(curve.endPointHandle); - const p1 = transform(curve.endPoint); - const t = solveTForX(targetX, p0, h0, h1, p1); - return cubicBezier(t, p0, h0, h1, p1); + // getPositionAtTime(timeInSeconds, curve, valueToX, transform) { + // // Convert time to pixel X position + // const targetX = valueToX(timeInSeconds); + + // // Transform control points + // const p0 = transform(curve.startPoint); + // const h0 = transform(curve.startPointHandle); + // const h1 = transform(curve.endPointHandle); + // const p1 = transform(curve.endPoint); + + + // const t = solveTForX(targetX, p0, h0, h1, p1); + // return cubicBezier(t, p0, h0, h1, p1); + // } + + getMotorPositionAtTime(motorID, timeInFrames) { + if (this.curveSets[motorID] === undefined || this.curveSets[motorID].length === 0) { + return null; + } + const curves = this.curveSets[motorID]; + const timeInSeconds = timeInFrames / this.framesPerSecond; + const currentTimeX = timeInFrames; + + for (let curve of curves) { + + const p0 = this.transform(curve.startPoint); + const h0 = this.transform(curve.startPointHandle); + const h1 = this.transform(curve.endPointHandle); + const p1 = this.transform(curve.endPoint); + + const minX = Math.min(p0.x, p1.x); + const maxX = Math.max(p0.x, p1.x); + if (currentTimeX >= minX && currentTimeX <= maxX) { + const t = this.solveTForX(currentTimeX, p0, h0, h1, p1); + const pt = this.cubicBezier(t, p0, h0, h1, p1); + return this.yToExportRange(pt.y); + } + } + + return null; // No curve segment matched the time } + drawCurves(ctx, curves, opacity = 1.0, showControlPoints = true) { ctx.save(); ctx.globalAlpha = opacity; diff --git a/feetechDefinitions.js b/feetechDefinitions.js index f30fd68..c98e5e9 100644 --- a/feetechDefinitions.js +++ b/feetechDefinitions.js @@ -34,6 +34,7 @@ export class ServoMotor { // Simplified constructor this.CHANNEL = arg1; this.ID = arg2; + this.MODEL = arg3 || 'Unknown Model'; } } diff --git a/index.html b/index.html index b495ee1..dfe34f7 100644 --- a/index.html +++ b/index.html @@ -156,28 +156,13 @@
-
+ -
-
-
512 -
-
-
512 -
-
-
512 -
-
-
512 -
-
-
512 -
-
+
+ @@ -185,7 +170,7 @@ Feedback - +

diff --git a/script.js b/script.js index 56eb20d..8a7abdf 100644 --- a/script.js +++ b/script.js @@ -37,13 +37,12 @@ window.onload = () => { const dials = []; const frameSlider = document.getElementById('frameSlider'); const frameDisplay = document.getElementById('frameDisplay'); - const canvas = document.getElementById('timelineCanvas'); - const ctx = canvas.getContext('2d'); - const totalFrames = 500; + + const curveCanvas = document.getElementById('curveCanvas'); - const curveEditor = new CurveEditor(curveCanvas, 10); + const curveEditor = new CurveEditor(curveCanvas, 10, frameSlider); // Animation File List @@ -74,8 +73,10 @@ window.onload = () => { for (let i = 0; i < 5; i++) { let motor = new ServoMotor(0, 10 + i, "SCS009") robot.assignMotor(positions[i], motor); - } + addDial(motor.ID); + } + // addDial(123); // Retrieve motor by position //console.log(robot.getMotor('eyelids')); @@ -176,78 +177,80 @@ window.onload = () => { frameSlider.oninput = () => { currentFrame = parseInt(frameSlider.value); frameDisplay.textContent = currentFrame; - isInterpolating = true; - - for (let ch = 0; ch < 5; ch++) { - const keyframes = dialKeyframes[ch]; - let prevFrame = null, nextFrame = null; - - for (let f = currentFrame; f >= 0; f--) { - if (keyframes[f] !== undefined) { - prevFrame = f; - break; - } - } - for (let f = currentFrame; f <= totalFrames; f++) { - if (keyframes[f] !== undefined) { - nextFrame = f; - break; - } - } - - let value; - if (prevFrame !== null && nextFrame !== null && prevFrame !== nextFrame) { - const prevVal = keyframes[prevFrame]; - const nextVal = keyframes[nextFrame]; - const t = (currentFrame - prevFrame) / (nextFrame - prevFrame); - value = Math.round(prevVal + (nextVal - prevVal) * t); - } else if (prevFrame !== null) { - value = keyframes[prevFrame]; - } else if (nextFrame !== null) { - value = keyframes[nextFrame]; - } else { - value = 512; - } - - dials[ch].value = value; - document.getElementById(`value${ch}`).textContent = value; - } - - drawTimelineMarkers(); - isInterpolating = false; - + //console.log(currentFrame); + syncDialsWithCurveEditor(); syncMotorsWithTimeline(); }; - for (let i = 0; i < 5; i++) { - dials[i] = new Nexus.Dial(`#dial${i}`, { + function addDial(motorID) { + const index = dials.length; + + // Create dial wrapper + const dialWrapper = document.createElement('div'); + dialWrapper.className = 'dial'; + dialWrapper.dataset.index = index; + + // Create label + const label = document.createElement('label'); + label.textContent = "Motor " + motorID; + console.log(motorID); + + // Create dial container + const dialDiv = document.createElement('div'); + dialDiv.id = `dial${index}`; + + // Create value display + const valueSpan = document.createElement('span'); + valueSpan.id = `value${index}`; + valueSpan.textContent = '2048'; + + // Assemble and append + dialWrapper.appendChild(label); + dialWrapper.appendChild(dialDiv); + dialWrapper.appendChild(valueSpan); + document.getElementById('dialArea').appendChild(dialWrapper); + + // Create Nexus dial + const dial = new Nexus.Dial(`#dial${index}`, { size: [80, 80], min: 0, - max: 1023, - value: 512 + max: 4095, + value: 4095 / 2 }); - dials[i].colorize("accent", dialColors[i]); + dial.motorID = motorID; + dial.colorize("accent", dialColors[index]); - dials[i].on('change', (v) => { + dial.on('change', (v) => { if (isInterpolating) return; const val = Math.round(v); - document.getElementById(`value${i}`).textContent = val; - dialKeyframes[i][currentFrame] = val; - drawTimelineMarkers(); - + document.getElementById(`value${index}`).textContent = val; + //dialKeyframes[index][currentFrame] = val; syncMotorsWithTimeline(); - }); - + dials.push(dial); } + function syncDialsWithCurveEditor() { + //let pos = curveEditor.getMotorPositionAtTime(11, currentFrame); + for (let ch = 0; ch < dials.length; ch++) { + console.log(dials[ch].motorID); + dials[ch].value = curveEditor.getMotorPositionAtTime(dials[ch].motorID, currentFrame); + //const value = dials[ch].value; + //motorPayloads.push({ motorId: ch, position: value }); + } + //console.log(pos); + } + + + function syncMotorsWithTimeline() { + const now = Date.now(); if (syncCheckbox.checked && now - lastSyncTime >= syncIntervalMs) { lastSyncTime = now; @@ -281,7 +284,6 @@ window.onload = () => { document.querySelectorAll('.dial').forEach(d => d.classList.remove('selected')); el.classList.add('selected'); - drawTimelineMarkers(); }; }); @@ -517,7 +519,7 @@ window.onload = () => { document.getElementById("filenameInput").value = selectedFile.replace(/\.anim$/i, ""); - drawTimelineMarkers(); + } @@ -660,7 +662,6 @@ window.onload = () => { clearBtn.addEventListener('click', () => { currentFrame = 0; dialKeyframes = Array.from({ length: 5 }, () => ({})); - drawTimelineMarkers(); }); @@ -671,73 +672,6 @@ window.onload = () => { document.getElementById('input').value = ''; }; - function drawTimelineMarkers() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - const width = canvas.width; - const height = canvas.height; - - // Draw tick marks every 50 frames - ctx.strokeStyle = '#aaa'; - ctx.lineWidth = 1; - for (let f = 0; f <= totalFrames; f += 25) { - const x = (f / totalFrames) * width; - ctx.beginPath(); - ctx.moveTo(x, height * 0.75); // small tick at bottom - ctx.lineTo(x, height); - ctx.stroke(); - } - for (let f = 0; f <= totalFrames; f += 50) { - const x = (f / totalFrames) * width; - ctx.beginPath(); - ctx.moveTo(x, height * 0.65); // small tick at bottom - ctx.lineTo(x, height); - ctx.stroke(); - } - - // Draw label on tick marks - ctx.fillStyle = '#666'; - ctx.font = '10px sans-serif'; - ctx.textAlign = 'center'; - - for (let f = 0; f <= totalFrames; f += 50) { - const x = (f / totalFrames) * width; - const seconds = f / 50; - ctx.fillText(seconds.toString() + "s", x, height - 12); - } - - - if (selectedDial !== null) { - for (let frame in dialKeyframes[selectedDial]) { - const x = (frame / totalFrames) * width; - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, height); - ctx.strokeStyle = dialColors[selectedDial]; - ctx.lineWidth = 2; - ctx.stroke(); - } - } else { - for (let ch = 0; ch < 5; ch++) { - for (let frame in dialKeyframes[ch]) { - const x = (frame / totalFrames) * width; - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, height); - ctx.strokeStyle = dialColors[ch]; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - } - - const currentX = (currentFrame / totalFrames) * width; - ctx.beginPath(); - ctx.moveTo(currentX, 0); - ctx.lineTo(currentX, height); - ctx.strokeStyle = 'black'; - ctx.lineWidth = 2; - ctx.stroke(); - } document.addEventListener('click', (e) => { const ignoredTags = ['BUTTON', 'INPUT', 'TEXTAREA', 'CANVAS']; @@ -747,66 +681,11 @@ window.onload = () => { if (!clickedInsideDial && !clickedControl && selectedDial !== null) { selectedDial = null; document.querySelectorAll('.dial').forEach(el => el.classList.remove('selected')); - drawTimelineMarkers(); + } }); - canvas.addEventListener('mousedown', (e) => { - const x = e.offsetX; - const dialIndices = selectedDial !== null ? [selectedDial] : [0, 1, 2, 3, 4]; - - for (let ch of dialIndices) { - for (let f in dialKeyframes[ch]) { - const frameNum = parseInt(f); - const fx = (frameNum / totalFrames) * canvas.width; - - if (Math.abs(fx - x) < 15) { - draggingKeyframe = { dialIndex: ch, originalFrame: frameNum }; - isDragging = true; - return; - } - } - } - }); - - - canvas.addEventListener('mousemove', (e) => { - if (!isDragging || !draggingKeyframe) return; - - const x = e.offsetX; - const newFrame = Math.max(0, Math.min(totalFrames - 1, Math.round((x / canvas.width) * totalFrames))); - - const { dialIndex, originalFrame } = draggingKeyframe; - const value = dialKeyframes[dialIndex][originalFrame]; - - if (newFrame !== originalFrame) { - delete dialKeyframes[dialIndex][originalFrame]; - dialKeyframes[dialIndex][newFrame] = value; - draggingKeyframe.originalFrame = newFrame; - drawTimelineMarkers(); - } - }); - - - canvas.addEventListener('mouseup', () => { - isDragging = false; - draggingKeyframe = null; - syncMotorsWithTimeline(); - }); - - canvas.addEventListener('dblclick', (e) => { - const x = e.offsetX; - const frame = Math.round((x / canvas.width) * totalFrames); - const dialIndices = selectedDial !== null ? [selectedDial] : [0, 1, 2, 3, 4]; - - for (let ch of dialIndices) { - if (dialKeyframes[ch][frame] !== undefined) { - delete dialKeyframes[ch][frame]; - drawTimelineMarkers(); - } - } - }); @@ -815,8 +694,6 @@ window.onload = () => { }; - drawTimelineMarkers(); -