147 lines
3.8 KiB
JavaScript
147 lines
3.8 KiB
JavaScript
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();
|