scimesh 0.3.2
Headless CPU-only 3D software renderer for scientific mesh visualization
Loading...
Searching...
No Matches
primitives.cpp
Go to the documentation of this file.
2#include <scimesh/normals.h>
4#include <glm/glm.hpp>
5#include <glm/gtc/constants.hpp>
6#include <cmath>
7#include <algorithm>
8
9namespace scimesh {
10
11static inline void make_basis(const Vec3 &dir, Vec3 &u, Vec3 &v) {
12 Vec3 arbitrary = (std::abs(glm::dot(dir, Vec3(0.0f, 1.0f, 0.0f))) < 0.999f)
13 ? Vec3(0.0f, 1.0f, 0.0f)
14 : Vec3(1.0f, 0.0f, 0.0f);
15 u = glm::normalize(glm::cross(arbitrary, dir));
16 v = glm::cross(dir, u);
17}
18
19
20Mesh generate_sphere(const Vec3 &center, float radius, int segments,
21 const Color &color) {
22 segments = std::max(3, segments);
23 Mesh m;
24
25 if (segments < 2) {
26 return m;
27 }
28
29 int n_rings = std::max(2, segments);
30 int verts_per_ring = segments;
31
32 uint32_t north_idx = 0;
33 m.vertices.push_back(center + Vec3(0.0f, radius, 0.0f));
34 m.colors.push_back(color);
35
36 for (int ring = 1; ring < n_rings; ++ring) {
37 float phi = glm::pi<float>() * static_cast<float>(ring) /
38 static_cast<float>(segments);
39 float y = radius * std::cos(phi);
40 float r = radius * std::sin(phi);
41
42 float step = glm::two_pi<float>() / static_cast<float>(verts_per_ring);
43 for (int j = 0; j < verts_per_ring; ++j) {
44 float theta = step * static_cast<float>(j);
45 m.vertices.push_back(
46 center + Vec3(r * std::cos(theta), y, r * std::sin(theta)));
47 m.colors.push_back(color);
48 }
49 }
50
51 uint32_t south_idx = static_cast<uint32_t>(m.vertices.size());
52 m.vertices.push_back(center + Vec3(0.0f, -radius, 0.0f));
53 m.colors.push_back(color);
54
55 m.normals.resize(m.vertices.size());
56 for (size_t i = 0; i < m.vertices.size(); ++i) {
57 m.normals[i] = glm::normalize(m.vertices[i] - center);
58 }
59
60 // North cap — outward-facing triangles (CCW from above)
61 for (int j = 0; j < verts_per_ring; ++j) {
62 uint32_t j_next = 1 + static_cast<uint32_t>((j + 1) % verts_per_ring);
63 m.triangles.push_back({north_idx, j_next, 1 + static_cast<uint32_t>(j)});
64 }
65
66 // Body rings
67 for (int ring = 0; ring < n_rings - 2; ++ring) {
68 uint32_t ring_start = 1 + static_cast<uint32_t>(ring) * verts_per_ring;
69 uint32_t next_start =
70 1 + static_cast<uint32_t>(ring + 1) * verts_per_ring;
71
72 for (int j = 0; j < verts_per_ring; ++j) {
73 uint32_t a = ring_start + j;
74 uint32_t b = ring_start + (j + 1) % verts_per_ring;
75 uint32_t c = next_start + (j + 1) % verts_per_ring;
76 uint32_t d = next_start + j;
77
78 m.triangles.push_back({a, b, c});
79 m.triangles.push_back({a, c, d});
80 }
81 }
82
83 // South cap — outward-facing triangles (CCW from below)
84 uint32_t last_start =
85 1 + static_cast<uint32_t>(n_rings - 2) * verts_per_ring;
86 for (int j = 0; j < verts_per_ring; ++j) {
87 uint32_t j_next = last_start + (j + 1) % verts_per_ring;
88 m.triangles.push_back({south_idx, last_start + j, j_next});
89 }
90
91 return m;
92}
93
94
95Mesh generate_cylinder(const Vec3 &start, const Vec3 &end, float radius,
96 int segments, const Color &color) {
97 segments = std::max(3, segments);
98 Mesh m;
99
100 Vec3 dir = glm::normalize(end - start);
101 Vec3 uu, vv;
102 make_basis(dir, uu, vv);
103
104 // Pre-calculate the radial vectors and positions for the rings
105 std::vector<Vec3> radials(segments);
106 std::vector<Vec3> bottom_ring(segments);
107 std::vector<Vec3> top_ring(segments);
108
109 float step = glm::two_pi<float>() / static_cast<float>(segments);
110 for (int i = 0; i < segments; ++i) {
111 float a = step * static_cast<float>(i);
112 radials[i] = std::cos(a) * uu + std::sin(a) * vv;
113 bottom_ring[i] = start + radius * radials[i];
114 top_ring[i] = end + radius * radials[i];
115 }
116
117 // --- 1. THE BODY (Smooth shading radially) ---
118 for (int i = 0; i < segments; ++i) {
119 m.vertices.push_back(bottom_ring[i]);
120 m.normals.push_back(radials[i]);
121 m.colors.push_back(color);
122 }
123 for (int i = 0; i < segments; ++i) {
124 m.vertices.push_back(top_ring[i]);
125 m.normals.push_back(radials[i]);
126 m.colors.push_back(color);
127 }
128
129 for (int i = 0; i < segments; ++i) {
130 uint32_t a = i;
131 uint32_t b = (i + 1) % segments;
132 uint32_t c = segments + (i + 1) % segments;
133 uint32_t d = segments + i;
134
135 // FIXED: Swapped from {a, c, b} to {a, b, c} for CCW outward facing
136 m.triangles.push_back({a, b, c});
137 m.triangles.push_back({a, c, d});
138 }
139
140 // --- 2. BOTTOM CAP (Flat shading, normal = -dir) ---
141 uint32_t bottom_cap_offset = static_cast<uint32_t>(m.vertices.size());
142
143 // Add the bottom center vertex
144 m.vertices.push_back(start);
145 m.normals.push_back(-dir);
146 m.colors.push_back(color);
147
148 // Add dedicated edge vertices for the bottom cap
149 for (int i = 0; i < segments; ++i) {
150 m.vertices.push_back(bottom_ring[i]);
151 m.normals.push_back(-dir); // Shared flat normal
152 m.colors.push_back(color);
153 }
154
155 for (int i = 0; i < segments; ++i) {
156 uint32_t center_idx = bottom_cap_offset;
157 uint32_t edge_idx = bottom_cap_offset + 1 + i;
158 uint32_t next_edge_idx = bottom_cap_offset + 1 + ((i + 1) % segments);
159
160 // Reversed CCW winding to face outward from the bottom
161 m.triangles.push_back({center_idx, next_edge_idx, edge_idx});
162 }
163
164 // --- 3. TOP CAP (Flat shading, normal = +dir) ---
165 uint32_t top_cap_offset = static_cast<uint32_t>(m.vertices.size());
166
167 // Add the top center vertex
168 m.vertices.push_back(end);
169 m.normals.push_back(dir);
170 m.colors.push_back(color);
171
172 // Add dedicated edge vertices for the top cap
173 for (int i = 0; i < segments; ++i) {
174 m.vertices.push_back(top_ring[i]);
175 m.normals.push_back(dir); // Shared flat normal
176 m.colors.push_back(color);
177 }
178
179 for (int i = 0; i < segments; ++i) {
180 uint32_t center_idx = top_cap_offset;
181 uint32_t edge_idx = top_cap_offset + 1 + i;
182 uint32_t next_edge_idx = top_cap_offset + 1 + ((i + 1) % segments);
183
184 // Standard CCW winding to face outward from the top
185 m.triangles.push_back({center_idx, edge_idx, next_edge_idx});
186 }
187
188 return m;
189}
190
191
192Mesh generate_cone(const Vec3 &base, const Vec3 &tip, float radius,
193 int segments, const Color &color) {
194 segments = std::max(3, segments);
195 Mesh m;
196
197 Vec3 dir_vec = tip - base;
198 float height = glm::length(dir_vec);
199
200 // Prevent division by zero if base and tip are identical
201 Vec3 dir = (height > 1e-8f) ? (dir_vec / height) : Vec3(0, 1, 0);
202
203 Vec3 uu, vv;
204 make_basis(dir, uu, vv);
205
206 float step = glm::two_pi<float>() / static_cast<float>(segments);
207
208 // --- 1. THE BODY (Smooth shading radially, duplicated tip) ---
209 uint32_t body_offset = 0;
210 for (int i = 0; i < segments; ++i) {
211 float a = step * static_cast<float>(i);
212 Vec3 radial = std::cos(a) * uu + std::sin(a) * vv;
213
214 // Calculate the mathematically perfect sloped normal for the cone surface
215 Vec3 slope_normal = glm::normalize(radial * height + dir * radius);
216
217 // Base ring vertex
218 m.vertices.push_back(base + radius * radial);
219 m.normals.push_back(slope_normal);
220 m.colors.push_back(color);
221
222 // Tip vertex (Duplicated for this specific slice to maintain the sloped normal)
223 m.vertices.push_back(tip);
224 m.normals.push_back(slope_normal);
225 m.colors.push_back(color);
226 }
227
228 for (int i = 0; i < segments; ++i) {
229 uint32_t base_idx = body_offset + i * 2;
230 uint32_t tip_idx = body_offset + i * 2 + 1;
231 uint32_t next_base_idx = body_offset + ((i + 1) % segments) * 2;
232
233 // Outward facing CCW
234 m.triangles.push_back({base_idx, next_base_idx, tip_idx});
235 }
236
237 // --- 2. BOTTOM CAP (Flat shading, normal = -dir) ---
238 uint32_t cap_offset = static_cast<uint32_t>(m.vertices.size());
239
240 // Add the bottom center vertex
241 m.vertices.push_back(base);
242 m.normals.push_back(-dir);
243 m.colors.push_back(color);
244
245 // Add dedicated edge vertices for the bottom cap
246 for (int i = 0; i < segments; ++i) {
247 float a = step * static_cast<float>(i);
248 Vec3 radial = std::cos(a) * uu + std::sin(a) * vv;
249
250 m.vertices.push_back(base + radius * radial);
251 m.normals.push_back(-dir); // Shared flat normal pointing down
252 m.colors.push_back(color);
253 }
254
255 for (int i = 0; i < segments; ++i) {
256 uint32_t center_idx = cap_offset;
257 uint32_t edge_idx = cap_offset + 1 + i;
258 uint32_t next_edge_idx = cap_offset + 1 + ((i + 1) % segments);
259
260 // Reversed CCW winding to face outward from the bottom
261 m.triangles.push_back({center_idx, next_edge_idx, edge_idx});
262 }
263
264 return m;
265}
266
267
268Mesh generate_arrow(const Vec3 &from, const Vec3 &to, float shaft_radius,
269 float head_radius, float head_length, int segments,
270 const Color &color) {
271 Vec3 dir_vec = to - from;
272 float total_len = glm::length(dir_vec);
273 if (total_len < 1e-8f)
274 return Mesh();
275
276 Vec3 dir = dir_vec / total_len;
277 float hl = std::min(head_length, total_len * 0.8f);
278
279 Vec3 head_base = to - hl * dir;
280
281 Mesh shaft = generate_cylinder(from, head_base, shaft_radius, segments, color);
282 Mesh head = generate_cone(head_base, to, head_radius, segments, color);
283
284 merge_mesh(shaft, head);
285 return shaft;
286}
287
288void merge_mesh(Mesh &dst, const Mesh &src) {
289 uint32_t offset = static_cast<uint32_t>(dst.vertices.size());
290 dst.vertices.insert(dst.vertices.end(), src.vertices.begin(),
291 src.vertices.end());
292 dst.colors.insert(dst.colors.end(), src.colors.begin(), src.colors.end());
293 dst.normals.insert(dst.normals.end(), src.normals.begin(), src.normals.end());
294
295 for (const auto &tri : src.triangles) {
296 dst.triangles.push_back(
297 {tri.v0 + offset, tri.v1 + offset, tri.v2 + offset});
298 }
299}
300
301Mesh generate_multi_spheres(const std::vector<Vec3> &centers,
302 const std::vector<float> &radii,
303 const std::vector<Color> &colors,
304 int segments) {
305 Mesh result;
306 size_t n = centers.size();
307 for (size_t i = 0; i < n; ++i) {
308 float r = (i < radii.size()) ? radii[i] : radii[0];
309 Color c = (i < colors.size()) ? colors[i] : colors[0];
310 Mesh sphere = generate_sphere(centers[i], r, segments, c);
311 merge_mesh(result, sphere);
312 }
313 return result;
314}
315
316Mesh generate_multi_cylinders(const std::vector<Vec3> &starts,
317 const std::vector<Vec3> &ends,
318 const std::vector<float> &radii,
319 const std::vector<Color> &colors,
320 int segments) {
321 Mesh result;
322 size_t n = starts.size();
323 for (size_t i = 0; i < n; ++i) {
324 float r = (i < radii.size()) ? radii[i] : radii[0];
325 Color c = (i < colors.size()) ? colors[i] : colors[0];
326 Mesh cyl = generate_cylinder(starts[i], ends[i], r, segments, c);
327 merge_mesh(result, cyl);
328 }
329 return result;
330}
331
332Mesh generate_cuboid(const Vec3 &center, const Vec3 &half,
333 const Color &color) {
334 Mesh m;
335
336 // 6 faces * 4 vertices = 24 distinct vertices
337 m.vertices.reserve(24);
338 m.normals.reserve(24);
339 m.colors.assign(24, color);
340 m.triangles.reserve(12);
341
342 float x = half.x, y = half.y, z = half.z;
343
344 // The 8 unique spatial positions (kept exactly as you had them)
345 Vec3 v[8] = {
346 center + Vec3(-x, -y, -z), // 0: Bottom-Left-Back
347 center + Vec3( x, -y, -z), // 1: Bottom-Right-Back
348 center + Vec3( x, y, -z), // 2: Top-Right-Back
349 center + Vec3(-x, y, -z), // 3: Top-Left-Back
350 center + Vec3(-x, -y, z), // 4: Bottom-Left-Front
351 center + Vec3( x, -y, z), // 5: Bottom-Right-Front
352 center + Vec3( x, y, z), // 6: Top-Right-Front
353 center + Vec3(-x, y, z), // 7: Top-Left-Front
354 };
355
356 // Define the 6 faces using the 8 positions, plus the perfect normal for that face
357 struct Face {
358 int v0, v1, v2, v3;
359 Vec3 normal;
360 };
361
362 Face faces[6] = {
363 {4, 5, 6, 7, Vec3( 0, 0, 1)}, // Front
364 {1, 0, 3, 2, Vec3( 0, 0, -1)}, // Back
365 {0, 1, 5, 4, Vec3( 0, -1, 0)}, // Bottom
366 {7, 6, 2, 3, Vec3( 0, 1, 0)}, // Top
367 {1, 2, 6, 5, Vec3( 1, 0, 0)}, // Right
368 {0, 4, 7, 3, Vec3(-1, 0, 0)} // Left
369 };
370
371 uint32_t index = 0;
372 for (int i = 0; i < 6; i++) {
373 // Push the 4 distinct vertices for this face
374 m.vertices.push_back(v[faces[i].v0]);
375 m.vertices.push_back(v[faces[i].v1]);
376 m.vertices.push_back(v[faces[i].v2]);
377 m.vertices.push_back(v[faces[i].v3]);
378
379 // Push the perfectly flat normal 4 times
380 for (int j = 0; j < 4; j++) {
381 m.normals.push_back(faces[i].normal);
382 }
383
384 // Create the two triangles for this quad using our new unrolled indices
385 m.triangles.push_back({index, index + 1, index + 2});
386 m.triangles.push_back({index, index + 2, index + 3});
387
388 index += 4;
389 }
390
391 return m;
392}
393
394
395Mesh generate_pyramid(const Vec3 &base_center, const Vec3 &apex,
396 float hw, const Color &color) {
397 Mesh m;
398
399 // 4 sides (3 verts each) + 1 square base (4 verts) = 16 distinct vertices
400 m.vertices.reserve(16);
401 m.normals.reserve(16);
402 m.colors.assign(16, color);
403 m.triangles.reserve(6);
404
405 // Define the base corner positions
406 Vec3 p0 = base_center + Vec3(-hw, 0, -hw); // Back-Left
407 Vec3 p1 = base_center + Vec3( hw, 0, -hw); // Back-Right
408 Vec3 p2 = base_center + Vec3( hw, 0, hw); // Front-Right
409 Vec3 p3 = base_center + Vec3(-hw, 0, hw); // Front-Left
410
411 // 1. GENERATE THE 4 SIDE FACES (Unrolled for sharp edges)
412 std::vector<std::array<Vec3, 3>> side_faces = {
413 {p1, p0, apex}, // Back side
414 {p2, p1, apex}, // Right side
415 {p3, p2, apex}, // Front side
416 {p0, p3, apex} // Left side
417 };
418
419 uint32_t index = 0;
420 for (const auto& face : side_faces) {
421 Vec3 A = face[0], B = face[1], C = face[2];
422
423 // Calculate exact perpendicular normal for this side
424 Vec3 normal = glm::normalize(glm::cross(B - A, C - A));
425
426 // Push 3 dedicated vertices for this face
427 m.vertices.push_back(A);
428 m.vertices.push_back(B);
429 m.vertices.push_back(C);
430
431 m.normals.push_back(normal);
432 m.normals.push_back(normal);
433 m.normals.push_back(normal);
434
435 m.triangles.push_back({index, index + 1, index + 2});
436 index += 3;
437 }
438
439 // 2. GENERATE THE BASE QUAD (4 shared vertices, identical straight-down normal)
440 Vec3 base_normal = Vec3(0.0f, -1.0f, 0.0f);
441
442 m.vertices.push_back(p0); // index 12
443 m.vertices.push_back(p1); // index 13
444 m.vertices.push_back(p2); // index 14
445 m.vertices.push_back(p3); // index 15
446
447 for (int i = 0; i < 4; i++) {
448 m.normals.push_back(base_normal);
449 }
450
451 // Two CCW triangles forming the square base (viewed from below)
452 m.triangles.push_back({index, index + 2, index + 3}); // {p0, p2, p3}
453 m.triangles.push_back({index, index + 1, index + 2}); // {p0, p1, p2}
454
455 return m;
456}
457
458Mesh generate_tetrahedron(const Vec3 &p0, const Vec3 &p1,
459 const Vec3 &p2, const Vec3 &p3,
460 const Color &color) {
461 Mesh m;
462
463 // 4 faces * 3 vertices per face = 12 distinct vertices
464 m.vertices.reserve(12);
465 m.normals.reserve(12);
466 m.colors.assign(12, color);
467 m.triangles.reserve(4);
468
469 Vec3 centroid = (p0 + p1 + p2 + p3) * 0.25f;
470
471 std::vector<std::array<Vec3, 3>> faces = {
472 {p0, p1, p2}, {p0, p2, p3}, {p0, p3, p1}, {p1, p3, p2}
473 };
474
475 uint32_t index = 0;
476 for (const auto& face : faces) {
477 Vec3 A = face[0], B = face[1], C = face[2];
478 Vec3 normal = glm::normalize(glm::cross(B - A, C - A));
479
480 // Flip normal and vertex order if facing inward
481 if (glm::dot(normal, A - centroid) < 0.0f) {
482 normal = -normal;
483 std::swap(B, C);
484 }
485
486 // Push 3 distinct vertices for this specific face
487 m.vertices.push_back(A);
488 m.vertices.push_back(B);
489 m.vertices.push_back(C);
490
491 // All 3 vertices share the exact same perpendicular face normal
492 m.normals.push_back(normal);
493 m.normals.push_back(normal);
494 m.normals.push_back(normal);
495
496 m.triangles.push_back({index, index + 1, index + 2});
497 index += 3;
498 }
499
500 return m;
501}
502
503
504Mesh generate_torus(const Vec3 &center, float R, float r,
505 int seg_major, int seg_minor, const Color &color) {
506 Mesh m;
507
508 // We can pre-allocate memory for slight performance gains
509 int total_vertices = seg_major * seg_minor;
510 m.vertices.reserve(total_vertices);
511 m.normals.reserve(total_vertices);
512 m.colors.reserve(total_vertices);
513 m.triangles.reserve(total_vertices * 2);
514
515 // 1. Generate Vertices, Colors, and Normals
516 for (int i = 0; i < seg_major; i++) {
517 float phi = glm::two_pi<float>() * float(i) / float(seg_major);
518 float cp = std::cos(phi), sp = std::sin(phi);
519
520 // Calculate the center of the current tube ring (minor center)
521 Vec3 minor_center = center + Vec3(R * cp, 0.0f, R * sp);
522
523 for (int j = 0; j < seg_minor; j++) {
524 float theta = glm::two_pi<float>() * float(j) / float(seg_minor);
525 float ct = std::cos(theta), st = std::sin(theta);
526
527 float px = (R + r * ct) * cp;
528 float py = r * st;
529 float pz = (R + r * ct) * sp;
530
531 Vec3 pos = center + Vec3(px, py, pz);
532 m.vertices.push_back(pos);
533 m.colors.push_back(color);
534
535 // The normal is simply the direction from the tube's center to the vertex
536 m.normals.push_back(glm::normalize(pos - minor_center));
537 }
538 }
539
540 // 2. Generate Triangles (with fixed, outward-facing winding order)
541 for (int i = 0; i < seg_major; i++) {
542 int ni = (i + 1) % seg_major;
543
544 for (int j = 0; j < seg_minor; j++) {
545 int nj = (j + 1) % seg_minor;
546
547 uint32_t a = i * seg_minor + j; // Current ring, current slice
548 uint32_t b = ni * seg_minor + j; // Next ring, current slice
549 uint32_t c = ni * seg_minor + nj; // Next ring, next slice
550 uint32_t d = i * seg_minor + nj; // Current ring, next slice
551
552 // FIXED: Flipped winding order so the torus renders from the outside
553 m.triangles.push_back({a, d, c});
554 m.triangles.push_back({a, c, b});
555 }
556 }
557
558 return m;
559}
560
561Mesh generate_plane(const Vec3 &center, const Vec3 &normal,
562 float hx, float hy, const Color &color) {
563 Mesh m;
564 Vec3 n = glm::length(normal) > 1e-9f ? glm::normalize(normal) : Vec3(0,0,1);
565 Vec3 u, v;
566 if (std::abs(n.x) < 0.9f) u = glm::normalize(glm::cross(n, Vec3(1,0,0)));
567 else u = glm::normalize(glm::cross(n, Vec3(0,1,0)));
568 v = glm::cross(n, u);
569
570 // Calculate the 4 corner positions
571 Vec3 p0 = center - u * hx - v * hy; // Bottom-Left
572 Vec3 p1 = center + u * hx - v * hy; // Bottom-Right
573 Vec3 p2 = center + u * hx + v * hy; // Top-Right
574 Vec3 p3 = center - u * hx + v * hy; // Top-Left
575
576 m.vertices.reserve(8);
577 m.normals.reserve(8);
578 m.colors.reserve(8);
579 m.triangles.reserve(4);
580
581 // --- 1. FRONT FACE (Normal = +n) ---
582 m.vertices.insert(m.vertices.end(), {p0, p1, p2, p3});
583 for (int i = 0; i < 4; i++) {
584 m.normals.push_back(n);
585 m.colors.push_back(color);
586 }
587 // Standard CCW winding for the front
588 m.triangles.push_back({0, 1, 2});
589 m.triangles.push_back({0, 2, 3});
590
591 // --- 2. BACK FACE (Normal = -n) ---
592 m.vertices.insert(m.vertices.end(), {p0, p1, p2, p3});
593 for (int i = 0; i < 4; i++) {
594 m.normals.push_back(-n);
595 m.colors.push_back(color);
596 }
597 // Reversed CCW winding so the triangles face outward from the back
598 m.triangles.push_back({4, 6, 5});
599 m.triangles.push_back({4, 7, 6});
600
601 return m;
602}
603
604} // namespace scimesh
Low-level math utilities for the rendering pipeline.
Mesh generate_pyramid(const Vec3 &base_center, const Vec3 &apex, float hw, const Color &color)
Generate a square-based pyramid.
Mesh generate_tetrahedron(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3, const Color &color)
Generate a tetrahedron (triangular pyramid) from four points.
Mesh generate_sphere(const Vec3 &center, float radius, int segments, const Color &color)
Generate a UV-sphere (latitude/longitude tessellation).
glm::vec3 Vec3
3-component floating-point vector (xyz).
Definition types.h:46
Mesh generate_cuboid(const Vec3 &center, const Vec3 &half, const Color &color)
Generate an axis-aligned cuboid (rectangular box).
void merge_mesh(Mesh &dst, const Mesh &src)
Merge one mesh into another (append geometry).
Mesh generate_arrow(const Vec3 &from, const Vec3 &to, float shaft_radius, float head_radius, float head_length, int segments, const Color &color)
Generate a 3D arrow from from to to.
Mesh generate_cylinder(const Vec3 &start, const Vec3 &end, float radius, int segments, const Color &color)
Generate a cylinder between two endpoints.
Mesh generate_torus(const Vec3 &center, float R, float r, int seg_major, int seg_minor, const Color &color)
Generate a torus (donut shape).
Mesh generate_multi_spheres(const std::vector< Vec3 > &centers, const std::vector< float > &radii, const std::vector< Color > &colors, int segments)
Generate multiple spheres in a single mesh (efficient batching).
Mesh generate_plane(const Vec3 &center, const Vec3 &normal, float hx, float hy, const Color &color)
Generate a flat rectangular plane.
Mesh generate_cone(const Vec3 &base, const Vec3 &tip, float radius, int segments, const Color &color)
Generate a cone from a base circle to a tip point.
Mesh generate_multi_cylinders(const std::vector< Vec3 > &starts, const std::vector< Vec3 > &ends, const std::vector< float > &radii, const std::vector< Color > &colors, int segments)
Generate multiple cylinders in a single mesh (efficient batching).
Compute per-vertex surface normals for lighting.
Procedural geometry generators.
An RGBA color with floating-point components.
Definition types.h:88
A 3D triangle mesh using an indexed face set representation.
Definition mesh.h:76
std::vector< Color > colors
Per-vertex RGBA colors.
Definition mesh.h:107
std::vector< Vec3 > normals
Per-vertex surface normals (unit-length direction vectors).
Definition mesh.h:125
std::vector< Vec3 > vertices
3D vertex positions.
Definition mesh.h:86
std::vector< Triangle > triangles
Triangle index triplets.
Definition mesh.h:94