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