Skip to main content

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

isVisible
boolean
required
Controls whether the GUI is visible
isRunning
boolean
required
Current simulation state (running/paused)

Control Callbacks

onPlay
(val: boolean) => void
required
Callback to start/pause simulation
onReset
() => void
required
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

gravity
boolean
required
Current gravity state
onGravity
(val: boolean) => void
required
Callback to toggle gravity
friction
number
required
Ground friction coefficient (0-2)
setFriction
(v: number) => void
required
Callback to update friction value
deltaT
number
required
Physics time step (0.001-0.1 seconds)
setDeltaT
(v: number) => void
required
Callback to update time step

Visualization Settings

path
boolean
required
Whether particle trails are visible
onTogglePath
(val: boolean) => void
required
Callback to toggle trail visibility
axes
boolean
required
Whether coordinate axes are visible
onToggleAxes
(val: boolean) => void
required
Callback to toggle axes visibility
showGrid
boolean
required
Whether ground grid is visible
setShowGrid
(val: boolean) => void
required
Callback to toggle grid visibility
showParticles
boolean
required
Whether particle spheres are visible
setShowParticles
(val: boolean) => void
required
Callback to toggle particle visibility
particleRadius
number
required
Radius of particle spheres (0.1-3)
setParticleRadius
(v: number) => void
required
Callback to update particle radius
forceMode
ForceDisplayMode
required
Force visualization mode (0=off, 1=resultant, 2=individual)
setForceMode
(mode: ForceDisplayMode) => void
required
Callback to change force display mode
showInfo
boolean
required
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
onResetCamera
() => void
required
Callback to reset camera to default position

Particle Management

particulas
PData[]
required
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.

Build docs developers (and LLMs) love