Spaces:
Running
Running
| import EpicycleSystem from './epicycle_system.js'; | |
| export default class AnimationController { | |
| constructor(canvasId, epicycleData) { | |
| this.canvas = document.getElementById(canvasId); | |
| this.context = this.canvas.getContext('2d'); | |
| this.width = this.canvas.width; | |
| this.height = this.canvas.height; | |
| // Calculate total extent of all circles | |
| let totalRadius = 0; | |
| for (let [amplitude] of epicycleData) { | |
| totalRadius += Math.abs(amplitude); | |
| } | |
| console.log('Total radius extent:', totalRadius); | |
| // Scale to fit within 60% of canvas size | |
| const targetSize = Math.min(this.width, this.height) * 1; | |
| this.scale = totalRadius > 0 ? targetSize / totalRadius : 1; | |
| console.log('Using scale:', this.scale); | |
| this.epicycleSystem = new EpicycleSystem(epicycleData); | |
| this.isRunning = true; | |
| // NEW: Normalized time system | |
| this.totalSteps = 500; // Total number of discrete steps in one period | |
| this.currentStep = 0; // Current step [0, totalSteps) | |
| this.stepsPerFrame = 1; // How many steps to advance per frame (speed control) | |
| this.xOffset = this.width /2; | |
| this.yOffset = this.height /2 ; | |
| } | |
| update() { | |
| if (this.isRunning) { | |
| // Discrete stepping approach (like the reference code) | |
| this.currentStep += this.stepsPerFrame; | |
| // Wrap around when we complete one period | |
| if (this.currentStep >= this.totalSteps) { | |
| this.currentStep = this.currentStep % this.totalSteps; | |
| // Optionally clear path for clean restart | |
| // this.epicycleSystem.clearPath(); | |
| } | |
| // Calculate normalized time t ∈ [0, 1] | |
| const t = this.currentStep / this.totalSteps; | |
| // Update system with normalized time | |
| this.epicycleSystem.updateWithNormalizedTime(t); | |
| /* Alternative: Continuous time approach | |
| this.time += this.speed; | |
| if (this.time >= 1.0) { | |
| this.time = this.time % 1.0; | |
| } | |
| this.epicycleSystem.updateWithNormalizedTime(this.time); | |
| */ | |
| } | |
| } | |
| render() { | |
| this.context.fillStyle = '#000'; | |
| this.context.fillRect(0, 0, this.width, this.height); | |
| this.epicycleSystem.render(this.context, this.xOffset, this.yOffset, this.scale); | |
| // Optional: Display current time for debugging | |
| this.context.fillStyle = '#fff'; | |
| this.context.font = '14px monospace'; | |
| const t = (this.currentStep / this.totalSteps).toFixed(3); | |
| this.context.fillText(`t = ${t}`, 10, 20); | |
| } | |
| loop() { | |
| this.update(); | |
| this.render(); | |
| requestAnimationFrame(() => this.loop()); | |
| } | |
| pause() { | |
| this.isRunning = !this.isRunning; | |
| } | |
| reset() { | |
| this.epicycleSystem.reset(); | |
| this.currentStep = 0; | |
| // this.time = 0; // If using continuous time | |
| } | |
| clearPath() { | |
| this.epicycleSystem.clearPath(); | |
| } | |
| // NEW: Jump to specific time | |
| seekTo(t) { | |
| // t should be in [0, 1] | |
| this.currentStep = Math.floor(t * this.totalSteps); | |
| this.epicycleSystem.clearPath(); | |
| this.epicycleSystem.updateWithNormalizedTime(t); | |
| } | |
| // NEW: Control speed | |
| setSpeed(stepsPerFrame) { | |
| this.stepsPerFrame = stepsPerFrame; | |
| } | |
| start() { | |
| this.loop(); | |
| } | |
| } |