MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PhysicsOperator.cpp
Go to the documentation of this file.
1#include "PhysicsOperator.hpp"
2
4
6
9
11
17
18//-----------------------------------------------------------------------------
19// GraphicsOperator Interface (Basic Initialization)
20//-----------------------------------------------------------------------------
21
22void PhysicsOperator::initialize(const std::vector<PointVertex>& vertices)
23{
24 if (vertices.empty()) {
26 "Cannot initialize PhysicsOperator with zero vertices");
27 return;
28 }
29
30 m_collections.clear();
31 m_bond_root.clear();
32 m_accreted_mass.clear();
33 add_collection(vertices);
34
36 "PhysicsOperator initialized with {} points", vertices.size());
37}
38
39//-----------------------------------------------------------------------------
40// Advanced Initialization (Multiple Collections)
41//-----------------------------------------------------------------------------
42
44 const std::vector<std::vector<PointVertex>>& collections)
45{
46 for (const auto& collection : collections) {
47 add_collection(collection);
48 }
49
51 "PhysicsOperator initialized with {} collections",
52 collections.size());
53}
54
56 const std::vector<PointVertex>& vertices,
57 float mass_multiplier)
58{
59 if (vertices.empty()) {
61 "Cannot add collection with zero vertices");
62 return;
63 }
64
65 CollectionGroup group;
66 group.collection = std::make_shared<GpuSync::PointCollectionNode>();
67
68 group.physics_state.resize(vertices.size());
69
70 for (size_t i = 0; i < vertices.size(); ++i) {
71 group.physics_state[i] = PhysicsState {
72 .velocity = glm::vec3(0.0F),
73 .force = glm::vec3(0.0F),
74 .mass = mass_multiplier
75 };
76 }
77
78 group.collection->set_points(vertices);
79 group.collection->compute_frame();
80
81 uint32_t expected = 0;
82 while (!m_access_token.compare_exchange_weak(expected, 1,
83 std::memory_order_acquire, std::memory_order_relaxed)) {
84 if (m_shutdown.load(std::memory_order_relaxed))
85 return;
86 expected = 0;
87 }
88
89 m_collections.push_back(std::move(group));
90
91 m_access_token.store(0, std::memory_order_release);
92
94 "Added collection #{} with {} points (mass_mult={:.2f})",
95 m_collections.size(), vertices.size(), mass_multiplier);
96}
97
99{
100 if (get_vertex_count() > 0 || !upstream)
101 return;
102
103 const auto data = upstream->get_vertex_data();
104 if (data.empty())
105 return;
106
107 const size_t count = data.size() / sizeof(PointVertex);
108 if (count == 0)
109 return;
110
111 std::vector<PointVertex> vertices(count);
112 std::memcpy(vertices.data(), data.data(), data.size());
113 initialize(vertices);
114
116 "PhysicsOperator seeded {} vertices from upstream '{}'",
117 count, upstream->get_type_name());
118}
119
120//-----------------------------------------------------------------------------
121// Processing
122//-----------------------------------------------------------------------------
123
125{
126 uint32_t expected = 0;
127 while (!m_access_token.compare_exchange_weak(expected, 1,
128 std::memory_order_acquire, std::memory_order_relaxed)) {
129 if (m_shutdown.load(std::memory_order_relaxed))
130 return;
131 expected = 0;
132 }
133
134 if (m_collections.empty()) {
135 m_access_token.store(0, std::memory_order_release);
136 return;
137 }
138
139 const float effective_dt = m_force_internal_dt ? m_internal_dt : dt;
140 apply_forces();
141 integrate(effective_dt);
144
145 for (auto& group : m_collections) {
146 group.collection->compute_frame();
147 }
148
149 m_access_token.store(0, std::memory_order_release);
150}
151
152//-----------------------------------------------------------------------------
153// Parameter System
154//-----------------------------------------------------------------------------
155
156std::optional<PhysicsParameter> PhysicsOperator::string_to_parameter(std::string_view param)
157{
158 return Reflect::string_to_enum_case_insensitive<PhysicsParameter>(param);
159}
160
161void PhysicsOperator::set_parameter(std::string_view param, double value)
162{
163 auto param_enum = string_to_parameter(param);
164
165 if (!param_enum) {
166 try {
167 Reflect::string_to_enum_or_throw_case_insensitive<PhysicsParameter>(
168 param, "PhysicsOperator parameter");
169 } catch (const std::invalid_argument& e) {
171 "{}", e.what());
172 }
174 "Unknown physics parameter: '{}'", param);
175 return;
176 }
177
178 switch (*param_enum) {
180 m_gravity.x = static_cast<float>(value);
181 break;
183 m_gravity.y = static_cast<float>(value);
184 break;
186 m_gravity.z = static_cast<float>(value);
187 break;
189 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
190 break;
192 m_interaction_radius = static_cast<float>(value);
193 break;
195 m_spring_stiffness = static_cast<float>(value);
196 break;
198 m_repulsion_strength = static_cast<float>(value);
199 break;
202 break;
204 m_point_size = static_cast<float>(value);
205 for (auto& group : m_collections) {
206 auto& points = group.collection->get_points();
207 for (auto& pt : points) {
208 pt.size = m_point_size;
209 }
210 }
211 break;
213 m_attraction_strength = static_cast<float>(value);
214 break;
216 m_turbulence_strength = static_cast<float>(value);
217 break;
218 }
219}
220
221std::optional<double> PhysicsOperator::query_state(std::string_view query) const
222{
223 if (query == "point_count") {
224 return static_cast<double>(get_point_count());
225 }
226 if (query == "collection_count") {
227 return static_cast<double>(m_collections.size());
228 }
229 if (query == "avg_velocity") {
230 glm::vec3 avg(0.0F);
231 size_t total_points = 0;
232
233 for (const auto& group : m_collections) {
234 for (const auto& state : group.physics_state) {
235 avg += state.velocity;
236 ++total_points;
237 }
238 }
239
240 if (total_points > 0) {
241 avg /= static_cast<float>(total_points);
242 }
243 return static_cast<double>(glm::length(avg));
244 }
245
246 return std::nullopt;
247}
248
249//-----------------------------------------------------------------------------
250// GraphicsOperator Interface (Data Extraction)
251//-----------------------------------------------------------------------------
252
253std::vector<PointVertex> PhysicsOperator::extract_vertices() const
254{
255 std::vector<PointVertex> positions;
256
257 for (const auto& group : m_collections) {
258 const auto& points = group.collection->get_points();
259 for (const auto& pt : points) {
260 positions.push_back(pt);
261 }
262 }
263
264 return positions;
265}
266
267std::span<const uint8_t> PhysicsOperator::get_vertex_data_for_collection(uint32_t idx) const
268{
269 if (m_collections.empty() || idx >= m_collections.size()) {
270 return {};
271 }
272 return m_collections[idx].collection->get_vertex_data();
273}
274
275std::span<const uint8_t> PhysicsOperator::get_vertex_data() const
276{
278 for (const auto& group : m_collections) {
279 auto span = group.collection->get_vertex_data();
282 span.begin(), span.end());
283 }
284 return { m_vertex_data_aggregate.data(), m_vertex_data_aggregate.size() };
285}
286
287std::optional<double> PhysicsOperator::get_particle_velocity(size_t global_index) const
288{
289 size_t current_offset = 0;
290
291 for (const auto& group : m_collections) {
292 size_t group_size = group.physics_state.size();
293
294 if (global_index < current_offset + group_size) {
295 size_t local_index = global_index - current_offset;
296 return static_cast<double>(glm::length(group.physics_state[local_index].velocity));
297 }
298
299 current_offset += group_size;
300 }
301
302 return std::nullopt;
303}
304
306{
307 if (m_collections.empty()) {
308 return {};
309 }
310
311 auto layout_opt = m_collections[0].collection->get_vertex_layout();
312 if (!layout_opt.has_value()) {
313 return {};
314 }
315
316 auto layout = *layout_opt;
317 layout.vertex_count = static_cast<uint32_t>(get_vertex_count());
318 return layout;
319}
320
321std::optional<Portal::Graphics::PrimitiveTopology> PhysicsOperator::declared_topology() const
322{
323 if (m_collections.empty()) {
324 return std::nullopt;
325 }
326 return m_collections[0].collection->get_primitive_topology();
327}
328
329std::vector<DrawRun> PhysicsOperator::topology_runs() const
330{
331 std::vector<DrawRun> runs;
332 uint32_t offset = 0;
333
334 for (const auto& points : m_collections) {
335 const auto count = static_cast<uint32_t>(points.collection->get_vertex_count());
336 const auto topo = points.collection->get_primitive_topology();
337
338 if (!runs.empty() && runs.back().topology == topo && is_concatenable_topology(topo)) {
339 runs.back().vertex_count += count;
340 } else {
341 runs.push_back(DrawRun { .topology = topo, .vertex_offset = offset, .vertex_count = count });
342 }
343
344 offset += count;
345 }
346
347 return runs;
348}
349
351{
352 size_t total = 0;
353 for (const auto& group : m_collections) {
354 total += group.collection->get_vertex_count();
355 }
356 return total;
357}
358
360{
361 return std::ranges::any_of(
363 [](const auto& group) { return group.collection->needs_gpu_update(); });
364}
365
367{
368 for (auto& group : m_collections) {
369 group.collection->clear_gpu_update_flag();
370 }
371}
372
374{
375 size_t total = 0;
376 for (const auto& group : m_collections) {
377 total += group.collection->get_point_count();
378 }
379 return total;
380}
381
382void PhysicsOperator::set_bounds(const glm::vec3& min, const glm::vec3& max)
383{
384 m_bounds.min = min;
385 m_bounds.max = max;
386}
387
388void PhysicsOperator::set_attraction_point(const glm::vec3& point)
389{
390 m_attraction_point = point;
392}
393
394//-----------------------------------------------------------------------------
395// ONE_TO_ONE Parameter Mapping
396//-----------------------------------------------------------------------------
397
399 std::string_view param,
400 const std::shared_ptr<NodeNetwork>& source)
401{
402 size_t point_count = get_point_count();
403
404 if (source->get_node_count() != point_count) {
406 "ONE_TO_ONE size mismatch: {} particles vs {} source nodes",
407 point_count, source->get_node_count());
408 return;
409 }
410
411 if (param == "force_x" || param == "force_y" || param == "force_z") {
412 apply_per_particle_force(param, source);
413 } else if (param == "mass") {
415 } else {
417 }
418}
419
421 std::string_view param,
422 const std::shared_ptr<NodeNetwork>& source)
423{
424 size_t global_index = 0;
425
426 for (auto& group : m_collections) {
427 for (auto& i : group.physics_state) {
428 auto val = source->get_node_output(global_index++);
429 if (!val)
430 continue;
431
432 auto force = static_cast<float>(*val);
433
434 if (param == "force_x") {
435 i.force.x += force;
436 } else if (param == "force_y") {
437 i.force.y += force;
438 } else if (param == "force_z") {
439 i.force.z += force;
440 }
441 }
442 }
443}
444
446 const std::shared_ptr<NodeNetwork>& source)
447{
448 size_t global_index = 0;
449
450 for (auto& group : m_collections) {
451 for (auto& i : group.physics_state) {
452 auto val = source->get_node_output(global_index++);
453 if (!val)
454 continue;
455
456 i.mass = std::max(0.1F, static_cast<float>(*val));
457 }
458 }
459}
460
461//-----------------------------------------------------------------------------
462// Physics Simulation
463//-----------------------------------------------------------------------------
464
466{
467 for (auto& group : m_collections) {
468 for (auto& state : group.physics_state) {
469 state.force += m_gravity * state.mass;
470 }
471 }
472
475 }
476
477 if (m_turbulence_strength > 0.001F) {
479 }
480
483 }
484
485 if (!m_bond_root.empty()) {
487 }
488
489 if (!m_force_fields.empty()) {
490 for (auto& group : m_collections) {
491 auto& points = group.collection->get_points();
492
493 for (size_t i = 0; i < points.size(); ++i) {
494 for (const auto& field : m_force_fields) {
495 group.physics_state[i].force += field(points[i].position);
496 }
497 }
498 }
499 }
500}
501
503{
505
506 for (auto& group : m_collections) {
507 for (auto& state : group.physics_state) {
508 state.force += field(glm::vec3(0.0F));
509 }
510 }
511}
512
514{
515 for (size_t g1 = 0; g1 < m_collections.size(); ++g1) {
516 auto& group1 = m_collections[g1];
517 auto& points1 = group1.collection->get_points();
518
519 for (size_t i = 0; i < points1.size(); ++i) {
520 const auto& pos_i = points1[i].position;
521 auto& state_i = group1.physics_state[i];
522
523 for (size_t g2 = g1; g2 < m_collections.size(); ++g2) {
524 auto& group2 = m_collections[g2];
525 auto& points2 = group2.collection->get_points();
526
527 size_t start_j = (g1 == g2) ? i + 1 : 0;
528
529 for (size_t j = start_j; j < points2.size(); ++j) {
530 const auto& pos_j = points2[j].position;
531 auto& state_j = group2.physics_state[j];
532
533 glm::vec3 delta = pos_j - pos_i;
534 float distance = glm::length(delta);
535
536 if (distance < m_interaction_radius && distance > 0.001F) {
537 glm::vec3 direction = delta / distance;
538
539 float spring_force = m_spring_stiffness * (distance - m_interaction_radius * 0.5F);
540
541 float repulsion_force = 0.0F;
542 if (distance < m_interaction_radius * 0.3F) {
543 repulsion_force = m_repulsion_strength / (distance * distance);
544 }
545
546 glm::vec3 force = direction * (spring_force - repulsion_force);
547
548 state_i.force += force;
549 state_j.force -= force;
550 }
551 }
552 }
553 }
554 }
555}
556
557std::optional<PhysicsOperator::GroupIndex> PhysicsOperator::resolve_global_index(size_t global_index) const
558{
559 size_t offset = 0;
560 for (size_t g = 0; g < m_collections.size(); ++g) {
561 size_t count = m_collections[g].collection->get_point_count();
562 if (global_index < offset + count) {
563 return GroupIndex { .group = g, .local = global_index - offset };
564 }
565 offset += count;
566 }
567 return std::nullopt;
568}
569
570/**
571 * @brief Pull each bonded particle toward its root with a spring.
572 *
573 * Rest length scales with cbrt(root's accreted mass): a grown body's
574 * satellites settle at a wider spread than a fresh one's, the same way any
575 * fixed-density accumulation of matter occupies more volume as it gains
576 * mass. Growth is a wider swarm of small particles, not a bigger point.
577 */
579{
580 for (size_t i = 0; i < m_bond_root.size(); ++i) {
581 uint32_t root = m_bond_root[i];
582 if (root == i) {
583 continue;
584 }
585
586 auto self_idx = resolve_global_index(i);
587 auto root_idx = resolve_global_index(root);
588 if (!self_idx || !root_idx) {
589 continue;
590 }
591
592 auto& self_state = m_collections[self_idx->group].physics_state[self_idx->local];
593 const auto& self_pos = m_collections[self_idx->group].collection->get_points()[self_idx->local].position;
594 const auto& root_pos = m_collections[root_idx->group].collection->get_points()[root_idx->local].position;
595
596 glm::vec3 delta = root_pos - self_pos;
597 float distance = glm::length(delta);
598 if (distance < 0.001F) {
599 continue;
600 }
601
602 const float rest_length = m_bond_rest_length * std::cbrt(get_accreted_mass(root));
603
604 glm::vec3 direction = delta / distance;
605 self_state.force += direction * (m_bond_stiffness * (distance - rest_length));
606 }
607}
608
609void PhysicsOperator::sync_bonds_from_claims(std::span<const uint32_t> claimed_by)
610{
611 if (!m_bonds_enabled) {
612 return;
613 }
614
615 if (m_accreted_mass.size() != claimed_by.size()) {
616 m_accreted_mass.assign(claimed_by.size(), 1.0F);
617 }
618
619 for (size_t i = 0; i < claimed_by.size(); ++i) {
620 uint32_t root = claimed_by[i];
621 if (root == i || root >= m_accreted_mass.size()) {
622 continue;
623 }
625 m_accreted_mass[i] = 0.0F;
626 }
627
628 m_bond_root.assign(claimed_by.begin(), claimed_by.end());
629}
630
632{
633 m_bond_root.clear();
634}
635
637{
638 m_bonds_enabled = enable;
639 if (!enable) {
640 clear_bonds();
641 }
642}
643
644float PhysicsOperator::get_accreted_mass(size_t global_index) const
645{
646 if (global_index >= m_accreted_mass.size()) {
647 return 1.0F;
648 }
649 return m_accreted_mass[global_index];
650}
651
653{
654 const size_t count = get_point_count();
655 if (count > 0 && m_accreted_mass.size() != count) {
656 m_accreted_mass.assign(count, 1.0F);
657 }
658 return m_accreted_mass;
659}
660
662{
663 if (m_accreted_mass.empty()) {
664 float count = 0.0F;
665 for (const auto& group : m_collections) {
666 count += static_cast<float>(group.collection->get_point_count());
667 }
668 return count;
669 }
670
671 float total = 0.0F;
672 for (float mass : m_accreted_mass) {
673 total += mass;
674 }
675 return total;
676}
677
679{
680 size_t count = 0;
681 for (size_t i = 0; i < m_bond_root.size(); ++i) {
682 if (m_bond_root[i] != i) {
683 ++count;
684 }
685 }
686 return count;
687}
688
690{
692
693 for (auto& group : m_collections) {
694 auto& points = group.collection->get_points();
695
696 for (size_t i = 0; i < points.size(); ++i) {
697 group.physics_state[i].force += field(points[i].position) * group.physics_state[i].mass;
698 }
699 }
700}
701
703{
704 for (auto& group : m_collections) {
705 auto& points = group.collection->get_points();
706
707 for (size_t i = 0; i < points.size(); ++i) {
708 auto& state = group.physics_state[i];
709 auto& vertex = points[i];
710
711 glm::vec3 acceleration = state.force / state.mass;
712 state.velocity += acceleration * dt;
713 state.velocity *= (1.0F - m_drag);
714 vertex.position += state.velocity * dt;
715
716 state.force = glm::vec3(0.0F);
717 }
718 }
719}
720
722{
724 return;
725 }
726
727 constexpr float damping = 0.8F;
728
729 for (auto& group : m_collections) {
730 auto& points = group.collection->get_points();
731
732 for (size_t i = 0; i < points.size(); ++i) {
733 auto& vertex = points[i];
734 auto& state = group.physics_state[i];
735
736 for (int axis = 0; axis < 3; ++axis) {
737 if (vertex.position[axis] < m_bounds.min[axis]) {
738 switch (m_bounds_mode) {
740 vertex.position[axis] = m_bounds.min[axis];
741 state.velocity[axis] *= -damping;
742 break;
743 case BoundsMode::WRAP:
744 vertex.position[axis] = m_bounds.max[axis];
745 break;
747 vertex.position[axis] = m_bounds.min[axis];
748 state.velocity[axis] = 0.0F;
749 break;
750 case BoundsMode::NONE:
751 break;
752 }
753 } else if (vertex.position[axis] > m_bounds.max[axis]) {
754 switch (m_bounds_mode) {
756 vertex.position[axis] = m_bounds.max[axis];
757 state.velocity[axis] *= -damping;
758 break;
759 case BoundsMode::WRAP:
760 vertex.position[axis] = m_bounds.min[axis];
761 break;
763 vertex.position[axis] = m_bounds.max[axis];
764 state.velocity[axis] = 0.0F;
765 break;
766 case BoundsMode::NONE:
767 break;
768 }
769 }
770 }
771 }
772 }
773}
774
776{
777 for (auto& group : m_collections) {
778 group.collection->mark_vertex_data_dirty(true);
779 }
780}
781
782void* PhysicsOperator::get_data_at(size_t global_index)
783{
784 size_t offset = 0;
785 for (auto& group : m_collections) {
786 if (global_index < offset + group.collection->get_point_count()) {
787 size_t local_index = global_index - offset;
788 return &group.collection->get_points()[local_index];
789 }
790 offset += group.collection->get_point_count();
791 }
792 return nullptr;
793}
794
795void PhysicsOperator::apply_global_impulse(const glm::vec3& impulse)
796{
797 for (auto& group : m_collections) {
798 for (auto& state : group.physics_state) {
799 state.velocity += impulse / state.mass;
800 }
801 }
802}
803
804void PhysicsOperator::apply_impulse(size_t index, const glm::vec3& impulse)
805{
806 size_t offset = 0;
807 for (auto& group : m_collections) {
808 if (index < offset + group.collection->get_point_count()) {
809 size_t local_index = index - offset;
810 group.physics_state[local_index].velocity += impulse / group.physics_state[local_index].mass;
811 return;
812 }
813 offset += group.collection->get_point_count();
814 }
815}
816
817/**
818 * @brief Reads m_accreted_mass directly rather than calling
819 * get_accreted_mass_span(), which is non-const (it lazily seeds
820 * m_accreted_mass on first call): this override must stay const,
821 * so that method's own default of 1.0F per particle before the
822 * first sync_bonds_from_claims is replicated here instead.
823 */
824std::vector<std::pair<std::string, Kakshya::DataVariant>>
826{
827 std::vector<float> mass;
828 mass.reserve(get_point_count());
829 for (const auto& group : m_collections) {
830 for (const auto& state : group.physics_state) {
831 mass.push_back(state.mass);
832 }
833 }
834
835 std::vector<std::pair<std::string, Kakshya::DataVariant>> attributes;
836 attributes.emplace_back("mass", std::move(mass));
837
838 std::vector<float> accreted(m_accreted_mass.empty() ? get_point_count() : m_accreted_mass.size(), 1.0F);
839 if (!m_accreted_mass.empty()) {
840 std::copy(m_accreted_mass.begin(), m_accreted_mass.end(), accreted.begin());
841 }
842 attributes.emplace_back("accreted_mass", std::move(accreted));
843
844 return attributes;
845}
846
847std::vector<glm::vec3> PhysicsOperator::extract_vertex_velocities() const
848{
849 std::vector<glm::vec3> velocities;
850 velocities.reserve(get_point_count());
851 for (const auto& group : m_collections) {
852 for (const auto& state : group.physics_state) {
853 velocities.push_back(state.velocity);
854 }
855 }
856 return velocities;
857}
858
859std::vector<uint32_t> PhysicsOperator::build_cluster_ids() const
860{
861 std::vector<uint32_t> ids(get_vertex_count(), 0U);
862
863 if (m_collections.size() <= 1) {
864 return ids;
865 }
866
867 size_t offset = 0;
868 uint32_t cluster = 0;
869 for (const auto& group : m_collections) {
870 const size_t count = group.collection->get_vertex_count();
871 for (size_t i = 0; i < count && offset + i < ids.size(); ++i) {
872 ids[offset + i] = cluster;
873 }
874 offset += count;
875 ++cluster;
876 }
877
878 return ids;
879}
880
882{
883 m_force_fields.push_back(std::move(field));
884
886 "Added force field #{}", m_force_fields.size());
887}
888
896
897} // namespace MayaFlux::Nodes::Network
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
uint64_t offset
Definition Composite.cpp:10
std::optional< glm::vec3 > position
Definition Decoder.cpp:93
std::vector< glm::vec2 > * points
uint32_t total
std::vector< uint64_t > ids
std::vector< glm::vec3 > positions
std::vector< SpatialAttribute > attributes
std::vector< glm::vec3 > velocities
glm::ivec3 min
glm::ivec3 max
glm::vec3 value
uint32_t index
Definition VKDevice.cpp:122
size_t count
void apply_one_to_one(std::string_view param, const std::shared_ptr< NodeNetwork > &source) override
Apply ONE_TO_ONE parameter mapping.
virtual std::span< const uint8_t > get_vertex_data() const =0
Get vertex data for GPU upload.
Operator that produces GPU-renderable geometry.
virtual std::string_view get_type_name() const =0
Type name for introspection.
size_t bond_count() const
Number of particles currently bonded to a root other than themselves.
std::vector< PointVertex > extract_vertices() const
Extract current vertex data as PointVertex array.
size_t get_vertex_count() const override
Get number of vertices (may differ from point count for topology/path)
void apply_impulse(size_t index, const glm::vec3 &impulse)
Apply impulse to specific particle.
std::span< const float > get_accreted_mass_span()
Every particle's currently accreted mass, in global index order.
std::optional< GroupIndex > resolve_global_index(size_t global_index) const
Resolve a global index the same way apply_impulse/get_data_at do.
size_t get_point_count() const override
Get source point count (before topology expansion)
std::vector< CollectionGroup > m_collections
std::vector< glm::vec3 > extract_vertex_velocities() const override
Per-particle PhysicsState::velocity, in the same global vertex-index order get_vertex_data() produces...
void enable_bonds(bool enable)
Enable or disable adopting bonds from sync_bonds_from_claims.
Kinesis::Stochastic::Stochastic m_random_generator
void mark_vertex_data_clean() override
Clear dirty flag after GPU upload.
std::vector< DrawRun > topology_runs() const override
Coalesces adjacent point collections sharing a concatenable topology into one run.
void apply_per_particle_force(std::string_view param, const std::shared_ptr< NodeNetwork > &source)
std::optional< double > get_particle_velocity(size_t global_index) const
Get velocity magnitude for specific particle.
Kakshya::VertexLayout get_vertex_layout() const override
Get vertex layout describing vertex structure.
void set_attraction_point(const glm::vec3 &point)
void sync_bonds_from_claims(std::span< const uint32_t > claimed_by)
Adopt this cycle's GPU claim/absorption clustering as bonds.
std::span< const uint8_t > get_vertex_data() const override
Get vertex data for GPU upload.
void initialize_collections(const std::vector< std::vector< PointVertex > > &collections)
Initialize multiple physics collections.
bool is_vertex_data_dirty() const override
Check if geometry changed this frame.
@ BOUNCE
Reflect off boundaries with damping.
void apply_per_particle_mass(const std::shared_ptr< NodeNetwork > &source)
void add_collection(const std::vector< PointVertex > &vertices, float mass_multiplier=1.0F)
Add a single physics collection.
void set_bounds(const glm::vec3 &min, const glm::vec3 &max)
Set the simulation bounds.
std::vector< Kinesis::VectorField > m_force_fields
std::vector< std::pair< std::string, Kakshya::DataVariant > > extract_vertex_attributes() const override
Adds "mass" (PhysicsState::mass) and "accreted_mass" (get_accreted_mass_span()) as named float attrib...
void * get_data_at(size_t global_index) override
Get mutable access to point at global index.
std::optional< double > query_state(std::string_view query) const override
Query operator internal state.
void process(float dt) override
Process for one batch cycle.
void seed_from_upstream(const GraphicsOperator *upstream) override
Seed physics state from upstream operator's vertex data.
std::vector< float > m_accreted_mass
Lazily seeded to 1.0 per particle; see sync_bonds_from_claims.
std::span< const uint8_t > get_vertex_data_for_collection(uint32_t idx) const override
Get vertex data for specific collection (if multiple)
void clear_force_fields()
Remove all external force fields.
std::vector< uint32_t > build_cluster_ids() const override
Per-particle collection index, global index order.
float get_accreted_mass(size_t global_index) const
This particle's currently accreted mass.
float get_total_mass() const
Sum of every particle's accreted mass.
void add_force_field(Kinesis::VectorField field)
Add an external force field evaluated per-particle per-frame.
void clear_bonds()
Drop every adopted bond.
void set_parameter(std::string_view param, double value) override
Set operator parameter.
void apply_one_to_one(std::string_view param, const std::shared_ptr< NodeNetwork > &source) override
Apply ONE_TO_ONE parameter for physics-specific properties.
void initialize(const std::vector< PointVertex > &vertices)
Initialize with a single physics collection.
std::vector< uint32_t > m_bond_root
Empty when no bonds are adopted; see sync_bonds_from_claims.
std::optional< Portal::Graphics::PrimitiveTopology > declared_topology() const override
Forwards to the first collection's own get_primitive_topology().
void apply_global_impulse(const glm::vec3 &impulse)
Apply impulse to all particles.
static std::optional< PhysicsParameter > string_to_parameter(std::string_view param)
void apply_bond_forces()
Pull each bonded particle toward its root with a spring.
void initialize()
Definition main.cpp:10
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
VectorField turbulence(float strength, Stochastic::Stochastic rng=Stochastic::Stochastic())
Uniform random force field using Stochastic infrastructure.
VectorField point_attractor(const glm::vec3 &anchor, float strength)
Radial attraction/repulsion toward an anchor point.
Kakshya::PointVertex PointVertex
Definition VertexSpec.hpp:8
uint32_t vertex_count
Total number of vertices in this buffer.
Complete description of vertex data layout in a buffer.
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22
std::shared_ptr< GpuSync::PointCollectionNode > collection
A global particle index resolved to its owning collection.
Physics-specific data parallel to PointVertex array.
One contiguous span of vertices sharing a primitive topology.