Skip to main content

React Frontend Overview

The React frontend provides an interactive UI for controlling the ray tracer and displaying rendered images.

Component Architecture​

src/
β”œβ”€β”€ components/
β”‚ β”œβ”€β”€ canvas/
β”‚ β”‚ β”œβ”€β”€ RaytracerCanvas.jsx # Main render canvas
β”‚ β”‚ β”œβ”€β”€ RaytracerCanvas.css
β”‚ β”‚ β”œβ”€β”€ SceneToolbar.jsx # Scene preset selector
β”‚ β”‚ └── SceneToolbar.css
β”‚ β”œβ”€β”€ controls/
β”‚ β”‚ β”œβ”€β”€ ControlPanel.jsx # Tabbed control container
β”‚ β”‚ β”œβ”€β”€ ControlPanel.css
β”‚ β”‚ β”œβ”€β”€ LightControls.jsx # Light position controls
β”‚ β”‚ β”œβ”€β”€ MaterialControls.jsx # Material property controls
β”‚ β”‚ β”œβ”€β”€ CameraControls.jsx # Camera position/FOV controls
β”‚ β”‚ β”œβ”€β”€ ViewControls.jsx # Grid and resolution controls
β”‚ β”‚ └── ControlSection.css # Shared control styles
β”‚ β”œβ”€β”€ layout/
β”‚ β”‚ β”œβ”€β”€ Header.jsx # App header with stats
β”‚ β”‚ β”œβ”€β”€ Header.css
β”‚ β”‚ β”œβ”€β”€ InfoModal.jsx # About dialog
β”‚ β”‚ └── InfoModal.css
β”‚ └── ui/
β”‚ β”œβ”€β”€ Tabs.jsx # Reusable tab component
β”‚ β”œβ”€β”€ Tabs.css
β”‚ β”œβ”€β”€ Slider.jsx # Reusable slider component
β”‚ β”œβ”€β”€ Slider.css
β”‚ β”œβ”€β”€ Toggle.jsx # Reusable toggle component
β”‚ └── Toggle.css
β”œβ”€β”€ hooks/
β”‚ └── useWasm.js # WebAssembly loading hook
β”œβ”€β”€ App.jsx # Main application
β”œβ”€β”€ App.css # Global styles
└── main.jsx # Entry point

State Management​

The app uses React's useState for all state management:

// App.jsx
const [scenePreset, setScenePreset] = useState(0);
const [sphereCount, setSphereCount] = useState(1);
const [light, setLight] = useState({ x: 2, y: 3, z: -2 });
const [material, setMaterial] = useState({
color: { r: 0.9, g: 0.2, b: 0.15 },
specular: 0.5,
shininess: 32,
reflectivity: 0.3
});
const [camera, setCamera] = useState({
position: { x: 0, y: 0.5, z: -4 },
target: { x: 0, y: 0, z: 0 },
fov: 60
});
const [view, setView] = useState({
showGroundPlane: true,
showGrid: true,
gridScale: 1.0,
groundReflectivity: 0.15,
maxBounces: 5,
resolution: 512
});

Data Flow​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ App.jsx β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ State: light, material, camera, view, scenePreset β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ControlPanel β”‚ β”‚SceneToolbarβ”‚ β”‚ RaytracerCanvas β”‚ β”‚
β”‚ β”‚ (onChange) β”‚ β”‚(onPreset) β”‚ β”‚ (renders image) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ WASM Module β”‚ β”‚
β”‚ β”‚ (C++ API) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Design Decisions​

1. Prop Drilling vs Context​

We use prop drilling for simplicity. The component tree is shallow enough that Context would add unnecessary complexity.

2. Controlled Components​

All inputs are controlled - their values come from React state:

<Slider
value={light.x}
onChange={(v) => setLight(prev => ({ ...prev, x: v }))}
/>

3. Debounced Rendering​

Canvas renders are debounced with requestAnimationFrame to prevent excessive WASM calls:

useEffect(() => {
const frameId = requestAnimationFrame(renderFrame);
return () => cancelAnimationFrame(frameId);
}, [renderFrame]);

4. Modular UI Components​

Reusable components (Slider, Toggle, Tabs) are in components/ui/ and can be used anywhere.

Styling Approach​

  • CSS Variables for theming in :root
  • Component CSS files alongside each component
  • BEM-like naming for clarity
  • Dark theme by default
:root {
--bg-primary: #09090b;
--accent-primary: #e85d4c;
--text-primary: #f4f4f5;
/* ... */
}

Next Steps​

Explore each component category: