1.3.64
 
Loading...
Searching...
No Matches
Context_object.cpp
Go to the documentation of this file.
1
16#include "Context.h"
17
18using namespace helios;
19
20uint Context::addSphereObject(uint Ndivs, const vec3 &center, float radius) {
21 return addSphereObject(Ndivs, center, {radius, radius, radius}, {0.f, 0.75f, 0.f}); // Default color is green
22}
23
24uint Context::addSphereObject(uint Ndivs, const vec3 &center, float radius, const RGBcolor &color) {
25 return addSphereObject(Ndivs, center, {radius, radius, radius}, color);
26}
27
28uint Context::addSphereObject(uint Ndivs, const vec3 &center, float radius, const char *texturefile) {
29 return addSphereObject(Ndivs, center, {radius, radius, radius}, texturefile);
30}
31
32uint Context::addSphereObject(uint Ndivs, const vec3 &center, const vec3 &radius) {
33 return addSphereObject(Ndivs, center, radius, {0.f, 0.75f, 0.f}); // Default color is green
34}
35
36uint Context::addSphereObject(uint Ndivs, const vec3 &center, const vec3 &radius, const RGBcolor &color) {
37 if (radius.x <= 0.f || radius.y <= 0.f || radius.z <= 0.f) {
38 helios_runtime_error("ERROR (Context::addSphereObject): Radius of sphere must be positive.");
39 }
40
41 std::vector<uint> UUID;
42 UUID.reserve(Ndivs * (Ndivs - 2) * 2 + 2 * Ndivs);
43
44 float dtheta = PI_F / float(Ndivs);
45 float dphi = 2.0f * PI_F / float(Ndivs);
46
47 vec3 cart;
48
49 // bottom cap
50 for (int j = 0; j < Ndivs; j++) {
51 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F, 0));
52 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
53 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + dtheta, float(j) * dphi));
54 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
55 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + dtheta, float(j + 1) * dphi));
56 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
57
58 UUID.push_back(addTriangle(v0, v1, v2, color));
59 }
60
61 // top cap
62 for (int j = 0; j < Ndivs; j++) {
63 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F, 0));
64 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
65 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F - dtheta, float(j) * dphi));
66 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
67 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F - dtheta, float(j + 1) * dphi));
68 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
69
70 UUID.push_back(addTriangle(v2, v1, v0, color));
71 }
72
73 // middle
74 for (int j = 0; j < Ndivs; j++) {
75 for (int i = 1; i < Ndivs - 1; i++) {
76 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i) * dtheta, float(j) * dphi));
77 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
78 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i + 1) * dtheta, float(j) * dphi));
79 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
80 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i + 1) * dtheta, float(j + 1) * dphi));
81 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
82 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i) * dtheta, float(j + 1) * dphi));
83 vec3 v3 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
84
85 UUID.push_back(addTriangle(v0, v1, v2, color));
86 UUID.push_back(addTriangle(v0, v2, v3, color));
87 }
88 }
89
90 auto *sphere_new = (new Sphere(currentObjectID, UUID, Ndivs, "", this));
91
92 float T[16], transform[16];
93 sphere_new->getTransformationMatrix(transform);
94
95 makeScaleMatrix(radius, T);
96 matmult(T, transform, transform);
97
98 makeTranslationMatrix(center, T);
99 matmult(T, transform, transform);
100 sphere_new->setTransformationMatrix(transform);
101
102 sphere_new->setColor(color);
103
104 for (uint p: UUID) {
105 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
106 }
107
108 objects[currentObjectID] = sphere_new;
109 currentObjectID++;
110 return currentObjectID - 1;
111}
112
113uint Context::addSphereObject(uint Ndivs, const vec3 &center, const vec3 &radius, const char *texturefile) {
114 if (!validateTextureFileExtenstion(texturefile)) {
115 helios_runtime_error("ERROR (Context::addSphereObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
116 } else if (!doesTextureFileExist(texturefile)) {
117 helios_runtime_error("ERROR (Context::addSphereObject): Texture file " + std::string(texturefile) + " does not exist.");
118 } else if (radius.x <= 0.f || radius.y <= 0.f || radius.z <= 0.f) {
119 helios_runtime_error("ERROR (Context::addSphereObject): Radius of sphere must be positive.");
120 }
121
122 std::vector<uint> UUID;
123 UUID.reserve(Ndivs * (Ndivs - 2) * 2 + 2 * Ndivs);
124
125 float dtheta = PI_F / float(Ndivs);
126 float dphi = 2.0f * PI_F / float(Ndivs);
127
128 vec3 cart;
129
130 // bottom cap
131 for (int j = 0; j < Ndivs; j++) {
132 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F, 0));
133 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
134 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + dtheta, float(j) * dphi));
135 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
136 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + dtheta, float(j + 1) * dphi));
137 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
138
139 vec3 n0 = v0 - center;
140 n0.normalize();
141 vec3 n1 = v1 - center;
142 n1.normalize();
143 vec3 n2 = v2 - center;
144 n2.normalize();
145
146 vec2 uv0 = make_vec2(1.f - atan2f(sinf((float(j) + 0.5f) * dphi), -cosf((float(j) + 0.5f) * dphi)) / (2.f * PI_F) - 0.5f, 1.f - n0.z * 0.5f - 0.5f);
147 vec2 uv1 = make_vec2(1.f - atan2f(n1.x, -n1.y) / (2.f * PI_F) - 0.5f, 1.f - n1.z * 0.5f - 0.5f);
148 vec2 uv2 = make_vec2(1.f - atan2f(n2.x, -n2.y) / (2.f * PI_F) - 0.5f, 1.f - n2.z * 0.5f - 0.5f);
149
150 if (j == Ndivs - 1) {
151 uv2.x = 1;
152 }
153
154 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
155 if (getPrimitiveArea(triangle_uuid) > 0) {
156 UUID.push_back(triangle_uuid);
157 } else {
158 deletePrimitive(triangle_uuid);
159 }
160 }
161
162 // top cap
163 for (int j = 0; j < Ndivs; j++) {
164 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F, 0));
165 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
166 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F - dtheta, float(j + 1) * dphi));
167 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
168 cart = sphere2cart(make_SphericalCoord(1.f, 0.5f * PI_F - dtheta, float(j) * dphi));
169 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
170 ;
171
172 vec3 n0 = v0 - center;
173 n0.normalize();
174 vec3 n1 = v1 - center;
175 n1.normalize();
176 vec3 n2 = v2 - center;
177 n2.normalize();
178
179 vec2 uv0 = make_vec2(1.f - atan2f(sinf((float(j) + 0.5f) * dphi), -cosf((float(j) + 0.5f) * dphi)) / (2.f * PI_F) - 0.5f, 1.f - n0.z * 0.5f - 0.5f);
180 vec2 uv1 = make_vec2(1.f - atan2f(n1.x, -n1.y) / (2.f * PI_F) - 0.5f, 1.f - n1.z * 0.5f - 0.5f);
181 vec2 uv2 = make_vec2(1.f - atan2f(n2.x, -n2.y) / (2.f * PI_F) - 0.5f, 1.f - n2.z * 0.5f - 0.5f);
182
183 if (j == Ndivs - 1) {
184 uv2.x = 1;
185 }
186
187 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
188 if (getPrimitiveArea(triangle_uuid) > 0) {
189 UUID.push_back(triangle_uuid);
190 } else {
191 deletePrimitive(triangle_uuid);
192 }
193 }
194
195 // middle
196 for (int j = 0; j < Ndivs; j++) {
197 for (int i = 1; i < Ndivs - 1; i++) {
198 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i) * dtheta, float(j) * dphi));
199 vec3 v0 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
200 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i + 1) * dtheta, float(j) * dphi));
201 vec3 v1 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
202 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i + 1) * dtheta, float(j + 1) * dphi));
203 vec3 v2 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
204 cart = sphere2cart(make_SphericalCoord(1.f, -0.5f * PI_F + float(i) * dtheta, float(j + 1) * dphi));
205 vec3 v3 = center + make_vec3(cart.x * radius.x, cart.y * radius.y, cart.z * radius.z);
206
207 vec3 n0 = v0 - center;
208 n0.normalize();
209 vec3 n1 = v1 - center;
210 n1.normalize();
211 vec3 n2 = v2 - center;
212 n2.normalize();
213 vec3 n3 = v3 - center;
214 n3.normalize();
215
216 vec2 uv0 = make_vec2(1.f - atan2f(n0.x, -n0.y) / (2.f * PI_F) - 0.5f, 1.f - n0.z * 0.5f - 0.5f);
217 vec2 uv1 = make_vec2(1.f - atan2f(n1.x, -n1.y) / (2.f * PI_F) - 0.5f, 1.f - n1.z * 0.5f - 0.5f);
218 vec2 uv2 = make_vec2(1.f - atan2f(n2.x, -n2.y) / (2.f * PI_F) - 0.5f, 1.f - n2.z * 0.5f - 0.5f);
219 vec2 uv3 = make_vec2(1.f - atan2f(n3.x, -n3.y) / (2.f * PI_F) - 0.5f, 1.f - n3.z * 0.5f - 0.5f);
220
221 if (j == Ndivs - 1) {
222 uv2.x = 1;
223 uv3.x = 1;
224 }
225
226 uint triangle_uuid1 = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
227 if (getPrimitiveArea(triangle_uuid1) > 0) {
228 UUID.push_back(triangle_uuid1);
229 } else {
230 deletePrimitive(triangle_uuid1);
231 }
232
233 uint triangle_uuid2 = addTriangle(v0, v2, v3, texturefile, uv0, uv2, uv3);
234 if (getPrimitiveArea(triangle_uuid2) > 0) {
235 UUID.push_back(triangle_uuid2);
236 } else {
237 deletePrimitive(triangle_uuid2);
238 }
239 }
240 }
241
242 auto *sphere_new = (new Sphere(currentObjectID, UUID, Ndivs, texturefile, this));
243
244 float T[16], transform[16];
245 sphere_new->getTransformationMatrix(transform);
246
247 makeScaleMatrix(radius, T);
248 matmult(T, transform, transform);
249
250 makeTranslationMatrix(center, T);
251 matmult(T, transform, transform);
252 sphere_new->setTransformationMatrix(transform);
253
254 for (uint p: UUID) {
255 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
256 }
257
258 objects[currentObjectID] = sphere_new;
259 currentObjectID++;
260
261 return currentObjectID - 1;
262}
263
264uint Context::addTileObject(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv) {
265 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
266
267 return addTileObject(center, size, rotation, subdiv, color);
268}
269
270uint Context::addTileObject(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const RGBcolor &color) {
271 if (size.x == 0 || size.y == 0) {
272 helios_runtime_error("ERROR (Context::addTileObject): Size of tile must be greater than 0.");
273 } else if (subdiv.x < 1 || subdiv.y < 1) {
274 helios_runtime_error("ERROR (Context::addTileObject): Number of tile subdivisions must be greater than 0.");
275 }
276
277 std::vector<uint> UUID;
278 UUID.reserve(subdiv.x * subdiv.y);
279
280 vec2 subsize;
281 subsize.x = size.x / float(subdiv.x);
282 subsize.y = size.y / float(subdiv.y);
283
284 for (uint j = 0; j < subdiv.y; j++) {
285 for (uint i = 0; i < subdiv.x; i++) {
286 vec3 subcenter = make_vec3(-0.5f * size.x + (float(i) + 0.5f) * subsize.x, -0.5f * size.y + (float(j) + 0.5f) * subsize.y, 0.f);
287
288 UUID.push_back(addPatch(subcenter, subsize, make_SphericalCoord(0, 0), color));
289
290 if (rotation.elevation != 0.f) {
291 getPrimitivePointer_private(UUID.back())->rotate(-rotation.elevation, "x");
292 }
293 if (rotation.azimuth != 0.f) {
294 getPrimitivePointer_private(UUID.back())->rotate(-rotation.azimuth, "z");
295 }
296 getPrimitivePointer_private(UUID.back())->translate(center);
297 }
298 }
299
300 auto *tile_new = (new Tile(currentObjectID, UUID, subdiv, "", this));
301
302 float T[16], S[16], R[16];
303
304 float transform[16];
305 tile_new->getTransformationMatrix(transform);
306
307 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), S);
308 matmult(S, transform, transform);
309
310 makeRotationMatrix(-rotation.elevation, "x", R);
311 matmult(R, transform, transform);
312 makeRotationMatrix(-rotation.azimuth, "z", R);
313 matmult(R, transform, transform);
314
315 makeTranslationMatrix(center, T);
316 matmult(T, transform, transform);
317 tile_new->setTransformationMatrix(transform);
318
319 tile_new->setColor(color);
320
321 for (uint p: UUID) {
322 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
323 }
324
325 tile_new->object_origin = center;
326
327 objects[currentObjectID] = tile_new;
328 currentObjectID++;
329 return currentObjectID - 1;
330}
331
332uint Context::addTileObject(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const char *texturefile) {
333 return addTileObject(center, size, rotation, subdiv, texturefile, make_int2(1, 1));
334}
335
336uint Context::addTileObject(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const char *texturefile, const int2 &texture_repeat) {
337 if (!validateTextureFileExtenstion(texturefile)) {
338 helios_runtime_error("ERROR (Context::addTileObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
339 } else if (!doesTextureFileExist(texturefile)) {
340 helios_runtime_error("ERROR (Context::addTileObject): Texture file " + std::string(texturefile) + " does not exist.");
341 } else if (size.x == 0 || size.y == 0) {
342 helios_runtime_error("ERROR (Context::addTileObject): Size of tile must be greater than 0.");
343 } else if (subdiv.x < 1 || subdiv.y < 1) {
344 helios_runtime_error("ERROR (Context::addTileObject): Number of tile subdivisions must be greater than 0.");
345 } else if (texture_repeat.x < 1 || texture_repeat.y < 1) {
346 helios_runtime_error("ERROR (Context::addTileObject): Number of texture repeats must be greater than 0.");
347 }
348
349 // Automatically resize the repeat count so that it evenly divides the subdivisions.
350 int2 repeat = texture_repeat;
351 repeat.x = std::min(subdiv.x, repeat.x);
352 repeat.y = std::min(subdiv.y, repeat.y);
353 while (subdiv.x % repeat.x != 0) {
354 repeat.x--;
355 }
356 while (subdiv.y % repeat.y != 0) {
357 repeat.y--;
358 }
359
360 std::vector<uint> UUID;
361 UUID.reserve(subdiv.x * subdiv.y);
362
363 vec2 subsize;
364 subsize.x = size.x / float(subdiv.x);
365 subsize.y = size.y / float(subdiv.y);
366
367 std::vector<helios::vec2> uv(4);
368 int2 sub_per_repeat;
369 sub_per_repeat.x = subdiv.x / repeat.x;
370 sub_per_repeat.y = subdiv.y / repeat.y;
371 vec2 uv_sub;
372 uv_sub.x = 1.f / float(sub_per_repeat.x);
373 uv_sub.y = 1.f / float(sub_per_repeat.y);
374
375 addTexture(texturefile);
376
377 const int2 &sz = textures.at(texturefile).getImageResolution();
378 if (subdiv.x >= repeat.x * sz.x || subdiv.y >= repeat.y * sz.y) {
379 helios_runtime_error("ERROR (Context::addTileObject): The resolution of the texture image '" + std::string(texturefile) + "' is lower than the number of tile subdivisions. Increase resolution of the texture image.");
380 }
381
382 for (uint j = 0; j < subdiv.y; j++) {
383 for (uint i = 0; i < subdiv.x; i++) {
384 vec3 subcenter = make_vec3(-0.5f * size.x + (float(i) + 0.5f) * subsize.x, -0.5f * size.y + (float(j) + 0.5f) * subsize.y, 0.f);
385
386 uint i_local = i % sub_per_repeat.x;
387 uint j_local = j % sub_per_repeat.y;
388 uv.at(0) = make_vec2(float(i_local) * uv_sub.x, float(j_local) * uv_sub.y);
389 uv.at(1) = make_vec2(float(i_local + 1) * uv_sub.x, float(j_local) * uv_sub.y);
390 uv.at(2) = make_vec2(float(i_local + 1) * uv_sub.x, float(j_local + 1) * uv_sub.y);
391 uv.at(3) = make_vec2(float(i_local) * uv_sub.x, float(j_local + 1) * uv_sub.y);
392
393 auto *patch_new = (new Patch(texturefile, uv, textures, 0, currentUUID));
394
395 // \todo This is causing problems in the radiation intersection.
396 // if( patch_new->getSolidFraction()==0 ){
397 // delete patch_new;
398 // continue;
399 // }
400
401 assert(size.x > 0.f && size.y > 0.f);
402 patch_new->scale(make_vec3(subsize.x, subsize.y, 1));
403
404 patch_new->translate(subcenter);
405
406 if (rotation.elevation != 0) {
407 patch_new->rotate(-rotation.elevation, "x");
408 }
409 if (rotation.azimuth != 0) {
410 patch_new->rotate(-rotation.azimuth, "z");
411 }
412
413 patch_new->translate(center);
414
415 primitives[currentUUID] = patch_new;
416
417 // Set context pointer
418 patch_new->context_ptr = this;
419
420 // Create or reuse material with de-duplication
421 std::string mat_label = generateMaterialLabel(make_RGBAcolor(0, 0, 0, 1), texturefile, false);
422 if (!doesMaterialExist(mat_label)) {
423 patch_new->materialID = addMaterial_internal(mat_label, make_RGBAcolor(0, 0, 0, 1), texturefile);
424 } else {
425 patch_new->materialID = getMaterialIDFromLabel(mat_label);
426 }
427 // Increment material reference count
428 materials[patch_new->materialID].reference_count++;
429
430 currentUUID++;
431 UUID.push_back(currentUUID - 1);
432 }
433 }
434
435 auto *tile_new = (new Tile(currentObjectID, UUID, subdiv, texturefile, this));
436
437 float T[16], S[16], R[16];
438
439 float transform[16];
440 tile_new->getTransformationMatrix(transform);
441
442 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), S);
443 matmult(S, transform, transform);
444
445 makeRotationMatrix(-rotation.elevation, "x", R);
446 matmult(R, transform, transform);
447 makeRotationMatrix(-rotation.azimuth, "z", R);
448 matmult(R, transform, transform);
449
450 makeTranslationMatrix(center, T);
451 matmult(T, transform, transform);
452 tile_new->setTransformationMatrix(transform);
453
454 for (uint p: UUID) {
455 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
456 }
457
458 tile_new->object_origin = center;
459
460 objects[currentObjectID] = tile_new;
461 currentObjectID++;
462 return currentObjectID - 1;
463}
464
465uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius) {
466 uint node_count = nodes.size();
467
468 std::vector<RGBcolor> color(node_count);
469
470 for (uint i = 0; i < node_count; i++) {
471 color.at(i) = make_RGBcolor(0.f, 0.75f, 0.f); // Default color is green
472 }
473
474 return addTubeObject(radial_subdivisions, nodes, radius, color);
475}
476
477uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const std::vector<RGBcolor> &color) {
478 const uint node_count = nodes.size();
479
480 if (node_count == 0) {
481 helios_runtime_error("ERROR (Context::addTubeObject): Node and radius arrays are empty.");
482 } else if (node_count != radius.size()) {
483 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `radius' arguments must agree.");
484 } else if (node_count != color.size()) {
485 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `color' arguments must agree.");
486 }
487
488 // Clamp very small radii to avoid creating degenerate triangles
489 const float min_radius_threshold = 1e-5f;
490 std::vector<float> radius_clamped = radius;
491 for (int i = 0; i < node_count; i++) {
492 if (radius_clamped[i] < min_radius_threshold && radius_clamped[i] >= 0) {
493 radius_clamped[i] = min_radius_threshold;
494 }
495 }
496
497 vec3 axial_vector;
498 std::vector<float> cfact(radial_subdivisions + 1);
499 std::vector<float> sfact(radial_subdivisions + 1);
500 std::vector<std::vector<vec3>> triangle_vertices;
501 resize_vector(triangle_vertices, radial_subdivisions + 1, node_count);
502
503 // Initialize trigonometric factors for circle points
504 for (int j = 0; j < radial_subdivisions + 1; j++) {
505 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
506 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
507 }
508
509 vec3 initial_radial(1.0f, 0.0f, 0.0f);
510 vec3 previous_axial_vector;
511 vec3 previous_radial_dir;
512
513 for (int i = 0; i < node_count; i++) { // Looping over tube segments
514 if (radius.at(i) < 0) {
515 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
516 }
517
518 if (i == 0) {
519 axial_vector = nodes[i + 1] - nodes[i];
520 float mag = axial_vector.magnitude();
521 if (mag < 1e-6f) {
522 axial_vector = make_vec3(0, 0, 1);
523 } else {
524 axial_vector = axial_vector / mag;
525 }
526 if (fabs(axial_vector * initial_radial) > 0.95f) {
527 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
528 }
529 // Also handle nearly vertical axes
530 if (fabs(axial_vector.z) > 0.95f) {
531 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
532 }
533 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
534 } else {
535 if (i == node_count - 1) {
536 axial_vector = nodes[i] - nodes[i - 1];
537 } else {
538 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
539 }
540 float mag = axial_vector.magnitude();
541 if (mag < 1e-6f) {
542 axial_vector = make_vec3(0, 0, 1);
543 } else {
544 axial_vector = axial_vector / mag;
545 }
546
547 // Calculate radial direction using parallel transport
548 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
549 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
550 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
551 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
552 } else {
553 // Vectors are nearly parallel, use robust fallback
554 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
555 if (fabs(axial_vector * fallback_radial) > 0.95f) {
556 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
557 }
558 if (fabs(axial_vector.z) > 0.95f) {
559 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
560 }
561 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
562 }
563 }
564
565 previous_axial_vector = axial_vector;
566
567 vec3 radial_dir = previous_radial_dir;
568 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
569 orthogonal_dir.normalize();
570
571 for (int j = 0; j < radial_subdivisions + 1; j++) {
572 vec3 normal = cfact[j] * radius_clamped[i] * radial_dir + sfact[j] * radius_clamped[i] * orthogonal_dir;
573 triangle_vertices[i][j] = nodes[i] + normal;
574 }
575 }
576
577
578 std::vector<uint> UUIDs(2 * (node_count - 1) * radial_subdivisions);
579 vec3 v0, v1, v2;
580
581 int ii = 0;
582 for (int j = 0; j < radial_subdivisions; j++) {
583 for (int i = 0; i < node_count - 1; i++) {
584 v0 = triangle_vertices[i][j];
585 v1 = triangle_vertices[i + 1][j + 1];
586 v2 = triangle_vertices[i][j + 1];
587
588 UUIDs.at(ii) = addTriangle(v0, v1, v2, color.at(i));
589
590 v0 = triangle_vertices[i][j];
591 v1 = triangle_vertices[i + 1][j];
592 v2 = triangle_vertices[i + 1][j + 1];
593
594 UUIDs.at(ii + 1) = addTriangle(v0, v1, v2, color.at(i));
595
596 ii += 2;
597 }
598 }
599
600 auto *tube_new = (new Tube(currentObjectID, UUIDs, nodes, radius, color, triangle_vertices, radial_subdivisions, "", this));
601
602 for (uint p: UUIDs) {
603 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
604 }
605
606 objects[currentObjectID] = tube_new;
607 currentObjectID++;
608
609 uint objID = currentObjectID - 1;
610 tube_new->object_origin = getObjectCenter(objID);
611
612 return objID;
613}
614
615uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const char *texturefile) {
616 size_t node_count = nodes.size();
617 std::vector<float> textureuv_ufrac(node_count);
618 for (int i = 0; i < node_count; i++) {
619 textureuv_ufrac.at(i) = float(i) / float(node_count - 1);
620 }
621
622 return addTubeObject(radial_subdivisions, nodes, radius, texturefile, textureuv_ufrac);
623}
624
625uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const char *texturefile, const std::vector<float> &textureuv_ufrac) {
626 if (!validateTextureFileExtenstion(texturefile)) {
627 helios_runtime_error("ERROR (Context::addTubeObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
628 } else if (!doesTextureFileExist(texturefile)) {
629 helios_runtime_error("ERROR (Context::addTubeObject): Texture file " + std::string(texturefile) + " does not exist.");
630 }
631
632 const uint node_count = nodes.size();
633
634 if (node_count == 0) {
635 helios_runtime_error("ERROR (Context::addTubeObject): Node and radius arrays are empty.");
636 } else if (node_count != radius.size()) {
637 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `radius' arguments must agree.");
638 } else if (node_count != textureuv_ufrac.size()) {
639 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `textureuv_ufrac' arguments must agree.");
640 }
641
642 // Clamp very small radii to avoid creating degenerate triangles
643 const float min_radius_threshold = 1e-5f;
644 std::vector<float> radius_clamped = radius;
645 for (int i = 0; i < node_count; i++) {
646 if (radius_clamped[i] < min_radius_threshold && radius_clamped[i] >= 0) {
647 radius_clamped[i] = min_radius_threshold;
648 }
649 }
650
651 vec3 axial_vector;
652 std::vector<float> cfact(radial_subdivisions + 1);
653 std::vector<float> sfact(radial_subdivisions + 1);
654 std::vector<std::vector<vec3>> triangle_vertices;
655 resize_vector(triangle_vertices, radial_subdivisions + 1, node_count);
656 std::vector<std::vector<vec2>> uv;
657 resize_vector(uv, radial_subdivisions + 1, node_count);
658
659 // Initialize trigonometric factors for circle points
660 for (int j = 0; j < radial_subdivisions + 1; j++) {
661 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
662 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
663 }
664
665 vec3 initial_radial(1.0f, 0.0f, 0.0f);
666 vec3 previous_axial_vector;
667 vec3 previous_radial_dir;
668
669 for (int i = 0; i < node_count; i++) { // Looping over tube segments
670 if (radius.at(i) < 0) {
671 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
672 }
673
674 if (i == 0) {
675 axial_vector = nodes[i + 1] - nodes[i];
676 float mag = axial_vector.magnitude();
677 if (mag < 1e-6f) {
678 axial_vector = make_vec3(0, 0, 1);
679 } else {
680 axial_vector = axial_vector / mag;
681 }
682 if (fabs(axial_vector * initial_radial) > 0.95f) {
683 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
684 }
685 // Also handle nearly vertical axes
686 if (fabs(axial_vector.z) > 0.95f) {
687 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
688 }
689 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
690 } else {
691 if (i == node_count - 1) {
692 axial_vector = nodes[i] - nodes[i - 1];
693 } else {
694 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
695 }
696 float mag = axial_vector.magnitude();
697 if (mag < 1e-6f) {
698 axial_vector = make_vec3(0, 0, 1);
699 } else {
700 axial_vector = axial_vector / mag;
701 }
702
703 // Calculate radial direction using parallel transport
704 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
705 if (rotation_axis.magnitude() > 1e-5) {
706 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
707 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
708 } else {
709 // Vectors are nearly parallel, use robust fallback
710 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
711 if (fabs(axial_vector * fallback_radial) > 0.95f) {
712 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
713 }
714 if (fabs(axial_vector.z) > 0.95f) {
715 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
716 }
717 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
718 }
719 }
720
721 previous_axial_vector = axial_vector;
722
723 vec3 radial_dir = previous_radial_dir;
724 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
725 orthogonal_dir.normalize();
726
727 for (int j = 0; j < radial_subdivisions + 1; j++) {
728 vec3 normal = cfact[j] * radius_clamped[i] * radial_dir + sfact[j] * radius_clamped[i] * orthogonal_dir;
729 triangle_vertices[i][j] = nodes[i] + normal;
730
731 uv[i][j].x = textureuv_ufrac[i];
732 uv[i][j].y = float(j) / float(radial_subdivisions);
733 }
734 }
735
736 std::vector<uint> UUIDs;
737 UUIDs.reserve(2 * (node_count - 1) * radial_subdivisions); // Reserve expected capacity
738 vec3 v0, v1, v2;
739 vec2 uv0, uv1, uv2;
740 for (int j = 0; j < radial_subdivisions; j++) {
741 for (int i = 0; i < node_count - 1; i++) {
742 v0 = triangle_vertices[i][j];
743 v1 = triangle_vertices[i + 1][j + 1];
744 v2 = triangle_vertices[i][j + 1];
745
746 uv0 = uv[i][j];
747 uv1 = uv[i + 1][j + 1];
748 uv2 = uv[i][j + 1];
749
750 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
751 if (getPrimitiveArea(triangle_uuid) > 0) {
752 UUIDs.push_back(triangle_uuid);
753 } else {
754 deletePrimitive(triangle_uuid);
755 }
756
757 v0 = triangle_vertices[i][j];
758 v1 = triangle_vertices[i + 1][j];
759 v2 = triangle_vertices[i + 1][j + 1];
760
761 uv0 = uv[i][j];
762 uv1 = uv[i + 1][j];
763 uv2 = uv[i + 1][j + 1];
764
765 uint triangle_uuid2 = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
766 if (getPrimitiveArea(triangle_uuid2) > 0) {
767 UUIDs.push_back(triangle_uuid2);
768 } else {
769 deletePrimitive(triangle_uuid2);
770 }
771 }
772 }
773
774 std::vector<RGBcolor> colors(nodes.size());
775
776 auto *tube_new = (new Tube(currentObjectID, UUIDs, nodes, radius, colors, triangle_vertices, radial_subdivisions, texturefile, this));
777
778 for (uint p: UUIDs) {
779 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
780 }
781
782 objects[currentObjectID] = tube_new;
783 currentObjectID++;
784
785 uint objID = currentObjectID - 1;
786 tube_new->object_origin = getObjectCenter(objID);
787
788 return objID;
789}
790
791uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv) {
792 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
793
794 return addBoxObject(center, size, subdiv, color, false);
795}
796
797uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color) {
798 return addBoxObject(center, size, subdiv, color, false);
799}
800
801uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const char *texturefile) {
802 return addBoxObject(center, size, subdiv, texturefile, false);
803}
804
805uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color, bool reverse_normals) {
806 if (size.x <= 0 || size.y <= 0 || size.z <= 0) {
807 helios_runtime_error("ERROR (Context::addBoxObject): Size of box must be positive.");
808 } else if (subdiv.x < 1 || subdiv.y < 1 || subdiv.z < 1) {
809 helios_runtime_error("ERROR (Context::addBoxObject): Number of box subdivisions must be positive.");
810 }
811
812 std::vector<uint> UUID;
813 UUID.reserve(2 * (subdiv.z * (subdiv.x + subdiv.y) + subdiv.x * subdiv.y));
814
815 vec3 subsize;
816 subsize.x = size.x / float(subdiv.x);
817 subsize.y = size.y / float(subdiv.y);
818 subsize.z = size.z / float(subdiv.z);
819
820 vec3 subcenter;
821 std::vector<uint> U, U_copy;
822
823 if (reverse_normals) { // normals point inward
824
825 // x-z faces (vertical)
826
827 // right
828 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
829 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
830 UUID.insert(UUID.end(), U.begin(), U.end());
831
832 // left
833 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
834 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, 0), make_int2(subdiv.x, subdiv.z), color);
835 UUID.insert(UUID.end(), U.begin(), U.end());
836
837 // y-z faces (vertical)
838
839 // front
840 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
841 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5f * PI_F, 1.5f * PI_F), make_int2(subdiv.y, subdiv.z), color);
842 UUID.insert(UUID.end(), U.begin(), U.end());
843
844 // back
845 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
846 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5f * PI_F, 0.5f * PI_F), make_int2(subdiv.y, subdiv.z), color);
847 UUID.insert(UUID.end(), U.begin(), U.end());
848
849 // x-y faces (horizontal)
850
851 // top
852 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
853 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
854 UUID.insert(UUID.end(), U.begin(), U.end());
855
856 // bottom
857 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
858 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), color);
859 UUID.insert(UUID.end(), U.begin(), U.end());
860 } else { // normals point outward
861
862 // x-z faces (vertical)
863
864 // right
865 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
866 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, 0), make_int2(subdiv.x, subdiv.z), color);
867 UUID.insert(UUID.end(), U.begin(), U.end());
868
869 // left
870 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
871 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
872 UUID.insert(UUID.end(), U.begin(), U.end());
873
874 // y-z faces (vertical)
875
876 // front
877 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
878 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5f * PI_F, 0.5f * PI_F), make_int2(subdiv.y, subdiv.z), color);
879 UUID.insert(UUID.end(), U.begin(), U.end());
880
881 // back
882 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
883 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5f * PI_F, 1.5f * PI_F), make_int2(subdiv.y, subdiv.z), color);
884 UUID.insert(UUID.end(), U.begin(), U.end());
885
886 // x-y faces (horizontal)
887
888 // top
889 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
890 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), color);
891 UUID.insert(UUID.end(), U.begin(), U.end());
892
893 // bottom
894 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
895 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
896 UUID.insert(UUID.end(), U.begin(), U.end());
897 }
898
899 auto *box_new = (new Box(currentObjectID, UUID, subdiv, "", this));
900
901 float T[16], transform[16];
902 box_new->getTransformationMatrix(transform);
903
904 makeScaleMatrix(size, T);
905 matmult(T, transform, transform);
906
907 makeTranslationMatrix(center, T);
908 matmult(T, transform, transform);
909 box_new->setTransformationMatrix(transform);
910
911 box_new->setColor(color);
912
913 for (uint p: UUID) {
914 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
915 }
916
917 box_new->object_origin = center;
918
919 objects[currentObjectID] = box_new;
920 currentObjectID++;
921 return currentObjectID - 1;
922}
923
924uint Context::addBoxObject(vec3 center, const vec3 &size, const int3 &subdiv, const char *texturefile, bool reverse_normals) {
925 if (!validateTextureFileExtenstion(texturefile)) {
926 helios_runtime_error("ERROR (Context::addBoxObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
927 } else if (!doesTextureFileExist(texturefile)) {
928 helios_runtime_error("ERROR (Context::addBoxObject): Texture file " + std::string(texturefile) + " does not exist.");
929 }
930
931 std::vector<uint> UUID;
932
933 vec3 subsize;
934 subsize.x = size.x / float(subdiv.x);
935 subsize.y = size.y / float(subdiv.y);
936 subsize.z = size.z / float(subdiv.z);
937
938 vec3 subcenter;
939 std::vector<uint> U, U_copy;
940
941 if (reverse_normals) { // normals point inward
942
943 // x-z faces (vertical)
944
945 // right
946 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
947 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
948 UUID.insert(UUID.end(), U.begin(), U.end());
949
950 // left
951 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
952 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), texturefile);
953 UUID.insert(UUID.end(), U.begin(), U.end());
954
955 // y-z faces (vertical)
956
957 // front
958 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
959 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5 * PI_F, 1.5 * PI_F), make_int2(subdiv.y, subdiv.z), texturefile);
960 UUID.insert(UUID.end(), U.begin(), U.end());
961
962 // back
963 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
964 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5 * PI_F, 0.5 * PI_F), make_int2(subdiv.y, subdiv.z), texturefile);
965 UUID.insert(UUID.end(), U.begin(), U.end());
966
967 // x-y faces (horizontal)
968
969 // top
970 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
971 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
972 UUID.insert(UUID.end(), U.begin(), U.end());
973
974 // bottom
975 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
976 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), texturefile);
977 UUID.insert(UUID.end(), U.begin(), U.end());
978 } else { // normals point outward
979
980 // x-z faces (vertical)
981
982 // right
983 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
984 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), texturefile);
985 UUID.insert(UUID.end(), U.begin(), U.end());
986
987 // left
988 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
989 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
990 UUID.insert(UUID.end(), U.begin(), U.end());
991
992 // y-z faces (vertical)
993
994 // front
995 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
996 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5 * PI_F, 0.5 * PI_F), make_int2(subdiv.y, subdiv.z), texturefile);
997 UUID.insert(UUID.end(), U.begin(), U.end());
998
999 // back
1000 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
1001 U = addTile(subcenter, make_vec2(size.y, size.z), make_SphericalCoord(0.5 * PI_F, 1.5 * PI_F), make_int2(subdiv.y, subdiv.z), texturefile);
1002 UUID.insert(UUID.end(), U.begin(), U.end());
1003
1004 // x-y faces (horizontal)
1005
1006 // top
1007 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
1008 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), texturefile);
1009 UUID.insert(UUID.end(), U.begin(), U.end());
1010
1011 // bottom
1012 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
1013 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
1014 UUID.insert(UUID.end(), U.begin(), U.end());
1015 }
1016
1017 auto *box_new = (new Box(currentObjectID, UUID, subdiv, texturefile, this));
1018
1019 float T[16], transform[16];
1020 box_new->getTransformationMatrix(transform);
1021
1022 makeScaleMatrix(size, T);
1023 matmult(T, transform, transform);
1024
1025 makeTranslationMatrix(center, T);
1026 matmult(T, transform, transform);
1027 box_new->setTransformationMatrix(transform);
1028
1029 for (uint p: UUID) {
1030 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1031 }
1032
1033 box_new->object_origin = center;
1034
1035 objects[currentObjectID] = box_new;
1036 currentObjectID++;
1037 return currentObjectID - 1;
1038}
1039
1040uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size) {
1041 return addDiskObject(make_int2(Ndivs, 1), center, size, make_SphericalCoord(0, 0), make_RGBAcolor(1, 0, 0, 1));
1042}
1043
1044uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation) {
1045 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(1, 0, 0, 1));
1046}
1047
1048uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
1049 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(color, 1));
1050}
1051
1052uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
1053 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, color);
1054}
1055
1056uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texture_file) {
1057 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, texture_file);
1058}
1059
1060uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
1061 return addDiskObject(Ndivs, center, size, rotation, make_RGBAcolor(color, 1));
1062}
1063
1064uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
1065 std::vector<uint> UUID(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2);
1066
1067 int i = 0;
1068 for (int r = 0; r < Ndivs.y; r++) {
1069 for (int t = 0; t < Ndivs.x; t++) {
1070 float dtheta = 2.f * PI_F / float(Ndivs.x);
1071 float theta = dtheta * float(t);
1072 float theta_plus = dtheta * float(t + 1);
1073
1074 float rx = size.x / float(Ndivs.y) * float(r);
1075 float ry = size.y / float(Ndivs.y) * float(r);
1076
1077 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
1078 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
1079
1080 if (r == 0) {
1081 UUID.at(i) = addTriangle(make_vec3(0, 0, 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), make_vec3(rx_plus * cosf(theta_plus), ry_plus * sinf(theta_plus), 0), color);
1082 } else {
1083 UUID.at(i) = addTriangle(make_vec3(rx * cosf(theta_plus), ry * sinf(theta_plus), 0), make_vec3(rx * cosf(theta), ry * sinf(theta), 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), color);
1084 i++;
1085 UUID.at(i) = addTriangle(make_vec3(rx * cosf(theta_plus), ry * sinf(theta_plus), 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), make_vec3(rx_plus * cosf(theta_plus), ry_plus * sinf(theta_plus), 0), color);
1086 }
1087 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.elevation, "y");
1088 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.azimuth, "z");
1089 getPrimitivePointer_private(UUID.at(i))->translate(center);
1090
1091 i++;
1092 }
1093 }
1094
1095 auto *disk_new = (new Disk(currentObjectID, UUID, Ndivs, "", this));
1096
1097 float T[16], transform[16];
1098 disk_new->getTransformationMatrix(transform);
1099
1100 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), T);
1101 matmult(T, transform, transform);
1102
1103 makeTranslationMatrix(center, T);
1104 matmult(T, transform, transform);
1105 disk_new->setTransformationMatrix(transform);
1106
1107 disk_new->setColor(color);
1108
1109 for (uint p: UUID) {
1110 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1111 }
1112
1113 disk_new->object_origin = center;
1114
1115 objects[currentObjectID] = disk_new;
1116 currentObjectID++;
1117 return currentObjectID - 1;
1118}
1119
1120uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texturefile) {
1121 if (!validateTextureFileExtenstion(texturefile)) {
1122 helios_runtime_error("ERROR (Context::addDiskObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1123 } else if (!doesTextureFileExist(texturefile)) {
1124 helios_runtime_error("ERROR (Context::addDiskObject): Texture file " + std::string(texturefile) + " does not exist.");
1125 }
1126
1127 std::vector<uint> UUID;
1128 UUID.reserve(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2); // Reserve expected capacity
1129 for (int r = 0; r < Ndivs.y; r++) {
1130 for (int t = 0; t < Ndivs.x; t++) {
1131 float dtheta = 2.f * PI_F / float(Ndivs.x);
1132 float theta = dtheta * float(t);
1133 float theta_plus = dtheta * float(t + 1);
1134
1135 float rx = size.x / float(Ndivs.y) * float(r);
1136 float ry = size.y / float(Ndivs.y) * float(r);
1137 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
1138 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
1139
1140 if (r == 0) {
1141 uint triangle_uuid = addTriangle(make_vec3(0, 0, 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), make_vec3(rx_plus * cosf(theta_plus), ry_plus * sinf(theta_plus), 0), texturefile, make_vec2(0.5, 0.5),
1142 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)),
1143 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
1144 if (getPrimitiveArea(triangle_uuid) > 0) {
1145 UUID.push_back(triangle_uuid);
1146 } else {
1147 deletePrimitive(triangle_uuid);
1148 continue;
1149 }
1150 } else {
1151 uint triangle_uuid1 = addTriangle(make_vec3(rx * cosf(theta_plus), ry * sinf(theta_plus), 0), make_vec3(rx * cosf(theta), ry * sinf(theta), 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), texturefile,
1152 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx / size.x), 0.5f * (1.f + sinf(theta_plus) * ry / size.y)), make_vec2(0.5f * (1.f + cosf(theta) * rx / size.x), 0.5f * (1.f + sinf(theta) * ry / size.y)),
1153 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)));
1154 if (getPrimitiveArea(triangle_uuid1) > 0) {
1155 UUID.push_back(triangle_uuid1);
1156 } else {
1157 deletePrimitive(triangle_uuid1);
1158 }
1159
1160 uint triangle_uuid2 =
1161 addTriangle(make_vec3(rx * cosf(theta_plus), ry * sinf(theta_plus), 0), make_vec3(rx_plus * cosf(theta), ry_plus * sinf(theta), 0), make_vec3(rx_plus * cosf(theta_plus), ry_plus * sinf(theta_plus), 0), texturefile,
1162 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx / size.x), 0.5f * (1.f + sinf(theta_plus) * ry / size.y)), make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)),
1163 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
1164 if (getPrimitiveArea(triangle_uuid2) > 0) {
1165 UUID.push_back(triangle_uuid2);
1166 } else {
1167 deletePrimitive(triangle_uuid2);
1168 continue;
1169 }
1170 }
1171 // Apply transformations to all valid triangles added in this iteration
1172 size_t start_idx = UUID.size() - (r == 0 ? 1 : 2);
1173 for (size_t uuid_idx = start_idx; uuid_idx < UUID.size(); uuid_idx++) {
1174 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.elevation, "y");
1175 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.azimuth, "z");
1176 getPrimitivePointer_private(UUID.at(uuid_idx))->translate(center);
1177 }
1178 }
1179 }
1180
1181 auto *disk_new = (new Disk(currentObjectID, UUID, Ndivs, texturefile, this));
1182
1183 float T[16], transform[16];
1184 disk_new->getTransformationMatrix(transform);
1185
1186 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), T);
1187 matmult(T, transform, transform);
1188
1189 makeTranslationMatrix(center, T);
1190 matmult(T, transform, transform);
1191 disk_new->setTransformationMatrix(transform);
1192
1193 for (uint p: UUID) {
1194 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1195 }
1196
1197 disk_new->object_origin = center;
1198
1199 objects[currentObjectID] = disk_new;
1200 currentObjectID++;
1201 return currentObjectID - 1;
1202}
1203
1204uint Context::addPolymeshObject(const std::vector<uint> &UUIDs) {
1205 if (UUIDs.empty()) {
1206 helios_runtime_error("ERROR (Context::addPolymeshObject): UUIDs array is empty. Cannot create polymesh object.");
1207 } else if (!doesPrimitiveExist(UUIDs)) {
1208 helios_runtime_error("ERROR (Context::addPolymeshObject): One or more of the provided UUIDs does not exist. Cannot create polymesh object.");
1209 }
1210
1211 // Check whether primitives already belong to another object
1212 std::vector<uint> UUIDs_polymesh;
1213 UUIDs_polymesh.reserve(UUIDs.size());
1214 size_t skipped_UUIDs = 0;
1215 for (uint UUID: UUIDs) {
1216 if (getPrimitivePointer_private(UUID)->getParentObjectID() != 0) {
1217 skipped_UUIDs++;
1218 } else {
1219 UUIDs_polymesh.push_back(UUID);
1220 }
1221 }
1222 if (skipped_UUIDs > 0) {
1223 std::cerr << "WARNING (Context::addPolymeshObject): " << skipped_UUIDs << " primitives were not added to polymesh object because they already belong to another object." << std::endl;
1224 }
1225
1226 auto *polymesh_new = (new Polymesh(currentObjectID, UUIDs_polymesh, "", this));
1227
1228 float T[16], transform[16];
1229 polymesh_new->getTransformationMatrix(transform);
1230
1231 makeTranslationMatrix(getPrimitivePointer_private(UUIDs_polymesh.front())->getVertices().front(), T);
1232 matmult(T, transform, transform);
1233 polymesh_new->setTransformationMatrix(transform);
1234
1235 for (uint UUID: UUIDs_polymesh) {
1236 getPrimitivePointer_private(UUID)->setParentObjectID(currentObjectID);
1237 }
1238
1239 objects[currentObjectID] = polymesh_new;
1240 currentObjectID++;
1241
1242 uint objID = currentObjectID - 1;
1243 polymesh_new->object_origin = getObjectCenter(objID);
1244
1245 return objID;
1246}
1247
1248uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1) {
1249 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
1250 return addConeObject(Ndivs, node0, node1, radius0, radius1, color);
1251}
1252
1253uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const RGBcolor &color) {
1254 const std::vector nodes{node0, node1};
1255 const std::vector radii{radius0, radius1};
1256
1257 vec3 convec;
1258 std::vector<float> cfact(Ndivs + 1);
1259 std::vector<float> sfact(Ndivs + 1);
1260 std::vector<std::vector<vec3>> xyz(Ndivs + 1);
1261 std::vector<std::vector<vec3>> normal(Ndivs + 1);
1262
1263 for (uint j = 0; j < Ndivs + 1; j++) {
1264 xyz.at(j).resize(2);
1265 normal.at(j).resize(2);
1266 }
1267 vec3 nvec(0.1817f, 0.6198f, 0.7634f); // random vector to get things going
1268
1269 for (int j = 0; j < Ndivs + 1; j++) {
1270 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
1271 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
1272 }
1273
1274 for (int i = 0; i < 2; i++) {
1275 vec3 vec;
1276 // looping over cone segments
1277
1278 if (i == 0) {
1279 vec.x = nodes[i + 1].x - nodes[i].x;
1280 vec.y = nodes[i + 1].y - nodes[i].y;
1281 vec.z = nodes[i + 1].z - nodes[i].z;
1282 } else if (i == 1) {
1283 vec.x = nodes[i].x - nodes[i - 1].x;
1284 vec.y = nodes[i].y - nodes[i - 1].y;
1285 vec.z = nodes[i].z - nodes[i - 1].z;
1286 }
1287
1288 if (vec.magnitude() < 1e-6f) {
1289 vec = make_vec3(0, 0, 1);
1290 }
1291 float norm;
1292 convec = cross(nvec, vec);
1293 norm = convec.magnitude();
1294 if (norm < 1e-6f) {
1295 convec = cross(vec, fabs(vec.x) < 0.9f ? make_vec3(1, 0, 0) : make_vec3(0, 1, 0));
1296 norm = std::max(convec.magnitude(), 1e-6f);
1297 }
1298 convec = convec / norm;
1299 nvec = cross(vec, convec);
1300 norm = nvec.magnitude();
1301 if (norm < 1e-6f) {
1302 nvec = cross(convec, vec);
1303 norm = std::max(nvec.magnitude(), 1e-6f);
1304 }
1305 nvec = nvec / norm;
1306
1307 for (int j = 0; j < Ndivs + 1; j++) {
1308 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
1309 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
1310 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
1311
1312 xyz[j][i].x = nodes[i].x + normal[j][i].x;
1313 xyz[j][i].y = nodes[i].y + normal[j][i].y;
1314 xyz[j][i].z = nodes[i].z + normal[j][i].z;
1315
1316 normal[j][i] = normal[j][i] / radii[i];
1317 }
1318 }
1319
1320 vec3 v0, v1, v2;
1321 std::vector<uint> UUID(2 * Ndivs);
1322
1323 int i = 0;
1324 for (int j = 0; j < Ndivs; j++) {
1325 v0 = xyz[j][0];
1326 v1 = xyz[j + 1][1];
1327 v2 = xyz[j + 1][0];
1328
1329 UUID.at(i) = addTriangle(v0, v1, v2, color);
1330
1331 v0 = xyz[j][0];
1332 v1 = xyz[j][1];
1333 v2 = xyz[j + 1][1];
1334
1335 UUID.at(i + 1) = addTriangle(v0, v1, v2, color);
1336
1337 i += 2;
1338 }
1339
1340 auto *cone_new = (new Cone(currentObjectID, UUID, node0, node1, radius0, radius1, Ndivs, "", this));
1341
1342 for (uint p: UUID) {
1343 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1344 }
1345
1346 cone_new->setColor(color);
1347
1348 objects[currentObjectID] = cone_new;
1349 currentObjectID++;
1350
1351 uint objID = currentObjectID - 1;
1352 cone_new->object_origin = getObjectCenter(objID);
1353
1354 return objID;
1355}
1356
1357uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const char *texturefile) {
1358 if (!validateTextureFileExtenstion(texturefile)) {
1359 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1360 } else if (!doesTextureFileExist(texturefile)) {
1361 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " does not exist.");
1362 }
1363
1364 const std::vector<helios::vec3> nodes{node0, node1};
1365 const std::vector<float> radii{radius0, radius1};
1366
1367 vec3 convec;
1368 std::vector<float> cfact(Ndivs + 1);
1369 std::vector<float> sfact(Ndivs + 1);
1370 std::vector<std::vector<vec3>> xyz, normal;
1371 std::vector<std::vector<vec2>> uv;
1372 xyz.resize(Ndivs + 1);
1373 normal.resize(Ndivs + 1);
1374 uv.resize(Ndivs + 1);
1375 for (uint j = 0; j < Ndivs + 1; j++) {
1376 xyz.at(j).resize(2);
1377 normal.at(j).resize(2);
1378 uv.at(j).resize(2);
1379 }
1380 vec3 nvec(0.f, 1.f, 0.f);
1381
1382 for (int j = 0; j < Ndivs + 1; j++) {
1383 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
1384 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
1385 }
1386
1387 for (int i = 0; i < 2; i++) {
1388 vec3 vec;
1389 // looping over cone segments
1390
1391 if (i == 0) {
1392 vec.x = nodes[i + 1].x - nodes[i].x;
1393 vec.y = nodes[i + 1].y - nodes[i].y;
1394 vec.z = nodes[i + 1].z - nodes[i].z;
1395 } else if (i == 1) {
1396 vec.x = nodes[i].x - nodes[i - 1].x;
1397 vec.y = nodes[i].y - nodes[i - 1].y;
1398 vec.z = nodes[i].z - nodes[i - 1].z;
1399 }
1400
1401 if (vec.magnitude() < 1e-6f) {
1402 vec = make_vec3(0, 0, 1);
1403 }
1404 float norm;
1405 convec = cross(nvec, vec);
1406 norm = convec.magnitude();
1407 if (norm < 1e-6f) {
1408 convec = cross(vec, fabs(vec.x) < 0.9f ? make_vec3(1, 0, 0) : make_vec3(0, 1, 0));
1409 norm = std::max(convec.magnitude(), 1e-6f);
1410 }
1411 convec = convec / norm;
1412 nvec = cross(vec, convec);
1413 norm = nvec.magnitude();
1414 if (norm < 1e-6f) {
1415 nvec = cross(convec, vec);
1416 norm = std::max(nvec.magnitude(), 1e-6f);
1417 }
1418 nvec = nvec / norm;
1419
1420 for (int j = 0; j < Ndivs + 1; j++) {
1421 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
1422 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
1423 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
1424
1425 xyz[j][i].x = nodes[i].x + normal[j][i].x;
1426 xyz[j][i].y = nodes[i].y + normal[j][i].y;
1427 xyz[j][i].z = nodes[i].z + normal[j][i].z;
1428
1429 uv[j][i].x = float(i) / float(2 - 1);
1430 uv[j][i].y = float(j) / float(Ndivs);
1431
1432 normal[j][i] = normal[j][i] / radii[i];
1433 }
1434 }
1435
1436 vec3 v0, v1, v2;
1437 vec2 uv0, uv1, uv2;
1438 std::vector<uint> UUID;
1439
1440 for (int i = 0; i < 2 - 1; i++) {
1441 for (int j = 0; j < Ndivs; j++) {
1442 v0 = xyz[j][i];
1443 v1 = xyz[j + 1][i + 1];
1444 v2 = xyz[j + 1][i];
1445
1446 uv0 = uv[j][i];
1447 uv1 = uv[j + 1][i + 1];
1448 uv2 = uv[j + 1][i];
1449
1450 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1451 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1452 if (getPrimitiveArea(triangle_uuid) > 0) {
1453 UUID.push_back(triangle_uuid);
1454 } else {
1455 deletePrimitive(triangle_uuid);
1456 }
1457 }
1458
1459 v0 = xyz[j][i];
1460 v1 = xyz[j][i + 1];
1461 v2 = xyz[j + 1][i + 1];
1462
1463 uv0 = uv[j][i];
1464 uv1 = uv[j][i + 1];
1465 uv2 = uv[j + 1][i + 1];
1466
1467 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1468 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1469 if (getPrimitiveArea(triangle_uuid) > 0) {
1470 UUID.push_back(triangle_uuid);
1471 } else {
1472 deletePrimitive(triangle_uuid);
1473 }
1474 }
1475 }
1476 }
1477
1478 auto *cone_new = (new Cone(currentObjectID, UUID, node0, node1, radius0, radius1, Ndivs, texturefile, this));
1479
1480 for (uint p: UUID) {
1481 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1482 }
1483
1484 objects[currentObjectID] = cone_new;
1485 currentObjectID++;
1486
1487 uint objID = currentObjectID - 1;
1488 cone_new->object_origin = getObjectCenter(objID);
1489
1490 return objID;
1491}
1492
1493// ============== COMPOUND OBJECT CLASS METHOD DEFINITIONS ==============
1494
1496
1498 return OID;
1499}
1500
1502 return type;
1503}
1504
1506 return UUIDs.size();
1507}
1508
1509
1510std::vector<uint> CompoundObject::getPrimitiveUUIDs() const {
1511 return UUIDs;
1512}
1513
1515 return find(UUIDs.begin(), UUIDs.end(), UUID) != UUIDs.end();
1516}
1517
1519 vec2 xbounds, ybounds, zbounds;
1520
1521 const std::vector<uint> &U = getPrimitiveUUIDs();
1522
1523 context->getDomainBoundingBox(U, xbounds, ybounds, zbounds);
1524
1525 vec3 origin;
1526
1527 origin.x = 0.5f * (xbounds.x + xbounds.y);
1528 origin.y = 0.5f * (ybounds.x + ybounds.y);
1529 origin.z = 0.5f * (zbounds.x + zbounds.y);
1530
1531 return origin;
1532}
1533
1535 float area = 0.f;
1536
1537 for (uint UUID: UUIDs) {
1538 if (context->doesPrimitiveExist(UUID)) {
1539 area += context->getPrimitiveArea(UUID);
1540 }
1541 }
1542
1543 return area;
1544}
1545
1547 for (uint UUID: UUIDs) {
1548 if (context->doesPrimitiveExist(UUID)) {
1549 context->setPrimitiveColor(UUID, a_color);
1550 }
1551 }
1552}
1553
1555 for (uint UUID: UUIDs) {
1556 if (context->doesPrimitiveExist(UUID)) {
1557 context->setPrimitiveColor(UUID, a_color);
1558 }
1559 }
1560}
1561
1563 for (uint UUID: UUIDs) {
1564 if (context->doesPrimitiveExist(UUID)) {
1565 context->overridePrimitiveTextureColor(UUID);
1566 }
1567 }
1568}
1569
1571 for (uint UUID: UUIDs) {
1572 if (context->doesPrimitiveExist(UUID)) {
1573 context->usePrimitiveTextureColor(UUID);
1574 }
1575 }
1576}
1577
1579 if (getTextureFile().empty()) {
1580 return false;
1581 } else {
1582 return true;
1583 }
1584}
1585
1587 return texturefile;
1588}
1589
1591 if (shift == nullorigin) {
1592 return;
1593 }
1594
1595 float T[16], T_prim[16];
1596 makeTranslationMatrix(shift, T);
1597
1598 matmult(T, transform, transform);
1599
1600 for (uint UUID: UUIDs) {
1601 if (context->doesPrimitiveExist(UUID)) {
1602 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1603 matmult(T, T_prim, T_prim);
1604 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1605 }
1606 }
1607}
1608
1609void CompoundObject::rotate(float rotation_radians, const char *rotation_axis_xyz_string) {
1610 if (rotation_radians == 0) {
1611 return;
1612 }
1613
1614 if (strcmp(rotation_axis_xyz_string, "z") == 0) {
1615 float Rz[16], Rz_prim[16];
1616 makeRotationMatrix(-rotation_radians, "z", Rz);
1617 matmult(Rz, transform, transform);
1618
1619 for (uint UUID: UUIDs) {
1620 if (context->doesPrimitiveExist(UUID)) {
1621 context->getPrimitiveTransformationMatrix(UUID, Rz_prim);
1622 matmult(Rz, Rz_prim, Rz_prim);
1623 context->setPrimitiveTransformationMatrix(UUID, Rz_prim);
1624 }
1625 }
1626 } else if (strcmp(rotation_axis_xyz_string, "y") == 0) {
1627 float Ry[16], Ry_prim[16];
1628 makeRotationMatrix(rotation_radians, "y", Ry);
1629 matmult(Ry, transform, transform);
1630 for (uint UUID: UUIDs) {
1631 if (context->doesPrimitiveExist(UUID)) {
1632 context->getPrimitiveTransformationMatrix(UUID, Ry_prim);
1633 matmult(Ry, Ry_prim, Ry_prim);
1634 context->setPrimitiveTransformationMatrix(UUID, Ry_prim);
1635 }
1636 }
1637 } else if (strcmp(rotation_axis_xyz_string, "x") == 0) {
1638 float Rx[16], Rx_prim[16];
1639 makeRotationMatrix(rotation_radians, "x", Rx);
1640 matmult(Rx, transform, transform);
1641 for (uint UUID: UUIDs) {
1642 if (context->doesPrimitiveExist(UUID)) {
1643 context->getPrimitiveTransformationMatrix(UUID, Rx_prim);
1644 matmult(Rx, Rx_prim, Rx_prim);
1645 context->setPrimitiveTransformationMatrix(UUID, Rx_prim);
1646 }
1647 }
1648 } else {
1649 helios_runtime_error("ERROR (CompoundObject::rotate): Rotation axis should be one of x, y, or z.");
1650 }
1651}
1652
1653void CompoundObject::rotate(float rotation_radians, const helios::vec3 &rotation_axis_vector) {
1654 if (rotation_radians == 0) {
1655 return;
1656 }
1657
1658 float R[16], R_prim[16];
1659 makeRotationMatrix(rotation_radians, rotation_axis_vector, R);
1660 matmult(R, transform, transform);
1661
1662 for (uint UUID: UUIDs) {
1663 if (context->doesPrimitiveExist(UUID)) {
1664 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1665 matmult(R, R_prim, R_prim);
1666 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1667 }
1668 }
1669}
1670
1671void CompoundObject::rotate(float rotation_radians, const helios::vec3 &origin, const helios::vec3 &rotation_axis_vector) {
1672 if (rotation_radians == 0) {
1673 return;
1674 }
1675
1676 float R[16], R_prim[16];
1677 makeRotationMatrix(rotation_radians, origin, rotation_axis_vector, R);
1678 matmult(R, transform, transform);
1679
1680 for (uint UUID: UUIDs) {
1681 if (context->doesPrimitiveExist(UUID)) {
1682 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1683 matmult(R, R_prim, R_prim);
1684 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1685 }
1686 }
1687}
1688
1692
1696
1698 if (scale.x == 1.f && scale.y == 1.f && scale.z == 1.f) {
1699 return;
1700 }
1701
1702 float T[16], T_prim[16];
1703 makeScaleMatrix(scale, point, T);
1704 matmult(T, transform, transform);
1705
1706 for (uint UUID: UUIDs) {
1707 if (context->doesPrimitiveExist(UUID)) {
1708 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1709 matmult(T, T_prim, T_prim);
1710 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1711 }
1712 }
1713}
1714
1716 for (int i = 0; i < 16; i++) {
1717 T[i] = transform[i];
1718 }
1719}
1720
1722 for (int i = 0; i < 16; i++) {
1723 transform[i] = T[i];
1724 }
1725}
1726
1727void CompoundObject::setPrimitiveUUIDs(const std::vector<uint> &a_UUIDs) {
1728 UUIDs = a_UUIDs;
1729}
1730
1732 auto it = find(UUIDs.begin(), UUIDs.end(), UUID);
1733 if (it != UUIDs.end()) {
1734 std::iter_swap(it, UUIDs.end() - 1);
1735 UUIDs.pop_back();
1736 primitivesarecomplete = false;
1737 }
1738}
1739
1740void CompoundObject::deleteChildPrimitive(const std::vector<uint> &a_UUIDs) {
1741 for (uint UUID: a_UUIDs) {
1743 }
1744}
1745
1747 return primitivesarecomplete;
1748}
1749
1750// ============== TILE CLASS METHOD DEFINITIONS ==============
1751
1752Tile::Tile(uint a_OID, const std::vector<uint> &a_UUIDs, const int2 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1753 makeIdentityMatrix(transform);
1754
1755 OID = a_OID;
1757 UUIDs = a_UUIDs;
1758 subdiv = a_subdiv;
1759 texturefile = a_texturefile;
1760 context = a_context;
1761}
1762
1764 const std::vector<vec3> &vertices = getVertices();
1765 float l = (vertices.at(1) - vertices.at(0)).magnitude();
1766 float w = (vertices.at(3) - vertices.at(0)).magnitude();
1767 return make_vec2(l, w);
1768}
1769
1771 vec3 center;
1772 vec3 Y;
1773 Y.x = 0.f;
1774 Y.y = 0.f;
1775 Y.z = 0.f;
1776
1777 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1778 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1779 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1780
1781 return center;
1782}
1783
1784
1786 return subdiv;
1787}
1788
1790 subdiv = a_subdiv;
1791}
1792
1793
1794std::vector<helios::vec3> Tile::getVertices() const {
1795 std::vector<helios::vec3> vertices;
1796 vertices.resize(4);
1797
1798 // subcenter = make_vec3(-0.5*size.x+(float(i)+0.5)*subsize.x,-0.5*size.y+(float(j)+0.5)*subsize.y,0);
1799 // Y[0] = make_vec3( -0.5f, -0.5f, 0.f);
1800 // Y[1] = make_vec3( 0.5f, -0.5f, 0.f);
1801 // Y[2] = make_vec3( 0.5f, 0.5f, 0.f);
1802 // Y[3] = make_vec3( -0.5f, 0.5f, 0.f);
1803
1804
1805 vec3 Y[4];
1806 Y[0] = make_vec3(-0.5f, -0.5f, 0.f);
1807 Y[1] = make_vec3(0.5f, -0.5f, 0.f);
1808 Y[2] = make_vec3(0.5f, 0.5f, 0.f);
1809 Y[3] = make_vec3(-0.5f, 0.5f, 0.f);
1810
1811 for (int i = 0; i < 4; i++) {
1812 vertices[i].x = transform[0] * Y[i].x + transform[1] * Y[i].y + transform[2] * Y[i].z + transform[3];
1813 vertices[i].y = transform[4] * Y[i].x + transform[5] * Y[i].y + transform[6] * Y[i].z + transform[7];
1814 vertices[i].z = transform[8] * Y[i].x + transform[9] * Y[i].y + transform[10] * Y[i].z + transform[11];
1815 }
1816
1817 return vertices;
1818}
1819
1821 return context->getPrimitiveNormal(UUIDs.front());
1822}
1823
1824std::vector<helios::vec2> Tile::getTextureUV() const {
1825 return {make_vec2(0, 0), make_vec2(1, 0), make_vec2(1, 1), make_vec2(0, 1)};
1826}
1827
1828// ============== SPHERE CLASS METHOD DEFINITIONS ==============
1829
1830Sphere::Sphere(uint a_OID, const std::vector<uint> &a_UUIDs, uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1831 makeIdentityMatrix(transform);
1832
1833 OID = a_OID;
1835 UUIDs = a_UUIDs;
1836 subdiv = a_subdiv;
1837 texturefile = a_texturefile;
1838 context = a_context;
1839}
1840
1842 vec3 n0(0, 0, 0);
1843 vec3 nx(1, 0, 0);
1844 vec3 ny(0, 1, 0);
1845 vec3 nz(0, 0, 1);
1846 vec3 n0_T, nx_T, ny_T, nz_T;
1847
1848 vecmult(transform, n0, n0_T);
1849 vecmult(transform, nx, nx_T);
1850 vecmult(transform, ny, ny_T);
1851 vecmult(transform, nz, nz_T);
1852
1853 vec3 radii;
1854 radii.x = (nx_T - n0_T).magnitude();
1855 radii.y = (ny_T - n0_T).magnitude();
1856 radii.z = (nz_T - n0_T).magnitude();
1857
1858 return radii;
1859}
1860
1862 vec3 center;
1863 vec3 Y;
1864 Y.x = 0.f;
1865 Y.y = 0.f;
1866 Y.z = 0.f;
1867
1868 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1869 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1870 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1871
1872 return center;
1873}
1874
1876 return subdiv;
1877}
1878
1880 subdiv = a_subdiv;
1881}
1882
1883float Sphere::getVolume() const {
1884 const vec3 &radii = getRadius();
1885 return 4.f / 3.f * PI_F * radii.x * radii.y * radii.z;
1886}
1887
1888// ============== TUBE CLASS METHOD DEFINITIONS ==============
1889
1890Tube::Tube(uint a_OID, const std::vector<uint> &a_UUIDs, const std::vector<vec3> &a_nodes, const std::vector<float> &a_radius, const std::vector<helios::RGBcolor> &a_colors, const std::vector<std::vector<helios::vec3>> &a_triangle_vertices,
1891 uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1892 makeIdentityMatrix(transform);
1893
1894 OID = a_OID;
1896 UUIDs = a_UUIDs;
1897 nodes = a_nodes;
1898 radius = a_radius;
1899 colors = a_colors;
1900 triangle_vertices = a_triangle_vertices;
1901 subdiv = a_subdiv;
1902 texturefile = a_texturefile;
1903 context = a_context;
1904}
1905
1906std::vector<helios::vec3> Tube::getNodes() const {
1907 std::vector<vec3> nodes_T;
1908 nodes_T.resize(nodes.size());
1909
1910 for (uint i = 0; i < nodes.size(); i++) {
1911 nodes_T.at(i).x = transform[0] * nodes.at(i).x + transform[1] * nodes.at(i).y + transform[2] * nodes.at(i).z + transform[3];
1912 nodes_T.at(i).y = transform[4] * nodes.at(i).x + transform[5] * nodes.at(i).y + transform[6] * nodes.at(i).z + transform[7];
1913 nodes_T.at(i).z = transform[8] * nodes.at(i).x + transform[9] * nodes.at(i).y + transform[10] * nodes.at(i).z + transform[11];
1914 }
1915
1916 return nodes_T;
1917}
1918
1920 return scast<uint>(nodes.size());
1921}
1922
1923std::vector<float> Tube::getNodeRadii() const {
1924 std::vector<float> radius_T;
1925 radius_T.resize(radius.size());
1926 for (int i = 0; i < radius.size(); i++) {
1927 vec3 n0(0, 0, 0), nx(radius.at(i), 0, 0);
1928 vec3 n0_T, nx_T;
1929
1930 vecmult(transform, n0, n0_T);
1931 vecmult(transform, nx, nx_T);
1932
1933 radius_T.at(i) = (nx_T - n0_T).magnitude();
1934 }
1935 return radius_T;
1936}
1937
1938std::vector<helios::RGBcolor> Tube::getNodeColors() const {
1939 return colors;
1940}
1941
1942std::vector<std::vector<helios::vec3>> Tube::getTriangleVertices() const {
1943 return triangle_vertices;
1944}
1945
1947 return subdiv;
1948}
1949
1950float Tube::getLength() const {
1951 float length = 0.f;
1952 for (uint i = 0; i < nodes.size() - 1; i++) {
1953 length += (nodes.at(i + 1) - nodes.at(i)).magnitude();
1954 }
1955 return length;
1956}
1957
1958float Tube::getVolume() const {
1959 const std::vector<float> &radii = getNodeRadii();
1960 float volume = 0.f;
1961 for (uint i = 0; i < radii.size() - 1; i++) {
1962 float segment_length = (nodes.at(i + 1) - nodes.at(i)).magnitude();
1963 float r0 = radii.at(i);
1964 float r1 = radii.at(i + 1);
1965 volume += PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1966 }
1967
1968 return volume;
1969}
1970
1971float Tube::getSegmentVolume(uint segment_index) const {
1972 if (segment_index >= nodes.size() - 1) {
1973 helios_runtime_error("ERROR (Tube::getSegmentVolume): Segment index out of bounds.");
1974 }
1975
1976 float segment_length = (nodes.at(segment_index + 1) - nodes.at(segment_index)).magnitude();
1977 float r0 = radius.at(segment_index);
1978 float r1 = radius.at(segment_index + 1);
1979 float volume = PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1980
1981 return volume;
1982}
1983
1984void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const helios::RGBcolor &node_color) {
1985 //\todo This is a computationally inefficient method for appending the tube, but it ensures that there is no twisting of the tube relative to the previous tube segments.
1986
1987 if (node_radius < 0) {
1988 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
1989 }
1990 node_radius = std::max((float) 1e-5, node_radius);
1991
1992 uint radial_subdivisions = subdiv;
1993
1994 vec3 axial_vector;
1995 std::vector<float> cfact(radial_subdivisions + 1);
1996 std::vector<float> sfact(radial_subdivisions + 1);
1997
1998 for (int j = 0; j < radial_subdivisions + 1; j++) {
1999 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2000 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2001 }
2002
2003 triangle_vertices.resize(triangle_vertices.size() + 1);
2004 triangle_vertices.back().resize(radial_subdivisions + 1);
2005
2006 nodes.push_back(node_position);
2007 radius.push_back(node_radius);
2008 colors.push_back(node_color);
2009
2010 int node_count = nodes.size();
2011
2012 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2013 vec3 previous_axial_vector;
2014 vec3 previous_radial_dir;
2015
2016 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2017 if (radius.at(i) < 0) {
2018 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2019 }
2020
2021 if (i == 0) {
2022 axial_vector = nodes[i + 1] - nodes[i];
2023 float mag = axial_vector.magnitude();
2024 if (mag < 1e-6f) {
2025 axial_vector = make_vec3(0, 0, 1);
2026 } else {
2027 axial_vector = axial_vector / mag;
2028 }
2029 if (fabs(axial_vector * initial_radial) > 0.95f) {
2030 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2031 }
2032 // Also handle nearly vertical axes
2033 if (fabs(axial_vector.z) > 0.95f) {
2034 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2035 }
2036 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2037 } else {
2038 if (i == node_count - 1) {
2039 axial_vector = nodes[i] - nodes[i - 1];
2040 } else {
2041 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2042 }
2043 float mag = axial_vector.magnitude();
2044 if (mag < 1e-6f) {
2045 axial_vector = make_vec3(0, 0, 1);
2046 } else {
2047 axial_vector = axial_vector / mag;
2048 }
2049
2050 // Calculate radial direction using parallel transport
2051 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2052 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2053 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2054 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2055 } else {
2056 // Vectors are nearly parallel, use robust fallback
2057 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2058 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2059 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2060 }
2061 if (fabs(axial_vector.z) > 0.95f) {
2062 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2063 }
2064 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2065 }
2066 // else {
2067 // // Handle the case of nearly parallel vectors
2068 // // Ensure previous_radial_dir remains orthogonal to axial_vector
2069 // previous_radial_dir = cross(axial_vector, previous_radial_dir);
2070 // if (previous_radial_dir.magnitude() < 1e-6) {
2071 // // If still degenerate, choose another orthogonal direction
2072 // previous_radial_dir = cross(axial_vector, vec3(1.0f, 0.0f, 0.0f));
2073 // }
2074 // previous_radial_dir.normalize();
2075 // }
2076 }
2077
2078 previous_axial_vector = axial_vector;
2079
2080 vec3 radial_dir = previous_radial_dir;
2081 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2082 orthogonal_dir.normalize();
2083
2084 if (i < node_count - 2) {
2085 continue;
2086 }
2087
2088 for (int j = 0; j < radial_subdivisions + 1; j++) {
2089 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2090 triangle_vertices[i][j] = nodes[i] + normal;
2091 }
2092 }
2093
2094 // add triangles for new segment
2095
2096 int second_last = node_count - 2;
2097 int last = node_count - 1;
2098 for (int j = 0; j < radial_subdivisions; j++) {
2099 vec3 v0 = triangle_vertices.at(second_last).at(j);
2100 vec3 v1 = triangle_vertices.at(last).at(j + 1);
2101 vec3 v2 = triangle_vertices.at(second_last).at(j + 1);
2102
2103 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2104
2105 v0 = triangle_vertices.at(second_last).at(j);
2106 v1 = triangle_vertices.at(last).at(j);
2107 v2 = triangle_vertices.at(last).at(j + 1);
2108
2109 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2110 }
2111
2112 for (uint p: UUIDs) {
2113 context->setPrimitiveParentObjectID(p, this->OID);
2114 }
2115
2116 updateTriangleVertices();
2117}
2118
2119void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const char *texturefile, const helios::vec2 &textureuv_ufrac) {
2120 //\todo This is a computationally inefficient method for appending the tube, but it ensures that there is no twisting of the tube relative to the previous tube segments.
2121
2122 if (node_radius < 0) {
2123 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
2124 } else if (textureuv_ufrac.x < 0 || textureuv_ufrac.y < 0 || textureuv_ufrac.x > 1 || textureuv_ufrac.y > 1) {
2125 helios_runtime_error("ERROR (Tube::appendTubeSegment): Texture U fraction must be between 0 and 1.");
2126 }
2127 node_radius = std::max((float) 1e-5, node_radius);
2128
2129 uint radial_subdivisions = subdiv;
2130
2131 vec3 axial_vector;
2132 std::vector<float> cfact(radial_subdivisions + 1);
2133 std::vector<float> sfact(radial_subdivisions + 1);
2134
2135 for (int j = 0; j < radial_subdivisions + 1; j++) {
2136 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2137 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2138 }
2139
2140 triangle_vertices.resize(triangle_vertices.size() + 1);
2141 triangle_vertices.back().resize(radial_subdivisions + 1);
2142 std::vector<std::vector<vec2>> uv;
2143 resize_vector(uv, radial_subdivisions + 1, 2);
2144
2145 nodes.push_back(node_position);
2146 radius.push_back(node_radius);
2147 colors.push_back(RGB::black);
2148
2149 int node_count = nodes.size();
2150
2151 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2152 vec3 previous_axial_vector;
2153 vec3 previous_radial_dir;
2154
2155 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2156 if (radius.at(i) < 0) {
2157 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2158 }
2159
2160 if (i == 0) {
2161 axial_vector = nodes[i + 1] - nodes[i];
2162 float mag = axial_vector.magnitude();
2163 if (mag < 1e-6f) {
2164 axial_vector = make_vec3(0, 0, 1);
2165 } else {
2166 axial_vector = axial_vector / mag;
2167 }
2168 if (fabs(axial_vector * initial_radial) > 0.95f) {
2169 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2170 }
2171 // Also handle nearly vertical axes
2172 if (fabs(axial_vector.z) > 0.95f) {
2173 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2174 }
2175 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2176 } else {
2177 if (i == node_count - 1) {
2178 axial_vector = nodes[i] - nodes[i - 1];
2179 } else {
2180 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2181 }
2182 float mag = axial_vector.magnitude();
2183 if (mag < 1e-6f) {
2184 axial_vector = make_vec3(0, 0, 1);
2185 } else {
2186 axial_vector = axial_vector / mag;
2187 }
2188
2189 // Calculate radial direction using parallel transport
2190 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2191 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2192 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2193 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2194 } else {
2195 // Vectors are nearly parallel, use robust fallback
2196 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2197 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2198 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2199 }
2200 if (fabs(axial_vector.z) > 0.95f) {
2201 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2202 }
2203 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2204 }
2205 }
2206
2207 previous_axial_vector = axial_vector;
2208
2209 vec3 radial_dir = previous_radial_dir;
2210 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2211 orthogonal_dir.normalize();
2212
2213 if (i < node_count - 2) {
2214 continue;
2215 }
2216
2217 for (int j = 0; j < radial_subdivisions + 1; j++) {
2218 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2219 triangle_vertices[i][j] = nodes[i] + normal;
2220 }
2221 }
2222
2223 std::vector<float> ufrac{textureuv_ufrac.x, textureuv_ufrac.y};
2224 for (int i = 0; i < 2; i++) {
2225 for (int j = 0; j < radial_subdivisions + 1; j++) {
2226 uv[i][j].x = ufrac[i];
2227 uv[i][j].y = float(j) / float(radial_subdivisions);
2228 }
2229 }
2230
2231 int old_triangle_count = UUIDs.size();
2232
2233 vec3 v0, v1, v2;
2234 vec2 uv0, uv1, uv2;
2235
2236 // Add triangles for new segment
2237 for (int j = 0; j < radial_subdivisions; j++) {
2238 v0 = triangle_vertices[node_count - 2][j];
2239 v1 = triangle_vertices[node_count - 1][j + 1];
2240 v2 = triangle_vertices[node_count - 2][j + 1];
2241
2242 uv0 = uv[0][j];
2243 uv1 = uv[1][j + 1];
2244 uv2 = uv[0][j + 1];
2245
2246 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2247
2248 v0 = triangle_vertices[node_count - 2][j];
2249 v1 = triangle_vertices[node_count - 1][j];
2250 v2 = triangle_vertices[node_count - 1][j + 1];
2251
2252 uv0 = uv[0][j];
2253 uv1 = uv[1][j];
2254 uv2 = uv[1][j + 1];
2255
2256 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2257 }
2258
2259 for (uint p: UUIDs) {
2260 context->setPrimitiveParentObjectID(p, this->OID);
2261 }
2262
2263 updateTriangleVertices();
2264}
2265
2267 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2268 for (vec3 &vertex: triangle_vertices.at(segment)) {
2269 vec3 axis = vertex - nodes.at(segment);
2270
2271 float current_radius = axis.magnitude();
2272 axis = axis / current_radius;
2273
2274 vertex = nodes.at(segment) + axis * current_radius * S;
2275 }
2276 radius.at(segment) *= S;
2277 }
2278
2279 updateTriangleVertices();
2280}
2281
2282void Tube::setTubeRadii(const std::vector<float> &node_radii) {
2283 if (node_radii.size() != nodes.size()) {
2284 helios_runtime_error("ERROR (Tube::setTubeRadii): Number of radii in input vector must match number of tube nodes.");
2285 }
2286
2287 radius = node_radii;
2288
2289 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2290 for (vec3 &vertex: triangle_vertices.at(segment)) {
2291 vec3 axis = vertex - nodes.at(segment);
2292 axis.normalize();
2293
2294 vertex = nodes.at(segment) + axis * radius.at(segment);
2295 }
2296 }
2297
2298 updateTriangleVertices();
2299}
2300
2302 for (int segment = 0; segment < triangle_vertices.size() - 1; segment++) {
2303 vec3 central_axis = (nodes.at(segment + 1) - nodes.at(segment));
2304 float current_length = central_axis.magnitude();
2305 central_axis = central_axis / current_length;
2306 vec3 dL = central_axis * current_length * (1.f - S);
2307
2308 for (int downstream_segment = segment + 1; downstream_segment < triangle_vertices.size(); downstream_segment++) {
2309 nodes.at(downstream_segment) = nodes.at(downstream_segment) - dL;
2310
2311 for (int v = 0; v < triangle_vertices.at(downstream_segment).size(); v++) {
2312 triangle_vertices.at(downstream_segment).at(v) = triangle_vertices.at(downstream_segment).at(v) - dL;
2313 }
2314 }
2315 }
2316
2317 updateTriangleVertices();
2318}
2319
2320void Tube::setTubeNodes(const std::vector<helios::vec3> &node_xyz) {
2321 if (node_xyz.size() != nodes.size()) {
2322 helios_runtime_error("ERROR (Tube::setTubeNodes): Number of nodes in input vector must match number of tube nodes.");
2323 }
2324
2325 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2326 for (vec3 &vertex: triangle_vertices.at(segment)) {
2327 vertex = node_xyz.at(segment) + vertex - nodes.at(segment);
2328 }
2329 }
2330
2331 nodes = node_xyz;
2332
2333 updateTriangleVertices();
2334}
2335
2336void Tube::pruneTubeNodes(uint node_index) {
2337 if (node_index >= nodes.size()) {
2338 helios_runtime_error("ERROR (Tube::pruneTubeNodes): Node index of " + std::to_string(node_index) + " is out of bounds.");
2339 }
2340
2341 if (node_index == 0) {
2342 context->deleteObject(this->OID);
2343 return;
2344 }
2345
2346 nodes.erase(nodes.begin() + node_index, nodes.end());
2347 triangle_vertices.erase(triangle_vertices.begin() + node_index, triangle_vertices.end());
2348 radius.erase(radius.begin() + node_index, radius.end());
2349 colors.erase(colors.begin() + node_index, colors.end());
2350
2351 int ii = 0;
2352 for (int i = node_index; i < nodes.size() - 1; i++) {
2353 for (int j = 0; j < subdiv; j++) {
2354 context->deletePrimitive(UUIDs.at(ii));
2355 context->deletePrimitive(UUIDs.at(ii + 1));
2356 ii += 2;
2357 }
2358 }
2359}
2360
2361void Tube::updateTriangleVertices() const {
2362 int ii = 0;
2363 for (int i = 0; i < nodes.size() - 1; i++) {
2364 for (int j = 0; j < subdiv; j++) {
2365 vec3 v0 = triangle_vertices.at(i).at(j);
2366 vec3 v1 = triangle_vertices.at(i + 1).at(j + 1);
2367 vec3 v2 = triangle_vertices.at(i).at(j + 1);
2368 context->setTriangleVertices(UUIDs.at(ii), v0, v1, v2);
2369
2370 v0 = triangle_vertices.at(i).at(j);
2371 v1 = triangle_vertices.at(i + 1).at(j);
2372 v2 = triangle_vertices.at(i + 1).at(j + 1);
2373
2374 context->setTriangleVertices(UUIDs.at(ii + 1), v0, v1, v2);
2375
2376 ii += 2;
2377 }
2378 }
2379}
2380
2381// ============== BOX CLASS METHOD DEFINITIONS ==============
2382
2383Box::Box(uint a_OID, const std::vector<uint> &a_UUIDs, const int3 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2384 makeIdentityMatrix(transform);
2385
2386 OID = a_OID;
2388 UUIDs = a_UUIDs;
2389 subdiv = a_subdiv;
2390 texturefile = a_texturefile;
2391 context = a_context;
2392}
2393
2395 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0), nz(0, 0, 1);
2396
2397 vec3 n0_T, nx_T, ny_T, nz_T;
2398
2399 vecmult(transform, n0, n0_T);
2400 vecmult(transform, nx, nx_T);
2401 vecmult(transform, ny, ny_T);
2402 vecmult(transform, nz, nz_T);
2403
2404 float x = (nx_T - n0_T).magnitude();
2405 float y = (ny_T - n0_T).magnitude();
2406 float z = (nz_T - n0_T).magnitude();
2407
2408 return make_vec3(x, y, z);
2409}
2410
2412 vec3 center;
2413 vec3 Y;
2414 Y.x = 0.f;
2415 Y.y = 0.f;
2416 Y.z = 0.f;
2417
2418 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2419 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2420 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2421
2422 return center;
2423}
2424
2426 return subdiv;
2427}
2428
2430 subdiv = a_subdiv;
2431}
2432
2433float Box::getVolume() const {
2434 const vec3 &size = getSize();
2435 return size.x * size.y * size.z;
2436}
2437
2438// ============== DISK CLASS METHOD DEFINITIONS ==============
2439
2440Disk::Disk(uint a_OID, const std::vector<uint> &a_UUIDs, int2 a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2441 makeIdentityMatrix(transform);
2442
2443 OID = a_OID;
2445 UUIDs = a_UUIDs;
2446 subdiv = a_subdiv;
2447 texturefile = a_texturefile;
2448 context = a_context;
2449}
2450
2452 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0);
2453 vec3 n0_T, nx_T, ny_T;
2454
2455 vecmult(transform, n0, n0_T);
2456 vecmult(transform, nx, nx_T);
2457 vecmult(transform, ny, ny_T);
2458
2459 float x = (nx_T - n0_T).magnitude();
2460 float y = (ny_T - n0_T).magnitude();
2461
2462 return make_vec2(x, y);
2463}
2464
2466 vec3 center;
2467 vec3 Y;
2468 Y.x = 0.f;
2469 Y.y = 0.f;
2470 Y.z = 0.f;
2471
2472 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2473 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2474 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2475
2476 return center;
2477}
2478
2480 return subdiv;
2481}
2482
2484 subdiv = a_subdiv;
2485}
2486
2487// ============== POLYMESH CLASS METHOD DEFINITIONS ==============
2488
2489Polymesh::Polymesh(uint a_OID, const std::vector<uint> &a_UUIDs, const char *a_texturefile, helios::Context *a_context) {
2490 makeIdentityMatrix(transform);
2491
2492 OID = a_OID;
2494 UUIDs = a_UUIDs;
2495 texturefile = a_texturefile;
2496 context = a_context;
2497}
2498
2499float Polymesh::getVolume() const {
2500 float volume = 0.f;
2501 for (uint UUID: UUIDs) {
2502 if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_TRIANGLE) {
2503 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2504 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2505 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2506 volume += (1.f / 6.f) * v0 * cross(v1, v2);
2507 } else if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_PATCH) {
2508 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2509 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2510 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2511 const vec3 &v3 = context->getTriangleVertex(UUID, 3);
2512 volume += (1.f / 6.f) * v0 * cross(v1, v2) + (1.f / 6.f) * v0 * cross(v2, v3);
2513 }
2514 }
2515 return std::abs(volume);
2516}
2517
2518// ============== CONE CLASS METHOD DEFINITIONS ==============
2519
2520Cone::Cone(uint a_OID, const std::vector<uint> &a_UUIDs, const vec3 &a_node0, const vec3 &a_node1, float a_radius0, float a_radius1, uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2521 makeIdentityMatrix(transform);
2522
2523 OID = a_OID;
2525 UUIDs = a_UUIDs;
2526 subdiv = a_subdiv;
2527 texturefile = a_texturefile;
2528 context = a_context;
2529 nodes = {a_node0, a_node1};
2530 radii = {a_radius0, a_radius1};
2531}
2532
2533std::vector<helios::vec3> Cone::getNodeCoordinates() const {
2534 std::vector<vec3> nodes_T;
2535 nodes_T.resize(2);
2536
2537 for (int i = 0; i < 2; i++) {
2538 nodes_T.at(i).x = transform[0] * nodes.at(i).x + transform[1] * nodes.at(i).y + transform[2] * nodes.at(i).z + transform[3];
2539 nodes_T.at(i).y = transform[4] * nodes.at(i).x + transform[5] * nodes.at(i).y + transform[6] * nodes.at(i).z + transform[7];
2540 nodes_T.at(i).z = transform[8] * nodes.at(i).x + transform[9] * nodes.at(i).y + transform[10] * nodes.at(i).z + transform[11];
2541 }
2542
2543 return nodes_T;
2544}
2545
2547 if (node_index < 0 || node_index > 1) {
2548 helios_runtime_error("ERROR (Cone::getNodeCoordinate): node number must be 0 or 1.");
2549 }
2550
2551 vec3 node_T;
2552
2553 node_T.x = transform[0] * nodes.at(node_index).x + transform[1] * nodes.at(node_index).y + transform[2] * nodes.at(node_index).z + transform[3];
2554 node_T.y = transform[4] * nodes.at(node_index).x + transform[5] * nodes.at(node_index).y + transform[6] * nodes.at(node_index).z + transform[7];
2555 node_T.z = transform[8] * nodes.at(node_index).x + transform[9] * nodes.at(node_index).y + transform[10] * nodes.at(node_index).z + transform[11];
2556
2557 return node_T;
2558}
2559
2560std::vector<float> Cone::getNodeRadii() const {
2561 return radii;
2562}
2563
2564float Cone::getNodeRadius(int node_index) const {
2565 if (node_index < 0 || node_index > 1) {
2566 helios_runtime_error("ERROR (Cone::getNodeRadius): node number must be 0 or 1.");
2567 }
2568
2569 return radii.at(node_index);
2570}
2571
2573 return subdiv;
2574}
2575
2577 subdiv = a_subdiv;
2578}
2579
2581 std::vector<vec3> nodes_T;
2582 nodes_T.resize(2);
2583
2584 for (uint i = 0; i < 2; i++) {
2585 nodes_T.at(i).x = transform[0] * nodes.at(i).x + transform[1] * nodes.at(i).y + transform[2] * nodes.at(i).z + transform[3];
2586 nodes_T.at(i).y = transform[4] * nodes.at(i).x + transform[5] * nodes.at(i).y + transform[6] * nodes.at(i).z + transform[7];
2587 nodes_T.at(i).z = transform[8] * nodes.at(i).x + transform[9] * nodes.at(i).y + transform[10] * nodes.at(i).z + transform[11];
2588 }
2589
2590 helios::vec3 axis_unit_vector = helios::make_vec3(nodes_T.at(1).x - nodes_T.at(0).x, nodes_T.at(1).y - nodes_T.at(0).y, nodes_T.at(1).z - nodes_T.at(0).z);
2591 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2592 axis_unit_vector = axis_unit_vector / length;
2593
2594 return axis_unit_vector;
2595}
2596
2597float Cone::getLength() const {
2598 std::vector<vec3> nodes_T;
2599 nodes_T.resize(2);
2600
2601 for (uint i = 0; i < 2; i++) {
2602 nodes_T.at(i).x = transform[0] * nodes.at(i).x + transform[1] * nodes.at(i).y + transform[2] * nodes.at(i).z + transform[3];
2603 nodes_T.at(i).y = transform[4] * nodes.at(i).x + transform[5] * nodes.at(i).y + transform[6] * nodes.at(i).z + transform[7];
2604 nodes_T.at(i).z = transform[8] * nodes.at(i).x + transform[9] * nodes.at(i).y + transform[10] * nodes.at(i).z + transform[11];
2605 }
2606
2607 float length = powf(powf(nodes_T.at(1).x - nodes_T.at(0).x, 2) + powf(nodes_T.at(1).y - nodes_T.at(0).y, 2) + powf(nodes_T.at(1).z - nodes_T.at(0).z, 2), 0.5);
2608 return length;
2609}
2610
2611void Cone::scaleLength(float S) {
2612 // get the nodes and radii of the nodes with transformation matrix applied
2613 const std::vector<helios::vec3> &nodes_T = getNodeCoordinates();
2614 const std::vector<float> &radii_T = getNodeRadii();
2615
2616 // calculate the transformed axis unit vector of the cone
2617 vec3 axis_unit_vector = helios::make_vec3(nodes_T.at(1).x - nodes_T.at(0).x, nodes_T.at(1).y - nodes_T.at(0).y, nodes_T.at(1).z - nodes_T.at(0).z);
2618 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2619 axis_unit_vector = axis_unit_vector / length;
2620
2621 // translate node 0 back to origin
2622 translate(-1.0 * nodes_T.at(0));
2623
2624 // rotate the cone to align with z axis
2625 helios::vec3 z_axis = make_vec3(0, 0, 1);
2626 // get the axis about which to rotate
2627 vec3 ra = cross(z_axis, axis_unit_vector);
2628 // get the angle to rotate
2629 float dot = axis_unit_vector.x * z_axis.x + axis_unit_vector.y * z_axis.y + axis_unit_vector.z * z_axis.z;
2630 float angle = acos_safe(dot);
2631
2632 // only rotate if the cone is not alread aligned with the z axis (i.e., angle is not zero. If zero, the axis of rotation is 0,0,0 and we end up with problems)
2633 if (angle != 0.f) {
2634 rotate(-1 * angle, ra);
2635 }
2636
2637 // scale the cone in the z (length) dimension
2638 float T[16], T_prim[16];
2639 makeScaleMatrix(make_vec3(1, 1, S), T);
2640 matmult(T, transform, transform);
2641 for (uint UUID: UUIDs) {
2642 if (context->doesPrimitiveExist(UUID)) {
2643 context->getPrimitiveTransformationMatrix(UUID, T_prim);
2644 matmult(T, T_prim, T_prim);
2645 context->setPrimitiveTransformationMatrix(UUID, T_prim);
2646 }
2647 }
2648
2649 // rotate back
2650 if (angle != 0.f) {
2651 rotate(angle, ra);
2652 }
2653
2654 // translate back
2655 translate(nodes_T.at(0));
2656}
2657
2658void Cone::scaleGirth(float S) {
2659 // get the nodes and radii of the nodes with transformation matrix applied
2660 const std::vector<helios::vec3> &nodes_T = getNodeCoordinates();
2661 const std::vector<float> &radii_T = getNodeRadii();
2662
2663 // calculate the transformed axis unit vector of the cone
2664 vec3 axis_unit_vector = helios::make_vec3(nodes_T.at(1).x - nodes_T.at(0).x, nodes_T.at(1).y - nodes_T.at(0).y, nodes_T.at(1).z - nodes_T.at(0).z);
2665 axis_unit_vector.normalize();
2666
2667 // translate node 0 back to origin
2668 translate(-1.0 * nodes_T.at(0));
2669 // rotate the cone to align with z axis
2670 helios::vec3 z_axis = make_vec3(0, 0, 1);
2671 // get the axis about which to rotate
2672 vec3 ra = cross(z_axis, axis_unit_vector);
2673 // get the angle to rotate
2674 float dot = axis_unit_vector * z_axis;
2675 float angle = acos_safe(dot);
2676 // only rotate if the cone is not already aligned with the z axis (i.e., angle is not zero. If zero, the axis of rotation is 0,0,0 and we end up with problems)
2677 if (angle != 0.f) {
2678 rotate(-1 * angle, ra);
2679 }
2680
2681 // scale the cone in the x and y dimensions
2682 context->scaleObject(OID, make_vec3(S, S, 1));
2683
2684
2685 // rotate back
2686 if (angle != 0.f) {
2687 rotate(angle, ra);
2688 }
2689
2690 // translate back
2691 translate(nodes_T.at(0));
2692
2693 radii.at(0) *= S;
2694 radii.at(1) *= S;
2695}
2696
2697float Cone::getVolume() const {
2698 float r0 = getNodeRadius(0);
2699 float r1 = getNodeRadius(1);
2700 float h = getLength();
2701
2702 return PI_F * h / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
2703}