MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PathGeneratorNode.cpp
Go to the documentation of this file.
2
4
6
7namespace {
8 struct SegmentRange {
11
14 };
15
16 SegmentRange calculate_affected_segment_range(
17 size_t control_idx,
18 size_t total_controls,
20 Eigen::Index samples_per_segment)
21 {
22 SegmentRange range {};
23
24 switch (mode) {
27 range.start_control_idx = (control_idx > 0) ? control_idx - 1 : 0;
28 range.end_control_idx = std::min(control_idx + 2, total_controls - 1);
29 break;
30
33 range.start_control_idx = (control_idx / 4) * 4;
34 range.end_control_idx = std::min(range.start_control_idx + 3, total_controls - 1);
35 break;
36
38 range.start_control_idx = (control_idx / 3) * 3;
39 range.end_control_idx = std::min(range.start_control_idx + 2, total_controls - 1);
40 break;
41
42 default:
43 range.start_control_idx = control_idx;
44 range.end_control_idx = control_idx;
45 break;
46 }
47
48 range.start_vertex_idx = range.start_control_idx * samples_per_segment;
49 range.end_vertex_idx = (range.end_control_idx + 1) * samples_per_segment;
50
51 return range;
52 }
53}
54
57 Eigen::Index samples_per_segment,
58 size_t max_control_points,
59 double tension)
60 : GeometryWriterNode(static_cast<uint32_t>(samples_per_segment * 10))
61 , m_mode(mode)
62 , m_control_points(max_control_points)
63 , m_samples_per_segment(samples_per_segment)
64 , m_tension(tension)
65{
66 const auto& stride = sizeof(LineVertex);
67 set_vertex_stride(stride);
68
69 auto layout = Kakshya::VertexLayout::for_lines(stride);
70 layout.vertex_count = 0;
71 set_vertex_layout(layout);
72
73 m_vertices.reserve(samples_per_segment * max_control_points);
74
76 "Created PathGeneratorNode with mode {}, {} samples per segment, capacity {}",
77 static_cast<int>(mode), samples_per_segment, max_control_points);
78}
79
81 CustomPathFunction custom_func,
82 Eigen::Index samples_per_segment,
83 size_t max_control_points)
84 : GeometryWriterNode(static_cast<uint32_t>(samples_per_segment * 10))
85 , m_mode(Kinesis::InterpolationMode::CUSTOM)
86 , m_custom_func(std::move(custom_func))
87 , m_control_points(max_control_points)
88 , m_samples_per_segment(samples_per_segment)
89 , m_tension(0.5)
90{
91 const auto& stride = sizeof(LineVertex);
92 set_vertex_stride(stride);
93
94 auto layout = Kakshya::VertexLayout::for_lines(stride);
95 layout.vertex_count = 0;
96 set_vertex_layout(layout);
97
98 m_vertices.reserve(samples_per_segment * max_control_points);
99
101 "Created PathGeneratorNode with custom function");
102}
103
105{
106 m_control_points.push(vertex);
107 m_geometry_dirty = true;
108 m_vertex_data_dirty = true;
109}
110
112 const std::vector<LineVertex>& curve_verts,
113 size_t start_idx,
114 std::vector<LineVertex>& output)
115{
116 if (start_idx + 3 >= curve_verts.size()) {
117 return;
118 }
119
120 Eigen::MatrixXd segment_controls(3, 4);
121 for (Eigen::Index i = 0; i < 4; ++i) {
122 const auto& pt = curve_verts[start_idx + i].position;
123 segment_controls.col(i) << pt.x, pt.y, pt.z;
124 }
125
126 Eigen::MatrixXd interpolated = Kinesis::generate_interpolated_points(
127 segment_controls,
129 m_mode,
130 m_tension);
131
134 interpolated,
136 }
137
138 for (Eigen::Index i = 0; i < interpolated.cols() - 1; ++i) {
139 float t0 = static_cast<float>(i) / static_cast<float>(interpolated.cols() - 1);
140 float t1 = static_cast<float>(i + 1) / static_cast<float>(interpolated.cols() - 1);
141
142 size_t ctrl_idx0 = start_idx + std::min(static_cast<size_t>(t0 * 3), size_t(3));
143 size_t ctrl_idx1 = start_idx + std::min(static_cast<size_t>(t1 * 3), size_t(3));
144
145 glm::vec3 color0 = m_force_uniform_color ? m_current_color : curve_verts[ctrl_idx0].color;
146 glm::vec3 color1 = m_force_uniform_color ? m_current_color : curve_verts[ctrl_idx1].color;
147
148 float thick0 = m_force_uniform_thickness ? m_current_thickness : curve_verts[ctrl_idx0].thickness;
149 float thick1 = m_force_uniform_thickness ? m_current_thickness : curve_verts[ctrl_idx1].thickness;
150
151 glm::vec3 p0(interpolated(0, i), interpolated(1, i), interpolated(2, i));
152 glm::vec3 p1(interpolated(0, i + 1), interpolated(1, i + 1), interpolated(2, i + 1));
153
154 output.push_back({ p0, color0, thick0 });
155 output.push_back({ p1, color1, thick1 });
156 }
157}
158
160 const LineVertex& v0,
161 const LineVertex& v1,
162 std::vector<LineVertex>& output)
163{
164 glm::vec3 color0 = m_force_uniform_color ? m_current_color : v0.color;
165 glm::vec3 color1 = m_force_uniform_color ? m_current_color : v1.color;
166
169
170 output.push_back({ v0.position, color0, thick0 });
171 output.push_back({ v1.position, color1, thick1 });
172}
173
175{
176 m_draw_window.push_back(vertex);
177
178 if (m_draw_window.size() == 1) {
179 m_draw_vertices.push_back({ vertex });
180 } else {
182 m_draw_window[m_draw_window.size() - 2],
183 vertex,
185 }
186
187 m_vertex_data_dirty = true;
188}
189
190void PathGeneratorNode::set_control_points(const std::vector<LineVertex>& vertices)
191{
192 m_control_points.reset();
193
194 for (const auto& v : vertices) {
195 m_control_points.push(v);
196 }
197
198 m_vertex_data_dirty = true;
199 m_geometry_dirty = true;
200}
201
203{
204 if (index >= m_control_points.size()) {
206 "Control point index {} out of range (count: {})",
207 index, m_control_points.size());
208 return;
209 }
210
211 m_control_points.update(index, vertex);
212
213 auto range = calculate_affected_segment_range(
214 index,
215 m_control_points.size(),
216 m_mode,
218
220 m_dirty_segment_start = range.start_control_idx;
221 m_dirty_segment_end = range.end_control_idx;
222 } else {
223 m_dirty_segment_start = std::min(m_dirty_segment_start, range.start_control_idx);
224 m_dirty_segment_end = std::max(m_dirty_segment_end, range.end_control_idx);
225 }
226
227 m_geometry_dirty = true;
228 m_vertex_data_dirty = true;
229}
230
232{
233 if (index >= m_control_points.size()) {
235 "Control point index {} out of range (count: {})",
236 index, m_control_points.size());
237 return {};
238 }
239
240 return m_control_points[index];
241}
242
243std::vector<LineVertex> PathGeneratorNode::get_control_points() const
244{
245 auto view = m_control_points.linearized_view();
246 std::vector<LineVertex> positions;
247 positions.reserve(view.size());
248 for (const auto& v : view) {
249 positions.push_back(v);
250 }
251 return positions;
252}
253
267
268void PathGeneratorNode::set_path_color(const glm::vec3& color, bool force_uniform)
269{
270 m_current_color = color;
271 m_force_uniform_color = force_uniform;
272 m_vertex_data_dirty = true;
273 m_geometry_dirty = true;
274
276 for (auto& v : m_completed_draws)
277 v.color = color;
278
279 for (auto& v : m_draw_vertices)
280 v.color = color;
281 }
282}
283
285{
286 set_path_color(m_current_color, should_force);
287}
288
289void PathGeneratorNode::set_path_thickness(float thickness, bool force_uniform)
290{
291 m_current_thickness = thickness;
292 m_force_uniform_thickness = force_uniform;
293 m_vertex_data_dirty = true;
294 m_geometry_dirty = true;
295
297 for (auto& v : m_completed_draws)
298 v.thickness = thickness;
299
300 for (auto& v : m_draw_vertices)
301 v.thickness = thickness;
302 }
303}
304
306{
308}
309
318
327
336
345
347{
348 m_vertices.clear();
349
350 const size_t num_points = m_control_points.size();
351 if (num_points < 2) {
352 return;
353 }
354
359 } else {
361 }
362
363 m_vertex_data_dirty = true;
364}
365
367{
368 auto view = m_control_points.linearized_view();
369 m_vertices.reserve(view.size());
370
371 for (const auto& v : view) {
372 glm::vec3 color = m_force_uniform_color ? m_current_color : v.color;
373 float thickness = m_force_uniform_thickness ? m_current_thickness : v.thickness;
374
375 m_vertices.emplace_back(LineVertex {
376 .position = v.position,
377 .color = color,
378 .thickness = thickness });
379 }
380}
381
383{
384 auto view = m_control_points.linearized_view();
385 const size_t num_points = view.size();
386 size_t total_samples = m_samples_per_segment * (num_points - 1);
387
388 m_vertices.resize(total_samples);
389
390 for (Eigen::Index i = 0; i < total_samples; ++i) {
391 double t = static_cast<double>(i) / static_cast<double>(total_samples - 1);
392
393 auto ctrl_idx = std::min<size_t>(static_cast<size_t>(t * float(num_points - 1)), num_points - 1);
394 glm::vec3 color = m_force_uniform_color ? m_current_color : view[ctrl_idx].color;
395 float thickness = m_force_uniform_thickness ? m_current_thickness : view[ctrl_idx].thickness;
396
398 .position = m_custom_func(view, t),
399 .color = color,
400 .thickness = thickness
401 };
402 }
403}
404
406{
407 auto control_view = m_control_points.linearized_view();
408 std::vector<LineVertex> control_vec(control_view.begin(), control_view.end());
409
410 for (size_t i = 0; i + 3 < control_vec.size(); ++i) {
411 generate_curve_segment(control_vec, i, m_vertices);
412 }
413}
414
432
433void PathGeneratorNode::regenerate_segment_range(size_t start_ctrl_idx, size_t end_ctrl_idx)
434{
435 auto view = m_control_points.linearized_view();
436 const size_t num_points = view.size();
437
438 if (start_ctrl_idx >= num_points || end_ctrl_idx >= num_points) {
440 "Invalid segment range [{}, {}] for {} control points",
441 start_ctrl_idx, end_ctrl_idx, num_points);
442 return;
443 }
444
445 std::vector<LineVertex> segment_verts;
446 for (size_t i = start_ctrl_idx; i <= end_ctrl_idx; ++i) {
447 segment_verts.push_back(view[i]);
448 }
449
450 size_t start_vertex_idx = start_ctrl_idx * m_samples_per_segment * 2;
451
452 std::vector<LineVertex> new_segment;
453
454 for (size_t i = 0; i + 3 < segment_verts.size(); ++i) {
455 generate_curve_segment(segment_verts, i, new_segment);
456 }
457
458 if (start_vertex_idx + new_segment.size() > m_vertices.size()) {
459 m_vertices.resize(start_vertex_idx + new_segment.size());
460 }
461
462 std::ranges::copy(new_segment, m_vertices.begin() + (long)start_vertex_idx);
463}
464
466{
467 if (m_control_points.empty() && m_draw_vertices.empty() && m_completed_draws.empty()) {
469 return;
470 }
471
472 if (m_geometry_dirty) {
474 m_geometry_dirty = false;
475 }
476
477 if (!m_vertex_data_dirty) {
478 return;
479 }
480
481 m_combined_cache.clear();
482 m_combined_cache.reserve(m_vertices.size() + m_completed_draws.size() + m_draw_vertices.size());
483 m_combined_cache.insert(m_combined_cache.end(), m_vertices.begin(), m_vertices.end());
486
487 if (m_combined_cache.empty()) {
489 return;
490 }
491
492#ifdef MAYAFLUX_PLATFORM_MACOS
493 std::vector<LineVertex> expanded = expand_lines_to_triangles(m_combined_cache);
494 set_vertices<LineVertex>(std::span { expanded.data(), expanded.size() });
495
496 auto layout = get_vertex_layout();
497 layout->vertex_count = static_cast<uint32_t>(expanded.size());
498 set_vertex_layout(*layout);
499#else
500 set_vertices<LineVertex>(std::span { m_combined_cache.data(), m_combined_cache.size() });
501
502 auto layout = get_vertex_layout();
503 layout->vertex_count = static_cast<uint32_t>(m_combined_cache.size());
504 set_vertex_layout(*layout);
505#endif
506}
507
509{
510 if (m_draw_window.size() < 4) {
512 "Not enough points in draw window to generate curve segment ({} points)",
513 m_draw_window.size());
514 m_completed_draws.insert(
515 m_completed_draws.end(),
516 m_draw_vertices.begin(),
517 m_draw_vertices.end());
518 m_draw_vertices.clear();
519 m_draw_window.clear();
520 m_vertex_data_dirty = true;
521 return;
522 }
523
524 std::vector<LineVertex> smoothed;
525
526 size_t start_idx = 0;
527 while (start_idx + 3 < m_draw_window.size()) {
528 generate_curve_segment(m_draw_window, start_idx, smoothed);
529 start_idx++;
530 }
531
532 for (size_t i = start_idx + 1; i < m_draw_window.size(); ++i) {
533 append_line_segment(m_draw_window[i - 1], m_draw_window[i], smoothed);
534 }
535
536 m_completed_draws.insert(
537 m_completed_draws.end(),
538 smoothed.begin(),
539 smoothed.end());
540
541 m_draw_vertices.clear();
542 m_draw_window.clear();
543 m_vertex_data_dirty = true;
544}
545
546} // namespace MayaFlux::Nodes::GpuSync
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
size_t end_vertex_idx
size_t end_control_idx
size_t start_control_idx
size_t start_vertex_idx
bool m_vertex_data_dirty
Flag: vertex data or layout changed since last GPU upload.
std::optional< Kakshya::VertexLayout > get_vertex_layout() const
Get cached vertex layout.
void set_vertex_layout(const Kakshya::VertexLayout &layout)
Set cached vertex layout.
void resize_vertex_buffer(uint32_t vertex_count, bool preserve_data=false)
Resize vertex buffer to hold specified number of vertices.
void set_vertex_stride(size_t stride)
Set vertex stride (bytes per vertex)
bool m_needs_layout_update
Flag indicating if layout needs update.
Base class for nodes that generate 3D geometry data.
void set_samples_per_segment(Eigen::Index samples)
Set samples per segment.
void compute_frame() override
Compute frame - generates interpolated vertices from control points.
PathGeneratorNode(Kinesis::InterpolationMode mode=Kinesis::InterpolationMode::QUADRATIC_BEZIER, Eigen::Index samples_per_segment=32, size_t max_control_points=64, double tension=0.5)
Create path generator with interpolation mode.
std::function< glm::vec3(std::span< const LineVertex >, double)> CustomPathFunction
void set_tension(double tension)
Set tension parameter (for Catmull-Rom)
void set_path_color(const glm::vec3 &color, bool force_uniform=true)
Set path color (applied to all generated vertices)
void force_uniform_color(bool should_force)
Set uniform color mode.
Memory::HistoryBuffer< LineVertex > m_control_points
void complete()
Finish incremental drawing stroke.
void set_interpolation_mode(Kinesis::InterpolationMode mode)
Set interpolation mode.
void update_control_point(size_t index, const LineVertex &vertex)
Update specific control point with full LineVertex data.
std::vector< LineVertex > get_control_points() const
Get all control points as vector.
LineVertex get_control_point(size_t index) const
Get control point.
void set_path_thickness(float thickness, bool force_uniform=true)
Set path thickness (applied to all generated vertices)
void force_uniform_thickness(bool should_force)
Set uniform thickness mode.
void parameterize_arc_length(bool enable)
Enable/disable arc-length parameterization.
void set_control_points(const std::vector< LineVertex > &vertices)
Set all control points with full LineVertex data.
void add_control_point(const LineVertex &vertex)
Add control point with full LineVertex data.
void append_line_segment(const LineVertex &v0, const LineVertex &v1, std::vector< LineVertex > &output)
void clear_path()
Clear all control points and generated vertices.
void regenerate_segment_range(size_t start_ctrl_idx, size_t end_ctrl_idx)
void draw_to(const LineVertex &vertex)
Extend path with full LineVertex data.
void generate_curve_segment(const std::vector< LineVertex > &curve_verts, size_t start_idx, std::vector< LineVertex > &output)
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
std::vector< Nodes::LineVertex > reparameterize_by_arc_length(const std::vector< Nodes::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
Eigen::MatrixXd generate_interpolated_points(const Eigen::MatrixXd &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Generate interpolated points from control points.
InterpolationMode
Mathematical interpolation methods.
static VertexLayout for_lines(uint32_t stride=36)
Factory: Create layout for line primitives (position, color, thickness)