MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GeometryPrimitives.cpp
Go to the documentation of this file.
3
4#include <glm/gtc/matrix_transform.hpp>
5
6namespace MayaFlux::Kinesis {
7
8namespace {
9 constexpr std::array<Kakshya::TextureQuadVertex, 4> k_unit_quad = { {
10 { .position = { -1.0F, -1.0F, 0.0F }, .texcoord = { 0.0F, 1.0F } },
11 { .position = { 1.0F, -1.0F, 0.0F }, .texcoord = { 1.0F, 1.0F } },
12 { .position = { -1.0F, 1.0F, 0.0F }, .texcoord = { 0.0F, 0.0F } },
13 { .position = { 1.0F, 1.0F, 0.0F }, .texcoord = { 1.0F, 0.0F } },
14 } };
15
16} // namespace
17
18//==============================================================================
19// Primitive Generation
20//==============================================================================
21
22std::vector<glm::vec3> generate_circle(
23 const glm::vec3& center,
24 float radius,
25 size_t segments,
26 const glm::vec3& normal)
27{
28 if (segments < 3) {
29 segments = 3;
30 }
31
32 std::vector<glm::vec3> vertices;
33 vertices.reserve(segments + 1);
34
35 glm::vec3 n = glm::normalize(normal);
36 glm::vec3 u;
37
38 if (std::abs(n.z) < 0.9F) {
39 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
40 } else {
41 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
42 }
43
44 glm::vec3 v = glm::cross(n, u);
45
46 float angle_step = glm::two_pi<float>() / static_cast<float>(segments);
47
48 for (size_t i = 0; i <= segments; ++i) {
49 float angle = static_cast<float>(i) * angle_step;
50 float cos_a = std::cos(angle);
51 float sin_a = std::sin(angle);
52
53 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
54
55 vertices.push_back(position);
56 }
57
58 return vertices;
59}
60
61std::vector<glm::vec3> generate_ellipse(
62 const glm::vec3& center,
63 float semi_major,
64 float semi_minor,
65 size_t segments,
66 const glm::vec3& normal)
67{
68 if (segments < 3) {
69 segments = 3;
70 }
71
72 std::vector<glm::vec3> vertices;
73 vertices.reserve(segments + 1);
74
75 glm::vec3 n = glm::normalize(normal);
76 glm::vec3 u;
77
78 if (std::abs(n.z) < 0.9F) {
79 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
80 } else {
81 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
82 }
83
84 glm::vec3 v = glm::cross(n, u);
85
86 float angle_step = glm::two_pi<float>() / static_cast<float>(segments);
87
88 for (size_t i = 0; i <= segments; ++i) {
89 float angle = static_cast<float>(i) * angle_step;
90 float cos_a = std::cos(angle);
91 float sin_a = std::sin(angle);
92
93 glm::vec3 position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
94
95 vertices.push_back(position);
96 }
97
98 return vertices;
99}
100
101std::vector<glm::vec3> generate_rectangle(
102 const glm::vec3& center,
103 float width,
104 float height,
105 const glm::vec3& normal)
106{
107 glm::vec3 n = glm::normalize(normal);
108 glm::vec3 u;
109
110 if (std::abs(n.z) < 0.9F) {
111 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
112 } else {
113 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
114 }
115
116 glm::vec3 v = glm::cross(n, u);
117
118 float half_width = width * 0.5F;
119 float half_height = height * 0.5F;
120
121 std::vector<glm::vec3> vertices;
122 vertices.reserve(5);
123
124 vertices.push_back(center - half_width * u - half_height * v);
125
126 vertices.push_back(center + half_width * u - half_height * v);
127
128 vertices.push_back(center + half_width * u + half_height * v);
129
130 vertices.push_back(center - half_width * u + half_height * v);
131
132 vertices.push_back(vertices[0]);
133
134 return vertices;
135}
136
137std::vector<glm::vec3> generate_regular_polygon(
138 const glm::vec3& center,
139 float radius,
140 size_t sides,
141 const glm::vec3& normal,
142 float phase_offset)
143{
144 if (sides < 3) {
145 sides = 3;
146 }
147
148 std::vector<glm::vec3> vertices;
149 vertices.reserve(sides + 1);
150
151 glm::vec3 n = glm::normalize(normal);
152 glm::vec3 u;
153
154 if (std::abs(n.z) < 0.9F) {
155 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
156 } else {
157 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
158 }
159
160 glm::vec3 v = glm::cross(n, u);
161
162 float angle_step = glm::two_pi<float>() / static_cast<float>(sides);
163
164 for (size_t i = 0; i <= sides; ++i) {
165 float angle = static_cast<float>(i) * angle_step + phase_offset;
166 float cos_a = std::cos(angle);
167 float sin_a = std::sin(angle);
168
169 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
170
171 vertices.push_back(position);
172 }
173
174 return vertices;
175}
176
177//==============================================================================
178// Transformations
179//==============================================================================
180
182 std::vector<glm::vec3>& vertices,
183 const glm::mat4& transform)
184{
185 for (auto& vertex : vertices) {
186 glm::vec4 pos(vertex, 1.0F);
187 pos = transform * pos;
188 vertex = glm::vec3(pos);
189 }
190}
191
193 std::vector<glm::vec3>& vertices,
194 const glm::vec3& axis,
195 float angle,
196 const glm::vec3& origin)
197{
198 glm::vec3 normalized_axis = glm::normalize(axis);
199
200 glm::mat4 transform = glm::translate(glm::mat4(1.0F), origin);
201 transform = glm::rotate(transform, angle, normalized_axis);
202 transform = glm::translate(transform, -origin);
203
204 apply_transform(vertices, transform);
205}
206
208 std::vector<glm::vec3>& vertices,
209 const glm::vec3& displacement)
210{
211 for (auto& vertex : vertices) {
212 vertex += displacement;
213 }
214}
215
217 std::vector<glm::vec3>& vertices,
218 float scale,
219 const glm::vec3& origin)
220{
221 if (scale <= 0.0F) {
222 return;
223 }
224
225 for (auto& vertex : vertices) {
226 glm::vec3 offset = vertex - origin;
227 vertex = origin + offset * scale;
228 }
229}
230
232 std::vector<glm::vec3>& vertices,
233 const glm::vec3& scale,
234 const glm::vec3& origin)
235{
236 for (auto& vertex : vertices) {
237 glm::vec3 offset = vertex - origin;
238 vertex = origin + offset * scale;
239 }
240}
241
242//==============================================================================
243// Differential Geometry
244//==============================================================================
245
246std::vector<Kakshya::LineVertex> compute_path_normals(
247 const std::vector<Kakshya::LineVertex>& path_vertices,
248 float normal_length,
249 size_t stride)
250{
251 if (path_vertices.size() < 2 || stride == 0) {
252 return {};
253 }
254
255 std::vector<Kakshya::LineVertex> normals;
256 normals.reserve((path_vertices.size() - 1) / stride * 2);
257
258 for (size_t i = 0; i < path_vertices.size() - 1; i += stride) {
259 glm::vec3 p0 = path_vertices[i].position;
260 glm::vec3 p1 = path_vertices[i + 1].position;
261
262 glm::vec3 tangent = p1 - p0;
263 float length = glm::length(tangent);
264
265 if (length < 1e-6F) {
266 continue;
267 }
268
269 tangent /= length;
270
271 // Normal (perpendicular in XY plane)
272 // For 2D: rotate tangent 90° counter-clockwise
273 glm::vec3 normal(-tangent.y, tangent.x, 0.0F);
274 normal = glm::normalize(normal) * normal_length;
275
276 glm::vec3 midpoint = (p0 + p1) * 0.5F;
277
278 glm::vec3 color = path_vertices[i].color;
279 float thickness = path_vertices[i].thickness;
280
281 normals.push_back({ .position = midpoint - normal * 0.5F,
282 .color = color,
283 .thickness = thickness });
284
285 normals.push_back({ .position = midpoint + normal * 0.5F,
286 .color = color,
287 .thickness = thickness });
288 }
289
290 return normals;
291}
292
293std::vector<Kakshya::LineVertex> compute_path_tangents(
294 const std::vector<Kakshya::LineVertex>& path_vertices,
295 float tangent_length,
296 size_t stride)
297{
298 if (path_vertices.size() < 2 || stride == 0) {
299 return {};
300 }
301
302 std::vector<Kakshya::LineVertex> tangents;
303 tangents.reserve((path_vertices.size() - 1) / stride * 2);
304
305 for (size_t i = 0; i < path_vertices.size() - 1; i += stride) {
306 glm::vec3 p0 = path_vertices[i].position;
307 glm::vec3 p1 = path_vertices[i + 1].position;
308
309 glm::vec3 tangent = p1 - p0;
310 float length = glm::length(tangent);
311
312 if (length < 1e-6F) {
313 continue;
314 }
315
316 tangent = glm::normalize(tangent) * tangent_length;
317
318 glm::vec3 color = path_vertices[i].color;
319 float thickness = path_vertices[i].thickness;
320
321 tangents.push_back({ .position = p0 - tangent * 0.5F,
322 .color = color,
323 .thickness = thickness });
324
325 tangents.push_back({ .position = p0 + tangent * 0.5F,
326 .color = color,
327 .thickness = thickness });
328 }
329
330 return tangents;
331}
332
333std::vector<Kakshya::LineVertex> compute_path_curvature(
334 const std::vector<Kakshya::LineVertex>& path_vertices,
335 float curvature_scale,
336 size_t stride)
337{
338 if (path_vertices.size() < 3 || stride == 0) {
339 return {};
340 }
341
342 std::vector<Kakshya::LineVertex> curvatures;
343 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
344
345 for (size_t i = 1; i < path_vertices.size() - 1; i += stride) {
346 glm::vec3 p_prev = path_vertices[i - 1].position;
347 glm::vec3 p_curr = path_vertices[i].position;
348 glm::vec3 p_next = path_vertices[i + 1].position;
349
350 glm::vec3 curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
351
352 glm::vec3 color = path_vertices[i].color;
353 float thickness = path_vertices[i].thickness;
354
355 curvatures.push_back({ .position = p_curr,
356 .color = color,
357 .thickness = thickness });
358
359 curvatures.push_back({ .position = p_curr + curvature,
360 .color = color,
361 .thickness = thickness });
362 }
363
364 return curvatures;
365}
366
367//==============================================================================
368// Parametric Curves
369//==============================================================================
370
371std::vector<glm::vec3> sample_parametric_curve(
372 const std::function<glm::vec3(float)>& curve,
373 size_t samples)
374{
375 if (samples < 2) {
376 samples = 2;
377 }
378
379 std::vector<glm::vec3> vertices;
380 vertices.reserve(samples);
381
382 for (size_t i = 0; i < samples; ++i) {
383 float t = static_cast<float>(i) / static_cast<float>(samples - 1);
384 glm::vec3 position = curve(t);
385
386 vertices.push_back(position);
387 }
388
389 return vertices;
390}
391
392std::vector<Kakshya::LineVertex> reparameterize_by_arc_length(
393 const std::vector<Kakshya::LineVertex>& path_vertices,
394 size_t num_samples)
395{
396 if (path_vertices.size() < 2 || num_samples < 2) {
397 return path_vertices;
398 }
399
400 std::vector<float> arc_lengths;
401 arc_lengths.reserve(path_vertices.size());
402 arc_lengths.push_back(0.0F);
403
404 float total_length = 0.0F;
405 for (size_t i = 1; i < path_vertices.size(); ++i) {
406 float segment_length = glm::distance(
407 path_vertices[i].position,
408 path_vertices[i - 1].position);
409 total_length += segment_length;
410 arc_lengths.push_back(total_length);
411 }
412
413 if (total_length < 1e-6F) {
414 return path_vertices;
415 }
416
417 std::vector<Kakshya::LineVertex> resampled;
418 resampled.reserve(num_samples);
419
420 for (size_t i = 0; i < num_samples; ++i) {
421 float target_length = (static_cast<float>(i) / static_cast<float>(num_samples - 1)) * total_length;
422
423 auto it = std::ranges::lower_bound(arc_lengths, target_length);
424 size_t idx = std::distance(arc_lengths.begin(), it);
425
426 if (idx == 0) {
427 resampled.push_back(path_vertices[0]);
428 } else if (idx >= path_vertices.size()) {
429 resampled.push_back(path_vertices.back());
430 } else {
431 float s0 = arc_lengths[idx - 1];
432 float s1 = arc_lengths[idx];
433 float t = (target_length - s0) / (s1 - s0);
434
435 glm::vec3 p0 = path_vertices[idx - 1].position;
436 glm::vec3 p1 = path_vertices[idx].position;
437 glm::vec3 position = glm::mix(p0, p1, t);
438
439 glm::vec3 c0 = path_vertices[idx - 1].color;
440 glm::vec3 c1 = path_vertices[idx].color;
441 glm::vec3 color = glm::mix(c0, c1, t);
442
443 resampled.push_back({ .position = position,
444 .color = color,
445 .thickness = 1.0F });
446 }
447 }
448
449 return resampled;
450}
451
452//==============================================================================
453// Geometric Operations
454//==============================================================================
455
457 std::vector<glm::vec3>& vertices,
458 const glm::vec3& plane_point,
459 const glm::vec3& plane_normal)
460{
461 glm::vec3 n = glm::normalize(plane_normal);
462
463 for (auto& vertex : vertices) {
464 glm::vec3 offset = vertex - plane_point;
465 float distance = glm::dot(offset, n);
466 vertex = vertex - distance * n;
467 }
468}
469
470std::vector<glm::vec3> compute_convex_hull_2d(
471 const std::vector<glm::vec3>& vertices,
472 const glm::vec3& projection_normal)
473{
474 if (vertices.size() < 3) {
475 return vertices;
476 }
477
478 glm::vec3 n = glm::normalize(projection_normal);
479 glm::vec3 u;
480
481 if (std::abs(n.z) < 0.9F) {
482 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
483 } else {
484 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
485 }
486
487 glm::vec3 v = glm::cross(n, u);
488
489 struct Point2D {
490 glm::vec2 pos;
491 size_t index;
492 };
493
494 std::vector<Point2D> points;
495 points.reserve(vertices.size());
496
497 for (size_t i = 0; i < vertices.size(); ++i) {
498 glm::vec3 offset = vertices[i];
499 float x = glm::dot(offset, u);
500 float y = glm::dot(offset, v);
501 points.push_back({ .pos = glm::vec2(x, y), .index = i });
502 }
503
504 auto pivot_it = std::ranges::min_element(points,
505 [](const Point2D& a, const Point2D& b) {
506 if (std::abs(a.pos.y - b.pos.y) < 1e-6F) {
507 return a.pos.x < b.pos.x;
508 }
509 return a.pos.y < b.pos.y;
510 });
511
512 std::swap(points[0], *pivot_it);
513 Point2D pivot = points[0];
514
515 std::sort(points.begin() + 1, points.end(),
516 [&pivot](const Point2D& a, const Point2D& b) {
517 glm::vec2 va = a.pos - pivot.pos;
518 glm::vec2 vb = b.pos - pivot.pos;
519
520 float cross = va.x * vb.y - va.y * vb.x;
521 if (std::abs(cross) < 1e-6F) {
522 return glm::length(va) < glm::length(vb);
523 }
524 return cross > 0.0F;
525 });
526
527 // Graham scan
528 std::vector<size_t> hull;
529 hull.push_back(points[0].index);
530 hull.push_back(points[1].index);
531
532 auto ccw = [](const glm::vec2& a, const glm::vec2& b, const glm::vec2& c) {
533 return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0.0F;
534 };
535
536 for (size_t i = 2; i < points.size(); ++i) {
537 while (hull.size() >= 2) {
538 size_t top = hull.back();
539 size_t second = hull[hull.size() - 2];
540
541 glm::vec2 p1 = points[second].pos;
542 glm::vec2 p2 = points[top].pos;
543 glm::vec2 p3 = points[i].pos;
544
545 if (ccw(p1, p2, p3)) {
546 break;
547 }
548 hull.pop_back();
549 }
550 hull.push_back(points[i].index);
551 }
552
553 std::vector<glm::vec3> result;
554 result.reserve(hull.size() + 1);
555
556 for (size_t idx : hull) {
557 result.push_back(vertices[idx]);
558 }
559
560 result.push_back(vertices[hull[0]]);
561
562 return result;
563}
564
565//==============================================================================
566// Color Utilities
567//==============================================================================
568
569std::vector<Kakshya::Vertex> apply_color_gradient(
570 const std::vector<glm::vec3>& positions,
571 const std::vector<glm::vec3>& colors,
572 const std::vector<float>& color_positions,
573 float scalar)
574{
575 if (positions.empty() || colors.empty()) {
576 return {};
577 }
578
579 std::vector<Kakshya::Vertex> vertices;
580 vertices.reserve(positions.size());
581
582 if (colors.size() == 1) {
583 for (const auto& pos : positions) {
584 vertices.push_back({ .position = pos,
585 .color = colors[0],
586 .scalar = scalar });
587 }
588 return vertices;
589 }
590
591 std::vector<float> stops = color_positions;
592 if (stops.empty()) {
593 stops.reserve(colors.size());
594 for (size_t i = 0; i < colors.size(); ++i) {
595 stops.push_back(static_cast<float>(i) / static_cast<float>(colors.size() - 1));
596 }
597 }
598
599 for (size_t i = 0; i < positions.size(); ++i) {
600 float t = static_cast<float>(i) / static_cast<float>(positions.size() - 1);
601
602 glm::vec3 color;
603 if (t <= stops[0]) {
604 color = colors[0];
605 } else if (t >= stops.back()) {
606 color = colors.back();
607 } else {
608 size_t idx = 0;
609 for (size_t j = 1; j < stops.size(); ++j) {
610 if (t <= stops[j]) {
611 idx = j - 1;
612 break;
613 }
614 }
615
616 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
617 color = glm::mix(colors[idx], colors[idx + 1], local_t);
618 }
619
620 vertices.push_back({ .position = positions[i],
621 .color = color,
622 .scalar = scalar });
623 }
624
625 return vertices;
626}
627
628std::vector<Kakshya::Vertex> apply_uniform_color(
629 const std::vector<glm::vec3>& positions,
630 const glm::vec3& color,
631 float scalar)
632{
633 std::vector<Kakshya::Vertex> vertices;
634 vertices.reserve(positions.size());
635
636 for (const auto& pos : positions) {
637 vertices.push_back({ .position = pos,
638 .color = color,
639 .scalar = scalar });
640 }
641
642 return vertices;
643}
644
645std::vector<Kakshya::Vertex> apply_vertex_colors(
646 const std::vector<glm::vec3>& positions,
647 const std::vector<glm::vec3>& colors,
648 float scalar)
649{
650 if (positions.size() != colors.size()) {
651 return {};
652 }
653
654 std::vector<Kakshya::Vertex> vertices;
655 vertices.reserve(positions.size());
656
657 for (size_t i = 0; i < positions.size(); ++i) {
658 vertices.push_back({ .position = positions[i],
659 .color = colors[i],
660 .scalar = scalar });
661 }
662
663 return vertices;
664}
665
666QuadGeometry generate_quad(glm::vec2 position, glm::vec2 scale, float rotation)
667{
668 const float cos_r = std::cos(rotation);
669 const float sin_r = std::sin(rotation);
670
671 QuadGeometry out { .layout = Kakshya::VertexLayout::for_textured_quad() };
672
673 for (size_t i = 0; i < 4; ++i) {
674 const float x = k_unit_quad[i].position.x * scale.x;
675 const float y = k_unit_quad[i].position.y * scale.y;
676
677 out.vertices[i].position = {
678 x * cos_r - y * sin_r + position.x,
679 x * sin_r + y * cos_r + position.y,
680 0.0F,
681 };
682 out.vertices[i].texcoord = k_unit_quad[i].texcoord;
683 }
684
685 return out;
686}
687
688std::array<Kakshya::Vertex, 4> filled_rect(Kinesis::AABB2D region, glm::vec3 color)
689{
690 return { {
691 { .position = { region.min.x, region.min.y, 0.F }, .color = color },
692 { .position = { region.min.x, region.max.y, 0.F }, .color = color },
693 { .position = { region.max.x, region.min.y, 0.F }, .color = color },
694 { .position = { region.max.x, region.max.y, 0.F }, .color = color },
695 } };
696}
697
698std::array<Kakshya::TextureQuadVertex, 4> textured_rect(Kinesis::AABB2D region)
699{
700 return { {
701 { .position = { region.min.x, region.min.y, 0.F }, .texcoord = { 0.F, 1.F } },
702 { .position = { region.min.x, region.max.y, 0.F }, .texcoord = { 0.F, 0.F } },
703 { .position = { region.max.x, region.min.y, 0.F }, .texcoord = { 1.F, 1.F } },
704 { .position = { region.max.x, region.max.y, 0.F }, .texcoord = { 1.F, 0.F } },
705 } };
706}
707
708std::vector<Kakshya::Vertex> cuboid_wireframe(
709 const glm::vec3& center, const glm::vec3& half, const glm::vec3& color)
710{
711 const glm::vec3 v[8] = {
712 center + glm::vec3(-half.x, -half.y, -half.z),
713 center + glm::vec3(half.x, -half.y, -half.z),
714 center + glm::vec3(half.x, half.y, -half.z),
715 center + glm::vec3(-half.x, half.y, -half.z),
716 center + glm::vec3(-half.x, -half.y, half.z),
717 center + glm::vec3(half.x, -half.y, half.z),
718 center + glm::vec3(half.x, half.y, half.z),
719 center + glm::vec3(-half.x, half.y, half.z),
720 };
721 return {
722 { .position = v[0], .color = color },
723 { .position = v[1], .color = color },
724 { .position = v[1], .color = color },
725 { .position = v[2], .color = color },
726 { .position = v[2], .color = color },
727 { .position = v[3], .color = color },
728 { .position = v[3], .color = color },
729 { .position = v[0], .color = color },
730 { .position = v[4], .color = color },
731 { .position = v[5], .color = color },
732 { .position = v[5], .color = color },
733 { .position = v[6], .color = color },
734 { .position = v[6], .color = color },
735 { .position = v[7], .color = color },
736 { .position = v[7], .color = color },
737 { .position = v[4], .color = color },
738 { .position = v[0], .color = color },
739 { .position = v[4], .color = color },
740 { .position = v[1], .color = color },
741 { .position = v[5], .color = color },
742 { .position = v[2], .color = color },
743 { .position = v[6], .color = color },
744 { .position = v[3], .color = color },
745 { .position = v[7], .color = color },
746 };
747}
748
750 const glm::vec3& center,
751 const glm::vec3& half_extents,
752 uint32_t subdivisions)
753{
754 const uint32_t n = std::max(subdivisions, 1U);
755
756 std::vector<Kakshya::MeshVertex> verts;
757 std::vector<uint32_t> indices;
758 verts.reserve(uint32_t(6 * (n + 1) * (n + 1)));
759 indices.reserve(uint32_t(6 * n * n * 6));
760
761 struct FaceDef {
762 glm::vec3 origin;
763 glm::vec3 u_axis;
764 glm::vec3 v_axis;
765 glm::vec3 normal;
766 };
767
768 const std::array<FaceDef, 6> faces = { {
769 { .origin = { -1, -1, 1 }, .u_axis = { 2, 0, 0 }, .v_axis = { 0, 2, 0 }, .normal = { 0, 0, 1 } },
770 { .origin = { 1, -1, -1 }, .u_axis = { -2, 0, 0 }, .v_axis = { 0, 2, 0 }, .normal = { 0, 0, -1 } },
771 { .origin = { 1, -1, 1 }, .u_axis = { 0, 0, -2 }, .v_axis = { 0, 2, 0 }, .normal = { 1, 0, 0 } },
772 { .origin = { -1, -1, -1 }, .u_axis = { 0, 0, 2 }, .v_axis = { 0, 2, 0 }, .normal = { -1, 0, 0 } },
773 { .origin = { -1, 1, 1 }, .u_axis = { 2, 0, 0 }, .v_axis = { 0, 0, -2 }, .normal = { 0, 1, 0 } },
774 { .origin = { -1, -1, -1 }, .u_axis = { 2, 0, 0 }, .v_axis = { 0, 0, 2 }, .normal = { 0, -1, 0 } },
775 } };
776
777 for (const auto& f : faces) {
778 const auto base = static_cast<uint32_t>(verts.size());
779
780 for (uint32_t row = 0; row <= n; ++row) {
781 const float fv = static_cast<float>(row) / static_cast<float>(n);
782 for (uint32_t col = 0; col <= n; ++col) {
783 const float fu = static_cast<float>(col) / static_cast<float>(n);
784 const glm::vec3 p = f.origin + f.u_axis * fu + f.v_axis * fv;
785 verts.push_back({
786 .position = center + p * half_extents,
787 .uv = { fu, 1.0F - fv },
788 .normal = f.normal,
789 });
790 }
791 }
792
793 const uint32_t stride = n + 1;
794 for (uint32_t row = 0; row < n; ++row) {
795 for (uint32_t col = 0; col < n; ++col) {
796 const uint32_t a = base + row * stride + col;
797 const uint32_t b = a + 1;
798 const uint32_t c = a + stride;
799 const uint32_t d = c + 1;
800 indices.insert(indices.end(), { a, b, d, a, d, c });
801 }
802 }
803 }
804
805 auto data = Kakshya::MeshData::empty();
806 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
807 ins.insert_flat(
808 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()),
809 verts.size() * sizeof(Kakshya::MeshVertex)),
810 std::span<const uint32_t>(indices),
811 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
812 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
813 return data;
814}
815
817 const glm::vec3& center,
818 float extent_x,
819 float extent_z,
820 uint32_t cols,
821 uint32_t rows,
822 const glm::vec3& normal)
823{
824 cols = std::max(cols, 1U);
825 rows = std::max(rows, 1U);
826
827 const glm::vec3 n = glm::normalize(normal);
828 glm::vec3 u;
829 if (std::abs(n.y) < 0.9F) {
830 u = glm::normalize(glm::cross(n, glm::vec3(0.0F, 1.0F, 0.0F)));
831 } else {
832 u = glm::normalize(glm::cross(n, glm::vec3(1.0F, 0.0F, 0.0F)));
833 }
834 const glm::vec3 v = glm::normalize(glm::cross(u, n));
835
836 const float half_x = extent_x * 0.5F;
837 const float half_z = extent_z * 0.5F;
838
839 std::vector<Kakshya::MeshVertex> verts;
840 std::vector<uint32_t> indices;
841 verts.reserve(uint32_t(2 * (cols + 1) * (rows + 1)));
842 indices.reserve(uint32_t(2 * cols * rows * 6));
843
844 for (uint32_t row = 0; row <= rows; ++row) {
845 const float fv = static_cast<float>(row) / static_cast<float>(rows);
846 for (uint32_t col = 0; col <= cols; ++col) {
847 const float fu = static_cast<float>(col) / static_cast<float>(cols);
848 const glm::vec3 p = center
849 + u * glm::mix(-half_x, half_x, fu)
850 + v * glm::mix(-half_z, half_z, fv);
851 verts.push_back({
852 .position = p,
853 .uv = { fu, 1.0F - fv },
854 .normal = n,
855 });
856 }
857 }
858
859 const uint32_t stride = cols + 1;
860 const auto vert_count = static_cast<uint32_t>(verts.size());
861
862 for (uint32_t row = 0; row < rows; ++row) {
863 for (uint32_t col = 0; col < cols; ++col) {
864 const uint32_t a = row * stride + col;
865 const uint32_t b = a + 1;
866 const uint32_t c = a + stride;
867 const uint32_t d = c + 1;
868 indices.insert(indices.end(), { a, b, c, b, d, c });
869 }
870 }
871
872 for (uint32_t row = 0; row <= rows; ++row) {
873 const float fv = static_cast<float>(row) / static_cast<float>(rows);
874 for (uint32_t col = 0; col <= cols; ++col) {
875 const float fu = static_cast<float>(col) / static_cast<float>(cols);
876 const glm::vec3 p = center
877 + u * glm::mix(-half_x, half_x, fu)
878 + v * glm::mix(-half_z, half_z, fv);
879 verts.push_back({
880 .position = p,
881 .uv = { fu, 1.0F - fv },
882 .normal = -n,
883 });
884 }
885 }
886
887 for (uint32_t row = 0; row < rows; ++row) {
888 for (uint32_t col = 0; col < cols; ++col) {
889 const uint32_t a = vert_count + row * stride + col;
890 const uint32_t b = a + 1;
891 const uint32_t c = a + stride;
892 const uint32_t d = c + 1;
893 indices.insert(indices.end(), { a, c, b, b, c, d });
894 }
895 }
896
897 auto data = Kakshya::MeshData::empty();
898 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
899 ins.insert_flat(
900 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()),
901 verts.size() * sizeof(Kakshya::MeshVertex)),
902 std::span<const uint32_t>(indices),
903 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
904 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
905 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
906 return data;
907}
908
910 const std::function<glm::vec3(float, float)>& fn,
911 uint32_t u_segs,
912 uint32_t v_segs)
913{
914 u_segs = std::max(u_segs, 1U);
915 v_segs = std::max(v_segs, 1U);
916
917 std::vector<Kakshya::MeshVertex> verts;
918 std::vector<uint32_t> indices;
919 verts.reserve(uint32_t((u_segs + 1) * (v_segs + 1)));
920 indices.reserve(uint32_t(u_segs * v_segs * 6));
921
922 constexpr float eps = 1e-4F;
923
924 for (uint32_t row = 0; row <= v_segs; ++row) {
925 const float fv = static_cast<float>(row) / static_cast<float>(v_segs);
926 for (uint32_t col = 0; col <= u_segs; ++col) {
927 const float fu = static_cast<float>(col) / static_cast<float>(u_segs);
928
929 const glm::vec3 p = fn(fu, fv);
930 const glm::vec3 pu = fn(fu + eps, fv) - fn(fu - eps, fv);
931 const glm::vec3 pv = fn(fu, fv + eps) - fn(fu, fv - eps);
932 const glm::vec3 n = glm::normalize(glm::cross(pu, pv));
933
934 verts.push_back({
935 .position = p,
936 .uv = { fu, 1.0F - fv },
937 .normal = n,
938 });
939 }
940 }
941
942 const uint32_t stride = u_segs + 1;
943 for (uint32_t row = 0; row < v_segs; ++row) {
944 for (uint32_t col = 0; col < u_segs; ++col) {
945 const uint32_t a = row * stride + col;
946 const uint32_t b = a + 1;
947 const uint32_t c = a + stride;
948 const uint32_t d = c + 1;
949 indices.insert(indices.end(), { a, b, c, b, d, c });
950 }
951 }
952
953 auto data = Kakshya::MeshData::empty();
954 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
955 ins.insert_flat(
956 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()),
957 verts.size() * sizeof(Kakshya::MeshVertex)),
958 std::span<const uint32_t>(indices),
959 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
960 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
961 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
962
963 return data;
964}
965
967 std::span<const glm::vec3> path,
968 const std::function<float(float)>& radius_fn,
969 uint32_t radial_segments,
970 bool capped)
971{
972 const uint32_t seg = std::max(radial_segments, 3U);
973 const auto n_pts = static_cast<uint32_t>(std::max<size_t>(path.size(), 2));
974
975 std::vector<Kakshya::MeshVertex> verts;
976 std::vector<uint32_t> indices;
977 verts.reserve(uint32_t(n_pts * (seg + 1) + (capped ? 2 * (seg + 1) : 0)));
978 indices.reserve(uint32_t((n_pts - 1) * seg * 6 + (capped ? 2 * seg * 3 : 0)));
979
980 glm::vec3 tangent = glm::normalize(path[1] - path[0]);
981 glm::vec3 u_axis;
982 if (std::abs(tangent.y) < 0.9F) {
983 u_axis = glm::normalize(glm::cross(tangent, glm::vec3(0.0F, 1.0F, 0.0F)));
984 } else {
985 u_axis = glm::normalize(glm::cross(tangent, glm::vec3(1.0F, 0.0F, 0.0F)));
986 }
987
988 glm::vec3 v_axis = glm::normalize(glm::cross(tangent, u_axis));
989
990 const float total_len = [&]() {
991 float l = 0.0F;
992 for (uint32_t i = 1; i < n_pts; ++i)
993 l += glm::distance(path[i], path[i - 1]);
994 return l;
995 }();
996
997 float arc = 0.0F;
998
999 for (uint32_t pi = 0; pi < n_pts; ++pi) {
1000 if (pi > 0) {
1001 const glm::vec3 new_tan = (pi + 1 < n_pts)
1002 ? glm::normalize(path[pi + 1] - path[pi - 1])
1003 : glm::normalize(path[pi] - path[pi - 1]);
1004 const glm::vec3 axis = glm::cross(tangent, new_tan);
1005 const float axis_len = glm::length(axis);
1006 if (axis_len > 1e-6F) {
1007 const float angle = std::asin(glm::clamp(axis_len, 0.0F, 1.0F));
1008 const glm::mat4 rot = glm::rotate(glm::mat4(1.0F), angle,
1009 glm::normalize(axis));
1010 u_axis = glm::vec3(rot * glm::vec4(u_axis, 0.0F));
1011 v_axis = glm::vec3(rot * glm::vec4(v_axis, 0.0F));
1012 tangent = new_tan;
1013 }
1014 arc += glm::distance(path[pi], path[pi - 1]);
1015 }
1016
1017 const float t = (total_len > 1e-6F) ? (arc / total_len) : 0.0F;
1018 const float r = radius_fn(t);
1019 const float angle_step = glm::two_pi<float>() / static_cast<float>(seg);
1020
1021 for (uint32_t s = 0; s <= seg; ++s) {
1022 const float a = static_cast<float>(s) * angle_step;
1023 const float ca = std::cos(a);
1024 const float sa = std::sin(a);
1025 const glm::vec3 radial = ca * u_axis + sa * v_axis;
1026 verts.push_back({
1027 .position = path[pi] + radial * r,
1028 .uv = { static_cast<float>(s) / static_cast<float>(seg), t },
1029 .normal = radial,
1030 });
1031 }
1032 }
1033
1034 const uint32_t ring = seg + 1;
1035 for (uint32_t pi = 0; pi < n_pts - 1; ++pi) {
1036 for (uint32_t s = 0; s < seg; ++s) {
1037 const uint32_t a = pi * ring + s;
1038 const uint32_t b = a + 1;
1039 const uint32_t c = a + ring;
1040 const uint32_t d = c + 1;
1041 indices.insert(indices.end(), { a, b, c, b, d, c });
1042 }
1043 }
1044
1045 if (capped) {
1046 for (int end = 0; end < 2; ++end) {
1047 const uint32_t ring_base = (end == 0) ? 0 : (n_pts - 1) * ring;
1048 const float t_cap = (end == 0) ? 0.0F : 1.0F;
1049 const glm::vec3 cap_nrm = (end == 0) ? -tangent : tangent;
1050 const glm::vec3 cap_pos = path[(end == 0) ? 0 : n_pts - 1];
1051
1052 const auto centre_idx = static_cast<uint32_t>(verts.size());
1053 verts.push_back({
1054 .position = cap_pos,
1055 .uv = { 0.5F, t_cap },
1056 .normal = cap_nrm,
1057 });
1058
1059 for (uint32_t s = 0; s < seg; ++s) {
1060 const uint32_t a = ring_base + s;
1061 const uint32_t b = ring_base + s + 1;
1062 if (end == 0) {
1063 indices.insert(indices.end(), { centre_idx, b, a });
1064 } else {
1065 indices.insert(indices.end(), { centre_idx, a, b });
1066 }
1067 }
1068 }
1069 }
1070
1071 auto data = Kakshya::MeshData::empty();
1072 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
1073 ins.insert_flat(
1074 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()),
1075 verts.size() * sizeof(Kakshya::MeshVertex)),
1076 std::span<const uint32_t>(indices),
1077 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
1078 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
1079 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
1080 return data;
1081}
1082
1084 const std::function<glm::vec2(float)>& profile_fn,
1085 uint32_t profile_segs,
1086 uint32_t radial_segs,
1087 float sweep_radians)
1088{
1089 profile_segs = std::max(profile_segs, 2U);
1090 radial_segs = std::max(radial_segs, 3U);
1091
1092 std::vector<Kakshya::MeshVertex> verts;
1093 std::vector<uint32_t> indices;
1094 verts.reserve(size_t((profile_segs + 1)) * (radial_segs + 1));
1095 indices.reserve(uint32_t(profile_segs * radial_segs * 6));
1096
1097 for (uint32_t pi = 0; pi <= profile_segs; ++pi) {
1098 const float t = static_cast<float>(pi) / static_cast<float>(profile_segs);
1099 const glm::vec2 p = profile_fn(t); // p.x = radius from Y axis, p.y = height
1100
1101 for (uint32_t ri = 0; ri <= radial_segs; ++ri) {
1102 const float u = static_cast<float>(ri) / static_cast<float>(radial_segs);
1103 const float angle = u * sweep_radians;
1104 const float ca = std::cos(angle);
1105 const float sa = std::sin(angle);
1106
1107 const glm::vec3 pos { p.x * ca, p.y, p.x * sa };
1108
1109 constexpr float eps = 1e-4F;
1110 const glm::vec2 dp = profile_fn(glm::clamp(t + eps, 0.0F, 1.0F))
1111 - profile_fn(glm::clamp(t - eps, 0.0F, 1.0F));
1112 const glm::vec3 profile_tan { dp.x * ca, dp.y, dp.x * sa };
1113 const glm::vec3 radial { ca, 0.0F, sa };
1114 const glm::vec3 nrm = glm::normalize(glm::cross(profile_tan, radial));
1115
1116 verts.push_back({
1117 .position = pos,
1118 .uv = { u, 1.0F - t },
1119 .normal = nrm,
1120 });
1121 }
1122 }
1123
1124 const uint32_t stride = radial_segs + 1;
1125 for (uint32_t pi = 0; pi < profile_segs; ++pi) {
1126 for (uint32_t ri = 0; ri < radial_segs; ++ri) {
1127 const uint32_t a = pi * stride + ri;
1128 const uint32_t b = a + 1;
1129 const uint32_t c = a + stride;
1130 const uint32_t d = c + 1;
1131 indices.insert(indices.end(), { a, b, c, b, d, c });
1132 }
1133 }
1134
1135 auto data = Kakshya::MeshData::empty();
1136 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
1137 ins.insert_flat(
1138 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()),
1139 verts.size() * sizeof(Kakshya::MeshVertex)),
1140 std::span<const uint32_t>(indices),
1141 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
1142 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
1143 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
1144 return data;
1145}
1146
1147} // namespace MayaFlux::Kinesis
uint32_t width
Definition Decoder.cpp:59
std::vector< glm::vec2 > * points
size_t a
size_t b
void insert_flat(std::span< const uint8_t > vertex_bytes, std::span< const uint32_t > index_data, const VertexLayout &layout)
Insert a single flat mesh (no submesh tracking).
Write counterpart to MeshAccess.
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.
Kakshya::MeshData generate_revolution(const std::function< glm::vec2(float)> &profile_fn, uint32_t profile_segs, uint32_t radial_segs, float sweep_radians)
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.
std::vector< Kakshya::Vertex > apply_vertex_colors(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, float scalar)
Convert positions to LineVertex with per-vertex colors.
void apply_scale(std::vector< glm::vec3 > &vertices, const glm::vec3 &scale, const glm::vec3 &origin)
Apply non-uniform scaling to vertex set.
Kakshya::MeshData generate_parametric_surface(const std::function< glm::vec3(float, float)> &fn, uint32_t u_segs, uint32_t v_segs)
void apply_uniform_scale(std::vector< glm::vec3 > &vertices, float scale, const glm::vec3 &origin)
Apply uniform scaling to vertex set.
Kakshya::MeshData generate_box(const glm::vec3 &center, const glm::vec3 &half_extents, uint32_t subdivisions)
Generate a solid box as an indexed TRIANGLE_LIST mesh.
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.
std::vector< Kakshya::Vertex > apply_color_gradient(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, const std::vector< float > &color_positions, float scalar)
Apply color interpolation to position vertices.
void apply_transform(std::vector< glm::vec3 > &vertices, const glm::mat4 &transform)
Apply rigid transformation to vertex set.
QuadGeometry generate_quad(glm::vec2 position, glm::vec2 scale, float rotation)
Generate a textured quad centred on the origin.
std::vector< Kakshya::LineVertex > compute_path_tangents(const std::vector< Kakshya::LineVertex > &path_vertices, float tangent_length, size_t stride)
Compute tangent vectors along a piecewise-linear path.
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.
void apply_translation(std::vector< glm::vec3 > &vertices, const glm::vec3 &displacement)
Apply translation to vertex set.
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< Kakshya::LineVertex > compute_path_curvature(const std::vector< Kakshya::LineVertex > &path_vertices, float curvature_scale, size_t stride)
Compute curvature vectors along a path (2nd derivative approximation)
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)
SpatialField distance(const glm::vec3 &anchor, float radius, DistanceMetric metric=DistanceMetric::EUCLIDEAN)
Normalized distance from an anchor point using the specified metric.
Kakshya::MeshData generate_grid(const glm::vec3 &center, float extent_x, float extent_z, uint32_t cols, uint32_t rows, const glm::vec3 &normal)
Generate a subdivided flat grid in the XZ plane.
std::vector< Kakshya::Vertex > cuboid_wireframe(const glm::vec3 &center, const glm::vec3 &half, const glm::vec3 &color)
Generate a cuboid wireframe as LINE_LIST pairs.
std::array< Kakshya::TextureQuadVertex, 4 > textured_rect(Kinesis::AABB2D region)
Generate a UV-mapped TRIANGLE_STRIP quad from an AABB2D.
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< Kakshya::LineVertex > compute_path_normals(const std::vector< Kakshya::LineVertex > &path_vertices, float normal_length, size_t stride)
Compute normal vectors along a piecewise-linear path.
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
Definition Tendency.hpp:97
std::vector< Kakshya::LineVertex > reparameterize_by_arc_length(const std::vector< Kakshya::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
std::array< Kakshya::Vertex, 4 > filled_rect(Kinesis::AABB2D region, glm::vec3 color)
Generate a filled TRIANGLE_STRIP quad from an AABB2D.
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< Kakshya::Vertex > apply_uniform_color(const std::vector< glm::vec3 > &positions, const glm::vec3 &color, float scalar)
Apply uniform color to position vertices.
Kakshya::MeshData generate_tube(std::span< const glm::vec3 > path, const std::function< float(float)> &radius_fn, uint32_t radial_segments, bool capped)
Owning CPU-side representation of a loaded or generated mesh.
Definition MeshData.hpp:33
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Axis-aligned bounding rectangle in a 2D coordinate space.
Definition Bounds.hpp:21
Textured quad vertex data together with its semantic layout descriptor.