MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ParticleNetwork.cpp
Go to the documentation of this file.
1#include "ParticleNetwork.hpp"
4
5#include "glm/gtc/constants.hpp"
6
7namespace MayaFlux::Nodes {
8
9//-----------------------------------------------------------------------------
10// Construction
11//-----------------------------------------------------------------------------
12
14 size_t num_particles,
15 const glm::vec3& bounds_min,
16 const glm::vec3& bounds_max,
17 InitializationMode init_mode)
18 : m_bounds_min(bounds_min)
19 , m_bounds_max(bounds_max)
20 , m_init_mode(init_mode)
21{
24
25 m_particles.reserve(num_particles);
26
27 for (size_t i = 0; i < num_particles; ++i) {
28 ParticleNode particle;
29 particle.index = i;
30 particle.point = std::make_shared<GpuSync::PointNode>();
31 particle.mass = 1.0F;
32 particle.velocity = glm::vec3(0.0F);
33 particle.acceleration = glm::vec3(0.0F);
34 particle.force = glm::vec3(0.0F);
35
36 m_particles.push_back(std::move(particle));
37 }
38
40 "Created ParticleNetwork with {} particles, bounds [{}, {}] to [{}, {}], mode={}",
41 num_particles,
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));
45}
46
47//-----------------------------------------------------------------------------
48// NodeNetwork Interface Implementation
49//-----------------------------------------------------------------------------
50
52{
54
57 } else if (m_topology == Topology::GRID_2D) {
58 auto grid_size = static_cast<size_t>(std::sqrt(m_particles.size()));
59 m_neighbor_map = build_grid_2d_neighbors(grid_size, grid_size);
60 } else if (m_topology == Topology::RING) {
62 } else if (m_topology == Topology::CHAIN) {
64 }
65
67 "Initialized ParticleNetwork: {} particles, topology={}",
68 m_particles.size(), static_cast<int>(m_topology));
69}
70
72{
73 for (auto& particle : m_particles) {
74 particle.velocity = glm::vec3(0.0F);
75 particle.acceleration = glm::vec3(0.0F);
76 particle.force = glm::vec3(0.0F);
77 particle.mass = 1.0F;
78 }
79
82}
83
84void ParticleNetwork::process_batch(unsigned int num_samples)
85{
87
88 if (!is_enabled()) {
89 return;
90 }
91
93
94 for (unsigned int frame = 0; frame < num_samples; ++frame) {
97 apply_drag();
98
101 }
102
105 }
106
109 }
110
112
114 "ParticleNetwork processed {} frames, {} particles",
115 num_samples, m_particles.size());
116}
117
118std::unordered_map<std::string, std::string>
120{
121 auto metadata = NodeNetwork::get_metadata();
122
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]() {
129 switch (m_bounds_mode) {
130 case BoundsMode::NONE:
131 return "NONE";
133 return "BOUNCE";
134 case BoundsMode::WRAP:
135 return "WRAP";
137 return "CLAMP";
139 return "DESTROY";
140 default:
141 return "UNKNOWN";
142 }
143 }();
144
145 glm::vec3 avg_velocity(0.0F);
146 for (const auto& p : m_particles) {
147 avg_velocity += p.velocity;
148 }
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);
152
154 metadata["interaction_radius"] = std::to_string(m_interaction_radius);
155 metadata["neighbor_map_size"] = std::to_string(m_neighbor_map.size());
156 }
157
158 return metadata;
159}
160
161std::optional<double> ParticleNetwork::get_node_output(size_t index) const
162{
163 if (index < m_particles.size()) {
164 return static_cast<double>(glm::length(m_particles[index].velocity));
165 }
166 return std::nullopt;
167}
168
169//-----------------------------------------------------------------------------
170// Parameter Mapping
171//-----------------------------------------------------------------------------
172
174{
175 for (const auto& mapping : m_parameter_mappings) {
176 if (mapping.mode == MappingMode::BROADCAST && mapping.broadcast_source) {
177 double value = mapping.broadcast_source->get_last_output();
178 apply_broadcast_parameter(mapping.param_name, value);
179 } else if (mapping.mode == MappingMode::ONE_TO_ONE && mapping.network_source) {
180 apply_one_to_one_parameter(mapping.param_name, mapping.network_source);
181 }
182 }
183}
184
185void ParticleNetwork::apply_broadcast_parameter(const std::string& param, double value)
186{
187 if (param == "gravity") {
188 m_gravity.y = static_cast<float>(value);
189 } else if (param == "drag") {
190 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
191 } else if (param == "turbulence") {
192 for (auto& particle : m_particles) {
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);
198 }
199 } else if (param == "attraction") {
200 m_attraction_strength = static_cast<float>(value);
201 }
202}
203
205 const std::string& param,
206 const std::shared_ptr<NodeNetwork>& source)
207{
208 if (source->get_node_count() != m_particles.size()) {
210 "Parameter mapping size mismatch: {} particles vs {} source nodes",
211 m_particles.size(), source->get_node_count());
212 return;
213 }
214
215 if (param == "force_x") {
216 for (size_t i = 0; i < m_particles.size(); ++i) {
217 auto val = source->get_node_output(i);
218 if (val) {
219 m_particles[i].force.x += static_cast<float>(*val);
220 }
221 }
222 } else if (param == "force_y") {
223 for (size_t i = 0; i < m_particles.size(); ++i) {
224 auto val = source->get_node_output(i);
225 if (val) {
226 m_particles[i].force.y += static_cast<float>(*val);
227 }
228 }
229 } else if (param == "force_z") {
230 for (size_t i = 0; i < m_particles.size(); ++i) {
231 auto val = source->get_node_output(i);
232 if (val) {
233 m_particles[i].force.z += static_cast<float>(*val);
234 }
235 }
236 } else if (param == "color") {
237 for (size_t i = 0; i < m_particles.size(); ++i) {
238 auto val = source->get_node_output(i);
239 if (val) {
240 float normalized = glm::clamp(static_cast<float>(*val), 0.0F, 1.0F);
241 glm::vec3 color(normalized, 0.5F, 1.0F - normalized);
242 m_particles[i].point->set_color(color);
243 }
244 }
245 } else if (param == "size") {
246 for (size_t i = 0; i < m_particles.size(); ++i) {
247 auto val = source->get_node_output(i);
248 if (val) {
249 float size = glm::clamp(static_cast<float>(*val) * 10.0F, 1.0F, 50.0F);
250 m_particles[i].point->set_size(size);
251 }
252 }
253 } else if (param == "mass") {
254 for (size_t i = 0; i < m_particles.size(); ++i) {
255 auto val = source->get_node_output(i);
256 if (val) {
257 m_particles[i].mass = std::max(0.1F, static_cast<float>(*val));
258 }
259 }
260 }
261}
262
264 const std::string& param_name,
265 const std::shared_ptr<Node>& source,
266 MappingMode mode)
267{
268 unmap_parameter(param_name);
269
270 ParameterMapping mapping;
271 mapping.param_name = param_name;
272 mapping.mode = mode;
273 mapping.broadcast_source = source;
274 mapping.network_source = nullptr;
275
276 m_parameter_mappings.push_back(std::move(mapping));
277
279 "Mapped parameter '{}' in BROADCAST mode", param_name);
280}
281
283 const std::string& param_name,
284 const std::shared_ptr<NodeNetwork>& source_network)
285{
286 unmap_parameter(param_name);
287
288 ParameterMapping mapping;
289 mapping.param_name = param_name;
291 mapping.broadcast_source = nullptr;
292 mapping.network_source = source_network;
293
294 m_parameter_mappings.push_back(std::move(mapping));
295
297 "Mapped parameter '{}' in ONE_TO_ONE mode ({} → {} particles)",
298 param_name, source_network->get_node_count(), m_particles.size());
299}
300
301void ParticleNetwork::unmap_parameter(const std::string& param_name)
302{
304 std::remove_if(m_parameter_mappings.begin(), m_parameter_mappings.end(),
305 [&](const auto& m) { return m.param_name == param_name; }),
307}
308
309//-----------------------------------------------------------------------------
310// Physics Simulation
311//-----------------------------------------------------------------------------
312
314{
315 for (auto& particle : m_particles) {
316 particle.force = glm::vec3(0.0F);
317 }
318}
319
321{
322 for (auto& particle : m_particles) {
323 particle.force += m_gravity * particle.mass;
324 }
325}
326
328{
329 for (auto& particle : m_particles) {
330 // F_drag = -k * v
331 particle.force -= particle.velocity * m_drag;
332 }
333}
334
336{
337 for (auto& particle : m_particles) {
338 glm::vec3 to_attractor = m_attraction_point - particle.point->get_position();
339 float distance = glm::length(to_attractor);
340
341 if (distance > 0.001F) {
342 glm::vec3 direction = to_attractor / distance;
343 float force_magnitude = m_attraction_strength / std::max(distance * distance, 0.1F);
344 particle.force += direction * force_magnitude * particle.mass;
345 }
346 }
347}
348
350{
354 }
355
356 for (const auto& [idx, neighbors] : m_neighbor_map) {
357 auto& particle_a = m_particles[idx];
358 glm::vec3 pos_a = particle_a.point->get_position();
359
360 for (size_t neighbor_idx : neighbors) {
361 auto& particle_b = m_particles[neighbor_idx];
362 glm::vec3 pos_b = particle_b.point->get_position();
363
364 glm::vec3 delta = pos_b - pos_a;
365 float distance = glm::length(delta);
366
367 if (distance > 0.001F) {
368 glm::vec3 direction = delta / distance;
369
370 float spring_force = m_spring_stiffness * (distance - m_interaction_radius);
371
372 float repulsion_force = 0.0F;
373 if (distance < m_interaction_radius * 0.5F) {
374 repulsion_force = m_repulsion_strength / (distance * distance);
375 }
376
377 glm::vec3 force = direction * (spring_force - repulsion_force);
378 particle_a.force += force;
379 }
380 }
381 }
383 for (const auto& [idx, neighbors] : m_neighbor_map) {
384 auto& particle_a = m_particles[idx];
385 glm::vec3 pos_a = particle_a.point->get_position();
386
387 for (size_t neighbor_idx : neighbors) {
388 auto& particle_b = m_particles[neighbor_idx];
389 glm::vec3 pos_b = particle_b.point->get_position();
390
391 glm::vec3 delta = pos_b - pos_a;
392 float distance = glm::length(delta);
393
394 if (distance > 0.001F) {
395 glm::vec3 direction = delta / distance;
396 float spring_force = m_spring_stiffness * (distance - 1.0F); // Rest length = 1.0
397 particle_a.force += direction * spring_force;
398 }
399 }
400 }
401 }
402}
403
405{
406 for (auto& particle : m_particles) {
407 // F = ma → a = F/m
408 particle.acceleration = particle.force / particle.mass;
409
410 // Semi-implicit Euler integration
411 particle.velocity += particle.acceleration * dt;
412
413 glm::vec3 new_position = particle.point->get_position() + particle.velocity * dt;
414 particle.point->set_position(new_position);
415 }
416}
417
419{
421 return;
422 }
423
424 for (auto& particle : m_particles) {
425 glm::vec3 pos = particle.point->get_position();
426 bool out_of_bounds = false;
427
428 for (int axis = 0; axis < 3; ++axis) {
429 if (pos[axis] < m_bounds_min[axis] || pos[axis] > m_bounds_max[axis]) {
430 out_of_bounds = true;
431
432 switch (m_bounds_mode) {
434 if (pos[axis] < m_bounds_min[axis]) {
435 pos[axis] = m_bounds_min[axis];
436 particle.velocity[axis] = -particle.velocity[axis] * 0.8F; // Energy loss
437 } else if (pos[axis] > m_bounds_max[axis]) {
438 pos[axis] = m_bounds_max[axis];
439 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
440 }
441 break;
442
443 case BoundsMode::WRAP:
444 if (pos[axis] < m_bounds_min[axis]) {
445 pos[axis] = m_bounds_max[axis];
446 } else if (pos[axis] > m_bounds_max[axis]) {
447 pos[axis] = m_bounds_min[axis];
448 }
449 break;
450
452 pos[axis] = glm::clamp(pos[axis], m_bounds_min[axis], m_bounds_max[axis]);
453 particle.velocity[axis] = 0.0F;
454 break;
455
458 particle.velocity = glm::vec3(0.0F);
459 break;
460
461 case BoundsMode::NONE:
462 break;
463 }
464 }
465 }
466
467 if (out_of_bounds) {
468 particle.point->set_position(pos);
469 }
470 }
471}
472
474{
475 for (auto& particle : m_particles) {
476 float speed = glm::length(particle.velocity);
477 float normalized_speed = glm::clamp(speed / 10.0F, 0.0F, 1.0F);
478
479 // Map speed to color: blue (slow) → red (fast)
480 glm::vec3 color(normalized_speed, 0.3F, 1.0F - normalized_speed);
481 particle.point->set_color(color);
482
483 particle.point->compute_frame();
484 }
485}
486
487//-----------------------------------------------------------------------------
488// Topology Management
489//-----------------------------------------------------------------------------
490
492{
493 m_neighbor_map.clear();
494
495 // Simple O(n²) spatial query - could be optimized with spatial hash
496 for (size_t i = 0; i < m_particles.size(); ++i) {
497 glm::vec3 pos_i = m_particles[i].point->get_position();
498
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);
502
503 if (distance < m_interaction_radius) {
504 m_neighbor_map[i].push_back(j);
505 m_neighbor_map[j].push_back(i);
506 }
507 }
508 }
509
510 m_neighbor_map_dirty = false;
511
513 "Rebuilt spatial neighbor map: {} particles with neighbors",
514 m_neighbor_map.size());
515}
516
517std::vector<size_t> ParticleNetwork::get_neighbors(size_t index) const
518{
519 auto it = m_neighbor_map.find(index);
520 if (it != m_neighbor_map.end()) {
521 return it->second;
522 }
523 return {};
524}
525
526//-----------------------------------------------------------------------------
527// Initialization Helpers
528//-----------------------------------------------------------------------------
529
531{
532 switch (mode) {
534 for (auto& particle : m_particles) {
535 particle.point->set_position(random_position_volume());
536 }
537 break;
538
540 for (auto& particle : m_particles) {
541 particle.point->set_position(random_position_surface());
542 }
543 break;
544
546 auto grid_size = static_cast<size_t>(std::cbrt(m_particles.size()));
547 glm::vec3 spacing = (m_bounds_max - m_bounds_min) / static_cast<float>(grid_size);
548
549 size_t idx = 0;
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;
554 m_particles[idx++].point->set_position(pos);
555 }
556 }
557 }
558 break;
559 }
560
562 glm::vec3 center = (m_bounds_min + m_bounds_max) * 0.5F;
563 float radius = glm::length(m_bounds_max - center);
564 for (auto& particle : m_particles) {
565 particle.point->set_position(center + random_position_sphere(radius));
566 }
567 break;
568 }
569
571 glm::vec3 center = (m_bounds_min + m_bounds_max) * 0.5F;
572 float radius = glm::length(m_bounds_max - center);
573 for (auto& particle : m_particles) {
574 particle.point->set_position(center + random_position_sphere_surface(radius));
575 }
576 break;
577 }
578
580 break;
581 }
582
584}
585
587{
588 return {
589 m_bounds_min.x + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.x - m_bounds_min.x),
590 m_bounds_min.y + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.y - m_bounds_min.y),
591 m_bounds_min.z + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.z - m_bounds_min.z)
592 };
593}
594
596{
597 int face = rand() % 6;
598 float u = static_cast<float>(rand()) / RAND_MAX;
599 float v = static_cast<float>(rand()) / RAND_MAX;
600
601 switch (face) {
602 case 0:
603 return { m_bounds_min.x, glm::mix(m_bounds_min.y, m_bounds_max.y, u), glm::mix(m_bounds_min.z, m_bounds_max.z, v) };
604 case 1:
605 return { m_bounds_max.x, glm::mix(m_bounds_min.y, m_bounds_max.y, u), glm::mix(m_bounds_min.z, m_bounds_max.z, v) };
606 case 2:
607 return { glm::mix(m_bounds_min.x, m_bounds_max.x, u), m_bounds_min.y, glm::mix(m_bounds_min.z, m_bounds_max.z, v) };
608 case 3:
609 return { glm::mix(m_bounds_min.x, m_bounds_max.x, u), m_bounds_max.y, glm::mix(m_bounds_min.z, m_bounds_max.z, v) };
610 case 4:
611 return { glm::mix(m_bounds_min.x, m_bounds_max.x, u), glm::mix(m_bounds_min.y, m_bounds_max.y, v), m_bounds_min.z };
612 case 5:
613 return { glm::mix(m_bounds_min.x, m_bounds_max.x, u), glm::mix(m_bounds_min.y, m_bounds_max.y, v), m_bounds_max.z };
614 default:
615 return m_bounds_min;
616 }
617}
618
619glm::vec3 ParticleNetwork::random_position_sphere(float radius) const
620{
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);
624
625 return {
626 r * std::sin(phi) * std::cos(theta),
627 r * std::sin(phi) * std::sin(theta),
628 r * std::cos(phi)
629 };
630}
631
633{
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);
636
637 return {
638 radius * std::sin(phi) * std::cos(theta),
639 radius * std::sin(phi) * std::sin(theta),
640 radius * std::cos(phi)
641 };
642}
643
644//-----------------------------------------------------------------------------
645// Particle Control
646//-----------------------------------------------------------------------------
647
648void ParticleNetwork::apply_global_impulse(const glm::vec3& impulse)
649{
650 for (auto& particle : m_particles) {
651 particle.velocity += impulse / particle.mass;
652 }
653}
654
655void ParticleNetwork::apply_impulse(size_t index, const glm::vec3& impulse)
656{
657 if (index < m_particles.size()) {
658 m_particles[index].velocity += impulse / m_particles[index].mass;
659 }
660}
661
666
668{
669 for (auto& particle : m_particles) {
670 particle.velocity = glm::vec3(0.0F);
671 particle.acceleration = glm::vec3(0.0F);
672 particle.force = glm::vec3(0.0F);
673 }
674}
675
676std::unordered_map<size_t, std::vector<size_t>>
677NodeNetwork::build_grid_2d_neighbors(size_t width, size_t height)
678{
679 std::unordered_map<size_t, std::vector<size_t>> neighbors;
680
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;
685
686 if (x > 0) {
687 node_neighbors.push_back(idx - 1);
688 }
689 if (x < width - 1) {
690 node_neighbors.push_back(idx + 1);
691 }
692 if (y > 0) {
693 node_neighbors.push_back(idx - width);
694 }
695 if (y < height - 1) {
696 node_neighbors.push_back(idx + width);
697 }
698
699 if (!node_neighbors.empty()) {
700 neighbors[idx] = std::move(node_neighbors);
701 }
702 }
703 }
704
705 return neighbors;
706}
707
708std::unordered_map<size_t, std::vector<size_t>>
709NodeNetwork::build_grid_3d_neighbors(size_t width, size_t height, size_t depth)
710{
711 std::unordered_map<size_t, std::vector<size_t>> neighbors;
712
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;
718
719 if (x > 0) {
720 node_neighbors.push_back(idx - 1);
721 }
722 if (x < width - 1) {
723 node_neighbors.push_back(idx + 1);
724 }
725
726 if (y > 0) {
727 node_neighbors.push_back(idx - width);
728 }
729 if (y < height - 1) {
730 node_neighbors.push_back(idx + width);
731 }
732
733 if (z > 0) {
734 node_neighbors.push_back(idx - (width * height));
735 }
736 if (z < depth - 1) {
737 node_neighbors.push_back(idx + (width * height));
738 }
739
740 if (!node_neighbors.empty()) {
741 neighbors[idx] = std::move(node_neighbors);
742 }
743 }
744 }
745 }
746
747 return neighbors;
748}
749
750std::unordered_map<size_t, std::vector<size_t>>
752{
753 std::unordered_map<size_t, std::vector<size_t>> neighbors;
754
755 for (size_t i = 0; i < count; ++i) {
756 std::vector<size_t> node_neighbors;
757
758 node_neighbors.push_back((i == 0) ? count - 1 : i - 1);
759
760 node_neighbors.push_back((i == count - 1) ? 0 : i + 1);
761
762 neighbors[i] = std::move(node_neighbors);
763 }
764
765 return neighbors;
766}
767
768std::unordered_map<size_t, std::vector<size_t>>
770{
771 std::unordered_map<size_t, std::vector<size_t>> neighbors;
772
773 for (size_t i = 0; i < count; ++i) {
774 std::vector<size_t> node_neighbors;
775
776 if (i > 0) {
777 node_neighbors.push_back(i - 1);
778 }
779
780 if (i < count - 1) {
781 node_neighbors.push_back(i + 1);
782 }
783
784 if (!node_neighbors.empty()) {
785 neighbors[i] = std::move(node_neighbors);
786 }
787 }
788
789 return neighbors;
790}
791
792} // namespace MayaFlux::Nodes
#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.
void initialize() override
Called once before first process_batch()
void map_parameter(const std::string &param_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 &param, const std::shared_ptr< NodeNetwork > &source)
Apply one-to-one parameter from another network.
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.
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.
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 &param, double value)
Apply broadcast parameter to all particles.
@ DESTROY
Remove particle at boundary (respawn optional)
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 &param_name) override
Remove parameter mapping.
void apply_global_impulse(const glm::vec3 &impulse)
Apply impulse to all particles.
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.
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.
Definition Chronie.hpp:5
std::vector< double > normalized(const std::vector< double > &data, double target_peak)
Normalize single-channel data (non-destructive)
Definition Yantra.cpp:593
std::shared_ptr< NodeNetwork > network_source
std::shared_ptr< GpuSync::PointNode > point
Actual GeometryWriterNode.
glm::vec3 force
Accumulated force this frame.
Single particle with physics state.