MayaFlux 0.5.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_max_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_control_points.reserve(max_control_points);
74 m_vertices.reserve(samples_per_segment * max_control_points);
75
77 "Created PathGeneratorNode with mode {}, {} samples per segment, capacity {}",
78 static_cast<int>(mode), samples_per_segment, max_control_points);
79}
80
82 CustomPathFunction custom_func,
83 Eigen::Index samples_per_segment,
84 size_t max_control_points)
85 : GeometryWriterNode(static_cast<uint32_t>(samples_per_segment * 10))
86 , m_mode(Kinesis::InterpolationMode::CUSTOM)
87 , m_custom_func(std::move(custom_func))
88 , m_max_control_points(max_control_points)
89 , m_samples_per_segment(samples_per_segment)
90 , m_tension(0.5)
91{
92 const auto& stride = sizeof(LineVertex);
93 set_vertex_stride(stride);
94
95 auto layout = Kakshya::VertexLayout::for_lines(stride);
96 layout.vertex_count = 0;
97 set_vertex_layout(layout);
98
99 m_control_points.reserve(max_control_points);
100 m_vertices.reserve(samples_per_segment * max_control_points);
101
103 "Created PathGeneratorNode with custom function");
104}
105
107{
108 m_control_points.insert(m_control_points.begin(), vertex);
110 m_control_points.pop_back();
111 }
112 m_geometry_dirty = true;
113 m_vertex_data_dirty = true;
114}
115
117{
118 return static_cast<size_t>(std::max<Eigen::Index>(m_samples_per_segment - 1, 0)) * 2;
119}
120
122 const std::vector<LineVertex>& curve_verts,
123 size_t start_idx,
124 LineVertex* dst) const
125{
126 if (m_samples_per_segment < 2) {
127 return;
128 }
129
130 const auto span = static_cast<size_t>(m_samples_per_segment - 1);
131
132 for (size_t i = 0; i < span; ++i) {
133 const size_t c0 = start_idx + std::min((i * 3) / span, size_t(3));
134 const size_t c1 = start_idx + std::min(((i + 1) * 3) / span, size_t(3));
135
136 LineVertex& v0 = dst[i * 2];
137 LineVertex& v1 = dst[i * 2 + 1];
138
139 v0.color = m_force_uniform_color ? m_current_color : curve_verts[c0].color;
140 v1.color = m_force_uniform_color ? m_current_color : curve_verts[c1].color;
141
142 v0.thickness = m_force_uniform_thickness ? m_current_thickness : curve_verts[c0].thickness;
143 v1.thickness = m_force_uniform_thickness ? m_current_thickness : curve_verts[c1].thickness;
144 }
145}
146
148 const std::vector<LineVertex>& curve_verts,
149 size_t start_idx,
150 LineVertex* dst)
151{
152 if (start_idx + 3 >= curve_verts.size()) {
153 return;
154 }
155
157
158 for (size_t i = 0; i < 4; ++i) {
159 const auto& pt = curve_verts[start_idx + i].position;
160 m_segment_controls[i * 3 + 0] = pt.x;
161 m_segment_controls[i * 3 + 1] = pt.y;
162 m_segment_controls[i * 3 + 2] = pt.z;
163 }
164
166
167 const std::vector<double>* curve = &m_curve_primary;
168
172 curve = &m_curve_secondary;
173 }
174
175 const auto count = static_cast<size_t>(m_samples_per_segment);
176 if (count < 2) {
177 return;
178 }
179
180 const double* x = curve->data();
181 const double* y = x + count;
182 const double* z = y + count;
183
184 for (size_t i = 0; i < count - 1; ++i) {
185 dst[i * 2].position = {
186 static_cast<float>(x[i]), static_cast<float>(y[i]), static_cast<float>(z[i])
187 };
188 dst[i * 2 + 1].position = {
189 static_cast<float>(x[i + 1]), static_cast<float>(y[i + 1]), static_cast<float>(z[i + 1])
190 };
191 }
192
193 write_segment_attributes(curve_verts, start_idx, dst);
194}
195
197{
199 const size_t n = std::min(m_vertices.size(), m_control_points.size());
200 for (size_t i = 0; i < n; ++i) {
203 }
204 return;
205 }
206
208 const size_t num_points = m_control_points.size();
209 const size_t total = m_vertices.size();
210 if (num_points == 0 || total < 2) {
211 return;
212 }
213 for (size_t i = 0; i < total; ++i) {
214 const size_t ctrl_idx = std::min((i * (num_points - 1)) / (total - 1), num_points - 1);
216 m_vertices[i].thickness = m_force_uniform_thickness ? m_current_thickness : m_control_points[ctrl_idx].thickness;
217 }
218 return;
219 }
220
221 if (m_control_points.size() < 4) {
222 return;
223 }
224
225 const size_t per_window = vertices_per_window();
226 const size_t windows = m_control_points.size() - 3;
227
228 if (m_vertices.size() < windows * per_window) {
229 return;
230 }
231
232 for (size_t w = 0; w < windows; ++w) {
233 write_segment_attributes(m_control_points, w, m_vertices.data() + w * per_window);
234 }
235}
236
238 const LineVertex& v0,
239 const LineVertex& v1,
240 std::vector<LineVertex>& output)
241{
242 glm::vec3 color0 = m_force_uniform_color ? m_current_color : v0.color;
243 glm::vec3 color1 = m_force_uniform_color ? m_current_color : v1.color;
244
247
248 output.push_back({ .position = v0.position, .color = color0, .thickness = thick0 });
249 output.push_back({ .position = v1.position, .color = color1, .thickness = thick1 });
250}
251
253{
254 m_draw_window.push_back(vertex);
255
256 if (m_draw_window.size() == 1) {
257 m_draw_vertices.push_back({ vertex });
258 } else {
260 m_draw_window[m_draw_window.size() - 2],
261 vertex,
263 }
264
265 m_vertex_data_dirty = true;
266}
267
268void PathGeneratorNode::set_control_points(const std::vector<LineVertex>& vertices)
269{
270 m_control_points.assign(vertices.rbegin(), vertices.rend());
272 m_control_points.erase(
273 m_control_points.begin() + static_cast<std::ptrdiff_t>(m_max_control_points),
274 m_control_points.end());
275 }
276
277 m_vertex_data_dirty = true;
278 m_geometry_dirty = true;
279}
280
282{
283 if (index >= m_control_points.size()) {
285 "Control point index {} out of range (count: {})",
286 index, m_control_points.size());
287 return;
288 }
289
290 m_control_points[index] = vertex;
291
292 auto range = calculate_affected_segment_range(
293 index,
294 m_control_points.size(),
295 m_mode,
297
299 m_dirty_segment_start = range.start_control_idx;
300 m_dirty_segment_end = range.end_control_idx;
301 } else {
302 m_dirty_segment_start = std::min(m_dirty_segment_start, range.start_control_idx);
303 m_dirty_segment_end = std::max(m_dirty_segment_end, range.end_control_idx);
304 }
305
306 m_geometry_dirty = true;
307 m_vertex_data_dirty = true;
308}
309
311{
312 if (index >= m_control_points.size()) {
314 "Control point index {} out of range (count: {})",
315 index, m_control_points.size());
316 return {};
317 }
318
319 return m_control_points[index];
320}
321
322std::vector<LineVertex> PathGeneratorNode::get_control_points() const
323{
324 return m_control_points;
325}
326
340
341void PathGeneratorNode::set_path_color(const glm::vec3& color, bool force_uniform)
342{
343 m_current_color = color;
344 m_force_uniform_color = force_uniform;
345 m_attributes_dirty = true;
346 m_vertex_data_dirty = true;
347
349 for (auto& v : m_completed_draws)
350 v.color = color;
351
352 for (auto& v : m_draw_vertices)
353 v.color = color;
354 }
355}
356
358{
359 set_path_color(m_current_color, should_force);
360}
361
362void PathGeneratorNode::set_path_thickness(float thickness, bool force_uniform)
363{
364 m_current_thickness = thickness;
365 m_force_uniform_thickness = force_uniform;
366 m_attributes_dirty = true;
367 m_vertex_data_dirty = true;
368
370 for (auto& v : m_completed_draws)
371 v.thickness = thickness;
372
373 for (auto& v : m_draw_vertices)
374 v.thickness = thickness;
375 }
376}
377
379{
381}
382
391
400
409
418
420{
421 m_vertices.clear();
422
423 const size_t num_points = m_control_points.size();
424 if (num_points < 2) {
425 return;
426 }
427
432 } else {
434 }
435
436 m_vertex_data_dirty = true;
437}
438
440{
441 m_vertices.reserve(m_control_points.size());
442
443 for (const auto& v : m_control_points) {
444 glm::vec3 color = m_force_uniform_color ? m_current_color : v.color;
445 float thickness = m_force_uniform_thickness ? m_current_thickness : v.thickness;
446
447 m_vertices.emplace_back(LineVertex {
448 .position = v.position,
449 .color = color,
450 .thickness = thickness });
451 }
452}
453
455{
456 const size_t num_points = m_control_points.size();
457 size_t total_samples = m_samples_per_segment * (num_points - 1);
458
459 m_vertices.resize(total_samples);
460
461 for (Eigen::Index i = 0; i < total_samples; ++i) {
462 double t = static_cast<double>(i) / static_cast<double>(total_samples - 1);
463
464 auto ctrl_idx = std::min<size_t>(static_cast<size_t>(t * float(num_points - 1)), num_points - 1);
465 glm::vec3 color = m_force_uniform_color ? m_current_color : m_control_points[ctrl_idx].color;
466 float thickness = m_force_uniform_thickness ? m_current_thickness : m_control_points[ctrl_idx].thickness;
467
470 .color = color,
471 .thickness = thickness
472 };
473 }
474}
475
477{
478 if (m_control_points.size() < 4) {
479 return;
480 }
481
482 const size_t windows = m_control_points.size() - 3;
483 const size_t per_window = vertices_per_window();
484 const size_t base = m_vertices.size();
485
486 m_vertices.resize(base + windows * per_window);
487
488 for (size_t w = 0; w < windows; ++w) {
489 write_curve_segment(m_control_points, w, m_vertices.data() + base + w * per_window);
490 }
491}
492
510
511void PathGeneratorNode::regenerate_segment_range(size_t start_ctrl_idx, size_t end_ctrl_idx)
512{
513 const size_t num_points = m_control_points.size();
514
515 if (start_ctrl_idx >= num_points || end_ctrl_idx >= num_points) {
517 "Invalid segment range [{}, {}] for {} control points",
518 start_ctrl_idx, end_ctrl_idx, num_points);
519 return;
520 }
521
522 m_range_cache.clear();
523 m_range_cache.reserve(end_ctrl_idx - start_ctrl_idx + 1);
524 for (size_t i = start_ctrl_idx; i <= end_ctrl_idx; ++i) {
525 m_range_cache.push_back(m_control_points[i]);
526 }
527
528 if (m_range_cache.size() < 4) {
529 return;
530 }
531
532 const size_t per_window = vertices_per_window();
533 const size_t windows = m_range_cache.size() - 3;
534 const size_t start_vertex_idx = start_ctrl_idx * per_window;
535
536 if (start_vertex_idx + windows * per_window > m_vertices.size()) {
537 m_vertices.resize(start_vertex_idx + windows * per_window);
538 }
539
540 for (size_t w = 0; w < windows; ++w) {
542 m_vertices.data() + start_vertex_idx + w * per_window);
543 }
544}
545
547{
548 if (m_control_points.empty() && m_draw_vertices.empty() && m_completed_draws.empty()) {
550 return;
551 }
552
553 if (m_geometry_dirty) {
555 m_attributes_dirty = false;
556 } else if (m_attributes_dirty) {
558 m_attributes_dirty = false;
559 m_vertex_data_dirty = true;
560 }
561
562 if (!m_vertex_data_dirty) {
563 return;
564 }
565
566 m_combined_cache.clear();
567 m_combined_cache.reserve(m_vertices.size() + m_completed_draws.size() + m_draw_vertices.size());
568 m_combined_cache.insert(m_combined_cache.end(), m_vertices.begin(), m_vertices.end());
571
572 if (m_combined_cache.empty()) {
574 return;
575 }
576
577#ifdef MAYAFLUX_PLATFORM_MACOS
578 m_expand_cache = expand_lines_to_triangles(m_combined_cache);
579 set_vertices<LineVertex>(std::span { m_expand_cache.data(), m_expand_cache.size() });
580
581 auto layout = get_vertex_layout();
582 layout->vertex_count = static_cast<uint32_t>(m_expand_cache.size());
583 set_vertex_layout(*layout);
584#else
585 set_vertices<LineVertex>(std::span { m_combined_cache.data(), m_combined_cache.size() });
586
587 auto layout = get_vertex_layout();
588 layout->vertex_count = static_cast<uint32_t>(m_combined_cache.size());
589 set_vertex_layout(*layout);
590#endif
591
592 m_vertex_data_dirty = false;
593}
594
596{
597 if (m_draw_window.size() < 4) {
599 "Not enough points in draw window to generate curve segment ({} points)",
600 m_draw_window.size());
601 m_completed_draws.insert(
602 m_completed_draws.end(),
603 m_draw_vertices.begin(),
604 m_draw_vertices.end());
605 m_draw_vertices.clear();
606 m_draw_window.clear();
607 m_vertex_data_dirty = true;
608 return;
609 }
610
611 const size_t per_window = vertices_per_window();
612 const size_t windows = m_draw_window.size() - 3;
613 const size_t base = m_completed_draws.size();
614
615 m_completed_draws.resize(base + windows * per_window);
616
617 for (size_t w = 0; w < windows; ++w) {
618 write_curve_segment(m_draw_window, w, m_completed_draws.data() + base + w * per_window);
619 }
620
621 for (size_t i = windows + 1; i < m_draw_window.size(); ++i) {
623 }
624
625 m_draw_vertices.clear();
626 m_draw_window.clear();
627 m_vertex_data_dirty = true;
628}
629
630} // 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
uint32_t index
Definition VKDevice.cpp:142
size_t count
std::shared_ptr< Core::VKImage > output
void reparameterize_planar(std::span< const double > points, size_t dim, Eigen::Index point_count, Eigen::Index num_samples, std::vector< double > &out)
Resample a polyline to uniform arc length, coordinate-major.
void evaluate_planar(std::span< const double > control_points, size_t dim, Eigen::Index num_samples, std::vector< double > &out)
Evaluate a curve into a coordinate-major buffer.
void configure(InterpolationMode mode, double tension)
Re-resolve the kernel.
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.
void refresh_attributes()
Rewrite colour and thickness over existing geometry, no curve evaluation.
size_t vertices_per_window() const
Vertices emitted per four-point window.
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 write_curve_segment(const std::vector< LineVertex > &curve_verts, size_t start_idx, LineVertex *dst)
Evaluate one window and write its vertices.
std::vector< LineVertex > m_control_points
Control points, newest first: index 0 is the most recently added.
void force_uniform_color(bool should_force)
Set uniform color mode.
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 write_segment_attributes(const std::vector< LineVertex > &curve_verts, size_t start_idx, LineVertex *dst) const
Write colour and thickness for one window, leaving positions intact.
void draw_to(const LineVertex &vertex)
Extend path with full LineVertex data.
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
std::vector< double > range(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Value range (max - min) per window.
Definition Analysis.cpp:452
InterpolationMode
Mathematical interpolation methods.
Kakshya::LineVertex LineVertex
Definition VertexSpec.hpp:8
Vertex type for line primitives (LINE_LIST / LINE_STRIP topology)
static VertexLayout for_lines(uint32_t stride=60)
Factory: layout for LineVertex (position, color, thickness, uv, normal, tangent)