3#include <glm/gtc/matrix_transform.hpp>
8 constexpr std::array<Nodes::TextureQuadVertex, 4> k_unit_quad = { {
9 { .position = { -1.0F, -1.0F, 0.0F }, .texcoord = { 0.0F, 1.0F } },
10 { .position = { 1.0F, -1.0F, 0.0F }, .texcoord = { 1.0F, 1.0F } },
11 { .position = { -1.0F, 1.0F, 0.0F }, .texcoord = { 0.0F, 0.0F } },
12 { .position = { 1.0F, 1.0F, 0.0F }, .texcoord = { 1.0F, 0.0F } },
22 const glm::vec3& center,
25 const glm::vec3& normal)
31 std::vector<glm::vec3> vertices;
32 vertices.reserve(segments + 1);
34 glm::vec3 n = glm::normalize(normal);
37 if (std::abs(n.z) < 0.9F) {
38 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
40 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
43 glm::vec3 v = glm::cross(n, u);
45 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
47 for (
size_t i = 0; i <= segments; ++i) {
48 float angle =
static_cast<float>(i) * angle_step;
49 float cos_a = std::cos(angle);
50 float sin_a = std::sin(angle);
61 const glm::vec3& center,
65 const glm::vec3& normal)
71 std::vector<glm::vec3> vertices;
72 vertices.reserve(segments + 1);
74 glm::vec3 n = glm::normalize(normal);
77 if (std::abs(n.z) < 0.9F) {
78 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
80 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
83 glm::vec3 v = glm::cross(n, u);
85 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
87 for (
size_t i = 0; i <= segments; ++i) {
88 float angle =
static_cast<float>(i) * angle_step;
89 float cos_a = std::cos(angle);
90 float sin_a = std::sin(angle);
92 glm::vec3
position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
101 const glm::vec3& center,
104 const glm::vec3& normal)
106 glm::vec3 n = glm::normalize(normal);
109 if (std::abs(n.z) < 0.9F) {
110 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
112 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
115 glm::vec3 v = glm::cross(n, u);
117 float half_width =
width * 0.5F;
118 float half_height = height * 0.5F;
120 std::vector<glm::vec3> vertices;
123 vertices.push_back(center - half_width * u - half_height * v);
125 vertices.push_back(center + half_width * u - half_height * v);
127 vertices.push_back(center + half_width * u + half_height * v);
129 vertices.push_back(center - half_width * u + half_height * v);
131 vertices.push_back(vertices[0]);
137 const glm::vec3& center,
140 const glm::vec3& normal,
147 std::vector<glm::vec3> vertices;
148 vertices.reserve(sides + 1);
150 glm::vec3 n = glm::normalize(normal);
153 if (std::abs(n.z) < 0.9F) {
154 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
156 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
159 glm::vec3 v = glm::cross(n, u);
161 float angle_step = glm::two_pi<float>() /
static_cast<float>(sides);
163 for (
size_t i = 0; i <= sides; ++i) {
164 float angle =
static_cast<float>(i) * angle_step + phase_offset;
165 float cos_a = std::cos(angle);
166 float sin_a = std::sin(angle);
181 std::vector<glm::vec3>& vertices,
182 const glm::mat4& transform)
184 for (
auto& vertex : vertices) {
185 glm::vec4 pos(vertex, 1.0F);
186 pos = transform * pos;
187 vertex = glm::vec3(pos);
192 std::vector<glm::vec3>& vertices,
193 const glm::vec3& axis,
195 const glm::vec3& origin)
197 glm::vec3 normalized_axis = glm::normalize(axis);
199 glm::mat4 transform = glm::translate(glm::mat4(1.0F), origin);
200 transform = glm::rotate(transform, angle, normalized_axis);
201 transform = glm::translate(transform, -origin);
207 std::vector<glm::vec3>& vertices,
208 const glm::vec3& displacement)
210 for (
auto& vertex : vertices) {
211 vertex += displacement;
216 std::vector<glm::vec3>& vertices,
218 const glm::vec3& origin)
224 for (
auto& vertex : vertices) {
225 glm::vec3 offset = vertex - origin;
226 vertex = origin + offset *
scale;
231 std::vector<glm::vec3>& vertices,
232 const glm::vec3&
scale,
233 const glm::vec3& origin)
235 for (
auto& vertex : vertices) {
236 glm::vec3 offset = vertex - origin;
237 vertex = origin + offset *
scale;
246 const std::vector<Nodes::LineVertex>& path_vertices,
250 if (path_vertices.size() < 2 || stride == 0) {
254 std::vector<Nodes::LineVertex> normals;
255 normals.reserve((path_vertices.size() - 1) / stride * 2);
257 for (
size_t i = 0; i < path_vertices.size() - 1; i += stride) {
258 glm::vec3 p0 = path_vertices[i].position;
259 glm::vec3 p1 = path_vertices[i + 1].position;
261 glm::vec3 tangent = p1 - p0;
262 float length = glm::length(tangent);
264 if (length < 1e-6F) {
272 glm::vec3 normal(-tangent.y, tangent.x, 0.0F);
273 normal = glm::normalize(normal) * normal_length;
275 glm::vec3 midpoint = (p0 + p1) * 0.5F;
277 glm::vec3
color = path_vertices[i].color;
278 float thickness = path_vertices[i].thickness;
280 normals.push_back({ .position = midpoint - normal * 0.5F,
282 .thickness = thickness });
284 normals.push_back({ .position = midpoint + normal * 0.5F,
286 .thickness = thickness });
293 const std::vector<Nodes::LineVertex>& path_vertices,
294 float tangent_length,
297 if (path_vertices.size() < 2 || stride == 0) {
301 std::vector<Nodes::LineVertex> tangents;
302 tangents.reserve((path_vertices.size() - 1) / stride * 2);
304 for (
size_t i = 0; i < path_vertices.size() - 1; i += stride) {
305 glm::vec3 p0 = path_vertices[i].position;
306 glm::vec3 p1 = path_vertices[i + 1].position;
308 glm::vec3 tangent = p1 - p0;
309 float length = glm::length(tangent);
311 if (length < 1e-6F) {
315 tangent = glm::normalize(tangent) * tangent_length;
317 glm::vec3
color = path_vertices[i].color;
318 float thickness = path_vertices[i].thickness;
320 tangents.push_back({ .position = p0 - tangent * 0.5F,
322 .thickness = thickness });
324 tangents.push_back({ .position = p0 + tangent * 0.5F,
326 .thickness = thickness });
333 const std::vector<Nodes::LineVertex>& path_vertices,
334 float curvature_scale,
337 if (path_vertices.size() < 3 || stride == 0) {
341 std::vector<Nodes::LineVertex> curvatures;
342 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
344 for (
size_t i = 1; i < path_vertices.size() - 1; i += stride) {
345 glm::vec3 p_prev = path_vertices[i - 1].position;
346 glm::vec3 p_curr = path_vertices[i].position;
347 glm::vec3 p_next = path_vertices[i + 1].position;
349 glm::vec3 curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
351 glm::vec3
color = path_vertices[i].color;
352 float thickness = path_vertices[i].thickness;
354 curvatures.push_back({ .position = p_curr,
356 .thickness = thickness });
358 curvatures.push_back({ .position = p_curr + curvature,
360 .thickness = thickness });
371 const std::function<glm::vec3(
float)>& curve,
378 std::vector<glm::vec3> vertices;
379 vertices.reserve(samples);
381 for (
size_t i = 0; i < samples; ++i) {
382 float t =
static_cast<float>(i) /
static_cast<float>(samples - 1);
392 const std::vector<Nodes::LineVertex>& path_vertices,
395 if (path_vertices.size() < 2 || num_samples < 2) {
396 return path_vertices;
399 std::vector<float> arc_lengths;
400 arc_lengths.reserve(path_vertices.size());
401 arc_lengths.push_back(0.0F);
403 float total_length = 0.0F;
404 for (
size_t i = 1; i < path_vertices.size(); ++i) {
405 float segment_length = glm::distance(
408 total_length += segment_length;
409 arc_lengths.push_back(total_length);
412 if (total_length < 1e-6F) {
413 return path_vertices;
416 std::vector<Nodes::LineVertex> resampled;
417 resampled.reserve(num_samples);
419 for (
size_t i = 0; i < num_samples; ++i) {
420 float target_length = (
static_cast<float>(i) /
static_cast<float>(num_samples - 1)) * total_length;
422 auto it = std::ranges::lower_bound(arc_lengths, target_length);
423 size_t idx = std::distance(arc_lengths.begin(), it);
426 resampled.push_back(path_vertices[0]);
427 }
else if (idx >= path_vertices.size()) {
428 resampled.push_back(path_vertices.back());
430 float s0 = arc_lengths[idx - 1];
431 float s1 = arc_lengths[idx];
432 float t = (target_length - s0) / (s1 - s0);
434 glm::vec3 p0 = path_vertices[idx - 1].position;
435 glm::vec3 p1 = path_vertices[idx].position;
436 glm::vec3
position = glm::mix(p0, p1, t);
438 glm::vec3 c0 = path_vertices[idx - 1].color;
439 glm::vec3 c1 = path_vertices[idx].color;
440 glm::vec3
color = glm::mix(c0, c1, t);
442 resampled.push_back({ .position =
position,
444 .thickness = 1.0F });
456 std::vector<glm::vec3>& vertices,
457 const glm::vec3& plane_point,
458 const glm::vec3& plane_normal)
460 glm::vec3 n = glm::normalize(plane_normal);
462 for (
auto& vertex : vertices) {
463 glm::vec3 offset = vertex - plane_point;
464 float distance = glm::dot(offset, n);
470 const std::vector<glm::vec3>& vertices,
471 const glm::vec3& projection_normal)
473 if (vertices.size() < 3) {
477 glm::vec3 n = glm::normalize(projection_normal);
480 if (std::abs(n.z) < 0.9F) {
481 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
483 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
486 glm::vec3 v = glm::cross(n, u);
493 std::vector<Point2D> points;
494 points.reserve(vertices.size());
496 for (
size_t i = 0; i < vertices.size(); ++i) {
497 glm::vec3 offset = vertices[i];
498 float x = glm::dot(offset, u);
499 float y = glm::dot(offset, v);
500 points.push_back({ glm::vec2(x, y), i });
503 auto pivot_it = std::ranges::min_element(points,
504 [](
const Point2D&
a,
const Point2D&
b) {
505 if (std::abs(
a.pos.y -
b.pos.y) < 1e-6F) {
506 return a.pos.x < b.pos.x;
508 return a.pos.y <
b.pos.y;
511 std::swap(points[0], *pivot_it);
512 Point2D pivot = points[0];
514 std::sort(points.begin() + 1, points.end(),
515 [&pivot](
const Point2D&
a,
const Point2D&
b) {
516 glm::vec2 va = a.pos - pivot.pos;
517 glm::vec2 vb = b.pos - pivot.pos;
519 float cross = va.x * vb.y - va.y * vb.x;
520 if (std::abs(cross) < 1e-6F) {
521 return glm::length(va) < glm::length(vb);
527 std::vector<size_t> hull;
528 hull.push_back(points[0].index);
529 hull.push_back(points[1].index);
531 auto ccw = [](
const glm::vec2&
a,
const glm::vec2&
b,
const glm::vec2& c) {
532 return (
b.x -
a.x) * (c.y -
a.y) - (
b.y -
a.y) * (c.x -
a.x) > 0.0F;
535 for (
size_t i = 2; i < points.size(); ++i) {
536 while (hull.size() >= 2) {
537 size_t top = hull.back();
538 size_t second = hull[hull.size() - 2];
540 glm::vec2 p1 = points[second].pos;
541 glm::vec2 p2 = points[top].pos;
542 glm::vec2 p3 = points[i].pos;
544 if (ccw(p1, p2, p3)) {
549 hull.push_back(points[i].index);
552 std::vector<glm::vec3> result;
553 result.reserve(hull.size() + 1);
555 for (
size_t idx : hull) {
556 result.push_back(vertices[idx]);
559 result.push_back(vertices[hull[0]]);
569 const std::vector<glm::vec3>& positions,
570 const std::vector<glm::vec3>& colors,
571 const std::vector<float>& color_positions,
572 float default_thickness)
574 if (positions.empty() || colors.empty()) {
578 std::vector<Nodes::LineVertex> vertices;
579 vertices.reserve(positions.size());
581 if (colors.size() == 1) {
582 for (
const auto& pos : positions) {
583 vertices.push_back({ .position = pos,
585 .thickness = default_thickness });
590 std::vector<float> stops = color_positions;
592 stops.reserve(colors.size());
593 for (
size_t i = 0; i < colors.size(); ++i) {
594 stops.push_back(
static_cast<float>(i) /
static_cast<float>(colors.size() - 1));
598 for (
size_t i = 0; i < positions.size(); ++i) {
599 float t =
static_cast<float>(i) /
static_cast<float>(positions.size() - 1);
604 }
else if (t >= stops.back()) {
605 color = colors.back();
608 for (
size_t j = 1; j < stops.size(); ++j) {
615 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
616 color = glm::mix(colors[idx], colors[idx + 1], local_t);
619 vertices.push_back({ .position = positions[i],
621 .thickness = default_thickness });
628 const std::vector<glm::vec3>& positions,
629 const glm::vec3&
color,
630 float default_thickness)
632 std::vector<Nodes::LineVertex> vertices;
633 vertices.reserve(positions.size());
635 for (
const auto& pos : positions) {
636 vertices.push_back({ .position = pos,
638 .thickness = default_thickness });
645 const std::vector<glm::vec3>& positions,
646 const std::vector<glm::vec3>& colors,
647 float default_thickness)
649 if (positions.size() != colors.size()) {
653 std::vector<Nodes::LineVertex> vertices;
654 vertices.reserve(positions.size());
656 for (
size_t i = 0; i < positions.size(); ++i) {
657 vertices.push_back({ .position = positions[i],
659 .thickness = default_thickness });
667 const float cos_r = std::cos(rotation);
668 const float sin_r = std::sin(rotation);
672 for (
size_t i = 0; i < 4; ++i) {
673 const float x = k_unit_quad[i].position.x *
scale.x;
674 const float y = k_unit_quad[i].position.y *
scale.y;
676 out.vertices[i].position = {
681 out.vertices[i].texcoord = k_unit_quad[i].texcoord;
std::optional< glm::vec3 > color
std::vector< Nodes::LineVertex > compute_path_normals(const std::vector< Nodes::LineVertex > &path_vertices, float normal_length, size_t stride)
Compute normal vectors along a piecewise-linear path.
std::vector< Nodes::LineVertex > reparameterize_by_arc_length(const std::vector< Nodes::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
void apply_transform(std::vector< glm::vec3 > &vertices, const glm::mat4 &transform)
Apply rigid transformation to vertex set.
void apply_translation(std::vector< glm::vec3 > &vertices, const glm::vec3 &displacement)
Apply translation to vertex set.
std::vector< Nodes::LineVertex > apply_color_gradient(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, const std::vector< float > &color_positions, float default_thickness)
Apply color interpolation to position vertices.
std::vector< Nodes::LineVertex > compute_path_tangents(const std::vector< Nodes::LineVertex > &path_vertices, float tangent_length, size_t stride)
Compute tangent vectors along a piecewise-linear path.
std::vector< Nodes::LineVertex > apply_vertex_colors(const std::vector< glm::vec3 > &positions, const std::vector< glm::vec3 > &colors, float default_thickness)
Convert positions to LineVertex with per-vertex colors.
std::vector< Nodes::LineVertex > apply_uniform_color(const std::vector< glm::vec3 > &positions, const glm::vec3 &color, float default_thickness)
Apply uniform color to position vertices.
std::vector< glm::vec3 > compute_convex_hull_2d(const std::vector< glm::vec3 > &vertices, const glm::vec3 &projection_normal)
Compute convex hull of vertex set (2D projection)
void project_onto_plane(std::vector< glm::vec3 > &vertices, const glm::vec3 &plane_point, const glm::vec3 &plane_normal)
Project vertices onto plane defined by normal.
std::vector< glm::vec3 > generate_ellipse(const glm::vec3 ¢er, float semi_major, float semi_minor, size_t segments, const glm::vec3 &normal)
Generate vertices along an elliptical path.
std::vector< glm::vec3 > sample_parametric_curve(const std::function< glm::vec3(float)> &curve, size_t samples)
Sample parametric curve uniformly in parameter space.
QuadGeometry generate_quad(glm::vec2 position, glm::vec2 scale, float rotation)
Generate a textured quad centred on the origin.
SpatialField distance(const glm::vec3 &anchor, float radius, DistanceMetric metric=DistanceMetric::EUCLIDEAN)
Normalized distance from an anchor point using the specified metric.
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< glm::vec3 > generate_rectangle(const glm::vec3 ¢er, float width, float height, const glm::vec3 &normal)
Generate vertices of an axis-aligned rectangular path.
void apply_scale(std::vector< glm::vec3 > &vertices, const glm::vec3 &scale, const glm::vec3 &origin)
Apply non-uniform scaling to vertex set.
std::vector< glm::vec3 > generate_regular_polygon(const glm::vec3 ¢er, float radius, size_t sides, const glm::vec3 &normal, float phase_offset)
Generate vertices of a regular n-gon.
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
void apply_rotation(std::vector< glm::vec3 > &vertices, const glm::vec3 &axis, float angle, const glm::vec3 &origin)
Apply rotation to vertex set around arbitrary axis.
void apply_uniform_scale(std::vector< glm::vec3 > &vertices, float scale, const glm::vec3 &origin)
Apply uniform scaling to vertex set.
std::vector< Nodes::LineVertex > compute_path_curvature(const std::vector< Nodes::LineVertex > &path_vertices, float curvature_scale, size_t stride)
Compute curvature vectors along a path (2nd derivative approximation)
Kakshya::VertexLayout layout
Textured quad vertex data together with its semantic layout descriptor.