Compare commits
3 Commits
trueOdomet
...
evenlySpac
| Author | SHA1 | Date | |
|---|---|---|---|
|
07204a476e
|
|||
|
5662142274
|
|||
|
83049815db
|
210
index.html
210
index.html
@ -14,94 +14,6 @@
|
|||||||
<p>Interactive simulation of a swerve drive robot with configurable size, speeds, and number of wheels</p>
|
<p>Interactive simulation of a swerve drive robot with configurable size, speeds, and number of wheels</p>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section class="visualization-canvas">
|
|
||||||
<h2>Robot Visualization</h2>
|
|
||||||
<canvas id="swerve-canvas" width="800" height="800"></canvas>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="control-panel">
|
|
||||||
<h2>Drive Controls</h2>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Translation & Rotation</legend>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="vy-slider">Move Forward/Backward (pixels/s)</label>
|
|
||||||
<input type="range" id="vy-slider" min="-300" max="300" step="10" value="0">
|
|
||||||
<output id="vy-value">0</output>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="vx-slider">Strafe Left/Right (pixels/s)</label>
|
|
||||||
<input type="range" id="vx-slider" min="-300" max="300" step="10" value="0">
|
|
||||||
<output id="vx-value">0</output>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="omega-slider">Rotation (rad/s)</label>
|
|
||||||
<input type="range" id="omega-slider" min="-3" max="3" step="0.1" value="0">
|
|
||||||
<output id="omega-value">0</output>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="reset-btn" type="button">Reset Controls</button>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Performance Limits</legend>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="max-speed-slider">Max Module Speed (pixels/s)</label>
|
|
||||||
<input type="range" id="max-speed-slider" min="200" max="1000" step="10" value="400">
|
|
||||||
<output id="max-speed-value">0</output>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
|
||||||
<section class="config-panel">
|
|
||||||
<h2>Robot Configuration</h2>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Quick Presets</legend>
|
|
||||||
<div class="preset-buttons">
|
|
||||||
<button id="preset-2wheel" type="button">2-Wheel</button>
|
|
||||||
<button id="preset-3wheel" type="button">3-Wheel Triangle</button>
|
|
||||||
<button id="preset-4wheel" type="button">4-Wheel Square</button>
|
|
||||||
<button id="preset-4rect" type="button">4-Wheel Rectangle</button>
|
|
||||||
<button id="preset-6wheel" type="button">6-Wheel Hexagon</button>
|
|
||||||
<button id="preset-8wheel" type="button">8-Wheel Octagon</button>
|
|
||||||
<button id="preset-8square" type="button">8-Wheel Square</button>
|
|
||||||
<button id="preset-12hex" type="button">12-Wheel Hexagon</button>
|
|
||||||
<button id="preset-16oct" type="button">16-Wheel Octogon</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Custom Configuration</legend>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label for="module-count">Number of Modules</label>
|
|
||||||
<input type="number" id="module-count" min="2" max="12" value="4" step="1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="generate-inputs-btn" type="button">Generate Position Inputs</button>
|
|
||||||
<button id="delete-inputs-btn" type="button">Remove Position Inputs</button>
|
|
||||||
|
|
||||||
<div id="module-position-inputs" class="position-inputs">
|
|
||||||
<!-- Dynamically generated position inputs will appear here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="apply-custom-btn" type="button" style="display: none;">Apply Custom Configuration</button>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
|
||||||
<section class="module-states">
|
|
||||||
<h2>Module States</h2>
|
|
||||||
<div id="current-config-info" class="config-info">
|
|
||||||
Current Configuration: <strong id="config-name">4-Wheel Rectangle</strong>
|
|
||||||
(<span id="module-count-display">4</span> modules)
|
|
||||||
<br>
|
|
||||||
Gyro Heading: <strong id="gyro-heading-display">0.0°</strong>
|
|
||||||
</div>
|
|
||||||
<div class="module-grid" id="module-grid">
|
|
||||||
<!-- Dynamically generated module data will appear here -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="documentation">
|
<section class="documentation">
|
||||||
<h2>About This Project</h2>
|
<h2>About This Project</h2>
|
||||||
<details>
|
<details>
|
||||||
@ -275,6 +187,128 @@ if scale < 1:
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="visualization-canvas">
|
||||||
|
<h2>Robot Visualization</h2>
|
||||||
|
<canvas id="swerve-canvas" width="800" height="800"></canvas>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="control-panel">
|
||||||
|
<h2>Drive Controls</h2>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Translation & Rotation</legend>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<button id="control-mode-toggle" type="button">Switch to Keyboard Controls (WASD + QE)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="slider-controls">
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="vy-slider">Move Forward/Backward (pixels/s)</label>
|
||||||
|
<input type="range" id="vy-slider" min="-300" max="300" step="10" value="0">
|
||||||
|
<output id="vy-value">0</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="vx-slider">Strafe Left/Right (pixels/s)</label>
|
||||||
|
<input type="range" id="vx-slider" min="-300" max="300" step="10" value="0">
|
||||||
|
<output id="vx-value">0</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="omega-slider">Rotation (rad/s)</label>
|
||||||
|
<input type="range" id="omega-slider" min="-3" max="3" step="0.1" value="0">
|
||||||
|
<output id="omega-value">0</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="reset-btn" type="button">Reset Controls</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="keyboard-controls" style="display: none;">
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="keyboard-max-speed">Max Speed (pixels/s)</label>
|
||||||
|
<input type="range" id="keyboard-max-speed" min="50" max="300" step="10" value="150">
|
||||||
|
<output id="keyboard-max-speed-value">150</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="keyboard-max-rotation">Max Rotation (rad/s)</label>
|
||||||
|
<input type="range" id="keyboard-max-rotation" min="0.5" max="3" step="0.1" value="1.5">
|
||||||
|
<output id="keyboard-max-rotation-value">1.5</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="keyboard-instructions">
|
||||||
|
<h4>Keyboard Controls:</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>W:</strong> Move Forward</li>
|
||||||
|
<li><strong>A:</strong> Strafe Left</li>
|
||||||
|
<li><strong>S:</strong> Move Backward</li>
|
||||||
|
<li><strong>D:</strong> Strafe Right</li>
|
||||||
|
<li><strong>Q:</strong> Rotate Counter-Clockwise</li>
|
||||||
|
<li><strong>E:</strong> Rotate Clockwise</li>
|
||||||
|
</ul>
|
||||||
|
<p><em>Click on the canvas area to focus for keyboard input</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Performance Limits</legend>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="max-speed-slider">Max Module Speed (pixels/s)</label>
|
||||||
|
<input type="range" id="max-speed-slider" min="200" max="1000" step="10" value="400">
|
||||||
|
<output id="max-speed-value">0</output>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
|
<section class="config-panel">
|
||||||
|
<h2>Robot Configuration</h2>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Quick Presets</legend>
|
||||||
|
<div class="preset-buttons">
|
||||||
|
<button id="preset-2wheel" type="button">2-Wheel</button>
|
||||||
|
<button id="preset-3wheel" type="button">3-Wheel Triangle</button>
|
||||||
|
<button id="preset-4wheel" type="button">4-Wheel Square</button>
|
||||||
|
<button id="preset-4rect" type="button">4-Wheel Rectangle</button>
|
||||||
|
<button id="preset-6wheel" type="button">6-Wheel Hexagon</button>
|
||||||
|
<button id="preset-8wheel" type="button">8-Wheel Octagon</button>
|
||||||
|
<button id="preset-8square" type="button">8-Wheel Square</button>
|
||||||
|
<button id="preset-12hex" type="button">12-Wheel Hexagon</button>
|
||||||
|
<button id="preset-16oct" type="button">16-Wheel Octogon</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Custom Configuration</legend>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="module-count">Number of Modules</label>
|
||||||
|
<input type="number" id="module-count" min="2" max="64" value="4" step="1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="generate-inputs-btn" type="button">Generate Position Inputs</button>
|
||||||
|
<button id="delete-inputs-btn" type="button">Remove Position Inputs</button>
|
||||||
|
|
||||||
|
<div id="module-position-inputs" class="position-inputs">
|
||||||
|
<!-- Dynamically generated position inputs will appear here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="apply-custom-btn" type="button" style="display: none;">Apply Custom Configuration</button>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
|
<section class="module-states">
|
||||||
|
<h2>Module States</h2>
|
||||||
|
<div id="current-config-info" class="config-info">
|
||||||
|
Current Configuration: <strong id="config-name">4-Wheel Rectangle</strong>
|
||||||
|
(<span id="module-count-display">4</span> modules)
|
||||||
|
<br>
|
||||||
|
Gyro Heading: <strong id="gyro-heading-display">0.0°</strong>
|
||||||
|
</div>
|
||||||
|
<div class="module-grid" id="module-grid">
|
||||||
|
<!-- Dynamically generated module data will appear here -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script type="module" src="vendor/lucio/graham-scan.mjs"></script>
|
<script type="module" src="vendor/lucio/graham-scan.mjs"></script>
|
||||||
|
|||||||
213
script.js
213
script.js
@ -288,6 +288,15 @@ const generateInputsBtn = document.getElementById('generate-inputs-btn');
|
|||||||
const clearInputsBtn = document.getElementById('delete-inputs-btn');
|
const clearInputsBtn = document.getElementById('delete-inputs-btn');
|
||||||
const applyCustomBtn = document.getElementById('apply-custom-btn');
|
const applyCustomBtn = document.getElementById('apply-custom-btn');
|
||||||
|
|
||||||
|
// Get control mode elements
|
||||||
|
const controlModeToggle = document.getElementById('control-mode-toggle');
|
||||||
|
const sliderControls = document.getElementById('slider-controls');
|
||||||
|
const keyboardControls = document.getElementById('keyboard-controls');
|
||||||
|
const keyboardMaxSpeed = document.getElementById('keyboard-max-speed');
|
||||||
|
const keyboardMaxSpeedOutput = document.getElementById('keyboard-max-speed-value');
|
||||||
|
const keyboardMaxRotation = document.getElementById('keyboard-max-rotation');
|
||||||
|
const keyboardMaxRotationOutput = document.getElementById('keyboard-max-rotation-value');
|
||||||
|
|
||||||
// Preset buttons
|
// Preset buttons
|
||||||
const preset2WheelBtn = document.getElementById('preset-2wheel');
|
const preset2WheelBtn = document.getElementById('preset-2wheel');
|
||||||
const preset3WheelBtn = document.getElementById('preset-3wheel');
|
const preset3WheelBtn = document.getElementById('preset-3wheel');
|
||||||
@ -301,6 +310,29 @@ const preset16OctBtn = document.getElementById('preset-16oct');
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* END DOM VARIABLES
|
* END DOM VARIABLES
|
||||||
|
* BEGIN CONTROL MODE AND INPUT DEVICE VARIABLES
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Control mode state
|
||||||
|
let isManualInputMode = false; // true = keyboard/gamepad mode, false = slider mode
|
||||||
|
|
||||||
|
// Keyboard state tracking
|
||||||
|
const keyState = {
|
||||||
|
w: false, // Forward
|
||||||
|
a: false, // Left
|
||||||
|
s: false, // Backward
|
||||||
|
d: false, // Right
|
||||||
|
q: false, // Counter-clockwise
|
||||||
|
e: false // Clockwise
|
||||||
|
};
|
||||||
|
|
||||||
|
// Current manual input velocities (from keyboard or gamepad)
|
||||||
|
let manualInputVelX = 0;
|
||||||
|
let manualInputVelY = 0;
|
||||||
|
let manualInputOmega = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* END CONTROL MODE AND INPUT DEVICE VARIABLES
|
||||||
* BEGIN LISTENER CODE
|
* BEGIN LISTENER CODE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -319,6 +351,93 @@ resetBtn.addEventListener('click', (e) => {
|
|||||||
omegaOutput.textContent = parseFloat(omegaSlider.value);
|
omegaOutput.textContent = parseFloat(omegaSlider.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keyboard control sliders
|
||||||
|
keyboardMaxSpeed.addEventListener('input', (e) => {
|
||||||
|
keyboardMaxSpeedOutput.textContent = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
keyboardMaxSpeedOutput.textContent = parseFloat(keyboardMaxSpeed.value);
|
||||||
|
|
||||||
|
keyboardMaxRotation.addEventListener('input', (e) => {
|
||||||
|
keyboardMaxRotationOutput.textContent = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
keyboardMaxRotationOutput.textContent = parseFloat(keyboardMaxRotation.value);
|
||||||
|
|
||||||
|
// Control mode toggle
|
||||||
|
controlModeToggle.addEventListener('click', () => {
|
||||||
|
isManualInputMode = !isManualInputMode;
|
||||||
|
|
||||||
|
if (isManualInputMode) {
|
||||||
|
// Switch to manual input mode (keyboard/gamepad)
|
||||||
|
sliderControls.style.display = 'none';
|
||||||
|
keyboardControls.style.display = 'block';
|
||||||
|
controlModeToggle.textContent = 'Switch to Slider Controls';
|
||||||
|
|
||||||
|
// Reset slider values when switching to manual input
|
||||||
|
vxSlider.value = 0;
|
||||||
|
vySlider.value = 0;
|
||||||
|
omegaSlider.value = 0;
|
||||||
|
vxOutput.textContent = '0';
|
||||||
|
vyOutput.textContent = '0';
|
||||||
|
omegaOutput.textContent = '0';
|
||||||
|
} else {
|
||||||
|
// Switch to slider mode
|
||||||
|
sliderControls.style.display = 'block';
|
||||||
|
keyboardControls.style.display = 'none';
|
||||||
|
controlModeToggle.textContent = 'Switch to Keyboard Controls (WASD + QE)';
|
||||||
|
|
||||||
|
// Reset manual input state
|
||||||
|
Object.keys(keyState).forEach(key => keyState[key] = false);
|
||||||
|
manualInputVelX = 0;
|
||||||
|
manualInputVelY = 0;
|
||||||
|
manualInputOmega = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard event listeners
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (!isManualInputMode) return;
|
||||||
|
|
||||||
|
const key = e.key.toLowerCase();
|
||||||
|
if (key in keyState) {
|
||||||
|
keyState[key] = true;
|
||||||
|
e.preventDefault();
|
||||||
|
updateManualInputVelocities();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keyup', (e) => {
|
||||||
|
if (!isManualInputMode) return;
|
||||||
|
|
||||||
|
const key = e.key.toLowerCase();
|
||||||
|
if (key in keyState) {
|
||||||
|
keyState[key] = false;
|
||||||
|
e.preventDefault();
|
||||||
|
updateManualInputVelocities();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to update velocities based on manual input devices (keyboard/gamepad)
|
||||||
|
function updateManualInputVelocities() {
|
||||||
|
const maxSpeed = parseFloat(keyboardMaxSpeed.value);
|
||||||
|
const maxRotation = parseFloat(keyboardMaxRotation.value);
|
||||||
|
|
||||||
|
// Calculate translation velocities from keyboard input
|
||||||
|
manualInputVelX = 0;
|
||||||
|
manualInputVelY = 0;
|
||||||
|
|
||||||
|
if (keyState.d) manualInputVelX += maxSpeed; // Right
|
||||||
|
if (keyState.a) manualInputVelX -= maxSpeed; // Left
|
||||||
|
if (keyState.w) manualInputVelY += maxSpeed; // Forward
|
||||||
|
if (keyState.s) manualInputVelY -= maxSpeed; // Backward
|
||||||
|
|
||||||
|
// Calculate rotation velocity from keyboard input
|
||||||
|
manualInputOmega = 0;
|
||||||
|
if (keyState.e) manualInputOmega += maxRotation; // Clockwise
|
||||||
|
if (keyState.q) manualInputOmega -= maxRotation; // Counter-clockwise
|
||||||
|
|
||||||
|
// TODO: Add gamepad input processing here
|
||||||
|
}
|
||||||
|
|
||||||
// Preset button event listeners
|
// Preset button event listeners
|
||||||
preset2WheelBtn.addEventListener('click', () => {
|
preset2WheelBtn.addEventListener('click', () => {
|
||||||
const positions = PresetConfigs.twoWheel(robotSize);
|
const positions = PresetConfigs.twoWheel(robotSize);
|
||||||
@ -397,8 +516,8 @@ preset16OctBtn.addEventListener('click', () => {
|
|||||||
generateInputsBtn.addEventListener('click', () => {
|
generateInputsBtn.addEventListener('click', () => {
|
||||||
const count = parseInt(moduleCountInput.value);
|
const count = parseInt(moduleCountInput.value);
|
||||||
|
|
||||||
if (isNaN(count) || count < 2) {
|
if (isNaN(count) || count < 2 || count > 64) {
|
||||||
alert('Please enter a valid number of modules above or equal to 2.');
|
alert('Please enter a valid number of modules between 2 and 64.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
generateModuleInputs(count);
|
generateModuleInputs(count);
|
||||||
@ -438,11 +557,67 @@ applyCustomBtn.addEventListener('click', () => {
|
|||||||
* BEGIN DYNAMIC DOM FUNCTIONS
|
* BEGIN DYNAMIC DOM FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Function to calculate evenly spaced module positions in circular layers
|
||||||
|
function calculateModulePositions(count) {
|
||||||
|
if (count <= 0) return [];
|
||||||
|
|
||||||
|
const baseRadius = 50; // Base radius for the first layer
|
||||||
|
const positions = [];
|
||||||
|
let remainingModules = count;
|
||||||
|
let numberOfLayers = Math.ceil(count / 6);
|
||||||
|
let modulesPerLayer = Math.ceil(count / numberOfLayers);
|
||||||
|
let angleOffset = 0;
|
||||||
|
let currentLayer = 0;
|
||||||
|
let moduleIndex = 0;
|
||||||
|
|
||||||
|
while (remainingModules > 0) {
|
||||||
|
// Determine modules for this layer
|
||||||
|
let modulesInThisLayer;
|
||||||
|
|
||||||
|
if (remainingModules <= modulesPerLayer) {
|
||||||
|
// Last layer gets all remaining modules
|
||||||
|
modulesInThisLayer = remainingModules;
|
||||||
|
} else {
|
||||||
|
// All other layers: try to distribute evenly with max modulesPerLayer
|
||||||
|
const remainingLayers = Math.ceil(remainingModules / modulesPerLayer);
|
||||||
|
modulesInThisLayer = Math.min(modulesPerLayer, Math.ceil(remainingModules / remainingLayers));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate radius for this layer
|
||||||
|
const radius = currentLayer === 0 ? baseRadius : baseRadius * (1 + currentLayer * 0.75);
|
||||||
|
|
||||||
|
// Calculate positions for modules in this layer
|
||||||
|
for (let i = 0; i < modulesInThisLayer; i++) {
|
||||||
|
const angle = (angleOffset + 2 * Math.PI * i) / modulesInThisLayer;
|
||||||
|
const x = Math.round(radius * Math.cos(angle));
|
||||||
|
const y = Math.round(radius * Math.sin(angle));
|
||||||
|
|
||||||
|
positions.push({
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
name: `Layer ${currentLayer + 1} Module ${(moduleIndex % modulesPerLayer) + 1}`
|
||||||
|
});
|
||||||
|
|
||||||
|
moduleIndex++;
|
||||||
|
}
|
||||||
|
angleOffset += Math.PI;
|
||||||
|
|
||||||
|
remainingModules -= modulesInThisLayer;
|
||||||
|
currentLayer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
function generateModuleInputs(count) {
|
function generateModuleInputs(count) {
|
||||||
const container = document.getElementById('module-position-inputs');
|
const container = document.getElementById('module-position-inputs');
|
||||||
container.innerHTML = ''; // Clear existing inputs
|
container.innerHTML = ''; // Clear existing inputs
|
||||||
|
|
||||||
|
// Calculate evenly spaced positions
|
||||||
|
const positions = calculateModulePositions(count);
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
const position = positions[i];
|
||||||
const moduleFieldset = document.createElement('fieldset');
|
const moduleFieldset = document.createElement('fieldset');
|
||||||
moduleFieldset.className = 'module-input-group';
|
moduleFieldset.className = 'module-input-group';
|
||||||
moduleFieldset.innerHTML = `
|
moduleFieldset.innerHTML = `
|
||||||
@ -450,15 +625,15 @@ function generateModuleInputs(count) {
|
|||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="module-${i}-name">Module Name</label>
|
<label for="module-${i}-name">Module Name</label>
|
||||||
<input type="text" id="module-${i}-name" value="Module ${i + 1}" required>
|
<input type="text" id="module-${i}-name" value="${position.name}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="module-${i}-x">X Position (pixels)</label>
|
<label for="module-${i}-x">X Position (pixels)</label>
|
||||||
<input type="number" id="module-${i}-x" step="1" value="0" required>
|
<input type="number" id="module-${i}-x" step="1" value="${position.x}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="module-${i}-y">Y Position (pixels)</label>
|
<label for="module-${i}-y">Y Position (pixels)</label>
|
||||||
<input type="number" id="module-${i}-y" step="0.1" value="0" required>
|
<input type="number" id="module-${i}-y" step="0.1" value="${position.y}" required>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(moduleFieldset);
|
container.appendChild(moduleFieldset);
|
||||||
@ -652,10 +827,16 @@ function animate() {
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||||
|
|
||||||
// Update speeds based on sliders
|
// Update speeds based on control mode
|
||||||
xSpeed = parseFloat(vxSlider.value);
|
if (isManualInputMode) {
|
||||||
ySpeed = -parseFloat(vySlider.value);
|
xSpeed = manualInputVelX;
|
||||||
turnSpeed = parseFloat(omegaSlider.value);
|
ySpeed = -manualInputVelY; // Negative because canvas Y axis is inverted
|
||||||
|
turnSpeed = manualInputOmega;
|
||||||
|
} else {
|
||||||
|
xSpeed = parseFloat(vxSlider.value);
|
||||||
|
ySpeed = -parseFloat(vySlider.value);
|
||||||
|
turnSpeed = parseFloat(omegaSlider.value);
|
||||||
|
}
|
||||||
|
|
||||||
// Update module states before drawing the robot
|
// Update module states before drawing the robot
|
||||||
// The drive() method will update the gyroHeading internally
|
// The drive() method will update the gyroHeading internally
|
||||||
@ -665,11 +846,17 @@ function animate() {
|
|||||||
// Get the actual robot velocity (after scaling to max module speed) for grid animation
|
// Get the actual robot velocity (after scaling to max module speed) for grid animation
|
||||||
const actualVelocity = robot.getActualVelocity();
|
const actualVelocity = robot.getActualVelocity();
|
||||||
|
|
||||||
|
|
||||||
// Update control outputs with actual speeds
|
// Update control outputs with actual speeds
|
||||||
vxOutput.textContent = `Requested: ${vxSlider.value} | Actual: ${actualVelocity.x.toFixed(2)}`;
|
if (isManualInputMode) {
|
||||||
vyOutput.textContent = `Requested: ${vySlider.value} | Actual: ${-actualVelocity.y.toFixed(2)}`;
|
// In manual input mode (keyboard/gamepad), show the current values
|
||||||
omegaOutput.textContent = `Requested: ${omegaSlider.value} | Actual: ${robot.actualTurnSpeed.toFixed(2)}`;
|
keyboardMaxSpeedOutput.textContent = `Max: ${keyboardMaxSpeed.value} | Current: ${Math.max(Math.abs(actualVelocity.x), Math.abs(actualVelocity.y)).toFixed(1)}`;
|
||||||
|
keyboardMaxRotationOutput.textContent = `Max: ${keyboardMaxRotation.value} | Current: ${Math.abs(robot.actualTurnSpeed || 0).toFixed(2)}`;
|
||||||
|
} else {
|
||||||
|
// In slider mode, show requested vs actual
|
||||||
|
vxOutput.textContent = `Requested: ${vxSlider.value} | Actual: ${actualVelocity.x.toFixed(2)}`;
|
||||||
|
vyOutput.textContent = `Requested: ${vySlider.value} | Actual: ${-actualVelocity.y.toFixed(2)}`;
|
||||||
|
omegaOutput.textContent = `Requested: ${omegaSlider.value} | Actual: ${(robot.actualTurnSpeed || 0).toFixed(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Animate the grid
|
// Animate the grid
|
||||||
let offsetSpeedDivisor = (100 - gridSquareSize <= 0 ? 1 : 100 - gridSquareSize);
|
let offsetSpeedDivisor = (100 - gridSquareSize <= 0 ? 1 : 100 - gridSquareSize);
|
||||||
|
|||||||
10
styles.css
10
styles.css
@ -238,7 +238,7 @@ tr:hover {
|
|||||||
|
|
||||||
.visualization-area {
|
.visualization-area {
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 1 / 3;
|
grid-row: 2 / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#swerve-canvas {
|
#swerve-canvas {
|
||||||
@ -252,22 +252,22 @@ tr:hover {
|
|||||||
|
|
||||||
.controls-panel {
|
.controls-panel {
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 2 / 3;
|
grid-row: 3 / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-panel {
|
.config-panel {
|
||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
grid-row: 1 / 2;
|
grid-row: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-states {
|
.module-states {
|
||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
grid-row: 2 / 3;
|
grid-row: 3 / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentation {
|
.documentation {
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
grid-row: 4 / 5;
|
grid-row: 1 / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
|
|||||||
Reference in New Issue
Block a user