MayaFlux 0.4.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 if (m_collections.empty()) {
125 return;
126 }
127
128 const float effective_dt = m_force_internal_dt ? m_internal_dt : dt;
129 apply_forces();
130 integrate(effective_dt);
133
134 uint32_t expected = 0;
135 while (!m_access_token.compare_exchange_weak(expected, 1,
136 std::memory_order_acquire, std::memory_order_relaxed)) {
137 if (m_shutdown.load(std::memory_order_relaxed))
138 return;
139 expected = 0;
140 }
141
142 for (auto& group : m_collections) {
143 group.collection->compute_frame();
144 }
145
146 m_access_token.store(0, std::memory_order_release);
147}
148
149//-----------------------------------------------------------------------------
150// Parameter System
151//-----------------------------------------------------------------------------
152
153std::optional<PhysicsParameter> PhysicsOperator::string_to_parameter(std::string_view param)
154{
155 return Reflect::string_to_enum_case_insensitive<PhysicsParameter>(param);
156}
157
158void PhysicsOperator::set_parameter(std::string_view param, double value)
159{
160 auto param_enum = string_to_parameter(param);
161
162 if (!param_enum) {
163 try {
164 Reflect::string_to_enum_or_throw_case_insensitive<PhysicsParameter>(
165 param, "PhysicsOperator parameter");
166 } catch (const std::invalid_argument& e) {
168 "{}", e.what());
169 }
171 "Unknown physics parameter: '{}'", param);
172 return;
173 }
174
175 switch (*param_enum) {
177 m_gravity.x = static_cast<float>(value);
178 break;
180 m_gravity.y = static_cast<float>(value);
181 break;
183 m_gravity.z = static_cast<float>(value);
184 break;
186 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
187 break;
189 m_interaction_radius = static_cast<float>(value);
190 break;
192 m_spring_stiffness = static_cast<float>(value);
193 break;
195 m_repulsion_strength = static_cast<float>(value);
196 break;
198 m_spatial_interactions_enabled = (value > 0.5);
199 break;
201 m_point_size = static_cast<float>(value);
202 for (auto& group : m_collections) {
203 auto& points = group.collection->get_points();
204 for (auto& pt : points) {
205 pt.size = m_point_size;
206 }
207 }
208 break;
210 m_attraction_strength = static_cast<float>(value);
211 break;
213 m_turbulence_strength = static_cast<float>(value);
214 break;
215 }
216}
217
218std::optional<double> PhysicsOperator::query_state(std::string_view query) const
219{
220 if (query == "point_count") {
221 return static_cast<double>(get_point_count());
222 }
223 if (query == "collection_count") {
224 return static_cast<double>(m_collections.size());
225 }
226 if (query == "avg_velocity") {
227 glm::vec3 avg(0.0F);
228 size_t total_points = 0;
229
230 for (const auto& group : m_collections) {
231 for (const auto& state : group.physics_state) {
232 avg += state.velocity;
233 ++total_points;
234 }
235 }
236
237 if (total_points > 0) {
238 avg /= static_cast<float>(total_points);
239 }
240 return static_cast<double>(glm::length(avg));
241 }
242
243 return std::nullopt;
244}
245
246//-----------------------------------------------------------------------------
247// GraphicsOperator Interface (Data Extraction)
248//-----------------------------------------------------------------------------
249
250std::vector<PointVertex> PhysicsOperator::extract_vertices() const
251{
252 std::vector<PointVertex> positions;
253
254 for (const auto& group : m_collections) {
255 const auto& points = group.collection->get_points();
256 for (const auto& pt : points) {
257 positions.push_back(pt);
258 }
259 }
260
261 return positions;
262}
263
264std::span<const uint8_t> PhysicsOperator::get_vertex_data_for_collection(uint32_t idx) const
265{
266 if (m_collections.empty() || idx >= m_collections.size()) {
267 return {};
268 }
269 return m_collections[idx].collection->get_vertex_data();
270}
271
272std::span<const uint8_t> PhysicsOperator::get_vertex_data() const
273{
275 for (const auto& group : m_collections) {
276 auto span = group.collection->get_vertex_data();
279 span.begin(), span.end());
280 }
281 return { m_vertex_data_aggregate.data(), m_vertex_data_aggregate.size() };
282}
283
284std::optional<double> PhysicsOperator::get_particle_velocity(size_t global_index) const
285{
286 size_t current_offset = 0;
287
288 for (const auto& group : m_collections) {
289 size_t group_size = group.physics_state.size();
290
291 if (global_index < current_offset + group_size) {
292 size_t local_index = global_index - current_offset;
293 return static_cast<double>(glm::length(group.physics_state[local_index].velocity));
294 }
295
296 current_offset += group_size;
297 }
298
299 return std::nullopt;
300}
301
303{
304 if (m_collections.empty()) {
305 return {};
306 }
307
308 auto layout_opt = m_collections[0].collection->get_vertex_layout();
309 if (!layout_opt.has_value()) {
310 return {};
311 }
312
313 auto layout = *layout_opt;
314 layout.vertex_count = static_cast<uint32_t>(get_vertex_count());
315 return layout;
316}
317
319{
320 size_t total = 0;
321 for (const auto& group : m_collections) {
322 total += group.collection->get_vertex_count();
323 }
324 return total;
325}
326
328{
329 return std::ranges::any_of(
331 [](const auto& group) { return group.collection->needs_gpu_update(); });
332}
333
335{
336 for (auto& group : m_collections) {
337 group.collection->mark_vertex_data_dirty(false);
338 }
339}
340
342{
343 size_t total = 0;
344 for (const auto& group : m_collections) {
345 total += group.collection->get_point_count();
346 }
347 return total;
348}
349
350void PhysicsOperator::set_bounds(const glm::vec3& min, const glm::vec3& max)
351{
352 m_bounds.min = min;
353 m_bounds.max = max;
354}
355
356void PhysicsOperator::set_attraction_point(const glm::vec3& point)
357{
358 m_attraction_point = point;
360}
361
362//-----------------------------------------------------------------------------
363// ONE_TO_ONE Parameter Mapping
364//-----------------------------------------------------------------------------
365
367 std::string_view param,
368 const std::shared_ptr<NodeNetwork>& source)
369{
370 size_t point_count = get_point_count();
371
372 if (source->get_node_count() != point_count) {
374 "ONE_TO_ONE size mismatch: {} particles vs {} source nodes",
375 point_count, source->get_node_count());
376 return;
377 }
378
379 if (param == "force_x" || param == "force_y" || param == "force_z") {
380 apply_per_particle_force(param, source);
381 } else if (param == "mass") {
383 } else {
385 }
386}
387
389 std::string_view param,
390 const std::shared_ptr<NodeNetwork>& source)
391{
392 size_t global_index = 0;
393
394 for (auto& group : m_collections) {
395 for (auto& i : group.physics_state) {
396 auto val = source->get_node_output(global_index++);
397 if (!val)
398 continue;
399
400 auto force = static_cast<float>(*val);
401
402 if (param == "force_x") {
403 i.force.x += force;
404 } else if (param == "force_y") {
405 i.force.y += force;
406 } else if (param == "force_z") {
407 i.force.z += force;
408 }
409 }
410 }
411}
412
414 const std::shared_ptr<NodeNetwork>& source)
415{
416 size_t global_index = 0;
417
418 for (auto& group : m_collections) {
419 for (auto& i : group.physics_state) {
420 auto val = source->get_node_output(global_index++);
421 if (!val)
422 continue;
423
424 i.mass = std::max(0.1F, static_cast<float>(*val));
425 }
426 }
427}
428
429//-----------------------------------------------------------------------------
430// Physics Simulation
431//-----------------------------------------------------------------------------
432
434{
435 for (auto& group : m_collections) {
436 for (auto& state : group.physics_state) {
437 state.force = m_gravity * state.mass;
438 }
439 }
440
443 }
444
445 if (m_turbulence_strength > 0.001F) {
447 }
448
451 }
452
453 if (!m_force_fields.empty()) {
454 for (auto& group : m_collections) {
455 auto& points = group.collection->get_points();
456
457 for (size_t i = 0; i < points.size(); ++i) {
458 for (const auto& field : m_force_fields) {
459 group.physics_state[i].force += field(points[i].position);
460 }
461 }
462 }
463 }
464}
465
467{
469
470 for (auto& group : m_collections) {
471 for (auto& state : group.physics_state) {
472 state.force += field(glm::vec3(0.0F));
473 }
474 }
475}
476
478{
479 for (size_t g1 = 0; g1 < m_collections.size(); ++g1) {
480 auto& group1 = m_collections[g1];
481 auto& points1 = group1.collection->get_points();
482
483 for (size_t i = 0; i < points1.size(); ++i) {
484 const auto& pos_i = points1[i].position;
485 auto& state_i = group1.physics_state[i];
486
487 for (size_t g2 = 0; g2 < m_collections.size(); ++g2) {
488 auto& group2 = m_collections[g2];
489 auto& points2 = group2.collection->get_points();
490
491 size_t start_j = (g1 == g2) ? i + 1 : 0;
492
493 for (size_t j = start_j; j < points2.size(); ++j) {
494 const auto& pos_j = points2[j].position;
495 auto& state_j = group2.physics_state[j];
496
497 glm::vec3 delta = pos_j - pos_i;
498 float distance = glm::length(delta);
499
500 if (distance < m_interaction_radius && distance > 0.001F) {
501 glm::vec3 direction = delta / distance;
502
503 float spring_force = m_spring_stiffness * (distance - m_interaction_radius * 0.5F);
504
505 float repulsion_force = 0.0F;
506 if (distance < m_interaction_radius * 0.3F) {
507 repulsion_force = m_repulsion_strength / (distance * distance);
508 }
509
510 glm::vec3 force = direction * (spring_force - repulsion_force);
511
512 state_i.force += force;
513 state_j.force -= force;
514 }
515 }
516 }
517 }
518 }
519}
520
522{
524
525 for (auto& group : m_collections) {
526 auto& points = group.collection->get_points();
527
528 for (size_t i = 0; i < points.size(); ++i) {
529 group.physics_state[i].force += field(points[i].position) * group.physics_state[i].mass;
530 }
531 }
532}
533
535{
536 for (auto& group : m_collections) {
537 auto& points = group.collection->get_points();
538
539 for (size_t i = 0; i < points.size(); ++i) {
540 auto& state = group.physics_state[i];
541 auto& vertex = points[i];
542
543 glm::vec3 acceleration = state.force / state.mass;
544 state.velocity += acceleration * dt;
545 state.velocity *= (1.0F - m_drag);
546 vertex.position += state.velocity * dt;
547
548 state.force = glm::vec3(0.0F);
549 }
550 }
551}
552
554{
556 return;
557 }
558
559 constexpr float damping = 0.8F;
560
561 for (auto& group : m_collections) {
562 auto& points = group.collection->get_points();
563
564 for (size_t i = 0; i < points.size(); ++i) {
565 auto& vertex = points[i];
566 auto& state = group.physics_state[i];
567
568 for (int axis = 0; axis < 3; ++axis) {
569 if (vertex.position[axis] < m_bounds.min[axis]) {
570 switch (m_bounds_mode) {
572 vertex.position[axis] = m_bounds.min[axis];
573 state.velocity[axis] *= -damping;
574 break;
575 case BoundsMode::WRAP:
576 vertex.position[axis] = m_bounds.max[axis];
577 break;
579 vertex.position[axis] = m_bounds.min[axis];
580 state.velocity[axis] = 0.0F;
581 break;
582 case BoundsMode::NONE:
583 break;
584 }
585 } else if (vertex.position[axis] > m_bounds.max[axis]) {
586 switch (m_bounds_mode) {
588 vertex.position[axis] = m_bounds.max[axis];
589 state.velocity[axis] *= -damping;
590 break;
591 case BoundsMode::WRAP:
592 vertex.position[axis] = m_bounds.min[axis];
593 break;
595 vertex.position[axis] = m_bounds.max[axis];
596 state.velocity[axis] = 0.0F;
597 break;
598 case BoundsMode::NONE:
599 break;
600 }
601 }
602 }
603 }
604 }
605}
606
608{
609 for (auto& group : m_collections) {
610 group.collection->mark_vertex_data_dirty(true);
611 }
612}
613
614void* PhysicsOperator::get_data_at(size_t global_index)
615{
616 size_t offset = 0;
617 for (auto& group : m_collections) {
618 if (global_index < offset + group.collection->get_point_count()) {
619 size_t local_index = global_index - offset;
620 return &group.collection->get_points()[local_index];
621 }
622 offset += group.collection->get_point_count();
623 }
624 return nullptr;
625}
626
627void PhysicsOperator::apply_global_impulse(const glm::vec3& impulse)
628{
629 for (auto& group : m_collections) {
630 for (auto& state : group.physics_state) {
631 state.velocity += impulse / state.mass;
632 }
633 }
634}
635
636void PhysicsOperator::apply_impulse(size_t index, const glm::vec3& impulse)
637{
638 size_t offset = 0;
639 for (auto& group : m_collections) {
640 if (index < offset + group.collection->get_point_count()) {
641 size_t local_index = index - offset;
642 group.physics_state[local_index].velocity += impulse / group.physics_state[local_index].mass;
643 return;
644 }
645 offset += group.collection->get_point_count();
646 }
647}
648
650{
651 m_force_fields.push_back(std::move(field));
652
654 "Added force field #{}", m_force_fields.size());
655}
656
664
665} // namespace MayaFlux::Nodes::Network
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< glm::vec2 > * points
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.
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.
size_t get_point_count() const override
Get source point count (before topology expansion)
std::vector< CollectionGroup > m_collections
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)
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::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.
void add_force_field(Kinesis::VectorField field)
Add an external force field evaluated per-particle per-frame.
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.
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 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
Physics-specific data parallel to PointVertex array.