MayaFlux 0.2.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
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.