Overview
The GUI component provides the main user interface for controlling the simulation. It features collapsible sections for controls, environment settings, particle management, and configuration save/load functionality.
File: src/gui/GUI.tsx
Interface
interface GUIProps {
isVisible: boolean;
onAdd: (x: number, y: number, z: number) => void;
onPlay: (val: boolean) => void;
onReset: () => void;
onGravity: (val: boolean) => void;
onTogglePath: (val: boolean) => void;
onToggleAxes: (val: boolean) => void;
onFocus: (part: PData) => void;
onResetCamera: () => void;
friction: number;
setFriction: (v: number) => void;
deltaT: number;
setDeltaT: (v: number) => void;
isRunning: boolean;
gravity: boolean;
path: boolean;
axes: boolean;
showGrid: boolean;
setShowGrid: (val: boolean) => void;
showParticles: boolean;
setShowParticles: (val: boolean) => void;
particleRadius: number;
setParticleRadius: (v: number) => void;
particulas: PData[];
onUpdatePart: (id: number, data: any) => void;
onDelete: (id: number) => void;
onLoadConfig?: (config: SavedConfig) => void;
forceMode: ForceDisplayMode;
setForceMode: (mode: ForceDisplayMode) => void;
showInfo: boolean;
setShowInfo: (val: boolean) => void;
physicsRefs: MutableRefObject<Record<number, LiveData>>;
}
Props
Visibility & State
Controls whether the GUI is visible
Current simulation state (running/paused)
Control Callbacks
onPlay
(val: boolean) => void
required
Callback to start/pause simulation
Callback to reset simulation to initial state
onAdd
(x: number, y: number, z: number) => void
required
Callback to add a new particle at specified coordinates
Environment Settings
onGravity
(val: boolean) => void
required
Callback to toggle gravity
Ground friction coefficient (0-2)
setFriction
(v: number) => void
required
Callback to update friction value
Physics time step (0.001-0.1 seconds)
setDeltaT
(v: number) => void
required
Callback to update time step
Visualization Settings
Whether particle trails are visible
onTogglePath
(val: boolean) => void
required
Callback to toggle trail visibility
Whether coordinate axes are visible
onToggleAxes
(val: boolean) => void
required
Callback to toggle axes visibility
Whether ground grid is visible
setShowGrid
(val: boolean) => void
required
Callback to toggle grid visibility
Whether particle spheres are visible
setShowParticles
(val: boolean) => void
required
Callback to toggle particle visibility
Radius of particle spheres (0.1-3)
setParticleRadius
(v: number) => void
required
Callback to update particle radius
Force visualization mode (0=off, 1=resultant, 2=individual)
setForceMode
(mode: ForceDisplayMode) => void
required
Callback to change force display mode
Whether info panel is visible
setShowInfo
(val: boolean) => void
required
Callback to toggle info panel
Camera Controls
onFocus
(part: PData) => void
required
Callback to focus camera on a particle
Callback to reset camera to default position
Particle Management
Array of all particles in the simulation
onUpdatePart
(id: number, data: any) => void
required
Callback to update a particle’s properties
onDelete
(id: number) => void
required
Callback to delete a particle
physicsRefs
MutableRefObject<Record<number, LiveData>>
required
Ref object containing live physics data for all particles
Configuration
onLoadConfig
(config: SavedConfig) => void
Optional callback to load a saved configuration
GUI Sections
The GUI is organized into collapsible sections:
1. Controls Section
<div className="section">
<div className="section-header" onClick={() => toggleSection('controls')}>
<h4>Controls</h4>
<span className={`toggle-icon ${openSections.controls ? 'open' : ''}`}>▼</span>
</div>
{openSections.controls && (
<div className="section-content">
<button
className={p.isRunning ? "btn-pause" : "btn-play"}
onClick={() => p.onPlay(!p.isRunning)}
>
{p.isRunning ? "PAUSE" : "START"}
</button>
<button onClick={p.onReset}>RESET</button>
</div>
)}
</div>
Contains:
- Start/Pause button
- Reset button
2. Environment Section
Controls for simulation environment:
- Gravity: Toggle gravity on/off
- Trail: Toggle particle trails
- Axes: Toggle coordinate axes
- Grid: Toggle ground grid
- Particles: Toggle particle sphere visibility
- Particle Size: Slider to adjust particle radius (0.1-3.0)
- Forces Display: Cycle through force visualization modes (OFF → Resultant → Individual)
- Info: Toggle info panel
- Ground Friction: Slider to adjust friction coefficient (0-2)
- Delta T: Slider to adjust physics time step (0.001-0.1)
3. New Particle Section
<div className="section-content">
<div style={{ display: "flex", gap: 4 }}>
<input type="number" placeholder="X" value={nX} onChange={(e) => setNX(Number(e.target.value))} />
<input type="number" placeholder="Y" value={nY} onChange={(e) => setNY(Number(e.target.value))} />
<input type="number" placeholder="Z" value={nZ} onChange={(e) => setNZ(Number(e.target.value))} />
</div>
<button className="btn-play" onClick={() => p.onAdd(nX, nY, nZ)}>
+ ADD PARTICLE
</button>
</div>
Allows adding new particles:
- X, Y, Z coordinate inputs
- Add particle button
4. Particles List Section
<div className="lista-particulas">
{p.particulas.map((part) => (
<div
key={part.id}
className="particle-item"
onClick={(e) => {
if (e.shiftKey) {
p.onResetCamera();
return;
}
setSelId(part.id);
}}
onContextMenu={(e) => {
e.preventDefault();
p.onFocus(part);
}}
style={{ color: selId === part.id ? "#fff" : part.color }}
>
<span>
P-{part.id.toString().slice(-3)} {part.enSuelo ? "(Ground)" : ""}
</span>
<button
onClick={(e) => {
e.stopPropagation();
p.onDelete(part.id);
if (selId === part.id) setSelId(null);
}}
className="btn-delete"
>
x
</button>
</div>
))}
</div>
Displays all particles with:
- Click to select and edit
- Right-click to focus camera
- Shift+click to reset camera
- Delete button for each particle
- Ground indicator when particle is on the ground
Configuration Management
Save Configuration
const handleSaveConfig = () => {
const config: SavedConfig = {
version: "1.0",
timestamp: new Date().toISOString(),
settings: {
gravity: p.gravity,
friction: p.friction,
deltaT: p.deltaT,
path: p.path,
axes: p.axes,
},
particulas: p.particulas,
};
const blob = new Blob([JSON.stringify(config, null, 2)], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `physics-config-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
Saves current simulation state as JSON file including:
- Version and timestamp
- All environment settings
- All particle configurations
Load Configuration
const handleLoadConfig = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const config: SavedConfig = JSON.parse(event.target?.result as string);
if (p.onLoadConfig) {
p.onLoadConfig(config);
}
} catch (err) {
alert("Error loading configuration file");
}
};
reader.readAsText(file);
e.target.value = ""; // Reset input
};
Loads configuration from JSON file.
Load Example
const handleLoadExample = async () => {
try {
const response = await fetch('/default.json');
const config: SavedConfig = await response.json();
if (p.onLoadConfig) {
p.onLoadConfig(config);
}
} catch (err) {
alert("Error loading example");
}
};
Loads a default example configuration from public/default.json.
Particle Editor Integration
When a particle is selected, the ParticleEditor component is rendered:
{sel && (
<ParticleEditor
sel={sel}
onUpdatePart={p.onUpdatePart}
onClose={() => setSelId(null)}
/>
)}
This provides a detailed editor for:
- Initial position and velocity
- Mass and massless mode
- Force definitions
- Event triggers
- Particle color
Info Panel Integration
<InfoPanel
particulas={p.particulas}
physicsRefs={p.physicsRefs}
gravity={p.gravity}
friction={p.friction}
isVisible={p.showInfo}
isRunning={p.isRunning}
/>
Displays real-time information about all particles when enabled.
Auto-Pause on Edit
useEffect(() => {
if (selId !== null) p.onPlay(false);
}, [selId]);
Automatically pauses the simulation when a particle is selected for editing.