5#include "glm/gtc/constants.hpp"
15 const glm::vec3& bounds_min,
16 const glm::vec3& bounds_max,
18 : m_bounds_min(bounds_min)
19 , m_bounds_max(bounds_max)
20 , m_init_mode(init_mode)
27 for (
size_t i = 0; i < num_particles; ++i) {
30 particle.
point = std::make_shared<GpuSync::PointNode>();
31 particle.
point->set_in_network(
true);
35 particle.
force = glm::vec3(0.0F);
41 "Created ParticleNetwork with {} particles, bounds [{}, {}] to [{}, {}], mode={}",
43 bounds_min.x, bounds_min.y, bounds_min.z,
44 bounds_max.x, bounds_max.y, bounds_max.z,
45 static_cast<int>(init_mode));
59 auto grid_size =
static_cast<size_t>(std::sqrt(
m_particles.size()));
68 "Initialized ParticleNetwork: {} particles, topology={}",
75 particle.velocity = glm::vec3(0.0F);
76 particle.acceleration = glm::vec3(0.0F);
77 particle.force = glm::vec3(0.0F);
95 for (
unsigned int frame = 0; frame < num_samples; ++frame) {
115 "ParticleNetwork processed {} frames, {} particles",
119std::unordered_map<std::string, std::string>
124 metadata[
"particle_count"] = std::to_string(
m_particles.size());
125 metadata[
"gravity"] = std::format(
"({:.2f}, {:.2f}, {:.2f})",
127 metadata[
"drag"] = std::to_string(
m_drag);
128 metadata[
"timestep"] = std::to_string(
m_timestep);
129 metadata[
"bounds_mode"] = [
this]() {
146 glm::vec3 avg_velocity(0.0F);
148 avg_velocity += p.velocity;
150 avg_velocity /=
static_cast<float>(
m_particles.size());
151 metadata[
"avg_velocity"] = std::format(
"({:.2f}, {:.2f}, {:.2f})",
152 avg_velocity.x, avg_velocity.y, avg_velocity.z);
156 metadata[
"neighbor_map_size"] = std::to_string(
m_neighbor_map.size());
165 return static_cast<double>(glm::length(
m_particles[index].velocity));
178 double value = mapping.broadcast_source->get_last_output();
188 if (param ==
"gravity") {
190 }
else if (param ==
"drag") {
191 m_drag = glm::clamp(
static_cast<float>(value), 0.0F, 1.0F);
192 }
else if (param ==
"turbulence") {
194 glm::vec3 random_force(
195 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F,
196 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F,
197 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F);
198 particle.force += random_force *
static_cast<float>(value);
200 }
else if (param ==
"attraction") {
202 }
else if (param ==
"spring_stiffness") {
204 }
else if (param ==
"repulsion_strength") {
206 }
else if (param ==
"force_x") {
208 particle.force.x +=
static_cast<float>(value);
210 }
else if (param ==
"force_y") {
212 particle.force.y +=
static_cast<float>(value);
214 }
else if (param ==
"force_z") {
216 particle.force.z +=
static_cast<float>(value);
222 const std::string& param,
223 const std::shared_ptr<NodeNetwork>& source)
225 if (source->get_node_count() !=
m_particles.size()) {
227 "Parameter mapping size mismatch: {} particles vs {} source nodes",
232 if (param ==
"force_x") {
234 auto val = source->get_node_output(i);
236 m_particles[i].force.x +=
static_cast<float>(*val);
239 }
else if (param ==
"force_y") {
241 auto val = source->get_node_output(i);
243 m_particles[i].force.y +=
static_cast<float>(*val);
246 }
else if (param ==
"force_z") {
248 auto val = source->get_node_output(i);
250 m_particles[i].force.z +=
static_cast<float>(*val);
253 }
else if (param ==
"color") {
255 auto val = source->get_node_output(i);
257 float normalized = glm::clamp(
static_cast<float>(*val), 0.0F, 1.0F);
262 }
else if (param ==
"size") {
264 auto val = source->get_node_output(i);
266 float size = glm::clamp(
static_cast<float>(*val) * 10.0F, 1.0F, 50.0F);
270 }
else if (param ==
"mass") {
272 auto val = source->get_node_output(i);
274 m_particles[i].mass = std::max(0.1F,
static_cast<float>(*val));
281 const std::string& param_name,
282 const std::shared_ptr<Node>& source,
296 "Mapped parameter '{}' in BROADCAST mode", param_name);
300 const std::string& param_name,
301 const std::shared_ptr<NodeNetwork>& source_network)
314 "Mapped parameter '{}' in ONE_TO_ONE mode ({} → {} particles)",
315 param_name, source_network->get_node_count(),
m_particles.size());
322 [&](
const auto& m) { return m.param_name == param_name; }),
333 particle.force = glm::vec3(0.0F);
340 particle.force +=
m_gravity * particle.mass;
348 particle.force -= particle.velocity *
m_drag;
356 float distance = glm::length(to_attractor);
358 if (distance > 0.001F) {
359 glm::vec3 direction = to_attractor / distance;
361 particle.force += direction * force_magnitude * particle.mass;
375 glm::vec3 pos_a = particle_a.point->get_position();
377 for (
size_t neighbor_idx : neighbors) {
379 glm::vec3 pos_b = particle_b.point->get_position();
381 glm::vec3 delta = pos_b - pos_a;
382 float distance = glm::length(delta);
384 if (distance > 0.001F) {
385 glm::vec3 direction = delta / distance;
389 float repulsion_force = 0.0F;
394 glm::vec3 force = direction * (spring_force - repulsion_force);
395 particle_a.force += force;
402 glm::vec3 pos_a = particle_a.point->get_position();
404 for (
size_t neighbor_idx : neighbors) {
406 glm::vec3 pos_b = particle_b.point->get_position();
408 glm::vec3 delta = pos_b - pos_a;
409 float distance = glm::length(delta);
411 if (distance > 0.001F) {
412 glm::vec3 direction = delta / distance;
414 particle_a.force += direction * spring_force;
425 particle.acceleration = particle.force / particle.mass;
428 particle.velocity += particle.acceleration * dt;
430 glm::vec3 new_position = particle.point->get_position() + particle.velocity * dt;
431 particle.point->set_position(new_position);
442 glm::vec3 pos = particle.point->get_position();
443 bool out_of_bounds =
false;
445 for (
int axis = 0; axis < 3; ++axis) {
447 out_of_bounds =
true;
453 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
456 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
470 particle.velocity[axis] = 0.0F;
475 particle.velocity = glm::vec3(0.0F);
485 particle.point->set_position(pos);
493 float speed = glm::length(particle.velocity);
494 float normalized_speed = glm::clamp(speed / 10.0F, 0.0F, 1.0F);
497 glm::vec3 color(normalized_speed, 0.3F, 1.0F - normalized_speed);
498 particle.point->set_color(color);
500 particle.point->compute_frame();
514 glm::vec3 pos_i =
m_particles[i].point->get_position();
516 for (
size_t j = i + 1; j <
m_particles.size(); ++j) {
517 glm::vec3 pos_j =
m_particles[j].point->get_position();
518 float distance = glm::length(pos_j - pos_i);
530 "Rebuilt spatial neighbor map: {} particles with neighbors",
563 auto grid_size =
static_cast<size_t>(std::cbrt(
m_particles.size()));
567 for (
size_t x = 0; x < grid_size && idx <
m_particles.size(); ++x) {
568 for (
size_t y = 0; y < grid_size && idx <
m_particles.size(); ++y) {
569 for (
size_t z = 0; z < grid_size && idx <
m_particles.size(); ++z) {
570 glm::vec3 pos =
m_bounds_min + glm::vec3(x, y, z) * spacing;
614 int face = rand() % 6;
615 float u =
static_cast<float>(rand()) / RAND_MAX;
616 float v =
static_cast<float>(rand()) / RAND_MAX;
638 float theta = 2.0F * glm::pi<float>() * (
static_cast<float>(rand()) / RAND_MAX);
639 float phi = std::acos(2.0F * (
static_cast<float>(rand()) / RAND_MAX) - 1.0F);
640 float r = radius * std::cbrt(
static_cast<float>(rand()) / RAND_MAX);
643 r * std::sin(phi) * std::cos(theta),
644 r * std::sin(phi) * std::sin(theta),
651 float theta = 2.0F * glm::pi<float>() * (
static_cast<float>(rand()) / RAND_MAX);
652 float phi = std::acos(2.0F * (
static_cast<float>(rand()) / RAND_MAX) - 1.0F);
655 radius * std::sin(phi) * std::cos(theta),
656 radius * std::sin(phi) * std::sin(theta),
657 radius * std::cos(phi)
668 particle.velocity += impulse / particle.mass;
687 particle.velocity = glm::vec3(0.0F);
688 particle.acceleration = glm::vec3(0.0F);
689 particle.force = glm::vec3(0.0F);
693std::unordered_map<size_t, std::vector<size_t>>
696 std::unordered_map<size_t, std::vector<size_t>> neighbors;
698 for (
size_t y = 0; y < height; ++y) {
699 for (
size_t x = 0; x < width; ++x) {
700 size_t idx = y * width + x;
701 std::vector<size_t> node_neighbors;
704 node_neighbors.push_back(idx - 1);
707 node_neighbors.push_back(idx + 1);
710 node_neighbors.push_back(idx - width);
712 if (y < height - 1) {
713 node_neighbors.push_back(idx + width);
716 if (!node_neighbors.empty()) {
717 neighbors[idx] = std::move(node_neighbors);
725std::unordered_map<size_t, std::vector<size_t>>
728 std::unordered_map<size_t, std::vector<size_t>> neighbors;
730 for (
size_t z = 0; z < depth; ++z) {
731 for (
size_t y = 0; y < height; ++y) {
732 for (
size_t x = 0; x < width; ++x) {
733 size_t idx = z * (width * height) + y * width + x;
734 std::vector<size_t> node_neighbors;
737 node_neighbors.push_back(idx - 1);
740 node_neighbors.push_back(idx + 1);
744 node_neighbors.push_back(idx - width);
746 if (y < height - 1) {
747 node_neighbors.push_back(idx + width);
751 node_neighbors.push_back(idx - (width * height));
754 node_neighbors.push_back(idx + (width * height));
757 if (!node_neighbors.empty()) {
758 neighbors[idx] = std::move(node_neighbors);
767std::unordered_map<size_t, std::vector<size_t>>
770 std::unordered_map<size_t, std::vector<size_t>> neighbors;
772 for (
size_t i = 0; i < count; ++i) {
773 std::vector<size_t> node_neighbors;
775 node_neighbors.push_back((i == 0) ? count - 1 : i - 1);
777 node_neighbors.push_back((i == count - 1) ? 0 : i + 1);
779 neighbors[i] = std::move(node_neighbors);
785std::unordered_map<size_t, std::vector<size_t>>
788 std::unordered_map<size_t, std::vector<size_t>> neighbors;
790 for (
size_t i = 0; i < count; ++i) {
791 std::vector<size_t> node_neighbors;
794 node_neighbors.push_back(i - 1);
798 node_neighbors.push_back(i + 1);
801 if (!node_neighbors.empty()) {
802 neighbors[i] = std::move(node_neighbors);
#define MF_INFO(comp, ctx,...)
#define MF_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
static std::unordered_map< size_t, std::vector< size_t > > build_chain_neighbors(size_t count)
Build neighbor map for CHAIN topology.
std::vector< ParameterMapping > m_parameter_mappings
void set_topology(Topology topology)
Set the network's topology.
@ GRAPHICS_BIND
State available for visualization (read-only)
MappingMode
Defines how nodes map to external entities (e.g., audio channels, graphics objects)
@ ONE_TO_ONE
Node array/network → network nodes (must match count)
@ BROADCAST
One node → all network nodes.
bool is_enabled() const
Check if network is enabled.
static std::unordered_map< size_t, std::vector< size_t > > build_ring_neighbors(size_t count)
Build neighbor map for RING topology.
@ CHAIN
Linear sequence: node[i] → node[i+1].
@ GRID_2D
2D lattice with 4-connectivity
@ INDEPENDENT
No connections, nodes process independently.
@ GRID_3D
3D lattice with 6-connectivity
@ SPATIAL
Dynamic proximity-based (nodes within radius interact)
@ RING
Circular: last node connects to first.
static std::unordered_map< size_t, std::vector< size_t > > build_grid_3d_neighbors(size_t width, size_t height, size_t depth)
Build neighbor map for GRID_3D topology.
static std::unordered_map< size_t, std::vector< size_t > > build_grid_2d_neighbors(size_t width, size_t height)
Build neighbor map for GRID_2D topology.
void ensure_initialized()
Ensure initialize() is called exactly once.
virtual std::unordered_map< std::string, std::string > get_metadata() const
Get network metadata for debugging/visualization.
void set_output_mode(OutputMode mode)
Set the network's output routing mode.
std::optional< double > get_node_output(size_t index) const override
Get output of specific internal node (for ONE_TO_ONE mapping)
void reinitialize_positions(InitializationMode mode)
Reinitialize all particle positions.
void reset_velocities()
Reset all velocities to zero.
InitializationMode
Particle spawn distribution.
@ RANDOM_SURFACE
Random positions on bounds surface.
@ CUSTOM
User-provided initialization function.
@ RANDOM_VOLUME
Random positions in bounds volume.
@ GRID
Regular grid distribution.
@ SPHERE_VOLUME
Random in sphere.
@ SPHERE_SURFACE
Random on sphere surface.
void handle_bounds()
Handle boundary conditions.
void apply_one_to_one_parameter(const std::string ¶m, const std::shared_ptr< NodeNetwork > &source)
Apply one-to-one parameter from another network.
glm::vec3 random_position_surface() const
Random position on bounds surface.
float m_attraction_strength
std::vector< size_t > get_neighbors(size_t index) const
Get neighbors for a particle based on current topology.
void rebuild_spatial_neighbors()
Rebuild neighbor map for SPATIAL topology.
ParticleNetwork(size_t num_particles, const glm::vec3 &bounds_min=glm::vec3(-10.0F), const glm::vec3 &bounds_max=glm::vec3(10.0F), InitializationMode init_mode=InitializationMode::RANDOM_VOLUME)
Create particle network with spatial bounds.
std::vector< ParticleNode > m_particles
float m_repulsion_strength
std::unordered_map< size_t, std::vector< size_t > > m_neighbor_map
void apply_attraction_force()
Apply attraction point force.
void process_batch(unsigned int num_samples) override
Process the network for the given number of samples.
glm::vec3 random_position_sphere(float radius) const
Random position in sphere.
glm::vec3 random_position_volume() const
Random position in bounds volume.
void apply_drag()
Apply drag forces.
void apply_broadcast_parameter(const std::string ¶m, double value)
Apply broadcast parameter to all particles.
void apply_gravity()
Apply gravity to all particles.
void unmap_parameter(const std::string ¶m_name) override
Remove parameter mapping.
glm::vec3 random_position_sphere_surface(float radius) const
Random position on sphere surface.
float m_interaction_radius
void integrate(float dt)
Integrate forces → velocities → positions.
void initialize() override
Called once before first process_batch()
void apply_global_impulse(const glm::vec3 &impulse)
Apply impulse to all particles.
InitializationMode m_init_mode
void apply_interaction_forces()
Apply interaction forces based on topology.
bool m_has_attraction_point
void update_point_nodes()
Update PointNode states from physics.
void apply_impulse(size_t index, const glm::vec3 &impulse)
Apply impulse to specific particle.
void clear_forces()
Clear accumulated forces.
void initialize_particle_positions(InitializationMode mode)
Initialize particles based on mode.
void update_mapped_parameters()
Update mapped parameters before physics step.
@ BOUNCE
Reflect off boundaries.
@ NONE
No bounds checking.
@ DESTROY
Remove particle at boundary (respawn optional)
@ WRAP
Teleport to opposite side.
void reset() override
Reset network to initial state.
glm::vec3 m_attraction_point
void map_parameter(const std::string ¶m_name, const std::shared_ptr< Node > &source, MappingMode mode=MappingMode::BROADCAST) override
Map external node output to network parameter.
std::unordered_map< std::string, std::string > get_metadata() const override
Get network metadata for debugging/visualization.
bool m_neighbor_map_dirty
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
std::vector< double > normalized(const std::vector< double > &data, double target_peak)
Normalize single-channel data (non-destructive)
std::shared_ptr< Node > broadcast_source
std::shared_ptr< NodeNetwork > network_source
std::shared_ptr< GpuSync::PointNode > point
Actual GeometryWriterNode.
glm::vec3 velocity
Current velocity.
size_t index
Index in network.
glm::vec3 force
Accumulated force this frame.
glm::vec3 acceleration
Current acceleration.
Single particle with physics state.