4#include <glm/gtc/matrix_transform.hpp>
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 } },
23 const glm::vec3& center,
26 const glm::vec3& normal)
32 std::vector<glm::vec3> vertices;
33 vertices.reserve(segments + 1);
35 glm::vec3 n = glm::normalize(normal);
38 if (std::abs(n.z) < 0.9F) {
39 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
41 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
44 glm::vec3 v = glm::cross(n, u);
46 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
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);
53 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
55 vertices.push_back(position);
62 const glm::vec3& center,
66 const glm::vec3& normal)
72 std::vector<glm::vec3> vertices;
73 vertices.reserve(segments + 1);
75 glm::vec3 n = glm::normalize(normal);
78 if (std::abs(n.z) < 0.9F) {
79 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
81 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
84 glm::vec3 v = glm::cross(n, u);
86 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
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);
93 glm::vec3 position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
95 vertices.push_back(position);
102 const glm::vec3& center,
105 const glm::vec3& normal)
107 glm::vec3 n = glm::normalize(normal);
110 if (std::abs(n.z) < 0.9F) {
111 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
113 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
116 glm::vec3 v = glm::cross(n, u);
118 float half_width =
width * 0.5F;
119 float half_height = height * 0.5F;
121 std::vector<glm::vec3> vertices;
124 vertices.push_back(center - half_width * u - half_height * v);
126 vertices.push_back(center + half_width * u - half_height * v);
128 vertices.push_back(center + half_width * u + half_height * v);
130 vertices.push_back(center - half_width * u + half_height * v);
132 vertices.push_back(vertices[0]);
138 const glm::vec3& center,
141 const glm::vec3& normal,
148 std::vector<glm::vec3> vertices;
149 vertices.reserve(sides + 1);
151 glm::vec3 n = glm::normalize(normal);
154 if (std::abs(n.z) < 0.9F) {
155 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
157 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
160 glm::vec3 v = glm::cross(n, u);
162 float angle_step = glm::two_pi<float>() /
static_cast<float>(sides);
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);
169 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
171 vertices.push_back(position);
182 std::vector<glm::vec3>& vertices,
183 const glm::mat4& transform)
185 for (
auto& vertex : vertices) {
186 glm::vec4 pos(vertex, 1.0F);
187 pos = transform * pos;
188 vertex = glm::vec3(pos);
193 std::vector<glm::vec3>& vertices,
194 const glm::vec3& axis,
196 const glm::vec3& origin)
198 glm::vec3 normalized_axis = glm::normalize(axis);
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);
208 std::vector<glm::vec3>& vertices,
209 const glm::vec3& displacement)
211 for (
auto& vertex : vertices) {
212 vertex += displacement;
217 std::vector<glm::vec3>& vertices,
219 const glm::vec3& origin)
225 for (
auto& vertex : vertices) {
226 glm::vec3 offset = vertex - origin;
227 vertex = origin + offset *
scale;
232 std::vector<glm::vec3>& vertices,
233 const glm::vec3&
scale,
234 const glm::vec3& origin)
236 for (
auto& vertex : vertices) {
237 glm::vec3 offset = vertex - origin;
238 vertex = origin + offset *
scale;
247 const std::vector<Kakshya::LineVertex>& path_vertices,
251 if (path_vertices.size() < 2 || stride == 0) {
255 std::vector<Kakshya::LineVertex> normals;
256 normals.reserve((path_vertices.size() - 1) / stride * 2);
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;
262 glm::vec3 tangent = p1 - p0;
263 float length = glm::length(tangent);
265 if (length < 1e-6F) {
273 glm::vec3 normal(-tangent.y, tangent.x, 0.0F);
274 normal = glm::normalize(normal) * normal_length;
276 glm::vec3 midpoint = (p0 + p1) * 0.5F;
278 glm::vec3 color = path_vertices[i].color;
279 float thickness = path_vertices[i].thickness;
281 normals.push_back({ .position = midpoint - normal * 0.5F,
283 .thickness = thickness });
285 normals.push_back({ .position = midpoint + normal * 0.5F,
287 .thickness = thickness });
294 const std::vector<Kakshya::LineVertex>& path_vertices,
295 float tangent_length,
298 if (path_vertices.size() < 2 || stride == 0) {
302 std::vector<Kakshya::LineVertex> tangents;
303 tangents.reserve((path_vertices.size() - 1) / stride * 2);
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;
309 glm::vec3 tangent = p1 - p0;
310 float length = glm::length(tangent);
312 if (length < 1e-6F) {
316 tangent = glm::normalize(tangent) * tangent_length;
318 glm::vec3 color = path_vertices[i].color;
319 float thickness = path_vertices[i].thickness;
321 tangents.push_back({ .position = p0 - tangent * 0.5F,
323 .thickness = thickness });
325 tangents.push_back({ .position = p0 + tangent * 0.5F,
327 .thickness = thickness });
334 const std::vector<Kakshya::LineVertex>& path_vertices,
335 float curvature_scale,
338 if (path_vertices.size() < 3 || stride == 0) {
342 std::vector<Kakshya::LineVertex> curvatures;
343 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
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;
350 glm::vec3 curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
352 glm::vec3 color = path_vertices[i].color;
353 float thickness = path_vertices[i].thickness;
355 curvatures.push_back({ .position = p_curr,
357 .thickness = thickness });
359 curvatures.push_back({ .position = p_curr + curvature,
361 .thickness = thickness });
372 const std::function<glm::vec3(
float)>& curve,
379 std::vector<glm::vec3> vertices;
380 vertices.reserve(samples);
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);
386 vertices.push_back(position);
393 const std::vector<Kakshya::LineVertex>& path_vertices,
396 if (path_vertices.size() < 2 || num_samples < 2) {
397 return path_vertices;
400 std::vector<float> arc_lengths;
401 arc_lengths.reserve(path_vertices.size());
402 arc_lengths.push_back(0.0F);
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);
413 if (total_length < 1e-6F) {
414 return path_vertices;
417 std::vector<Kakshya::LineVertex> resampled;
418 resampled.reserve(num_samples);
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;
423 auto it = std::ranges::lower_bound(arc_lengths, target_length);
424 size_t idx = std::distance(arc_lengths.begin(), it);
427 resampled.push_back(path_vertices[0]);
428 }
else if (idx >= path_vertices.size()) {
429 resampled.push_back(path_vertices.back());
431 float s0 = arc_lengths[idx - 1];
432 float s1 = arc_lengths[idx];
433 float t = (target_length - s0) / (s1 - s0);
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);
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);
443 resampled.push_back({ .position = position,
445 .thickness = 1.0F });
457 std::vector<glm::vec3>& vertices,
458 const glm::vec3& plane_point,
459 const glm::vec3& plane_normal)
461 glm::vec3 n = glm::normalize(plane_normal);
463 for (
auto& vertex : vertices) {
464 glm::vec3 offset = vertex - plane_point;
465 float distance = glm::dot(offset, n);
471 const std::vector<glm::vec3>& vertices,
472 const glm::vec3& projection_normal)
474 if (vertices.size() < 3) {
478 glm::vec3 n = glm::normalize(projection_normal);
481 if (std::abs(n.z) < 0.9F) {
482 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
484 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
487 glm::vec3 v = glm::cross(n, u);
494 std::vector<Point2D>
points;
495 points.reserve(vertices.size());
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 });
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;
509 return a.pos.y <
b.pos.y;
512 std::swap(
points[0], *pivot_it);
513 Point2D pivot =
points[0];
516 [&pivot](
const Point2D&
a,
const Point2D&
b) {
517 glm::vec2 va = a.pos - pivot.pos;
518 glm::vec2 vb = b.pos - pivot.pos;
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);
528 std::vector<size_t> hull;
529 hull.push_back(
points[0].index);
530 hull.push_back(
points[1].index);
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;
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];
541 glm::vec2 p1 =
points[second].pos;
542 glm::vec2 p2 =
points[top].pos;
543 glm::vec2 p3 =
points[i].pos;
545 if (ccw(p1, p2, p3)) {
550 hull.push_back(
points[i].index);
553 std::vector<glm::vec3> result;
554 result.reserve(hull.size() + 1);
556 for (
size_t idx : hull) {
557 result.push_back(vertices[idx]);
560 result.push_back(vertices[hull[0]]);
570 const std::vector<glm::vec3>& positions,
571 const std::vector<glm::vec3>& colors,
572 const std::vector<float>& color_positions,
575 if (positions.empty() || colors.empty()) {
579 std::vector<Kakshya::Vertex> vertices;
580 vertices.reserve(positions.size());
582 if (colors.size() == 1) {
583 for (
const auto& pos : positions) {
584 vertices.push_back({ .position = pos,
591 std::vector<float> stops = color_positions;
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));
599 for (
size_t i = 0; i < positions.size(); ++i) {
600 float t =
static_cast<float>(i) /
static_cast<float>(positions.size() - 1);
605 }
else if (t >= stops.back()) {
606 color = colors.back();
609 for (
size_t j = 1; j < stops.size(); ++j) {
616 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
617 color = glm::mix(colors[idx], colors[idx + 1], local_t);
620 vertices.push_back({ .position = positions[i],
629 const std::vector<glm::vec3>& positions,
630 const glm::vec3& color,
633 std::vector<Kakshya::Vertex> vertices;
634 vertices.reserve(positions.size());
636 for (
const auto& pos : positions) {
637 vertices.push_back({ .position = pos,
646 const std::vector<glm::vec3>& positions,
647 const std::vector<glm::vec3>& colors,
650 if (positions.size() != colors.size()) {
654 std::vector<Kakshya::Vertex> vertices;
655 vertices.reserve(positions.size());
657 for (
size_t i = 0; i < positions.size(); ++i) {
658 vertices.push_back({ .position = positions[i],
668 const float cos_r = std::cos(rotation);
669 const float sin_r = std::sin(rotation);
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;
677 out.vertices[i].position = {
678 x * cos_r - y * sin_r + position.x,
679 x * sin_r + y * cos_r + position.y,
682 out.vertices[i].texcoord = k_unit_quad[i].texcoord;
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 },
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 } },
709 const glm::vec3& center,
const glm::vec3& half,
const glm::vec3& color)
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),
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 },
750 const glm::vec3& center,
751 const glm::vec3& half_extents,
752 uint32_t subdivisions)
754 const uint32_t n = std::max(subdivisions, 1U);
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));
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 } },
777 for (
const auto& f : faces) {
778 const auto base =
static_cast<uint32_t
>(verts.size());
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;
786 .position = center + p * half_extents,
787 .uv = { fu, 1.0F - fv },
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 });
805 auto data = Kakshya::MeshData::empty();
808 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
810 std::span<const uint32_t>(indices),
817 const glm::vec3& center,
822 const glm::vec3& normal)
824 cols = std::max(cols, 1U);
825 rows = std::max(rows, 1U);
827 const glm::vec3 n = glm::normalize(normal);
829 if (std::abs(n.y) < 0.9F) {
830 u = glm::normalize(glm::cross(n, glm::vec3(0.0F, 1.0F, 0.0F)));
832 u = glm::normalize(glm::cross(n, glm::vec3(1.0F, 0.0F, 0.0F)));
834 const glm::vec3 v = glm::normalize(glm::cross(u, n));
836 const float half_x = extent_x * 0.5F;
837 const float half_z = extent_z * 0.5F;
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));
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);
853 .uv = { fu, 1.0F - fv },
859 const uint32_t stride = cols + 1;
860 const auto vert_count =
static_cast<uint32_t
>(verts.size());
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 });
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);
881 .uv = { fu, 1.0F - fv },
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 });
897 auto data = Kakshya::MeshData::empty();
900 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
902 std::span<const uint32_t>(indices),
905 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
910 const std::function<glm::vec3(
float,
float)>& fn,
914 u_segs = std::max(u_segs, 1U);
915 v_segs = std::max(v_segs, 1U);
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));
922 constexpr float eps = 1e-4F;
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);
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));
936 .uv = { fu, 1.0F - fv },
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 });
953 auto data = Kakshya::MeshData::empty();
956 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
958 std::span<const uint32_t>(indices),
961 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
967 std::span<const glm::vec3> path,
968 const std::function<
float(
float)>& radius_fn,
969 uint32_t radial_segments,
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));
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)));
980 glm::vec3 tangent = glm::normalize(path[1] - path[0]);
982 if (std::abs(tangent.y) < 0.9F) {
983 u_axis = glm::normalize(glm::cross(tangent, glm::vec3(0.0F, 1.0F, 0.0F)));
985 u_axis = glm::normalize(glm::cross(tangent, glm::vec3(1.0F, 0.0F, 0.0F)));
988 glm::vec3 v_axis = glm::normalize(glm::cross(tangent, u_axis));
990 const float total_len = [&]() {
992 for (uint32_t i = 1; i < n_pts; ++i)
993 l += glm::distance(path[i], path[i - 1]);
999 for (uint32_t pi = 0; pi < n_pts; ++pi) {
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));
1014 arc += glm::distance(path[pi], path[pi - 1]);
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);
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;
1027 .position = path[pi] + radial * r,
1028 .uv = {
static_cast<float>(s) /
static_cast<float>(seg), t },
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 });
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];
1052 const auto centre_idx =
static_cast<uint32_t
>(verts.size());
1054 .position = cap_pos,
1055 .uv = { 0.5F, t_cap },
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;
1063 indices.insert(indices.end(), { centre_idx,
b,
a });
1065 indices.insert(indices.end(), { centre_idx,
a,
b });
1071 auto data = Kakshya::MeshData::empty();
1074 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
1076 std::span<const uint32_t>(indices),
1079 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
1084 const std::function<glm::vec2(
float)>& profile_fn,
1085 uint32_t profile_segs,
1086 uint32_t radial_segs,
1087 float sweep_radians)
1089 profile_segs = std::max(profile_segs, 2U);
1090 radial_segs = std::max(radial_segs, 3U);
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));
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);
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);
1107 const glm::vec3 pos { p.x * ca, p.y, p.x * sa };
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));
1118 .uv = { u, 1.0F - t },
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 });
1135 auto data = Kakshya::MeshData::empty();
1138 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
1140 std::span<const uint32_t>(indices),
1143 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
std::vector< glm::vec2 > * points
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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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.
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.
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Axis-aligned bounding rectangle in a 2D coordinate space.
Kakshya::VertexLayout layout
Textured quad vertex data together with its semantic layout descriptor.