Idle animations. Closes #14
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import IK from './kinematics.js';
|
||||
import Motion from './motion.js';
|
||||
|
||||
let activeMode = 'none'; // 'none', 'circles', 'erratic', 'park-bounce'
|
||||
let parkPosition = { x: 0, y: 80 };
|
||||
let anchorPos = { x: 0, y: 80 };
|
||||
let timerId = null;
|
||||
let currentAngle = 0;
|
||||
let bounceState = 'to-park'; // 'to-park' or 'to-anchor'
|
||||
|
||||
const IDLE_WAIT_MS = 5000;
|
||||
const CONTINUOUS_WAIT_MS = 500; // 0.5s pause between idle movements
|
||||
const RADIUS = 20;
|
||||
|
||||
export const IdleEffects = {
|
||||
setMode(mode) {
|
||||
activeMode = mode || 'none';
|
||||
localStorage.setItem('wiji_idle_mode', activeMode);
|
||||
|
||||
// Update all dropdowns in UI to keep them in sync
|
||||
document.querySelectorAll('.idle-mode-select').forEach(el => {
|
||||
if (el.value !== activeMode) el.value = activeMode;
|
||||
});
|
||||
|
||||
if (activeMode === 'none') {
|
||||
this.stopTimer();
|
||||
} else {
|
||||
if (!Motion.isMoving && localStorage.getItem('wiji_homed') === 'true') {
|
||||
this.updateAnchorPos();
|
||||
this.startTimer(IDLE_WAIT_MS);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getMode() {
|
||||
return activeMode;
|
||||
},
|
||||
|
||||
setParkPosition(x, y) {
|
||||
parkPosition = { x, y };
|
||||
},
|
||||
|
||||
updateAnchorPos() {
|
||||
const { steps1, steps2 } = Motion.getSteps();
|
||||
const pos = IK.forward(IK.stepsToRad(steps1), IK.stepsToRad(steps2));
|
||||
if (pos.valid) {
|
||||
anchorPos = { x: pos.endX, y: pos.endY };
|
||||
currentAngle = 0;
|
||||
bounceState = 'to-park';
|
||||
}
|
||||
},
|
||||
|
||||
startTimer(ms) {
|
||||
this.stopTimer();
|
||||
if (activeMode === 'none') return;
|
||||
|
||||
timerId = setTimeout(() => {
|
||||
this.triggerNextMove();
|
||||
}, ms);
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
},
|
||||
|
||||
async triggerNextMove() {
|
||||
if (activeMode === 'none' || localStorage.getItem('wiji_homed') !== 'true') return;
|
||||
|
||||
let targetX = anchorPos.x;
|
||||
let targetY = anchorPos.y;
|
||||
let motionMode = 'direct';
|
||||
|
||||
if (activeMode === 'circles') {
|
||||
currentAngle += Math.PI / 4; // 45 degrees
|
||||
targetX = anchorPos.x + RADIUS * Math.cos(currentAngle);
|
||||
targetY = anchorPos.y + RADIUS * Math.sin(currentAngle);
|
||||
motionMode = 'direct';
|
||||
} else if (activeMode === 'erratic') {
|
||||
const randAngle = Math.random() * Math.PI * 2;
|
||||
const randR = Math.random() * RADIUS;
|
||||
targetX = anchorPos.x + randR * Math.cos(randAngle);
|
||||
targetY = anchorPos.y + randR * Math.sin(randAngle);
|
||||
motionMode = 'erratic';
|
||||
} else if (activeMode === 'park-bounce') {
|
||||
if (bounceState === 'to-park') {
|
||||
targetX = parkPosition.x;
|
||||
targetY = parkPosition.y;
|
||||
bounceState = 'to-anchor';
|
||||
} else {
|
||||
targetX = anchorPos.x;
|
||||
targetY = anchorPos.y;
|
||||
bounceState = 'to-park';
|
||||
}
|
||||
motionMode = 'direct';
|
||||
}
|
||||
|
||||
// Workspace check
|
||||
const check = IK.checkWorkspace(targetX, targetY);
|
||||
if (!check.ok) {
|
||||
// If out of bounds, skip this move and wait for next
|
||||
this.startTimer(CONTINUOUS_WAIT_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire the idle move!
|
||||
try {
|
||||
await Motion.goto(targetX, targetY, motionMode, true);
|
||||
} catch(e) {
|
||||
// If aborted, it's fine.
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
const saved = localStorage.getItem('wiji_idle_mode');
|
||||
if (saved) activeMode = saved;
|
||||
|
||||
document.addEventListener('wiji:motion-start', (e) => {
|
||||
const isIdleMove = e.detail && e.detail.isIdleMove;
|
||||
if (!isIdleMove) {
|
||||
this.stopTimer();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('wiji:motion-end', (e) => {
|
||||
if (activeMode === 'none' || localStorage.getItem('wiji_homed') !== 'true') return;
|
||||
|
||||
const isIdleMove = e.detail && e.detail.isIdleMove;
|
||||
if (!isIdleMove) {
|
||||
this.updateAnchorPos();
|
||||
this.startTimer(IDLE_WAIT_MS);
|
||||
} else {
|
||||
this.startTimer(CONTINUOUS_WAIT_MS);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('wiji:unhomed', () => {
|
||||
this.setMode('none');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-init globally
|
||||
IdleEffects.init();
|
||||
Reference in New Issue
Block a user