MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GeometryPrimitives.cpp
Go to the documentation of this file.
2
3#include <glm/gtc/matrix_transform.hpp>
4
5namespace MayaFlux::Kinesis {
6
7//==============================================================================
8// Primitive Generation
9//==============================================================================
10
11std::vector<glm::vec3> generate_circle(
12 const glm::vec3& center,
13 float radius,
14 size_t segments,
15 const glm::vec3& normal)
16{
17 if (segments < 3) {
18 segments = 3;
19 }
20
21 std::vector<glm::vec3> vertices;
22 vertices.reserve(segments + 1);
23
24 glm::vec3 n = glm::normalize(normal);
25 glm::vec3 u;
26
27 if (std::abs(n.z) < 0.9F) {
28 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
29 } else {
30 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
31 }
32
33 glm::vec3 v = glm::cross(n, u);
34
35 float angle_step = glm::two_pi<float>() / static_cast<float>(segments);
36
37 for (size_t i = 0; i <= segments; ++i) {
38 float angle = static_cast<float>(i) * angle_step;
39 float cos_a = std::cos(angle);
40 float sin_a = std::sin(angle);
41
42 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
43
44 vertices.push_back(position);
45 }
46
47 return vertices;
48}
49
50std::vector<glm::vec3> generate_ellipse(
51 const glm::vec3& center,
52 float semi_major,
53 float semi_minor,
54 size_t segments,
55 const glm::vec3& normal)
56{
57 if (segments < 3) {
58 segments = 3;
59 }
60
61 std::vector<glm::vec3> vertices;
62 vertices.reserve(segments + 1);
63
64 glm::vec3 n = glm::normalize(normal);
65 glm::vec3 u;
66
67 if (std::abs(n.z) < 0.9F) {
68 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
69 } else {
70 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
71 }
72
73 glm::vec3 v = glm::cross(n, u);
74
75 float angle_step = glm::two_pi<float>() / static_cast<float>(segments);
76
77 for (size_t i = 0; i <= segments; ++i) {
78 float angle = static_cast<float>(i) * angle_step;
79 float cos_a = std::cos(angle);
80 float sin_a = std::sin(angle);
81
82 glm::vec3 position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
83
84 vertices.push_back(position);
85 }
86
87 return vertices;
88}
89
90std::vector<glm::vec3> generate_rectangle(
91 const glm::vec3& center,
92 float width,
93 float height,
94 const glm::vec3& normal)
95{
96 glm::vec3 n = glm::normalize(normal);
97 glm::vec3 u;
98
99 if (std::abs(n.z) < 0.9F) {
100 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
101 } else {
102 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
103 }
104
105 glm::vec3 v = glm::cross(n, u);
106
107 float half_width = width * 0.5F;
108 float half_height = height * 0.5F;
109
110 std::vector<glm::vec3> vertices;
111 vertices.reserve(5);
112
113 vertices.push_back(center - half_width * u - half_height * v);
114
115 vertices.push_back(center + half_width * u - half_height * v);
116
117 vertices.push_back(center + half_width * u + half_height * v);
118
119 vertices.push_back(center - half_width * u + half_height * v);
120
121 vertices.push_back(vertices[0]);
122
123 return vertices;
124}
125
126std::vector<glm::vec3> generate_regular_polygon(
127 const glm::vec3& center,
128 float radius,
129 size_t sides,
130 const glm::vec3& normal,
131 float phase_offset)
132{
133 if (sides < 3) {
134 sides = 3;
135 }
136
137 std::vector<glm::vec3> vertices;
138 vertices.reserve(sides + 1);
139
140 glm::vec3 n = glm::normalize(normal);
141 glm::vec3 u;
142
143 if (std::abs(n.z) < 0.9F) {
144 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
145 } else {
146 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
147 }
148
149 glm::vec3 v = glm::cross(n, u);
150
151 float angle_step = glm::two_pi<float>() / static_cast<float>(sides);
152
153 for (size_t i = 0; i <= sides; ++i) {
154 float angle = static_cast<float>(i) * angle_step + phase_offset;
155 float cos_a = std::cos(angle);
156 float sin_a = std::sin(angle);
157
158 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
159
160 vertices.push_back(position);
161 }
162
163 return vertices;
164}
165
166//==============================================================================
167// Transformations
168//==============================================================================
169
171 std::vector<glm::vec3>& vertices,
172 const glm::mat4& transform)
173{
174 for (auto& vertex : vertices) {
175 glm::vec4 pos(vertex, 1.0F);
176 pos = transform * pos;
177 vertex = glm::vec3(pos);
178 }
179}
180
182 std::vector<glm::vec3>& vertices,
183 const glm::vec3& axis,
184 float angle,
185 const glm::vec3& origin)
186{
187 glm::vec3 normalized_axis = glm::normalize(axis);
188
189 glm::mat4 transform = glm::translate(glm::mat4(1.0F), origin);
190 transform = glm::rotate(transform, angle, normalized_axis);
191 transform = glm::translate(transform, -origin);
192
193 apply_transform(vertices, transform);
194}
195
197 std::vector<glm::vec3>& vertices,
198 const glm::vec3& displacement)
199{
200 for (auto& vertex : vertices) {
201 vertex += displacement;
202 }
203}
204
206 std::vector<glm::vec3>& vertices,
207 float scale,
208 const glm::vec3& origin)
209{
210 if (scale <= 0.0F) {
211 return;
212 }
213
214 for (auto& vertex : vertices) {
215 glm::vec3 offset = vertex - origin;
216 vertex = origin + offset * scale;
217 }
218}
219
221 std::vector<glm::vec3>& vertices,
222 const glm::vec3& scale,
223 const glm::vec3& origin)
224{
225 for (auto& vertex : vertices) {
226 glm::vec3 offset = vertex - origin;
227 vertex = origin + offset * scale;
228 }
229}
230
231//==============================================================================
232// Differential Geometry
233//==============================================================================
234
235std::vector<Nodes::LineVertex> compute_path_normals(
236 const std::vector<Nodes::LineVertex>& path_vertices,
237 float normal_length,
238 size_t stride)
239{
240 if (path_vertices.size() < 2 || stride == 0) {
241 return {};
242 }
243
244 std::vector<Nodes::LineVertex> normals;
245 normals.reserve((path_vertices.size() - 1) / stride * 2);
246
247 for (size_t i = 0; i < path_vertices.size() - 1; i += stride) {
248 glm::vec3 p0 = path_vertices[i].position;
249 glm::vec3 p1 = path_vertices[i + 1].position;
250
251 glm::vec3 tangent = p1 - p0;
252 float length = glm::length(tangent);
253
254 if (length < 1e-6F) {
255 continue;
256 }
257
258 tangent /= length;
259
260 // Normal (perpendicular in XY plane)
261 // For 2D: rotate tangent 90° counter-clockwise
262 glm::vec3 normal(-tangent.y, tangent.x, 0.0F);
263 normal = glm::normalize(normal) * normal_length;
264
265 glm::vec3 midpoint = (p0 + p1) * 0.5F;
266
267 glm::vec3 color = path_vertices[i].color;
268 float thickness = path_vertices[i].thickness;
269
270 normals.push_back({ .position = midpoint - normal * 0.5F,
271 .color = color,
272 .thickness = thickness });
273
274 normals.push_back({ .position = midpoint + normal * 0.5F,
275 .color = color,
276 .thickness = thickness });
277 }
278
279 return normals;
280}
281
282std::vector<Nodes::LineVertex> compute_path_tangents(
283 const std::vector<Nodes::LineVertex>& path_vertices,
284 float tangent_length,
285 size_t stride)
286{
287 if (path_vertices.size() < 2 || stride == 0) {
288 return {};
289 }
290
291 std::vector<Nodes::LineVertex> tangents;
292 tangents.reserve((path_vertices.size() - 1) / stride * 2);
293
294 for (size_t i = 0; i < path_vertices.size() - 1; i += stride) {
295 glm::vec3 p0 = path_vertices[i].position;
296 glm::vec3 p1 = path_vertices[i + 1].position;
297
298 glm::vec3 tangent = p1 - p0;
299 float length = glm::length(tangent);
300
301 if (length < 1e-6F) {
302 continue;
303 }
304
305 tangent = glm::normalize(tangent) * tangent_length;
306
307 glm::vec3 color = path_vertices[i].color;
308 float thickness = path_vertices[i].thickness;
309
310 tangents.push_back({ .position = p0 - tangent * 0.5F,
311 .color = color,
312 .thickness = thickness });
313
314 tangents.push_back({ .position = p0 + tangent * 0.5F,
315 .color = color,
316 .thickness = thickness });
317 }
318
319 return tangents;
320}
321
322std::vector<Nodes::LineVertex> compute_path_curvature(
323 const std::vector<Nodes::LineVertex>& path_vertices,
324 float curvature_scale,
325 size_t stride)
326{
327 if (path_vertices.size() < 3 || stride == 0) {
328 return {};
329 }
330
331 std::vector<Nodes::LineVertex> curvatures;
332 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
333
334 for (size_t i = 1; i < path_vertices.size() - 1; i += stride) {
335 glm::vec3 p_prev = path_vertices[i - 1].position;
336 glm::vec3 p_curr = path_vertices[i].position;
337 glm::vec3 p_next = path_vertices[i + 1].position;
338
339 glm::vec3 curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
340
341 glm::vec3 color = path_vertices[i].color;
342 float thickness = path_vertices[i].thickness;
343
344 curvatures.push_back({ .position = p_curr,
345 .color = color,
346 .thickness = thickness });
347
348 curvatures.push_back({ .position = p_curr + curvature,
349 .color = color,
350 .thickness = thickness });
351 }
352
353 return curvatures;
354}
355
356//==============================================================================
357// Parametric Curves
358//==============================================================================
359
360std::vector<glm::vec3> sample_parametric_curve(
361 const std::function<glm::vec3(float)>& curve,
362 size_t samples)
363{
364 if (samples < 2) {
365 samples = 2;
366 }
367
368 std::vector<glm::vec3> vertices;
369 vertices.reserve(samples);
370
371 for (size_t i = 0; i < samples; ++i) {
372 float t = static_cast<float>(i) / static_cast<float>(samples - 1);
373 glm::vec3 position = curve(t);
374
375 vertices.push_back(position);
376 }
377
378 return vertices;
379}
380
381std::vector<Nodes::LineVertex> reparameterize_by_arc_length(
382 const std::vector<Nodes::LineVertex>& path_vertices,
383 size_t num_samples)
384{
385 if (path_vertices.size() < 2 || num_samples < 2) {
386 return path_vertices;
387 }
388
389 std::vector<float> arc_lengths;
390 arc_lengths.reserve(path_vertices.size());
391 arc_lengths.push_back(0.0F);
392
393 float total_length = 0.0F;
394 for (size_t i = 1; i < path_vertices.size(); ++i) {
395 float segment_length = glm::distance(
396 path_vertices[i].position,
397 path_vertices[i - 1].position);
398 total_length += segment_length;
399 arc_lengths.push_back(total_length);
400 }
401
402 if (total_length < 1e-6F) {
403 return path_vertices;
404 }
405
406 std::vector<Nodes::LineVertex> resampled;
407 resampled.reserve(num_samples);
408
409 for (size_t i = 0; i < num_samples; ++i) {
410 float target_length = (static_cast<float>(i) / static_cast<float>(num_samples - 1)) * total_length;
411
412 auto it = std::ranges::lower_bound(arc_lengths, target_length);
413 size_t idx = std::distance(arc_lengths.begin(), it);
414
415 if (idx == 0) {
416 resampled.push_back(path_vertices[0]);
417 } else if (idx >= path_vertices.size()) {
418 resampled.push_back(path_vertices.back());
419 } else {
420 float s0 = arc_lengths[idx - 1];
421 float s1 = arc_lengths[idx];
422 float t = (target_length - s0) / (s1 - s0);
423
424 glm::vec3 p0 = path_vertices[idx - 1].position;
425 glm::vec3 p1 = path_vertices[idx].position;
426 glm::vec3 position = glm::mix(p0, p1, t);
427
428 glm::vec3 c0 = path_vertices[idx - 1].color;
429 glm::vec3 c1 = path_vertices[idx].color;
430 glm::vec3 color = glm::mix(c0, c1, t);
431
432 resampled.push_back({ .position = position,
433 .color = color,
434 .thickness = 1.0F });
435 }
436 }
437
438 return resampled;
439}
440
441//==============================================================================
442// Geometric Operations
443//==============================================================================
444
446 std::vector<glm::vec3>& vertices,
447 const glm::vec3& plane_point,
448 const glm::vec3& plane_normal)
449{
450 glm::vec3 n = glm::normalize(plane_normal);
451
452 for (auto& vertex : vertices) {
453 glm::vec3 offset = vertex - plane_point;
454 float distance = glm::dot(offset, n);
455 vertex = vertex - distance * n;
456 }
457}
458
459std::vector<glm::vec3> compute_convex_hull_2d(
460 const std::vector<glm::vec3>& vertices,
461 const glm::vec3& projection_normal)
462{
463 if (vertices.size() < 3) {
464 return vertices;
465 }
466
467 glm::vec3 n = glm::normalize(projection_normal);
468 glm::vec3 u;
469
470 if (std::abs(n.z) < 0.9F) {
471 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
472 } else {
473 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
474 }
475
476 glm::vec3 v = glm::cross(n, u);
477
478 struct Point2D {
479 glm::vec2 pos;
480 size_t index;
481 };
482
483 std::vector<Point2D> points;
484 points.reserve(vertices.size());
485
486 for (size_t i = 0; i < vertices.size(); ++i) {
487 glm::vec3 offset = vertices[i];
488 float x = glm::dot(offset, u);
489 float y = glm::dot(offset, v);
490 points.push_back({ glm::vec2(x, y), i });
491 }
492
493 auto pivot_it = std::ranges::min_element(points,
494 [](const Point2D& a, const Point2D& b) {
495 if (std::abs(a.pos.y - b.pos.y) < 1e-6F) {
496 return a.pos.x < b.pos.x;
497 }
498 return a.pos.y < b.pos.y;
499 });
500
501 std::swap(points[0], *pivot_it);
502 Point2D pivot = points[0];
503
504 std::sort(points.begin() + 1, points.end(),
505 [&pivot](const Point2D& a, const Point2D& b) {
506 glm::vec2 va = a.pos - pivot.pos;
507 glm::vec2 vb = b.pos - pivot.pos;
508
509 float cross = va.x * vb.y - va.y * vb.x;
510 if (std::abs(cross) < 1e-6F) {
511 return glm::length(va) < glm::length(vb);
512 }
513 return cross > 0.0F;
514 });
515
516 // Graham scan
517 std::vector<size_t> hull;
518 hull.push_back(points[0].index);
519 hull.push_back(points[1].index);
520
521 auto ccw = [](const glm::vec2& a, const glm::vec2& b, const glm::vec2& c) {
522 return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0.0F;
523 };
524
525 for (size_t i = 2; i < points.size(); ++i) {
526 while (hull.size() >= 2) {
527 size_t top = hull.back();
528 size_t second = hull[hull.size() - 2];
529
530 glm::vec2 p1 = points[second].pos;
531 glm::vec2 p2 = points[top].pos;
532 glm::vec2 p3 = points[i].pos;
533
534 if (ccw(p1, p2, p3)) {
535 break;
536 }
537 hull.pop_back();
538 }
539 hull.push_back(points[i].index);
540 }
541
542 std::vector<glm::vec3> result;
543 result.reserve(hull.size() + 1);
544
545 for (size_t idx : hull) {
546 result.push_back(vertices[idx]);
547 }
548
549 result.push_back(vertices[hull[0]]);
550
551 return result;
552}
553
554//==============================================================================
555// Color Utilities
556//==============================================================================
557
558std::vector<Nodes::LineVertex> apply_color_gradient(
559 const std::vector<glm::vec3>& positions,
560 const std::vector<glm::vec3>& colors,
561 const std::vector<float>& color_positions,
562 float default_thickness)
563{
564 if (positions.empty() || colors.empty()) {
565 return {};
566 }
567
568 std::vector<Nodes::LineVertex> vertices;
569 vertices.reserve(positions.size());
570
571 if (colors.size() == 1) {
572 for (const auto& pos : positions) {
573 vertices.push_back({ .position = pos,
574 .color = colors[0],
575 .thickness = default_thickness });
576 }
577 return vertices;
578 }
579
580 std::vector<float> stops = color_positions;
581 if (stops.empty()) {
582 stops.reserve(colors.size());
583 for (size_t i = 0; i < colors.size(); ++i) {
584 stops.push_back(static_cast<float>(i) / static_cast<float>(colors.size() - 1));
585 }
586 }
587
588 for (size_t i = 0; i < positions.size(); ++i) {
589 float t = static_cast<float>(i) / static_cast<float>(positions.size() - 1);
590
591 glm::vec3 color;
592 if (t <= stops[0]) {
593 color = colors[0];
594 } else if (t >= stops.back()) {
595 color = colors.back();
596 } else {
597 size_t idx = 0;
598 for (size_t j = 1; j < stops.size(); ++j) {
599 if (t <= stops[j]) {
600 idx = j - 1;
601 break;
602 }
603 }
604
605 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
606 color = glm::mix(colors[idx], colors[idx + 1], local_t);
607 }
608
609 vertices.push_back({ .position = positions[i],
610 .color = color,
611 .thickness = default_thickness });
612 }
613
614 return vertices;
615}
616
617std::vector<Nodes::LineVertex> apply_uniform_color(
618 const std::vector<glm::vec3>& positions,
619 const glm::vec3& color,
620 float default_thickness)
621{
622 std::vector<Nodes::LineVertex> vertices;
623 vertices.reserve(positions.size());
624
625 for (const auto& pos : positions) {
626 vertices.push_back({ .position = pos,
627 .color = color,
628 .thickness = default_thickness });
629 }
630
631 return vertices;
632}
633
634std::vector<Nodes::LineVertex> apply_vertex_colors(
635 const std::vector<glm::vec3>& positions,
636 const std::vector<glm::vec3>& colors,
637 float default_thickness)
638{
639 if (positions.size() != colors.size()) {
640 return {};
641 }
642
643 std::vector<Nodes::LineVertex> vertices;
644 vertices.reserve(positions.size());
645
646 for (size_t i = 0; i < positions.size(); ++i) {
647 vertices.push_back({ .position = positions[i],
648 .color = colors[i],
649 .thickness = default_thickness });
650 }
651
652 return vertices;
653}
654
655} // namespace MayaFlux::Kinesis
size_t a
size_t b
std::vector< Nodes::LineVertex > compute_path_normals(const std::vector< Nodes::LineVertex > &path_vertices, float normal_length, size_t stride)
Compute normal vectors along a piecewise-linear path.
std::vector< Nodes::LineVertex > reparameterize_by_arc_length(const std::vector< Nodes::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
void apply_transform(std::vector< glm::vec3 > &vertices, const glm::mat4 &transform)
Apply rigid transformation to vertex set.
void apply_translation(std::vector< glm::vec3 > &vertices, const glm::vec3 &displacement)
Apply translation to vertex set.
std::vector< Nodes::LineVertex > apply_color_gradient(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, const std::vector< float > &color_positions, float default_thickness)
Apply color interpolation to position vertices.
std::vector< Nodes::LineVertex > compute_path_tangents(const std::vector< Nodes::LineVertex > &path_vertices, float tangent_length, size_t stride)
Compute tangent vectors along a piecewise-linear path.
std::vector< Nodes::LineVertex > apply_vertex_colors(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, float default_thickness)
Convert positions to LineVertex with per-vertex colors.
std::vector< Nodes::LineVertex > apply_uniform_color(const std::vector< glm::vec3 > &positions, const glm::vec3 &color, float default_thickness)
Apply uniform color to position vertices.
std::vector< glm::vec3 > compute_convex_hull_2d(const std::vector< glm::vec3 > &vertices, const glm::vec3 &projection_normal)
Compute convex hull of vertex set (2D projection)
void project_onto_plane(std::vector< glm::vec3 > &vertices, const glm::vec3 &plane_point, const glm::vec3 &plane_normal)
Project vertices onto plane defined by normal.
std::vector< glm::vec3 > generate_ellipse(const glm::vec3 &center, float semi_major, float semi_minor, size_t segments, const glm::vec3 &normal)
Generate vertices along an elliptical path.
std::vector< glm::vec3 > sample_parametric_curve(const std::function< glm::vec3(float)> &curve, size_t samples)
Sample parametric curve uniformly in parameter space.
std::vector< glm::vec3 > generate_circle(const glm::vec3 &center, float radius, size_t segments, const glm::vec3 &normal)
Generate vertices along a circular path.
std::vector< glm::vec3 > generate_rectangle(const glm::vec3 &center, float width, float height, const glm::vec3 &normal)
Generate vertices of an axis-aligned rectangular path.
void apply_scale(std::vector< glm::vec3 > &vertices, const glm::vec3 &scale, const glm::vec3 &origin)
Apply non-uniform scaling to vertex set.
std::vector< glm::vec3 > generate_regular_polygon(const glm::vec3 &center, float radius, size_t sides, const glm::vec3 &normal, float phase_offset)
Generate vertices of a regular n-gon.
void apply_rotation(std::vector< glm::vec3 > &vertices, const glm::vec3 &axis, float angle, const glm::vec3 &origin)
Apply rotation to vertex set around arbitrary axis.
void apply_uniform_scale(std::vector< glm::vec3 > &vertices, float scale, const glm::vec3 &origin)
Apply uniform scaling to vertex set.
std::vector< Nodes::LineVertex > compute_path_curvature(const std::vector< Nodes::LineVertex > &path_vertices, float curvature_scale, size_t stride)
Compute curvature vectors along a path (2nd derivative approximation)