Added joystick controls

This commit is contained in:
2025-11-10 14:01:47 -05:00
parent b1b67fb0bd
commit 7d02789a39

136
script.js
View File

@ -4,6 +4,76 @@
import GrahamScan from "./vendor/lucio/graham-scan.mjs"; import GrahamScan from "./vendor/lucio/graham-scan.mjs";
class Joystick {
constructor(ctx, x, y, radius) {
this.ctx = ctx;
this.x = x;
this.y = y;
this.radius = radius;
this.nubX = x;
this.nubY = y;
this.active = false;
this.visible = false;
}
draw() {
if (!this.visible)
return;
this.ctx.save();
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
this.ctx.fillStyle = '#5554';
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(this.nubX, this.nubY, this.radius / 3, 0, Math.PI * 2);
this.ctx.fillStyle = '#445bcaff';
this.ctx.fill();
this.ctx.restore();
}
checkShouldActivate(x, y) {
if (!this.touchInRange(x, y))
return;
this.active = true;
this.processTouch(x, y);
}
deactivate() {
this.active = false;
}
touchInRange(x, y) {
const deltaX = x - this.x;
const deltaY = y - this.y;
const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return dist <= this.radius;
}
processTouch(x, y) {
if (this.touchInRange(x, y) && this.active) {
this.nubX = x;
this.nubY = y;
}
}
getX() {
return (this.nubX - this.x) / this.radius;
}
getY() {
return (this.y - this.nubY) / this.radius;
}
setIsVisible(visible) {
this.visible = visible;
}
}
// 2D vector class to make some of the math easier // 2D vector class to make some of the math easier
class Vec2D { class Vec2D {
constructor(x, y) { constructor(x, y) {
@ -269,6 +339,15 @@ const PresetConfigs = {
* BEGIN DOM VARIABLES * BEGIN DOM VARIABLES
*/ */
// Get canvas
const canvas = document.getElementById('swerve-canvas');
// Get the canvas context as constant
const ctx = canvas.getContext('2d');
// Get CSS variables for use in canvas
const rootStyles = getComputedStyle(document.documentElement);
// Get all control elements // Get all control elements
const vxSlider = document.getElementById('vx-slider'); const vxSlider = document.getElementById('vx-slider');
const vySlider = document.getElementById('vy-slider'); const vySlider = document.getElementById('vy-slider');
@ -379,6 +458,8 @@ controlModeToggle.addEventListener('click', () => {
vxOutput.textContent = '0'; vxOutput.textContent = '0';
vyOutput.textContent = '0'; vyOutput.textContent = '0';
omegaOutput.textContent = '0'; omegaOutput.textContent = '0';
leftJoystick.setIsVisible(true);
rightJoystick.setIsVisible(true);
} else { } else {
// Switch to slider mode // Switch to slider mode
sliderControls.style.display = 'block'; sliderControls.style.display = 'block';
@ -390,6 +471,8 @@ controlModeToggle.addEventListener('click', () => {
manualInputVelX = 0; manualInputVelX = 0;
manualInputVelY = 0; manualInputVelY = 0;
manualInputOmega = 0; manualInputOmega = 0;
leftJoystick.setIsVisible(false);
rightJoystick.setIsVisible(false);
} }
}); });
@ -552,6 +635,30 @@ applyCustomBtn.addEventListener('click', () => {
updateModuleDisplays(robot); updateModuleDisplays(robot);
}); });
function convertTouchToCanvas(inCoords) {
const rect = canvas.getBoundingClientRect();
const x = inCoords.x - rect.left - canvas.width / 2;
const y = inCoords.y - rect.top - canvas.height / 2;
return { x, y };
}
canvas.addEventListener('mousedown', (event) => {
const canvasCoords = convertTouchToCanvas({ x: event.clientX, y: event.clientY });
leftJoystick.checkShouldActivate(canvasCoords.x, canvasCoords.y);
rightJoystick.checkShouldActivate(canvasCoords.x, canvasCoords.y);
});
canvas.addEventListener('mousemove', (event) => {
const canvasCoords = convertTouchToCanvas({ x: event.clientX, y: event.clientY });
leftJoystick.processTouch(canvasCoords.x, canvasCoords.y);
rightJoystick.processTouch(canvasCoords.x, canvasCoords.y);
});
canvas.addEventListener('mouseup', (event) => {
leftJoystick.deactivate();
rightJoystick.deactivate();
});
/* /*
* END LISTENER CODE * END LISTENER CODE
* BEGIN DYNAMIC DOM FUNCTIONS * BEGIN DYNAMIC DOM FUNCTIONS
@ -696,12 +803,8 @@ function updateModuleDisplays(robot) {
* BEGIN ANIMATION CODE * BEGIN ANIMATION CODE
*/ */
// Get the canvas and context as constants const leftJoystick = new Joystick(ctx, -250, 250, 100);
const canvas = document.getElementById('swerve-canvas'); const rightJoystick = new Joystick(ctx, 250, 250, 100);
const ctx = canvas.getContext('2d');
// Get CSS variables for use in canvas
const rootStyles = getComputedStyle(document.documentElement);
function drawGrid(ctx, sideLength, gridSquareSize, xOffset, yOffset) { function drawGrid(ctx, sideLength, gridSquareSize, xOffset, yOffset) {
ctx.save(); ctx.save();
@ -806,8 +909,12 @@ function drawRobot(ctx, robot, heading) {
ctx.restore(); // Restore to remove rotation ctx.restore(); // Restore to remove rotation
} }
// Initialize Variables // Initialize Variables
// Joysticks
const supportsTouch = (navigator.maxTouchPoints > 0);
// General robot
const robotSize = 200; const robotSize = 200;
const defaultModulePositions = PresetConfigs.fourWheelSquare(robotSize); const defaultModulePositions = PresetConfigs.fourWheelSquare(robotSize);
const robot = new SwerveDrive(defaultModulePositions, "4-Wheel Square"); const robot = new SwerveDrive(defaultModulePositions, "4-Wheel Square");
@ -827,11 +934,20 @@ function animate() {
ctx.save(); ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2); ctx.translate(canvas.width / 2, canvas.height / 2);
leftJoystick.draw();
rightJoystick.draw();
// Update speeds based on control mode // Update speeds based on control mode
if (isManualInputMode) { if (isManualInputMode) {
xSpeed = manualInputVelX; const maxSpeed = parseFloat(keyboardMaxSpeed.value);
ySpeed = -manualInputVelY; // Negative because canvas Y axis is inverted const maxRotation = parseFloat(keyboardMaxRotation.value);
turnSpeed = manualInputOmega; // xSpeed = manualInputVelX;
// ySpeed = -manualInputVelY; // Negative because canvas Y axis is inverted
// turnSpeed = manualInputOmega;
xSpeed = leftJoystick.getX() * maxSpeed;
ySpeed = -leftJoystick.getY() * maxSpeed;
turnSpeed = rightJoystick.getX() * maxRotation;
} else { } else {
xSpeed = parseFloat(vxSlider.value); xSpeed = parseFloat(vxSlider.value);
ySpeed = -parseFloat(vySlider.value); ySpeed = -parseFloat(vySlider.value);