Skip to content

Commit ecfb83d

Browse files
author
Oblarg
committed
add fire control simulation
1 parent 957066b commit ecfb83d

6 files changed

Lines changed: 1503 additions & 0 deletions

File tree

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
class DynamicShootingWidget {
2+
constructor(divIdPrefix) {
3+
this.divIdPrefix = divIdPrefix;
4+
5+
this.containerDiv = document.getElementById(divIdPrefix + "_container");
6+
this.visualizationDrawDiv = document.getElementById(divIdPrefix + "_viz");
7+
this.controlDrawDiv = document.getElementById(divIdPrefix + "_ctrls");
8+
9+
if (!this.containerDiv || !this.visualizationDrawDiv || !this.controlDrawDiv) {
10+
console.error('[DynamicShootingWidget] Missing required DOM elements!');
11+
return;
12+
}
13+
14+
// Initialize visualization
15+
this.visualization = new DynamicShootingVisualization(this.visualizationDrawDiv);
16+
17+
// Default values
18+
this.robotVelocityX = 0.0;
19+
this.robotVelocityY = 0.0;
20+
this.numIterations = 10; // Number of iterations to compute and display
21+
this.currentIteration = 1; // Which iteration to highlight/display (1 to numIterations)
22+
this.projectileSpeed = 3.5; // m/s
23+
this.convergenceTolerance = 0.1; // meters
24+
25+
// Set initial values on visualization
26+
this.visualization.setNumIterations(this.numIterations);
27+
this.visualization.setProjectileSpeed(this.projectileSpeed);
28+
this.visualization.setConvergenceTolerance(this.convergenceTolerance);
29+
30+
// Build control table
31+
this.buildControlTable(divIdPrefix);
32+
33+
// Make container square immediately
34+
if (this.containerDiv) {
35+
const width = this.containerDiv.offsetWidth || this.containerDiv.clientWidth;
36+
if (width > 0) {
37+
this.containerDiv.style.height = width + "px";
38+
}
39+
}
40+
41+
// Initial update - synchronous
42+
this.update();
43+
}
44+
45+
buildControlTable(divIdPrefix) {
46+
// Clear the old control table and use the control div for our control bar
47+
this.controlDrawDiv.innerHTML = ""; // Clear existing content
48+
this.controlDrawDiv.style.display = "flex"; // Use flex layout
49+
this.controlDrawDiv.style.alignItems = "center";
50+
this.controlDrawDiv.style.justifyContent = "center";
51+
this.controlDrawDiv.style.gap = "10px";
52+
this.controlDrawDiv.style.padding = "5px";
53+
this.controlDrawDiv.style.backgroundColor = "#f5f5f5";
54+
this.controlDrawDiv.style.borderTop = "1px solid #ddd";
55+
this.controlDrawDiv.style.width = "100%"; // Full width
56+
this.controlDrawDiv.style.flexShrink = "0"; // Don't shrink
57+
this.controlDrawDiv.style.flexBasis = "auto"; // Natural height
58+
this.controlDrawDiv.style.order = "2"; // Ensure it appears after visualization
59+
this.controlDrawDiv.style.position = "relative"; // Proper positioning
60+
this.controlDrawDiv.style.zIndex = "10"; // Ensure it's above canvas elements
61+
this.controlDrawDiv.style.boxSizing = "border-box"; // Include padding in size
62+
63+
// Create a tiny control bar - we'll add elements directly to controlDrawDiv
64+
const controlBar = this.controlDrawDiv;
65+
66+
// Iteration label
67+
const iterationLabel = document.createElement("label");
68+
iterationLabel.innerHTML = "Iterations:";
69+
iterationLabel.style.cssText = "font-size: 12px; margin-right: 5px;";
70+
controlBar.appendChild(iterationLabel);
71+
72+
// Previous button
73+
const prevButton = document.createElement("button");
74+
prevButton.innerHTML = "◀";
75+
prevButton.style.cssText = "padding: 2px 8px; font-size: 12px; cursor: pointer;";
76+
prevButton.onclick = function() {
77+
if (this.currentIteration > 1) {
78+
this.currentIteration--;
79+
if (this.iterationInput) {
80+
this.iterationInput.value = this.currentIteration;
81+
}
82+
this.update();
83+
}
84+
}.bind(this);
85+
controlBar.appendChild(prevButton);
86+
87+
// Iteration number input (for which iteration to display)
88+
const input = document.createElement("INPUT");
89+
input.setAttribute("type", "number");
90+
input.setAttribute("min", "1");
91+
input.setAttribute("max", this.numIterations.toString());
92+
input.setAttribute("step", "1");
93+
input.setAttribute("value", "1");
94+
input.setAttribute("id", divIdPrefix + "_iteration");
95+
input.style.cssText = "width: 50px; padding: 2px; font-size: 12px; text-align: center;";
96+
97+
// Store reference to input for syncing
98+
this.iterationInput = input;
99+
100+
input.onchange = function (event) {
101+
let val = parseInt(event.target.value);
102+
if (isNaN(val) || val < 1) val = 1;
103+
if (val > this.numIterations) val = this.numIterations;
104+
this.currentIteration = val;
105+
event.target.value = val;
106+
this.update();
107+
}.bind(this);
108+
109+
input.oninput = function (event) {
110+
let val = parseInt(event.target.value);
111+
if (!isNaN(val) && val >= 1 && val <= this.numIterations) {
112+
this.currentIteration = val;
113+
this.update();
114+
}
115+
}.bind(this);
116+
117+
controlBar.appendChild(input);
118+
119+
// Next button
120+
const nextButton = document.createElement("button");
121+
nextButton.innerHTML = "▶";
122+
nextButton.style.cssText = "padding: 2px 8px; font-size: 12px; cursor: pointer;";
123+
nextButton.onclick = function() {
124+
if (this.currentIteration < this.numIterations) {
125+
this.currentIteration++;
126+
if (this.iterationInput) {
127+
this.iterationInput.value = this.currentIteration;
128+
}
129+
this.update();
130+
}
131+
}.bind(this);
132+
controlBar.appendChild(nextButton);
133+
134+
// Add separator
135+
const separator = document.createElement("span");
136+
separator.innerHTML = "|";
137+
separator.style.cssText = "margin: 0 10px; color: #999; font-size: 12px;";
138+
controlBar.appendChild(separator);
139+
140+
// Projectile speed label
141+
const speedLabel = document.createElement("label");
142+
speedLabel.innerHTML = "Projectile Speed (m/s):";
143+
speedLabel.style.cssText = "font-size: 12px; margin-right: 5px;";
144+
controlBar.appendChild(speedLabel);
145+
146+
// Projectile speed slider
147+
const speedSlider = document.createElement("INPUT");
148+
speedSlider.setAttribute("type", "range");
149+
speedSlider.setAttribute("min", "0.1");
150+
speedSlider.setAttribute("max", "20.0");
151+
speedSlider.setAttribute("step", "0.1");
152+
speedSlider.setAttribute("value", this.projectileSpeed.toString());
153+
speedSlider.setAttribute("id", divIdPrefix + "_projectile_speed");
154+
speedSlider.style.cssText = "width: 120px; height: 20px; margin: 0 5px; cursor: pointer;";
155+
156+
// Value display
157+
const speedValueDisplay = document.createElement("span");
158+
speedValueDisplay.innerHTML = this.projectileSpeed.toFixed(1);
159+
speedValueDisplay.style.cssText = "font-size: 12px; min-width: 35px; text-align: center; display: inline-block;";
160+
161+
// Store references
162+
this.speedSlider = speedSlider;
163+
this.speedValueDisplay = speedValueDisplay;
164+
165+
speedSlider.oninput = function (event) {
166+
let val = parseFloat(event.target.value);
167+
if (!isNaN(val) && val >= 0.1 && val <= 20.0) {
168+
this.projectileSpeed = val;
169+
this.speedValueDisplay.innerHTML = val.toFixed(1);
170+
this.update();
171+
}
172+
}.bind(this);
173+
174+
speedSlider.onchange = function (event) {
175+
let val = parseFloat(event.target.value);
176+
if (isNaN(val) || val < 0.1) val = 0.1;
177+
if (val > 20.0) val = 20.0;
178+
this.projectileSpeed = val;
179+
this.speedValueDisplay.innerHTML = val.toFixed(1);
180+
this.update();
181+
}.bind(this);
182+
183+
controlBar.appendChild(speedSlider);
184+
controlBar.appendChild(speedValueDisplay);
185+
186+
// Add separator
187+
const separator2 = document.createElement("span");
188+
separator2.innerHTML = "|";
189+
separator2.style.cssText = "margin: 0 10px; color: #999; font-size: 12px;";
190+
controlBar.appendChild(separator2);
191+
192+
// Tolerance label
193+
const toleranceLabel = document.createElement("label");
194+
toleranceLabel.innerHTML = "Tolerance (m):";
195+
toleranceLabel.style.cssText = "font-size: 12px; margin-right: 5px;";
196+
controlBar.appendChild(toleranceLabel);
197+
198+
// Tolerance slider
199+
const toleranceSlider = document.createElement("INPUT");
200+
toleranceSlider.setAttribute("type", "range");
201+
toleranceSlider.setAttribute("min", "0.01");
202+
toleranceSlider.setAttribute("max", "1.0");
203+
toleranceSlider.setAttribute("step", "0.01");
204+
toleranceSlider.setAttribute("value", this.convergenceTolerance.toString());
205+
toleranceSlider.setAttribute("id", divIdPrefix + "_tolerance");
206+
toleranceSlider.style.cssText = "width: 120px; height: 20px; margin: 0 5px; cursor: pointer;";
207+
208+
// Value display
209+
const toleranceValueDisplay = document.createElement("span");
210+
toleranceValueDisplay.innerHTML = this.convergenceTolerance.toFixed(2);
211+
toleranceValueDisplay.style.cssText = "font-size: 12px; min-width: 40px; text-align: center; display: inline-block;";
212+
213+
// Store references
214+
this.toleranceSlider = toleranceSlider;
215+
this.toleranceValueDisplay = toleranceValueDisplay;
216+
217+
toleranceSlider.oninput = function (event) {
218+
let val = parseFloat(event.target.value);
219+
if (!isNaN(val) && val >= 0.01 && val <= 1.0) {
220+
this.convergenceTolerance = val;
221+
this.toleranceValueDisplay.innerHTML = val.toFixed(2);
222+
this.update();
223+
}
224+
}.bind(this);
225+
226+
toleranceSlider.onchange = function (event) {
227+
let val = parseFloat(event.target.value);
228+
if (isNaN(val) || val < 0.01) val = 0.01;
229+
if (val > 1.0) val = 1.0;
230+
this.convergenceTolerance = val;
231+
this.toleranceValueDisplay.innerHTML = val.toFixed(2);
232+
this.update();
233+
}.bind(this);
234+
235+
controlBar.appendChild(toleranceSlider);
236+
controlBar.appendChild(toleranceValueDisplay);
237+
238+
// Control bar is already in the right place (controlDrawDiv is in the flex-grid)
239+
// Make sure the visualization div doesn't expand into the control area
240+
if (this.visualizationDrawDiv.parentNode) {
241+
// Ensure the parent flex-grid uses column layout if needed
242+
const flexGrid = this.visualizationDrawDiv.parentNode;
243+
if (flexGrid.classList && flexGrid.classList.contains('flex-grid')) {
244+
// Make flex-grid use column layout to stack vertically
245+
flexGrid.style.flexDirection = "column";
246+
flexGrid.style.alignItems = "stretch"; // Stretch children to full width
247+
flexGrid.style.height = "100%"; // Fill container height
248+
flexGrid.style.width = "100%"; // Fill container width
249+
flexGrid.style.margin = "0"; // Remove default margin
250+
flexGrid.style.padding = "0"; // Remove padding
251+
flexGrid.style.boxSizing = "border-box"; // Include borders in size
252+
253+
// Override the .col class constraints - let it grow to fill available space
254+
this.visualizationDrawDiv.style.minHeight = "0"; // Remove min-height constraint
255+
this.visualizationDrawDiv.style.maxHeight = "none"; // Remove max-height constraint
256+
this.visualizationDrawDiv.style.height = "auto"; // Let it grow
257+
this.visualizationDrawDiv.style.flex = "1 1 auto"; // Grow to fill available space
258+
this.visualizationDrawDiv.style.overflow = "hidden"; // Prevent overflow
259+
this.visualizationDrawDiv.style.position = "relative"; // Ensure proper positioning
260+
this.visualizationDrawDiv.style.order = "1"; // Ensure it appears before controls
261+
this.visualizationDrawDiv.style.width = "100%"; // Full width
262+
this.visualizationDrawDiv.style.boxSizing = "border-box"; // Include padding in size
263+
264+
// Ensure control div is properly positioned
265+
this.controlDrawDiv.style.flex = "0 0 auto"; // Don't grow/shrink
266+
this.controlDrawDiv.style.order = "2"; // Ensure it appears after visualization
267+
}
268+
}
269+
270+
// Make the container div square based on its width
271+
// This ensures the bounding box is square and the visualization can fill it
272+
if (this.containerDiv) {
273+
this.containerDiv.style.overflow = "hidden"; // Prevent container overflow
274+
this.containerDiv.style.width = "100%"; // Full width
275+
276+
// Make container square: set height to match width
277+
const makeSquare = () => {
278+
const width = this.containerDiv.offsetWidth || this.containerDiv.clientWidth;
279+
if (width > 0) {
280+
this.containerDiv.style.height = width + "px";
281+
if (this.visualization) {
282+
this.visualization.updateSize();
283+
this.update();
284+
}
285+
}
286+
};
287+
288+
// Update on resize
289+
window.addEventListener("resize", makeSquare);
290+
}
291+
292+
// Set up velocity callback for drag-to-update
293+
this.visualization.setVelocityCallback((velX, velY) => {
294+
this.robotVelocityX = velX;
295+
this.robotVelocityY = velY;
296+
this.update();
297+
});
298+
}
299+
300+
update() {
301+
// Update visualization with current values
302+
this.visualization.setRobotVelocity(this.robotVelocityX, this.robotVelocityY);
303+
this.visualization.setCurrentIteration(this.currentIteration);
304+
this.visualization.setNumIterations(this.numIterations);
305+
this.visualization.setProjectileSpeed(this.projectileSpeed);
306+
this.visualization.setConvergenceTolerance(this.convergenceTolerance);
307+
this.visualization.update();
308+
309+
// Sync iteration input field
310+
if (this.iterationInput) {
311+
this.iterationInput.value = this.currentIteration;
312+
}
313+
314+
// Sync speed slider and value display
315+
if (this.speedSlider) {
316+
this.speedSlider.value = this.projectileSpeed.toString();
317+
}
318+
if (this.speedValueDisplay) {
319+
this.speedValueDisplay.innerHTML = this.projectileSpeed.toFixed(1);
320+
}
321+
322+
// Sync tolerance slider and value display
323+
if (this.toleranceSlider) {
324+
this.toleranceSlider.value = this.convergenceTolerance.toString();
325+
}
326+
if (this.toleranceValueDisplay) {
327+
this.toleranceValueDisplay.innerHTML = this.convergenceTolerance.toFixed(2);
328+
}
329+
}
330+
}

0 commit comments

Comments
 (0)