3#include <glm/gtc/matrix_transform.hpp>
12 const glm::vec3& center,
15 const glm::vec3& normal)
21 std::vector<glm::vec3> vertices;
22 vertices.reserve(segments + 1);
24 glm::vec3 n = glm::normalize(normal);
27 if (std::abs(n.z) < 0.9F) {
28 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
30 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
33 glm::vec3 v = glm::cross(n, u);
35 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
37 for (
size_t i = 0; i <= segments; ++i) {
38 float angle =
static_cast<float>(i) * angle_step;
39 float cos_a = std::cos(angle);
40 float sin_a = std::sin(angle);
42 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
44 vertices.push_back(position);
51 const glm::vec3& center,
55 const glm::vec3& normal)
61 std::vector<glm::vec3> vertices;
62 vertices.reserve(segments + 1);
64 glm::vec3 n = glm::normalize(normal);
67 if (std::abs(n.z) < 0.9F) {
68 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
70 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
73 glm::vec3 v = glm::cross(n, u);
75 float angle_step = glm::two_pi<float>() /
static_cast<float>(segments);
77 for (
size_t i = 0; i <= segments; ++i) {
78 float angle =
static_cast<float>(i) * angle_step;
79 float cos_a = std::cos(angle);
80 float sin_a = std::sin(angle);
82 glm::vec3 position = center + semi_major * cos_a * u + semi_minor * sin_a * v;
84 vertices.push_back(position);
91 const glm::vec3& center,
94 const glm::vec3& normal)
96 glm::vec3 n = glm::normalize(normal);
99 if (std::abs(n.z) < 0.9F) {
100 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
102 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
105 glm::vec3 v = glm::cross(n, u);
107 float half_width = width * 0.5F;
108 float half_height = height * 0.5F;
110 std::vector<glm::vec3> vertices;
113 vertices.push_back(center - half_width * u - half_height * v);
115 vertices.push_back(center + half_width * u - half_height * v);
117 vertices.push_back(center + half_width * u + half_height * v);
119 vertices.push_back(center - half_width * u + half_height * v);
121 vertices.push_back(vertices[0]);
127 const glm::vec3& center,
130 const glm::vec3& normal,
137 std::vector<glm::vec3> vertices;
138 vertices.reserve(sides + 1);
140 glm::vec3 n = glm::normalize(normal);
143 if (std::abs(n.z) < 0.9F) {
144 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
146 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
149 glm::vec3 v = glm::cross(n, u);
151 float angle_step = glm::two_pi<float>() /
static_cast<float>(sides);
153 for (
size_t i = 0; i <= sides; ++i) {
154 float angle =
static_cast<float>(i) * angle_step + phase_offset;
155 float cos_a = std::cos(angle);
156 float sin_a = std::sin(angle);
158 glm::vec3 position = center + radius * (cos_a * u + sin_a * v);
160 vertices.push_back(position);
171 std::vector<glm::vec3>& vertices,
172 const glm::mat4& transform)
174 for (
auto& vertex : vertices) {
175 glm::vec4 pos(vertex, 1.0F);
176 pos = transform * pos;
177 vertex = glm::vec3(pos);
182 std::vector<glm::vec3>& vertices,
183 const glm::vec3& axis,
185 const glm::vec3& origin)
187 glm::vec3 normalized_axis = glm::normalize(axis);
189 glm::mat4 transform = glm::translate(glm::mat4(1.0F), origin);
190 transform = glm::rotate(transform, angle, normalized_axis);
191 transform = glm::translate(transform, -origin);
197 std::vector<glm::vec3>& vertices,
198 const glm::vec3& displacement)
200 for (
auto& vertex : vertices) {
201 vertex += displacement;
206 std::vector<glm::vec3>& vertices,
208 const glm::vec3& origin)
214 for (
auto& vertex : vertices) {
215 glm::vec3 offset = vertex - origin;
216 vertex = origin + offset * scale;
221 std::vector<glm::vec3>& vertices,
222 const glm::vec3& scale,
223 const glm::vec3& origin)
225 for (
auto& vertex : vertices) {
226 glm::vec3 offset = vertex - origin;
227 vertex = origin + offset * scale;
236 const std::vector<Nodes::LineVertex>& path_vertices,
240 if (path_vertices.size() < 2 || stride == 0) {
244 std::vector<Nodes::LineVertex> normals;
245 normals.reserve((path_vertices.size() - 1) / stride * 2);
247 for (
size_t i = 0; i < path_vertices.size() - 1; i += stride) {
248 glm::vec3 p0 = path_vertices[i].position;
249 glm::vec3 p1 = path_vertices[i + 1].position;
251 glm::vec3 tangent = p1 - p0;
252 float length = glm::length(tangent);
254 if (length < 1e-6F) {
262 glm::vec3 normal(-tangent.y, tangent.x, 0.0F);
263 normal = glm::normalize(normal) * normal_length;
265 glm::vec3 midpoint = (p0 + p1) * 0.5F;
267 glm::vec3 color = path_vertices[i].color;
268 float thickness = path_vertices[i].thickness;
270 normals.push_back({ .position = midpoint - normal * 0.5F,
272 .thickness = thickness });
274 normals.push_back({ .position = midpoint + normal * 0.5F,
276 .thickness = thickness });
283 const std::vector<Nodes::LineVertex>& path_vertices,
284 float tangent_length,
287 if (path_vertices.size() < 2 || stride == 0) {
291 std::vector<Nodes::LineVertex> tangents;
292 tangents.reserve((path_vertices.size() - 1) / stride * 2);
294 for (
size_t i = 0; i < path_vertices.size() - 1; i += stride) {
295 glm::vec3 p0 = path_vertices[i].position;
296 glm::vec3 p1 = path_vertices[i + 1].position;
298 glm::vec3 tangent = p1 - p0;
299 float length = glm::length(tangent);
301 if (length < 1e-6F) {
305 tangent = glm::normalize(tangent) * tangent_length;
307 glm::vec3 color = path_vertices[i].color;
308 float thickness = path_vertices[i].thickness;
310 tangents.push_back({ .position = p0 - tangent * 0.5F,
312 .thickness = thickness });
314 tangents.push_back({ .position = p0 + tangent * 0.5F,
316 .thickness = thickness });
323 const std::vector<Nodes::LineVertex>& path_vertices,
324 float curvature_scale,
327 if (path_vertices.size() < 3 || stride == 0) {
331 std::vector<Nodes::LineVertex> curvatures;
332 curvatures.reserve((path_vertices.size() - 2) / stride * 2);
334 for (
size_t i = 1; i < path_vertices.size() - 1; i += stride) {
335 glm::vec3 p_prev = path_vertices[i - 1].position;
336 glm::vec3 p_curr = path_vertices[i].position;
337 glm::vec3 p_next = path_vertices[i + 1].position;
339 glm::vec3 curvature = (p_next - 2.0F * p_curr + p_prev) * curvature_scale;
341 glm::vec3 color = path_vertices[i].color;
342 float thickness = path_vertices[i].thickness;
344 curvatures.push_back({ .position = p_curr,
346 .thickness = thickness });
348 curvatures.push_back({ .position = p_curr + curvature,
350 .thickness = thickness });
361 const std::function<glm::vec3(
float)>& curve,
368 std::vector<glm::vec3> vertices;
369 vertices.reserve(samples);
371 for (
size_t i = 0; i < samples; ++i) {
372 float t =
static_cast<float>(i) /
static_cast<float>(samples - 1);
373 glm::vec3 position = curve(t);
375 vertices.push_back(position);
382 const std::vector<Nodes::LineVertex>& path_vertices,
385 if (path_vertices.size() < 2 || num_samples < 2) {
386 return path_vertices;
389 std::vector<float> arc_lengths;
390 arc_lengths.reserve(path_vertices.size());
391 arc_lengths.push_back(0.0F);
393 float total_length = 0.0F;
394 for (
size_t i = 1; i < path_vertices.size(); ++i) {
395 float segment_length = glm::distance(
396 path_vertices[i].position,
397 path_vertices[i - 1].position);
398 total_length += segment_length;
399 arc_lengths.push_back(total_length);
402 if (total_length < 1e-6F) {
403 return path_vertices;
406 std::vector<Nodes::LineVertex> resampled;
407 resampled.reserve(num_samples);
409 for (
size_t i = 0; i < num_samples; ++i) {
410 float target_length = (
static_cast<float>(i) /
static_cast<float>(num_samples - 1)) * total_length;
412 auto it = std::ranges::lower_bound(arc_lengths, target_length);
413 size_t idx = std::distance(arc_lengths.begin(), it);
416 resampled.push_back(path_vertices[0]);
417 }
else if (idx >= path_vertices.size()) {
418 resampled.push_back(path_vertices.back());
420 float s0 = arc_lengths[idx - 1];
421 float s1 = arc_lengths[idx];
422 float t = (target_length - s0) / (s1 - s0);
424 glm::vec3 p0 = path_vertices[idx - 1].position;
425 glm::vec3 p1 = path_vertices[idx].position;
426 glm::vec3 position = glm::mix(p0, p1, t);
428 glm::vec3 c0 = path_vertices[idx - 1].color;
429 glm::vec3 c1 = path_vertices[idx].color;
430 glm::vec3 color = glm::mix(c0, c1, t);
432 resampled.push_back({ .position = position,
434 .thickness = 1.0F });
446 std::vector<glm::vec3>& vertices,
447 const glm::vec3& plane_point,
448 const glm::vec3& plane_normal)
450 glm::vec3 n = glm::normalize(plane_normal);
452 for (
auto& vertex : vertices) {
453 glm::vec3 offset = vertex - plane_point;
454 float distance = glm::dot(offset, n);
455 vertex = vertex - distance * n;
460 const std::vector<glm::vec3>& vertices,
461 const glm::vec3& projection_normal)
463 if (vertices.size() < 3) {
467 glm::vec3 n = glm::normalize(projection_normal);
470 if (std::abs(n.z) < 0.9F) {
471 u = glm::normalize(glm::cross(n, glm::vec3(0, 0, 1)));
473 u = glm::normalize(glm::cross(n, glm::vec3(1, 0, 0)));
476 glm::vec3 v = glm::cross(n, u);
483 std::vector<Point2D> points;
484 points.reserve(vertices.size());
486 for (
size_t i = 0; i < vertices.size(); ++i) {
487 glm::vec3 offset = vertices[i];
488 float x = glm::dot(offset, u);
489 float y = glm::dot(offset, v);
490 points.push_back({ glm::vec2(x, y), i });
493 auto pivot_it = std::ranges::min_element(points,
494 [](
const Point2D&
a,
const Point2D&
b) {
495 if (std::abs(
a.pos.y -
b.pos.y) < 1e-6F) {
496 return a.pos.x < b.pos.x;
498 return a.pos.y <
b.pos.y;
501 std::swap(points[0], *pivot_it);
502 Point2D pivot = points[0];
504 std::sort(points.begin() + 1, points.end(),
505 [&pivot](
const Point2D&
a,
const Point2D&
b) {
506 glm::vec2 va = a.pos - pivot.pos;
507 glm::vec2 vb = b.pos - pivot.pos;
509 float cross = va.x * vb.y - va.y * vb.x;
510 if (std::abs(cross) < 1e-6F) {
511 return glm::length(va) < glm::length(vb);
517 std::vector<size_t> hull;
518 hull.push_back(points[0].index);
519 hull.push_back(points[1].index);
521 auto ccw = [](
const glm::vec2&
a,
const glm::vec2&
b,
const glm::vec2& c) {
522 return (
b.x -
a.x) * (c.y -
a.y) - (
b.y -
a.y) * (c.x -
a.x) > 0.0F;
525 for (
size_t i = 2; i < points.size(); ++i) {
526 while (hull.size() >= 2) {
527 size_t top = hull.back();
528 size_t second = hull[hull.size() - 2];
530 glm::vec2 p1 = points[second].pos;
531 glm::vec2 p2 = points[top].pos;
532 glm::vec2 p3 = points[i].pos;
534 if (ccw(p1, p2, p3)) {
539 hull.push_back(points[i].index);
542 std::vector<glm::vec3> result;
543 result.reserve(hull.size() + 1);
545 for (
size_t idx : hull) {
546 result.push_back(vertices[idx]);
549 result.push_back(vertices[hull[0]]);
559 const std::vector<glm::vec3>& positions,
560 const std::vector<glm::vec3>& colors,
561 const std::vector<float>& color_positions,
562 float default_thickness)
564 if (positions.empty() || colors.empty()) {
568 std::vector<Nodes::LineVertex> vertices;
569 vertices.reserve(positions.size());
571 if (colors.size() == 1) {
572 for (
const auto& pos : positions) {
573 vertices.push_back({ .position = pos,
575 .thickness = default_thickness });
580 std::vector<float> stops = color_positions;
582 stops.reserve(colors.size());
583 for (
size_t i = 0; i < colors.size(); ++i) {
584 stops.push_back(
static_cast<float>(i) /
static_cast<float>(colors.size() - 1));
588 for (
size_t i = 0; i < positions.size(); ++i) {
589 float t =
static_cast<float>(i) /
static_cast<float>(positions.size() - 1);
594 }
else if (t >= stops.back()) {
595 color = colors.back();
598 for (
size_t j = 1; j < stops.size(); ++j) {
605 float local_t = (t - stops[idx]) / (stops[idx + 1] - stops[idx]);
606 color = glm::mix(colors[idx], colors[idx + 1], local_t);
609 vertices.push_back({ .position = positions[i],
611 .thickness = default_thickness });
618 const std::vector<glm::vec3>& positions,
619 const glm::vec3& color,
620 float default_thickness)
622 std::vector<Nodes::LineVertex> vertices;
623 vertices.reserve(positions.size());
625 for (
const auto& pos : positions) {
626 vertices.push_back({ .position = pos,
628 .thickness = default_thickness });
635 const std::vector<glm::vec3>& positions,
636 const std::vector<glm::vec3>& colors,
637 float default_thickness)
639 if (positions.size() != colors.size()) {
643 std::vector<Nodes::LineVertex> vertices;
644 vertices.reserve(positions.size());
646 for (
size_t i = 0; i < positions.size(); ++i) {
647 vertices.push_back({ .position = positions[i],
649 .thickness = default_thickness });
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.
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.
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)