1.3.49
 
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 currentUUID++;
417 UUID.push_back(currentUUID - 1);
418 }
419 }
420
421 auto *tile_new = (new Tile(currentObjectID, UUID, subdiv, texturefile, this));
422
423 float T[16], S[16], R[16];
424
425 float transform[16];
426 tile_new->getTransformationMatrix(transform);
427
428 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), S);
429 matmult(S, transform, transform);
430
431 makeRotationMatrix(-rotation.elevation, "x", R);
432 matmult(R, transform, transform);
433 makeRotationMatrix(-rotation.azimuth, "z", R);
434 matmult(R, transform, transform);
435
436 makeTranslationMatrix(center, T);
437 matmult(T, transform, transform);
438 tile_new->setTransformationMatrix(transform);
439
440 for (uint p: UUID) {
441 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
442 }
443
444 tile_new->object_origin = center;
445
446 objects[currentObjectID] = tile_new;
447 currentObjectID++;
448 return currentObjectID - 1;
449}
450
451uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius) {
452 uint node_count = nodes.size();
453
454 std::vector<RGBcolor> color(node_count);
455
456 for (uint i = 0; i < node_count; i++) {
457 color.at(i) = make_RGBcolor(0.f, 0.75f, 0.f); // Default color is green
458 }
459
460 return addTubeObject(radial_subdivisions, nodes, radius, color);
461}
462
463uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const std::vector<RGBcolor> &color) {
464 const uint node_count = nodes.size();
465
466 if (node_count == 0) {
467 helios_runtime_error("ERROR (Context::addTubeObject): Node and radius arrays are empty.");
468 } else if (node_count != radius.size()) {
469 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `radius' arguments must agree.");
470 } else if (node_count != color.size()) {
471 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `color' arguments must agree.");
472 }
473
474 vec3 axial_vector;
475 std::vector<float> cfact(radial_subdivisions + 1);
476 std::vector<float> sfact(radial_subdivisions + 1);
477 std::vector<std::vector<vec3>> triangle_vertices;
478 resize_vector(triangle_vertices, radial_subdivisions + 1, node_count);
479
480 // Initialize trigonometric factors for circle points
481 for (int j = 0; j < radial_subdivisions + 1; j++) {
482 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
483 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
484 }
485
486 vec3 initial_radial(1.0f, 0.0f, 0.0f);
487 vec3 previous_axial_vector;
488 vec3 previous_radial_dir;
489
490 for (int i = 0; i < node_count; i++) { // Looping over tube segments
491 if (radius.at(i) < 0) {
492 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
493 }
494
495 if (i == 0) {
496 axial_vector = nodes[i + 1] - nodes[i];
497 float mag = axial_vector.magnitude();
498 if (mag < 1e-6f) {
499 axial_vector = make_vec3(0, 0, 1);
500 } else {
501 axial_vector = axial_vector / mag;
502 }
503 if (fabs(axial_vector * initial_radial) > 0.95f) {
504 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
505 }
506 // Also handle nearly vertical axes
507 if (fabs(axial_vector.z) > 0.95f) {
508 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
509 }
510 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
511 } else {
512 if (i == node_count - 1) {
513 axial_vector = nodes[i] - nodes[i - 1];
514 } else {
515 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
516 }
517 float mag = axial_vector.magnitude();
518 if (mag < 1e-6f) {
519 axial_vector = make_vec3(0, 0, 1);
520 } else {
521 axial_vector = axial_vector / mag;
522 }
523
524 // Calculate radial direction using parallel transport
525 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
526 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
527 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
528 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
529 } else {
530 // Vectors are nearly parallel, use robust fallback
531 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
532 if (fabs(axial_vector * fallback_radial) > 0.95f) {
533 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
534 }
535 if (fabs(axial_vector.z) > 0.95f) {
536 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
537 }
538 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
539 }
540 }
541
542 previous_axial_vector = axial_vector;
543
544 vec3 radial_dir = previous_radial_dir;
545 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
546 orthogonal_dir.normalize();
547
548 for (int j = 0; j < radial_subdivisions + 1; j++) {
549 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
550 triangle_vertices[i][j] = nodes[i] + normal;
551 }
552 }
553
554
555 std::vector<uint> UUIDs(2 * (node_count - 1) * radial_subdivisions);
556 vec3 v0, v1, v2;
557
558 int ii = 0;
559 for (int j = 0; j < radial_subdivisions; j++) {
560 for (int i = 0; i < node_count - 1; i++) {
561 v0 = triangle_vertices[i][j];
562 v1 = triangle_vertices[i + 1][j + 1];
563 v2 = triangle_vertices[i][j + 1];
564
565 UUIDs.at(ii) = addTriangle(v0, v1, v2, color.at(i));
566
567 v0 = triangle_vertices[i][j];
568 v1 = triangle_vertices[i + 1][j];
569 v2 = triangle_vertices[i + 1][j + 1];
570
571 UUIDs.at(ii + 1) = addTriangle(v0, v1, v2, color.at(i));
572
573 ii += 2;
574 }
575 }
576
577 auto *tube_new = (new Tube(currentObjectID, UUIDs, nodes, radius, color, triangle_vertices, radial_subdivisions, "", this));
578
579 for (uint p: UUIDs) {
580 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
581 }
582
583 objects[currentObjectID] = tube_new;
584 currentObjectID++;
585
586 uint objID = currentObjectID - 1;
587 tube_new->object_origin = getObjectCenter(objID);
588
589 return objID;
590}
591
592uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const char *texturefile) {
593 size_t node_count = nodes.size();
594 std::vector<float> textureuv_ufrac(node_count);
595 for (int i = 0; i < node_count; i++) {
596 textureuv_ufrac.at(i) = float(i) / float(node_count - 1);
597 }
598
599 return addTubeObject(radial_subdivisions, nodes, radius, texturefile, textureuv_ufrac);
600}
601
602uint Context::addTubeObject(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const char *texturefile, const std::vector<float> &textureuv_ufrac) {
603 if (!validateTextureFileExtenstion(texturefile)) {
604 helios_runtime_error("ERROR (Context::addTubeObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
605 } else if (!doesTextureFileExist(texturefile)) {
606 helios_runtime_error("ERROR (Context::addTubeObject): Texture file " + std::string(texturefile) + " does not exist.");
607 }
608
609 const uint node_count = nodes.size();
610
611 if (node_count == 0) {
612 helios_runtime_error("ERROR (Context::addTubeObject): Node and radius arrays are empty.");
613 } else if (node_count != radius.size()) {
614 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `radius' arguments must agree.");
615 } else if (node_count != textureuv_ufrac.size()) {
616 helios_runtime_error("ERROR (Context::addTubeObject): Size of `nodes' and `textureuv_ufrac' arguments must agree.");
617 }
618
619 vec3 axial_vector;
620 std::vector<float> cfact(radial_subdivisions + 1);
621 std::vector<float> sfact(radial_subdivisions + 1);
622 std::vector<std::vector<vec3>> triangle_vertices;
623 resize_vector(triangle_vertices, radial_subdivisions + 1, node_count);
624 std::vector<std::vector<vec2>> uv;
625 resize_vector(uv, radial_subdivisions + 1, node_count);
626
627 // Initialize trigonometric factors for circle points
628 for (int j = 0; j < radial_subdivisions + 1; j++) {
629 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
630 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
631 }
632
633 vec3 initial_radial(1.0f, 0.0f, 0.0f);
634 vec3 previous_axial_vector;
635 vec3 previous_radial_dir;
636
637 for (int i = 0; i < node_count; i++) { // Looping over tube segments
638 if (radius.at(i) < 0) {
639 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
640 }
641
642 if (i == 0) {
643 axial_vector = nodes[i + 1] - nodes[i];
644 float mag = axial_vector.magnitude();
645 if (mag < 1e-6f) {
646 axial_vector = make_vec3(0, 0, 1);
647 } else {
648 axial_vector = axial_vector / mag;
649 }
650 if (fabs(axial_vector * initial_radial) > 0.95f) {
651 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
652 }
653 // Also handle nearly vertical axes
654 if (fabs(axial_vector.z) > 0.95f) {
655 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
656 }
657 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
658 } else {
659 if (i == node_count - 1) {
660 axial_vector = nodes[i] - nodes[i - 1];
661 } else {
662 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
663 }
664 float mag = axial_vector.magnitude();
665 if (mag < 1e-6f) {
666 axial_vector = make_vec3(0, 0, 1);
667 } else {
668 axial_vector = axial_vector / mag;
669 }
670
671 // Calculate radial direction using parallel transport
672 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
673 if (rotation_axis.magnitude() > 1e-5) {
674 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
675 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
676 } else {
677 // Vectors are nearly parallel, use robust fallback
678 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
679 if (fabs(axial_vector * fallback_radial) > 0.95f) {
680 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
681 }
682 if (fabs(axial_vector.z) > 0.95f) {
683 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
684 }
685 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
686 }
687 }
688
689 previous_axial_vector = axial_vector;
690
691 vec3 radial_dir = previous_radial_dir;
692 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
693 orthogonal_dir.normalize();
694
695 for (int j = 0; j < radial_subdivisions + 1; j++) {
696 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
697 triangle_vertices[i][j] = nodes[i] + normal;
698
699 uv[i][j].x = textureuv_ufrac[i];
700 uv[i][j].y = float(j) / float(radial_subdivisions);
701 }
702 }
703
704 std::vector<uint> UUIDs;
705 UUIDs.reserve(2 * (node_count - 1) * radial_subdivisions); // Reserve expected capacity
706 vec3 v0, v1, v2;
707 vec2 uv0, uv1, uv2;
708 for (int j = 0; j < radial_subdivisions; j++) {
709 for (int i = 0; i < node_count - 1; i++) {
710 v0 = triangle_vertices[i][j];
711 v1 = triangle_vertices[i + 1][j + 1];
712 v2 = triangle_vertices[i][j + 1];
713
714 uv0 = uv[i][j];
715 uv1 = uv[i + 1][j + 1];
716 uv2 = uv[i][j + 1];
717
718 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
719 if (getPrimitiveArea(triangle_uuid) > 0) {
720 UUIDs.push_back(triangle_uuid);
721 } else {
722 deletePrimitive(triangle_uuid);
723 }
724
725 v0 = triangle_vertices[i][j];
726 v1 = triangle_vertices[i + 1][j];
727 v2 = triangle_vertices[i + 1][j + 1];
728
729 uv0 = uv[i][j];
730 uv1 = uv[i + 1][j];
731 uv2 = uv[i + 1][j + 1];
732
733 uint triangle_uuid2 = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
734 if (getPrimitiveArea(triangle_uuid2) > 0) {
735 UUIDs.push_back(triangle_uuid2);
736 } else {
737 deletePrimitive(triangle_uuid2);
738 }
739 }
740 }
741
742 std::vector<RGBcolor> colors(nodes.size());
743
744 auto *tube_new = (new Tube(currentObjectID, UUIDs, nodes, radius, colors, triangle_vertices, radial_subdivisions, texturefile, this));
745
746 for (uint p: UUIDs) {
747 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
748 }
749
750 objects[currentObjectID] = tube_new;
751 currentObjectID++;
752
753 uint objID = currentObjectID - 1;
754 tube_new->object_origin = getObjectCenter(objID);
755
756 return objID;
757}
758
759uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv) {
760 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
761
762 return addBoxObject(center, size, subdiv, color, false);
763}
764
765uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color) {
766 return addBoxObject(center, size, subdiv, color, false);
767}
768
769uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const char *texturefile) {
770 return addBoxObject(center, size, subdiv, texturefile, false);
771}
772
773uint Context::addBoxObject(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color, bool reverse_normals) {
774 if (size.x <= 0 || size.y <= 0 || size.z <= 0) {
775 helios_runtime_error("ERROR (Context::addBoxObject): Size of box must be positive.");
776 } else if (subdiv.x < 1 || subdiv.y < 1 || subdiv.z < 1) {
777 helios_runtime_error("ERROR (Context::addBoxObject): Number of box subdivisions must be positive.");
778 }
779
780 std::vector<uint> UUID;
781 UUID.reserve(2 * (subdiv.z * (subdiv.x + subdiv.y) + subdiv.x * subdiv.y));
782
783 vec3 subsize;
784 subsize.x = size.x / float(subdiv.x);
785 subsize.y = size.y / float(subdiv.y);
786 subsize.z = size.z / float(subdiv.z);
787
788 vec3 subcenter;
789 std::vector<uint> U, U_copy;
790
791 if (reverse_normals) { // normals point inward
792
793 // x-z faces (vertical)
794
795 // right
796 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
797 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
798 UUID.insert(UUID.end(), U.begin(), U.end());
799
800 // left
801 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
802 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, 0), make_int2(subdiv.x, subdiv.z), color);
803 UUID.insert(UUID.end(), U.begin(), U.end());
804
805 // y-z faces (vertical)
806
807 // front
808 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
809 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);
810 UUID.insert(UUID.end(), U.begin(), U.end());
811
812 // back
813 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
814 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);
815 UUID.insert(UUID.end(), U.begin(), U.end());
816
817 // x-y faces (horizontal)
818
819 // top
820 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
821 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
822 UUID.insert(UUID.end(), U.begin(), U.end());
823
824 // bottom
825 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
826 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), color);
827 UUID.insert(UUID.end(), U.begin(), U.end());
828 } else { // normals point outward
829
830 // x-z faces (vertical)
831
832 // right
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 // left
838 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
839 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5f * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
840 UUID.insert(UUID.end(), U.begin(), U.end());
841
842 // y-z faces (vertical)
843
844 // front
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 // back
850 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
851 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);
852 UUID.insert(UUID.end(), U.begin(), U.end());
853
854 // x-y faces (horizontal)
855
856 // top
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
861 // bottom
862 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
863 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
864 UUID.insert(UUID.end(), U.begin(), U.end());
865 }
866
867 auto *box_new = (new Box(currentObjectID, UUID, subdiv, "", this));
868
869 float T[16], transform[16];
870 box_new->getTransformationMatrix(transform);
871
872 makeScaleMatrix(size, T);
873 matmult(T, transform, transform);
874
875 makeTranslationMatrix(center, T);
876 matmult(T, transform, transform);
877 box_new->setTransformationMatrix(transform);
878
879 box_new->setColor(color);
880
881 for (uint p: UUID) {
882 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
883 }
884
885 box_new->object_origin = center;
886
887 objects[currentObjectID] = box_new;
888 currentObjectID++;
889 return currentObjectID - 1;
890}
891
892uint Context::addBoxObject(vec3 center, const vec3 &size, const int3 &subdiv, const char *texturefile, bool reverse_normals) {
893 if (!validateTextureFileExtenstion(texturefile)) {
894 helios_runtime_error("ERROR (Context::addBoxObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
895 } else if (!doesTextureFileExist(texturefile)) {
896 helios_runtime_error("ERROR (Context::addBoxObject): Texture file " + std::string(texturefile) + " does not exist.");
897 }
898
899 std::vector<uint> UUID;
900
901 vec3 subsize;
902 subsize.x = size.x / float(subdiv.x);
903 subsize.y = size.y / float(subdiv.y);
904 subsize.z = size.z / float(subdiv.z);
905
906 vec3 subcenter;
907 std::vector<uint> U, U_copy;
908
909 if (reverse_normals) { // normals point inward
910
911 // x-z faces (vertical)
912
913 // right
914 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
915 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
916 UUID.insert(UUID.end(), U.begin(), U.end());
917
918 // left
919 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
920 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), texturefile);
921 UUID.insert(UUID.end(), U.begin(), U.end());
922
923 // y-z faces (vertical)
924
925 // front
926 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
927 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);
928 UUID.insert(UUID.end(), U.begin(), U.end());
929
930 // back
931 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
932 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);
933 UUID.insert(UUID.end(), U.begin(), U.end());
934
935 // x-y faces (horizontal)
936
937 // top
938 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
939 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
940 UUID.insert(UUID.end(), U.begin(), U.end());
941
942 // bottom
943 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
944 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), texturefile);
945 UUID.insert(UUID.end(), U.begin(), U.end());
946 } else { // normals point outward
947
948 // x-z faces (vertical)
949
950 // right
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 // left
956 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
957 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
958 UUID.insert(UUID.end(), U.begin(), U.end());
959
960 // y-z faces (vertical)
961
962 // front
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 // back
968 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
969 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);
970 UUID.insert(UUID.end(), U.begin(), U.end());
971
972 // x-y faces (horizontal)
973
974 // top
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
979 // bottom
980 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
981 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
982 UUID.insert(UUID.end(), U.begin(), U.end());
983 }
984
985 auto *box_new = (new Box(currentObjectID, UUID, subdiv, texturefile, this));
986
987 float T[16], transform[16];
988 box_new->getTransformationMatrix(transform);
989
990 makeScaleMatrix(size, T);
991 matmult(T, transform, transform);
992
993 makeTranslationMatrix(center, T);
994 matmult(T, transform, transform);
995 box_new->setTransformationMatrix(transform);
996
997 for (uint p: UUID) {
998 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
999 }
1000
1001 box_new->object_origin = center;
1002
1003 objects[currentObjectID] = box_new;
1004 currentObjectID++;
1005 return currentObjectID - 1;
1006}
1007
1008uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size) {
1009 return addDiskObject(make_int2(Ndivs, 1), center, size, make_SphericalCoord(0, 0), make_RGBAcolor(1, 0, 0, 1));
1010}
1011
1012uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation) {
1013 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(1, 0, 0, 1));
1014}
1015
1016uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
1017 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(color, 1));
1018}
1019
1020uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
1021 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, color);
1022}
1023
1024uint Context::addDiskObject(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texture_file) {
1025 return addDiskObject(make_int2(Ndivs, 1), center, size, rotation, texture_file);
1026}
1027
1028uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
1029 return addDiskObject(Ndivs, center, size, rotation, make_RGBAcolor(color, 1));
1030}
1031
1032uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
1033 std::vector<uint> UUID(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2);
1034
1035 int i = 0;
1036 for (int r = 0; r < Ndivs.y; r++) {
1037 for (int t = 0; t < Ndivs.x; t++) {
1038 float dtheta = 2.f * PI_F / float(Ndivs.x);
1039 float theta = dtheta * float(t);
1040 float theta_plus = dtheta * float(t + 1);
1041
1042 float rx = size.x / float(Ndivs.y) * float(r);
1043 float ry = size.y / float(Ndivs.y) * float(r);
1044
1045 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
1046 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
1047
1048 if (r == 0) {
1049 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);
1050 } else {
1051 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);
1052 i++;
1053 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);
1054 }
1055 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.elevation, "y");
1056 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.azimuth, "z");
1057 getPrimitivePointer_private(UUID.at(i))->translate(center);
1058
1059 i++;
1060 }
1061 }
1062
1063 auto *disk_new = (new Disk(currentObjectID, UUID, Ndivs, "", this));
1064
1065 float T[16], transform[16];
1066 disk_new->getTransformationMatrix(transform);
1067
1068 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), T);
1069 matmult(T, transform, transform);
1070
1071 makeTranslationMatrix(center, T);
1072 matmult(T, transform, transform);
1073 disk_new->setTransformationMatrix(transform);
1074
1075 disk_new->setColor(color);
1076
1077 for (uint p: UUID) {
1078 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1079 }
1080
1081 disk_new->object_origin = center;
1082
1083 objects[currentObjectID] = disk_new;
1084 currentObjectID++;
1085 return currentObjectID - 1;
1086}
1087
1088uint Context::addDiskObject(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texturefile) {
1089 if (!validateTextureFileExtenstion(texturefile)) {
1090 helios_runtime_error("ERROR (Context::addDiskObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1091 } else if (!doesTextureFileExist(texturefile)) {
1092 helios_runtime_error("ERROR (Context::addDiskObject): Texture file " + std::string(texturefile) + " does not exist.");
1093 }
1094
1095 std::vector<uint> UUID;
1096 UUID.reserve(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2); // Reserve expected capacity
1097 for (int r = 0; r < Ndivs.y; r++) {
1098 for (int t = 0; t < Ndivs.x; t++) {
1099 float dtheta = 2.f * PI_F / float(Ndivs.x);
1100 float theta = dtheta * float(t);
1101 float theta_plus = dtheta * float(t + 1);
1102
1103 float rx = size.x / float(Ndivs.y) * float(r);
1104 float ry = size.y / float(Ndivs.y) * float(r);
1105 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
1106 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
1107
1108 if (r == 0) {
1109 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),
1110 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)),
1111 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
1112 if (getPrimitiveArea(triangle_uuid) > 0) {
1113 UUID.push_back(triangle_uuid);
1114 } else {
1115 deletePrimitive(triangle_uuid);
1116 continue;
1117 }
1118 } else {
1119 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,
1120 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)),
1121 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)));
1122 if (getPrimitiveArea(triangle_uuid1) > 0) {
1123 UUID.push_back(triangle_uuid1);
1124 } else {
1125 deletePrimitive(triangle_uuid1);
1126 }
1127
1128 uint triangle_uuid2 =
1129 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,
1130 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)),
1131 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
1132 if (getPrimitiveArea(triangle_uuid2) > 0) {
1133 UUID.push_back(triangle_uuid2);
1134 } else {
1135 deletePrimitive(triangle_uuid2);
1136 continue;
1137 }
1138 }
1139 // Apply transformations to all valid triangles added in this iteration
1140 size_t start_idx = UUID.size() - (r == 0 ? 1 : 2);
1141 for (size_t uuid_idx = start_idx; uuid_idx < UUID.size(); uuid_idx++) {
1142 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.elevation, "y");
1143 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.azimuth, "z");
1144 getPrimitivePointer_private(UUID.at(uuid_idx))->translate(center);
1145 }
1146 }
1147 }
1148
1149 auto *disk_new = (new Disk(currentObjectID, UUID, Ndivs, texturefile, this));
1150
1151 float T[16], transform[16];
1152 disk_new->getTransformationMatrix(transform);
1153
1154 makeScaleMatrix(make_vec3(size.x, size.y, 1.f), T);
1155 matmult(T, transform, transform);
1156
1157 makeTranslationMatrix(center, T);
1158 matmult(T, transform, transform);
1159 disk_new->setTransformationMatrix(transform);
1160
1161 for (uint p: UUID) {
1162 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1163 }
1164
1165 disk_new->object_origin = center;
1166
1167 objects[currentObjectID] = disk_new;
1168 currentObjectID++;
1169 return currentObjectID - 1;
1170}
1171
1172uint Context::addPolymeshObject(const std::vector<uint> &UUIDs) {
1173 if (UUIDs.empty()) {
1174 helios_runtime_error("ERROR (Context::addPolymeshObject): UUIDs array is empty. Cannot create polymesh object.");
1175 } else if (!doesPrimitiveExist(UUIDs)) {
1176 helios_runtime_error("ERROR (Context::addPolymeshObject): One or more of the provided UUIDs does not exist. Cannot create polymesh object.");
1177 }
1178
1179 // Check whether primitives already belong to another object
1180 std::vector<uint> UUIDs_polymesh;
1181 UUIDs_polymesh.reserve(UUIDs.size());
1182 size_t skipped_UUIDs = 0;
1183 for (uint UUID: UUIDs) {
1184 if (getPrimitivePointer_private(UUID)->getParentObjectID() != 0) {
1185 skipped_UUIDs++;
1186 } else {
1187 UUIDs_polymesh.push_back(UUID);
1188 }
1189 }
1190 if (skipped_UUIDs > 0) {
1191 std::cerr << "WARNING (Context::addPolymeshObject): " << skipped_UUIDs << " primitives were not added to polymesh object because they already belong to another object." << std::endl;
1192 }
1193
1194 auto *polymesh_new = (new Polymesh(currentObjectID, UUIDs_polymesh, "", this));
1195
1196 float T[16], transform[16];
1197 polymesh_new->getTransformationMatrix(transform);
1198
1199 makeTranslationMatrix(getPrimitivePointer_private(UUIDs_polymesh.front())->getVertices().front(), T);
1200 matmult(T, transform, transform);
1201 polymesh_new->setTransformationMatrix(transform);
1202
1203 for (uint UUID: UUIDs_polymesh) {
1204 getPrimitivePointer_private(UUID)->setParentObjectID(currentObjectID);
1205 }
1206
1207 objects[currentObjectID] = polymesh_new;
1208 currentObjectID++;
1209
1210 uint objID = currentObjectID - 1;
1211 polymesh_new->object_origin = getObjectCenter(objID);
1212
1213 return objID;
1214}
1215
1216uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1) {
1217 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
1218 return addConeObject(Ndivs, node0, node1, radius0, radius1, color);
1219}
1220
1221uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const RGBcolor &color) {
1222 const std::vector nodes{node0, node1};
1223 const std::vector radii{radius0, radius1};
1224
1225 vec3 convec;
1226 std::vector<float> cfact(Ndivs + 1);
1227 std::vector<float> sfact(Ndivs + 1);
1228 std::vector<std::vector<vec3>> xyz(Ndivs + 1);
1229 std::vector<std::vector<vec3>> normal(Ndivs + 1);
1230
1231 for (uint j = 0; j < Ndivs + 1; j++) {
1232 xyz.at(j).resize(2);
1233 normal.at(j).resize(2);
1234 }
1235 vec3 nvec(0.1817f, 0.6198f, 0.7634f); // random vector to get things going
1236
1237 for (int j = 0; j < Ndivs + 1; j++) {
1238 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
1239 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
1240 }
1241
1242 for (int i = 0; i < 2; i++) {
1243 vec3 vec;
1244 // looping over cone segments
1245
1246 if (i == 0) {
1247 vec.x = nodes[i + 1].x - nodes[i].x;
1248 vec.y = nodes[i + 1].y - nodes[i].y;
1249 vec.z = nodes[i + 1].z - nodes[i].z;
1250 } else if (i == 1) {
1251 vec.x = nodes[i].x - nodes[i - 1].x;
1252 vec.y = nodes[i].y - nodes[i - 1].y;
1253 vec.z = nodes[i].z - nodes[i - 1].z;
1254 }
1255
1256 if (vec.magnitude() < 1e-6f) {
1257 vec = make_vec3(0, 0, 1);
1258 }
1259 float norm;
1260 convec = cross(nvec, vec);
1261 norm = convec.magnitude();
1262 if (norm < 1e-6f) {
1263 convec = cross(vec, fabs(vec.x) < 0.9f ? make_vec3(1, 0, 0) : make_vec3(0, 1, 0));
1264 norm = std::max(convec.magnitude(), 1e-6f);
1265 }
1266 convec = convec / norm;
1267 nvec = cross(vec, convec);
1268 norm = nvec.magnitude();
1269 if (norm < 1e-6f) {
1270 nvec = cross(convec, vec);
1271 norm = std::max(nvec.magnitude(), 1e-6f);
1272 }
1273 nvec = nvec / norm;
1274
1275 for (int j = 0; j < Ndivs + 1; j++) {
1276 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
1277 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
1278 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
1279
1280 xyz[j][i].x = nodes[i].x + normal[j][i].x;
1281 xyz[j][i].y = nodes[i].y + normal[j][i].y;
1282 xyz[j][i].z = nodes[i].z + normal[j][i].z;
1283
1284 normal[j][i] = normal[j][i] / radii[i];
1285 }
1286 }
1287
1288 vec3 v0, v1, v2;
1289 std::vector<uint> UUID(2 * Ndivs);
1290
1291 int i = 0;
1292 for (int j = 0; j < Ndivs; j++) {
1293 v0 = xyz[j][0];
1294 v1 = xyz[j + 1][1];
1295 v2 = xyz[j + 1][0];
1296
1297 UUID.at(i) = addTriangle(v0, v1, v2, color);
1298
1299 v0 = xyz[j][0];
1300 v1 = xyz[j][1];
1301 v2 = xyz[j + 1][1];
1302
1303 UUID.at(i + 1) = addTriangle(v0, v1, v2, color);
1304
1305 i += 2;
1306 }
1307
1308 auto *cone_new = (new Cone(currentObjectID, UUID, node0, node1, radius0, radius1, Ndivs, "", this));
1309
1310 for (uint p: UUID) {
1311 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1312 }
1313
1314 cone_new->setColor(color);
1315
1316 objects[currentObjectID] = cone_new;
1317 currentObjectID++;
1318
1319 uint objID = currentObjectID - 1;
1320 cone_new->object_origin = getObjectCenter(objID);
1321
1322 return objID;
1323}
1324
1325uint Context::addConeObject(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const char *texturefile) {
1326 if (!validateTextureFileExtenstion(texturefile)) {
1327 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1328 } else if (!doesTextureFileExist(texturefile)) {
1329 helios_runtime_error("ERROR (Context::addConeObject): Texture file " + std::string(texturefile) + " does not exist.");
1330 }
1331
1332 const std::vector<helios::vec3> nodes{node0, node1};
1333 const std::vector<float> radii{radius0, radius1};
1334
1335 vec3 convec;
1336 std::vector<float> cfact(Ndivs + 1);
1337 std::vector<float> sfact(Ndivs + 1);
1338 std::vector<std::vector<vec3>> xyz, normal;
1339 std::vector<std::vector<vec2>> uv;
1340 xyz.resize(Ndivs + 1);
1341 normal.resize(Ndivs + 1);
1342 uv.resize(Ndivs + 1);
1343 for (uint j = 0; j < Ndivs + 1; j++) {
1344 xyz.at(j).resize(2);
1345 normal.at(j).resize(2);
1346 uv.at(j).resize(2);
1347 }
1348 vec3 nvec(0.f, 1.f, 0.f);
1349
1350 for (int j = 0; j < Ndivs + 1; j++) {
1351 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
1352 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
1353 }
1354
1355 for (int i = 0; i < 2; i++) {
1356 vec3 vec;
1357 // looping over cone segments
1358
1359 if (i == 0) {
1360 vec.x = nodes[i + 1].x - nodes[i].x;
1361 vec.y = nodes[i + 1].y - nodes[i].y;
1362 vec.z = nodes[i + 1].z - nodes[i].z;
1363 } else if (i == 1) {
1364 vec.x = nodes[i].x - nodes[i - 1].x;
1365 vec.y = nodes[i].y - nodes[i - 1].y;
1366 vec.z = nodes[i].z - nodes[i - 1].z;
1367 }
1368
1369 if (vec.magnitude() < 1e-6f) {
1370 vec = make_vec3(0, 0, 1);
1371 }
1372 float norm;
1373 convec = cross(nvec, vec);
1374 norm = convec.magnitude();
1375 if (norm < 1e-6f) {
1376 convec = cross(vec, fabs(vec.x) < 0.9f ? make_vec3(1, 0, 0) : make_vec3(0, 1, 0));
1377 norm = std::max(convec.magnitude(), 1e-6f);
1378 }
1379 convec = convec / norm;
1380 nvec = cross(vec, convec);
1381 norm = nvec.magnitude();
1382 if (norm < 1e-6f) {
1383 nvec = cross(convec, vec);
1384 norm = std::max(nvec.magnitude(), 1e-6f);
1385 }
1386 nvec = nvec / norm;
1387
1388 for (int j = 0; j < Ndivs + 1; j++) {
1389 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
1390 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
1391 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
1392
1393 xyz[j][i].x = nodes[i].x + normal[j][i].x;
1394 xyz[j][i].y = nodes[i].y + normal[j][i].y;
1395 xyz[j][i].z = nodes[i].z + normal[j][i].z;
1396
1397 uv[j][i].x = float(i) / float(2 - 1);
1398 uv[j][i].y = float(j) / float(Ndivs);
1399
1400 normal[j][i] = normal[j][i] / radii[i];
1401 }
1402 }
1403
1404 vec3 v0, v1, v2;
1405 vec2 uv0, uv1, uv2;
1406 std::vector<uint> UUID;
1407
1408 for (int i = 0; i < 2 - 1; i++) {
1409 for (int j = 0; j < Ndivs; j++) {
1410 v0 = xyz[j][i];
1411 v1 = xyz[j + 1][i + 1];
1412 v2 = xyz[j + 1][i];
1413
1414 uv0 = uv[j][i];
1415 uv1 = uv[j + 1][i + 1];
1416 uv2 = uv[j + 1][i];
1417
1418 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1419 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1420 if (getPrimitiveArea(triangle_uuid) > 0) {
1421 UUID.push_back(triangle_uuid);
1422 } else {
1423 deletePrimitive(triangle_uuid);
1424 }
1425 }
1426
1427 v0 = xyz[j][i];
1428 v1 = xyz[j][i + 1];
1429 v2 = xyz[j + 1][i + 1];
1430
1431 uv0 = uv[j][i];
1432 uv1 = uv[j][i + 1];
1433 uv2 = uv[j + 1][i + 1];
1434
1435 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
1436 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1437 if (getPrimitiveArea(triangle_uuid) > 0) {
1438 UUID.push_back(triangle_uuid);
1439 } else {
1440 deletePrimitive(triangle_uuid);
1441 }
1442 }
1443 }
1444 }
1445
1446 auto *cone_new = (new Cone(currentObjectID, UUID, node0, node1, radius0, radius1, Ndivs, texturefile, this));
1447
1448 for (uint p: UUID) {
1449 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1450 }
1451
1452 objects[currentObjectID] = cone_new;
1453 currentObjectID++;
1454
1455 uint objID = currentObjectID - 1;
1456 cone_new->object_origin = getObjectCenter(objID);
1457
1458 return objID;
1459}
1460
1461// ============== COMPOUND OBJECT CLASS METHOD DEFINITIONS ==============
1462
1464
1466 return OID;
1467}
1468
1470 return type;
1471}
1472
1474 return UUIDs.size();
1475}
1476
1477
1478std::vector<uint> CompoundObject::getPrimitiveUUIDs() const {
1479 return UUIDs;
1480}
1481
1483 return find(UUIDs.begin(), UUIDs.end(), UUID) != UUIDs.end();
1484}
1485
1487 vec2 xbounds, ybounds, zbounds;
1488
1489 const std::vector<uint> &U = getPrimitiveUUIDs();
1490
1491 context->getDomainBoundingBox(U, xbounds, ybounds, zbounds);
1492
1493 vec3 origin;
1494
1495 origin.x = 0.5f * (xbounds.x + xbounds.y);
1496 origin.y = 0.5f * (ybounds.x + ybounds.y);
1497 origin.z = 0.5f * (zbounds.x + zbounds.y);
1498
1499 return origin;
1500}
1501
1503 float area = 0.f;
1504
1505 for (uint UUID: UUIDs) {
1506 if (context->doesPrimitiveExist(UUID)) {
1507 area += context->getPrimitiveArea(UUID);
1508 }
1509 }
1510
1511 return area;
1512}
1513
1515 for (uint UUID: UUIDs) {
1516 if (context->doesPrimitiveExist(UUID)) {
1517 context->setPrimitiveColor(UUID, a_color);
1518 }
1519 }
1520}
1521
1523 for (uint UUID: UUIDs) {
1524 if (context->doesPrimitiveExist(UUID)) {
1525 context->setPrimitiveColor(UUID, a_color);
1526 }
1527 }
1528}
1529
1531 for (uint UUID: UUIDs) {
1532 if (context->doesPrimitiveExist(UUID)) {
1533 context->overridePrimitiveTextureColor(UUID);
1534 }
1535 }
1536}
1537
1539 for (uint UUID: UUIDs) {
1540 if (context->doesPrimitiveExist(UUID)) {
1541 context->usePrimitiveTextureColor(UUID);
1542 }
1543 }
1544}
1545
1547 if (getTextureFile().empty()) {
1548 return false;
1549 } else {
1550 return true;
1551 }
1552}
1553
1555 return texturefile;
1556}
1557
1559 if (shift == nullorigin) {
1560 return;
1561 }
1562
1563 float T[16], T_prim[16];
1564 makeTranslationMatrix(shift, T);
1565
1566 matmult(T, transform, transform);
1567
1568 for (uint UUID: UUIDs) {
1569 if (context->doesPrimitiveExist(UUID)) {
1570 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1571 matmult(T, T_prim, T_prim);
1572 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1573 }
1574 }
1575}
1576
1577void CompoundObject::rotate(float rotation_radians, const char *rotation_axis_xyz_string) {
1578 if (rotation_radians == 0) {
1579 return;
1580 }
1581
1582 if (strcmp(rotation_axis_xyz_string, "z") == 0) {
1583 float Rz[16], Rz_prim[16];
1584 makeRotationMatrix(-rotation_radians, "z", Rz);
1585 matmult(Rz, transform, transform);
1586
1587 for (uint UUID: UUIDs) {
1588 if (context->doesPrimitiveExist(UUID)) {
1589 context->getPrimitiveTransformationMatrix(UUID, Rz_prim);
1590 matmult(Rz, Rz_prim, Rz_prim);
1591 context->setPrimitiveTransformationMatrix(UUID, Rz_prim);
1592 }
1593 }
1594 } else if (strcmp(rotation_axis_xyz_string, "y") == 0) {
1595 float Ry[16], Ry_prim[16];
1596 makeRotationMatrix(rotation_radians, "y", Ry);
1597 matmult(Ry, transform, transform);
1598 for (uint UUID: UUIDs) {
1599 if (context->doesPrimitiveExist(UUID)) {
1600 context->getPrimitiveTransformationMatrix(UUID, Ry_prim);
1601 matmult(Ry, Ry_prim, Ry_prim);
1602 context->setPrimitiveTransformationMatrix(UUID, Ry_prim);
1603 }
1604 }
1605 } else if (strcmp(rotation_axis_xyz_string, "x") == 0) {
1606 float Rx[16], Rx_prim[16];
1607 makeRotationMatrix(rotation_radians, "x", Rx);
1608 matmult(Rx, transform, transform);
1609 for (uint UUID: UUIDs) {
1610 if (context->doesPrimitiveExist(UUID)) {
1611 context->getPrimitiveTransformationMatrix(UUID, Rx_prim);
1612 matmult(Rx, Rx_prim, Rx_prim);
1613 context->setPrimitiveTransformationMatrix(UUID, Rx_prim);
1614 }
1615 }
1616 } else {
1617 helios_runtime_error("ERROR (CompoundObject::rotate): Rotation axis should be one of x, y, or z.");
1618 }
1619}
1620
1621void CompoundObject::rotate(float rotation_radians, const helios::vec3 &rotation_axis_vector) {
1622 if (rotation_radians == 0) {
1623 return;
1624 }
1625
1626 float R[16], R_prim[16];
1627 makeRotationMatrix(rotation_radians, rotation_axis_vector, R);
1628 matmult(R, transform, transform);
1629
1630 for (uint UUID: UUIDs) {
1631 if (context->doesPrimitiveExist(UUID)) {
1632 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1633 matmult(R, R_prim, R_prim);
1634 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1635 }
1636 }
1637}
1638
1639void CompoundObject::rotate(float rotation_radians, const helios::vec3 &origin, const helios::vec3 &rotation_axis_vector) {
1640 if (rotation_radians == 0) {
1641 return;
1642 }
1643
1644 float R[16], R_prim[16];
1645 makeRotationMatrix(rotation_radians, origin, rotation_axis_vector, R);
1646 matmult(R, transform, transform);
1647
1648 for (uint UUID: UUIDs) {
1649 if (context->doesPrimitiveExist(UUID)) {
1650 context->getPrimitiveTransformationMatrix(UUID, R_prim);
1651 matmult(R, R_prim, R_prim);
1652 context->setPrimitiveTransformationMatrix(UUID, R_prim);
1653 }
1654 }
1655}
1656
1660
1664
1666 if (scale.x == 1.f && scale.y == 1.f && scale.z == 1.f) {
1667 return;
1668 }
1669
1670 float T[16], T_prim[16];
1671 makeScaleMatrix(scale, point, T);
1672 matmult(T, transform, transform);
1673
1674 for (uint UUID: UUIDs) {
1675 if (context->doesPrimitiveExist(UUID)) {
1676 context->getPrimitiveTransformationMatrix(UUID, T_prim);
1677 matmult(T, T_prim, T_prim);
1678 context->setPrimitiveTransformationMatrix(UUID, T_prim);
1679 }
1680 }
1681}
1682
1684 for (int i = 0; i < 16; i++) {
1685 T[i] = transform[i];
1686 }
1687}
1688
1690 for (int i = 0; i < 16; i++) {
1691 transform[i] = T[i];
1692 }
1693}
1694
1695void CompoundObject::setPrimitiveUUIDs(const std::vector<uint> &a_UUIDs) {
1696 UUIDs = a_UUIDs;
1697}
1698
1700 auto it = find(UUIDs.begin(), UUIDs.end(), UUID);
1701 if (it != UUIDs.end()) {
1702 std::iter_swap(it, UUIDs.end() - 1);
1703 UUIDs.pop_back();
1704 primitivesarecomplete = false;
1705 }
1706}
1707
1708void CompoundObject::deleteChildPrimitive(const std::vector<uint> &a_UUIDs) {
1709 for (uint UUID: a_UUIDs) {
1711 }
1712}
1713
1715 return primitivesarecomplete;
1716}
1717
1718// ============== TILE CLASS METHOD DEFINITIONS ==============
1719
1720Tile::Tile(uint a_OID, const std::vector<uint> &a_UUIDs, const int2 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1721 makeIdentityMatrix(transform);
1722
1723 OID = a_OID;
1725 UUIDs = a_UUIDs;
1726 subdiv = a_subdiv;
1727 texturefile = a_texturefile;
1728 context = a_context;
1729}
1730
1732#ifdef HELIOS_DEBUG
1733 if (objects.find(ObjID) == objects.end()) {
1734 helios_runtime_error("ERROR (Context::getTileObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
1735 }
1736#endif
1737 return dynamic_cast<Tile *>(objects.at(ObjID));
1738}
1739
1741 const std::vector<vec3> &vertices = getVertices();
1742 float l = (vertices.at(1) - vertices.at(0)).magnitude();
1743 float w = (vertices.at(3) - vertices.at(0)).magnitude();
1744 return make_vec2(l, w);
1745}
1746
1748 vec3 center;
1749 vec3 Y;
1750 Y.x = 0.f;
1751 Y.y = 0.f;
1752 Y.z = 0.f;
1753
1754 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1755 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1756 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1757
1758 return center;
1759}
1760
1761
1763 return subdiv;
1764}
1765
1767 subdiv = a_subdiv;
1768}
1769
1770
1771std::vector<helios::vec3> Tile::getVertices() const {
1772 std::vector<helios::vec3> vertices;
1773 vertices.resize(4);
1774
1775 // subcenter = make_vec3(-0.5*size.x+(float(i)+0.5)*subsize.x,-0.5*size.y+(float(j)+0.5)*subsize.y,0);
1776 // Y[0] = make_vec3( -0.5f, -0.5f, 0.f);
1777 // Y[1] = make_vec3( 0.5f, -0.5f, 0.f);
1778 // Y[2] = make_vec3( 0.5f, 0.5f, 0.f);
1779 // Y[3] = make_vec3( -0.5f, 0.5f, 0.f);
1780
1781
1782 vec3 Y[4];
1783 Y[0] = make_vec3(-0.5f, -0.5f, 0.f);
1784 Y[1] = make_vec3(0.5f, -0.5f, 0.f);
1785 Y[2] = make_vec3(0.5f, 0.5f, 0.f);
1786 Y[3] = make_vec3(-0.5f, 0.5f, 0.f);
1787
1788 for (int i = 0; i < 4; i++) {
1789 vertices[i].x = transform[0] * Y[i].x + transform[1] * Y[i].y + transform[2] * Y[i].z + transform[3];
1790 vertices[i].y = transform[4] * Y[i].x + transform[5] * Y[i].y + transform[6] * Y[i].z + transform[7];
1791 vertices[i].z = transform[8] * Y[i].x + transform[9] * Y[i].y + transform[10] * Y[i].z + transform[11];
1792 }
1793
1794 return vertices;
1795}
1796
1798 return context->getPrimitiveNormal(UUIDs.front());
1799}
1800
1801std::vector<helios::vec2> Tile::getTextureUV() const {
1802 return {make_vec2(0, 0), make_vec2(1, 0), make_vec2(1, 1), make_vec2(0, 1)};
1803}
1804
1805// ============== SPHERE CLASS METHOD DEFINITIONS ==============
1806
1807Sphere::Sphere(uint a_OID, const std::vector<uint> &a_UUIDs, uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1808 makeIdentityMatrix(transform);
1809
1810 OID = a_OID;
1812 UUIDs = a_UUIDs;
1813 subdiv = a_subdiv;
1814 texturefile = a_texturefile;
1815 context = a_context;
1816}
1817
1819#ifdef HELIOS_DEBUG
1820 if (objects.find(ObjID) == objects.end()) {
1821 helios_runtime_error("ERROR (Context::getSphereObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
1822 }
1823#endif
1824 return dynamic_cast<Sphere *>(objects.at(ObjID));
1825}
1826
1828 vec3 n0(0, 0, 0);
1829 vec3 nx(1, 0, 0);
1830 vec3 ny(0, 1, 0);
1831 vec3 nz(0, 0, 1);
1832 vec3 n0_T, nx_T, ny_T, nz_T;
1833
1834 vecmult(transform, n0, n0_T);
1835 vecmult(transform, nx, nx_T);
1836 vecmult(transform, ny, ny_T);
1837 vecmult(transform, nz, nz_T);
1838
1839 vec3 radii;
1840 radii.x = (nx_T - n0_T).magnitude();
1841 radii.y = (ny_T - n0_T).magnitude();
1842 radii.z = (nz_T - n0_T).magnitude();
1843
1844 return radii;
1845}
1846
1848 vec3 center;
1849 vec3 Y;
1850 Y.x = 0.f;
1851 Y.y = 0.f;
1852 Y.z = 0.f;
1853
1854 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
1855 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
1856 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
1857
1858 return center;
1859}
1860
1862 return subdiv;
1863}
1864
1866 subdiv = a_subdiv;
1867}
1868
1869float Sphere::getVolume() const {
1870 const vec3 &radii = getRadius();
1871 return 4.f / 3.f * PI_F * radii.x * radii.y * radii.z;
1872}
1873
1874// ============== TUBE CLASS METHOD DEFINITIONS ==============
1875
1876Tube::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,
1877 uint a_subdiv, const char *a_texturefile, helios::Context *a_context) {
1878 makeIdentityMatrix(transform);
1879
1880 OID = a_OID;
1882 UUIDs = a_UUIDs;
1883 nodes = a_nodes;
1884 radius = a_radius;
1885 colors = a_colors;
1886 triangle_vertices = a_triangle_vertices;
1887 subdiv = a_subdiv;
1888 texturefile = a_texturefile;
1889 context = a_context;
1890}
1891
1893#ifdef HELIOS_DEBUG
1894 if (objects.find(ObjID) == objects.end()) {
1895 helios_runtime_error("ERROR (Context::getTubeObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
1896 }
1897#endif
1898 return dynamic_cast<Tube *>(objects.at(ObjID));
1899}
1900
1901std::vector<helios::vec3> Tube::getNodes() const {
1902 std::vector<vec3> nodes_T;
1903 nodes_T.resize(nodes.size());
1904
1905 for (uint i = 0; i < nodes.size(); i++) {
1906 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];
1907 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];
1908 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];
1909 }
1910
1911 return nodes_T;
1912}
1913
1915 return scast<uint>(nodes.size());
1916}
1917
1918std::vector<float> Tube::getNodeRadii() const {
1919 std::vector<float> radius_T;
1920 radius_T.resize(radius.size());
1921 for (int i = 0; i < radius.size(); i++) {
1922 vec3 n0(0, 0, 0), nx(radius.at(i), 0, 0);
1923 vec3 n0_T, nx_T;
1924
1925 vecmult(transform, n0, n0_T);
1926 vecmult(transform, nx, nx_T);
1927
1928 radius_T.at(i) = (nx_T - n0_T).magnitude();
1929 }
1930 return radius_T;
1931}
1932
1933std::vector<helios::RGBcolor> Tube::getNodeColors() const {
1934 return colors;
1935}
1936
1937std::vector<std::vector<helios::vec3>> Tube::getTriangleVertices() const {
1938 return triangle_vertices;
1939}
1940
1942 return subdiv;
1943}
1944
1945float Tube::getLength() const {
1946 float length = 0.f;
1947 for (uint i = 0; i < nodes.size() - 1; i++) {
1948 length += (nodes.at(i + 1) - nodes.at(i)).magnitude();
1949 }
1950 return length;
1951}
1952
1953float Tube::getVolume() const {
1954 const std::vector<float> &radii = getNodeRadii();
1955 float volume = 0.f;
1956 for (uint i = 0; i < radii.size() - 1; i++) {
1957 float segment_length = (nodes.at(i + 1) - nodes.at(i)).magnitude();
1958 float r0 = radii.at(i);
1959 float r1 = radii.at(i + 1);
1960 volume += PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1961 }
1962
1963 return volume;
1964}
1965
1966float Tube::getSegmentVolume(uint segment_index) const {
1967 if (segment_index >= nodes.size() - 1) {
1968 helios_runtime_error("ERROR (Tube::getSegmentVolume): Segment index out of bounds.");
1969 }
1970
1971 float segment_length = (nodes.at(segment_index + 1) - nodes.at(segment_index)).magnitude();
1972 float r0 = radius.at(segment_index);
1973 float r1 = radius.at(segment_index + 1);
1974 float volume = PI_F * segment_length / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
1975
1976 return volume;
1977}
1978
1979void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const helios::RGBcolor &node_color) {
1980 //\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.
1981
1982 if (node_radius < 0) {
1983 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
1984 }
1985 node_radius = std::max((float) 1e-5, node_radius);
1986
1987 uint radial_subdivisions = subdiv;
1988
1989 vec3 axial_vector;
1990 std::vector<float> cfact(radial_subdivisions + 1);
1991 std::vector<float> sfact(radial_subdivisions + 1);
1992
1993 for (int j = 0; j < radial_subdivisions + 1; j++) {
1994 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
1995 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
1996 }
1997
1998 triangle_vertices.resize(triangle_vertices.size() + 1);
1999 triangle_vertices.back().resize(radial_subdivisions + 1);
2000
2001 nodes.push_back(node_position);
2002 radius.push_back(node_radius);
2003 colors.push_back(node_color);
2004
2005 int node_count = nodes.size();
2006
2007 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2008 vec3 previous_axial_vector;
2009 vec3 previous_radial_dir;
2010
2011 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2012 if (radius.at(i) < 0) {
2013 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2014 }
2015
2016 if (i == 0) {
2017 axial_vector = nodes[i + 1] - nodes[i];
2018 float mag = axial_vector.magnitude();
2019 if (mag < 1e-6f) {
2020 axial_vector = make_vec3(0, 0, 1);
2021 } else {
2022 axial_vector = axial_vector / mag;
2023 }
2024 if (fabs(axial_vector * initial_radial) > 0.95f) {
2025 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2026 }
2027 // Also handle nearly vertical axes
2028 if (fabs(axial_vector.z) > 0.95f) {
2029 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2030 }
2031 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2032 } else {
2033 if (i == node_count - 1) {
2034 axial_vector = nodes[i] - nodes[i - 1];
2035 } else {
2036 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2037 }
2038 float mag = axial_vector.magnitude();
2039 if (mag < 1e-6f) {
2040 axial_vector = make_vec3(0, 0, 1);
2041 } else {
2042 axial_vector = axial_vector / mag;
2043 }
2044
2045 // Calculate radial direction using parallel transport
2046 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2047 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2048 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2049 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2050 } else {
2051 // Vectors are nearly parallel, use robust fallback
2052 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2053 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2054 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2055 }
2056 if (fabs(axial_vector.z) > 0.95f) {
2057 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2058 }
2059 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2060 }
2061 // else {
2062 // // Handle the case of nearly parallel vectors
2063 // // Ensure previous_radial_dir remains orthogonal to axial_vector
2064 // previous_radial_dir = cross(axial_vector, previous_radial_dir);
2065 // if (previous_radial_dir.magnitude() < 1e-6) {
2066 // // If still degenerate, choose another orthogonal direction
2067 // previous_radial_dir = cross(axial_vector, vec3(1.0f, 0.0f, 0.0f));
2068 // }
2069 // previous_radial_dir.normalize();
2070 // }
2071 }
2072
2073 previous_axial_vector = axial_vector;
2074
2075 vec3 radial_dir = previous_radial_dir;
2076 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2077 orthogonal_dir.normalize();
2078
2079 if (i < node_count - 2) {
2080 continue;
2081 }
2082
2083 for (int j = 0; j < radial_subdivisions + 1; j++) {
2084 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2085 triangle_vertices[i][j] = nodes[i] + normal;
2086 }
2087 }
2088
2089 // add triangles for new segment
2090
2091 for (int j = 0; j < radial_subdivisions; j++) {
2092 vec3 v0 = triangle_vertices.at(1).at(j);
2093 vec3 v1 = triangle_vertices.at(1 + 1).at(j + 1);
2094 vec3 v2 = triangle_vertices.at(1).at(j + 1);
2095
2096 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2097
2098 v0 = triangle_vertices.at(1).at(j);
2099 v1 = triangle_vertices.at(1 + 1).at(j);
2100 v2 = triangle_vertices.at(1 + 1).at(j + 1);
2101
2102 UUIDs.push_back(context->addTriangle(v0, v1, v2, node_color));
2103 }
2104
2105 for (uint p: UUIDs) {
2106 context->setPrimitiveParentObjectID(p, this->OID);
2107 }
2108
2109 updateTriangleVertices();
2110}
2111
2112void Tube::appendTubeSegment(const helios::vec3 &node_position, float node_radius, const char *texturefile, const helios::vec2 &textureuv_ufrac) {
2113 //\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.
2114
2115 if (node_radius < 0) {
2116 helios_runtime_error("ERROR (Tube::appendTubeSegment): Node radius must be positive.");
2117 } else if (textureuv_ufrac.x < 0 || textureuv_ufrac.y < 0 || textureuv_ufrac.x > 1 || textureuv_ufrac.y > 1) {
2118 helios_runtime_error("ERROR (Tube::appendTubeSegment): Texture U fraction must be between 0 and 1.");
2119 }
2120 node_radius = std::max((float) 1e-5, node_radius);
2121
2122 uint radial_subdivisions = subdiv;
2123
2124 vec3 axial_vector;
2125 std::vector<float> cfact(radial_subdivisions + 1);
2126 std::vector<float> sfact(radial_subdivisions + 1);
2127
2128 for (int j = 0; j < radial_subdivisions + 1; j++) {
2129 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2130 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2131 }
2132
2133 triangle_vertices.resize(triangle_vertices.size() + 1);
2134 triangle_vertices.back().resize(radial_subdivisions + 1);
2135 std::vector<std::vector<vec2>> uv;
2136 resize_vector(uv, radial_subdivisions + 1, 2);
2137
2138 nodes.push_back(node_position);
2139 radius.push_back(node_radius);
2140 colors.push_back(RGB::black);
2141
2142 int node_count = nodes.size();
2143
2144 vec3 initial_radial(1.0f, 0.0f, 0.0f);
2145 vec3 previous_axial_vector;
2146 vec3 previous_radial_dir;
2147
2148 for (int i = 0; i < node_count; i++) { // Looping over tube segments
2149 if (radius.at(i) < 0) {
2150 helios_runtime_error("ERROR (Context::addTubeObject): Radius of tube must be positive.");
2151 }
2152
2153 if (i == 0) {
2154 axial_vector = nodes[i + 1] - nodes[i];
2155 float mag = axial_vector.magnitude();
2156 if (mag < 1e-6f) {
2157 axial_vector = make_vec3(0, 0, 1);
2158 } else {
2159 axial_vector = axial_vector / mag;
2160 }
2161 if (fabs(axial_vector * initial_radial) > 0.95f) {
2162 initial_radial = vec3(0.0f, 1.0f, 0.0f); // Avoid parallel vectors
2163 }
2164 // Also handle nearly vertical axes
2165 if (fabs(axial_vector.z) > 0.95f) {
2166 initial_radial = vec3(1.0f, 0.0f, 0.0f); // Use horizontal radial for vertical axes
2167 }
2168 previous_radial_dir = cross(axial_vector, initial_radial).normalize();
2169 } else {
2170 if (i == node_count - 1) {
2171 axial_vector = nodes[i] - nodes[i - 1];
2172 } else {
2173 axial_vector = 0.5f * ((nodes[i] - nodes[i - 1]) + (nodes[i + 1] - nodes[i]));
2174 }
2175 float mag = axial_vector.magnitude();
2176 if (mag < 1e-6f) {
2177 axial_vector = make_vec3(0, 0, 1);
2178 } else {
2179 axial_vector = axial_vector / mag;
2180 }
2181
2182 // Calculate radial direction using parallel transport
2183 vec3 rotation_axis = cross(previous_axial_vector, axial_vector);
2184 if (rotation_axis.magnitude() > 1e-5) { // More conservative threshold
2185 float angle = acos(std::clamp(previous_axial_vector * axial_vector, -1.0f, 1.0f));
2186 previous_radial_dir = rotatePointAboutLine(previous_radial_dir, nullorigin, rotation_axis, angle);
2187 } else {
2188 // Vectors are nearly parallel, use robust fallback
2189 vec3 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2190 if (fabs(axial_vector * fallback_radial) > 0.95f) {
2191 fallback_radial = vec3(0.0f, 1.0f, 0.0f);
2192 }
2193 if (fabs(axial_vector.z) > 0.95f) {
2194 fallback_radial = vec3(1.0f, 0.0f, 0.0f);
2195 }
2196 previous_radial_dir = cross(axial_vector, fallback_radial).normalize();
2197 }
2198 }
2199
2200 previous_axial_vector = axial_vector;
2201
2202 vec3 radial_dir = previous_radial_dir;
2203 vec3 orthogonal_dir = cross(radial_dir, axial_vector);
2204 orthogonal_dir.normalize();
2205
2206 if (i < node_count - 2) {
2207 continue;
2208 }
2209
2210 for (int j = 0; j < radial_subdivisions + 1; j++) {
2211 vec3 normal = cfact[j] * radius[i] * radial_dir + sfact[j] * radius[i] * orthogonal_dir;
2212 triangle_vertices[i][j] = nodes[i] + normal;
2213 }
2214 }
2215
2216 std::vector<float> ufrac{textureuv_ufrac.x, textureuv_ufrac.y};
2217 for (int i = 0; i < 2; i++) {
2218 for (int j = 0; j < radial_subdivisions + 1; j++) {
2219 uv[i][j].x = ufrac[i];
2220 uv[i][j].y = float(j) / float(radial_subdivisions);
2221 }
2222 }
2223
2224 int old_triangle_count = UUIDs.size();
2225
2226 vec3 v0, v1, v2;
2227 vec2 uv0, uv1, uv2;
2228
2229 // Add triangles for new segment
2230 for (int j = 0; j < radial_subdivisions; j++) {
2231 v0 = triangle_vertices[node_count - 2][j];
2232 v1 = triangle_vertices[node_count - 1][j + 1];
2233 v2 = triangle_vertices[node_count - 2][j + 1];
2234
2235 uv0 = uv[0][j];
2236 uv1 = uv[1][j + 1];
2237 uv2 = uv[0][j + 1];
2238
2239 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2240
2241 v0 = triangle_vertices[node_count - 2][j];
2242 v1 = triangle_vertices[node_count - 1][j];
2243 v2 = triangle_vertices[node_count - 1][j + 1];
2244
2245 uv0 = uv[0][j];
2246 uv1 = uv[1][j];
2247 uv2 = uv[1][j + 1];
2248
2249 UUIDs.push_back(context->addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2));
2250 }
2251
2252 for (uint p: UUIDs) {
2253 context->setPrimitiveParentObjectID(p, this->OID);
2254 }
2255
2256 updateTriangleVertices();
2257}
2258
2260 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2261 for (vec3 &vertex: triangle_vertices.at(segment)) {
2262 vec3 axis = vertex - nodes.at(segment);
2263
2264 float current_radius = axis.magnitude();
2265 axis = axis / current_radius;
2266
2267 vertex = nodes.at(segment) + axis * current_radius * S;
2268 }
2269 radius.at(segment) *= S;
2270 }
2271
2272 updateTriangleVertices();
2273}
2274
2275void Tube::setTubeRadii(const std::vector<float> &node_radii) {
2276 if (node_radii.size() != nodes.size()) {
2277 helios_runtime_error("ERROR (Tube::setTubeRadii): Number of radii in input vector must match number of tube nodes.");
2278 }
2279
2280 radius = node_radii;
2281
2282 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2283 for (vec3 &vertex: triangle_vertices.at(segment)) {
2284 vec3 axis = vertex - nodes.at(segment);
2285 axis.normalize();
2286
2287 vertex = nodes.at(segment) + axis * radius.at(segment);
2288 }
2289 }
2290
2291 updateTriangleVertices();
2292}
2293
2295 for (int segment = 0; segment < triangle_vertices.size() - 1; segment++) {
2296 vec3 central_axis = (nodes.at(segment + 1) - nodes.at(segment));
2297 float current_length = central_axis.magnitude();
2298 central_axis = central_axis / current_length;
2299 vec3 dL = central_axis * current_length * (1.f - S);
2300
2301 for (int downstream_segment = segment + 1; downstream_segment < triangle_vertices.size(); downstream_segment++) {
2302 nodes.at(downstream_segment) = nodes.at(downstream_segment) - dL;
2303
2304 for (int v = 0; v < triangle_vertices.at(downstream_segment).size(); v++) {
2305 triangle_vertices.at(downstream_segment).at(v) = triangle_vertices.at(downstream_segment).at(v) - dL;
2306 }
2307 }
2308 }
2309
2310 updateTriangleVertices();
2311}
2312
2313void Tube::setTubeNodes(const std::vector<helios::vec3> &node_xyz) {
2314 if (node_xyz.size() != nodes.size()) {
2315 helios_runtime_error("ERROR (Tube::setTubeNodes): Number of nodes in input vector must match number of tube nodes.");
2316 }
2317
2318 for (int segment = 0; segment < triangle_vertices.size(); segment++) {
2319 for (vec3 &vertex: triangle_vertices.at(segment)) {
2320 vertex = node_xyz.at(segment) + vertex - nodes.at(segment);
2321 }
2322 }
2323
2324 nodes = node_xyz;
2325
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 == 0) {
2335 context->deleteObject(this->OID);
2336 return;
2337 }
2338
2339 nodes.erase(nodes.begin() + node_index, nodes.end());
2340 triangle_vertices.erase(triangle_vertices.begin() + node_index, triangle_vertices.end());
2341 radius.erase(radius.begin() + node_index, radius.end());
2342 colors.erase(colors.begin() + node_index, colors.end());
2343
2344 int ii = 0;
2345 for (int i = node_index; i < nodes.size() - 1; i++) {
2346 for (int j = 0; j < subdiv; j++) {
2347 context->deletePrimitive(UUIDs.at(ii));
2348 context->deletePrimitive(UUIDs.at(ii + 1));
2349 ii += 2;
2350 }
2351 }
2352}
2353
2354void Tube::updateTriangleVertices() const {
2355 int ii = 0;
2356 for (int i = 0; i < nodes.size() - 1; i++) {
2357 for (int j = 0; j < subdiv; j++) {
2358 vec3 v0 = triangle_vertices.at(i).at(j);
2359 vec3 v1 = triangle_vertices.at(i + 1).at(j + 1);
2360 vec3 v2 = triangle_vertices.at(i).at(j + 1);
2361 context->setTriangleVertices(UUIDs.at(ii), v0, v1, v2);
2362
2363 v0 = triangle_vertices.at(i).at(j);
2364 v1 = triangle_vertices.at(i + 1).at(j);
2365 v2 = triangle_vertices.at(i + 1).at(j + 1);
2366
2367 context->setTriangleVertices(UUIDs.at(ii + 1), v0, v1, v2);
2368
2369 ii += 2;
2370 }
2371 }
2372}
2373
2374// ============== BOX CLASS METHOD DEFINITIONS ==============
2375
2376Box::Box(uint a_OID, const std::vector<uint> &a_UUIDs, const int3 &a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2377 makeIdentityMatrix(transform);
2378
2379 OID = a_OID;
2381 UUIDs = a_UUIDs;
2382 subdiv = a_subdiv;
2383 texturefile = a_texturefile;
2384 context = a_context;
2385}
2386
2388#ifdef HELIOS_DEBUG
2389 if (objects.find(ObjID) == objects.end()) {
2390 helios_runtime_error("ERROR (Context::getBoxObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
2391 }
2392#endif
2393 return dynamic_cast<Box *>(objects.at(ObjID));
2394}
2395
2397 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0), nz(0, 0, 1);
2398
2399 vec3 n0_T, nx_T, ny_T, nz_T;
2400
2401 vecmult(transform, n0, n0_T);
2402 vecmult(transform, nx, nx_T);
2403 vecmult(transform, ny, ny_T);
2404 vecmult(transform, nz, nz_T);
2405
2406 float x = (nx_T - n0_T).magnitude();
2407 float y = (ny_T - n0_T).magnitude();
2408 float z = (nz_T - n0_T).magnitude();
2409
2410 return make_vec3(x, y, z);
2411}
2412
2414 vec3 center;
2415 vec3 Y;
2416 Y.x = 0.f;
2417 Y.y = 0.f;
2418 Y.z = 0.f;
2419
2420 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2421 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2422 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2423
2424 return center;
2425}
2426
2428 return subdiv;
2429}
2430
2432 subdiv = a_subdiv;
2433}
2434
2435float Box::getVolume() const {
2436 const vec3 &size = getSize();
2437 return size.x * size.y * size.z;
2438}
2439
2440// ============== DISK CLASS METHOD DEFINITIONS ==============
2441
2442Disk::Disk(uint a_OID, const std::vector<uint> &a_UUIDs, int2 a_subdiv, const char *a_texturefile, helios::Context *a_context) {
2443 makeIdentityMatrix(transform);
2444
2445 OID = a_OID;
2447 UUIDs = a_UUIDs;
2448 subdiv = a_subdiv;
2449 texturefile = a_texturefile;
2450 context = a_context;
2451}
2452
2454#ifdef HELIOS_DEBUG
2455 if (objects.find(ObjID) == objects.end()) {
2456 helios_runtime_error("ERROR (Context::getDiskObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
2457 }
2458#endif
2459 return dynamic_cast<Disk *>(objects.at(ObjID));
2460}
2461
2463 vec3 n0(0, 0, 0), nx(1, 0, 0), ny(0, 1, 0);
2464 vec3 n0_T, nx_T, ny_T;
2465
2466 vecmult(transform, n0, n0_T);
2467 vecmult(transform, nx, nx_T);
2468 vecmult(transform, ny, ny_T);
2469
2470 float x = (nx_T - n0_T).magnitude();
2471 float y = (ny_T - n0_T).magnitude();
2472
2473 return make_vec2(x, y);
2474}
2475
2477 vec3 center;
2478 vec3 Y;
2479 Y.x = 0.f;
2480 Y.y = 0.f;
2481 Y.z = 0.f;
2482
2483 center.x = transform[0] * Y.x + transform[1] * Y.y + transform[2] * Y.z + transform[3];
2484 center.y = transform[4] * Y.x + transform[5] * Y.y + transform[6] * Y.z + transform[7];
2485 center.z = transform[8] * Y.x + transform[9] * Y.y + transform[10] * Y.z + transform[11];
2486
2487 return center;
2488}
2489
2491 return subdiv;
2492}
2493
2495 subdiv = a_subdiv;
2496}
2497
2498// ============== POLYMESH CLASS METHOD DEFINITIONS ==============
2499
2500Polymesh::Polymesh(uint a_OID, const std::vector<uint> &a_UUIDs, const char *a_texturefile, helios::Context *a_context) {
2501 makeIdentityMatrix(transform);
2502
2503 OID = a_OID;
2505 UUIDs = a_UUIDs;
2506 texturefile = a_texturefile;
2507 context = a_context;
2508}
2509
2511#ifdef HELIOS_DEBUG
2512 if (objects.find(ObjID) == objects.end()) {
2513 helios_runtime_error("ERROR (Context::getPolymeshObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
2514 }
2515#endif
2516 return dynamic_cast<Polymesh *>(objects.at(ObjID));
2517}
2518
2519float Polymesh::getVolume() const {
2520 float volume = 0.f;
2521 for (uint UUID: UUIDs) {
2522 if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_TRIANGLE) {
2523 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2524 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2525 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2526 volume += (1.f / 6.f) * v0 * cross(v1, v2);
2527 } else if (context->getPrimitiveType(UUID) == PRIMITIVE_TYPE_PATCH) {
2528 const vec3 &v0 = context->getTriangleVertex(UUID, 0);
2529 const vec3 &v1 = context->getTriangleVertex(UUID, 1);
2530 const vec3 &v2 = context->getTriangleVertex(UUID, 2);
2531 const vec3 &v3 = context->getTriangleVertex(UUID, 3);
2532 volume += (1.f / 6.f) * v0 * cross(v1, v2) + (1.f / 6.f) * v0 * cross(v2, v3);
2533 }
2534 }
2535 return std::abs(volume);
2536}
2537
2538// ============== CONE CLASS METHOD DEFINITIONS ==============
2539
2540Cone::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) {
2541 makeIdentityMatrix(transform);
2542
2543 OID = a_OID;
2545 UUIDs = a_UUIDs;
2546 subdiv = a_subdiv;
2547 texturefile = a_texturefile;
2548 context = a_context;
2549 nodes = {a_node0, a_node1};
2550 radii = {a_radius0, a_radius1};
2551}
2552
2554#ifdef HELIOS_DEBUG
2555 if (objects.find(ObjID) == objects.end()) {
2556 helios_runtime_error("ERROR (Context::getConeObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
2557 }
2558#endif
2559 return dynamic_cast<Cone *>(objects.at(ObjID));
2560}
2561
2562std::vector<helios::vec3> Cone::getNodeCoordinates() const {
2563 std::vector<vec3> nodes_T;
2564 nodes_T.resize(2);
2565
2566 for (int i = 0; i < 2; i++) {
2567 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];
2568 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];
2569 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];
2570 }
2571
2572 return nodes_T;
2573}
2574
2576 if (node_index < 0 || node_index > 1) {
2577 helios_runtime_error("ERROR (Cone::getNodeCoordinate): node number must be 0 or 1.");
2578 }
2579
2580 vec3 node_T;
2581
2582 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];
2583 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];
2584 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];
2585
2586 return node_T;
2587}
2588
2589std::vector<float> Cone::getNodeRadii() const {
2590 return radii;
2591}
2592
2593float Cone::getNodeRadius(int node_index) const {
2594 if (node_index < 0 || node_index > 1) {
2595 helios_runtime_error("ERROR (Cone::getNodeRadius): node number must be 0 or 1.");
2596 }
2597
2598 return radii.at(node_index);
2599}
2600
2602 return subdiv;
2603}
2604
2606 subdiv = a_subdiv;
2607}
2608
2610 std::vector<vec3> nodes_T;
2611 nodes_T.resize(2);
2612
2613 for (uint i = 0; i < 2; i++) {
2614 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];
2615 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];
2616 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];
2617 }
2618
2619 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);
2620 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2621 axis_unit_vector = axis_unit_vector / length;
2622
2623 return axis_unit_vector;
2624}
2625
2626float Cone::getLength() const {
2627 std::vector<vec3> nodes_T;
2628 nodes_T.resize(2);
2629
2630 for (uint i = 0; i < 2; i++) {
2631 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];
2632 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];
2633 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];
2634 }
2635
2636 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);
2637 return length;
2638}
2639
2640void Cone::scaleLength(float S) {
2641 // get the nodes and radii of the nodes with transformation matrix applied
2642 const std::vector<helios::vec3> &nodes_T = context->getConeObjectPointer(OID)->getNodeCoordinates();
2643 const std::vector<float> &radii_T = context->getConeObjectPointer(OID)->getNodeRadii();
2644
2645 // calculate the transformed axis unit vector of the cone
2646 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);
2647 float length = powf(powf(axis_unit_vector.x, 2) + powf(axis_unit_vector.y, 2) + powf(axis_unit_vector.z, 2), 0.5);
2648 axis_unit_vector = axis_unit_vector / length;
2649
2650 // translate node 0 back to origin
2651 context->getConeObjectPointer(OID)->translate(-1.0 * nodes_T.at(0));
2652
2653 // rotate the cone to align with z axis
2654 helios::vec3 z_axis = make_vec3(0, 0, 1);
2655 // get the axis about which to rotate
2656 vec3 ra = cross(z_axis, axis_unit_vector);
2657 // get the angle to rotate
2658 float dot = axis_unit_vector.x * z_axis.x + axis_unit_vector.y * z_axis.y + axis_unit_vector.z * z_axis.z;
2659 float angle = acos_safe(dot);
2660
2661 // 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)
2662 if (angle != float(0.0)) {
2663 context->getConeObjectPointer(OID)->rotate(-1 * angle, ra);
2664 }
2665
2666 // scale the cone in the z (length) dimension
2667 float T[16], T_prim[16];
2668 makeScaleMatrix(make_vec3(1, 1, S), T);
2669 matmult(T, transform, transform);
2670 for (uint UUID: UUIDs) {
2671 if (context->doesPrimitiveExist(UUID)) {
2672 context->getPrimitiveTransformationMatrix(UUID, T_prim);
2673 matmult(T, T_prim, T_prim);
2674 context->setPrimitiveTransformationMatrix(UUID, T_prim);
2675 }
2676 }
2677
2678 // rotate back
2679 if (angle != 0.0) {
2680 context->getConeObjectPointer(OID)->rotate(angle, ra);
2681 }
2682
2683 // translate back
2684 context->getConeObjectPointer(OID)->translate(nodes_T.at(0));
2685}
2686
2687void Cone::scaleGirth(float S) {
2688 // get the nodes and radii of the nodes with transformation matrix applied
2689 const std::vector<helios::vec3> &nodes_T = context->getConeObjectPointer(OID)->getNodeCoordinates();
2690 const std::vector<float> &radii_T = context->getConeObjectPointer(OID)->getNodeRadii();
2691
2692 // calculate the transformed axis unit vector of the cone
2693 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);
2694 axis_unit_vector.normalize();
2695
2696 // translate node 0 back to origin
2697 context->getConeObjectPointer(OID)->translate(-1.0 * nodes_T.at(0));
2698 // rotate the cone to align with z axis
2699 helios::vec3 z_axis = make_vec3(0, 0, 1);
2700 // get the axis about which to rotate
2701 vec3 ra = cross(z_axis, axis_unit_vector);
2702 // get the angle to rotate
2703 float dot = axis_unit_vector * z_axis;
2704 float angle = acos_safe(dot);
2705 // 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)
2706 if (angle != float(0.0)) {
2707 context->getConeObjectPointer(OID)->rotate(-1 * angle, ra);
2708 }
2709
2710 // scale the cone in the x and y dimensions
2711 context->scaleObject(OID, make_vec3(S, S, 1));
2712
2713
2714 // rotate back
2715 if (angle != 0.0) {
2716 context->getConeObjectPointer(OID)->rotate(angle, ra);
2717 }
2718
2719 // translate back
2720 context->getConeObjectPointer(OID)->translate(nodes_T.at(0));
2721
2722 radii.at(0) *= S;
2723 radii.at(1) *= S;
2724}
2725
2726float Cone::getVolume() const {
2727 float r0 = getNodeRadius(0);
2728 float r1 = getNodeRadius(1);
2729 float h = getLength();
2730
2731 return PI_F * h / 3.f * (r0 * r0 + r0 * r1 + r1 * r1);
2732}