1.3.72
 
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 objects[currentObjectID] = cone_new;
1347 currentObjectID++;
1348
1349 uint objID = currentObjectID - 1;
1350 cone_new->object_origin = getObjectCenter(objID);
1351
1352 return objID;
1353}
1354
1355uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const char *texturefile) {
1356 if (!validateTextureFileExtenstion(texturefile)) {
1357 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1358 } else if (!doesTextureFileExist(texturefile)) {
1359 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " does not exist.");
1360 }
1361
1362 const std::vector<helios::vec3> nodes{node0, node1};
1363 const std::vector<float> radii{radius0, radius1};
1364
1365 vec3 convec;
1366 std::vector<float> cfact(Ndivs + 1);
1367 std::vector<float> sfact(Ndivs + 1);
1368 std::vector<std::vector<vec3>> xyz, normal;
1369 std::vector<std::vector<vec2>> uv;
1370 xyz.resize(Ndivs + 1);
1371 normal.resize(Ndivs + 1);
1372 uv.resize(Ndivs + 1);
1373 for (uint j = 0; j < Ndivs + 1; j++) {
1374 xyz.at(j).resize(2);
1375 normal.at(j).resize(2);
1376 uv.at(j).resize(2);
1377 }
1378 vec3 nvec(0.f, 1.f, 0.f);
1379
1380 for (int j = 0; j < Ndivs + 1; j++) {
1381 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
1382 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
1383 }
1384
1385 for (int i = 0; i < 2; i++) {
1386 vec3 vec;
1387 // looping over cone segments
1388
1389 if (i == 0) {
1390 vec.x = nodes[i + 1].x - nodes[i].x;
1391 vec.y = nodes[i + 1].y - nodes[i].y;
1392 vec.z = nodes[i + 1].z - nodes[i].z;
1393 } else if (i == 1) {
1394 vec.x = nodes[i].x - nodes[i - 1].x;
1395 vec.y = nodes[i].y - nodes[i - 1].y;
1396 vec.z = nodes[i].z - nodes[i - 1].z;
1397 }
1398
1399 if (vec.magnitude() < 1e-6f) {
1400 vec = make_vec3(0, 0, 1);
1401 }
1402 float norm;
1403 convec = cross(nvec, vec);
1404 norm = convec.magnitude();
1405 if (norm < 1e-6f) {
1406 convec = cross(vec, fabs(vec.x) < 0.9f ? make_vec3(1, 0, 0) : make_vec3(0, 1, 0));
1407 norm = std::max(convec.magnitude(), 1e-6f);
1408 }
1409 convec = convec / norm;
1410 nvec = cross(vec, convec);
1411 norm = nvec.magnitude();
1412 if (norm < 1e-6f) {
1413 nvec = cross(convec, vec);
1414 norm = std::max(nvec.magnitude(), 1e-6f);
1415 }
1416 nvec = nvec / norm;
1417
1418 for (int j = 0; j < Ndivs + 1; j++) {
1419 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
1420 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
1421 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
1422
1423 xyz[j][i].x = nodes[i].x + normal[j][i].x;
1424 xyz[j][i].y = nodes[i].y + normal[j][i].y;
1425 xyz[j][i].z = nodes[i].z + normal[j][i].z;
1426
1427 uv[j][i].x = float(i) / float(2 - 1);
1428 uv[j][i].y = float(j) / float(Ndivs);
1429
1430 normal[j][i] = normal[j][i] / radii[i];
1431 }
1432 }
1433
1434 vec3 v0, v1, v2;
1435 vec2 uv0, uv1, uv2;
1436 std::vector<uint> UUID;
1437
1438 for (int i = 0; i < 2 - 1; i++) {
1439 for (int j = 0; j < Ndivs; j++) {
1440 v0 = xyz[j][i];
1441 v1 = xyz[j + 1][i + 1];
1442 v2 = xyz[j + 1][i];
1443
1444 uv0 = uv[j][i];
1445 uv1 = uv[j + 1][i + 1];
1446 uv2 = uv[j + 1][i];
1447
1448 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1449 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1450 if (getPrimitiveArea(triangle_uuid) > 0) {
1451 UUID.push_back(triangle_uuid);
1452 } else {
1453 deletePrimitive(triangle_uuid);
1454 }
1455 }
1456
1457 v0 = xyz[j][i];
1458 v1 = xyz[j][i + 1];
1459 v2 = xyz[j + 1][i + 1];
1460
1461 uv0 = uv[j][i];
1462 uv1 = uv[j][i + 1];
1463 uv2 = uv[j + 1][i + 1];
1464
1465 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1466 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1467 if (getPrimitiveArea(triangle_uuid) > 0) {
1468 UUID.push_back(triangle_uuid);
1469 } else {
1470 deletePrimitive(triangle_uuid);
1471 }
1472 }
1473 }
1474 }
1475
1476 auto *cone_new = (new Cone(currentObjectID, UUID, node0, node1, radius0, radius1, Ndivs, texturefile, this));
1477
1478 for (uint p: UUID) {
1479 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1480 }
1481
1482 objects[currentObjectID] = cone_new;
1483 currentObjectID++;
1484
1485 uint objID = currentObjectID - 1;
1486 cone_new->object_origin = getObjectCenter(objID);
1487
1488 return objID;
1489}
1490
1491// ============== COMPOUND OBJECT CLASS METHOD DEFINITIONS ==============
1492
1494
1496 return OID;
1497}
1498
1500 return type;
1501}
1502
1504 return UUIDs.size();
1505}
1506
1507
1508std::vector<uint> CompoundObject::getPrimitiveUUIDs() const {
1509 return UUIDs;
1510}
1511
1513 return find(UUIDs.begin(), UUIDs.end(), UUID) != UUIDs.end();
1514}
1515
1517 vec2 xbounds, ybounds, zbounds;
1518
1519 const std::vector<uint> &U = getPrimitiveUUIDs();
1520
1521 context->getDomainBoundingBox(U, xbounds, ybounds, zbounds);
1522
1523 vec3 origin;
1524
1525 origin.x = 0.5f * (xbounds.x + xbounds.y);
1526 origin.y = 0.5f * (ybounds.x + ybounds.y);
1527 origin.z = 0.5f * (zbounds.x + zbounds.y);
1528
1529 return origin;
1530}
1531
1533 float area = 0.f;
1534
1535 for (uint UUID: UUIDs) {
1536 if (context->doesPrimitiveExist(UUID)) {
1537 area += context->getPrimitiveArea(UUID);
1538 }
1539 }
1540
1541 return area;
1542}
1543
1545 for (uint UUID: UUIDs) {
1546 if (context->doesPrimitiveExist(UUID)) {
1547 context->setPrimitiveColor(UUID, a_color);
1548 }
1549 }
1550}
1551
1553 for (uint UUID: UUIDs) {
1554 if (context->doesPrimitiveExist(UUID)) {
1555 context->setPrimitiveColor(UUID, a_color);
1556 }
1557 }
1558}
1559
1561 for (uint UUID: UUIDs) {
1562 if (context->doesPrimitiveExist(UUID)) {
1563 context->overridePrimitiveTextureColor(UUID);
1564 }
1565 }
1566}
1567
1569 for (uint UUID: UUIDs) {
1570 if (context->doesPrimitiveExist(UUID)) {
1571 context->usePrimitiveTextureColor(UUID);
1572 }
1573 }
1574}
1575
1577 if (getTextureFile().empty()) {
1578 return false;
1579 } else {
1580 return true;
1581 }
1582}
1583
1585 return texturefile;
1586}
1587
1589 if (shift == nullorigin) {
1590 return;
1591 }
1592
1593 float T[16], T_prim[16];
1594 makeTranslationMatrix(shift, T);
1595
1596 matmult(T, transform, transform);
1597
1598 for (uint UUID: UUIDs) {
1599 if (context->doesPrimitiveExist(UUID)) {
1600 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1601 matmult(T, T_prim, T_prim);
1602 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1603 }
1604 }
1605}
1606
1607void CompoundObject::rotate(float rotation_radians, const char *rotation_axis_xyz_string) {
1608 if (rotation_radians == 0) {
1609 return;
1610 }
1611
1612 if (strcmp(rotation_axis_xyz_string, "z") == 0) {
1613 float Rz[16], Rz_prim[16];
1614 makeRotationMatrix(-rotation_radians, "z", Rz);
1615 matmult(Rz, transform, transform);
1616
1617 for (uint UUID: UUIDs) {
1618 if (context->doesPrimitiveExist(UUID)) {
1619 context->getPrimitiveTransformationMatrix(UUID, Rz_prim);
1620 matmult(Rz, Rz_prim, Rz_prim);
1621 context->setPrimitiveTransformationMatrix(UUID, Rz_prim);
1622 }
1623 }
1624 } else if (strcmp(rotation_axis_xyz_string, "y") == 0) {
1625 float Ry[16], Ry_prim[16];
1626 makeRotationMatrix(rotation_radians, "y", Ry);
1627 matmult(Ry, transform, transform);
1628 for (uint UUID: UUIDs) {
1629 if (context->doesPrimitiveExist(UUID)) {
1630 context->getPrimitiveTransformationMatrix(UUID, Ry_prim);
1631 matmult(Ry, Ry_prim, Ry_prim);
1632 context->setPrimitiveTransformationMatrix(UUID, Ry_prim);
1633 }
1634 }
1635 } else if (strcmp(rotation_axis_xyz_string, "x") == 0) {
1636 float Rx[16], Rx_prim[16];
1637 makeRotationMatrix(rotation_radians, "x", Rx);
1638 matmult(Rx, transform, transform);
1639 for (uint UUID: UUIDs) {
1640 if (context->doesPrimitiveExist(UUID)) {
1641 context->getPrimitiveTransformationMatrix(UUID, Rx_prim);
1642 matmult(Rx, Rx_prim, Rx_prim);
1643 context->setPrimitiveTransformationMatrix(UUID, Rx_prim);
1644 }
1645 }
1646 } else {
1647 helios_runtime_error("ERROR (CompoundObject::rotate): Rotation axis should be one of x, y, or z.");
1648 }
1649}
1650
1651void CompoundObject::rotate(float rotation_radians, const helios::vec3 &rotation_axis_vector) {
1652 if (rotation_radians == 0) {
1653 return;
1654 }
1655
1656 float R[16], R_prim[16];
1657 makeRotationMatrix(rotation_radians, rotation_axis_vector, R);
1658 matmult(R, transform, transform);
1659
1660 for (uint UUID: UUIDs) {
1661 if (context->doesPrimitiveExist(UUID)) {
1662 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1663 matmult(R, R_prim, R_prim);
1664 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1665 }
1666 }
1667}
1668
1669void CompoundObject::rotate(float rotation_radians, const helios::vec3 &origin, const helios::vec3 &rotation_axis_vector) {
1670 if (rotation_radians == 0) {
1671 return;
1672 }
1673
1674 float R[16], R_prim[16];
1675 makeRotationMatrix(rotation_radians, origin, rotation_axis_vector, R);
1676 matmult(R, transform, transform);
1677
1678 for (uint UUID: UUIDs) {
1679 if (context->doesPrimitiveExist(UUID)) {
1680 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1681 matmult(R, R_prim, R_prim);
1682 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1683 }
1684 }
1685}
1686
1690
1694
1696 if (scale.x == 1.f && scale.y == 1.f && scale.z == 1.f) {
1697 return;
1698 }
1699
1700 float T[16], T_prim[16];
1701 makeScaleMatrix(scale, point, T);
1702 matmult(T, transform, transform);
1703
1704 for (uint UUID: UUIDs) {
1705 if (context->doesPrimitiveExist(UUID)) {
1706 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1707 matmult(T, T_prim, T_prim);
1708 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1709 }
1710 }
1711}
1712
1714 for (int i = 0; i < 16; i++) {
1715 T[i] = transform[i];
1716 }
1717}
1718
1720 for (int i = 0; i < 16; i++) {
1721 transform[i] = T[i];
1722 }
1723}
1724
1725void CompoundObject::setPrimitiveUUIDs(const std::vector<uint> &a_UUIDs) {
1726 UUIDs = a_UUIDs;
1727}
1728
1730 auto it = find(UUIDs.begin(), UUIDs.end(), UUID);
1731 if (it != UUIDs.end()) {
1732 std::iter_swap(it, UUIDs.end() - 1);
1733 UUIDs.pop_back();
1734 primitivesarecomplete = false;
1735 }
1736}
1737
1738void CompoundObject::deleteChildPrimitive(const std::vector<uint> &a_UUIDs) {
1739 for (uint UUID: a_UUIDs) {
1741 }
1742}
1743
1745 return primitivesarecomplete;
1746}
1747
1748// ============== TILE CLASS METHOD DEFINITIONS ==============
1749
1750Tile::Tile(uint a_OID, const std::vector<uint> &a_UUIDs, const int2 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1751 makeIdentityMatrix(transform);
1752
1753 OID = a_OID;
1755 UUIDs = a_UUIDs;
1756 subdiv = a_subdiv;
1757 texturefile = a_texturefile;
1758 context = a_context;
1759}
1760
1762 const std::vector<vec3> &vertices = getVertices();
1763 float l = (vertices.at(1) - vertices.at(0)).magnitude();
1764 float w = (vertices.at(3) - vertices.at(0)).magnitude();
1765 return make_vec2(l, w);
1766}
1767
1769 vec3 center;
1770 vec3 Y;
1771 Y.x = 0.f;
1772 Y.y = 0.f;
1773 Y.z = 0.f;
1774
1775 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1776 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1777 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1778
1779 return center;
1780}
1781
1782
1784 return subdiv;
1785}
1786
1788 subdiv = a_subdiv;
1789}
1790
1791
1792std::vector<helios::vec3> Tile::getVertices() const {
1793 std::vector<helios::vec3> vertices;
1794 vertices.resize(4);
1795
1796 // subcenter = make_vec3(-0.5*size.x+(float(i)+0.5)*subsize.x,-0.5*size.y+(float(j)+0.5)*subsize.y,0);
1797 // Y[0] = make_vec3( -0.5f, -0.5f, 0.f);
1798 // Y[1] = make_vec3( 0.5f, -0.5f, 0.f);
1799 // Y[2] = make_vec3( 0.5f, 0.5f, 0.f);
1800 // Y[3] = make_vec3( -0.5f, 0.5f, 0.f);
1801
1802
1803 vec3 Y[4];
1804 Y[0] = make_vec3(-0.5f, -0.5f, 0.f);
1805 Y[1] = make_vec3(0.5f, -0.5f, 0.f);
1806 Y[2] = make_vec3(0.5f, 0.5f, 0.f);
1807 Y[3] = make_vec3(-0.5f, 0.5f, 0.f);
1808
1809 for (int i = 0; i < 4; i++) {
1810 vertices[i].x = transform[0] * Y[i].x + transform[1] * Y[i].y + transform[2] * Y[i].z + transform[3];
1811 vertices[i].y = transform[4] * Y[i].x + transform[5] * Y[i].y + transform[6] * Y[i].z + transform[7];
1812 vertices[i].z = transform[8] * Y[i].x + transform[9] * Y[i].y + transform[10] * Y[i].z + transform[11];
1813 }
1814
1815 return vertices;
1816}
1817
1819 return context->getPrimitiveNormal(UUIDs.front());
1820}
1821
1822std::vector<helios::vec2> Tile::getTextureUV() const {
1823 return {make_vec2(0, 0), make_vec2(1, 0), make_vec2(1, 1), make_vec2(0, 1)};
1824}
1825
1826// ============== SPHERE CLASS METHOD DEFINITIONS ==============
1827
1828Sphere::Sphere(uint a_OID, const std::vector<uint> &a_UUIDs, uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1829 makeIdentityMatrix(transform);
1830
1831 OID = a_OID;
1833 UUIDs = a_UUIDs;
1834 subdiv = a_subdiv;
1835 texturefile = a_texturefile;
1836 context = a_context;
1837}
1838
1840 vec3 n0(0, 0, 0);
1841 vec3 nx(1, 0, 0);
1842 vec3 ny(0, 1, 0);
1843 vec3 nz(0, 0, 1);
1844 vec3 n0_T, nx_T, ny_T, nz_T;
1845
1846 vecmult(transform, n0, n0_T);
1847 vecmult(transform, nx, nx_T);
1848 vecmult(transform, ny, ny_T);
1849 vecmult(transform, nz, nz_T);
1850
1851 vec3 radii;
1852 radii.x = (nx_T - n0_T).magnitude();
1853 radii.y = (ny_T - n0_T).magnitude();
1854 radii.z = (nz_T - n0_T).magnitude();
1855
1856 return radii;
1857}
1858
1860 vec3 center;
1861 vec3 Y;
1862 Y.x = 0.f;
1863 Y.y = 0.f;
1864 Y.z = 0.f;
1865
1866 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1867 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1868 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1869
1870 return center;
1871}
1872
1874 return subdiv;
1875}
1876
1878 subdiv = a_subdiv;
1879}
1880
1881float Sphere::getVolume() const {
1882 const vec3 &radii = getRadius();
1883 return 4.f / 3.f * PI_F * radii.x * radii.y * radii.z;
1884}
1885
1886// ============== TUBE CLASS METHOD DEFINITIONS ==============
1887
1888Tube::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,
1889 uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1890 makeIdentityMatrix(transform);
1891
1892 OID = a_OID;
1894 UUIDs = a_UUIDs;
1895 nodes = a_nodes;
1896 radius = a_radius;
1897 colors = a_colors;
1898 triangle_vertices = a_triangle_vertices;
1899 subdiv = a_subdiv;
1900 texturefile = a_texturefile;
1901 context = a_context;
1902}
1903
1904std::vector<helios::vec3> Tube::getNodes() const {
1905 std::vector<vec3> nodes_T;
1906 nodes_T.resize(nodes.size());
1907
1908 for (uint i = 0; i < nodes.size(); i++) {
1909 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];
1910 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];
1911 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];
1912 }
1913
1914 return nodes_T;
1915}
1916
1918 return scast<uint>(nodes.size());
1919}
1920
1921std::vector<float> Tube::getNodeRadii() const {
1922 std::vector<float> radius_T;
1923 radius_T.resize(radius.size());
1924 for (int i = 0; i < radius.size(); i++) {
1925 vec3 n0(0, 0, 0), nx(radius.at(i), 0, 0);
1926 vec3 n0_T, nx_T;
1927
1928 vecmult(transform, n0, n0_T);
1929 vecmult(transform, nx, nx_T);
1930
1931 radius_T.at(i) = (nx_T - n0_T).magnitude();
1932 }
1933 return radius_T;
1934}
1935
1936std::vector<helios::RGBcolor> Tube::getNodeColors() const {
1937 return colors;
1938}
1939
1940std::vector<std::vector<helios::vec3>> Tube::getTriangleVertices() const {
1941 return triangle_vertices;
1942}
1943
1945 return subdiv;
1946}
1947
1948float Tube::getLength() const {
1949 float length = 0.f;
1950 for (uint i = 0; i < nodes.size() - 1; i++) {
1951 length += (nodes.at(i + 1) - nodes.at(i)).magnitude();
1952 }
1953 return length;
1954}
1955
1956float Tube::getVolume() const {
1957 const std::vector<float> &radii = getNodeRadii();
1958 float volume = 0.f;
1959 for (uint i = 0; i < radii.size() - 1; i++) {
1960 float segment_length = (nodes.at(i + 1) - nodes.at(i)).magnitude();
1961 float r0 = radii.at(i);
1962 float r1 = radii.at(i + 1);
1963 volume += PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1964 }
1965
1966 return volume;
1967}
1968
1969float Tube::getSegmentVolume(uint segment_index) const {
1970 if (segment_index >= nodes.size() - 1) {
1971 helios_runtime_error("ERROR (Tube::getSegmentVolume): Segment index out of bounds.");
1972 }
1973
1974 float segment_length = (nodes.at(segment_index + 1) - nodes.at(segment_index)).magnitude();
1975 float r0 = radius.at(segment_index);
1976 float r1 = radius.at(segment_index + 1);
1977 float volume = PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1978
1979 return volume;
1980}
1981
1982void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const helios::RGBcolor &node_color) {
1983 //\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.
1984
1985 if (node_radius < 0) {
1986 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
1987 }
1988 node_radius = std::max((float) 1e-5, node_radius);
1989
1990 uint radial_subdivisions = subdiv;
1991
1992 vec3 axial_vector;
1993 std::vector<float> cfact(radial_subdivisions + 1);
1994 std::vector<float> sfact(radial_subdivisions + 1);
1995
1996 for (int j = 0; j < radial_subdivisions + 1; j++) {
1997 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
1998 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
1999 }
2000
2001 triangle_vertices.resize(triangle_vertices.size() + 1);
2002 triangle_vertices.back().resize(radial_subdivisions + 1);
2003
2004 nodes.push_back(node_position);
2005 radius.push_back(node_radius);
2006 colors.push_back(node_color);
2007
2008 int node_count = nodes.size();
2009
2010 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2011 vec3 previous_axial_vector;
2012 vec3 previous_radial_dir;
2013
2014 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2015 if (radius.at(i) < 0) {
2016 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2017 }
2018
2019 if (i == 0) {
2020 axial_vector = nodes[i + 1] - nodes[i];
2021 float mag = axial_vector.magnitude();
2022 if (mag < 1e-6f) {
2023 axial_vector = make_vec3(0, 0, 1);
2024 } else {
2025 axial_vector = axial_vector / mag;
2026 }
2027 if (fabs(axial_vector * initial_radial) > 0.95f) {
2028 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2029 }
2030 // Also handle nearly vertical axes
2031 if (fabs(axial_vector.z) > 0.95f) {
2032 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2033 }
2034 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2035 } else {
2036 if (i == node_count - 1) {
2037 axial_vector = nodes[i] - nodes[i - 1];
2038 } else {
2039 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2040 }
2041 float mag = axial_vector.magnitude();
2042 if (mag < 1e-6f) {
2043 axial_vector = make_vec3(0, 0, 1);
2044 } else {
2045 axial_vector = axial_vector / mag;
2046 }
2047
2048 // Calculate radial direction using parallel transport
2049 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2050 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2051 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2052 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2053 } else {
2054 // Vectors are nearly parallel, use robust fallback
2055 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2056 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2057 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2058 }
2059 if (fabs(axial_vector.z) > 0.95f) {
2060 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2061 }
2062 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2063 }
2064 // else {
2065 // // Handle the case of nearly parallel vectors
2066 // // Ensure previous_radial_dir remains orthogonal to axial_vector
2067 // previous_radial_dir = cross(axial_vector, previous_radial_dir);
2068 // if (previous_radial_dir.magnitude() < 1e-6) {
2069 // // If still degenerate, choose another orthogonal direction
2070 // previous_radial_dir = cross(axial_vector, vec3(1.0f, 0.0f, 0.0f));
2071 // }
2072 // previous_radial_dir.normalize();
2073 // }
2074 }
2075
2076 previous_axial_vector = axial_vector;
2077
2078 vec3 radial_dir = previous_radial_dir;
2079 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2080 orthogonal_dir.normalize();
2081
2082 if (i < node_count - 2) {
2083 continue;
2084 }
2085
2086 for (int j = 0; j < radial_subdivisions + 1; j++) {
2087 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2088 triangle_vertices[i][j] = nodes[i] + normal;
2089 }
2090 }
2091
2092 // add triangles for new segment
2093
2094 int second_last = node_count - 2;
2095 int last = node_count - 1;
2096 for (int j = 0; j < radial_subdivisions; j++) {
2097 vec3 v0 = triangle_vertices.at(second_last).at(j);
2098 vec3 v1 = triangle_vertices.at(last).at(j + 1);
2099 vec3 v2 = triangle_vertices.at(second_last).at(j + 1);
2100
2101 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2102
2103 v0 = triangle_vertices.at(second_last).at(j);
2104 v1 = triangle_vertices.at(last).at(j);
2105 v2 = triangle_vertices.at(last).at(j + 1);
2106
2107 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2108 }
2109
2110 for (uint p: UUIDs) {
2111 context->setPrimitiveParentObjectID(p, this->OID);
2112 }
2113
2114 updateTriangleVertices();
2115}
2116
2117void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const char *texturefile, const helios::vec2 &textureuv_ufrac) {
2118 //\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.
2119
2120 if (node_radius < 0) {
2121 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
2122 } else if (textureuv_ufrac.x < 0 || textureuv_ufrac.y < 0 || textureuv_ufrac.x > 1 || textureuv_ufrac.y > 1) {
2123 helios_runtime_error("ERROR (Tube::appendTubeSegment): Texture U fraction must be between 0 and 1.");
2124 }
2125 node_radius = std::max((float) 1e-5, node_radius);
2126
2127 uint radial_subdivisions = subdiv;
2128
2129 vec3 axial_vector;
2130 std::vector<float> cfact(radial_subdivisions + 1);
2131 std::vector<float> sfact(radial_subdivisions + 1);
2132
2133 for (int j = 0; j < radial_subdivisions + 1; j++) {
2134 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2135 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2136 }
2137
2138 triangle_vertices.resize(triangle_vertices.size() + 1);
2139 triangle_vertices.back().resize(radial_subdivisions + 1);
2140 std::vector<std::vector<vec2>> uv;
2141 resize_vector(uv, radial_subdivisions + 1, 2);
2142
2143 nodes.push_back(node_position);
2144 radius.push_back(node_radius);
2145 colors.push_back(RGB::black);
2146
2147 int node_count = nodes.size();
2148
2149 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2150 vec3 previous_axial_vector;
2151 vec3 previous_radial_dir;
2152
2153 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2154 if (radius.at(i) < 0) {
2155 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2156 }
2157
2158 if (i == 0) {
2159 axial_vector = nodes[i + 1] - nodes[i];
2160 float mag = axial_vector.magnitude();
2161 if (mag < 1e-6f) {
2162 axial_vector = make_vec3(0, 0, 1);
2163 } else {
2164 axial_vector = axial_vector / mag;
2165 }
2166 if (fabs(axial_vector * initial_radial) > 0.95f) {
2167 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2168 }
2169 // Also handle nearly vertical axes
2170 if (fabs(axial_vector.z) > 0.95f) {
2171 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2172 }
2173 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2174 } else {
2175 if (i == node_count - 1) {
2176 axial_vector = nodes[i] - nodes[i - 1];
2177 } else {
2178 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2179 }
2180 float mag = axial_vector.magnitude();
2181 if (mag < 1e-6f) {
2182 axial_vector = make_vec3(0, 0, 1);
2183 } else {
2184 axial_vector = axial_vector / mag;
2185 }
2186
2187 // Calculate radial direction using parallel transport
2188 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2189 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2190 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2191 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2192 } else {
2193 // Vectors are nearly parallel, use robust fallback
2194 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2195 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2196 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2197 }
2198 if (fabs(axial_vector.z) > 0.95f) {
2199 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2200 }
2201 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2202 }
2203 }
2204
2205 previous_axial_vector = axial_vector;
2206
2207 vec3 radial_dir = previous_radial_dir;
2208 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2209 orthogonal_dir.normalize();
2210
2211 if (i < node_count - 2) {
2212 continue;
2213 }
2214
2215 for (int j = 0; j < radial_subdivisions + 1; j++) {
2216 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2217 triangle_vertices[i][j] = nodes[i] + normal;
2218 }
2219 }
2220
2221 std::vector<float> ufrac{textureuv_ufrac.x, textureuv_ufrac.y};
2222 for (int i = 0; i < 2; i++) {
2223 for (int j = 0; j < radial_subdivisions + 1; j++) {
2224 uv[i][j].x = ufrac[i];
2225 uv[i][j].y = float(j) / float(radial_subdivisions);
2226 }
2227 }
2228
2229 int old_triangle_count = UUIDs.size();
2230
2231 vec3 v0, v1, v2;
2232 vec2 uv0, uv1, uv2;
2233
2234 // Add triangles for new segment
2235 for (int j = 0; j < radial_subdivisions; j++) {
2236 v0 = triangle_vertices[node_count - 2][j];
2237 v1 = triangle_vertices[node_count - 1][j + 1];
2238 v2 = triangle_vertices[node_count - 2][j + 1];
2239
2240 uv0 = uv[0][j];
2241 uv1 = uv[1][j + 1];
2242 uv2 = uv[0][j + 1];
2243
2244 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2245
2246 v0 = triangle_vertices[node_count - 2][j];
2247 v1 = triangle_vertices[node_count - 1][j];
2248 v2 = triangle_vertices[node_count - 1][j + 1];
2249
2250 uv0 = uv[0][j];
2251 uv1 = uv[1][j];
2252 uv2 = uv[1][j + 1];
2253
2254 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2255 }
2256
2257 for (uint p: UUIDs) {
2258 context->setPrimitiveParentObjectID(p, this->OID);
2259 }
2260
2261 updateTriangleVertices();
2262}
2263
2265 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2266 for (vec3 &vertex: triangle_vertices.at(segment)) {
2267 vec3 axis = vertex - nodes.at(segment);
2268
2269 float current_radius = axis.magnitude();
2270 axis = axis / current_radius;
2271
2272 vertex = nodes.at(segment) + axis * current_radius * S;
2273 }
2274 radius.at(segment) *= S;
2275 }
2276
2277 updateTriangleVertices();
2278}
2279
2280void Tube::setTubeRadii(const std::vector<float> &node_radii) {
2281 if (node_radii.size() != nodes.size()) {
2282 helios_runtime_error("ERROR (Tube::setTubeRadii): Number of radii in input vector must match number of tube nodes.");
2283 }
2284
2285 radius = node_radii;
2286
2287 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2288 for (vec3 &vertex: triangle_vertices.at(segment)) {
2289 vec3 axis = vertex - nodes.at(segment);
2290 axis.normalize();
2291
2292 vertex = nodes.at(segment) + axis * radius.at(segment);
2293 }
2294 }
2295
2296 updateTriangleVertices();
2297}
2298
2300 for (int segment = 0; segment < triangle_vertices.size() - 1; segment++) {
2301 vec3 central_axis = (nodes.at(segment + 1) - nodes.at(segment));
2302 float current_length = central_axis.magnitude();
2303 central_axis = central_axis / current_length;
2304 vec3 dL = central_axis * current_length * (1.f - S);
2305
2306 for (int downstream_segment = segment + 1; downstream_segment < triangle_vertices.size(); downstream_segment++) {
2307 nodes.at(downstream_segment) = nodes.at(downstream_segment) - dL;
2308
2309 for (int v = 0; v < triangle_vertices.at(downstream_segment).size(); v++) {
2310 triangle_vertices.at(downstream_segment).at(v) = triangle_vertices.at(downstream_segment).at(v) - dL;
2311 }
2312 }
2313 }
2314
2315 updateTriangleVertices();
2316}
2317
2318void Tube::setTubeNodes(const std::vector<helios::vec3> &node_xyz) {
2319 if (node_xyz.size() != nodes.size()) {
2320 helios_runtime_error("ERROR (Tube::setTubeNodes): Number of nodes in input vector must match number of tube nodes.");
2321 }
2322
2323 nodes = node_xyz;
2324
2325 recomputeCrossSections();
2326 updateTriangleVertices();
2327}
2328
2329void Tube::pruneTubeNodes(uint node_index) {
2330 if (node_index >= nodes.size()) {
2331 helios_runtime_error("ERROR (Tube::pruneTubeNodes): Node index of " + std::to_string(node_index) + " is out of bounds.");
2332 }
2333
2334 if (node_index <= 1) {
2335 // 0 or 1 remaining nodes means 0 segments — delete the entire object.
2336 // NOTE: this deletes 'this', so we must return immediately after.
2337 context->deleteObject(this->OID);
2338 return;
2339 }
2340
2341 int original_segment_count = (int)nodes.size() - 1;
2342 int segments_to_keep = (int)node_index - 1;
2343
2344 // Partition UUIDs into kept vs. to-delete. UUIDs are in j-outer, i-inner order.
2345 std::vector<uint> kept_UUIDs;
2346 std::vector<uint> uuids_to_delete;
2347 int ii = 0;
2348 for (int j = 0; j < subdiv; j++) {
2349 for (int i = 0; i < original_segment_count; i++) {
2350 if (i < segments_to_keep) {
2351 kept_UUIDs.push_back(UUIDs.at(ii));
2352 kept_UUIDs.push_back(UUIDs.at(ii + 1));
2353 } else {
2354 uuids_to_delete.push_back(UUIDs.at(ii));
2355 uuids_to_delete.push_back(UUIDs.at(ii + 1));
2356 }
2357 ii += 2;
2358 }
2359 }
2360
2361 // Update object data first
2362 nodes.erase(nodes.begin() + node_index, nodes.end());
2363 triangle_vertices.erase(triangle_vertices.begin() + node_index, triangle_vertices.end());
2364 radius.erase(radius.begin() + node_index, radius.end());
2365 colors.erase(colors.begin() + node_index, colors.end());
2366 UUIDs = kept_UUIDs;
2367
2368 // Delete removed primitives. Since we already removed them from UUIDs,
2369 // deleteChildPrimitive won't find them and won't trigger object auto-deletion.
2370 for (uint uuid : uuids_to_delete) {
2371 context->deletePrimitive(uuid);
2372 }
2373}
2374
2375void Tube::recomputeCrossSections() {
2376 int node_count = (int)nodes.size();
2377 uint radial_subdivisions = subdiv;
2378
2379 // Clamp very small radii to avoid creating degenerate triangles
2380 const float min_radius_threshold = 1e-5f;
2381 std::vector<float> radius_clamped = radius;
2382 for (int i = 0; i < node_count; i++) {
2383 if (radius_clamped[i] < min_radius_threshold && radius_clamped[i] >= 0) {
2384 radius_clamped[i] = min_radius_threshold;
2385 }
2386 }
2387
2388 std::vector<float> cfact(radial_subdivisions + 1);
2389 std::vector<float> sfact(radial_subdivisions + 1);
2390 for (int j = 0; j < radial_subdivisions + 1; j++) {
2391 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2392 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2393 }
2394
2395 vec3 axial_vector;
2396 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2397 vec3 previous_axial_vector;
2398 vec3 previous_radial_dir;
2399
2400 for (int i = 0; i < node_count; i++) {
2401 if (i == 0) {
2402 axial_vector = nodes[i + 1] - nodes[i];
2403 float mag = axial_vector.magnitude();
2404 if (mag < 1e-6f) {
2405 axial_vector = make_vec3(0, 0, 1);
2406 } else {
2407 axial_vector = axial_vector / mag;
2408 }
2409 if (fabs(axial_vector * initial_radial) > 0.95f) {
2410 initial_radial = vec3(0.0f, 1.0f, 0.0f);
2411 }
2412 if (fabs(axial_vector.z) > 0.95f) {
2413 initial_radial = vec3(1.0f, 0.0f, 0.0f);
2414 }
2415 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2416 } else {
2417 if (i == node_count - 1) {
2418 axial_vector = nodes[i] - nodes[i - 1];
2419 } else {
2420 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2421 }
2422 float mag = axial_vector.magnitude();
2423 if (mag < 1e-6f) {
2424 axial_vector = make_vec3(0, 0, 1);
2425 } else {
2426 axial_vector = axial_vector / mag;
2427 }
2428
2429 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2430 if (rotation_axis.magnitude() > 1e-5) {
2431 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2432 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2433 } else {
2434 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2435 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2436 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2437 }
2438 if (fabs(axial_vector.z) > 0.95f) {
2439 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2440 }
2441 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2442 }
2443 }
2444
2445 previous_axial_vector = axial_vector;
2446
2447 vec3 radial_dir = previous_radial_dir;
2448 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2449 orthogonal_dir.normalize();
2450
2451 for (int j = 0; j < radial_subdivisions + 1; j++) {
2452 triangle_vertices[i][j] = nodes[i] + cfact[j] * radius_clamped[i] * radial_dir + sfact[j] * radius_clamped[i] * orthogonal_dir;
2453 }
2454 }
2455}
2456
2457void Tube::updateTriangleVertices() const {
2458 int ii = 0;
2459 for (int j = 0; j < subdiv; j++) {
2460 for (int i = 0; i < nodes.size() - 1; i++) {
2461 vec3 v0 = triangle_vertices.at(i).at(j);
2462 vec3 v1 = triangle_vertices.at(i + 1).at(j + 1);
2463 vec3 v2 = triangle_vertices.at(i).at(j + 1);
2464 context->setTriangleVertices(UUIDs.at(ii), v0, v1, v2);
2465
2466 v0 = triangle_vertices.at(i).at(j);
2467 v1 = triangle_vertices.at(i + 1).at(j);
2468 v2 = triangle_vertices.at(i + 1).at(j + 1);
2469
2470 context->setTriangleVertices(UUIDs.at(ii + 1), v0, v1, v2);
2471
2472 ii += 2;
2473 }
2474 }
2475}
2476
2477// ============== BOX CLASS METHOD DEFINITIONS ==============
2478
2479Box::Box(uint a_OID, const std::vector<uint> &a_UUIDs, const int3 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2480 makeIdentityMatrix(transform);
2481
2482 OID = a_OID;
2484 UUIDs = a_UUIDs;
2485 subdiv = a_subdiv;
2486 texturefile = a_texturefile;
2487 context = a_context;
2488}
2489
2491 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0), nz(0, 0, 1);
2492
2493 vec3 n0_T, nx_T, ny_T, nz_T;
2494
2495 vecmult(transform, n0, n0_T);
2496 vecmult(transform, nx, nx_T);
2497 vecmult(transform, ny, ny_T);
2498 vecmult(transform, nz, nz_T);
2499
2500 float x = (nx_T - n0_T).magnitude();
2501 float y = (ny_T - n0_T).magnitude();
2502 float z = (nz_T - n0_T).magnitude();
2503
2504 return make_vec3(x, y, z);
2505}
2506
2508 vec3 center;
2509 vec3 Y;
2510 Y.x = 0.f;
2511 Y.y = 0.f;
2512 Y.z = 0.f;
2513
2514 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2515 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2516 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2517
2518 return center;
2519}
2520
2522 return subdiv;
2523}
2524
2526 subdiv = a_subdiv;
2527}
2528
2529float Box::getVolume() const {
2530 const vec3 &size = getSize();
2531 return size.x * size.y * size.z;
2532}
2533
2534// ============== DISK CLASS METHOD DEFINITIONS ==============
2535
2536Disk::Disk(uint a_OID, const std::vector<uint> &a_UUIDs, int2 a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2537 makeIdentityMatrix(transform);
2538
2539 OID = a_OID;
2541 UUIDs = a_UUIDs;
2542 subdiv = a_subdiv;
2543 texturefile = a_texturefile;
2544 context = a_context;
2545}
2546
2548 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0);
2549 vec3 n0_T, nx_T, ny_T;
2550
2551 vecmult(transform, n0, n0_T);
2552 vecmult(transform, nx, nx_T);
2553 vecmult(transform, ny, ny_T);
2554
2555 float x = (nx_T - n0_T).magnitude();
2556 float y = (ny_T - n0_T).magnitude();
2557
2558 return make_vec2(x, y);
2559}
2560
2562 vec3 center;
2563 vec3 Y;
2564 Y.x = 0.f;
2565 Y.y = 0.f;
2566 Y.z = 0.f;
2567
2568 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2569 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2570 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2571
2572 return center;
2573}
2574
2576 return subdiv;
2577}
2578
2580 subdiv = a_subdiv;
2581}
2582
2583// ============== POLYMESH CLASS METHOD DEFINITIONS ==============
2584
2585Polymesh::Polymesh(uint a_OID, const std::vector<uint> &a_UUIDs, const char *a_texturefile, helios::Context *a_context) {
2586 makeIdentityMatrix(transform);
2587
2588 OID = a_OID;
2590 UUIDs = a_UUIDs;
2591 texturefile = a_texturefile;
2592 context = a_context;
2593}
2594
2595float Polymesh::getVolume() const {
2596 float volume = 0.f;
2597 for (uint UUID: UUIDs) {
2598 if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_TRIANGLE) {
2599 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2600 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2601 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2602 volume += (1.f / 6.f) * v0 * cross(v1, v2);
2603 } else if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_PATCH) {
2604 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2605 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2606 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2607 const vec3 &v3 = context->getTriangleVertex(UUID, 3);
2608 volume += (1.f / 6.f) * v0 * cross(v1, v2) + (1.f / 6.f) * v0 * cross(v2, v3);
2609 }
2610 }
2611 return std::abs(volume);
2612}
2613
2614// ============== CONE CLASS METHOD DEFINITIONS ==============
2615
2616Cone::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) {
2617 makeIdentityMatrix(transform);
2618
2619 OID = a_OID;
2621 UUIDs = a_UUIDs;
2622 subdiv = a_subdiv;
2623 texturefile = a_texturefile;
2624 context = a_context;
2625 nodes = {a_node0, a_node1};
2626 radii = {a_radius0, a_radius1};
2627}
2628
2629std::vector<helios::vec3> Cone::getNodeCoordinates() const {
2630 std::vector<vec3> nodes_T;
2631 nodes_T.resize(2);
2632
2633 for (int i = 0; i < 2; i++) {
2634 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];
2635 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];
2636 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];
2637 }
2638
2639 return nodes_T;
2640}
2641
2643 if (node_index < 0 || node_index > 1) {
2644 helios_runtime_error("ERROR (Cone::getNodeCoordinate): node number must be 0 or 1.");
2645 }
2646
2647 vec3 node_T;
2648
2649 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];
2650 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];
2651 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];
2652
2653 return node_T;
2654}
2655
2656std::vector<float> Cone::getNodeRadii() const {
2657 return radii;
2658}
2659
2660float Cone::getNodeRadius(int node_index) const {
2661 if (node_index < 0 || node_index > 1) {
2662 helios_runtime_error("ERROR (Cone::getNodeRadius): node number must be 0 or 1.");
2663 }
2664
2665 return radii.at(node_index);
2666}
2667
2669 return subdiv;
2670}
2671
2673 subdiv = a_subdiv;
2674}
2675
2677 std::vector<vec3> nodes_T;
2678 nodes_T.resize(2);
2679
2680 for (uint i = 0; i < 2; i++) {
2681 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];
2682 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];
2683 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];
2684 }
2685
2686 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);
2687 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2688 axis_unit_vector = axis_unit_vector / length;
2689
2690 return axis_unit_vector;
2691}
2692
2693float Cone::getLength() const {
2694 std::vector<vec3> nodes_T;
2695 nodes_T.resize(2);
2696
2697 for (uint i = 0; i < 2; i++) {
2698 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];
2699 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];
2700 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];
2701 }
2702
2703 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);
2704 return length;
2705}
2706
2707void Cone::scaleLength(float S) {
2708 // get the nodes and radii of the nodes with transformation matrix applied
2709 const std::vector<helios::vec3> &nodes_T = getNodeCoordinates();
2710 const std::vector<float> &radii_T = getNodeRadii();
2711
2712 // calculate the transformed axis unit vector of the cone
2713 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);
2714 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2715 axis_unit_vector = axis_unit_vector / length;
2716
2717 // translate node 0 back to origin
2718 translate(-1.0 * nodes_T.at(0));
2719
2720 // rotate the cone to align with z axis
2721 helios::vec3 z_axis = make_vec3(0, 0, 1);
2722 // get the axis about which to rotate
2723 vec3 ra = cross(z_axis, axis_unit_vector);
2724 // get the angle to rotate
2725 float dot = axis_unit_vector.x * z_axis.x + axis_unit_vector.y * z_axis.y + axis_unit_vector.z * z_axis.z;
2726 float angle = acos_safe(dot);
2727
2728 // 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)
2729 if (angle != 0.f) {
2730 rotate(-1 * angle, ra);
2731 }
2732
2733 // scale the cone in the z (length) dimension
2734 float T[16], T_prim[16];
2735 makeScaleMatrix(make_vec3(1, 1, S), T);
2736 matmult(T, transform, transform);
2737 for (uint UUID: UUIDs) {
2738 if (context->doesPrimitiveExist(UUID)) {
2739 context->getPrimitiveTransformationMatrix(UUID, T_prim);
2740 matmult(T, T_prim, T_prim);
2741 context->setPrimitiveTransformationMatrix(UUID, T_prim);
2742 }
2743 }
2744
2745 // rotate back
2746 if (angle != 0.f) {
2747 rotate(angle, ra);
2748 }
2749
2750 // translate back
2751 translate(nodes_T.at(0));
2752}
2753
2754void Cone::scaleGirth(float S) {
2755 // get the nodes and radii of the nodes with transformation matrix applied
2756 const std::vector<helios::vec3> &nodes_T = getNodeCoordinates();
2757 const std::vector<float> &radii_T = getNodeRadii();
2758
2759 // calculate the transformed axis unit vector of the cone
2760 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);
2761 axis_unit_vector.normalize();
2762
2763 // translate node 0 back to origin
2764 translate(-1.0 * nodes_T.at(0));
2765 // rotate the cone to align with z axis
2766 helios::vec3 z_axis = make_vec3(0, 0, 1);
2767 // get the axis about which to rotate
2768 vec3 ra = cross(z_axis, axis_unit_vector);
2769 // get the angle to rotate
2770 float dot = axis_unit_vector * z_axis;
2771 float angle = acos_safe(dot);
2772 // 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)
2773 if (angle != 0.f) {
2774 rotate(-1 * angle, ra);
2775 }
2776
2777 // scale the cone in the x and y dimensions
2778 context->scaleObject(OID, make_vec3(S, S, 1));
2779
2780
2781 // rotate back
2782 if (angle != 0.f) {
2783 rotate(angle, ra);
2784 }
2785
2786 // translate back
2787 translate(nodes_T.at(0));
2788
2789 radii.at(0) *= S;
2790 radii.at(1) *= S;
2791}
2792
2793float Cone::getVolume() const {
2794 float r0 = getNodeRadius(0);
2795 float r1 = getNodeRadius(1);
2796 float h = getLength();
2797
2798 return PI_F * h / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
2799}