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>();
34 particle.
force = glm::vec3(0.0F);
40 "Created ParticleNetwork with {} particles, bounds [{}, {}] to [{}, {}], mode={}",
42 bounds_min.x, bounds_min.y, bounds_min.z,
43 bounds_max.x, bounds_max.y, bounds_max.z,
44 static_cast<int>(init_mode));
58 auto grid_size =
static_cast<size_t>(std::sqrt(
m_particles.size()));
67 "Initialized ParticleNetwork: {} particles, topology={}",
74 particle.velocity = glm::vec3(0.0F);
75 particle.acceleration = glm::vec3(0.0F);
76 particle.force = glm::vec3(0.0F);
94 for (
unsigned int frame = 0; frame < num_samples; ++frame) {
114 "ParticleNetwork processed {} frames, {} particles",
118std::unordered_map<std::string, std::string>
123 metadata[
"particle_count"] = std::to_string(
m_particles.size());
124 metadata[
"gravity"] = std::format(
"({:.2f}, {:.2f}, {:.2f})",
126 metadata[
"drag"] = std::to_string(
m_drag);
127 metadata[
"timestep"] = std::to_string(
m_timestep);
128 metadata[
"bounds_mode"] = [
this]() {
145 glm::vec3 avg_velocity(0.0F);
147 avg_velocity += p.velocity;
149 avg_velocity /=
static_cast<float>(
m_particles.size());
150 metadata[
"avg_velocity"] = std::format(
"({:.2f}, {:.2f}, {:.2f})",
151 avg_velocity.x, avg_velocity.y, avg_velocity.z);
155 metadata[
"neighbor_map_size"] = std::to_string(
m_neighbor_map.size());
164 return static_cast<double>(glm::length(
m_particles[index].velocity));
177 double value = mapping.broadcast_source->get_last_output();
187 if (param ==
"gravity") {
189 }
else if (param ==
"drag") {
190 m_drag = glm::clamp(
static_cast<float>(value), 0.0F, 1.0F);
191 }
else if (param ==
"turbulence") {
193 glm::vec3 random_force(
194 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F,
195 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F,
196 (
static_cast<float>(rand()) / RAND_MAX - 0.5F) * 2.0F);
197 particle.force += random_force *
static_cast<float>(value);
199 }
else if (param ==
"attraction") {
205 const std::string& param,
206 const std::shared_ptr<NodeNetwork>& source)
208 if (source->get_node_count() !=
m_particles.size()) {
210 "Parameter mapping size mismatch: {} particles vs {} source nodes",
215 if (param ==
"force_x") {
217 auto val = source->get_node_output(i);
219 m_particles[i].force.x +=
static_cast<float>(*val);
222 }
else if (param ==
"force_y") {
224 auto val = source->get_node_output(i);
226 m_particles[i].force.y +=
static_cast<float>(*val);
229 }
else if (param ==
"force_z") {
231 auto val = source->get_node_output(i);
233 m_particles[i].force.z +=
static_cast<float>(*val);
236 }
else if (param ==
"color") {
238 auto val = source->get_node_output(i);
240 float normalized = glm::clamp(
static_cast<float>(*val), 0.0F, 1.0F);
245 }
else if (param ==
"size") {
247 auto val = source->get_node_output(i);
249 float size = glm::clamp(
static_cast<float>(*val) * 10.0F, 1.0F, 50.0F);
253 }
else if (param ==
"mass") {
255 auto val = source->get_node_output(i);
257 m_particles[i].mass = std::max(0.1F,
static_cast<float>(*val));
264 const std::string& param_name,
265 const std::shared_ptr<Node>& source,
279 "Mapped parameter '{}' in BROADCAST mode", param_name);
283 const std::string& param_name,
284 const std::shared_ptr<NodeNetwork>& source_network)
297 "Mapped parameter '{}' in ONE_TO_ONE mode ({} → {} particles)",
298 param_name, source_network->get_node_count(),
m_particles.size());
305 [&](
const auto& m) { return m.param_name == param_name; }),
316 particle.force = glm::vec3(0.0F);
323 particle.force +=
m_gravity * particle.mass;
331 particle.force -= particle.velocity *
m_drag;
339 float distance = glm::length(to_attractor);
341 if (distance > 0.001F) {
342 glm::vec3 direction = to_attractor / distance;
344 particle.force += direction * force_magnitude * particle.mass;
358 glm::vec3 pos_a = particle_a.point->get_position();
360 for (
size_t neighbor_idx : neighbors) {
362 glm::vec3 pos_b = particle_b.point->get_position();
364 glm::vec3 delta = pos_b - pos_a;
365 float distance = glm::length(delta);
367 if (distance > 0.001F) {
368 glm::vec3 direction = delta / distance;
372 float repulsion_force = 0.0F;
377 glm::vec3 force = direction * (spring_force - repulsion_force);
378 particle_a.force += force;
385 glm::vec3 pos_a = particle_a.point->get_position();
387 for (
size_t neighbor_idx : neighbors) {
389 glm::vec3 pos_b = particle_b.point->get_position();
391 glm::vec3 delta = pos_b - pos_a;
392 float distance = glm::length(delta);
394 if (distance > 0.001F) {
395 glm::vec3 direction = delta / distance;
397 particle_a.force += direction * spring_force;
408 particle.acceleration = particle.force / particle.mass;
411 particle.velocity += particle.acceleration * dt;
413 glm::vec3 new_position = particle.point->get_position() + particle.velocity * dt;
414 particle.point->set_position(new_position);
425 glm::vec3 pos = particle.point->get_position();
426 bool out_of_bounds =
false;
428 for (
int axis = 0; axis < 3; ++axis) {
430 out_of_bounds =
true;
436 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
439 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
453 particle.velocity[axis] = 0.0F;
458 particle.velocity = glm::vec3(0.0F);
468 particle.point->set_position(pos);
476 float speed = glm::length(particle.velocity);
477 float normalized_speed = glm::clamp(speed / 10.0F, 0.0F, 1.0F);
480 glm::vec3 color(normalized_speed, 0.3F, 1.0F - normalized_speed);
481 particle.point->set_color(color);
483 particle.point->compute_frame();
497 glm::vec3 pos_i =
m_particles[i].point->get_position();
499 for (
size_t j = i + 1; j <
m_particles.size(); ++j) {
500 glm::vec3 pos_j =
m_particles[j].point->get_position();
501 float distance = glm::length(pos_j - pos_i);
513 "Rebuilt spatial neighbor map: {} particles with neighbors",
546 auto grid_size =
static_cast<size_t>(std::cbrt(
m_particles.size()));
550 for (
size_t x = 0; x < grid_size && idx <
m_particles.size(); ++x) {
551 for (
size_t y = 0; y < grid_size && idx <
m_particles.size(); ++y) {
552 for (
size_t z = 0; z < grid_size && idx <
m_particles.size(); ++z) {
553 glm::vec3 pos =
m_bounds_min + glm::vec3(x, y, z) * spacing;
597 int face = rand() % 6;
598 float u =
static_cast<float>(rand()) / RAND_MAX;
599 float v =
static_cast<float>(rand()) / RAND_MAX;
621 float theta = 2.0F * glm::pi<float>() * (
static_cast<float>(rand()) / RAND_MAX);
622 float phi = std::acos(2.0F * (
static_cast<float>(rand()) / RAND_MAX) - 1.0F);
623 float r = radius * std::cbrt(
static_cast<float>(rand()) / RAND_MAX);
626 r * std::sin(phi) * std::cos(theta),
627 r * std::sin(phi) * std::sin(theta),
634 float theta = 2.0F * glm::pi<float>() * (
static_cast<float>(rand()) / RAND_MAX);
635 float phi = std::acos(2.0F * (
static_cast<float>(rand()) / RAND_MAX) - 1.0F);
638 radius * std::sin(phi) * std::cos(theta),
639 radius * std::sin(phi) * std::sin(theta),
640 radius * std::cos(phi)
651 particle.velocity += impulse / particle.mass;
670 particle.velocity = glm::vec3(0.0F);
671 particle.acceleration = glm::vec3(0.0F);
672 particle.force = glm::vec3(0.0F);
676std::unordered_map<size_t, std::vector<size_t>>
679 std::unordered_map<size_t, std::vector<size_t>> neighbors;
681 for (
size_t y = 0; y < height; ++y) {
682 for (
size_t x = 0; x < width; ++x) {
683 size_t idx = y * width + x;
684 std::vector<size_t> node_neighbors;
687 node_neighbors.push_back(idx - 1);
690 node_neighbors.push_back(idx + 1);
693 node_neighbors.push_back(idx - width);
695 if (y < height - 1) {
696 node_neighbors.push_back(idx + width);
699 if (!node_neighbors.empty()) {
700 neighbors[idx] = std::move(node_neighbors);
708std::unordered_map<size_t, std::vector<size_t>>
711 std::unordered_map<size_t, std::vector<size_t>> neighbors;
713 for (
size_t z = 0; z < depth; ++z) {
714 for (
size_t y = 0; y < height; ++y) {
715 for (
size_t x = 0; x < width; ++x) {
716 size_t idx = z * (width * height) + y * width + x;
717 std::vector<size_t> node_neighbors;
720 node_neighbors.push_back(idx - 1);
723 node_neighbors.push_back(idx + 1);
727 node_neighbors.push_back(idx - width);
729 if (y < height - 1) {
730 node_neighbors.push_back(idx + width);
734 node_neighbors.push_back(idx - (width * height));
737 node_neighbors.push_back(idx + (width * height));
740 if (!node_neighbors.empty()) {
741 neighbors[idx] = std::move(node_neighbors);
750std::unordered_map<size_t, std::vector<size_t>>
753 std::unordered_map<size_t, std::vector<size_t>> neighbors;
755 for (
size_t i = 0; i < count; ++i) {
756 std::vector<size_t> node_neighbors;
758 node_neighbors.push_back((i == 0) ? count - 1 : i - 1);
760 node_neighbors.push_back((i == count - 1) ? 0 : i + 1);
762 neighbors[i] = std::move(node_neighbors);
768std::unordered_map<size_t, std::vector<size_t>>
771 std::unordered_map<size_t, std::vector<size_t>> neighbors;
773 for (
size_t i = 0; i < count; ++i) {
774 std::vector<size_t> node_neighbors;
777 node_neighbors.push_back(i - 1);
781 node_neighbors.push_back(i + 1);
784 if (!node_neighbors.empty()) {
785 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,...)
std::vector< ParameterMapping > m_parameter_mappings
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.
void set_topology(Topology topology)
Set the network's topology.
void ensure_initialized()
Ensure initialize() is called exactly once.
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.
static std::unordered_map< size_t, std::vector< size_t > > build_chain_neighbors(size_t count)
Build neighbor map for CHAIN 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.
virtual std::unordered_map< std::string, std::string > get_metadata() const
Get network metadata for debugging/visualization.
@ GRAPHICS_BIND
State available for visualization (read-only)
bool is_enabled() const
Check if network is enabled.
void set_output_mode(OutputMode mode)
Set the network's output routing mode.
static std::unordered_map< size_t, std::vector< size_t > > build_ring_neighbors(size_t count)
Build neighbor map for RING 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 handle_bounds()
Handle boundary conditions.
glm::vec3 random_position_sphere_surface(float radius) const
Random position on sphere surface.
void update_point_nodes()
Update PointNode states from physics.
void reset_velocities()
Reset all velocities to zero.
std::unordered_map< size_t, std::vector< size_t > > m_neighbor_map
void apply_drag()
Apply drag forces.
void apply_interaction_forces()
Apply interaction forces based on topology.
void apply_attraction_force()
Apply attraction point force.
glm::vec3 m_attraction_point
void initialize() override
Called once before first process_batch()
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.
void apply_one_to_one_parameter(const std::string ¶m, const std::shared_ptr< NodeNetwork > &source)
Apply one-to-one parameter from another network.
bool m_has_attraction_point
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.
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.
float m_attraction_strength
void initialize_particle_positions(InitializationMode mode)
Initialize particles based on mode.
glm::vec3 random_position_sphere(float radius) const
Random position in sphere.
void reset() override
Reset network to initial state.
float m_repulsion_strength
void integrate(float dt)
Integrate forces → velocities → positions.
void reinitialize_positions(InitializationMode mode)
Reinitialize all particle positions.
void update_mapped_parameters()
Update mapped parameters before physics step.
void apply_broadcast_parameter(const std::string ¶m, double value)
Apply broadcast parameter to all particles.
@ BOUNCE
Reflect off boundaries.
@ NONE
No bounds checking.
@ DESTROY
Remove particle at boundary (respawn optional)
@ WRAP
Teleport to opposite side.
InitializationMode m_init_mode
void clear_forces()
Clear accumulated forces.
void apply_impulse(size_t index, const glm::vec3 &impulse)
Apply impulse to specific particle.
void unmap_parameter(const std::string ¶m_name) override
Remove parameter mapping.
void apply_global_impulse(const glm::vec3 &impulse)
Apply impulse to all particles.
float m_interaction_radius
glm::vec3 random_position_surface() const
Random position on bounds surface.
std::unordered_map< std::string, std::string > get_metadata() const override
Get network metadata for debugging/visualization.
glm::vec3 random_position_volume() const
Random position in bounds volume.
bool m_neighbor_map_dirty
void apply_gravity()
Apply gravity to all particles.
std::vector< ParticleNode > m_particles
std::vector< size_t > get_neighbors(size_t index) const
Get neighbors for a particle based on current topology.
void process_batch(unsigned int num_samples) override
Process the network for the given number of samples.
std::optional< double > get_node_output(size_t index) const override
Get output of specific internal node (for ONE_TO_ONE mapping)
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
Contains the node-based computational processing system components.
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 force
Accumulated force this frame.
glm::vec3 acceleration
Current acceleration.
glm::vec3 velocity
Current velocity.
size_t index
Index in network.
Single particle with physics state.