Source code for electrosim.rendering.overlay

from __future__ import annotations

import pygame

from electrosim import config


[docs] def draw_overlay(screen: pygame.Surface, font: pygame.font.Font, sim_state: dict, overlay_enabled: bool = True) -> None: """Render a multi-line overlay with FPS, counts, energies, and controls. Parameters ---------- screen : pygame.Surface Target surface. font : pygame.font.Font Font used for text rendering. sim_state : dict Simulation state values including fps, n, speed_label, dt_s, substeps, energies, and optional profiling timings under key `profile`. overlay_enabled : bool Whether to render the overlay. If False, nothing is drawn. """ if not overlay_enabled: return lines = [] lines.append(f"FPS: {sim_state['fps']:.1f}") lines.append(f"Particles: {sim_state['n']}") lines.append(f"Speed: {sim_state['speed_label']}") lines.append(f"dt: {sim_state['dt_s']:.4f} s substeps/frame: {sim_state['substeps']}") lines.append(f"E_kin: {sim_state['E_kin']:.3e} J E_pot_elec: {sim_state['E_pot']:.3e} J E_tot: {sim_state['E_tot']:.3e} J") if getattr(config, "PROFILE_OVERLAY_ENABLED", False): prof = sim_state.get("profile") if prof: lines.append( f"performance (ms): physics={prof.get('physics_ms',0):.1f} field={prof.get('field_ms',0):.1f} draw={prof.get('draw_ms',0):.1f} total={prof.get('total_ms',0):.1f}" ) lines.append("Controls: LMB=place, Shift=negative, Alt/Ctrl=fixed, P=pause, R=reset, C=clear, Esc=exit") lines.append("Editing: click=select, drag selected=move; Q/W charge, A/S mass, Z/X radius, Space=fixed, Delete=remove") lines.append("Show/Hide: G grid (meters), F forces, V velocities, E electric field, T trajectories, O overlay, I info tooltip, 1..4 speed") mode_label = "Fixed brightness" if config.FIELD_VIS_MODE == "brightness" else "Variable length" lines.append(f"Electric field (M): mode = {mode_label}") lines.append(f"Particle glow: B Show/Hide (state: {'ON' if config.GLOW_ENABLED else 'OFF'})") lines.append("Validation: U = uniform electric field (10 s)") # Validation summary val = sim_state.get("validation") if val and val.get("active"): E = val.get("E", (0.0, 0.0)) a = val.get("a", (0.0, 0.0)) t = float(val.get("t", 0.0)) dt = float(val.get("dt_s", 0.0)) pos_err = float(val.get("pos_err", 0.0)) vel_err = float(val.get("vel_err", 0.0)) lines.append(f"Validation: E=({E[0]:.2f},{E[1]:.2f}) N/C a=({a[0]:.2f},{a[1]:.2f}) m/s^2") lines.append(f"t={t:.2f} s dt={dt:.4f} s |pos_err|={pos_err:.3e} |vel_err|={vel_err:.3e}") # Final comparison if available if val.get("reached_end"): pth = val.get("pos_th") vth = val.get("vel_th") ps = val.get("pos_sim") vs = val.get("vel_sim") if pth and vth: lines.append(f"FINAL pos_th=({pth[0]:.3f},{pth[1]:.3f}) vel_th=({vth[0]:.3f},{vth[1]:.3f})") if ps and vs: lines.append(f"FINAL pos_sim=({ps[0]:.3f},{ps[1]:.3f}) vel_sim=({vs[0]:.3f},{vs[1]:.3f})") y = 45 for text in lines: render = font.render(text, True, config.OVERLAY_TEXT_COLOR) shadow = font.render(text, True, config.OVERLAY_SHADOW_COLOR) screen.blit(shadow, (10 + config.OVERLAY_SHADOW_OFFSET[0], y + config.OVERLAY_SHADOW_OFFSET[1])) screen.blit(render, (10, y)) y += render.get_height() + 2