MayaFlux 1.0.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
276private:
279
280 /**
281 * @brief Control points, newest first: index 0 is the most recently added.
282 *
283 * Capped at m_max_control_points; add_control_point() and
284 * set_control_points() drop the oldest entries past that bound.
285 */
286 std::vector<LineVertex> m_control_points;
288 std::vector<LineVertex> m_vertices;
289 std::vector<LineVertex> m_draw_vertices;
290 std::vector<LineVertex> m_completed_draws;
291
292 std::vector<LineVertex> m_combined_cache;
293
294 std::vector<LineVertex> m_draw_window;
295
297
298 std::array<double, 12> m_segment_controls;
299 std::vector<double> m_curve_primary, m_curve_secondary, m_curve_tangents;
300
302 double m_tension;
303
304
305 glm::vec3 m_current_color { 1.0F, 1.0F, 1.0F };
306 float m_current_thickness { 2.0F };
307
308 bool m_force_uniform_color {};
309 bool m_force_uniform_thickness {};
310 bool m_geometry_dirty { true };
311 bool m_path_dirty { true };
312 bool m_arc_length_parameterization {};
313 bool m_attributes_dirty {};
314
315 static constexpr size_t INVALID_SEGMENT { std::numeric_limits<size_t>::max() };
316 size_t m_dirty_segment_start { INVALID_SEGMENT };
317 size_t m_dirty_segment_end { INVALID_SEGMENT };
318
319 /** @brief Vertices emitted per four-point window. */
320 [[nodiscard]] size_t vertices_per_window() const;
321
322 /**
323 * @brief Evaluate one window and write its vertices.
324 * @param curve_verts Control point source.
325 * @param start_idx First control point of the window.
326 * @param dst Destination for vertices_per_window() vertices.
327 */
328 void write_curve_segment(
329 const std::vector<LineVertex>& curve_verts,
330 size_t start_idx,
331 LineVertex* dst);
332
333 /**
334 * @brief Write colour and thickness for one window, leaving positions intact.
335 * @param curve_verts Control point source.
336 * @param start_idx First control point of the window.
337 * @param dst Destination for vertices_per_window() vertices.
338 */
339 void write_segment_attributes(
340 const std::vector<LineVertex>& curve_verts,
341 size_t start_idx,
342 LineVertex* dst) const;
343
344 /** @brief Rewrite colour and thickness over existing geometry, no curve evaluation. */
345 void refresh_attributes();
346
347 void generate_path_vertices();
348 void generate_direct_path();
349 void generate_custom_path();
350 void generate_interpolated_path();
351 void regenerate_geometry();
352 void regenerate_segment_range(size_t first_window, size_t last_window);
353
354 void append_line_segment(
355 const LineVertex& v0,
356 const LineVertex& v1,
357 std::vector<LineVertex>& output);
358};
359
360} // namespace MayaFlux::Nodes::GpuSync
glm::vec3 color
Definition Decoder.cpp:97
uint32_t * dst
uint32_t mode
uint32_t index
Definition VKDevice.cpp:122
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.
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.
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.
Vertex type for line primitives (LINE_LIST / LINE_STRIP topology)