7#include <glm/gtc/matrix_transform.hpp>
8#include <glm/gtc/quaternion.hpp>
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 } },
20 constexpr size_t k_parallel_vertex_threshold = 16384;
22 struct SurfaceTopology {
28 [[nodiscard]]
const std::vector<uint32_t>& surface_indices(uint32_t
columns, uint32_t
rows)
30 thread_local SurfaceTopology cache;
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);
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;
41 const uint32_t d = c + 1;
42 cache.indices.insert(cache.indices.end(), { a, c, b, b, c, d });
60 const glm::vec3& center,
63 const glm::vec3& normal)
69 std::vector<glm::vec3> vertices;
70 vertices.reserve(segments + 1);
72 glm::vec3 n = glm::normalize(normal);
75 if (std::abs(n.z) < 0.9F) {
76 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
78 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
81 glm::vec3 v = glm::cross(n, u);
83 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
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);
99 const glm::vec3& center,
103 const glm::vec3& normal)
109 std::vector<glm::vec3> vertices;
110 vertices.reserve(segments + 1);
112 glm::vec3 n = glm::normalize(normal);
115 if (std::abs(n.z) < 0.9F) {
116 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
118 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
121 glm::vec3 v = glm::cross(n, u);
123 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
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);
130 glm::vec3
position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
139 const glm::vec3& center,
142 const glm::vec3& normal)
144 glm::vec3 n = glm::normalize(normal);
147 if (std::abs(n.z) < 0.9F) {
148 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
150 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
153 glm::vec3 v = glm::cross(n, u);
155 float half_width =
width * 0.5F;
156 float half_height =
height * 0.5F;
158 std::vector<glm::vec3> vertices;
161 vertices.push_back(center - half_width * u - half_height * v);
163 vertices.push_back(center + half_width * u - half_height * v);
165 vertices.push_back(center + half_width * u + half_height * v);
167 vertices.push_back(center - half_width * u + half_height * v);
169 vertices.push_back(vertices[0]);
175 const glm::vec3& center,
178 const glm::vec3& normal,
185 std::vector<glm::vec3> vertices;
186 vertices.reserve(sides + 1);
188 glm::vec3 n = glm::normalize(normal);
191 if (std::abs(n.z) < 0.9F) {
192 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
194 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
197 glm::vec3 v = glm::cross(n, u);
199 float angle_step = glm::two_pi<float>() /
static_cast<float>(sides);
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);
219 std::vector<glm::vec3>& vertices,
220 const glm::mat4& transform)
222 for (
auto& vertex : vertices) {
223 glm::vec4 pos(vertex, 1.0F);
224 pos = transform * pos;
225 vertex = glm::vec3(pos);
230 std::vector<glm::vec3>& vertices,
231 const glm::vec3& axis,
233 const glm::vec3& origin)
235 glm::vec3 normalized_axis = glm::normalize(axis);
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);
245 std::vector<glm::vec3>& vertices,
246 const glm::vec3& displacement)
248 for (
auto& vertex : vertices) {
249 vertex += displacement;
254 const glm::vec3& pivot,
255 const glm::vec3& target,
256 const glm::quat& rotation)
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;
264 constexpr float epsilon = 1.0e-6F;
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);
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());
277 std::vector<glm::vec3>& vertices,
279 const glm::vec3& origin)
285 for (
auto& vertex : vertices) {
286 glm::vec3
offset = vertex - origin;
292 std::vector<glm::vec3>& vertices,
293 const glm::vec3&
scale,
294 const glm::vec3& origin)
296 for (
auto& vertex : vertices) {
297 glm::vec3
offset = vertex - origin;
307 const std::vector<Kakshya::LineVertex>& path_vertices,
311 if (path_vertices.size() < 2 || stride == 0) {
315 std::vector<Kakshya::LineVertex> normals;
316 normals.reserve((path_vertices.size() - 1) / stride * 2);
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;
334 normal = glm::normalize(normal) * normal_length;
336 glm::vec3 midpoint = (p0 + p1) * 0.5F;
338 glm::vec3
color = path_vertices[i].color;
339 float thickness = path_vertices[i].thickness;
341 normals.push_back({ .position = midpoint - normal * 0.5F,
343 .thickness = thickness });
345 normals.push_back({ .position = midpoint + normal * 0.5F,
347 .thickness = thickness });
354 const std::vector<Kakshya::LineVertex>& path_vertices,
355 float tangent_length,
358 if (path_vertices.size() < 2 || stride == 0) {
362 std::vector<Kakshya::LineVertex> tangents;
363 tangents.reserve((path_vertices.size() - 1) / stride * 2);
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;
378 glm::vec3
color = path_vertices[i].color;
379 float thickness = path_vertices[i].thickness;
381 tangents.push_back({ .position = p0 -
tangent * 0.5F,
383 .thickness = thickness });
385 tangents.push_back({ .position = p0 +
tangent * 0.5F,
387 .thickness = thickness });
394 const std::vector<Kakshya::LineVertex>& path_vertices,
395 float curvature_scale,
398 if (path_vertices.size() < 3 || stride == 0) {
402 std::vector<Kakshya::LineVertex> curvatures;
403 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
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;
410 glm::vec3
curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
412 glm::vec3
color = path_vertices[i].color;
413 float thickness = path_vertices[i].thickness;
415 curvatures.push_back({ .position = p_curr,
417 .thickness = thickness });
419 curvatures.push_back({ .position = p_curr +
curvature,
421 .thickness = thickness });
432 const std::function<glm::vec3(
float)>& curve,
439 std::vector<glm::vec3> vertices;
440 vertices.reserve(samples);
442 for (
size_t i = 0; i < samples; ++i) {
443 float t =
static_cast<float>(i) /
static_cast<float>(samples - 1);
453 const std::vector<Kakshya::LineVertex>& path_vertices,
456 if (path_vertices.size() < 2 || num_samples < 2) {
457 return path_vertices;
460 std::vector<float> arc_lengths;
461 arc_lengths.reserve(path_vertices.size());
462 arc_lengths.push_back(0.0F);
464 float total_length = 0.0F;
465 for (
size_t i = 1; i < path_vertices.size(); ++i) {
466 float segment_length = glm::distance(
469 total_length += segment_length;
470 arc_lengths.push_back(total_length);
473 if (total_length < 1e-6F) {
474 return path_vertices;
477 std::vector<Kakshya::LineVertex> resampled;
478 resampled.reserve(num_samples);
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;
483 auto it = std::ranges::lower_bound(arc_lengths, target_length);
484 size_t idx = std::distance(arc_lengths.begin(), it);
487 resampled.push_back(path_vertices[0]);
488 }
else if (idx >= path_vertices.size()) {
489 resampled.push_back(path_vertices.back());
491 float s0 = arc_lengths[idx - 1];
492 float s1 = arc_lengths[idx];
493 float t = (target_length - s0) / (s1 - s0);
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);
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);
503 resampled.push_back({ .position =
position,
505 .thickness = 1.0F });
517 std::vector<glm::vec3>& vertices,
518 const glm::vec3& plane_point,
519 const glm::vec3& plane_normal)
521 glm::vec3 n = glm::normalize(plane_normal);
523 for (
auto& vertex : vertices) {
524 glm::vec3
offset = vertex - plane_point;
531 const std::vector<glm::vec3>& vertices,
532 const glm::vec3& projection_normal)
534 if (vertices.size() < 3) {
538 glm::vec3 n = glm::normalize(projection_normal);
541 if (std::abs(n.z) < 0.9F) {
542 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
544 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
547 glm::vec3 v = glm::cross(n, u);
554 std::vector<Point2D>
points;
555 points.reserve(vertices.size());
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 });
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;
569 return a.pos.y <
b.pos.y;
572 std::swap(
points[0], *pivot_it);
573 Point2D pivot =
points[0];
576 [&pivot](
const Point2D&
a,
const Point2D&
b) {
577 glm::vec2 va = a.pos - pivot.pos;
578 glm::vec2 vb = b.pos - pivot.pos;
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);
588 std::vector<size_t> hull;
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;
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];
601 glm::vec2 p1 =
points[second].pos;
602 glm::vec2 p2 =
points[top].pos;
603 glm::vec2 p3 =
points[i].pos;
605 if (ccw(p1, p2, p3)) {
613 std::vector<glm::vec3> result;
614 result.reserve(hull.size() + 1);
616 for (
size_t idx : hull) {
617 result.push_back(vertices[idx]);
620 result.push_back(vertices[hull[0]]);
631 const std::vector<glm::vec3>& colors,
632 const std::vector<float>& color_positions,
635 if (
positions.empty() || colors.empty()) {
639 std::vector<Kakshya::Vertex> vertices;
642 if (colors.size() == 1) {
644 vertices.push_back({ .position = pos,
651 std::vector<float> stops = color_positions;
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));
659 for (
size_t i = 0; i <
positions.size(); ++i) {
660 float t =
static_cast<float>(i) /
static_cast<float>(
positions.size() - 1);
665 }
else if (t >= stops.back()) {
666 color = colors.back();
669 for (
size_t j = 1; j < stops.size(); ++j) {
676 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
677 color = glm::mix(colors[idx], colors[idx + 1], local_t);
680 vertices.push_back({ .position =
positions[i],
690 const glm::vec3&
color,
693 std::vector<Kakshya::Vertex> vertices;
697 vertices.push_back({ .position = pos,
707 const std::vector<glm::vec3>& colors,
714 std::vector<Kakshya::Vertex> vertices;
717 for (
size_t i = 0; i <
positions.size(); ++i) {
718 vertices.push_back({ .position =
positions[i],
728 const float cos_r = std::cos(rotation);
729 const float sin_r = std::sin(rotation);
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;
737 out.vertices[i].position = {
742 out.vertices[i].texcoord = k_unit_quad[i].texcoord;
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 },
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 } },
771 const glm::vec2 mn = region.
min;
772 const glm::vec2 mx = region.
max;
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 } },
784 const glm::vec3& center,
const glm::vec3& half,
const glm::vec3&
color)
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),
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 },
825 const glm::vec3& center,
826 const glm::vec3& half_extents,
827 uint32_t subdivisions)
829 const uint32_t n = std::max(subdivisions, 1U);
831 std::vector<Kakshya::MeshVertex> verts;
833 verts.reserve(uint32_t(6 * (n + 1) * (n + 1)));
834 indices.reserve(uint32_t(6 * n * n * 6));
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 } },
852 for (
const auto& f : faces) {
853 const auto base =
static_cast<uint32_t
>(verts.size());
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;
861 .position = center + p * half_extents,
862 .uv = { fu, 1.0F - fv },
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;
880 auto data = Kakshya::MeshData::empty();
883 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
885 std::span<const uint32_t>(
indices),
888 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
893 const glm::vec3& center,
898 const glm::vec3& normal)
900 cols = std::max(cols, 1U);
903 const glm::vec3 n = glm::normalize(normal);
905 if (std::abs(n.y) < 0.9F) {
906 u = glm::normalize(glm::cross(n, glm::vec3(0.0F, 1.0F, 0.0F)));
908 u = glm::normalize(glm::cross(n, glm::vec3(1.0F, 0.0F, 0.0F)));
910 const glm::vec3 v = glm::normalize(glm::cross(u, n));
912 const float half_x = extent_x * 0.5F;
913 const float half_z = extent_z * 0.5F;
915 std::vector<Kakshya::MeshVertex> verts;
917 verts.reserve(uint32_t(2 * (cols + 1) * (
rows + 1)));
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);
929 .uv = { fu, 1.0F - fv },
935 const uint32_t stride = cols + 1;
936 const auto vert_count =
static_cast<uint32_t
>(verts.size());
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;
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);
957 .uv = { fu, 1.0F - fv },
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;
973 auto data = Kakshya::MeshData::empty();
976 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
978 std::span<const uint32_t>(
indices),
981 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
986 const std::function<glm::vec3(
float,
float)>& fn,
990 u_segs = std::max(u_segs, 1U);
991 v_segs = std::max(v_segs, 1U);
993 std::vector<Kakshya::MeshVertex> verts;
995 verts.reserve(uint32_t((u_segs + 1) * (v_segs + 1)));
996 indices.reserve(uint32_t(u_segs * v_segs * 6));
998 constexpr float eps = 1e-4F;
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);
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));
1012 .uv = { fu, 1.0F - fv },
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;
1029 auto data = Kakshya::MeshData::empty();
1032 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
1034 std::span<const uint32_t>(
indices),
1037 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
1043 std::span<const float> heights,
1046 const glm::vec2& extent,
1048 const glm::vec3& center)
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 {}",
1054 return Kakshya::MeshData::empty();
1058 const size_t vertex_count =
static_cast<size_t>(
columns) *
rows;
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);
1067 std::vector<uint8_t> bytes(vertex_count * stride);
1068 uint8_t*
const out = bytes.data();
1069 const float*
const h = heights.data();
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);
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;
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;
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;
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);
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 };
1100 std::memcpy(
out + (row *
columns + col) * stride, &vertex, stride);
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);
1109 for (
size_t row = 0; row <
rows; ++row) {
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);
1123 std::span<const glm::vec3> path,
1124 const std::function<
float(
float)>& radius_fn,
1125 uint32_t radial_segments,
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));
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)));
1136 constexpr float k_min_segment = 1e-6F;
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);
1148 if (std::abs(
tangent.y) < 0.9F) {
1149 u_axis = glm::normalize(glm::cross(
tangent, glm::vec3(0.0F, 1.0F, 0.0F)));
1151 u_axis = glm::normalize(glm::cross(
tangent, glm::vec3(1.0F, 0.0F, 0.0F)));
1154 glm::vec3 v_axis = glm::normalize(glm::cross(
tangent, u_axis));
1156 const float total_len = [&]() {
1158 for (uint32_t i = 1; i < n_pts; ++i)
1159 l += glm::distance(path[i], path[i - 1]);
1165 for (uint32_t pi = 0; pi < n_pts; ++pi) {
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)
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));
1183 arc += glm::distance(path[pi], path[pi - 1]);
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);
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;
1196 .position = path[pi] + radial * r,
1197 .uv = {
static_cast<float>(s) /
static_cast<float>(seg), t },
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;
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;
1219 const glm::vec3 cap_pos = path[(end == 0) ? 0 : n_pts - 1];
1221 const auto centre_idx =
static_cast<uint32_t
>(verts.size());
1223 .position = cap_pos,
1224 .uv = { 0.5F, t_cap },
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;
1240 auto data = Kakshya::MeshData::empty();
1243 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
1245 std::span<const uint32_t>(
indices),
1248 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
1253 const std::function<glm::vec2(
float)>& profile_fn,
1254 uint32_t profile_segs,
1255 uint32_t radial_segs,
1256 float sweep_radians)
1258 profile_segs = std::max(profile_segs, 2U);
1259 radial_segs = std::max(radial_segs, 3U);
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));
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);
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);
1276 const glm::vec3 pos { p.x * ca, p.y, p.x * sa };
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));
1287 .uv = { u, 1.0F - t },
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;
1304 auto data = Kakshya::MeshData::empty();
1307 std::span<const uint8_t>(
reinterpret_cast<const uint8_t*
>(verts.data()),
1309 std::span<const uint32_t>(
indices),
1312 data.layout.vertex_count =
static_cast<uint32_t
>(verts.size());
#define MF_ERROR(comp, ctx,...)
std::optional< glm::vec3 > position
std::vector< uint32_t > indices
std::vector< glm::vec2 > * points
std::vector< glm::vec3 > positions
std::vector< float > * out
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.
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 ¢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.
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 ¢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.
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 ¢er)
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.
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Axis-aligned bounding rectangle in a 2D coordinate space.
glm::vec3 center() const noexcept
glm::vec3 extent() const noexcept
Axis-aligned bounding box in 3D world space.
Kakshya::VertexLayout layout
Textured quad vertex data together with its semantic layout descriptor.