MayaFlux 0.5.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 add_collection(vertices);
32
34 "PhysicsOperator initialized with {} points", vertices.size());
35}
36
37//-----------------------------------------------------------------------------
38// Advanced Initialization (Multiple Collections)
39//-----------------------------------------------------------------------------
40
42 const std::vector<std::vector<PointVertex>>& collections)
43{
44 for (const auto& collection : collections) {
45 add_collection(collection);
46 }
47
49 "PhysicsOperator initialized with {} collections",
50 collections.size());
51}
52
54 const std::vector<PointVertex>& vertices,
55 float mass_multiplier)
56{
57 if (vertices.empty()) {
59 "Cannot add collection with zero vertices");
60 return;
61 }
62
63 CollectionGroup group;
64 group.collection = std::make_shared<GpuSync::PointCollectionNode>();
65
66 group.physics_state.resize(vertices.size());
67
68 for (size_t i = 0; i < vertices.size(); ++i) {
69 group.physics_state[i] = PhysicsState {
70 .velocity = glm::vec3(0.0F),
71 .force = glm::vec3(0.0F),
72 .mass = mass_multiplier
73 };
74 }
75
76 group.collection->set_points(vertices);
77 group.collection->compute_frame();
78
79 uint32_t expected = 0;
80 while (!m_access_token.compare_exchange_weak(expected, 1,
81 std::memory_order_acquire, std::memory_order_relaxed)) {
82 if (m_shutdown.load(std::memory_order_relaxed))
83 return;
84 expected = 0;
85 }
86
87 m_collections.push_back(std::move(group));
88
89 m_access_token.store(0, std::memory_order_release);
90
92 "Added collection #{} with {} points (mass_mult={:.2f})",
93 m_collections.size(), vertices.size(), mass_multiplier);
94}
95
97{
98 if (get_vertex_count() > 0 || !upstream)
99 return;
100
101 const auto data = upstream->get_vertex_data();
102 if (data.empty())
103 return;
104
105 const size_t count = data.size() / sizeof(PointVertex);
106 if (count == 0)
107 return;
108
109 std::vector<PointVertex> vertices(count);
110 std::memcpy(vertices.data(), data.data(), data.size());
111 initialize(vertices);
112
114 "PhysicsOperator seeded {} vertices from upstream '{}'",
115 count, upstream->get_type_name());
116}
117
118//-----------------------------------------------------------------------------
119// Processing
120//-----------------------------------------------------------------------------
121
123{
124 uint32_t expected = 0;
125 while (!m_access_token.compare_exchange_weak(expected, 1,
126 std::memory_order_acquire, std::memory_order_relaxed)) {
127 if (m_shutdown.load(std::memory_order_relaxed))
128 return;
129 expected = 0;
130 }
131
132 if (m_collections.empty()) {
133 m_access_token.store(0, std::memory_order_release);
134 return;
135 }
136
137 const float effective_dt = m_force_internal_dt ? m_internal_dt : dt;
138 apply_forces();
139 integrate(effective_dt);
142
143 for (auto& group : m_collections) {
144 group.collection->compute_frame();
145 }
146
147 m_access_token.store(0, std::memory_order_release);
148}
149
150//-----------------------------------------------------------------------------
151// Parameter System
152//-----------------------------------------------------------------------------
153
154std::optional<PhysicsParameter> PhysicsOperator::string_to_parameter(std::string_view param)
155{
156 return Reflect::string_to_enum_case_insensitive<PhysicsParameter>(param);
157}
158
159void PhysicsOperator::set_parameter(std::string_view param, double value)
160{
161 auto param_enum = string_to_parameter(param);
162
163 if (!param_enum) {
164 try {
165 Reflect::string_to_enum_or_throw_case_insensitive<PhysicsParameter>(
166 param, "PhysicsOperator parameter");
167 } catch (const std::invalid_argument& e) {
169 "{}", e.what());
170 }
172 "Unknown physics parameter: '{}'", param);
173 return;
174 }
175
176 switch (*param_enum) {
178 m_gravity.x = static_cast<float>(value);
179 break;
181 m_gravity.y = static_cast<float>(value);
182 break;
184 m_gravity.z = static_cast<float>(value);
185 break;
187 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
188 break;
190 m_interaction_radius = static_cast<float>(value);
191 break;
193 m_spring_stiffness = static_cast<float>(value);
194 break;
196 m_repulsion_strength = static_cast<float>(value);
197 break;
200 break;
202 m_point_size = static_cast<float>(value);
203 for (auto& group : m_collections) {
204 auto& points = group.collection->get_points();
205 for (auto& pt : points) {
206 pt.size = m_point_size;
207 }
208 }
209 break;
211 m_attraction_strength = static_cast<float>(value);
212 break;
214 m_turbulence_strength = static_cast<float>(value);
215 break;
216 }
217}
218
219std::optional<double> PhysicsOperator::query_state(std::string_view query) const
220{
221 if (query == "point_count") {
222 return static_cast<double>(get_point_count());
223 }
224 if (query == "collection_count") {
225 return static_cast<double>(m_collections.size());
226 }
227 if (query == "avg_velocity") {
228 glm::vec3 avg(0.0F);
229 size_t total_points = 0;
230
231 for (const auto& group : m_collections) {
232 for (const auto& state : group.physics_state) {
233 avg += state.velocity;
234 ++total_points;
235 }
236 }
237
238 if (total_points > 0) {
239 avg /= static_cast<float>(total_points);
240 }
241 return static_cast<double>(glm::length(avg));
242 }
243
244 return std::nullopt;
245}
246
247//-----------------------------------------------------------------------------
248// GraphicsOperator Interface (Data Extraction)
249//-----------------------------------------------------------------------------
250
251std::vector<PointVertex> PhysicsOperator::extract_vertices() const
252{
253 std::vector<PointVertex> positions;
254
255 for (const auto& group : m_collections) {
256 const auto& points = group.collection->get_points();
257 for (const auto& pt : points) {
258 positions.push_back(pt);
259 }
260 }
261
262 return positions;
263}
264
265std::span<const uint8_t> PhysicsOperator::get_vertex_data_for_collection(uint32_t idx) const
266{
267 if (m_collections.empty() || idx >= m_collections.size()) {
268 return {};
269 }
270 return m_collections[idx].collection->get_vertex_data();
271}
272
273std::span<const uint8_t> PhysicsOperator::get_vertex_data() const
274{
276 for (const auto& group : m_collections) {
277 auto span = group.collection->get_vertex_data();
280 span.begin(), span.end());
281 }
282 return { m_vertex_data_aggregate.data(), m_vertex_data_aggregate.size() };
283}
284
285std::optional<double> PhysicsOperator::get_particle_velocity(size_t global_index) const
286{
287 size_t current_offset = 0;
288
289 for (const auto& group : m_collections) {
290 size_t group_size = group.physics_state.size();
291
292 if (global_index < current_offset + group_size) {
293 size_t local_index = global_index - current_offset;
294 return static_cast<double>(glm::length(group.physics_state[local_index].velocity));
295 }
296
297 current_offset += group_size;
298 }
299
300 return std::nullopt;
301}
302
304{
305 if (m_collections.empty()) {
306 return {};
307 }
308
309 auto layout_opt = m_collections[0].collection->get_vertex_layout();
310 if (!layout_opt.has_value()) {
311 return {};
312 }
313
314 auto layout = *layout_opt;
315 layout.vertex_count = static_cast<uint32_t>(get_vertex_count());
316 return layout;
317}
318
320{
321 size_t total = 0;
322 for (const auto& group : m_collections) {
323 total += group.collection->get_vertex_count();
324 }
325 return total;
326}
327
329{
330 return std::ranges::any_of(
332 [](const auto& group) { return group.collection->needs_gpu_update(); });
333}
334
336{
337 for (auto& group : m_collections) {
338 group.collection->mark_vertex_data_dirty(false);
339 }
340}
341
343{
344 size_t total = 0;
345 for (const auto& group : m_collections) {
346 total += group.collection->get_point_count();
347 }
348 return total;
349}
350
351void PhysicsOperator::set_bounds(const glm::vec3& min, const glm::vec3& max)
352{
353 m_bounds.min = min;
354 m_bounds.max = max;
355}
356
357void PhysicsOperator::set_attraction_point(const glm::vec3& point)
358{
359 m_attraction_point = point;
361}
362
363//-----------------------------------------------------------------------------
364// ONE_TO_ONE Parameter Mapping
365//-----------------------------------------------------------------------------
366
368 std::string_view param,
369 const std::shared_ptr<NodeNetwork>& source)
370{
371 size_t point_count = get_point_count();
372
373 if (source->get_node_count() != point_count) {
375 "ONE_TO_ONE size mismatch: {} particles vs {} source nodes",
376 point_count, source->get_node_count());
377 return;
378 }
379
380 if (param == "force_x" || param == "force_y" || param == "force_z") {
381 apply_per_particle_force(param, source);
382 } else if (param == "mass") {
384 } else {
386 }
387}
388
390 std::string_view param,
391 const std::shared_ptr<NodeNetwork>& source)
392{
393 size_t global_index = 0;
394
395 for (auto& group : m_collections) {
396 for (auto& i : group.physics_state) {
397 auto val = source->get_node_output(global_index++);
398 if (!val)
399 continue;
400
401 auto force = static_cast<float>(*val);
402
403 if (param == "force_x") {
404 i.force.x += force;
405 } else if (param == "force_y") {
406 i.force.y += force;
407 } else if (param == "force_z") {
408 i.force.z += force;
409 }
410 }
411 }
412}
413
415 const std::shared_ptr<NodeNetwork>& source)
416{
417 size_t global_index = 0;
418
419 for (auto& group : m_collections) {
420 for (auto& i : group.physics_state) {
421 auto val = source->get_node_output(global_index++);
422 if (!val)
423 continue;
424
425 i.mass = std::max(0.1F, static_cast<float>(*val));
426 }
427 }
428}
429
430//-----------------------------------------------------------------------------
431// Physics Simulation
432//-----------------------------------------------------------------------------
433
435{
436 for (auto& group : m_collections) {
437 for (auto& state : group.physics_state) {
438 state.force = m_gravity * state.mass;
439 }
440 }
441
444 }
445
446 if (m_turbulence_strength > 0.001F) {
448 }
449
452 }
453
454 if (!m_bond_root.empty()) {
456 }
457
458 if (!m_force_fields.empty()) {
459 for (auto& group : m_collections) {
460 auto& points = group.collection->get_points();
461
462 for (size_t i = 0; i < points.size(); ++i) {
463 for (const auto& field : m_force_fields) {
464 group.physics_state[i].force += field(points[i].position);
465 }
466 }
467 }
468 }
469}
470
472{
474
475 for (auto& group : m_collections) {
476 for (auto& state : group.physics_state) {
477 state.force += field(glm::vec3(0.0F));
478 }
479 }
480}
481
483{
484 for (size_t g1 = 0; g1 < m_collections.size(); ++g1) {
485 auto& group1 = m_collections[g1];
486 auto& points1 = group1.collection->get_points();
487
488 for (size_t i = 0; i < points1.size(); ++i) {
489 const auto& pos_i = points1[i].position;
490 auto& state_i = group1.physics_state[i];
491
492 for (size_t g2 = 0; g2 < m_collections.size(); ++g2) {
493 auto& group2 = m_collections[g2];
494 auto& points2 = group2.collection->get_points();
495
496 size_t start_j = (g1 == g2) ? i + 1 : 0;
497
498 for (size_t j = start_j; j < points2.size(); ++j) {
499 const auto& pos_j = points2[j].position;
500 auto& state_j = group2.physics_state[j];
501
502 glm::vec3 delta = pos_j - pos_i;
503 float distance = glm::length(delta);
504
505 if (distance < m_interaction_radius && distance > 0.001F) {
506 glm::vec3 direction = delta / distance;
507
508 float spring_force = m_spring_stiffness * (distance - m_interaction_radius * 0.5F);
509
510 float repulsion_force = 0.0F;
511 if (distance < m_interaction_radius * 0.3F) {
512 repulsion_force = m_repulsion_strength / (distance * distance);
513 }
514
515 glm::vec3 force = direction * (spring_force - repulsion_force);
516
517 state_i.force += force;
518 state_j.force -= force;
519 }
520 }
521 }
522 }
523 }
524}
525
526std::optional<PhysicsOperator::GroupIndex> PhysicsOperator::resolve_global_index(size_t global_index) const
527{
528 size_t offset = 0;
529 for (size_t g = 0; g < m_collections.size(); ++g) {
530 size_t count = m_collections[g].collection->get_point_count();
531 if (global_index < offset + count) {
532 return GroupIndex { .group = g, .local = global_index - offset };
533 }
534 offset += count;
535 }
536 return std::nullopt;
537}
538
539/**
540 * @brief Pull each bonded particle toward its root with a spring.
541 *
542 * Rest length scales with cbrt(root's accreted mass): a grown body's
543 * satellites settle at a wider spread than a fresh one's, the same way any
544 * fixed-density accumulation of matter occupies more volume as it gains
545 * mass. Growth is a wider swarm of small particles, not a bigger point.
546 */
548{
549 for (size_t i = 0; i < m_bond_root.size(); ++i) {
550 uint32_t root = m_bond_root[i];
551 if (root == i) {
552 continue;
553 }
554
555 auto self_idx = resolve_global_index(i);
556 auto root_idx = resolve_global_index(root);
557 if (!self_idx || !root_idx) {
558 continue;
559 }
560
561 auto& self_state = m_collections[self_idx->group].physics_state[self_idx->local];
562 const auto& self_pos = m_collections[self_idx->group].collection->get_points()[self_idx->local].position;
563 const auto& root_pos = m_collections[root_idx->group].collection->get_points()[root_idx->local].position;
564
565 glm::vec3 delta = root_pos - self_pos;
566 float distance = glm::length(delta);
567 if (distance < 0.001F) {
568 continue;
569 }
570
571 const float rest_length = m_bond_rest_length * std::cbrt(get_accreted_mass(root));
572
573 glm::vec3 direction = delta / distance;
574 self_state.force += direction * (m_bond_stiffness * (distance - rest_length));
575 }
576}
577
578void PhysicsOperator::sync_bonds_from_claims(std::span<const uint32_t> claimed_by)
579{
580 if (!m_bonds_enabled) {
581 return;
582 }
583
584 if (m_accreted_mass.size() != claimed_by.size()) {
585 m_accreted_mass.assign(claimed_by.size(), 1.0F);
586 }
587
588 for (size_t i = 0; i < claimed_by.size(); ++i) {
589 uint32_t root = claimed_by[i];
590 if (root == i || root >= m_accreted_mass.size()) {
591 continue;
592 }
594 m_accreted_mass[i] = 0.0F;
595 }
596
597 m_bond_root.assign(claimed_by.begin(), claimed_by.end());
598}
599
601{
602 m_bond_root.clear();
603}
604
606{
607 m_bonds_enabled = enable;
608 if (!enable) {
609 clear_bonds();
610 }
611}
612
613float PhysicsOperator::get_accreted_mass(size_t global_index) const
614{
615 if (global_index >= m_accreted_mass.size()) {
616 return 1.0F;
617 }
618 return m_accreted_mass[global_index];
619}
620
622{
623 const size_t count = get_point_count();
624 if (count > 0 && m_accreted_mass.size() != count) {
625 m_accreted_mass.assign(count, 1.0F);
626 }
627 return m_accreted_mass;
628}
629
631{
632 if (m_accreted_mass.empty()) {
633 float count = 0.0F;
634 for (const auto& group : m_collections) {
635 count += static_cast<float>(group.collection->get_point_count());
636 }
637 return count;
638 }
639
640 float total = 0.0F;
641 for (float mass : m_accreted_mass) {
642 total += mass;
643 }
644 return total;
645}
646
648{
649 size_t count = 0;
650 for (size_t i = 0; i < m_bond_root.size(); ++i) {
651 if (m_bond_root[i] != i) {
652 ++count;
653 }
654 }
655 return count;
656}
657
659{
661
662 for (auto& group : m_collections) {
663 auto& points = group.collection->get_points();
664
665 for (size_t i = 0; i < points.size(); ++i) {
666 group.physics_state[i].force += field(points[i].position) * group.physics_state[i].mass;
667 }
668 }
669}
670
672{
673 for (auto& group : m_collections) {
674 auto& points = group.collection->get_points();
675
676 for (size_t i = 0; i < points.size(); ++i) {
677 auto& state = group.physics_state[i];
678 auto& vertex = points[i];
679
680 glm::vec3 acceleration = state.force / state.mass;
681 state.velocity += acceleration * dt;
682 state.velocity *= (1.0F - m_drag);
683 vertex.position += state.velocity * dt;
684
685 state.force = glm::vec3(0.0F);
686 }
687 }
688}
689
691{
693 return;
694 }
695
696 constexpr float damping = 0.8F;
697
698 for (auto& group : m_collections) {
699 auto& points = group.collection->get_points();
700
701 for (size_t i = 0; i < points.size(); ++i) {
702 auto& vertex = points[i];
703 auto& state = group.physics_state[i];
704
705 for (int axis = 0; axis < 3; ++axis) {
706 if (vertex.position[axis] < m_bounds.min[axis]) {
707 switch (m_bounds_mode) {
709 vertex.position[axis] = m_bounds.min[axis];
710 state.velocity[axis] *= -damping;
711 break;
712 case BoundsMode::WRAP:
713 vertex.position[axis] = m_bounds.max[axis];
714 break;
716 vertex.position[axis] = m_bounds.min[axis];
717 state.velocity[axis] = 0.0F;
718 break;
719 case BoundsMode::NONE:
720 break;
721 }
722 } else if (vertex.position[axis] > m_bounds.max[axis]) {
723 switch (m_bounds_mode) {
725 vertex.position[axis] = m_bounds.max[axis];
726 state.velocity[axis] *= -damping;
727 break;
728 case BoundsMode::WRAP:
729 vertex.position[axis] = m_bounds.min[axis];
730 break;
732 vertex.position[axis] = m_bounds.max[axis];
733 state.velocity[axis] = 0.0F;
734 break;
735 case BoundsMode::NONE:
736 break;
737 }
738 }
739 }
740 }
741 }
742}
743
745{
746 for (auto& group : m_collections) {
747 group.collection->mark_vertex_data_dirty(true);
748 }
749}
750
751void* PhysicsOperator::get_data_at(size_t global_index)
752{
753 size_t offset = 0;
754 for (auto& group : m_collections) {
755 if (global_index < offset + group.collection->get_point_count()) {
756 size_t local_index = global_index - offset;
757 return &group.collection->get_points()[local_index];
758 }
759 offset += group.collection->get_point_count();
760 }
761 return nullptr;
762}
763
764void PhysicsOperator::apply_global_impulse(const glm::vec3& impulse)
765{
766 for (auto& group : m_collections) {
767 for (auto& state : group.physics_state) {
768 state.velocity += impulse / state.mass;
769 }
770 }
771}
772
773void PhysicsOperator::apply_impulse(size_t index, const glm::vec3& impulse)
774{
775 size_t offset = 0;
776 for (auto& group : m_collections) {
777 if (index < offset + group.collection->get_point_count()) {
778 size_t local_index = index - offset;
779 group.physics_state[local_index].velocity += impulse / group.physics_state[local_index].mass;
780 return;
781 }
782 offset += group.collection->get_point_count();
783 }
784}
785
786std::vector<uint32_t> PhysicsOperator::build_cluster_ids() const
787{
788 std::vector<uint32_t> ids(get_vertex_count(), 0U);
789
790 if (m_collections.size() <= 1) {
791 return ids;
792 }
793
794 size_t offset = 0;
795 uint32_t cluster = 0;
796 for (const auto& group : m_collections) {
797 const size_t count = group.collection->get_vertex_count();
798 for (size_t i = 0; i < count && offset + i < ids.size(); ++i) {
799 ids[offset + i] = cluster;
800 }
801 offset += count;
802 ++cluster;
803 }
804
805 return ids;
806}
807
809{
810 m_force_fields.push_back(std::move(field));
811
813 "Added force field #{}", m_force_fields.size());
814}
815
823
824} // namespace MayaFlux::Nodes::Network
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< glm::vec2 > * points
uint32_t index
Definition VKDevice.cpp:142
size_t count
float value
float offset
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
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.
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
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.
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:11
@ 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:7
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.