MayaFlux 0.2.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
7
9
15
16//-----------------------------------------------------------------------------
17// GraphicsOperator Interface (Basic Initialization)
18//-----------------------------------------------------------------------------
19
20void PhysicsOperator::initialize(const std::vector<PointVertex>& vertices)
21{
22 if (vertices.empty()) {
24 "Cannot initialize PhysicsOperator with zero vertices");
25 return;
26 }
27
28 m_collections.clear();
29 add_collection(vertices);
30
32 "PhysicsOperator initialized with {} points", vertices.size());
33}
34
35//-----------------------------------------------------------------------------
36// Advanced Initialization (Multiple Collections)
37//-----------------------------------------------------------------------------
38
40 const std::vector<std::vector<PointVertex>>& collections)
41{
42 for (const auto& collection : collections) {
43 add_collection(collection);
44 }
45
47 "PhysicsOperator initialized with {} collections",
48 collections.size());
49}
50
52 const std::vector<PointVertex>& vertices,
53 float mass_multiplier)
54{
55 if (vertices.empty()) {
57 "Cannot add collection with zero vertices");
58 return;
59 }
60
61 CollectionGroup group;
62 group.collection = std::make_shared<GpuSync::PointCollectionNode>();
63
64 group.physics_state.resize(vertices.size());
65
66 for (size_t i = 0; i < vertices.size(); ++i) {
67 group.physics_state[i] = PhysicsState {
68 .velocity = glm::vec3(0.0F),
69 .force = glm::vec3(0.0F),
70 .mass = mass_multiplier
71 };
72 }
73
74 group.collection->set_points(vertices);
75 group.collection->compute_frame();
76
77 m_collections.push_back(std::move(group));
78
80 "Added collection #{} with {} points (mass_mult={:.2f})",
81 m_collections.size(), vertices.size(), mass_multiplier);
82}
83
84//-----------------------------------------------------------------------------
85// Processing
86//-----------------------------------------------------------------------------
87
89{
90 if (m_collections.empty()) {
91 return;
92 }
93
95 integrate(dt);
98
99 for (auto& group : m_collections) {
100 group.collection->compute_frame();
101 }
102}
103
104//-----------------------------------------------------------------------------
105// Parameter System
106//-----------------------------------------------------------------------------
107
108std::optional<PhysicsParameter> PhysicsOperator::string_to_parameter(std::string_view param)
109{
110 return Reflect::string_to_enum_case_insensitive<PhysicsParameter>(param);
111}
112
113void PhysicsOperator::set_parameter(std::string_view param, double value)
114{
115 auto param_enum = string_to_parameter(param);
116
117 if (!param_enum) {
118 try {
119 Reflect::string_to_enum_or_throw_case_insensitive<PhysicsParameter>(
120 param, "PhysicsOperator parameter");
121 } catch (const std::invalid_argument& e) {
123 "{}", e.what());
124 }
126 "Unknown physics parameter: '{}'", param);
127 return;
128 }
129
130 switch (*param_enum) {
132 m_gravity.x = static_cast<float>(value);
133 break;
135 m_gravity.y = static_cast<float>(value);
136 break;
138 m_gravity.z = static_cast<float>(value);
139 break;
141 m_drag = glm::clamp(static_cast<float>(value), 0.0F, 1.0F);
142 break;
144 m_interaction_radius = static_cast<float>(value);
145 break;
147 m_spring_stiffness = static_cast<float>(value);
148 break;
150 m_repulsion_strength = static_cast<float>(value);
151 break;
153 m_spatial_interactions_enabled = (value > 0.5);
154 break;
156 m_point_size = static_cast<float>(value);
157 for (auto& group : m_collections) {
158 auto& points = group.collection->get_points();
159 for (auto& pt : points) {
160 pt.size = m_point_size;
161 }
162 }
163 break;
165 m_attraction_strength = static_cast<float>(value);
166 break;
168 m_turbulence_strength = static_cast<float>(value);
169 break;
170 }
171}
172
173std::optional<double> PhysicsOperator::query_state(std::string_view query) const
174{
175 if (query == "point_count") {
176 return static_cast<double>(get_point_count());
177 }
178 if (query == "collection_count") {
179 return static_cast<double>(m_collections.size());
180 }
181 if (query == "avg_velocity") {
182 glm::vec3 avg(0.0F);
183 size_t total_points = 0;
184
185 for (const auto& group : m_collections) {
186 for (const auto& state : group.physics_state) {
187 avg += state.velocity;
188 ++total_points;
189 }
190 }
191
192 if (total_points > 0) {
193 avg /= static_cast<float>(total_points);
194 }
195 return static_cast<double>(glm::length(avg));
196 }
197
198 return std::nullopt;
199}
200
201//-----------------------------------------------------------------------------
202// GraphicsOperator Interface (Data Extraction)
203//-----------------------------------------------------------------------------
204
205std::vector<PointVertex> PhysicsOperator::extract_vertices() const
206{
207 std::vector<PointVertex> positions;
208
209 for (const auto& group : m_collections) {
210 const auto& points = group.collection->get_points();
211 for (const auto& pt : points) {
212 positions.push_back(pt);
213 }
214 }
215
216 return positions;
217}
218
219std::span<const uint8_t> PhysicsOperator::get_vertex_data_for_collection(uint32_t idx) const
220{
221 if (m_collections.empty() || idx >= m_collections.size()) {
222 return {};
223 }
224 return m_collections[idx].collection->get_vertex_data();
225}
226
227std::span<const uint8_t> PhysicsOperator::get_vertex_data() const
228{
230 for (const auto& group : m_collections) {
231 auto span = group.collection->get_vertex_data();
234 span.begin(), span.end());
235 }
236 return { m_vertex_data_aggregate.data(), m_vertex_data_aggregate.size() };
237}
238
239std::optional<double> PhysicsOperator::get_particle_velocity(size_t global_index) const
240{
241 size_t current_offset = 0;
242
243 for (const auto& group : m_collections) {
244 size_t group_size = group.physics_state.size();
245
246 if (global_index < current_offset + group_size) {
247 size_t local_index = global_index - current_offset;
248 return static_cast<double>(glm::length(group.physics_state[local_index].velocity));
249 }
250
251 current_offset += group_size;
252 }
253
254 return std::nullopt;
255}
256
258{
259 if (m_collections.empty()) {
260 return {};
261 }
262
263 auto layout_opt = m_collections[0].collection->get_vertex_layout();
264 if (!layout_opt.has_value()) {
265 return {};
266 }
267
268 return *layout_opt;
269}
270
272{
273 size_t total = 0;
274 for (const auto& group : m_collections) {
275 total += group.collection->get_vertex_count();
276 }
277 return total;
278}
279
281{
282 return std::ranges::any_of(
284 [](const auto& group) { return group.collection->needs_gpu_update(); });
285}
286
288{
289 for (auto& group : m_collections) {
290 group.collection->mark_vertex_data_dirty(false);
291 }
292}
293
295{
296 size_t total = 0;
297 for (const auto& group : m_collections) {
298 total += group.collection->get_point_count();
299 }
300 return total;
301}
302
303void PhysicsOperator::set_bounds(const glm::vec3& min, const glm::vec3& max)
304{
305 m_bounds.min = min;
306 m_bounds.max = max;
307}
308
309void PhysicsOperator::set_attraction_point(const glm::vec3& point)
310{
311 m_attraction_point = point;
313}
314
315//-----------------------------------------------------------------------------
316// ONE_TO_ONE Parameter Mapping
317//-----------------------------------------------------------------------------
318
320 std::string_view param,
321 const std::shared_ptr<NodeNetwork>& source)
322{
323 size_t point_count = get_point_count();
324
325 if (source->get_node_count() != point_count) {
327 "ONE_TO_ONE size mismatch: {} particles vs {} source nodes",
328 point_count, source->get_node_count());
329 return;
330 }
331
332 if (param == "force_x" || param == "force_y" || param == "force_z") {
333 apply_per_particle_force(param, source);
334 } else if (param == "mass") {
336 } else {
338 }
339}
340
342 std::string_view param,
343 const std::shared_ptr<NodeNetwork>& source)
344{
345 size_t global_index = 0;
346
347 for (auto& group : m_collections) {
348 for (auto& i : group.physics_state) {
349 auto val = source->get_node_output(global_index++);
350 if (!val)
351 continue;
352
353 auto force = static_cast<float>(*val);
354
355 if (param == "force_x") {
356 i.force.x += force;
357 } else if (param == "force_y") {
358 i.force.y += force;
359 } else if (param == "force_z") {
360 i.force.z += force;
361 }
362 }
363 }
364}
365
367 const std::shared_ptr<NodeNetwork>& source)
368{
369 size_t global_index = 0;
370
371 for (auto& group : m_collections) {
372 for (auto& i : group.physics_state) {
373 auto val = source->get_node_output(global_index++);
374 if (!val)
375 continue;
376
377 i.mass = std::max(0.1F, static_cast<float>(*val));
378 }
379 }
380}
381
382//-----------------------------------------------------------------------------
383// Physics Simulation
384//-----------------------------------------------------------------------------
385
387{
388 for (auto& group : m_collections) {
389 for (auto& state : group.physics_state) {
390 state.force = m_gravity * state.mass;
391 }
392 }
393
396 }
397
398 if (m_turbulence_strength > 0.001F) {
400 }
401
404 }
405}
406
408{
409 for (auto& group : m_collections) {
410 for (auto& state : group.physics_state) {
411 glm::vec3 random_force(
412 m_random_generator(-1.0F, 1.0F),
413 m_random_generator(-1.0F, 1.0F),
414 m_random_generator(-1.0F, 1.0F));
415 state.force += random_force * m_turbulence_strength;
416 }
417 }
418}
419
421{
422 for (size_t g1 = 0; g1 < m_collections.size(); ++g1) {
423 auto& group1 = m_collections[g1];
424 auto& points1 = group1.collection->get_points();
425
426 for (size_t i = 0; i < points1.size(); ++i) {
427 const auto& pos_i = points1[i].position;
428 auto& state_i = group1.physics_state[i];
429
430 for (size_t g2 = 0; g2 < m_collections.size(); ++g2) {
431 auto& group2 = m_collections[g2];
432 auto& points2 = group2.collection->get_points();
433
434 size_t start_j = (g1 == g2) ? i + 1 : 0;
435
436 for (size_t j = start_j; j < points2.size(); ++j) {
437 const auto& pos_j = points2[j].position;
438 auto& state_j = group2.physics_state[j];
439
440 glm::vec3 delta = pos_j - pos_i;
441 float distance = glm::length(delta);
442
443 if (distance < m_interaction_radius && distance > 0.001F) {
444 glm::vec3 direction = delta / distance;
445
446 float spring_force = m_spring_stiffness * (distance - m_interaction_radius * 0.5F);
447
448 float repulsion_force = 0.0F;
449 if (distance < m_interaction_radius * 0.3F) {
450 repulsion_force = m_repulsion_strength / (distance * distance);
451 }
452
453 glm::vec3 force = direction * (spring_force - repulsion_force);
454
455 state_i.force += force;
456 state_j.force -= force;
457 }
458 }
459 }
460 }
461 }
462}
463
465{
466 for (auto& group : m_collections) {
467 auto& points = group.collection->get_points();
468
469 for (size_t i = 0; i < points.size(); ++i) {
470 glm::vec3 to_attractor = m_attraction_point - points[i].position;
471 float distance = glm::length(to_attractor);
472
473 if (distance > 0.001F) {
474 glm::vec3 direction = to_attractor / distance;
475 float force_magnitude = m_attraction_strength / std::max(distance * distance, 0.1F);
476 group.physics_state[i].force += direction * force_magnitude * group.physics_state[i].mass;
477 }
478 }
479 }
480}
481
483{
484 for (auto& group : m_collections) {
485 auto& points = group.collection->get_points();
486
487 for (size_t i = 0; i < points.size(); ++i) {
488 auto& state = group.physics_state[i];
489 auto& vertex = points[i];
490
491 glm::vec3 acceleration = state.force / state.mass;
492 state.velocity += acceleration * dt;
493 state.velocity *= (1.0F - m_drag);
494 vertex.position += state.velocity * dt;
495
496 state.force = glm::vec3(0.0F);
497 }
498 }
499}
500
502{
504 return;
505 }
506
507 constexpr float damping = 0.8F;
508
509 for (auto& group : m_collections) {
510 auto& points = group.collection->get_points();
511
512 for (size_t i = 0; i < points.size(); ++i) {
513 auto& vertex = points[i];
514 auto& state = group.physics_state[i];
515
516 for (int axis = 0; axis < 3; ++axis) {
517 if (vertex.position[axis] < m_bounds.min[axis]) {
518 switch (m_bounds_mode) {
520 vertex.position[axis] = m_bounds.min[axis];
521 state.velocity[axis] *= -damping;
522 break;
523 case BoundsMode::WRAP:
524 vertex.position[axis] = m_bounds.max[axis];
525 break;
527 vertex.position[axis] = m_bounds.min[axis];
528 state.velocity[axis] = 0.0F;
529 break;
530 case BoundsMode::NONE:
531 break;
532 }
533 } else if (vertex.position[axis] > m_bounds.max[axis]) {
534 switch (m_bounds_mode) {
536 vertex.position[axis] = m_bounds.max[axis];
537 state.velocity[axis] *= -damping;
538 break;
539 case BoundsMode::WRAP:
540 vertex.position[axis] = m_bounds.min[axis];
541 break;
543 vertex.position[axis] = m_bounds.max[axis];
544 state.velocity[axis] = 0.0F;
545 break;
546 case BoundsMode::NONE:
547 break;
548 }
549 }
550 }
551 }
552 }
553}
554
556{
557 for (auto& group : m_collections) {
558 group.collection->mark_vertex_data_dirty(true);
559 }
560}
561
562void* PhysicsOperator::get_data_at(size_t global_index)
563{
564 size_t offset = 0;
565 for (auto& group : m_collections) {
566 if (global_index < offset + group.collection->get_point_count()) {
567 size_t local_index = global_index - offset;
568 return &group.collection->get_points()[local_index];
569 }
570 offset += group.collection->get_point_count();
571 }
572 return nullptr;
573}
574
575void PhysicsOperator::apply_global_impulse(const glm::vec3& impulse)
576{
577 for (auto& group : m_collections) {
578 for (auto& state : group.physics_state) {
579 state.velocity += impulse / state.mass;
580 }
581 }
582}
583
584void PhysicsOperator::apply_impulse(size_t index, const glm::vec3& impulse)
585{
586 size_t offset = 0;
587 for (auto& group : m_collections) {
588 if (index < offset + group.collection->get_point_count()) {
589 size_t local_index = index - offset;
590 group.physics_state[local_index].velocity += impulse / group.physics_state[local_index].mass;
591 return;
592 }
593 offset += group.collection->get_point_count();
594 }
595}
596
597} // namespace MayaFlux::Nodes::Network
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
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.
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 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.
Complete description of vertex data layout in a buffer.
std::shared_ptr< GpuSync::PointCollectionNode > collection
Physics-specific data parallel to PointVertex array.