#pragma once struct GraphData; // forward declare struct ForceLayoutConfig { float repulsion = 500.0f; // repulsion strength between all nodes float attraction = 0.01f; // spring constant for edges float damping = 0.85f; // velocity decay per step float min_distance = 1.0f; // minimum distance (avoid division by zero) float theta = 0.5f; // Barnes-Hut threshold (0 = exact, 1 = fast) float gravity = 0.1f; // pull toward center (prevents drift) float max_velocity = 50.0f; // cap velocity per axis int iterations = 1; // steps per call }; // Perform one (or more) steps of force-directed layout. // Modifies node positions (x, y) and velocities (vx, vy) in-place. // Returns the total kinetic energy (sum of |v|^2). When energy < threshold, // layout has converged. float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config = {}); // Reset: randomize positions within [-spread, spread], zero velocities. void graph_force_layout_reset(GraphData& graph, float spread = 200.0f); // Preset layouts (non-iterative). Wrappers deprecados que delegan en // graph_layouts.h. Se mantienen por compat — usar graph::layout_circular / // graph::layout_grid directamente en codigo nuevo. void graph_layout_circular(GraphData& graph, float radius = 100.0f); void graph_layout_grid(GraphData& graph, float spacing = 20.0f); // Auto-pause helper. Pure: el caller mantiene `consecutive_low_frames` y se // encarga de incrementarlo / ponerlo a cero cada frame. // // Patron de uso tipico: // static int low = 0; // float energy = graph_force_layout_step(g, cfg); // float per_node = g.node_count > 0 ? energy / g.node_count : 0.0f; // if (per_node < threshold) low++; else low = 0; // if (graph_force_layout_should_pause(low, min_consecutive)) running = false; // // Devuelve `true` si la energia ha caido por debajo del umbral durante al // menos `min_consecutive` frames consecutivos. La firma toma `low_frames` // directamente (en lugar de manejar el contador internamente) para que la // funcion sea pura — facil de testear y sin estado oculto. bool graph_force_layout_should_pause(int consecutive_low_frames, int min_consecutive);