MayaFlux 0.2.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
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.point->set_in_network(true);
32 particle.mass = 1.0F;
33 particle.velocity = glm::vec3(0.0F);
34 particle.acceleration = glm::vec3(0.0F);
35 particle.force = glm::vec3(0.0F);
36
37 m_particles.push_back(std::move(particle));
38 }
39
41 "Created ParticleNetwork with {} particles, bounds [{}, {}] to [{}, {}], mode={}",
42 num_particles,
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));
46}
47
48//-----------------------------------------------------------------------------
49// NodeNetwork Interface Implementation
50//-----------------------------------------------------------------------------
51
53{
55
58 } else if (m_topology == Topology::GRID_2D) {
59 auto grid_size = static_cast<size_t>(std::sqrt(m_particles.size()));
60 m_neighbor_map = build_grid_2d_neighbors(grid_size, grid_size);
61 } else if (m_topology == Topology::RING) {
63 } else if (m_topology == Topology::CHAIN) {
65 }
66
68 "Initialized ParticleNetwork: {} particles, topology={}",
69 m_particles.size(), static_cast<int>(m_topology));
70}
71
73{
74 for (auto& particle : m_particles) {
75 particle.velocity = glm::vec3(0.0F);
76 particle.acceleration = glm::vec3(0.0F);
77 particle.force = glm::vec3(0.0F);
78 particle.mass = 1.0F;
79 }
80
83}
84
85void ParticleNetwork::process_batch(unsigned int num_samples)
86{
88
89 if (!is_enabled()) {
90 return;
91 }
92
94
95 for (unsigned int frame = 0; frame < num_samples; ++frame) {
98 apply_drag();
99
102 }
103
106 }
107
110 }
111
113
115 "ParticleNetwork processed {} frames, {} particles",
116 num_samples, m_particles.size());
117}
118
119std::unordered_map<std::string, std::string>
121{
122 auto metadata = NodeNetwork::get_metadata();
123
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]() {
130 switch (m_bounds_mode) {
131 case BoundsMode::NONE:
132 return "NONE";
134 return "BOUNCE";
135 case BoundsMode::WRAP:
136 return "WRAP";
138 return "CLAMP";
140 return "DESTROY";
141 default:
142 return "UNKNOWN";
143 }
144 }();
145
146 glm::vec3 avg_velocity(0.0F);
147 for (const auto& p : m_particles) {
148 avg_velocity += p.velocity;
149 }
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);
153
155 metadata["interaction_radius"] = std::to_string(m_interaction_radius);
156 metadata["neighbor_map_size"] = std::to_string(m_neighbor_map.size());
157 }
158
159 return metadata;
160}
161
162std::optional<double> ParticleNetwork::get_node_output(size_t index) const
163{
164 if (index < m_particles.size()) {
165 return static_cast<double>(glm::length(m_particles[index].velocity));
166 }
167 return std::nullopt;
168}
169
170//-----------------------------------------------------------------------------
171// Parameter Mapping
172//-----------------------------------------------------------------------------
173
175{
176 for (const auto& mapping : m_parameter_mappings) {
177 if (mapping.mode == MappingMode::BROADCAST && mapping.broadcast_source) {
178 double value = mapping.broadcast_source->get_last_output();
179 apply_broadcast_parameter(mapping.param_name, value);
180 } else if (mapping.mode == MappingMode::ONE_TO_ONE && mapping.network_source) {
181 apply_one_to_one_parameter(mapping.param_name, mapping.network_source);
182 }
183 }
184}
185
186void ParticleNetwork::apply_broadcast_parameter(const std::string& param, double value)
187{
188 if (param == "gravity") {
189 m_gravity.y = static_cast<float>(value);
190 } else if (param == "drag") {
191 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
192 } else if (param == "turbulence") {
193 for (auto& particle : m_particles) {
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);
199 }
200 } else if (param == "attraction") {
201 m_attraction_strength = static_cast<float>(value);
202 } else if (param == "spring_stiffness") {
203 m_spring_stiffness = glm::max(0.0F, static_cast<float>(value));
204 } else if (param == "repulsion_strength") {
205 m_repulsion_strength = glm::max(0.0F, static_cast<float>(value));
206 } else if (param == "force_x") {
207 for (auto& particle : m_particles) {
208 particle.force.x += static_cast<float>(value);
209 }
210 } else if (param == "force_y") {
211 for (auto& particle : m_particles) {
212 particle.force.y += static_cast<float>(value);
213 }
214 } else if (param == "force_z") {
215 for (auto& particle : m_particles) {
216 particle.force.z += static_cast<float>(value);
217 }
218 }
219}
220
222 const std::string& param,
223 const std::shared_ptr<NodeNetwork>& source)
224{
225 if (source->get_node_count() != m_particles.size()) {
227 "Parameter mapping size mismatch: {} particles vs {} source nodes",
228 m_particles.size(), source->get_node_count());
229 return;
230 }
231
232 if (param == "force_x") {
233 for (size_t i = 0; i < m_particles.size(); ++i) {
234 auto val = source->get_node_output(i);
235 if (val) {
236 m_particles[i].force.x += static_cast<float>(*val);
237 }
238 }
239 } else if (param == "force_y") {
240 for (size_t i = 0; i < m_particles.size(); ++i) {
241 auto val = source->get_node_output(i);
242 if (val) {
243 m_particles[i].force.y += static_cast<float>(*val);
244 }
245 }
246 } else if (param == "force_z") {
247 for (size_t i = 0; i < m_particles.size(); ++i) {
248 auto val = source->get_node_output(i);
249 if (val) {
250 m_particles[i].force.z += static_cast<float>(*val);
251 }
252 }
253 } else if (param == "color") {
254 for (size_t i = 0; i < m_particles.size(); ++i) {
255 auto val = source->get_node_output(i);
256 if (val) {
257 float normalized = glm::clamp(static_cast<float>(*val), 0.0F, 1.0F);
258 glm::vec3 color(normalized, 0.5F, 1.0F - normalized);
259 m_particles[i].point->set_color(color);
260 }
261 }
262 } else if (param == "size") {
263 for (size_t i = 0; i < m_particles.size(); ++i) {
264 auto val = source->get_node_output(i);
265 if (val) {
266 float size = glm::clamp(static_cast<float>(*val) * 10.0F, 1.0F, 50.0F);
267 m_particles[i].point->set_size(size);
268 }
269 }
270 } else if (param == "mass") {
271 for (size_t i = 0; i < m_particles.size(); ++i) {
272 auto val = source->get_node_output(i);
273 if (val) {
274 m_particles[i].mass = std::max(0.1F, static_cast<float>(*val));
275 }
276 }
277 }
278}
279
281 const std::string& param_name,
282 const std::shared_ptr<Node>& source,
283 MappingMode mode)
284{
285 unmap_parameter(param_name);
286
287 ParameterMapping mapping;
288 mapping.param_name = param_name;
289 mapping.mode = mode;
290 mapping.broadcast_source = source;
291 mapping.network_source = nullptr;
292
293 m_parameter_mappings.push_back(std::move(mapping));
294
296 "Mapped parameter '{}' in BROADCAST mode", param_name);
297}
298
300 const std::string& param_name,
301 const std::shared_ptr<NodeNetwork>& source_network)
302{
303 unmap_parameter(param_name);
304
305 ParameterMapping mapping;
306 mapping.param_name = param_name;
308 mapping.broadcast_source = nullptr;
309 mapping.network_source = source_network;
310
311 m_parameter_mappings.push_back(std::move(mapping));
312
314 "Mapped parameter '{}' in ONE_TO_ONE mode ({} → {} particles)",
315 param_name, source_network->get_node_count(), m_particles.size());
316}
317
318void ParticleNetwork::unmap_parameter(const std::string& param_name)
319{
321 std::remove_if(m_parameter_mappings.begin(), m_parameter_mappings.end(),
322 [&](const auto& m) { return m.param_name == param_name; }),
324}
325
326//-----------------------------------------------------------------------------
327// Physics Simulation
328//-----------------------------------------------------------------------------
329
331{
332 for (auto& particle : m_particles) {
333 particle.force = glm::vec3(0.0F);
334 }
335}
336
338{
339 for (auto& particle : m_particles) {
340 particle.force += m_gravity * particle.mass;
341 }
342}
343
345{
346 for (auto& particle : m_particles) {
347 // F_drag = -k * v
348 particle.force -= particle.velocity * m_drag;
349 }
350}
351
353{
354 for (auto& particle : m_particles) {
355 glm::vec3 to_attractor = m_attraction_point - particle.point->get_position();
356 float distance = glm::length(to_attractor);
357
358 if (distance > 0.001F) {
359 glm::vec3 direction = to_attractor / distance;
360 float force_magnitude = m_attraction_strength / std::max(distance * distance, 0.1F);
361 particle.force += direction * force_magnitude * particle.mass;
362 }
363 }
364}
365
367{
371 }
372
373 for (const auto& [idx, neighbors] : m_neighbor_map) {
374 auto& particle_a = m_particles[idx];
375 glm::vec3 pos_a = particle_a.point->get_position();
376
377 for (size_t neighbor_idx : neighbors) {
378 auto& particle_b = m_particles[neighbor_idx];
379 glm::vec3 pos_b = particle_b.point->get_position();
380
381 glm::vec3 delta = pos_b - pos_a;
382 float distance = glm::length(delta);
383
384 if (distance > 0.001F) {
385 glm::vec3 direction = delta / distance;
386
387 float spring_force = m_spring_stiffness * (distance - m_interaction_radius);
388
389 float repulsion_force = 0.0F;
390 if (distance < m_interaction_radius * 0.5F) {
391 repulsion_force = m_repulsion_strength / (distance * distance);
392 }
393
394 glm::vec3 force = direction * (spring_force - repulsion_force);
395 particle_a.force += force;
396 }
397 }
398 }
400 for (const auto& [idx, neighbors] : m_neighbor_map) {
401 auto& particle_a = m_particles[idx];
402 glm::vec3 pos_a = particle_a.point->get_position();
403
404 for (size_t neighbor_idx : neighbors) {
405 auto& particle_b = m_particles[neighbor_idx];
406 glm::vec3 pos_b = particle_b.point->get_position();
407
408 glm::vec3 delta = pos_b - pos_a;
409 float distance = glm::length(delta);
410
411 if (distance > 0.001F) {
412 glm::vec3 direction = delta / distance;
413 float spring_force = m_spring_stiffness * (distance - 1.0F); // Rest length = 1.0
414 particle_a.force += direction * spring_force;
415 }
416 }
417 }
418 }
419}
420
422{
423 for (auto& particle : m_particles) {
424 // F = ma → a = F/m
425 particle.acceleration = particle.force / particle.mass;
426
427 // Semi-implicit Euler integration
428 particle.velocity += particle.acceleration * dt;
429
430 glm::vec3 new_position = particle.point->get_position() + particle.velocity * dt;
431 particle.point->set_position(new_position);
432 }
433}
434
436{
438 return;
439 }
440
441 for (auto& particle : m_particles) {
442 glm::vec3 pos = particle.point->get_position();
443 bool out_of_bounds = false;
444
445 for (int axis = 0; axis < 3; ++axis) {
446 if (pos[axis] < m_bounds_min[axis] || pos[axis] > m_bounds_max[axis]) {
447 out_of_bounds = true;
448
449 switch (m_bounds_mode) {
451 if (pos[axis] < m_bounds_min[axis]) {
452 pos[axis] = m_bounds_min[axis];
453 particle.velocity[axis] = -particle.velocity[axis] * 0.8F; // Energy loss
454 } else if (pos[axis] > m_bounds_max[axis]) {
455 pos[axis] = m_bounds_max[axis];
456 particle.velocity[axis] = -particle.velocity[axis] * 0.8F;
457 }
458 break;
459
460 case BoundsMode::WRAP:
461 if (pos[axis] < m_bounds_min[axis]) {
462 pos[axis] = m_bounds_max[axis];
463 } else if (pos[axis] > m_bounds_max[axis]) {
464 pos[axis] = m_bounds_min[axis];
465 }
466 break;
467
469 pos[axis] = glm::clamp(pos[axis], m_bounds_min[axis], m_bounds_max[axis]);
470 particle.velocity[axis] = 0.0F;
471 break;
472
475 particle.velocity = glm::vec3(0.0F);
476 break;
477
478 case BoundsMode::NONE:
479 break;
480 }
481 }
482 }
483
484 if (out_of_bounds) {
485 particle.point->set_position(pos);
486 }
487 }
488}
489
491{
492 for (auto& particle : m_particles) {
493 float speed = glm::length(particle.velocity);
494 float normalized_speed = glm::clamp(speed / 10.0F, 0.0F, 1.0F);
495
496 // Map speed to color: blue (slow) → red (fast)
497 glm::vec3 color(normalized_speed, 0.3F, 1.0F - normalized_speed);
498 particle.point->set_color(color);
499
500 particle.point->compute_frame();
501 }
502}
503
504//-----------------------------------------------------------------------------
505// Topology Management
506//-----------------------------------------------------------------------------
507
509{
510 m_neighbor_map.clear();
511
512 // Simple O(n²) spatial query - could be optimized with spatial hash
513 for (size_t i = 0; i < m_particles.size(); ++i) {
514 glm::vec3 pos_i = m_particles[i].point->get_position();
515
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);
519
520 if (distance < m_interaction_radius) {
521 m_neighbor_map[i].push_back(j);
522 m_neighbor_map[j].push_back(i);
523 }
524 }
525 }
526
527 m_neighbor_map_dirty = false;
528
530 "Rebuilt spatial neighbor map: {} particles with neighbors",
531 m_neighbor_map.size());
532}
533
534std::vector<size_t> ParticleNetwork::get_neighbors(size_t index) const
535{
536 auto it = m_neighbor_map.find(index);
537 if (it != m_neighbor_map.end()) {
538 return it->second;
539 }
540 return {};
541}
542
543//-----------------------------------------------------------------------------
544// Initialization Helpers
545//-----------------------------------------------------------------------------
546
548{
549 switch (mode) {
551 for (auto& particle : m_particles) {
552 particle.point->set_position(random_position_volume());
553 }
554 break;
555
557 for (auto& particle : m_particles) {
558 particle.point->set_position(random_position_surface());
559 }
560 break;
561
563 auto grid_size = static_cast<size_t>(std::cbrt(m_particles.size()));
564 glm::vec3 spacing = (m_bounds_max - m_bounds_min) / static_cast<float>(grid_size);
565
566 size_t idx = 0;
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;
571 m_particles[idx++].point->set_position(pos);
572 }
573 }
574 }
575 break;
576 }
577
579 glm::vec3 center = (m_bounds_min + m_bounds_max) * 0.5F;
580 float radius = glm::length(m_bounds_max - center);
581 for (auto& particle : m_particles) {
582 particle.point->set_position(center + random_position_sphere(radius));
583 }
584 break;
585 }
586
588 glm::vec3 center = (m_bounds_min + m_bounds_max) * 0.5F;
589 float radius = glm::length(m_bounds_max - center);
590 for (auto& particle : m_particles) {
591 particle.point->set_position(center + random_position_sphere_surface(radius));
592 }
593 break;
594 }
595
597 break;
598 }
599
601}
602
604{
605 return {
606 m_bounds_min.x + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.x - m_bounds_min.x),
607 m_bounds_min.y + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.y - m_bounds_min.y),
608 m_bounds_min.z + (static_cast<float>(rand()) / RAND_MAX) * (m_bounds_max.z - m_bounds_min.z)
609 };
610}
611
613{
614 int face = rand() % 6;
615 float u = static_cast<float>(rand()) / RAND_MAX;
616 float v = static_cast<float>(rand()) / RAND_MAX;
617
618 switch (face) {
619 case 0:
620 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) };
621 case 1:
622 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) };
623 case 2:
624 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) };
625 case 3:
626 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) };
627 case 4:
628 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 };
629 case 5:
630 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 };
631 default:
632 return m_bounds_min;
633 }
634}
635
636glm::vec3 ParticleNetwork::random_position_sphere(float radius) const
637{
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);
641
642 return {
643 r * std::sin(phi) * std::cos(theta),
644 r * std::sin(phi) * std::sin(theta),
645 r * std::cos(phi)
646 };
647}
648
650{
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);
653
654 return {
655 radius * std::sin(phi) * std::cos(theta),
656 radius * std::sin(phi) * std::sin(theta),
657 radius * std::cos(phi)
658 };
659}
660
661//-----------------------------------------------------------------------------
662// Particle Control
663//-----------------------------------------------------------------------------
664
665void ParticleNetwork::apply_global_impulse(const glm::vec3& impulse)
666{
667 for (auto& particle : m_particles) {
668 particle.velocity += impulse / particle.mass;
669 }
670}
671
672void ParticleNetwork::apply_impulse(size_t index, const glm::vec3& impulse)
673{
674 if (index < m_particles.size()) {
675 m_particles[index].velocity += impulse / m_particles[index].mass;
676 }
677}
678
683
685{
686 for (auto& particle : m_particles) {
687 particle.velocity = glm::vec3(0.0F);
688 particle.acceleration = glm::vec3(0.0F);
689 particle.force = glm::vec3(0.0F);
690 }
691}
692
693std::unordered_map<size_t, std::vector<size_t>>
694NodeNetwork::build_grid_2d_neighbors(size_t width, size_t height)
695{
696 std::unordered_map<size_t, std::vector<size_t>> neighbors;
697
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;
702
703 if (x > 0) {
704 node_neighbors.push_back(idx - 1);
705 }
706 if (x < width - 1) {
707 node_neighbors.push_back(idx + 1);
708 }
709 if (y > 0) {
710 node_neighbors.push_back(idx - width);
711 }
712 if (y < height - 1) {
713 node_neighbors.push_back(idx + width);
714 }
715
716 if (!node_neighbors.empty()) {
717 neighbors[idx] = std::move(node_neighbors);
718 }
719 }
720 }
721
722 return neighbors;
723}
724
725std::unordered_map<size_t, std::vector<size_t>>
726NodeNetwork::build_grid_3d_neighbors(size_t width, size_t height, size_t depth)
727{
728 std::unordered_map<size_t, std::vector<size_t>> neighbors;
729
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;
735
736 if (x > 0) {
737 node_neighbors.push_back(idx - 1);
738 }
739 if (x < width - 1) {
740 node_neighbors.push_back(idx + 1);
741 }
742
743 if (y > 0) {
744 node_neighbors.push_back(idx - width);
745 }
746 if (y < height - 1) {
747 node_neighbors.push_back(idx + width);
748 }
749
750 if (z > 0) {
751 node_neighbors.push_back(idx - (width * height));
752 }
753 if (z < depth - 1) {
754 node_neighbors.push_back(idx + (width * height));
755 }
756
757 if (!node_neighbors.empty()) {
758 neighbors[idx] = std::move(node_neighbors);
759 }
760 }
761 }
762 }
763
764 return neighbors;
765}
766
767std::unordered_map<size_t, std::vector<size_t>>
769{
770 std::unordered_map<size_t, std::vector<size_t>> neighbors;
771
772 for (size_t i = 0; i < count; ++i) {
773 std::vector<size_t> node_neighbors;
774
775 node_neighbors.push_back((i == 0) ? count - 1 : i - 1);
776
777 node_neighbors.push_back((i == count - 1) ? 0 : i + 1);
778
779 neighbors[i] = std::move(node_neighbors);
780 }
781
782 return neighbors;
783}
784
785std::unordered_map<size_t, std::vector<size_t>>
787{
788 std::unordered_map<size_t, std::vector<size_t>> neighbors;
789
790 for (size_t i = 0; i < count; ++i) {
791 std::vector<size_t> node_neighbors;
792
793 if (i > 0) {
794 node_neighbors.push_back(i - 1);
795 }
796
797 if (i < count - 1) {
798 node_neighbors.push_back(i + 1);
799 }
800
801 if (!node_neighbors.empty()) {
802 neighbors[i] = std::move(node_neighbors);
803 }
804 }
805
806 return neighbors;
807}
808
809} // namespace MayaFlux::Nodes
#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.
@ CUSTOM
User-provided initialization function.
void handle_bounds()
Handle boundary conditions.
void apply_one_to_one_parameter(const std::string &param, 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.
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::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_broadcast_parameter(const std::string &param, double value)
Apply broadcast parameter to all particles.
void apply_gravity()
Apply gravity to all particles.
void unmap_parameter(const std::string &param_name) override
Remove parameter mapping.
glm::vec3 random_position_sphere_surface(float radius) const
Random position on sphere surface.
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.
void apply_interaction_forces()
Apply interaction forces based on topology.
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.
@ DESTROY
Remove particle at boundary (respawn optional)
void reset() override
Reset network to initial state.
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.
std::unordered_map< std::string, std::string > get_metadata() const override
Get network metadata for debugging/visualization.
@ 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)
Definition Yantra.cpp:593
std::shared_ptr< GpuSync::PointNode > point
Actual GeometryWriterNode.