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