MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PathGeneratorNode.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
8/**
9 * @class PathGeneratorNode
10 * @brief Generates dense vertex paths from sparse control points or freehand drawing
11 *
12 * Supports two distinct workflows:
13 * 1. Parametric curve editing via control points (full regeneration on changes)
14 * 2. Incremental freehand drawing (append-only strokes with smoothing on completion)
15 *
16 * Both modes use Catmull-Rom or other interpolation methods to generate smooth curves.
17 * Produces LINE_STRIP topology output.
18 *
19 * Philosophy:
20 * - Dual-mode design: parametric editing + incremental drawing in one node
21 * - Structural Reactivity: Only parametric paths (control points) are live. Changing interpolation
22 * modes, tension, or sampling density will rebuild the parametric curve but will NOT
23 * affect completed freehand strokes.
24 * - Control points are sparse input (artist/algorithm provides key positions)
25 * - Freehand strokes draw linearly in real-time, smooth on completion
26 * - Output vertices are dense (GPU draws smooth curves)
27 * - Interpolation happens CPU-side, leveraging Kinesis math primitives
28 * - Fixed memory allocation (real-time safe after construction)
29 * - Visual Reactivity: Changes to color and thickness are applied globally to both live
30 * control-point paths and "baked" freehand strokes.
31 * - Memory Efficiency: Freehand strokes are converted to static vertices upon completion;
32 * raw mouse data is discarded, preventing re-interpolation of baked strokes.
33 *
34 * Parametric Mode Usage:
35 * ```cpp
36 * auto path = std::make_shared<PathGeneratorNode>(
37 * Kinesis::InterpolationMode::CATMULL_ROM,
38 * 32, // 32 vertices between each control point
39 * 100 // Store up to 100 control points
40 * );
41 *
42 * path->add_control_point({glm::vec3(0.0f, 0.0f, 0.0f)});
43 * path->add_control_point({glm::vec3(0.5f, 0.5f, 0.0f)});
44 * path->add_control_point({glm::vec3(1.0f, 0.0f, 0.0f)});
45 * // Entire curve regenerates through all control points
46 *
47 * auto buffer = std::make_shared<GeometryBuffer>(path);
48 * buffer->setup_rendering({
49 * .target_window = window,
50 * .topology = PrimitiveTopology::LINE_STRIP
51 * });
52 * ```
53 *
54 * Freehand Drawing Mode Usage:
55 * ```cpp
56 * auto path = std::make_shared<PathGeneratorNode>(
57 * Kinesis::InterpolationMode::CATMULL_ROM,
58 * 32 // Smoothing resolution
59 * );
60 *
61 * // Real-time drawing (linear segments)
62 * window->on_mouse_move([path](double x, double y) {
63 * if (mouse_button_pressed) {
64 * path->draw_to(screen_to_ndc(x, y)); // Appends linear segment
65 * }
66 * });
67 *
68 * // Smooth the stroke when finished
69 * window->on_mouse_release([path]() {
70 * path->complete(); // Replaces linear segments with smooth curve
71 * });
72 *
73 * auto buffer = std::make_shared<GeometryBuffer>(path);
74 * buffer->setup_rendering({.target_window = window});
75 * ```
76 *
77 * Implementation Details:
78 * - Control points stored in fixed-capacity ring buffer (index [0] = newest)
79 * - Freehand strokes use sliding 4-point window for Catmull-Rom interpolation
80 * - Three vertex collections: control point geometry, completed strokes, in-progress stroke
81 * - Parametric edits trigger full regeneration; freehand is append-only
82 * - Both modes can coexist: control points and freehand strokes are independent
83 */
84class MAYAFLUX_API PathGeneratorNode : public GeometryWriterNode {
85public:
86 using CustomPathFunction = std::function<glm::vec3(std::span<const LineVertex>, double)>;
87
88 /**
89 * @brief Create path generator with interpolation mode
90 * @param mode Interpolation method
91 * @param samples_per_segment Vertices generated between control points
92 * @param max_control_points Maximum control points in history
93 * @param tension Tension parameter for applicable modes
94 */
95 explicit PathGeneratorNode(
96 Kinesis::InterpolationMode mode = Kinesis::InterpolationMode::QUADRATIC_BEZIER,
97 Eigen::Index samples_per_segment = 32,
98 size_t max_control_points = 64,
99 double tension = 0.5);
100
101 /**
102 * @brief Create path generator with custom interpolation function
103 * @param custom_func User-provided interpolation function
104 * @param samples_per_segment Vertices per segment
105 * @param max_control_points Maximum control points in history
106 */
108 CustomPathFunction custom_func,
109 Eigen::Index samples_per_segment = 32,
110 size_t max_control_points = 64);
111
112 /**
113 * @brief Add control point with full LineVertex data
114 * @param vertex LineVertex containing position, color, thickness
115 *
116 * Pushes vertex to front of ring buffer (index [0]).
117 * Oldest vertex discarded if buffer full.
118 */
119 void add_control_point(const LineVertex& vertex);
120
121 /**
122 * @brief Extend path with full LineVertex data
123 * @param vertex LineVertex containing target position, color, thickness
124 *
125 * Generates interpolated vertices between last added point and vertex.position.
126 * Appends generated vertices to existing geometry. No history awareness beyond last point.
127 */
128 void draw_to(const LineVertex& vertex);
129
130 /**
131 * @brief Set all control points with full LineVertex data
132 * @param vertices Vector of LineVertex data (ordered newest to oldest)
133 *
134 * Clears existing history and fills buffer with new vertices.
135 * If vertices.size() > capacity, only most recent vertices kept.
136 */
137 void set_control_points(const std::vector<LineVertex>& vertices);
138
139 /**
140 * @brief Update specific control point with full LineVertex data
141 * @param index Control point index (0 = newest)
142 * @param vertex New LineVertex data
143 */
144 void update_control_point(size_t index, const LineVertex& vertex);
145
146 /**
147 * @brief Get control point
148 * @param index Control point index (0 = newest)
149 * @return Control point position
150 */
151 [[nodiscard]] LineVertex get_control_point(size_t index) const;
152
153 /**
154 * @brief Get all control points as vector
155 * @return Vector of control point positions (ordered newest to oldest)
156 */
157 [[nodiscard]] std::vector<LineVertex> get_control_points() const;
158
159 /**
160 * @brief Clear all control points and generated vertices
161 */
162 void clear_path();
163
164 /**
165 * @brief Set path color (applied to all generated vertices)
166 * @param color RGB color
167 * @param force_uniform If true, ignores per-vertex color and uses this color for all vertices
168 */
169 void set_path_color(const glm::vec3& color, bool force_uniform = true);
170
171 /**
172 * @brief Set uniform color mode
173 * @param should_force If true, all vertices will use m_current_color instead of per-vertex color
174 */
175 void force_uniform_color(bool should_force);
176
177 /**
178 * @brief Check if uniform color mode is enabled
179 * @return True if uniform color is forced, false if per-vertex color is used
180 */
181 bool should_force_uniform_color() const { return m_force_uniform_color; }
182
183 /**
184 * @brief Set path thickness (applied to all generated vertices)
185 * @param thickness Line thickness
186 * @param force_uniform If true, ignores per-segment thickness and uses this thickness for all vertices
187 */
188 void set_path_thickness(float thickness, bool force_uniform = true);
189
190 /**
191 * @brief Set uniform thickness mode
192 * @param should_force If true, all vertices will use m_current_thickness instead of per-segment thickness
193 */
194 void force_uniform_thickness(bool should_force);
195
196 /**
197 * @brief Get current path color
198 * @return RGB color
199 */
200 [[nodiscard]] const glm::vec3& get_path_color() const { return m_current_color; }
201
202 /**
203 * @brief Get current path thickness
204 * @return Line thickness
205 */
206 [[nodiscard]] const float& get_path_thickness() const { return m_current_thickness; }
207
208 /**
209 * @brief Set interpolation mode
210 * @note Structural change: Only affects control-point paths.
211 * Baked freehand strokes remain unchanged.
212 */
213 void set_interpolation_mode(Kinesis::InterpolationMode mode);
214
215 /**
216 * @brief Set samples per segment
217 * @note Structural change: Only affects control-point paths.
218 */
219 void set_samples_per_segment(Eigen::Index samples);
220
221 /**
222 * @brief Set tension parameter (for Catmull-Rom)
223 * @param tension Tension value
224 */
225 void set_tension(double tension);
226
227 /**
228 * @brief Enable/disable arc-length parameterization
229 * @param enable If true, reparameterize by arc length for uniform spacing
230 */
231 void parameterize_arc_length(bool enable);
232
233 /**
234 * @brief Get number of control points currently stored
235 * @return Control point count
236 */
237 [[nodiscard]] size_t get_control_point_count() const { return m_control_points.size(); }
238
239 /**
240 * @brief Get maximum control point capacity
241 * @return Maximum control points
242 */
243 [[nodiscard]] size_t get_control_point_capacity() const { return m_max_control_points; }
244
245 /**
246 * @brief Get number of generated vertices
247 * @return Vertex count
248 */
249 [[nodiscard]] size_t get_generated_vertex_count() const { return m_vertices.size(); }
250
251 /**
252 * @brief Get combined vertex count (control points + completed strokes + in-progress stroke)
253 * @return Total vertex count
254 */
255 [[nodiscard]] size_t get_all_vertex_count() const { return m_combined_cache.size(); }
256
257 /**
258 * @brief Get all generated vertices (control points + completed strokes + in-progress stroke)
259 * @return Vector of all vertices
260 */
261 [[nodiscard]] const std::vector<LineVertex>& get_all_vertices() const { return m_combined_cache; }
262
263 /**
264 * @brief Compute frame - generates interpolated vertices from control points
265 */
266 void compute_frame() override;
267
268 /**
269 * @brief Finish incremental drawing stroke
270 *
271 * Clears the sliding window. Call this when pen lifts or stroke ends.
272 * Next draw_to() will start a fresh stroke.
273 */
274 void complete();
275
276 /**
277 * @brief Set primitive topology for rendering
278 * @param topology Primitive topology (e.g. LINE_LIST, TRIANGLE_LIST)
279 *
280 * This determines how the vertex data is interpreted when rendered.
281 * For example, LINE_LIST treats every pair of vertices as a line segment,
282 * while TRIANGLE_LIST treats every triplet of vertices as a triangle.
283 */
284 void set_primitive_topology(Portal::Graphics::PrimitiveTopology topology) { m_primitive_topology = topology; }
285
287 {
288 return m_primitive_topology;
289 }
290
291private:
294
295 /**
296 * @brief Control points, newest first: index 0 is the most recently added.
297 *
298 * Capped at m_max_control_points; add_control_point() and
299 * set_control_points() drop the oldest entries past that bound.
300 */
301 std::vector<LineVertex> m_control_points;
303 std::vector<LineVertex> m_vertices;
304 std::vector<LineVertex> m_draw_vertices;
305 std::vector<LineVertex> m_completed_draws;
306
307 std::vector<LineVertex> m_combined_cache;
308
309 std::vector<LineVertex> m_draw_window;
310
312
313 std::array<double, 12> m_segment_controls;
314 std::vector<double> m_curve_primary, m_curve_secondary;
315
317 double m_tension;
318
319 std::vector<LineVertex> m_range_cache;
320
321#ifdef MAYAFLUX_PLATFORM_MACOS
322 std::vector<LineVertex> m_expand_cache;
323#endif
324
325 glm::vec3 m_current_color { 1.0F, 1.0F, 1.0F };
326 float m_current_thickness { 2.0F };
327
328 bool m_force_uniform_color {};
329 bool m_force_uniform_thickness {};
330 bool m_geometry_dirty { true };
331 bool m_arc_length_parameterization {};
332 bool m_attributes_dirty {};
333 Portal::Graphics::PrimitiveTopology m_primitive_topology { Portal::Graphics::PrimitiveTopology::LINE_STRIP };
334
335 static constexpr size_t INVALID_SEGMENT { std::numeric_limits<size_t>::max() };
336 size_t m_dirty_segment_start { INVALID_SEGMENT };
337 size_t m_dirty_segment_end { INVALID_SEGMENT };
338
339 /** @brief Vertices emitted per four-point window. */
340 [[nodiscard]] size_t vertices_per_window() const;
341
342 /**
343 * @brief Evaluate one window and write its vertices.
344 * @param curve_verts Control point source.
345 * @param start_idx First control point of the window.
346 * @param dst Destination for vertices_per_window() vertices.
347 */
348 void write_curve_segment(
349 const std::vector<LineVertex>& curve_verts,
350 size_t start_idx,
351 LineVertex* dst);
352
353 /**
354 * @brief Write colour and thickness for one window, leaving positions intact.
355 * @param curve_verts Control point source.
356 * @param start_idx First control point of the window.
357 * @param dst Destination for vertices_per_window() vertices.
358 */
359 void write_segment_attributes(
360 const std::vector<LineVertex>& curve_verts,
361 size_t start_idx,
362 LineVertex* dst) const;
363
364 /** @brief Rewrite colour and thickness over existing geometry, no curve evaluation. */
365 void refresh_attributes();
366
367 void generate_path_vertices();
368 void generate_direct_path();
369 void generate_custom_path();
370 void generate_interpolated_path();
371 void regenerate_geometry();
372 void regenerate_segment_range(size_t start_ctrl_idx, size_t end_ctrl_idx);
373
374 void append_line_segment(
375 const LineVertex& v0,
376 const LineVertex& v1,
377 std::vector<LineVertex>& output);
378};
379
380} // namespace MayaFlux::Nodes::GpuSync
uint32_t index
Definition VKDevice.cpp:142
std::shared_ptr< Core::VKImage > output
Reusable interpolation state for callers evaluating many curves.
Base class for nodes that generate 3D geometry data.
const std::vector< LineVertex > & get_all_vertices() const
Get all generated vertices (control points + completed strokes + in-progress stroke)
const float & get_path_thickness() const
Get current path thickness.
bool should_force_uniform_color() const
Check if uniform color mode is enabled.
size_t get_control_point_capacity() const
Get maximum control point capacity.
void set_primitive_topology(Portal::Graphics::PrimitiveTopology topology)
Set primitive topology for rendering.
std::function< glm::vec3(std::span< const LineVertex >, double)> CustomPathFunction
size_t get_generated_vertex_count() const
Get number of generated vertices.
std::vector< LineVertex > m_control_points
Control points, newest first: index 0 is the most recently added.
size_t get_all_vertex_count() const
Get combined vertex count (control points + completed strokes + in-progress stroke)
size_t get_control_point_count() const
Get number of control points currently stored.
Portal::Graphics::PrimitiveTopology get_primitive_topology() const override
Get primitive topology for rendering.
const glm::vec3 & get_path_color() const
Get current path color.
Generates dense vertex paths from sparse control points or freehand drawing.
InterpolationMode
Mathematical interpolation methods.
PrimitiveTopology
Vertex assembly primitive topology.
Vertex type for line primitives (LINE_LIST / LINE_STRIP topology)