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 m_collections.push_back(std::move(group));
80
82 "Added collection #{} with {} points (mass_mult={:.2f})",
83 m_collections.size(), vertices.size(), mass_multiplier);
84}
85
86//-----------------------------------------------------------------------------
87// Processing
88//-----------------------------------------------------------------------------
89
91{
92 if (m_collections.empty()) {
93 return;
94 }
95
97 integrate(dt);
100
101 for (auto& group : m_collections) {
102 group.collection->compute_frame();
103 }
104}
105
106//-----------------------------------------------------------------------------
107// Parameter System
108//-----------------------------------------------------------------------------
109
110std::optional<PhysicsParameter> PhysicsOperator::string_to_parameter(std::string_view param)
111{
112 return Reflect::string_to_enum_case_insensitive<PhysicsParameter>(param);
113}
114
115void PhysicsOperator::set_parameter(std::string_view param, double value)
116{
117 auto param_enum = string_to_parameter(param);
118
119 if (!param_enum) {
120 try {
121 Reflect::string_to_enum_or_throw_case_insensitive<PhysicsParameter>(
122 param, "PhysicsOperator parameter");
123 } catch (const std::invalid_argument& e) {
125 "{}", e.what());
126 }
128 "Unknown physics parameter: '{}'", param);
129 return;
130 }
131
132 switch (*param_enum) {
134 m_gravity.x = static_cast<float>(value);
135 break;
137 m_gravity.y = static_cast<float>(value);
138 break;
140 m_gravity.z = static_cast<float>(value);
141 break;
143 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
144 break;
146 m_interaction_radius = static_cast<float>(value);
147 break;
149 m_spring_stiffness = static_cast<float>(value);
150 break;
152 m_repulsion_strength = static_cast<float>(value);
153 break;
155 m_spatial_interactions_enabled = (value > 0.5);
156 break;
158 m_point_size = static_cast<float>(value);
159 for (auto& group : m_collections) {
160 auto& points = group.collection->get_points();
161 for (auto& pt : points) {
162 pt.size = m_point_size;
163 }
164 }
165 break;
167 m_attraction_strength = static_cast<float>(value);
168 break;
170 m_turbulence_strength = static_cast<float>(value);
171 break;
172 }
173}
174
175std::optional<double> PhysicsOperator::query_state(std::string_view query) const
176{
177 if (query == "point_count") {
178 return static_cast<double>(get_point_count());
179 }
180 if (query == "collection_count") {
181 return static_cast<double>(m_collections.size());
182 }
183 if (query == "avg_velocity") {
184 glm::vec3 avg(0.0F);
185 size_t total_points = 0;
186
187 for (const auto& group : m_collections) {
188 for (const auto& state : group.physics_state) {
189 avg += state.velocity;
190 ++total_points;
191 }
192 }
193
194 if (total_points > 0) {
195 avg /= static_cast<float>(total_points);
196 }
197 return static_cast<double>(glm::length(avg));
198 }
199
200 return std::nullopt;
201}
202
203//-----------------------------------------------------------------------------
204// GraphicsOperator Interface (Data Extraction)
205//-----------------------------------------------------------------------------
206
207std::vector<PointVertex> PhysicsOperator::extract_vertices() const
208{
209 std::vector<PointVertex> positions;
210
211 for (const auto& group : m_collections) {
212 const auto& points = group.collection->get_points();
213 for (const auto& pt : points) {
214 positions.push_back(pt);
215 }
216 }
217
218 return positions;
219}
220
221std::span<const uint8_t> PhysicsOperator::get_vertex_data_for_collection(uint32_t idx) const
222{
223 if (m_collections.empty() || idx >= m_collections.size()) {
224 return {};
225 }
226 return m_collections[idx].collection->get_vertex_data();
227}
228
229std::span<const uint8_t> PhysicsOperator::get_vertex_data() const
230{
232 for (const auto& group : m_collections) {
233 auto span = group.collection->get_vertex_data();
236 span.begin(), span.end());
237 }
238 return { m_vertex_data_aggregate.data(), m_vertex_data_aggregate.size() };
239}
240
241std::optional<double> PhysicsOperator::get_particle_velocity(size_t global_index) const
242{
243 size_t current_offset = 0;
244
245 for (const auto& group : m_collections) {
246 size_t group_size = group.physics_state.size();
247
248 if (global_index < current_offset + group_size) {
249 size_t local_index = global_index - current_offset;
250 return static_cast<double>(glm::length(group.physics_state[local_index].velocity));
251 }
252
253 current_offset += group_size;
254 }
255
256 return std::nullopt;
257}
258
260{
261 if (m_collections.empty()) {
262 return {};
263 }
264
265 auto layout_opt = m_collections[0].collection->get_vertex_layout();
266 if (!layout_opt.has_value()) {
267 return {};
268 }
269
270 return *layout_opt;
271}
272
274{
275 size_t total = 0;
276 for (const auto& group : m_collections) {
277 total += group.collection->get_vertex_count();
278 }
279 return total;
280}
281
283{
284 return std::ranges::any_of(
286 [](const auto& group) { return group.collection->needs_gpu_update(); });
287}
288
290{
291 for (auto& group : m_collections) {
292 group.collection->mark_vertex_data_dirty(false);
293 }
294}
295
297{
298 size_t total = 0;
299 for (const auto& group : m_collections) {
300 total += group.collection->get_point_count();
301 }
302 return total;
303}
304
305void PhysicsOperator::set_bounds(const glm::vec3& min, const glm::vec3& max)
306{
307 m_bounds.min = min;
308 m_bounds.max = max;
309}
310
311void PhysicsOperator::set_attraction_point(const glm::vec3& point)
312{
313 m_attraction_point = point;
315}
316
317//-----------------------------------------------------------------------------
318// ONE_TO_ONE Parameter Mapping
319//-----------------------------------------------------------------------------
320
322 std::string_view param,
323 const std::shared_ptr<NodeNetwork>& source)
324{
325 size_t point_count = get_point_count();
326
327 if (source->get_node_count() != point_count) {
329 "ONE_TO_ONE size mismatch: {} particles vs {} source nodes",
330 point_count, source->get_node_count());
331 return;
332 }
333
334 if (param == "force_x" || param == "force_y" || param == "force_z") {
335 apply_per_particle_force(param, source);
336 } else if (param == "mass") {
338 } else {
340 }
341}
342
344 std::string_view param,
345 const std::shared_ptr<NodeNetwork>& source)
346{
347 size_t global_index = 0;
348
349 for (auto& group : m_collections) {
350 for (auto& i : group.physics_state) {
351 auto val = source->get_node_output(global_index++);
352 if (!val)
353 continue;
354
355 auto force = static_cast<float>(*val);
356
357 if (param == "force_x") {
358 i.force.x += force;
359 } else if (param == "force_y") {
360 i.force.y += force;
361 } else if (param == "force_z") {
362 i.force.z += force;
363 }
364 }
365 }
366}
367
369 const std::shared_ptr<NodeNetwork>& source)
370{
371 size_t global_index = 0;
372
373 for (auto& group : m_collections) {
374 for (auto& i : group.physics_state) {
375 auto val = source->get_node_output(global_index++);
376 if (!val)
377 continue;
378
379 i.mass = std::max(0.1F, static_cast<float>(*val));
380 }
381 }
382}
383
384//-----------------------------------------------------------------------------
385// Physics Simulation
386//-----------------------------------------------------------------------------
387
389{
390 for (auto& group : m_collections) {
391 for (auto& state : group.physics_state) {
392 state.force = m_gravity * state.mass;
393 }
394 }
395
398 }
399
400 if (m_turbulence_strength > 0.001F) {
402 }
403
406 }
407
408 if (!m_force_fields.empty()) {
409 for (auto& group : m_collections) {
410 auto& points = group.collection->get_points();
411
412 for (size_t i = 0; i < points.size(); ++i) {
413 for (const auto& field : m_force_fields) {
414 group.physics_state[i].force += field(points[i].position);
415 }
416 }
417 }
418 }
419}
420
422{
424
425 for (auto& group : m_collections) {
426 for (auto& state : group.physics_state) {
427 state.force += field(glm::vec3(0.0F));
428 }
429 }
430}
431
433{
434 for (size_t g1 = 0; g1 < m_collections.size(); ++g1) {
435 auto& group1 = m_collections[g1];
436 auto& points1 = group1.collection->get_points();
437
438 for (size_t i = 0; i < points1.size(); ++i) {
439 const auto& pos_i = points1[i].position;
440 auto& state_i = group1.physics_state[i];
441
442 for (size_t g2 = 0; g2 < m_collections.size(); ++g2) {
443 auto& group2 = m_collections[g2];
444 auto& points2 = group2.collection->get_points();
445
446 size_t start_j = (g1 == g2) ? i + 1 : 0;
447
448 for (size_t j = start_j; j < points2.size(); ++j) {
449 const auto& pos_j = points2[j].position;
450 auto& state_j = group2.physics_state[j];
451
452 glm::vec3 delta = pos_j - pos_i;
453 float distance = glm::length(delta);
454
455 if (distance < m_interaction_radius && distance > 0.001F) {
456 glm::vec3 direction = delta / distance;
457
458 float spring_force = m_spring_stiffness * (distance - m_interaction_radius * 0.5F);
459
460 float repulsion_force = 0.0F;
461 if (distance < m_interaction_radius * 0.3F) {
462 repulsion_force = m_repulsion_strength / (distance * distance);
463 }
464
465 glm::vec3 force = direction * (spring_force - repulsion_force);
466
467 state_i.force += force;
468 state_j.force -= force;
469 }
470 }
471 }
472 }
473 }
474}
475
477{
479
480 for (auto& group : m_collections) {
481 auto& points = group.collection->get_points();
482
483 for (size_t i = 0; i < points.size(); ++i) {
484 group.physics_state[i].force += field(points[i].position) * group.physics_state[i].mass;
485 }
486 }
487}
488
490{
491 for (auto& group : m_collections) {
492 auto& points = group.collection->get_points();
493
494 for (size_t i = 0; i < points.size(); ++i) {
495 auto& state = group.physics_state[i];
496 auto& vertex = points[i];
497
498 glm::vec3 acceleration = state.force / state.mass;
499 state.velocity += acceleration * dt;
500 state.velocity *= (1.0F - m_drag);
501 vertex.position += state.velocity * dt;
502
503 state.force = glm::vec3(0.0F);
504 }
505 }
506}
507
509{
511 return;
512 }
513
514 constexpr float damping = 0.8F;
515
516 for (auto& group : m_collections) {
517 auto& points = group.collection->get_points();
518
519 for (size_t i = 0; i < points.size(); ++i) {
520 auto& vertex = points[i];
521 auto& state = group.physics_state[i];
522
523 for (int axis = 0; axis < 3; ++axis) {
524 if (vertex.position[axis] < m_bounds.min[axis]) {
525 switch (m_bounds_mode) {
527 vertex.position[axis] = m_bounds.min[axis];
528 state.velocity[axis] *= -damping;
529 break;
530 case BoundsMode::WRAP:
531 vertex.position[axis] = m_bounds.max[axis];
532 break;
534 vertex.position[axis] = m_bounds.min[axis];
535 state.velocity[axis] = 0.0F;
536 break;
537 case BoundsMode::NONE:
538 break;
539 }
540 } else if (vertex.position[axis] > m_bounds.max[axis]) {
541 switch (m_bounds_mode) {
543 vertex.position[axis] = m_bounds.max[axis];
544 state.velocity[axis] *= -damping;
545 break;
546 case BoundsMode::WRAP:
547 vertex.position[axis] = m_bounds.min[axis];
548 break;
550 vertex.position[axis] = m_bounds.max[axis];
551 state.velocity[axis] = 0.0F;
552 break;
553 case BoundsMode::NONE:
554 break;
555 }
556 }
557 }
558 }
559 }
560}
561
563{
564 for (auto& group : m_collections) {
565 group.collection->mark_vertex_data_dirty(true);
566 }
567}
568
569void* PhysicsOperator::get_data_at(size_t global_index)
570{
571 size_t offset = 0;
572 for (auto& group : m_collections) {
573 if (global_index < offset + group.collection->get_point_count()) {
574 size_t local_index = global_index - offset;
575 return &group.collection->get_points()[local_index];
576 }
577 offset += group.collection->get_point_count();
578 }
579 return nullptr;
580}
581
582void PhysicsOperator::apply_global_impulse(const glm::vec3& impulse)
583{
584 for (auto& group : m_collections) {
585 for (auto& state : group.physics_state) {
586 state.velocity += impulse / state.mass;
587 }
588 }
589}
590
591void PhysicsOperator::apply_impulse(size_t index, const glm::vec3& impulse)
592{
593 size_t offset = 0;
594 for (auto& group : m_collections) {
595 if (index < offset + group.collection->get_point_count()) {
596 size_t local_index = index - offset;
597 group.physics_state[local_index].velocity += impulse / group.physics_state[local_index].mass;
598 return;
599 }
600 offset += group.collection->get_point_count();
601 }
602}
603
605{
606 m_force_fields.push_back(std::move(field));
607
609 "Added force field #{}", m_force_fields.size());
610}
611
619
620} // namespace MayaFlux::Nodes::Network
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
float max
glm::vec3 position
float min
void apply_one_to_one(std::string_view param, const std::shared_ptr< NodeNetwork > &source) override
Apply ONE_TO_ONE parameter mapping.
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.
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)
@ 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.
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.