1.3.72
 
Loading...
Searching...
No Matches
Context.cpp
Go to the documentation of this file.
1
16#include "Context.h"
17
18using namespace helios;
19
21
22 install_out_of_memory_handler();
23
24 //---- ALL DEFAULT VALUES ARE SET HERE ----//
25
26 sim_date = make_Date(1, 6, 2000);
27
28 sim_time = make_Time(12, 0);
29
30 sim_location = make_Location(38.55, 121.76, 8);
31
32 // --- Initialize random number generator ---- //
33
34 unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
35 generator.seed(seed);
36
37 // --- Set Geometry as `Clean' --- //
38
39 currentUUID = 0;
40
41 currentObjectID = 1; // object ID of 0 is reserved for default object
42
43 // --- Initialize Material System --- //
44
45 currentMaterialID = 0;
46
47 // Create default material (ID = 0) with reserved label
48 Material default_material(0, DEFAULT_MATERIAL_LABEL, make_RGBAcolor(0, 0, 0, 1), "", false);
49 materials[0] = default_material;
50 material_label_to_id[DEFAULT_MATERIAL_LABEL] = 0;
51 currentMaterialID = 1; // Next material will be ID 1
52}
53
55 generator.seed(seed);
56}
57
58std::minstd_rand0 *Context::getRandomGenerator() {
59 return &generator;
60}
61
62// Asset directory registration system removed - now using HELIOS_BUILD resolution
63
64std::filesystem::path Context::resolveFilePath(const std::string &filename) const {
65 // Use the global helios::resolveFilePath function which implements HELIOS_BUILD resolution
66 return helios::resolveFilePath(filename);
67}
68
69void Context::addTexture(const char *texture_file) {
70 if (textures.find(texture_file) == textures.end()) { // texture has not already been added
71
72 // texture must have type PNG or JPEG
73 const std::string &fn = texture_file;
74 const std::string &ext = getFileExtension(fn);
75 if (ext != ".png" && ext != ".PNG" && ext != ".jpg" && ext != ".jpeg" && ext != ".JPG" && ext != ".JPEG") {
76 helios_runtime_error("ERROR (Context::addTexture): Texture file " + fn + " is not PNG or JPEG format.");
77 } else if (!doesTextureFileExist(texture_file)) {
78 helios_runtime_error("ERROR (Context::addTexture): Texture file " + std::string(texture_file) + " does not exist.");
79 }
80
81 // Use unified path resolution
82 auto resolved_path = resolveFilePath(texture_file);
83 textures.emplace(texture_file, Texture(resolved_path.string().c_str()));
84 }
85}
86
87bool Context::doesTextureFileExist(const char *texture_file) const {
88 try {
89 auto resolved_path = resolveFilePath(texture_file);
90 return std::filesystem::exists(resolved_path);
91 } catch (const std::runtime_error &) {
92 return false;
93 }
94}
95
96bool Context::validateTextureFileExtenstion(const char *texture_file) const {
97 const std::string &fn = texture_file;
98 const std::string &ext = getFileExtension(fn);
99 if (ext != ".png" && ext != ".PNG" && ext != ".jpg" && ext != ".jpeg" && ext != ".JPG" && ext != ".JPEG") {
100 return false;
101 } else {
102 return true;
103 }
104}
105
106Texture::Texture(const char *texture_file) {
107 filename = texture_file;
108
109 //------ determine if transparency channel exists ---------//
110
111 // check if texture file has extension ".png"
112 const std::string &ext = getFileExtension(filename);
113 if (ext != ".png") {
114 hastransparencychannel = false;
115 } else {
116 hastransparencychannel = PNGHasAlpha(filename.c_str());
117 }
118
119 //-------- load transparency channel (if exists) ------------//
120
121 if (ext == ".png") {
122 transparencydata = readPNGAlpha(filename);
123 image_resolution = make_int2(int(transparencydata.front().size()), int(transparencydata.size()));
124 } else {
125 image_resolution = getImageResolutionJPEG(texture_file);
126 }
127
128 //-------- determine solid fraction --------------//
129
130 if (hastransparencychannel) {
131 size_t p = 0.f;
132 for (auto &j: transparencydata) {
133 for (bool transparency: j) {
134 if (transparency) {
135 p += 1;
136 }
137 }
138 }
139 float sf = float(p) / float(transparencydata.size() * transparencydata.front().size());
140 if (std::isnan(sf)) {
141 sf = 0.f;
142 }
143 solidfraction = sf;
144 } else {
145 solidfraction = 1.f;
146 }
147}
148
149std::string Texture::getTextureFile() const {
150 return filename;
151}
152
154 return image_resolution;
155}
156
158 return hastransparencychannel;
159}
160
161const std::vector<std::vector<bool>> *Texture::getTransparencyData() const {
162 return &transparencydata;
163}
164
165float Texture::getSolidFraction(const std::vector<helios::vec2> &uvs) {
166 float solidfraction = 1;
167
168 PixelUVKey key;
169 key.coords.reserve(2 * uvs.size());
170 for (auto &uvc: uvs) {
171 key.coords.push_back(int(std::round(uvc.x * (image_resolution.x - 1))));
172 key.coords.push_back(int(std::round(uvc.y * (image_resolution.y - 1))));
173 }
174
175 if (solidFracCache.find(key) != solidFracCache.end()) {
176 return solidFracCache.at(key);
177 }
178
179 solidfraction = computeSolidFraction(uvs);
180 solidFracCache.emplace(std::move(key), solidfraction);
181
182 return solidfraction;
183}
184
185float Texture::computeSolidFraction(const std::vector<helios::vec2> &uvs) const {
186 // Early out for opaque textures or degenerate UVs
187 if (!hasTransparencyChannel() || uvs.size() < 3) {
188 return 1.0f;
189 }
190
191 // Fetch alpha mask and dimensions
192 const auto *alpha2D = getTransparencyData(); // vector<vector<bool>>
193 int W = getImageResolution().x;
194 int H = getImageResolution().y;
195
196 // Flatten mask to contiguous array
197 std::vector<uint8_t> mask(W * H);
198 for (int y = 0; y < H; ++y)
199 for (int x = 0; x < W; ++x)
200 mask[y * W + x] = (*alpha2D)[H - 1 - y][x];
201
202 // Compute pixel‐space bounding box from UVs
203 float minU = uvs[0].x, maxU = uvs[0].x, minV = uvs[0].y, maxV = uvs[0].y;
204 for (auto &p: uvs) {
205 minU = std::min(minU, p.x);
206 maxU = std::max(maxU, p.x);
207 minV = std::min(minV, p.y);
208 maxV = std::max(maxV, p.y);
209 }
210 int xmin = std::clamp(int(std::floor(minU * (W - 1))), 0, W - 1);
211 int xmax = std::clamp(int(std::ceil(maxU * (W - 1))), 0, W - 1);
212 int ymin = std::clamp(int(std::floor(minV * (H - 1))), 0, H - 1);
213 int ymax = std::clamp(int(std::ceil(maxV * (H - 1))), 0, H - 1);
214
215 if (xmin > xmax || ymin > ymax)
216 return 0.0f;
217
218 // Precompute half‐space coefficients for each edge i→i+1
219 int N = int(uvs.size());
220 std::vector<float> A(N), B(N), C(N);
221 for (int i = 0; i < N; ++i) {
222 int j = (i + 1) % N;
223 const auto &a = uvs[i], &b = uvs[j];
224 // L(x,y) = (b.x - a.x)*y - (b.y - a.y)*x + (a.x*b.y - a.y*b.x)
225 A[i] = b.x - a.x;
226 B[i] = -(b.y - a.y);
227 C[i] = a.x * b.y - a.y * b.x;
228 }
229
230 // Check winding order using signed area (shoelace formula)
231 // If area is negative, triangle has clockwise winding - flip all coefficients
232 float signed_area = 0.0f;
233 for (int i = 0; i < N; ++i) {
234 int j = (i + 1) % N;
235 signed_area += uvs[i].x * uvs[j].y - uvs[j].x * uvs[i].y;
236 }
237 if (signed_area < 0.0f) {
238 // Clockwise winding - negate all half-space coefficients
239 for (int i = 0; i < N; ++i) {
240 A[i] = -A[i];
241 B[i] = -B[i];
242 C[i] = -C[i];
243 }
244 }
245
246 // Raster‐scan, test each pixel center
247 int64_t countTotal = 0, countOpaque = 0;
248 float invWm1 = 1.0f / float(W - 1);
249 float invHm1 = 1.0f / float(H - 1);
250
251 for (int j = ymin; j <= ymax; ++j) {
252 float yuv = (j + 0.5f) * invHm1;
253 for (int i = xmin; i <= xmax; ++i) {
254 float xuv = (i + 0.5f) * invWm1;
255 bool inside = true;
256
257 // all edges must satisfy L(xuv,yuv) >= 0
258 for (int k = 0; k < N; ++k) {
259 float L = A[k] * yuv + B[k] * xuv + C[k];
260 if (L < 0.0f) {
261 inside = false;
262 break;
263 }
264 }
265
266 if (!inside)
267 continue;
268
269 ++countTotal;
270 countOpaque += mask[j * W + i];
271 }
272 }
273
274 float result = countTotal == 0 ? 0.0f : float(countOpaque) / float(countTotal);
275 return result;
276}
277
279 for (auto &[UUID, primitive]: primitives) {
280 primitive->dirty_flag = false;
281 }
282 dirty_deleted_primitives.clear();
283}
284
286 for (auto &[UUID, primitive]: primitives) {
287 primitive->dirty_flag = true;
288 }
289}
290
292 if (!dirty_deleted_primitives.empty()) {
293 return true;
294 }
295 for (auto &[UUID, primitive]: primitives) {
296 if (primitive->dirty_flag) {
297 return true;
298 }
299 }
300 return false;
301}
302
304#ifdef HELIOS_DEBUG
305 if (!doesPrimitiveExist(UUID)) {
306 helios_runtime_error("ERROR (Context::markPrimitiveDirty): Primitive with UUID " + std::to_string(UUID) + " does not exist.");
307 }
308#endif
309 primitives.at(UUID)->dirty_flag = true;
310}
311
312void Context::markPrimitiveDirty(const std::vector<uint> &UUIDs) const {
313 for (uint UUID: UUIDs) {
314 markPrimitiveDirty(UUID);
315 }
316}
317
319#ifdef HELIOS_DEBUG
320 if (!doesPrimitiveExist(UUID)) {
321 helios_runtime_error("ERROR (Context::markPrimitiveDirty): Primitive with UUID " + std::to_string(UUID) + " does not exist.");
322 }
323#endif
324 primitives.at(UUID)->dirty_flag = false;
325}
326
327void Context::markPrimitiveClean(const std::vector<uint> &UUIDs) const {
328 for (uint UUID: UUIDs) {
329 markPrimitiveClean(UUID);
330 }
331}
332
333[[nodiscard]] bool Context::isPrimitiveDirty(uint UUID) const {
334#ifdef HELIOS_DEBUG
335 if (!doesPrimitiveExist(UUID)) {
336 helios_runtime_error("ERROR (Context::markPrimitiveDirty): Primitive with UUID " + std::to_string(UUID) + " does not exist.");
337 }
338#endif
339 return primitives.at(UUID)->dirty_flag;
340}
341
342
343void Context::setDate(int day, int month, int year) {
344 if (day < 1 || day > 31) {
345 helios_runtime_error("ERROR (Context::setDate): Day of month is out of range (day of " + std::to_string(day) + " was given).");
346 } else if (month < 1 || month > 12) {
347 helios_runtime_error("ERROR (Context::setDate): Month of year is out of range (month of " + std::to_string(month) + " was given).");
348 } else if (year < 1000) {
349 helios_runtime_error("ERROR (Context::setDate): Year should be specified in YYYY format.");
350 }
351
352 sim_date = make_Date(day, month, year);
353}
354
355void Context::setDate(const Date &date) {
356 if (date.day < 1 || date.day > 31) {
357 helios_runtime_error("ERROR (Context::setDate): Day of month is out of range (day of " + std::to_string(date.day) + " was given).");
358 } else if (date.month < 1 || date.month > 12) {
359 helios_runtime_error("ERROR (Context::setDate): Month of year is out of range (month of " + std::to_string(date.month) + " was given).");
360 } else if (date.year < 1000) {
361 helios_runtime_error("ERROR (Context::setDate): Year should be specified in YYYY format.");
362 }
363
364 sim_date = date;
365}
366
367void Context::setDate(int Julian_day, int year) {
368 if (Julian_day < 1 || Julian_day > 366) {
369 helios_runtime_error("ERROR (Context::setDate): Julian day out of range.");
370 } else if (year < 1000) {
371 helios_runtime_error("ERROR (Context::setDate): Year should be specified in YYYY format.");
372 }
373
374 sim_date = CalendarDay(Julian_day, year);
375}
376
378 return sim_date;
379}
380
381const char *Context::getMonthString() const {
382 if (sim_date.month == 1) {
383 return "JAN";
384 } else if (sim_date.month == 2) {
385 return "FEB";
386 } else if (sim_date.month == 3) {
387 return "MAR";
388 } else if (sim_date.month == 4) {
389 return "APR";
390 } else if (sim_date.month == 5) {
391 return "MAY";
392 } else if (sim_date.month == 6) {
393 return "JUN";
394 } else if (sim_date.month == 7) {
395 return "JUL";
396 } else if (sim_date.month == 8) {
397 return "AUG";
398 } else if (sim_date.month == 9) {
399 return "SEP";
400 } else if (sim_date.month == 10) {
401 return "OCT";
402 } else if (sim_date.month == 11) {
403 return "NOV";
404 } else {
405 return "DEC";
406 }
407}
408
410 return JulianDay(sim_date.day, sim_date.month, sim_date.year);
411}
412
413void Context::setTime(int minute, int hour) {
414 setTime(0, minute, hour);
415}
416
417void Context::setTime(int second, int minute, int hour) {
418 if (second < 0 || second > 59) {
419 helios_runtime_error("ERROR (Context::setTime): Second out of range (0-59).");
420 } else if (minute < 0 || minute > 59) {
421 helios_runtime_error("ERROR (Context::setTime): Minute out of range (0-59).");
422 } else if (hour < 0 || hour > 23) {
423 helios_runtime_error("ERROR (Context::setTime): Hour out of range (0-23).");
424 }
425
426 sim_time = make_Time(hour, minute, second);
427}
428
429void Context::setTime(const Time &time) {
430 if (time.minute < 0 || time.minute > 59) {
431 helios_runtime_error("ERROR (Context::setTime): Minute out of range (0-59).");
432 } else if (time.hour < 0 || time.hour > 23) {
433 helios_runtime_error("ERROR (Context::setTime): Hour out of range (0-23).");
434 }
435
436 sim_time = time;
437}
438
440 return sim_time;
441}
442
444 sim_location = location;
445}
446
448 return sim_location;
449}
450
452 return unif_distribution(generator);
453}
454
455float Context::randu(float minrange, float maxrange) {
456 if (maxrange < minrange) {
457 helios_runtime_error("ERROR (Context::randu): Maximum value of range must be greater than minimum value of range.");
458 return 0;
459 } else if (maxrange == minrange) {
460 return minrange;
461 } else {
462 return minrange + unif_distribution(generator) * (maxrange - minrange);
463 }
464}
465
466int Context::randu(int minrange, int maxrange) {
467 if (maxrange < minrange) {
468 helios_runtime_error("ERROR (Context::randu): Maximum value of range must be greater than minimum value of range.");
469 return 0;
470 } else if (maxrange == minrange) {
471 return minrange;
472 } else {
473 return minrange + (int) lroundf(unif_distribution(generator) * float(maxrange - minrange));
474 }
475}
476
478 return norm_distribution(generator);
479}
480
481float Context::randn(float mean, float stddev) {
482 return mean + norm_distribution(generator) * fabs(stddev);
483}
484
485
486std::vector<uint> Context::getAllUUIDs() const {
487 // Use cached result if valid
488 if (all_uuids_cache_valid) {
489 return cached_all_uuids;
490 }
491
492 // Rebuild cache
493 cached_all_uuids.clear();
494 cached_all_uuids.reserve(primitives.size());
495 for (const auto &[UUID, primitive]: primitives) {
496 if (primitive->ishidden) {
497 continue;
498 }
499 cached_all_uuids.push_back(UUID);
500 }
501
502 all_uuids_cache_valid = true;
503 return cached_all_uuids;
504}
505
506std::vector<uint> Context::getDirtyUUIDs(bool include_deleted_UUIDs) const {
507
508 size_t dirty_count = std::count_if(primitives.begin(), primitives.end(), [&](auto const &kv) { return isPrimitiveDirty(kv.first); });
509
510 std::vector<uint> dirty_UUIDs;
511 dirty_UUIDs.reserve(dirty_count);
512 for (const auto &[UUID, primitive]: primitives) {
513 if (!primitive->dirty_flag || primitive->ishidden) {
514 continue;
515 }
516 dirty_UUIDs.push_back(UUID);
517 }
518
519 if (include_deleted_UUIDs) {
520 dirty_UUIDs.insert(dirty_UUIDs.end(), dirty_deleted_primitives.begin(), dirty_deleted_primitives.end());
521 }
522
523 return dirty_UUIDs;
524}
525
526std::vector<uint> Context::getDeletedUUIDs() const {
527 return dirty_deleted_primitives;
528}
529
530void Context::hidePrimitive(uint UUID) const {
531#ifdef HELIOS_DEBUG
532 if (!doesPrimitiveExist(UUID)) {
533 helios_runtime_error("ERROR (Context::hidePrimitive): UUID of " + std::to_string(UUID) + " does not exist in the Context.");
534 }
535#endif
536 primitives.at(UUID)->ishidden = true;
537 invalidateAllUUIDsCache();
538}
539
540void Context::hidePrimitive(const std::vector<uint> &UUIDs) const {
541 for (uint UUID: UUIDs) {
542 hidePrimitive(UUID);
543 }
544}
545
546void Context::showPrimitive(uint UUID) const {
547#ifdef HELIOS_DEBUG
548 if (!doesPrimitiveExist(UUID)) {
549 helios_runtime_error("ERROR (Context::showPrimitive): UUID of " + std::to_string(UUID) + " does not exist in the Context.");
550 }
551#endif
552 primitives.at(UUID)->ishidden = false;
553 invalidateAllUUIDsCache();
554}
555
556void Context::showPrimitive(const std::vector<uint> &UUIDs) const {
557 for (uint UUID: UUIDs) {
558 showPrimitive(UUID);
559 }
560}
561
563 if (!doesPrimitiveExist(UUID)) {
564 helios_runtime_error("ERROR (Context::isPrimitiveHidden): UUID of " + std::to_string(UUID) + " does not exist in the Context.");
565 }
566 return primitives.at(UUID)->ishidden;
567}
568
569void Context::cleanDeletedUUIDs(std::vector<uint> &UUIDs) const {
570 for (size_t i = UUIDs.size(); i-- > 0;) {
571 if (!doesPrimitiveExist(UUIDs.at(i))) {
572 UUIDs.erase(UUIDs.begin() + i);
573 }
574 }
575}
576
577void Context::cleanDeletedUUIDs(std::vector<std::vector<uint>> &UUIDs) const {
578 for (auto &vec: UUIDs) {
579 for (auto it = vec.begin(); it != vec.end();) {
580 if (!doesPrimitiveExist(*it)) {
581 it = vec.erase(it);
582 } else {
583 ++it;
584 }
585 }
586 }
587}
588
589void Context::cleanDeletedUUIDs(std::vector<std::vector<std::vector<uint>>> &UUIDs) const {
590 for (auto &vec2D: UUIDs) {
591 for (auto &vec: vec2D) {
592 for (auto it = vec.begin(); it != vec.end();) {
593 if (!doesPrimitiveExist(*it)) {
594 it = vec.erase(it);
595 } else {
596 ++it;
597 }
598 }
599 }
600 }
601}
602
603void Context::addTimeseriesData(const char *label, float value, const Date &date, const Time &time) {
604 // floating point value corresponding to date and time
605 double date_value = floor(date.year * 366.25) + date.JulianDay();
606 date_value += double(time.hour) / 24. + double(time.minute) / 1440. + double(time.second) / 86400.;
607
608 // Check if data label already exists
609 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
610 timeseries_data[label].push_back(value);
611 timeseries_datevalue[label].push_back(date_value);
612 return;
613 } else { // exists
614
615 uint N = getTimeseriesLength(label);
616
617 auto it_data = timeseries_data[label].begin();
618 auto it_datevalue = timeseries_datevalue[label].begin();
619
620 if (N == 1) {
621 if (date_value < timeseries_datevalue[label].front()) {
622 timeseries_data[label].insert(it_data, value);
623 timeseries_datevalue[label].insert(it_datevalue, date_value);
624 return;
625 } else {
626 timeseries_data[label].insert(it_data + 1, value);
627 timeseries_datevalue[label].insert(it_datevalue + 1, date_value);
628 return;
629 }
630 } else {
631 if (date_value < timeseries_datevalue[label].front()) { // check if data should be inserted at beginning of timeseries
632 timeseries_data[label].insert(it_data, value);
633 timeseries_datevalue[label].insert(it_datevalue, date_value);
634 return;
635 } else if (date_value > timeseries_datevalue[label].back()) { // check if data should be inserted at end of timeseries
636 timeseries_data[label].push_back(value);
637 timeseries_datevalue[label].push_back(date_value);
638 return;
639 }
640
641 // data should be inserted somewhere in the middle of timeseries
642 for (uint t = 0; t < N - 1; t++) {
643 if (date_value == timeseries_datevalue[label].at(t)) {
644 std::cerr << "WARNING (Context::addTimeseriesData): Skipping duplicate timeseries date/time." << std::endl;
645 continue;
646 }
647 if (date_value > timeseries_datevalue[label].at(t) && date_value < timeseries_datevalue[label].at(t + 1)) {
648 timeseries_data[label].insert(it_data + t + 1, value);
649 timeseries_datevalue[label].insert(it_datevalue + t + 1, date_value);
650 return;
651 }
652 }
653 }
654 }
655
656 helios_runtime_error("ERROR (Context::addTimeseriesData): Failed to insert timeseries data for unknown reason.");
657}
658
659void Context::updateTimeseriesData(const char *label, const Date &date, const Time &time, float new_value) {
660 if (timeseries_data.find(label) == timeseries_data.end()) {
661 helios_runtime_error("ERROR (Context::updateTimeseriesData): Timeseries variable `" + std::string(label) + "' does not exist.");
662 }
663
664 double date_value = floor(date.year * 366.25) + date.JulianDay();
665 date_value += double(time.hour) / 24. + double(time.minute) / 1440. + double(time.second) / 86400.;
666
667 const std::vector<double> &datevalues = timeseries_datevalue.at(label);
668 for (uint i = 0; i < datevalues.size(); i++) {
669 if (datevalues.at(i) == date_value) {
670 timeseries_data.at(label).at(i) = new_value;
671 return;
672 }
673 }
674
675 helios_runtime_error("ERROR (Context::updateTimeseriesData): No timeseries data point exists at the specified date and time for variable `" + std::string(label) + "'.");
676}
677
678void Context::setCurrentTimeseriesPoint(const char *label, uint index) {
679 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
680 helios_runtime_error("ERROR (setCurrentTimeseriesPoint): Timeseries variable `" + std::string(label) + "' does not exist.");
681 }
682 setDate(queryTimeseriesDate(label, index));
683 setTime(queryTimeseriesTime(label, index));
684}
685
686float Context::queryTimeseriesData(const char *label, const Date &date, const Time &time) const {
687 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
688 helios_runtime_error("ERROR (setCurrentTimeseriesData): Timeseries variable `" + std::string(label) + "' does not exist.");
689 }
690
691 double date_value = floor(date.year * 366.25) + date.JulianDay();
692 date_value += double(time.hour) / 24. + double(time.minute) / 1440. + double(time.second) / 86400.;
693
694 double tmin = timeseries_datevalue.at(label).front();
695 double tmax = timeseries_datevalue.at(label).back();
696
697 if (date_value < tmin) {
698 std::cerr << "WARNING (queryTimeseriesData): Timeseries date and time is outside of the range of the data. Using the earliest data point in the timeseries." << std::endl;
699 return timeseries_data.at(label).front();
700 } else if (date_value > tmax) {
701 std::cerr << "WARNING (queryTimeseriesData): Timeseries date and time is outside of the range of the data. Using the latest data point in the timeseries." << std::endl;
702 return timeseries_data.at(label).back();
703 }
704
705 if (timeseries_datevalue.at(label).empty()) {
706 std::cerr << "WARNING (queryTimeseriesData): timeseries " << label << " does not contain any data." << std::endl;
707 return 0;
708 } else if (timeseries_datevalue.at(label).size() == 1) {
709 return timeseries_data.at(label).front();
710 } else {
711 int i;
712 bool success = false;
713 for (i = 0; i < timeseries_data.at(label).size() - 1; i++) {
714 if (date_value >= timeseries_datevalue.at(label).at(i) && date_value <= timeseries_datevalue.at(label).at(i + 1)) {
715 success = true;
716 break;
717 }
718 }
719
720 if (!success) {
721 helios_runtime_error("ERROR (queryTimeseriesData): Failed to query timeseries data for unknown reason.");
722 }
723
724 double xminus = timeseries_data.at(label).at(i);
725 double xplus = timeseries_data.at(label).at(i + 1);
726
727 double tminus = timeseries_datevalue.at(label).at(i);
728 double tplus = timeseries_datevalue.at(label).at(i + 1);
729
730 return float(xminus + (xplus - xminus) * (date_value - tminus) / (tplus - tminus));
731 }
732}
733
734float Context::queryTimeseriesData(const char *label) const {
735 return queryTimeseriesData(label, sim_date, sim_time);
736}
737
738float Context::queryTimeseriesData(const char *label, const uint index) const {
739 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
740 helios_runtime_error("ERROR( Context::getTimeseriesData): Timeseries variable " + std::string(label) + " does not exist.");
741 }
742
743 return timeseries_data.at(label).at(index);
744}
745
746Time Context::queryTimeseriesTime(const char *label, const uint index) const {
747 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
748 helios_runtime_error("ERROR( Context::getTimeseriesTime): Timeseries variable " + std::string(label) + " does not exist.");
749 }
750
751 double dateval = timeseries_datevalue.at(label).at(index);
752
753 int year = floor(floor(dateval) / 366.25);
754 assert(year > 1000 && year < 10000);
755
756 int JD = floor(dateval - floor(double(year) * 366.25));
757 assert(JD > 0 && JD < 367);
758
759 int hour = floor((dateval - floor(dateval)) * 24.);
760 int minute = floor(((dateval - floor(dateval)) * 24. - double(hour)) * 60.);
761 int second = (int) lround((((dateval - floor(dateval)) * 24. - double(hour)) * 60. - double(minute)) * 60.);
762
763 if (second == 60) {
764 second = 0;
765 minute++;
766 }
767
768 if (minute == 60) {
769 minute = 0;
770 hour++;
771 }
772
773 assert(second >= 0 && second < 60);
774 assert(minute >= 0 && minute < 60);
775 assert(hour >= 0 && hour < 24);
776
777 return make_Time(hour, minute, second);
778}
779
780Date Context::queryTimeseriesDate(const char *label, const uint index) const {
781 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
782 helios_runtime_error("ERROR( Context::getTimeseriesDate): Timeseries variable " + std::string(label) + " does not exist.");
783 }
784
785 double dateval = timeseries_datevalue.at(label).at(index);
786
787 int year = floor(floor(dateval) / 366.25);
788 assert(year > 1000 && year < 10000);
789
790 int JD = floor(dateval - floor(double(year) * 366.25));
791 assert(JD > 0 && JD < 367);
792
793 return Julian2Calendar(JD, year);
794}
795
796uint Context::getTimeseriesLength(const char *label) const {
797 uint size = 0;
798 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
799 helios_runtime_error("ERROR (Context::getTimeseriesDate): Timeseries variable `" + std::string(label) + "' does not exist.");
800 } else {
801 size = timeseries_data.at(label).size();
802 }
803
804 return size;
805}
806
807bool Context::doesTimeseriesVariableExist(const char *label) const {
808 if (timeseries_data.find(label) == timeseries_data.end()) { // does not exist
809 return false;
810 } else {
811 return true;
812 }
813}
814
815std::vector<std::string> Context::listTimeseriesVariables() const {
816 std::vector<std::string> labels;
817 labels.reserve(timeseries_data.size());
818 for (const auto &[timeseries_label, timeseries_data]: timeseries_data) {
819 labels.push_back(timeseries_label);
820 }
821 return labels;
822}
823
825 timeseries_data.clear();
826 timeseries_datevalue.clear();
827}
828
829void Context::deleteTimeseriesVariable(const char *label) {
830 auto it = timeseries_data.find(label);
831 if (it == timeseries_data.end()) {
832 std::cerr << "WARNING (Context::deleteTimeseriesVariable): Timeseries variable '" << label << "' does not exist. Nothing to delete." << std::endl;
833 return;
834 }
835 timeseries_data.erase(it);
836 timeseries_datevalue.erase(label);
837}
838
839void Context::getDomainBoundingBox(vec2 &xbounds, vec2 &ybounds, vec2 &zbounds) const {
840 getDomainBoundingBox(getAllUUIDs(), xbounds, ybounds, zbounds);
841}
842
843void Context::getDomainBoundingBox(const std::vector<uint> &UUIDs, vec2 &xbounds, vec2 &ybounds, vec2 &zbounds) const {
844 // Global bounding box initialization
845 xbounds.x = 1e8; // global min x
846 xbounds.y = -1e8; // global max x
847 ybounds.x = 1e8; // global min y
848 ybounds.y = -1e8; // global max y
849 zbounds.x = 1e8; // global min z
850 zbounds.y = -1e8; // global max z
851
852 // Parallel region over the primitives (UUIDs)
853#ifdef USE_OPENMP
854#pragma omp parallel
855 {
856 // Each thread creates its own local bounding box.
857 float local_xmin = 1e8, local_xmax = -1e8;
858 float local_ymin = 1e8, local_ymax = -1e8;
859 float local_zmin = 1e8, local_zmax = -1e8;
860
861// Parallelize the outer loop over primitives. Use "for" inside the parallel region.
862#pragma omp for nowait
863 for (int i = 0; i < (int) UUIDs.size(); i++) {
864 // For each primitive:
865 const std::vector<vec3> &verts = getPrimitivePointer_private(UUIDs[i])->getVertices();
866 // Update local bounding box for each vertex in this primitive.
867 for (const auto &vert: verts) {
868 local_xmin = std::min(local_xmin, vert.x);
869 local_xmax = std::max(local_xmax, vert.x);
870 local_ymin = std::min(local_ymin, vert.y);
871 local_ymax = std::max(local_ymax, vert.y);
872 local_zmin = std::min(local_zmin, vert.z);
873 local_zmax = std::max(local_zmax, vert.z);
874 }
875 }
876
877// Merge the thread-local bounds into the global bounds.
878#pragma omp critical
879 {
880 xbounds.x = std::min(xbounds.x, local_xmin);
881 xbounds.y = std::max(xbounds.y, local_xmax);
882 ybounds.x = std::min(ybounds.x, local_ymin);
883 ybounds.y = std::max(ybounds.y, local_ymax);
884 zbounds.x = std::min(zbounds.x, local_zmin);
885 zbounds.y = std::max(zbounds.y, local_zmax);
886 }
887 } // end parallel region
888
889#else
890
891 for (uint UUID: UUIDs) {
892 const std::vector<vec3> &verts = getPrimitivePointer_private(UUID)->getVertices();
893
894 for (auto &vert: verts) {
895 if (vert.x < xbounds.x) {
896 xbounds.x = vert.x;
897 } else if (vert.x > xbounds.y) {
898 xbounds.y = vert.x;
899 }
900 if (vert.y < ybounds.x) {
901 ybounds.x = vert.y;
902 } else if (vert.y > ybounds.y) {
903 ybounds.y = vert.y;
904 }
905 if (vert.z < zbounds.x) {
906 zbounds.x = vert.z;
907 } else if (vert.z > zbounds.y) {
908 zbounds.y = vert.z;
909 }
910 }
911 }
912
913#endif
914}
915
916void Context::getDomainBoundingSphere(vec3 &center, float &radius) const {
917 vec2 xbounds, ybounds, zbounds;
918 getDomainBoundingBox(xbounds, ybounds, zbounds);
919
920 center.x = xbounds.x + 0.5f * (xbounds.y - xbounds.x);
921 center.y = ybounds.x + 0.5f * (ybounds.y - ybounds.x);
922 center.z = zbounds.x + 0.5f * (zbounds.y - zbounds.x);
923
924 radius = 0.5f * sqrtf(powf(xbounds.y - xbounds.x, 2) + powf(ybounds.y - ybounds.x, 2) + powf((zbounds.y - zbounds.x), 2));
925}
926
927void Context::getDomainBoundingSphere(const std::vector<uint> &UUIDs, vec3 &center, float &radius) const {
928 vec2 xbounds, ybounds, zbounds;
929 getDomainBoundingBox(UUIDs, xbounds, ybounds, zbounds);
930
931 center.x = xbounds.x + 0.5f * (xbounds.y - xbounds.x);
932 center.y = ybounds.x + 0.5f * (ybounds.y - ybounds.x);
933 center.z = zbounds.x + 0.5f * (zbounds.y - zbounds.x);
934
935 radius = 0.5f * sqrtf(powf(xbounds.y - xbounds.x, 2) + powf(ybounds.y - ybounds.x, 2) + powf((zbounds.y - zbounds.x), 2));
936}
937
938void Context::cropDomainX(const vec2 &xbounds) {
939 const std::vector<uint> &UUIDs_all = getAllUUIDs();
940
941 for (uint p: UUIDs_all) {
942 const std::vector<vec3> &vertices = getPrimitivePointer_private(p)->getVertices();
943
944 for (auto &vertex: vertices) {
945 if (vertex.x < xbounds.x || vertex.x > xbounds.y) {
947 break;
948 }
949 }
950 }
951
952 if (getPrimitiveCount() == 0) {
953 std::cerr << "WARNING (Context::cropDomainX): No primitives were inside cropped area, and thus all primitives were deleted." << std::endl;
954 }
955}
956
957void Context::cropDomainY(const vec2 &ybounds) {
958 const std::vector<uint> &UUIDs_all = getAllUUIDs();
959
960 for (uint p: UUIDs_all) {
961 const std::vector<vec3> &vertices = getPrimitivePointer_private(p)->getVertices();
962
963 for (auto &vertex: vertices) {
964 if (vertex.y < ybounds.x || vertex.y > ybounds.y) {
966 break;
967 }
968 }
969 }
970
971 if (getPrimitiveCount() == 0) {
972 std::cerr << "WARNING (Context::cropDomainY): No primitives were inside cropped area, and thus all primitives were deleted." << std::endl;
973 }
974}
975
976void Context::cropDomainZ(const vec2 &zbounds) {
977 const std::vector<uint> &UUIDs_all = getAllUUIDs();
978
979 for (uint p: UUIDs_all) {
980 const std::vector<vec3> &vertices = getPrimitivePointer_private(p)->getVertices();
981
982 for (auto &vertex: vertices) {
983 if (vertex.z < zbounds.x || vertex.z > zbounds.y) {
985 break;
986 }
987 }
988 }
989
990 if (getPrimitiveCount() == 0) {
991 std::cerr << "WARNING (Context::cropDomainZ): No primitives were inside cropped area, and thus all primitives were deleted." << std::endl;
992 }
993}
994
995void Context::cropDomain(std::vector<uint> &UUIDs, const vec2 &xbounds, const vec2 &ybounds, const vec2 &zbounds) {
996 size_t delete_count = 0;
997 for (uint UUID: UUIDs) {
998 const std::vector<vec3> &vertices = getPrimitivePointer_private(UUID)->getVertices();
999
1000 for (auto &vertex: vertices) {
1001 if (vertex.x < xbounds.x || vertex.x > xbounds.y || vertex.y < ybounds.x || vertex.y > ybounds.y || vertex.z < zbounds.x || vertex.z > zbounds.y) {
1002 deletePrimitive(UUID);
1003 delete_count++;
1004 break;
1005 }
1006 }
1007 }
1008
1009 if (delete_count == UUIDs.size()) {
1010 std::cerr << "WARNING (Context::cropDomain): No specified primitives were entirely inside cropped area, and thus all specified primitives were deleted." << std::endl;
1011 }
1012
1013 cleanDeletedUUIDs(UUIDs);
1014}
1015
1016void Context::cropDomain(const vec2 &xbounds, const vec2 &ybounds, const vec2 &zbounds) {
1017 std::vector<uint> UUIDs = getAllUUIDs();
1018 cropDomain(UUIDs, xbounds, ybounds, zbounds);
1019}
1020
1021
1023#ifdef HELIOS_DEBUG
1024 if (!doesObjectExist(objID)) {
1025 helios_runtime_error("ERROR (Context::areObjectPrimitivesComplete): Object ID of " + std::to_string(objID) + " does not exist in the context.");
1026 }
1027#endif
1028 return getObjectPointer_private(objID)->arePrimitivesComplete();
1029}
1030
1031void Context::cleanDeletedObjectIDs(std::vector<uint> &objIDs) const {
1032 for (auto it = objIDs.begin(); it != objIDs.end();) {
1033 if (!doesObjectExist(*it)) {
1034 it = objIDs.erase(it);
1035 } else {
1036 ++it;
1037 }
1038 }
1039}
1040
1041void Context::cleanDeletedObjectIDs(std::vector<std::vector<uint>> &objIDs) const {
1042 for (auto &vec: objIDs) {
1043 for (auto it = vec.begin(); it != vec.end();) {
1044 if (!doesObjectExist(*it)) {
1045 it = vec.erase(it);
1046 } else {
1047 ++it;
1048 }
1049 }
1050 }
1051}
1052
1053void Context::cleanDeletedObjectIDs(std::vector<std::vector<std::vector<uint>>> &objIDs) const {
1054 for (auto &vec2D: objIDs) {
1055 for (auto &vec: vec2D) {
1056 for (auto it = vec.begin(); it != vec.end();) {
1057 if (!doesObjectExist(*it)) {
1058 it = vec.erase(it);
1059 } else {
1060 ++it;
1061 }
1062 }
1063 }
1064 }
1065}
1066
1068 return objects.size();
1069}
1070
1071bool Context::doesObjectExist(const uint ObjID) const {
1072 return objects.find(ObjID) != objects.end();
1073}
1074
1075std::vector<uint> Context::getAllObjectIDs() const {
1076 std::vector<uint> objIDs;
1077 objIDs.reserve(objects.size());
1078 size_t i = 0;
1079 for (auto [objID, object]: objects) {
1080 if (object->ishidden) {
1081 continue;
1082 }
1083 objIDs.push_back(objID);
1084 i++;
1085 }
1086 return objIDs;
1087}
1088
1089void Context::deleteObject(const std::vector<uint> &ObjIDs) {
1090 for (const uint ObjID: ObjIDs) {
1091 deleteObject(ObjID);
1092 }
1093}
1094
1096 if (objects.find(ObjID) == objects.end()) {
1097 helios_runtime_error("ERROR (Context::deleteObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1098 }
1099
1100 CompoundObject *obj = objects.at(ObjID);
1101
1102 for (const auto &[label, type]: obj->object_data_types) {
1103 decrementObjectDataLabelCounter(label);
1104 }
1105
1106 const std::vector<uint> &UUIDs = obj->getPrimitiveUUIDs();
1107
1108
1109 delete obj;
1110 objects.erase(ObjID);
1111
1112 deletePrimitive(UUIDs);
1113}
1114
1115std::vector<uint> Context::copyObject(const std::vector<uint> &ObjIDs) {
1116 std::vector<uint> ObjIDs_copy(ObjIDs.size());
1117 size_t i = 0;
1118 for (uint ObjID: ObjIDs) {
1119 ObjIDs_copy.at(i) = copyObject(ObjID);
1120 i++;
1121 }
1122
1123 return ObjIDs_copy;
1124}
1125
1127 if (objects.find(ObjID) == objects.end()) {
1128 helios_runtime_error("ERROR (Context::copyObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1129 }
1130
1131 ObjectType type = objects.at(ObjID)->getObjectType();
1132
1133 const std::vector<uint> &UUIDs = getObjectPointer_private(ObjID)->getPrimitiveUUIDs();
1134
1135 const std::vector<uint> &UUIDs_copy = copyPrimitive(UUIDs);
1136 for (uint p: UUIDs_copy) {
1137 getPrimitivePointer_private(p)->setParentObjectID(currentObjectID);
1138 }
1139
1140 const std::string &texturefile = objects.at(ObjID)->getTextureFile();
1141
1142 if (type == OBJECT_TYPE_TILE) {
1143 Tile *o = getTileObjectPointer_private(ObjID);
1144
1145 const int2 &subdiv = o->getSubdivisionCount();
1146
1147 auto *tile_new = (new Tile(currentObjectID, UUIDs_copy, subdiv, texturefile.c_str(), this));
1148
1149 objects[currentObjectID] = tile_new;
1150 } else if (type == OBJECT_TYPE_SPHERE) {
1151 Sphere *o = getSphereObjectPointer_private(ObjID);
1152
1153 uint subdiv = o->getSubdivisionCount();
1154
1155 auto *sphere_new = (new Sphere(currentObjectID, UUIDs_copy, subdiv, texturefile.c_str(), this));
1156
1157 objects[currentObjectID] = sphere_new;
1158 } else if (type == OBJECT_TYPE_TUBE) {
1159 Tube *o = getTubeObjectPointer_private(ObjID);
1160
1161 const std::vector<vec3> &nodes = o->getNodes();
1162 const std::vector<float> &radius = o->getNodeRadii();
1163 const std::vector<RGBcolor> &colors = o->getNodeColors();
1164 const std::vector<std::vector<vec3>> &triangle_vertices = o->getTriangleVertices();
1165 uint subdiv = o->getSubdivisionCount();
1166
1167 auto *tube_new = (new Tube(currentObjectID, UUIDs_copy, nodes, radius, colors, triangle_vertices, subdiv, texturefile.c_str(), this));
1168
1169 objects[currentObjectID] = tube_new;
1170 } else if (type == OBJECT_TYPE_BOX) {
1171 Box *o = getBoxObjectPointer_private(ObjID);
1172
1173 const int3 &subdiv = o->getSubdivisionCount();
1174
1175 auto *box_new = (new Box(currentObjectID, UUIDs_copy, subdiv, texturefile.c_str(), this));
1176
1177 objects[currentObjectID] = box_new;
1178 } else if (type == OBJECT_TYPE_DISK) {
1179 Disk *o = getDiskObjectPointer_private(ObjID);
1180
1181 const int2 &subdiv = o->getSubdivisionCount();
1182
1183 auto *disk_new = (new Disk(currentObjectID, UUIDs_copy, subdiv, texturefile.c_str(), this));
1184
1185 objects[currentObjectID] = disk_new;
1186 } else if (type == OBJECT_TYPE_POLYMESH) {
1187 auto *polymesh_new = (new Polymesh(currentObjectID, UUIDs_copy, texturefile.c_str(), this));
1188
1189 objects[currentObjectID] = polymesh_new;
1190 } else if (type == OBJECT_TYPE_CONE) {
1191 Cone *o = getConeObjectPointer_private(ObjID);
1192
1193 const std::vector<vec3> &nodes = o->getNodeCoordinates();
1194 const std::vector<float> &radius = o->getNodeRadii();
1195 uint subdiv = o->getSubdivisionCount();
1196
1197 auto *cone_new = (new Cone(currentObjectID, UUIDs_copy, nodes.at(0), nodes.at(1), radius.at(0), radius.at(1), subdiv, texturefile.c_str(), this));
1198
1199 objects[currentObjectID] = cone_new;
1200 }
1201
1202 copyObjectData(ObjID, currentObjectID);
1203
1204 float T[16];
1205 getObjectPointer_private(ObjID)->getTransformationMatrix(T);
1206
1207 getObjectPointer_private(currentObjectID)->setTransformationMatrix(T);
1208
1209 currentObjectID++;
1210 return currentObjectID - 1;
1211}
1212
1213std::vector<uint> Context::filterObjectsByData(const std::vector<uint> &IDs, const char *object_data, float threshold, const char *comparator) const {
1214 std::vector<uint> output_object_IDs;
1215 output_object_IDs.resize(IDs.size());
1216 uint passed_count = 0;
1217
1218 for (uint i = 0; i < IDs.size(); i++) {
1219 if (doesObjectDataExist(IDs.at(i), object_data)) {
1220 HeliosDataType type = getObjectDataType(object_data);
1221 if (type == HELIOS_TYPE_UINT) {
1222 uint R;
1223 getObjectData(IDs.at(i), object_data, R);
1224 if (strcmp(comparator, "<") == 0) {
1225 if (float(R) < threshold) {
1226 output_object_IDs.at(passed_count) = IDs.at(i);
1227 passed_count++;
1228 }
1229 } else if (strcmp(comparator, ">") == 0) {
1230 if (float(R) > threshold) {
1231 output_object_IDs.at(passed_count) = IDs.at(i);
1232 passed_count++;
1233 }
1234 } else if (strcmp(comparator, "=") == 0) {
1235 if (float(R) == threshold) {
1236 output_object_IDs.at(passed_count) = IDs.at(i);
1237 passed_count++;
1238 }
1239 }
1240 } else if (type == HELIOS_TYPE_FLOAT) {
1241 float R;
1242 getObjectData(IDs.at(i), object_data, R);
1243
1244 if (strcmp(comparator, "<") == 0) {
1245 if (R < threshold) {
1246 output_object_IDs.at(passed_count) = IDs.at(i);
1247 passed_count++;
1248 }
1249 } else if (strcmp(comparator, ">") == 0) {
1250 if (R > threshold) {
1251 output_object_IDs.at(passed_count) = IDs.at(i);
1252 passed_count++;
1253 }
1254 } else if (strcmp(comparator, "=") == 0) {
1255 if (R == threshold) {
1256 output_object_IDs.at(passed_count) = IDs.at(i);
1257 passed_count++;
1258 }
1259 }
1260 } else if (type == HELIOS_TYPE_INT) {
1261 int R;
1262 getObjectData(IDs.at(i), object_data, R);
1263
1264 if (strcmp(comparator, "<") == 0) {
1265 if (float(R) < threshold) {
1266 output_object_IDs.at(passed_count) = IDs.at(i);
1267 passed_count++;
1268 }
1269 } else if (strcmp(comparator, ">") == 0) {
1270 if (float(R) > threshold) {
1271 output_object_IDs.at(passed_count) = IDs.at(i);
1272 passed_count++;
1273 }
1274 } else if (strcmp(comparator, "=") == 0) {
1275 if (float(R) == threshold) {
1276 output_object_IDs.at(passed_count) = IDs.at(i);
1277 passed_count++;
1278 }
1279 }
1280 } else {
1281 std::cerr << "WARNING: Object data not of type UINT, INT, or FLOAT. Filtering for other types not yet supported." << std::endl;
1282 }
1283 }
1284 }
1285
1286 output_object_IDs.resize(passed_count);
1287
1288 return output_object_IDs;
1289}
1290
1291void Context::translateObject(uint ObjID, const vec3 &shift) const {
1292#ifdef HELIOS_DEBUG
1293 if (!doesObjectExist(ObjID)) {
1294 helios_runtime_error("ERROR (Context::translateObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1295 }
1296#endif
1297 getObjectPointer_private(ObjID)->translate(shift);
1298}
1299
1300void Context::translateObject(const std::vector<uint> &ObjIDs, const vec3 &shift) const {
1301 for (uint ID: ObjIDs) {
1302 translateObject(ID, shift);
1303 }
1304}
1305
1306void Context::rotateObject(uint ObjID, float rotation_radians, const char *rotation_axis_xyz) const {
1307#ifdef HELIOS_DEBUG
1308 if (!doesObjectExist(ObjID)) {
1309 helios_runtime_error("ERROR (Context::rotateObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1310 }
1311#endif
1312 getObjectPointer_private(ObjID)->rotate(rotation_radians, rotation_axis_xyz);
1313}
1314
1315void Context::rotateObject(const std::vector<uint> &ObjIDs, float rotation_radians, const char *rotation_axis_xyz) const {
1316 for (uint ID: ObjIDs) {
1317 rotateObject(ID, rotation_radians, rotation_axis_xyz);
1318 }
1319}
1320
1321void Context::rotateObject(uint ObjID, float rotation_radians, const vec3 &rotation_axis_vector) const {
1322#ifdef HELIOS_DEBUG
1323 if (!doesObjectExist(ObjID)) {
1324 helios_runtime_error("ERROR (Context::rotateObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1325 }
1326#endif
1327 getObjectPointer_private(ObjID)->rotate(rotation_radians, rotation_axis_vector);
1328}
1329
1330void Context::rotateObject(const std::vector<uint> &ObjIDs, float rotation_radians, const vec3 &rotation_axis_vector) const {
1331 for (uint ID: ObjIDs) {
1332 rotateObject(ID, rotation_radians, rotation_axis_vector);
1333 }
1334}
1335
1336void Context::rotateObject(uint ObjID, float rotation_radians, const vec3 &rotation_origin, const vec3 &rotation_axis_vector) const {
1337#ifdef HELIOS_DEBUG
1338 if (!doesObjectExist(ObjID)) {
1339 helios_runtime_error("ERROR (Context::rotateObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1340 }
1341#endif
1342 getObjectPointer_private(ObjID)->rotate(rotation_radians, rotation_origin, rotation_axis_vector);
1343}
1344
1345void Context::rotateObject(const std::vector<uint> &ObjIDs, float rotation_radians, const vec3 &rotation_origin, const vec3 &rotation_axis_vector) const {
1346 for (uint ID: ObjIDs) {
1347 rotateObject(ID, rotation_radians, rotation_origin, rotation_axis_vector);
1348 }
1349}
1350
1351void Context::rotateObjectAboutOrigin(uint ObjID, float rotation_radians, const vec3 &rotation_axis_vector) const {
1352#ifdef HELIOS_DEBUG
1353 if (!doesObjectExist(ObjID)) {
1354 helios_runtime_error("ERROR (Context::rotateObjectAboutOrigin): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1355 }
1356#endif
1357 getObjectPointer_private(ObjID)->rotate(rotation_radians, objects.at(ObjID)->object_origin, rotation_axis_vector);
1358}
1359
1360void Context::rotateObjectAboutOrigin(const std::vector<uint> &ObjIDs, float rotation_radians, const vec3 &rotation_axis_vector) const {
1361 for (uint ID: ObjIDs) {
1362 rotateObject(ID, rotation_radians, objects.at(ID)->object_origin, rotation_axis_vector);
1363 }
1364}
1365
1366void Context::scaleObject(uint ObjID, const helios::vec3 &scalefact) const {
1367#ifdef HELIOS_DEBUG
1368 if (!doesObjectExist(ObjID)) {
1369 helios_runtime_error("ERROR (Context::scaleObject): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1370 }
1371#endif
1372 getObjectPointer_private(ObjID)->scale(scalefact);
1373}
1374
1375void Context::scaleObject(const std::vector<uint> &ObjIDs, const helios::vec3 &scalefact) const {
1376 for (uint ID: ObjIDs) {
1377 scaleObject(ID, scalefact);
1378 }
1379}
1380
1381void Context::scaleObjectAboutCenter(uint ObjID, const helios::vec3 &scalefact) const {
1382#ifdef HELIOS_DEBUG
1383 if (!doesObjectExist(ObjID)) {
1384 helios_runtime_error("ERROR (Context::scaleObjectAboutCenter): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1385 }
1386#endif
1387 getObjectPointer_private(ObjID)->scaleAboutCenter(scalefact);
1388}
1389
1390void Context::scaleObjectAboutCenter(const std::vector<uint> &ObjIDs, const helios::vec3 &scalefact) const {
1391 for (uint ID: ObjIDs) {
1392 scaleObjectAboutCenter(ID, scalefact);
1393 }
1394}
1395
1396void Context::scaleObjectAboutPoint(uint ObjID, const helios::vec3 &scalefact, const helios::vec3 &point) const {
1397#ifdef HELIOS_DEBUG
1398 if (!doesObjectExist(ObjID)) {
1399 helios_runtime_error("ERROR (Context::scaleObjectAboutPoint): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1400 }
1401#endif
1402 getObjectPointer_private(ObjID)->scaleAboutPoint(scalefact, point);
1403}
1404
1405void Context::scaleObjectAboutPoint(const std::vector<uint> &ObjIDs, const helios::vec3 &scalefact, const helios::vec3 &point) const {
1406 for (uint ID: ObjIDs) {
1407 scaleObjectAboutPoint(ID, scalefact, point);
1408 }
1409}
1410
1411void Context::scaleObjectAboutOrigin(uint ObjID, const helios::vec3 &scalefact) const {
1412#ifdef HELIOS_DEBUG
1413 if (!doesObjectExist(ObjID)) {
1414 helios_runtime_error("ERROR (Context::scaleObjectAboutOrigin): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1415 }
1416#endif
1417 getObjectPointer_private(ObjID)->scaleAboutPoint(scalefact, objects.at(ObjID)->object_origin);
1418}
1419
1420void Context::scaleObjectAboutOrigin(const std::vector<uint> &ObjIDs, const helios::vec3 &scalefact) const {
1421 for (uint ID: ObjIDs) {
1422 scaleObjectAboutPoint(ID, scalefact, objects.at(ID)->object_origin);
1423 }
1424}
1425
1426std::vector<uint> Context::getObjectPrimitiveUUIDs(uint ObjID) const {
1427#ifdef HELIOS_DEBUG
1428 if (!doesObjectExist(ObjID) && ObjID != 0) {
1429 helios_runtime_error("ERROR (Context::getObjectPrimitiveUUIDs): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1430 }
1431#endif
1432
1433 if (ObjID == 0) {
1434 // \todo This is inefficient and should be improved by storing the UUIDs for all objID = 0 primitives in the Context.
1435 std::vector<uint> UUIDs;
1436 UUIDs.reserve(getPrimitiveCount());
1437 for (uint UUID: getAllUUIDs()) {
1438 if (getPrimitiveParentObjectID(UUID) == 0) {
1439 UUIDs.push_back(UUID);
1440 }
1441 }
1442 return UUIDs;
1443 }
1444
1445 return getObjectPointer_private(ObjID)->getPrimitiveUUIDs();
1446}
1447
1448std::vector<uint> Context::getObjectPrimitiveUUIDs(const std::vector<uint> &ObjIDs) const {
1449 std::vector<uint> output_UUIDs;
1450
1451 for (uint ObjID: ObjIDs) {
1452#ifdef HELIOS_DEBUG
1453 if (!doesObjectExist(ObjID)) {
1454 helios_runtime_error("ERROR (Context::getObjectPrimitiveUUIDs): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1455 }
1456#endif
1457 const std::vector<uint> &current_UUIDs = getObjectPrimitiveUUIDs(ObjID);
1458 output_UUIDs.insert(output_UUIDs.end(), current_UUIDs.begin(), current_UUIDs.end());
1459 }
1460 return output_UUIDs;
1461}
1462
1463std::vector<uint> Context::getObjectPrimitiveUUIDs(const std::vector<std::vector<uint>> &ObjIDs) const {
1464 std::vector<uint> output_UUIDs;
1465
1466 for (uint j = 0; j < ObjIDs.size(); j++) {
1467 for (uint i = 0; i < ObjIDs.at(j).size(); i++) {
1468#ifdef HELIOS_DEBUG
1469 if (!doesObjectExist(ObjIDs.at(j).at(i))) {
1470 helios_runtime_error("ERROR (Context::getObjectPrimitiveUUIDs): Object ID of " + std::to_string(ObjIDs.at(j).at(i)) + " not found in the context.");
1471 }
1472#endif
1473
1474 const std::vector<uint> &current_UUIDs = getObjectPointer_private(ObjIDs.at(j).at(i))->getPrimitiveUUIDs();
1475 output_UUIDs.insert(output_UUIDs.end(), current_UUIDs.begin(), current_UUIDs.end());
1476 }
1477 }
1478 return output_UUIDs;
1479}
1480
1482 if (ObjID == 0) {
1483 return OBJECT_TYPE_NONE;
1484 }
1485#ifdef HELIOS_DEBUG
1486 if (!doesObjectExist(ObjID)) {
1487 helios_runtime_error("ERROR (Context::getObjectType): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1488 }
1489#endif
1490 return getObjectPointer_private(ObjID)->getObjectType();
1491}
1492
1494#ifdef HELIOS_DEBUG
1495 if (!doesObjectExist(ObjID)) {
1496 helios_runtime_error("ERROR (Context::getTileObjectAreaRatio): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1497 }
1498#endif
1499 if (getObjectPointer_private(ObjID)->getObjectType() != OBJECT_TYPE_TILE) {
1500 std::cerr << "WARNING (Context::getTileObjectAreaRatio): ObjectID " << ObjID << " is not a tile object. Skipping..." << std::endl;
1501 return 0.0;
1502 }
1503
1504 if (!(getObjectPointer_private(ObjID)->arePrimitivesComplete())) {
1505 std::cerr << "WARNING (Context::getTileObjectAreaRatio): ObjectID " << ObjID << " is missing primitives. Area ratio calculated is area of non-missing subpatches divided by the area of an individual subpatch." << std::endl;
1506 }
1507
1508 const int2 &subdiv = getTileObjectPointer_private(ObjID)->getSubdivisionCount();
1509 if (subdiv.x == 1 && subdiv.y == 1) {
1510 return 1.0;
1511 }
1512
1513 float area = getTileObjectPointer_private(ObjID)->getArea();
1514 const vec2 size = getTileObjectPointer_private(ObjID)->getSize();
1515
1516 float subpatch_area = size.x * size.y / scast<float>(subdiv.x * subdiv.y);
1517 return area / subpatch_area;
1518}
1519
1520std::vector<float> Context::getTileObjectAreaRatio(const std::vector<uint> &ObjIDs) const {
1521 std::vector<float> AreaRatios(ObjIDs.size());
1522 for (uint i = 0; i < ObjIDs.size(); i++) {
1523 AreaRatios.at(i) = getTileObjectAreaRatio(ObjIDs.at(i));
1524 }
1525
1526 return AreaRatios;
1527}
1528
1529void Context::setTileObjectSubdivisionCount(const std::vector<uint> &ObjIDs, const int2 &new_subdiv) {
1530 // check that all objects are Tile Objects, and get vector of texture files
1531 std::vector<uint> tile_ObjectIDs;
1532 std::vector<uint> textured_tile_ObjectIDs;
1533
1534
1535 std::vector<std::string> tex;
1536
1537 for (uint ObjID: ObjIDs) {
1538#ifdef HELIOS_DEBUG
1539 if (!doesObjectExist(ObjID)) {
1540 helios_runtime_error("ERROR (Context::setTileObjectSubdivisionCount): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1541 }
1542#endif
1543
1544 // check if the object ID is a tile object and if it is add it the tile_ObjectIDs vector
1545 if (getObjectPointer_private(ObjID)->getObjectType() != OBJECT_TYPE_TILE) {
1546 std::cerr << "WARNING (Context::setTileObjectSubdivisionCount): ObjectID " << ObjID << " is not a tile object. Skipping..." << std::endl;
1547 } else if (!(getObjectPointer_private(ObjID)->arePrimitivesComplete())) {
1548 std::cerr << "WARNING (Context::setTileObjectSubdivisionCount): ObjectID " << ObjID << " is missing primitives. Skipping..." << std::endl;
1549 } else {
1550 // test if the tile is textured and push into two different vectors
1551 Patch *p = getPatchPointer_private(getObjectPointer_private(ObjID)->getPrimitiveUUIDs().at(0));
1552 if (!p->hasTexture()) { // no texture
1553 tile_ObjectIDs.push_back(ObjID);
1554 } else { // texture
1555 textured_tile_ObjectIDs.push_back(ObjID);
1556 tex.push_back(p->getTextureFile());
1557 }
1558 }
1559 }
1560
1561 // Here just call setSubdivisionCount directly for the non-textured tile objects
1562 for (unsigned int tile_ObjectID: tile_ObjectIDs) {
1563 Tile *current_object_pointer = getTileObjectPointer_private(tile_ObjectID);
1564 const std::vector<uint> &UUIDs_old = current_object_pointer->getPrimitiveUUIDs();
1565
1566 vec2 size = current_object_pointer->getSize();
1567 vec3 center = current_object_pointer->getCenter();
1568 vec3 normal = current_object_pointer->getNormal();
1569 SphericalCoord rotation = cart2sphere(normal);
1570 RGBcolor color = getPrimitiveColor(UUIDs_old.front());
1571
1572 std::vector<uint> UUIDs_new = addTile(center, size, rotation, new_subdiv, color);
1573
1574 for (uint UUID: UUIDs_new) {
1575 getPrimitivePointer_private(UUID)->setParentObjectID(tile_ObjectID);
1576 }
1577
1578 current_object_pointer->setPrimitiveUUIDs(UUIDs_new);
1579 current_object_pointer->setSubdivisionCount(new_subdiv);
1580 deletePrimitive(UUIDs_old);
1581 }
1582
1583 // get a vector of unique texture files that are represented in the input tile objects
1584 sort(tex.begin(), tex.end());
1585 std::vector<std::string>::iterator it;
1586 it = std::unique(tex.begin(), tex.end());
1587 tex.resize(std::distance(tex.begin(), it));
1588
1589 // create object templates for all the unique texture files
1590 std::vector<uint> object_templates;
1591 std::vector<std::vector<uint>> template_primitives;
1592 for (uint j = 0; j < tex.size(); j++) {
1593 // create a template object for the current texture
1594 uint object_template = addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, new_subdiv, tex.at(j).c_str());
1595 object_templates.emplace_back(object_template);
1596 std::vector<uint> object_primitives = getTileObjectPointer_private(object_template)->getPrimitiveUUIDs();
1597 template_primitives.emplace_back(object_primitives);
1598 }
1599
1600 // keep loop over objects on the outside, otherwise need to update textured_tile_ObjectIDs vector all the time
1601 // for each textured tile object
1602 for (uint i = 0; i < textured_tile_ObjectIDs.size(); i++) {
1603 // get info from current object
1604 Tile *current_object_pointer = getTileObjectPointer_private(textured_tile_ObjectIDs.at(i));
1605 std::string current_texture_file = current_object_pointer->getTextureFile();
1606
1607 std::vector<uint> UUIDs_old = current_object_pointer->getPrimitiveUUIDs();
1608
1609 vec2 size = current_object_pointer->getSize();
1610 vec3 center = current_object_pointer->getCenter();
1611 vec3 normal = current_object_pointer->getNormal();
1612 SphericalCoord rotation = cart2sphere(normal);
1613
1614 // for unique textures
1615 for (uint j = 0; j < tex.size(); j++) {
1616 // if the current tile object has the same texture file as the current unique texture file
1617 if (current_texture_file == tex.at(j)) {
1618 // copy the template primitives and create a new tile with them
1619 std::vector<uint> new_primitives = copyPrimitive(template_primitives.at(j));
1620
1621 // change the objectID for the new primitives
1622 setPrimitiveParentObjectID(new_primitives, textured_tile_ObjectIDs.at(i));
1623 current_object_pointer->setPrimitiveUUIDs(new_primitives);
1624 current_object_pointer->setSubdivisionCount(new_subdiv);
1625
1626 // delete the original object primitives
1627 deletePrimitive(UUIDs_old);
1628
1629 float IM[16];
1631 current_object_pointer->setTransformationMatrix(IM);
1632
1633 current_object_pointer->scale(make_vec3(size.x, size.y, 1));
1634
1635 // transform based on original object data
1636 if (rotation.elevation != 0) {
1637 current_object_pointer->rotate(-rotation.elevation, "x");
1638 }
1639 if (rotation.azimuth != 0) {
1640 current_object_pointer->rotate(rotation.azimuth, "z");
1641 }
1642 current_object_pointer->translate(center);
1643 }
1644 }
1645 }
1646
1647
1648 // delete the template (objects and primitives)
1649 deleteObject(object_templates);
1650}
1651
1652void Context::setTileObjectSubdivisionCount(const std::vector<uint> &ObjIDs, float area_ratio) {
1653 // check that all objects are Tile Objects, and get vector of texture files
1654 std::vector<uint> tile_ObjectIDs;
1655 std::vector<uint> textured_tile_ObjectIDs;
1656
1657 std::vector<std::string> tex;
1658 // for(uint i=1;i<ObjectIDs.size();i++)
1659 for (uint ObjID: ObjIDs) {
1660#ifdef HELIOS_DEBUG
1661 if (!doesObjectExist(ObjID)) {
1662 helios_runtime_error("ERROR (Context::setTileObjectSubdivisionCount): Object ID of " + std::to_string(ObjID) + " not found in the context.");
1663 }
1664#endif
1665
1666 // check if the object ID is a tile object and if it is add it the tile_ObjectIDs vector
1667 if (getObjectPointer_private(ObjID)->getObjectType() != OBJECT_TYPE_TILE) {
1668 std::cerr << "WARNING (Context::setTileObjectSubdivisionCount): ObjectID " << ObjID << " is not a tile object. Skipping..." << std::endl;
1669 } else if (!(getObjectPointer_private(ObjID)->arePrimitivesComplete())) {
1670 std::cerr << "WARNING (Context::setTileObjectSubdivisionCount): ObjectID " << ObjID << " is missing primitives. Skipping..." << std::endl;
1671 } else {
1672 // test if the tile is textured and push into two different vectors
1673 Patch *p = getPatchPointer_private(getObjectPointer_private(ObjID)->getPrimitiveUUIDs().at(0));
1674 if (!p->hasTexture()) { // no texture
1675 tile_ObjectIDs.push_back(ObjID);
1676 } else { // texture
1677 textured_tile_ObjectIDs.push_back(ObjID);
1678 tex.push_back(p->getTextureFile());
1679 }
1680 }
1681 }
1682
1683 // Here just call setSubdivisionCount directly for the non-textured tile objects
1684 for (uint i = 0; i < tile_ObjectIDs.size(); i++) {
1685 Tile *current_object_pointer = getTileObjectPointer_private(tile_ObjectIDs.at(i));
1686 std::vector<uint> UUIDs_old = current_object_pointer->getPrimitiveUUIDs();
1687
1688 vec2 size = current_object_pointer->getSize();
1689 vec3 center = current_object_pointer->getCenter();
1690 vec3 normal = current_object_pointer->getNormal();
1691 SphericalCoord rotation = cart2sphere(normal);
1692 RGBcolor color = getPrimitiveColor(UUIDs_old.front());
1693
1694 float tile_area = current_object_pointer->getArea();
1695
1696 // subpatch dimensions needed to keep the correct ratio and have the solid fraction area = the input area
1697 float subpatch_dimension = sqrtf(tile_area / area_ratio);
1698 float subpatch_per_x = size.x / subpatch_dimension;
1699 float subpatch_per_y = size.y / subpatch_dimension;
1700
1701 float option_1_AR = (tile_area / (size.x / ceil(subpatch_per_x) * size.y / floor(subpatch_per_y))) - area_ratio;
1702 float option_2_AR = (tile_area / (size.x / floor(subpatch_per_x) * size.y / ceil(subpatch_per_y))) - area_ratio;
1703
1704 int2 new_subdiv;
1705 if ((int) area_ratio == 1) {
1706 new_subdiv = make_int2(1, 1);
1707 } else if (option_1_AR >= option_2_AR) {
1708 new_subdiv = make_int2(ceil(subpatch_per_x), floor(subpatch_per_y));
1709 } else {
1710 new_subdiv = make_int2(floor(subpatch_per_x), ceil(subpatch_per_y));
1711 }
1712
1713
1714 std::vector<uint> UUIDs_new = addTile(center, size, rotation, new_subdiv, color);
1715
1716 for (uint UUID: UUIDs_new) {
1717 getPrimitivePointer_private(UUID)->setParentObjectID(tile_ObjectIDs.at(i));
1718 }
1719
1720 current_object_pointer->setPrimitiveUUIDs(UUIDs_new);
1721 current_object_pointer->setSubdivisionCount(new_subdiv);
1722 deletePrimitive(UUIDs_old);
1723 }
1724
1725 // get a vector of unique texture files that are represented in the input tile objects
1726 sort(tex.begin(), tex.end());
1727 std::vector<std::string>::iterator it;
1728 it = std::unique(tex.begin(), tex.end());
1729 tex.resize(std::distance(tex.begin(), it));
1730
1731 // create object templates for all the unique texture files
1732 // the assumption here is that all tile objects with the same texture have the same aspect ratio
1733 // if this is not true then the copying method won't work well because a new template will need to be created for each texture/aspect ratio combination
1734
1735 std::vector<uint> object_templates;
1736 std::vector<std::vector<uint>> template_primitives;
1737 for (uint j = 0; j < tex.size(); j++) {
1738 // here we just want to get one tile object with the matching texture
1739 uint ii;
1740 for (uint i = 0; i < textured_tile_ObjectIDs.size(); i++) {
1741 // get info from current object
1742 Tile *current_object_pointer_b = getTileObjectPointer_private(textured_tile_ObjectIDs.at(i));
1743 std::string current_texture_file_b = current_object_pointer_b->getTextureFile();
1744 // if the current tile object has the same texture file as the current unique texture file
1745 if (current_texture_file_b == tex.at(j)) {
1746 ii = i;
1747 break;
1748 }
1749 }
1750
1751 // get info from current object
1752 Tile *current_object_pointer = getTileObjectPointer_private(textured_tile_ObjectIDs.at(ii));
1753 vec2 tile_size = current_object_pointer->getSize();
1754 float tile_area = current_object_pointer->getArea();
1755
1756 // subpatch dimensions needed to keep the correct ratio and have the solid fraction area = the input area
1757 float subpatch_dimension = sqrtf(tile_area / area_ratio);
1758 float subpatch_per_x = tile_size.x / subpatch_dimension;
1759 float subpatch_per_y = tile_size.y / subpatch_dimension;
1760
1761 float option_1_AR = (tile_area / (tile_size.x / ceil(subpatch_per_x) * tile_size.y / floor(subpatch_per_y))) - area_ratio;
1762 float option_2_AR = (tile_area / (tile_size.x / floor(subpatch_per_x) * tile_size.y / ceil(subpatch_per_y))) - area_ratio;
1763
1764 int2 new_subdiv;
1765 if ((int) area_ratio == 1) {
1766 new_subdiv = make_int2(1, 1);
1767 } else if (option_1_AR >= option_2_AR) {
1768 new_subdiv = make_int2(ceil(subpatch_per_x), floor(subpatch_per_y));
1769 } else {
1770 new_subdiv = make_int2(floor(subpatch_per_x), ceil(subpatch_per_y));
1771 }
1772
1773 // create a template object for the current texture
1774 uint object_template = addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, new_subdiv, tex.at(j).c_str());
1775 object_templates.emplace_back(object_template);
1776 std::vector<uint> object_primitives = getTileObjectPointer_private(object_template)->getPrimitiveUUIDs();
1777 template_primitives.emplace_back(object_primitives);
1778 }
1779
1780 // keep loop over objects on the outside, otherwise need to update textured_tile_ObjectIDs vector all the time
1781 // for each textured tile object
1782 for (uint i = 0; i < textured_tile_ObjectIDs.size(); i++) {
1783 // get info from current object
1784 Tile *current_object_pointer = getTileObjectPointer_private(textured_tile_ObjectIDs.at(i));
1785 // std::string current_texture_file = getPrimitivePointer_private(current_object_pointer->getPrimitiveUUIDs().at(0))->getTextureFile();
1786 std::string current_texture_file = current_object_pointer->getTextureFile();
1787 // std::cout << "current_texture_file for ObjID " << textured_tile_ObjectIDs.at(i) << " = " << current_texture_file << std::endl;
1788 std::vector<uint> UUIDs_old = current_object_pointer->getPrimitiveUUIDs();
1789
1790 vec2 size = current_object_pointer->getSize();
1791 vec3 center = current_object_pointer->getCenter();
1792 vec3 normal = current_object_pointer->getNormal();
1793 SphericalCoord rotation = cart2sphere(normal);
1794
1795 // for unique textures
1796 for (uint j = 0; j < tex.size(); j++) {
1797 // if the current tile object has the same texture file as the current unique texture file
1798 if (current_texture_file == tex.at(j)) {
1799 // copy the template primitives and create a new tile with them
1800 std::vector<uint> new_primitives = copyPrimitive(template_primitives.at(j));
1801
1802 // change the objectID for the new primitives
1803 setPrimitiveParentObjectID(new_primitives, textured_tile_ObjectIDs.at(i));
1804
1805 int2 new_subdiv = getTileObjectPointer_private(object_templates.at(j))->getSubdivisionCount();
1806 current_object_pointer->setPrimitiveUUIDs(new_primitives);
1807 current_object_pointer->setSubdivisionCount(new_subdiv);
1808
1809 // delete the original object primitives
1810 deletePrimitive(UUIDs_old);
1811
1812 float IM[16];
1814 current_object_pointer->setTransformationMatrix(IM);
1815
1816 current_object_pointer->scale(make_vec3(size.x, size.y, 1));
1817
1818 if (rotation.elevation != 0) {
1819 current_object_pointer->rotate(-rotation.elevation, "x");
1820 }
1821 if (rotation.azimuth != 0) {
1822 current_object_pointer->rotate(rotation.azimuth, "z");
1823 }
1824 current_object_pointer->translate(center);
1825 }
1826 }
1827 }
1828
1829 // delete the template (objects and primitives)
1830 deleteObject(object_templates);
1831}
1832
1833
1834std::vector<uint> Context::addSphere(uint Ndivs, const vec3 &center, float radius) {
1835 RGBcolor color = make_RGBcolor(0.f, 0.75f, 0.f); // Default color is green
1836
1837 return addSphere(Ndivs, center, radius, color);
1838}
1839
1840std::vector<uint> Context::addSphere(uint Ndivs, const vec3 &center, float radius, const RGBcolor &color) {
1841 std::vector<uint> UUID;
1842
1843 float dtheta = PI_F / float(Ndivs);
1844 float dphi = 2.0f * PI_F / float(Ndivs);
1845
1846 // bottom cap
1847 for (int j = 0; j < Ndivs; j++) {
1848 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F, 0));
1849 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + dtheta, float(j) * dphi));
1850 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + dtheta, float(j + 1) * dphi));
1851
1852 UUID.push_back(addTriangle(v0, v1, v2, color));
1853 }
1854
1855 // top cap
1856 for (int j = 0; j < Ndivs; j++) {
1857 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F, 0));
1858 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F - dtheta, float(j) * dphi));
1859 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F - dtheta, float(j + 1) * dphi));
1860
1861 UUID.push_back(addTriangle(v2, v1, v0, color));
1862 }
1863
1864 // middle
1865 for (int j = 0; j < Ndivs; j++) {
1866 for (int i = 1; i < Ndivs - 1; i++) {
1867 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i) * dtheta, float(j) * dphi));
1868 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i + 1) * dtheta, float(j) * dphi));
1869 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i + 1) * dtheta, float(j + 1) * dphi));
1870 vec3 v3 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i) * dtheta, float(j + 1) * dphi));
1871
1872 UUID.push_back(addTriangle(v0, v1, v2, color));
1873 UUID.push_back(addTriangle(v0, v2, v3, color));
1874 }
1875 }
1876
1877 return UUID;
1878}
1879
1880std::vector<uint> Context::addSphere(uint Ndivs, const vec3 &center, float radius, const char *texturefile) {
1881 if (!validateTextureFileExtenstion(texturefile)) {
1882 helios_runtime_error("ERROR (Context::addSphere): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
1883 } else if (!doesTextureFileExist(texturefile)) {
1884 helios_runtime_error("ERROR (Context::addSphere): Texture file " + std::string(texturefile) + " does not exist.");
1885 }
1886
1887 std::vector<uint> UUID;
1888
1889 float dtheta = PI_F / float(Ndivs);
1890 float dphi = 2.0f * PI_F / float(Ndivs);
1891
1892 // bottom cap
1893 for (int j = 0; j < Ndivs; j++) {
1894 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F, 0));
1895 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + dtheta, float(j) * dphi));
1896 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + dtheta, float(j + 1) * dphi));
1897
1898 vec3 n0 = v0 - center;
1899 n0.normalize();
1900 vec3 n1 = v1 - center;
1901 n1.normalize();
1902 vec3 n2 = v2 - center;
1903 n2.normalize();
1904
1905 vec2 uv0 = make_vec2(1.f - atan2f(sin((float(j) + 0.5f) * dphi), -cos((float(j) + 0.5f) * dphi)) / (2.f * PI_F) - 0.5f, 1.f - n0.z * 0.5f - 0.5f);
1906 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);
1907 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);
1908
1909 if (j == Ndivs - 1) {
1910 uv2.x = 1;
1911 }
1912
1913 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1914 if (getPrimitiveArea(triangle_uuid) > 0) {
1915 UUID.push_back(triangle_uuid);
1916 } else {
1917 deletePrimitive(triangle_uuid);
1918 }
1919 }
1920
1921 // top cap
1922 for (int j = 0; j < Ndivs; j++) {
1923 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F, 0));
1924 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F - dtheta, float(j + 1) * dphi));
1925 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, 0.5f * PI_F - dtheta, float(j) * dphi));
1926
1927 vec3 n0 = v0 - center;
1928 n0.normalize();
1929 vec3 n1 = v1 - center;
1930 n1.normalize();
1931 vec3 n2 = v2 - center;
1932 n2.normalize();
1933
1934 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);
1935 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);
1936 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);
1937
1938 if (j == Ndivs - 1) {
1939 uv2.x = 1;
1940 }
1941
1942 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1943 if (getPrimitiveArea(triangle_uuid) > 0) {
1944 UUID.push_back(triangle_uuid);
1945 } else {
1946 deletePrimitive(triangle_uuid);
1947 }
1948 }
1949
1950 // middle
1951 for (int j = 0; j < Ndivs; j++) {
1952 for (int i = 1; i < Ndivs - 1; i++) {
1953 vec3 v0 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i) * dtheta, float(j) * dphi));
1954 vec3 v1 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i + 1) * dtheta, float(j) * dphi));
1955 vec3 v2 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i + 1) * dtheta, float(j + 1) * dphi));
1956 vec3 v3 = center + sphere2cart(make_SphericalCoord(radius, -0.5f * PI_F + float(i) * dtheta, float(j + 1) * dphi));
1957
1958 vec3 n0 = v0 - center;
1959 n0.normalize();
1960 vec3 n1 = v1 - center;
1961 n1.normalize();
1962 vec3 n2 = v2 - center;
1963 n2.normalize();
1964 vec3 n3 = v3 - center;
1965 n3.normalize();
1966
1967 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);
1968 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);
1969 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);
1970 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);
1971
1972 if (j == Ndivs - 1) {
1973 uv2.x = 1;
1974 uv3.x = 1;
1975 }
1976
1977 uint triangle_uuid1 = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
1978 if (getPrimitiveArea(triangle_uuid1) > 0) {
1979 UUID.push_back(triangle_uuid1);
1980 } else {
1981 deletePrimitive(triangle_uuid1);
1982 }
1983 uint triangle_uuid2 = addTriangle(v0, v2, v3, texturefile, uv0, uv2, uv3);
1984 if (getPrimitiveArea(triangle_uuid2) > 0) {
1985 UUID.push_back(triangle_uuid2);
1986 } else {
1987 deletePrimitive(triangle_uuid2);
1988 }
1989 }
1990 }
1991
1992 return UUID;
1993}
1994
1995std::vector<uint> Context::addTile(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv) {
1996 RGBcolor color(0.f, 0.75f, 0.f); // Default color is green
1997
1998 return addTile(center, size, rotation, subdiv, color);
1999}
2000
2001std::vector<uint> Context::addTile(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const RGBcolor &color) {
2002 vec2 subsize;
2003 subsize.x = size.x / float(subdiv.x);
2004 subsize.y = size.y / float(subdiv.y);
2005
2006 std::vector<uint> UUID(subdiv.x * subdiv.y);
2007
2008 size_t t = 0;
2009 for (uint j = 0; j < subdiv.y; j++) {
2010 for (uint i = 0; i < subdiv.x; i++) {
2011 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);
2012
2013 UUID[t] = addPatch(subcenter, subsize, make_SphericalCoord(0, 0), color);
2014
2015 if (rotation.elevation != 0.f) {
2016 getPrimitivePointer_private(UUID[t])->rotate(-rotation.elevation, "x");
2017 }
2018 if (rotation.azimuth != 0.f) {
2019 getPrimitivePointer_private(UUID[t])->rotate(-rotation.azimuth, "z");
2020 }
2021 getPrimitivePointer_private(UUID[t])->translate(center);
2022
2023 t++;
2024 }
2025 }
2026
2027 return UUID;
2028}
2029
2030std::vector<uint> Context::addTile(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const char *texturefile) {
2031 return addTile(center, size, rotation, subdiv, texturefile, make_int2(1, 1));
2032}
2033
2034std::vector<uint> Context::addTile(const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const int2 &subdiv, const char *texturefile, const int2 &texture_repeat) {
2035 if (!validateTextureFileExtenstion(texturefile)) {
2036 helios_runtime_error("ERROR (Context::addTile): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
2037 } else if (!doesTextureFileExist(texturefile)) {
2038 helios_runtime_error("ERROR (Context::addTile): Texture file " + std::string(texturefile) + " does not exist.");
2039 } else if (texture_repeat.x < 1 || texture_repeat.y < 1) {
2040 helios_runtime_error("ERROR (Context::addTile): Number of texture repeats must be greater than 0.");
2041 }
2042
2043 // Automatically resize the repeat count so that it evenly divides the subdivisions.
2044 int2 repeat = texture_repeat;
2045 repeat.x = std::min(subdiv.x, repeat.x);
2046 repeat.y = std::min(subdiv.y, repeat.y);
2047 while (subdiv.x % repeat.x != 0) {
2048 repeat.x--;
2049 }
2050 while (subdiv.y % repeat.y != 0) {
2051 repeat.y--;
2052 }
2053
2054 std::vector<uint> UUID;
2055
2056 vec2 subsize;
2057 subsize.x = size.x / float(subdiv.x);
2058 subsize.y = size.y / float(subdiv.y);
2059
2060 std::vector<helios::vec2> uv(4);
2061 int2 sub_per_repeat;
2062 sub_per_repeat.x = subdiv.x / repeat.x;
2063 sub_per_repeat.y = subdiv.y / repeat.y;
2064 vec2 uv_sub;
2065 uv_sub.x = 1.f / float(sub_per_repeat.x);
2066 uv_sub.y = 1.f / float(sub_per_repeat.y);
2067
2068 addTexture(texturefile);
2069
2070 const int2 &sz = textures.at(texturefile).getImageResolution();
2071 if (subdiv.x >= repeat.x * sz.x || subdiv.y >= repeat.y * sz.y) {
2072 helios_runtime_error("ERROR (Context::addTile): The resolution of the texture image '" + std::string(texturefile) + "' is lower than the number of tile subdivisions. Increase resolution of the texture image.");
2073 }
2074
2075 for (uint j = 0; j < subdiv.y; j++) {
2076 for (uint i = 0; i < subdiv.x; i++) {
2077 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);
2078
2079 uint i_local = i % sub_per_repeat.x;
2080 uint j_local = j % sub_per_repeat.y;
2081 uv.at(0) = make_vec2(float(i_local) * uv_sub.x, float(j_local) * uv_sub.y);
2082 uv.at(1) = make_vec2(float(i_local + 1) * uv_sub.x, float(j_local) * uv_sub.y);
2083 uv.at(2) = make_vec2(float(i_local + 1) * uv_sub.x, float(j_local + 1) * uv_sub.y);
2084 uv.at(3) = make_vec2(float(i_local) * uv_sub.x, float(j_local + 1) * uv_sub.y);
2085
2086 auto *patch_new = (new Patch(texturefile, uv, textures, 0, currentUUID));
2087
2088 if (patch_new->getSolidFraction() == 0) {
2089 delete patch_new;
2090 continue;
2091 }
2092
2093 assert(size.x > 0.f && size.y > 0.f);
2094 patch_new->scale(make_vec3(subsize.x, subsize.y, 1));
2095
2096 patch_new->translate(subcenter);
2097
2098 if (rotation.elevation != 0) {
2099 patch_new->rotate(-rotation.elevation, "x");
2100 }
2101 if (rotation.azimuth != 0) {
2102 patch_new->rotate(-rotation.azimuth, "z");
2103 }
2104
2105 patch_new->translate(center);
2106
2107 primitives[currentUUID] = patch_new;
2108
2109 // Set context pointer and use default material
2110 patch_new->context_ptr = this;
2111 patch_new->materialID = 0; // Default material
2112 // Increment material reference count
2113 materials[0].reference_count++;
2114
2115 currentUUID++;
2116 UUID.push_back(currentUUID - 1);
2117 }
2118 }
2119
2120 return UUID;
2121}
2122
2123std::vector<uint> Context::addTube(uint Ndivs, const std::vector<vec3> &nodes, const std::vector<float> &radius) {
2124 std::vector<RGBcolor> color(nodes.size(), make_RGBcolor(0.f, 0.75f, 0.f));
2125
2126 return addTube(Ndivs, nodes, radius, color);
2127}
2128
2129std::vector<uint> Context::addTube(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const std::vector<RGBcolor> &color) {
2130 const uint node_count = nodes.size();
2131
2132 if (node_count == 0) {
2133 helios_runtime_error("ERROR (Context::addTube): Node and radius arrays are empty.");
2134 } else if (node_count != radius.size()) {
2135 helios_runtime_error("ERROR (Context::addTube): Size of `nodes' and `radius' arguments must agree.");
2136 } else if (node_count != color.size()) {
2137 helios_runtime_error("ERROR (Context::addTube): Size of `nodes' and `color' arguments must agree.");
2138 }
2139
2140 vec3 vec, convec;
2141 std::vector<float> cfact(radial_subdivisions + 1);
2142 std::vector<float> sfact(radial_subdivisions + 1);
2143 std::vector<std::vector<vec3>> xyz;
2144 resize_vector(xyz, node_count, radial_subdivisions + 1);
2145
2146 vec3 nvec(0.1817f, 0.6198f, 0.7634f); // random vector to get things going
2147
2148 for (int j = 0; j < radial_subdivisions + 1; j++) {
2149 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2150 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2151 }
2152
2153 for (int i = 0; i < node_count; i++) { // looping over tube segments
2154
2155 if (radius.at(i) < 0) {
2156 helios_runtime_error("ERROR (Context::addTube): Radius of tube must be positive.");
2157 }
2158
2159 if (i == 0) {
2160 vec.x = nodes[i + 1].x - nodes[i].x;
2161 vec.y = nodes[i + 1].y - nodes[i].y;
2162 vec.z = nodes[i + 1].z - nodes[i].z;
2163 } else if (i == node_count - 1) {
2164 vec.x = nodes[i].x - nodes[i - 1].x;
2165 vec.y = nodes[i].y - nodes[i - 1].y;
2166 vec.z = nodes[i].z - nodes[i - 1].z;
2167 } else {
2168 vec.x = 0.5f * ((nodes[i].x - nodes[i - 1].x) + (nodes[i + 1].x - nodes[i].x));
2169 vec.y = 0.5f * ((nodes[i].y - nodes[i - 1].y) + (nodes[i + 1].y - nodes[i].y));
2170 vec.z = 0.5f * ((nodes[i].z - nodes[i - 1].z) + (nodes[i + 1].z - nodes[i].z));
2171 }
2172
2173 // Ensure nvec is not parallel to vec to avoid degenerate cross products
2174 vec.normalize();
2175 if (fabs(nvec * vec) > 0.95f) {
2176 nvec = vec3(0.1817f, 0.6198f, 0.7634f); // Reset to original random vector
2177 if (fabs(nvec * vec) > 0.95f) {
2178 nvec = vec3(1.0f, 0.0f, 0.0f); // Use x-axis if still parallel
2179 }
2180 }
2181 // Also handle nearly vertical axes
2182 if (fabs(vec.z) > 0.95f) {
2183 nvec = vec3(1.0f, 0.0f, 0.0f); // Use horizontal direction for vertical axes
2184 }
2185
2186 convec = cross(nvec, vec);
2187 convec.normalize();
2188 nvec = cross(vec, convec);
2189 nvec.normalize();
2190
2191 for (int j = 0; j < radial_subdivisions + 1; j++) {
2192 vec3 normal;
2193 normal.x = cfact[j] * radius[i] * nvec.x + sfact[j] * radius[i] * convec.x;
2194 normal.y = cfact[j] * radius[i] * nvec.y + sfact[j] * radius[i] * convec.y;
2195 normal.z = cfact[j] * radius[i] * nvec.z + sfact[j] * radius[i] * convec.z;
2196
2197 xyz[j][i].x = nodes[i].x + normal.x;
2198 xyz[j][i].y = nodes[i].y + normal.y;
2199 xyz[j][i].z = nodes[i].z + normal.z;
2200 }
2201 }
2202
2203 vec3 v0, v1, v2;
2204 std::vector<uint> UUIDs(2 * (node_count - 1) * radial_subdivisions);
2205
2206 int ii = 0;
2207 for (int i = 0; i < node_count - 1; i++) {
2208 for (int j = 0; j < radial_subdivisions; j++) {
2209 v0 = xyz[j][i];
2210 v1 = xyz[j + 1][i + 1];
2211 v2 = xyz[j + 1][i];
2212
2213 UUIDs.at(ii) = addTriangle(v0, v1, v2, color.at(i));
2214
2215 v0 = xyz[j][i];
2216 v1 = xyz[j][i + 1];
2217 v2 = xyz[j + 1][i + 1];
2218
2219 UUIDs.at(ii + 1) = addTriangle(v0, v1, v2, color.at(i));
2220
2221 ii += 2;
2222 }
2223 }
2224
2225 return UUIDs;
2226}
2227
2228std::vector<uint> Context::addTube(uint radial_subdivisions, const std::vector<vec3> &nodes, const std::vector<float> &radius, const char *texturefile) {
2229 if (!validateTextureFileExtenstion(texturefile)) {
2230 helios_runtime_error("ERROR (Context::addTube): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
2231 } else if (!doesTextureFileExist(texturefile)) {
2232 helios_runtime_error("ERROR (Context::addTube): Texture file " + std::string(texturefile) + " does not exist.");
2233 }
2234
2235 const uint node_count = nodes.size();
2236
2237 if (node_count == 0) {
2238 helios_runtime_error("ERROR (Context::addTube): Node and radius arrays are empty.");
2239 } else if (node_count != radius.size()) {
2240 helios_runtime_error("ERROR (Context::addTube): Size of `nodes' and `radius' arguments must agree.");
2241 }
2242
2243 vec3 vec, convec;
2244 std::vector<float> cfact(radial_subdivisions + 1);
2245 std::vector<float> sfact(radial_subdivisions + 1);
2246 std::vector<std::vector<vec3>> xyz, normal;
2247 std::vector<std::vector<vec2>> uv;
2248 resize_vector(xyz, node_count, radial_subdivisions + 1);
2249 resize_vector(normal, node_count, radial_subdivisions + 1);
2250 resize_vector(uv, node_count, radial_subdivisions + 1);
2251
2252 vec3 nvec(0.1817f, 0.6198f, 0.7634f); // random vector to get things going
2253
2254 for (int j = 0; j < radial_subdivisions + 1; j++) {
2255 cfact[j] = cosf(2.f * PI_F * float(j) / float(radial_subdivisions));
2256 sfact[j] = sinf(2.f * PI_F * float(j) / float(radial_subdivisions));
2257 }
2258
2259 for (int i = 0; i < node_count; i++) { // looping over tube segments
2260
2261 if (radius.at(i) < 0) {
2262 helios_runtime_error("ERROR (Context::addTube): Radius of tube must be positive.");
2263 }
2264
2265 if (i == 0) {
2266 vec.x = nodes[i + 1].x - nodes[i].x;
2267 vec.y = nodes[i + 1].y - nodes[i].y;
2268 vec.z = nodes[i + 1].z - nodes[i].z;
2269 } else if (i == node_count - 1) {
2270 vec.x = nodes[i].x - nodes[i - 1].x;
2271 vec.y = nodes[i].y - nodes[i - 1].y;
2272 vec.z = nodes[i].z - nodes[i - 1].z;
2273 } else {
2274 vec.x = 0.5f * ((nodes[i].x - nodes[i - 1].x) + (nodes[i + 1].x - nodes[i].x));
2275 vec.y = 0.5f * ((nodes[i].y - nodes[i - 1].y) + (nodes[i + 1].y - nodes[i].y));
2276 vec.z = 0.5f * ((nodes[i].z - nodes[i - 1].z) + (nodes[i + 1].z - nodes[i].z));
2277 }
2278
2279 // Ensure nvec is not parallel to vec to avoid degenerate cross products
2280 vec.normalize();
2281 if (fabs(nvec * vec) > 0.95f) {
2282 nvec = vec3(0.1817f, 0.6198f, 0.7634f); // Reset to original random vector
2283 if (fabs(nvec * vec) > 0.95f) {
2284 nvec = vec3(1.0f, 0.0f, 0.0f); // Use x-axis if still parallel
2285 }
2286 }
2287 // Also handle nearly vertical axes
2288 if (fabs(vec.z) > 0.95f) {
2289 nvec = vec3(1.0f, 0.0f, 0.0f); // Use horizontal direction for vertical axes
2290 }
2291
2292 convec = cross(nvec, vec);
2293 convec.normalize();
2294 nvec = cross(vec, convec);
2295 nvec.normalize();
2296
2297 for (int j = 0; j < radial_subdivisions + 1; j++) {
2298 normal[j][i].x = cfact[j] * radius[i] * nvec.x + sfact[j] * radius[i] * convec.x;
2299 normal[j][i].y = cfact[j] * radius[i] * nvec.y + sfact[j] * radius[i] * convec.y;
2300 normal[j][i].z = cfact[j] * radius[i] * nvec.z + sfact[j] * radius[i] * convec.z;
2301
2302 xyz[j][i].x = nodes[i].x + normal[j][i].x;
2303 xyz[j][i].y = nodes[i].y + normal[j][i].y;
2304 xyz[j][i].z = nodes[i].z + normal[j][i].z;
2305
2306 uv[j][i].x = float(i) / float(node_count - 1);
2307 uv[j][i].y = float(j) / float(radial_subdivisions);
2308
2309 normal[j][i] = normal[j][i] / radius[i];
2310 }
2311 }
2312
2313 vec3 v0, v1, v2;
2314 vec2 uv0, uv1, uv2;
2315 std::vector<uint> UUIDs(2 * (node_count - 1) * radial_subdivisions);
2316
2317 int ii = 0;
2318 for (int i = 0; i < node_count - 1; i++) {
2319 for (int j = 0; j < radial_subdivisions; j++) {
2320 v0 = xyz[j][i];
2321 v1 = xyz[j + 1][i + 1];
2322 v2 = xyz[j + 1][i];
2323
2324 uv0 = uv[j][i];
2325 uv1 = uv[j + 1][i + 1];
2326 uv2 = uv[j + 1][i];
2327
2328 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
2329 if (getPrimitiveArea(triangle_uuid) > 0) {
2330 UUIDs.at(ii) = triangle_uuid;
2331 } else {
2332 deletePrimitive(triangle_uuid);
2333 UUIDs.at(ii) = 0; // Mark as invalid
2334 }
2335
2336 v0 = xyz[j][i];
2337 v1 = xyz[j][i + 1];
2338 v2 = xyz[j + 1][i + 1];
2339
2340 uv0 = uv[j][i];
2341 uv1 = uv[j][i + 1];
2342 uv2 = uv[j + 1][i + 1];
2343
2344 uint triangle_uuid2 = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
2345 if (getPrimitiveArea(triangle_uuid2) > 0) {
2346 UUIDs.at(ii + 1) = triangle_uuid2;
2347 } else {
2348 deletePrimitive(triangle_uuid2);
2349 UUIDs.at(ii + 1) = 0; // Mark as invalid
2350 }
2351
2352 ii += 2;
2353 }
2354 }
2355
2356 // Remove invalid UUIDs (zeros) from the vector
2357 UUIDs.erase(std::remove(UUIDs.begin(), UUIDs.end(), 0), UUIDs.end());
2358
2359 return UUIDs;
2360}
2361
2362std::vector<uint> Context::addBox(const vec3 &center, const vec3 &size, const int3 &subdiv) {
2363 RGBcolor color = make_RGBcolor(0.f, 0.75f, 0.f); // Default color is green
2364
2365 return addBox(center, size, subdiv, color, false);
2366}
2367
2368std::vector<uint> Context::addBox(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color) {
2369 return addBox(center, size, subdiv, color, false);
2370}
2371
2372std::vector<uint> Context::addBox(const vec3 &center, const vec3 &size, const int3 &subdiv, const char *texturefile) {
2373 return addBox(center, size, subdiv, texturefile, false);
2374}
2375
2376std::vector<uint> Context::addBox(const vec3 &center, const vec3 &size, const int3 &subdiv, const RGBcolor &color, bool reverse_normals) {
2377 std::vector<uint> UUID;
2378
2379 vec3 subsize;
2380 subsize.x = size.x / float(subdiv.x);
2381 subsize.y = size.y / float(subdiv.y);
2382 subsize.z = size.z / float(subdiv.z);
2383
2384 vec3 subcenter;
2385 std::vector<uint> U;
2386
2387 if (reverse_normals) { // normals point inward
2388
2389 // x-z faces (vertical)
2390
2391 // right
2392 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
2393 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
2394 UUID.insert(UUID.end(), U.begin(), U.end());
2395
2396 // left
2397 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
2398 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), color);
2399 UUID.insert(UUID.end(), U.begin(), U.end());
2400
2401 // y-z faces (vertical)
2402
2403 // front
2404 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
2405 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), color);
2406 UUID.insert(UUID.end(), U.begin(), U.end());
2407
2408 // back
2409 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
2410 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), color);
2411 UUID.insert(UUID.end(), U.begin(), U.end());
2412
2413 // x-y faces (horizontal)
2414
2415 // top
2416 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
2417 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
2418 UUID.insert(UUID.end(), U.begin(), U.end());
2419
2420 // bottom
2421 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
2422 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), color);
2423 UUID.insert(UUID.end(), U.begin(), U.end());
2424 } else { // normals point outward
2425
2426 // x-z faces (vertical)
2427
2428 // right
2429 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
2430 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), color);
2431 UUID.insert(UUID.end(), U.begin(), U.end());
2432
2433 // left
2434 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
2435 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), color);
2436 UUID.insert(UUID.end(), U.begin(), U.end());
2437
2438 // y-z faces (vertical)
2439
2440 // front
2441 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
2442 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), color);
2443 UUID.insert(UUID.end(), U.begin(), U.end());
2444
2445 // back
2446 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
2447 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), color);
2448 UUID.insert(UUID.end(), U.begin(), U.end());
2449
2450 // x-y faces (horizontal)
2451
2452 // top
2453 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
2454 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), color);
2455 UUID.insert(UUID.end(), U.begin(), U.end());
2456
2457 // bottom
2458 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
2459 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), color);
2460 UUID.insert(UUID.end(), U.begin(), U.end());
2461 }
2462
2463 return UUID;
2464}
2465
2466std::vector<uint> Context::addBox(const vec3 &center, const vec3 &size, const int3 &subdiv, const char *texturefile, bool reverse_normals) {
2467 if (!validateTextureFileExtenstion(texturefile)) {
2468 helios_runtime_error("ERROR (Context::addBox): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
2469 } else if (!doesTextureFileExist(texturefile)) {
2470 helios_runtime_error("ERROR (Context::addBox): Texture file " + std::string(texturefile) + " does not exist.");
2471 }
2472
2473 std::vector<uint> UUID;
2474
2475 vec3 subsize;
2476 subsize.x = size.x / float(subdiv.x);
2477 subsize.y = size.y / float(subdiv.y);
2478 subsize.z = size.z / float(subdiv.z);
2479
2480 vec3 subcenter;
2481 std::vector<uint> U;
2482
2483 if (reverse_normals) { // normals point inward
2484
2485 // x-z faces (vertical)
2486
2487 // right
2488 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
2489 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
2490 UUID.insert(UUID.end(), U.begin(), U.end());
2491
2492 // left
2493 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
2494 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), texturefile);
2495 UUID.insert(UUID.end(), U.begin(), U.end());
2496
2497 // y-z faces (vertical)
2498
2499 // front
2500 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
2501 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);
2502 UUID.insert(UUID.end(), U.begin(), U.end());
2503
2504 // back
2505 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
2506 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);
2507 UUID.insert(UUID.end(), U.begin(), U.end());
2508
2509 // x-y faces (horizontal)
2510
2511 // top
2512 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
2513 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
2514 UUID.insert(UUID.end(), U.begin(), U.end());
2515
2516 // bottom
2517 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
2518 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), texturefile);
2519 UUID.insert(UUID.end(), U.begin(), U.end());
2520 } else { // normals point outward
2521
2522 // x-z faces (vertical)
2523
2524 // right
2525 subcenter = center + make_vec3(0, 0.5f * size.y, 0);
2526 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, 0), make_int2(subdiv.x, subdiv.z), texturefile);
2527 UUID.insert(UUID.end(), U.begin(), U.end());
2528
2529 // left
2530 subcenter = center - make_vec3(0, 0.5f * size.y, 0);
2531 U = addTile(subcenter, make_vec2(size.x, size.z), make_SphericalCoord(0.5 * PI_F, PI_F), make_int2(subdiv.x, subdiv.z), texturefile);
2532 UUID.insert(UUID.end(), U.begin(), U.end());
2533
2534 // y-z faces (vertical)
2535
2536 // front
2537 subcenter = center + make_vec3(0.5f * size.x, 0, 0);
2538 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);
2539 UUID.insert(UUID.end(), U.begin(), U.end());
2540
2541 // back
2542 subcenter = center - make_vec3(0.5f * size.x, 0, 0);
2543 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);
2544 UUID.insert(UUID.end(), U.begin(), U.end());
2545
2546 // x-y faces (horizontal)
2547
2548 // top
2549 subcenter = center + make_vec3(0, 0, 0.5f * size.z);
2550 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(0, 0), make_int2(subdiv.x, subdiv.y), texturefile);
2551 UUID.insert(UUID.end(), U.begin(), U.end());
2552
2553 // bottom
2554 subcenter = center - make_vec3(0, 0, 0.5f * size.z);
2555 U = addTile(subcenter, make_vec2(size.x, size.y), make_SphericalCoord(PI_F, 0), make_int2(subdiv.x, subdiv.y), texturefile);
2556 UUID.insert(UUID.end(), U.begin(), U.end());
2557 }
2558
2559 return UUID;
2560}
2561
2562std::vector<uint> Context::addDisk(uint Ndivs, const vec3 &center, const vec2 &size) {
2563 return addDisk(make_int2(Ndivs, 1), center, size, make_SphericalCoord(0, 0), make_RGBAcolor(1, 0, 0, 1));
2564}
2565
2566std::vector<uint> Context::addDisk(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation) {
2567 return addDisk(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(1, 0, 0, 1));
2568}
2569
2570std::vector<uint> Context::addDisk(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
2571 return addDisk(make_int2(Ndivs, 1), center, size, rotation, make_RGBAcolor(color, 1));
2572}
2573
2574std::vector<uint> Context::addDisk(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
2575 return addDisk(make_int2(Ndivs, 1), center, size, rotation, color);
2576}
2577
2578std::vector<uint> Context::addDisk(uint Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texture_file) {
2579 return addDisk(make_int2(Ndivs, 1), center, size, rotation, texture_file);
2580}
2581
2582std::vector<uint> Context::addDisk(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBcolor &color) {
2583 return addDisk(Ndivs, center, size, rotation, make_RGBAcolor(color, 1));
2584}
2585
2586std::vector<uint> Context::addDisk(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const RGBAcolor &color) {
2587 std::vector<uint> UUID(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2);
2588 int i = 0;
2589 for (int r = 0; r < Ndivs.y; r++) {
2590 for (int t = 0; t < Ndivs.x; t++) {
2591 float dtheta = 2.f * PI_F / float(Ndivs.x);
2592 float theta = dtheta * float(t);
2593 float theta_plus = dtheta * float(t + 1);
2594
2595 float rx = size.x / float(Ndivs.y) * float(r);
2596 float ry = size.y / float(Ndivs.y) * float(r);
2597
2598 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
2599 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
2600
2601 if (r == 0) {
2602 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);
2603 } else {
2604 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);
2605 i++;
2606 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);
2607 }
2608 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.elevation, "y");
2609 getPrimitivePointer_private(UUID.at(i))->rotate(rotation.azimuth, "z");
2610 getPrimitivePointer_private(UUID.at(i))->translate(center);
2611
2612 i++;
2613 }
2614 }
2615
2616 return UUID;
2617}
2618
2619std::vector<uint> Context::addDisk(const int2 &Ndivs, const vec3 &center, const vec2 &size, const SphericalCoord &rotation, const char *texturefile) {
2620 if (!validateTextureFileExtenstion(texturefile)) {
2621 helios_runtime_error("ERROR (Context::addDisk): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
2622 } else if (!doesTextureFileExist(texturefile)) {
2623 helios_runtime_error("ERROR (Context::addDisk): Texture file " + std::string(texturefile) + " does not exist.");
2624 }
2625
2626 std::vector<uint> UUID;
2627 UUID.reserve(Ndivs.x + Ndivs.x * (Ndivs.y - 1) * 2); // Reserve expected capacity
2628 for (int r = 0; r < Ndivs.y; r++) {
2629 for (int t = 0; t < Ndivs.x; t++) {
2630 float dtheta = 2.f * PI_F / float(Ndivs.x);
2631 float theta = dtheta * float(t);
2632 float theta_plus = dtheta * float(t + 1);
2633
2634 float rx = size.x / float(Ndivs.y) * float(r);
2635 float ry = size.y / float(Ndivs.y) * float(r);
2636 float rx_plus = size.x / float(Ndivs.y) * float(r + 1);
2637 float ry_plus = size.y / float(Ndivs.y) * float(r + 1);
2638
2639 if (r == 0) {
2640 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),
2641 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)),
2642 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
2643 if (getPrimitiveArea(triangle_uuid) > 0) {
2644 UUID.push_back(triangle_uuid);
2645 } else {
2646 deletePrimitive(triangle_uuid);
2647 continue;
2648 }
2649 } else {
2650 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,
2651 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)),
2652 make_vec2(0.5f * (1.f + cosf(theta) * rx_plus / size.x), 0.5f * (1.f + sinf(theta) * ry_plus / size.y)));
2653 if (getPrimitiveArea(triangle_uuid1) > 0) {
2654 UUID.push_back(triangle_uuid1);
2655 } else {
2656 deletePrimitive(triangle_uuid1);
2657 }
2658
2659 uint triangle_uuid2 =
2660 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,
2661 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)),
2662 make_vec2(0.5f * (1.f + cosf(theta_plus) * rx_plus / size.x), 0.5f * (1.f + sinf(theta_plus) * ry_plus / size.y)));
2663 if (getPrimitiveArea(triangle_uuid2) > 0) {
2664 UUID.push_back(triangle_uuid2);
2665 } else {
2666 deletePrimitive(triangle_uuid2);
2667 continue;
2668 }
2669 }
2670 // Apply transformations to all valid triangles added in this iteration
2671 size_t start_idx = UUID.size() - (r == 0 ? 1 : 2);
2672 for (size_t uuid_idx = start_idx; uuid_idx < UUID.size(); uuid_idx++) {
2673 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.elevation, "y");
2674 getPrimitivePointer_private(UUID.at(uuid_idx))->rotate(rotation.azimuth, "z");
2675 getPrimitivePointer_private(UUID.at(uuid_idx))->translate(center);
2676 }
2677 }
2678 }
2679
2680 return UUID;
2681}
2682
2683std::vector<uint> Context::addCone(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1) {
2684 RGBcolor color;
2685 color = make_RGBcolor(0.f, 0.75f, 0.f); // Default color is green
2686
2687 return addCone(Ndivs, node0, node1, radius0, radius1, color);
2688}
2689
2690std::vector<uint> Context::addCone(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, RGBcolor &color) {
2691 std::vector<helios::vec3> nodes{node0, node1};
2692 std::vector<float> radii{radius0, radius1};
2693
2694 vec3 vec, convec;
2695 std::vector<float> cfact(Ndivs + 1);
2696 std::vector<float> sfact(Ndivs + 1);
2697 std::vector<std::vector<vec3>> xyz, normal;
2698 xyz.resize(Ndivs + 1);
2699 normal.resize(Ndivs + 1);
2700 for (uint j = 0; j < Ndivs + 1; j++) {
2701 xyz.at(j).resize(2);
2702 normal.at(j).resize(2);
2703 }
2704 vec3 nvec(0.1817f, 0.6198f, 0.7634f); // random vector to get things going
2705
2706 for (int j = 0; j < Ndivs + 1; j++) {
2707 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
2708 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
2709 }
2710
2711 for (int i = 0; i < 2; i++) { // looping over cone segments
2712
2713 if (i == 0) {
2714 vec.x = nodes[i + 1].x - nodes[i].x;
2715 vec.y = nodes[i + 1].y - nodes[i].y;
2716 vec.z = nodes[i + 1].z - nodes[i].z;
2717 } else if (i == 1) {
2718 vec.x = nodes[i].x - nodes[i - 1].x;
2719 vec.y = nodes[i].y - nodes[i - 1].y;
2720 vec.z = nodes[i].z - nodes[i - 1].z;
2721 }
2722
2723 float norm;
2724 convec = cross(nvec, vec);
2725 norm = convec.magnitude();
2726 convec.x = convec.x / norm;
2727 convec.y = convec.y / norm;
2728 convec.z = convec.z / norm;
2729 nvec = cross(vec, convec);
2730 norm = nvec.magnitude();
2731 nvec.x = nvec.x / norm;
2732 nvec.y = nvec.y / norm;
2733 nvec.z = nvec.z / norm;
2734
2735
2736 for (int j = 0; j < Ndivs + 1; j++) {
2737 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
2738 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
2739 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
2740
2741 xyz[j][i].x = nodes[i].x + normal[j][i].x;
2742 xyz[j][i].y = nodes[i].y + normal[j][i].y;
2743 xyz[j][i].z = nodes[i].z + normal[j][i].z;
2744
2745 normal[j][i] = normal[j][i] / radii[i];
2746 }
2747 }
2748
2749 vec3 v0, v1, v2;
2750 std::vector<uint> UUID;
2751
2752 for (int i = 0; i < 2 - 1; i++) {
2753 for (int j = 0; j < Ndivs; j++) {
2754 v0 = xyz[j][i];
2755 v1 = xyz[j + 1][i + 1];
2756 v2 = xyz[j + 1][i];
2757
2758 UUID.push_back(addTriangle(v0, v1, v2, color));
2759
2760 v0 = xyz[j][i];
2761 v1 = xyz[j][i + 1];
2762 v2 = xyz[j + 1][i + 1];
2763
2764 UUID.push_back(addTriangle(v0, v1, v2, color));
2765 }
2766 }
2767
2768 return UUID;
2769}
2770
2771std::vector<uint> Context::addCone(uint Ndivs, const vec3 &node0, const vec3 &node1, float radius0, float radius1, const char *texturefile) {
2772 if (!validateTextureFileExtenstion(texturefile)) {
2773 helios_runtime_error("ERROR (Context::addCone): Texture file " + std::string(texturefile) + " is not PNG or JPEG format.");
2774 } else if (!doesTextureFileExist(texturefile)) {
2775 helios_runtime_error("ERROR (Context::addCone): Texture file " + std::string(texturefile) + " does not exist.");
2776 }
2777
2778 std::vector<helios::vec3> nodes{node0, node1};
2779 std::vector<float> radii{radius0, radius1};
2780
2781 vec3 vec, convec;
2782 std::vector<float> cfact(Ndivs + 1);
2783 std::vector<float> sfact(Ndivs + 1);
2784 std::vector<std::vector<vec3>> xyz, normal;
2785 std::vector<std::vector<vec2>> uv;
2786 xyz.resize(Ndivs + 1);
2787 normal.resize(Ndivs + 1);
2788 uv.resize(Ndivs + 1);
2789 for (uint j = 0; j < Ndivs + 1; j++) {
2790 xyz.at(j).resize(2);
2791 normal.at(j).resize(2);
2792 uv.at(j).resize(2);
2793 }
2794 vec3 nvec(0.f, 1.f, 0.f);
2795
2796 for (int j = 0; j < Ndivs + 1; j++) {
2797 cfact[j] = cosf(2.f * PI_F * float(j) / float(Ndivs));
2798 sfact[j] = sinf(2.f * PI_F * float(j) / float(Ndivs));
2799 }
2800
2801 for (int i = 0; i < 2; i++) { // looping over cone segments
2802
2803 if (i == 0) {
2804 vec.x = nodes[i + 1].x - nodes[i].x;
2805 vec.y = nodes[i + 1].y - nodes[i].y;
2806 vec.z = nodes[i + 1].z - nodes[i].z;
2807 } else if (i == 1) {
2808 vec.x = nodes[i].x - nodes[i - 1].x;
2809 vec.y = nodes[i].y - nodes[i - 1].y;
2810 vec.z = nodes[i].z - nodes[i - 1].z;
2811 }
2812
2813 float norm;
2814 convec = cross(nvec, vec);
2815 norm = convec.magnitude();
2816 convec.x = convec.x / norm;
2817 convec.y = convec.y / norm;
2818 convec.z = convec.z / norm;
2819 nvec = cross(vec, convec);
2820 norm = nvec.magnitude();
2821 nvec.x = nvec.x / norm;
2822 nvec.y = nvec.y / norm;
2823 nvec.z = nvec.z / norm;
2824
2825 for (int j = 0; j < Ndivs + 1; j++) {
2826 normal[j][i].x = cfact[j] * radii[i] * nvec.x + sfact[j] * radii[i] * convec.x;
2827 normal[j][i].y = cfact[j] * radii[i] * nvec.y + sfact[j] * radii[i] * convec.y;
2828 normal[j][i].z = cfact[j] * radii[i] * nvec.z + sfact[j] * radii[i] * convec.z;
2829
2830 xyz[j][i].x = nodes[i].x + normal[j][i].x;
2831 xyz[j][i].y = nodes[i].y + normal[j][i].y;
2832 xyz[j][i].z = nodes[i].z + normal[j][i].z;
2833
2834 uv[j][i].x = float(i) / float(2 - 1);
2835 uv[j][i].y = float(j) / float(Ndivs);
2836
2837 normal[j][i] = normal[j][i] / radii[i];
2838 }
2839 }
2840
2841 vec3 v0, v1, v2;
2842 vec2 uv0, uv1, uv2;
2843 std::vector<uint> UUID;
2844
2845 for (int i = 0; i < 2 - 1; i++) {
2846 for (int j = 0; j < Ndivs; j++) {
2847 v0 = xyz[j][i];
2848 v1 = xyz[j + 1][i + 1];
2849 v2 = xyz[j + 1][i];
2850
2851 uv0 = uv[j][i];
2852 uv1 = uv[j + 1][i + 1];
2853 uv2 = uv[j + 1][i];
2854
2855 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
2856 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
2857 if (getPrimitiveArea(triangle_uuid) > 0) {
2858 UUID.push_back(triangle_uuid);
2859 } else {
2860 deletePrimitive(triangle_uuid);
2861 }
2862 }
2863
2864 v0 = xyz[j][i];
2865 v1 = xyz[j][i + 1];
2866 v2 = xyz[j + 1][i + 1];
2867
2868 uv0 = uv[j][i];
2869 uv1 = uv[j][i + 1];
2870 uv2 = uv[j + 1][i + 1];
2871
2872 if ((v1 - v0).magnitude() > 1e-6 && (v2 - v0).magnitude() > 1e-6 && (v2 - v1).magnitude() > 1e-6) {
2873 uint triangle_uuid = addTriangle(v0, v1, v2, texturefile, uv0, uv1, uv2);
2874 if (getPrimitiveArea(triangle_uuid) > 0) {
2875 UUID.push_back(triangle_uuid);
2876 } else {
2877 deletePrimitive(triangle_uuid);
2878 }
2879 }
2880 }
2881 }
2882
2883 return UUID;
2884}
2885
2886void Context::colorPrimitiveByDataPseudocolor(const std::vector<uint> &UUIDs, const std::string &primitive_data, const std::string &colormap, uint Ncolors) {
2887 colorPrimitiveByDataPseudocolor(UUIDs, primitive_data, colormap, Ncolors, 9999999, -9999999);
2888}
2889
2890void Context::colorPrimitiveByDataPseudocolor(const std::vector<uint> &UUIDs, const std::string &primitive_data, const std::string &colormap, uint Ncolors, float data_min, float data_max) {
2891 std::map<uint, float> pcolor_data;
2892
2894 float data_min_new = 9999999;
2895 float data_max_new = -9999999;
2896 for (uint UUID: UUIDs) {
2897 if (!doesPrimitiveExist(UUID)) {
2898 warnings.addWarning("primitive_does_not_exist", "Primitive for UUID " + std::to_string(UUID) + " does not exist. Skipping this primitive.");
2899 continue;
2900 }
2901
2902 float dataf = 0;
2903 if (doesPrimitiveDataExist(UUID, primitive_data.c_str())) {
2904 if (getPrimitiveDataType(primitive_data.c_str()) != HELIOS_TYPE_FLOAT && getPrimitiveDataType(primitive_data.c_str()) != HELIOS_TYPE_INT && getPrimitiveDataType(primitive_data.c_str()) != HELIOS_TYPE_UINT &&
2905 getPrimitiveDataType(primitive_data.c_str()) != HELIOS_TYPE_DOUBLE) {
2906 warnings.addWarning("unsupported_data_type", "Only primitive data types of int, uint, float, and double are supported for this function. Skipping this primitive.");
2907 continue;
2908 }
2909
2910 if (getPrimitiveDataType(primitive_data.c_str()) == HELIOS_TYPE_FLOAT) {
2911 float data;
2912 getPrimitiveData(UUID, primitive_data.c_str(), data);
2913 dataf = data;
2914 } else if (getPrimitiveDataType(primitive_data.c_str()) == HELIOS_TYPE_DOUBLE) {
2915 double data;
2916 getPrimitiveData(UUID, primitive_data.c_str(), data);
2917 dataf = float(data);
2918 } else if (getPrimitiveDataType(primitive_data.c_str()) == HELIOS_TYPE_INT) {
2919 int data;
2920 getPrimitiveData(UUID, primitive_data.c_str(), data);
2921 dataf = float(data);
2922 } else if (getPrimitiveDataType(primitive_data.c_str()) == HELIOS_TYPE_UINT) {
2923 uint data;
2924 getPrimitiveData(UUID, primitive_data.c_str(), data);
2925 dataf = float(data);
2926 }
2927 }
2928
2929 if (data_min == 9999999 && data_max == -9999999) {
2930 if (dataf < data_min_new) {
2931 data_min_new = dataf;
2932 }
2933 if (dataf > data_max_new) {
2934 data_max_new = dataf;
2935 }
2936 }
2937
2938 pcolor_data[UUID] = dataf;
2939 }
2940
2941 if (data_min == 9999999 && data_max == -9999999) {
2942 data_min = data_min_new;
2943 data_max = data_max_new;
2944 }
2945
2946 std::vector<RGBcolor> colormap_data = generateColormap(colormap, Ncolors);
2947
2948 std::map<std::string, std::vector<std::string>> cmap_texture_filenames;
2949
2950 for (auto &[UUID, pdata]: pcolor_data) {
2951 std::string texturefile = getPrimitiveTextureFile(UUID);
2952
2953 int cmap_ind = std::round((pdata - data_min) / (data_max - data_min) * float(Ncolors - 1));
2954
2955 if (cmap_ind < 0) {
2956 cmap_ind = 0;
2957 } else if (cmap_ind >= Ncolors) {
2958 cmap_ind = Ncolors - 1;
2959 }
2960
2961 if (!texturefile.empty() && primitiveTextureHasTransparencyChannel(UUID)) { // primitive has texture with transparency channel
2962
2964 setPrimitiveColor(UUID, colormap_data.at(cmap_ind));
2965 } else { // primitive does not have texture with transparency channel - assign constant color
2966
2967 if (!getPrimitiveTextureFile(UUID).empty()) {
2969 }
2970
2971 setPrimitiveColor(UUID, colormap_data.at(cmap_ind));
2972 }
2973 }
2974
2975 warnings.report(std::cerr);
2976}
2977
2978std::vector<RGBcolor> Context::generateColormap(const std::vector<helios::RGBcolor> &ctable, const std::vector<float> &cfrac, uint Ncolors) {
2979 if (Ncolors > 9999) {
2980 std::cerr << "WARNING (Context::generateColormap): Truncating number of color map textures to maximum value of 9999." << std::endl;
2981 }
2982
2983 if (ctable.size() != cfrac.size()) {
2984 helios_runtime_error("ERROR (Context::generateColormap): The length of arguments 'ctable' and 'cfrac' must match.");
2985 }
2986 if (ctable.empty()) {
2987 helios_runtime_error("ERROR (Context::generateColormap): 'ctable' and 'cfrac' arguments contain empty vectors.");
2988 }
2989
2990 std::vector<RGBcolor> color_table(Ncolors);
2991
2992 for (int i = 0; i < Ncolors; i++) {
2993 float frac = float(i) / float(Ncolors - 1) * cfrac.back();
2994
2995 int j;
2996 for (j = 0; j < cfrac.size() - 1; j++) {
2997 if (frac >= cfrac.at(j) && frac <= cfrac.at(j + 1)) {
2998 break;
2999 }
3000 }
3001
3002 float cminus = std::fmaxf(0.f, cfrac.at(j));
3003 float cplus = std::fminf(1.f, cfrac.at(j + 1));
3004
3005 float jfrac = (frac - cminus) / (cplus - cminus);
3006
3007 RGBcolor color;
3008 color.r = ctable.at(j).r + jfrac * (ctable.at(j + 1).r - ctable.at(j).r);
3009 color.g = ctable.at(j).g + jfrac * (ctable.at(j + 1).g - ctable.at(j).g);
3010 color.b = ctable.at(j).b + jfrac * (ctable.at(j + 1).b - ctable.at(j).b);
3011
3012 color_table.at(i) = color;
3013 }
3014
3015 return color_table;
3016}
3017
3018std::vector<RGBcolor> Context::generateColormap(const std::string &colormap, uint Ncolors) {
3019 std::vector<RGBcolor> ctable_c;
3020 std::vector<float> clocs_c;
3021
3022 if (colormap == "hot") {
3023 ctable_c.resize(5);
3024 ctable_c.at(0) = make_RGBcolor(0.f, 0.f, 0.f);
3025 ctable_c.at(1) = make_RGBcolor(0.5f, 0.f, 0.5f);
3026 ctable_c.at(2) = make_RGBcolor(1.f, 0.f, 0.f);
3027 ctable_c.at(3) = make_RGBcolor(1.f, 0.5f, 0.f);
3028 ctable_c.at(4) = make_RGBcolor(1.f, 1.f, 0.f);
3029
3030 clocs_c.resize(5);
3031 clocs_c.at(0) = 0.f;
3032 clocs_c.at(1) = 0.25f;
3033 clocs_c.at(2) = 0.5f;
3034 clocs_c.at(3) = 0.75f;
3035 clocs_c.at(4) = 1.f;
3036 } else if (colormap == "cool") {
3037 ctable_c.resize(2);
3038 ctable_c.at(0) = RGB::cyan;
3039 ctable_c.at(1) = RGB::magenta;
3040
3041 clocs_c.resize(2);
3042 clocs_c.at(0) = 0.f;
3043 clocs_c.at(1) = 1.f;
3044 } else if (colormap == "lava") {
3045 ctable_c.resize(5);
3046 ctable_c.at(0) = make_RGBcolor(0.f, 0.05f, 0.05f);
3047 ctable_c.at(1) = make_RGBcolor(0.f, 0.6f, 0.6f);
3048 ctable_c.at(2) = make_RGBcolor(1.f, 1.f, 1.f);
3049 ctable_c.at(3) = make_RGBcolor(1.f, 0.f, 0.f);
3050 ctable_c.at(4) = make_RGBcolor(0.5f, 0.f, 0.f);
3051
3052 clocs_c.resize(5);
3053 clocs_c.at(0) = 0.f;
3054 clocs_c.at(1) = 0.4f;
3055 clocs_c.at(2) = 0.5f;
3056 clocs_c.at(3) = 0.6f;
3057 clocs_c.at(4) = 1.f;
3058 } else if (colormap == "rainbow") {
3059 ctable_c.resize(4);
3060 ctable_c.at(0) = RGB::navy;
3061 ctable_c.at(1) = RGB::cyan;
3062 ctable_c.at(2) = RGB::yellow;
3063 ctable_c.at(3) = make_RGBcolor(0.75f, 0.f, 0.f);
3064
3065 clocs_c.resize(4);
3066 clocs_c.at(0) = 0.f;
3067 clocs_c.at(1) = 0.3f;
3068 clocs_c.at(2) = 0.7f;
3069 clocs_c.at(3) = 1.f;
3070 } else if (colormap == "parula") {
3071 ctable_c.resize(4);
3072 ctable_c.at(0) = RGB::navy;
3073 ctable_c.at(1) = make_RGBcolor(0, 0.6, 0.6);
3074 ctable_c.at(2) = RGB::goldenrod;
3075 ctable_c.at(3) = RGB::yellow;
3076
3077 clocs_c.resize(4);
3078 clocs_c.at(0) = 0.f;
3079 clocs_c.at(1) = 0.4f;
3080 clocs_c.at(2) = 0.7f;
3081 clocs_c.at(3) = 1.f;
3082 } else if (colormap == "gray") {
3083 ctable_c.resize(2);
3084 ctable_c.at(0) = RGB::black;
3085 ctable_c.at(1) = RGB::white;
3086
3087 clocs_c.resize(2);
3088 clocs_c.at(0) = 0.f;
3089 clocs_c.at(1) = 1.f;
3090 } else if (colormap == "green") {
3091 ctable_c.resize(2);
3092 ctable_c.at(0) = RGB::black;
3093 ctable_c.at(1) = RGB::green;
3094
3095 clocs_c.resize(2);
3096 clocs_c.at(0) = 0.f;
3097 clocs_c.at(1) = 1.f;
3098 } else {
3099 helios_runtime_error("ERROR (Context::generateColormapTextures): Unknown colormap " + colormap + ".");
3100 }
3101
3102 return generateColormap(ctable_c, clocs_c, Ncolors);
3103}
3104
3105std::vector<std::string> Context::generateTexturesFromColormap(const std::string &texturefile, const std::vector<RGBcolor> &colormap_data) {
3106 uint Ncolors = colormap_data.size();
3107
3108 // check that texture file exists
3109 std::ifstream tfile(texturefile);
3110 if (!tfile) {
3111 helios_runtime_error("ERROR (Context::generateTexturesFromColormap): Texture file " + texturefile + " does not exist, or you do not have permission to read it.");
3112 }
3113 tfile.close();
3114
3115 // get file extension
3116 std::string file_ext = getFileExtension(texturefile);
3117
3118 // get file base/stem
3119 std::string file_base = getFileStem(texturefile);
3120
3121 std::vector<RGBcolor> color_table(Ncolors);
3122
3123 std::vector<std::string> texture_filenames(Ncolors);
3124
3125 if (file_ext == "png" || file_ext == "PNG") {
3126 std::vector<RGBAcolor> pixel_data;
3127 uint width, height;
3128 readPNG(texturefile, width, height, pixel_data);
3129
3130 for (int i = 0; i < Ncolors; i++) {
3131 std::ostringstream filename;
3132 filename << "lib/images/colormap_" << file_base << "_" << std::setw(4) << std::setfill('0') << std::to_string(i) << ".png";
3133
3134 texture_filenames.at(i) = filename.str();
3135
3136 RGBcolor color = colormap_data.at(i);
3137
3138 for (int row = 0; row < height; row++) {
3139 for (int col = 0; col < width; col++) {
3140 pixel_data.at(row * width + col) = make_RGBAcolor(color, pixel_data.at(row * width + col).a);
3141 }
3142 }
3143
3144 writePNG(filename.str(), width, height, pixel_data);
3145 }
3146 }
3147
3148 return texture_filenames;
3149}
3150
3151void Context::out_of_memory_handler() {
3152 helios_runtime_error("ERROR: Out of host memory. The program has run out of memory and cannot continue.");
3153}
3154
3155void Context::install_out_of_memory_handler() {
3156 std::set_new_handler(out_of_memory_handler);
3157}
3158
3160 for (auto &[UUID, primitive]: primitives) {
3161 delete getPrimitivePointer_private(UUID);
3162 }
3163
3164 for (auto &[UUID, object]: objects) {
3165 delete getObjectPointer_private(UUID);
3166 }
3167}
3168
3170#ifdef HELIOS_DEBUG
3171 if (!doesPrimitiveExist(UUID)) {
3172 helios_runtime_error("ERROR (Context::getPrimitiveType): Primitive with UUID of " + std::to_string(UUID) + " does not exist in the Context.");
3173 }
3174#endif
3175 return getPrimitivePointer_private(UUID)->getType();
3176}
3177
3179#ifdef HELIOS_DEBUG
3180 if (!doesPrimitiveExist(UUID)) {
3181 helios_runtime_error("ERROR (Context::setPrimitiveParentObjectID): Primitive with UUID of " + std::to_string(UUID) + " does not exist in the Context.");
3182 }
3183#endif
3184
3185 uint current_objID = getPrimitivePointer_private(UUID)->getParentObjectID();
3186 getPrimitivePointer_private(UUID)->setParentObjectID(objID);
3187
3188 if (current_objID != 0u && current_objID != objID) {
3189 if (doesObjectExist(current_objID)) {
3190 objects.at(current_objID)->deleteChildPrimitive(UUID);
3191
3192 if (getObjectPointer_private(current_objID)->getPrimitiveUUIDs().empty()) {
3193 CompoundObject *obj = objects.at(current_objID);
3194 delete obj;
3195 objects.erase(current_objID);
3196 }
3197 }
3198 }
3199}
3200
3201void Context::setPrimitiveParentObjectID(const std::vector<uint> &UUIDs, uint objID) {
3202 for (uint UUID: UUIDs) {
3203 setPrimitiveParentObjectID(UUID, objID);
3204 }
3205}
3206
3208#ifdef HELIOS_DEBUG
3209 if (!doesPrimitiveExist(UUID)) {
3210 helios_runtime_error("ERROR (Context::getPrimitiveParentObjectID): Primitive with UUID of " + std::to_string(UUID) + " does not exist in the Context.");
3211 }
3212#endif
3213 return getPrimitivePointer_private(UUID)->getParentObjectID();
3214}
3215
3216std::vector<uint> Context::getPrimitiveParentObjectID(const std::vector<uint> &UUIDs) const {
3217 std::vector<uint> objIDs(UUIDs.size());
3218 for (uint i = 0; i < UUIDs.size(); i++) {
3219#ifdef HELIOS_DEBUG
3220 if (!doesPrimitiveExist(UUIDs[i])) {
3221 helios_runtime_error("ERROR (Context::getPrimitiveParentObjectID): Primitive with UUID of " + std::to_string(UUIDs[i]) + " does not exist in the Context.");
3222 }
3223#endif
3224 objIDs[i] = getPrimitivePointer_private(UUIDs[i])->getParentObjectID();
3225 }
3226 return objIDs;
3227}
3228
3229
3230std::vector<uint> Context::getUniquePrimitiveParentObjectIDs(const std::vector<uint> &UUIDs) const {
3231 return getUniquePrimitiveParentObjectIDs(UUIDs, false);
3232}
3233
3234
3235std::vector<uint> Context::getUniquePrimitiveParentObjectIDs(const std::vector<uint> &UUIDs, bool include_ObjID_zero) const {
3236 std::vector<uint> primitiveObjIDs;
3237 if (UUIDs.empty()) {
3238 return primitiveObjIDs;
3239 }
3240
3241 // vector of parent object ID for each primitive
3242 primitiveObjIDs.resize(UUIDs.size());
3243 for (uint i = 0; i < UUIDs.size(); i++) {
3244#ifdef HELIOS_DEBUG
3245 if (!doesPrimitiveExist(UUIDs.at(i))) {
3246 helios_runtime_error("ERROR (Context::getUniquePrimitiveParentObjectIDs): Primitive with UUID of " + std::to_string(UUIDs.at(i)) + " does not exist in the Context.");
3247 }
3248#endif
3249 primitiveObjIDs.at(i) = getPrimitivePointer_private(UUIDs.at(i))->getParentObjectID();
3250 }
3251
3252 // sort
3253 std::sort(primitiveObjIDs.begin(), primitiveObjIDs.end());
3254
3255 // unique
3256 auto it = unique(primitiveObjIDs.begin(), primitiveObjIDs.end());
3257 primitiveObjIDs.resize(distance(primitiveObjIDs.begin(), it));
3258
3259 // remove object ID = 0 from the output if desired and it exists
3260 if (include_ObjID_zero == false & primitiveObjIDs.front() == uint(0)) {
3261 primitiveObjIDs.erase(primitiveObjIDs.begin());
3262 }
3263
3264 return primitiveObjIDs;
3265}
3266
3268#ifdef HELIOS_DEBUG
3269 if (!doesPrimitiveExist(UUID)) {
3270 helios_runtime_error("ERROR (Context::getPrimitiveArea): Primitive with UUID of " + std::to_string(UUID) + " does not exist in the Context.");
3271 }
3272#endif
3273 return getPrimitivePointer_private(UUID)->getArea();
3274}
3275
3276void Context::getPrimitiveBoundingBox(uint UUID, vec3 &min_corner, vec3 &max_corner) const {
3277 const std::vector UUIDs = {UUID};
3278 getPrimitiveBoundingBox(UUIDs, min_corner, max_corner);
3279}
3280
3281void Context::getPrimitiveBoundingBox(const std::vector<uint> &UUIDs, vec3 &min_corner, vec3 &max_corner) const {
3282 uint p = 0;
3283 for (uint UUID: UUIDs) {
3284 if (!doesPrimitiveExist(UUID)) {
3285 helios_runtime_error("ERROR (Context::getPrimitiveBoundingBox): Primitive with UUID of " + std::to_string(UUID) + " does not exist in the Context.");
3286 }
3287
3288 const std::vector<vec3> &vertices = getPrimitiveVertices(UUID);
3289
3290 if (p == 0) {
3291 min_corner = vertices.front();
3292 max_corner = min_corner;
3293 }
3294
3295 for (const vec3 &vert: vertices) {
3296 if (vert.x < min_corner.x) {
3297 min_corner.x = vert.x;
3298 }
3299 if (vert.y < min_corner.y) {
3300 min_corner.y = vert.y;
3301 }
3302 if (vert.z < min_corner.z) {
3303 min_corner.z = vert.z;
3304 }
3305 if (vert.x > max_corner.x) {
3306 max_corner.x = vert.x;
3307 }
3308 if (vert.y > max_corner.y) {
3309 max_corner.y = vert.y;
3310 }
3311 if (vert.z > max_corner.z) {
3312 max_corner.z = vert.z;
3313 }
3314 }
3315
3316 p++;
3317 }
3318}
3319
3321 return getPrimitivePointer_private(UUID)->getNormal();
3322}
3323
3324void Context::getPrimitiveTransformationMatrix(uint UUID, float (&T)[16]) const {
3325 getPrimitivePointer_private(UUID)->getTransformationMatrix(T);
3326}
3327
3329 getPrimitivePointer_private(UUID)->setTransformationMatrix(T);
3330}
3331
3332void Context::setPrimitiveTransformationMatrix(const std::vector<uint> &UUIDs, float (&T)[16]) {
3333 for (uint UUID: UUIDs) {
3334 getPrimitivePointer_private(UUID)->setTransformationMatrix(T);
3335 }
3336}
3337
3338std::vector<helios::vec3> Context::getPrimitiveVertices(uint UUID) const {
3339 return getPrimitivePointer_private(UUID)->getVertices();
3340}
3341
3342
3344 return getPrimitivePointer_private(UUID)->getColor();
3345}
3346
3348 return getPrimitivePointer_private(UUID)->getColorRGB();
3349}
3350
3352 return getPrimitivePointer_private(UUID)->getColorRGBA();
3353}
3354
3355void Context::setPrimitiveColor(uint UUID, const RGBcolor &color) const {
3356 api_warnings.addWarning("setPrimitiveColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3357 getPrimitivePointer_private(UUID)->setColor(color);
3358}
3359
3360void Context::setPrimitiveColor(const std::vector<uint> &UUIDs, const RGBcolor &color) const {
3361 api_warnings.addWarning("setPrimitiveColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3362 for (uint UUID: UUIDs) {
3363 getPrimitivePointer_private(UUID)->setColor(color);
3364 }
3365}
3366
3367void Context::setPrimitiveColor(uint UUID, const RGBAcolor &color) const {
3368 api_warnings.addWarning("setPrimitiveColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3369 getPrimitivePointer_private(UUID)->setColor(color);
3370}
3371
3372void Context::setPrimitiveColor(const std::vector<uint> &UUIDs, const RGBAcolor &color) const {
3373 api_warnings.addWarning("setPrimitiveColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3374 for (uint UUID: UUIDs) {
3375 getPrimitivePointer_private(UUID)->setColor(color);
3376 }
3377}
3378
3380 return getPrimitivePointer_private(UUID)->getTextureFile();
3381}
3382
3383void Context::setPrimitiveTextureFile(uint UUID, const std::string &texturefile) const {
3384 api_warnings.addWarning("setPrimitiveTextureFile_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3385 getPrimitivePointer_private(UUID)->setTextureFile(texturefile.c_str());
3386}
3387
3389 std::string texturefile = getPrimitivePointer_private(UUID)->getTextureFile();
3390 if (!texturefile.empty() && textures.find(texturefile) != textures.end()) {
3391 return textures.at(texturefile).getImageResolution();
3392 }
3393 return {0, 0};
3394}
3395
3396std::vector<helios::vec2> Context::getPrimitiveTextureUV(uint UUID) const {
3397 return getPrimitivePointer_private(UUID)->getTextureUV();
3398}
3399
3401 std::string texturefile = getPrimitivePointer_private(UUID)->getTextureFile();
3402 if (!texturefile.empty() && textures.find(texturefile) != textures.end()) {
3403 return textures.at(texturefile).hasTransparencyChannel();
3404 }
3405 return false;
3406}
3407
3408const std::vector<std::vector<bool>> *Context::getPrimitiveTextureTransparencyData(uint UUID) const {
3410 const std::vector<std::vector<bool>> *data = textures.at(getPrimitivePointer_private(UUID)->getTextureFile()).getTransparencyData();
3411 return data;
3412 }
3413
3414 helios_runtime_error("ERROR (Context::getPrimitiveTransparencyData): Texture transparency data does not exist for primitive " + std::to_string(UUID) + ".");
3415 return nullptr;
3416}
3417
3419 api_warnings.addWarning("overridePrimitiveTextureColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3420 getPrimitivePointer_private(UUID)->overrideTextureColor();
3421}
3422
3423void Context::overridePrimitiveTextureColor(const std::vector<uint> &UUIDs) const {
3424 api_warnings.addWarning("overridePrimitiveTextureColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3425 for (uint UUID: UUIDs) {
3426 getPrimitivePointer_private(UUID)->overrideTextureColor();
3427 }
3428}
3429
3431 api_warnings.addWarning("usePrimitiveTextureColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3432 getPrimitivePointer_private(UUID)->useTextureColor();
3433}
3434
3435void Context::usePrimitiveTextureColor(const std::vector<uint> &UUIDs) const {
3436 api_warnings.addWarning("usePrimitiveTextureColor_inefficient_api", "This method creates per-primitive materials. For better memory efficiency, use addMaterial() + assignMaterialToPrimitive().");
3437 for (uint UUID: UUIDs) {
3438 getPrimitivePointer_private(UUID)->useTextureColor();
3439 }
3440}
3441
3443 return getPrimitivePointer_private(UUID)->isTextureColorOverridden();
3444}
3445
3447 return getPrimitivePointer_private(UUID)->getSolidFraction();
3448}
3449
3451 std::cout << "-------------------------------------------" << std::endl;
3452 std::cout << "Info for UUID " << UUID << std::endl;
3453 std::cout << "-------------------------------------------" << std::endl;
3454
3455 PrimitiveType type = getPrimitiveType(UUID);
3456 std::string stype;
3457 if (type == 0) {
3458 stype = "PRIMITIVE_TYPE_PATCH";
3459 } else if (type == 1) {
3460 stype = "PRIMITIVE_TYPE_TRIANGLE";
3461 } else if (type == 2) {
3462 stype = "PRIMITIVE_TYPE_VOXEL";
3463 }
3464
3465 std::cout << "Type: " << stype << std::endl;
3466 std::cout << "Parent ObjID: " << getPrimitiveParentObjectID(UUID) << std::endl;
3467 std::cout << "Surface Area: " << getPrimitiveArea(UUID) << std::endl;
3468 std::cout << "Normal Vector: " << getPrimitiveNormal(UUID) << std::endl;
3469
3470 if (type == PRIMITIVE_TYPE_PATCH) {
3471 std::cout << "Patch Center: " << getPatchCenter(UUID) << std::endl;
3472 std::cout << "Patch Size: " << getPatchSize(UUID) << std::endl;
3473 } else if (type == PRIMITIVE_TYPE_VOXEL) {
3474 std::cout << "Voxel Center: " << getVoxelCenter(UUID) << std::endl;
3475 std::cout << "Voxel Size: " << getVoxelSize(UUID) << std::endl;
3476 }
3477
3478 std::vector<vec3> primitive_vertices = getPrimitiveVertices(UUID);
3479 std::cout << "Vertices: " << std::endl;
3480 for (uint i = 0; i < primitive_vertices.size(); i++) {
3481 std::cout << " " << primitive_vertices.at(i) << std::endl;
3482 }
3483
3484 float T[16];
3486 std::cout << "Transform: " << std::endl;
3487 std::cout << " " << T[0] << " " << T[1] << " " << T[2] << " " << T[3] << std::endl;
3488 std::cout << " " << T[4] << " " << T[5] << " " << T[6] << " " << T[7] << std::endl;
3489 std::cout << " " << T[8] << " " << T[9] << " " << T[10] << " " << T[11] << std::endl;
3490 std::cout << " " << T[12] << " " << T[13] << " " << T[14] << " " << T[15] << std::endl;
3491
3492 std::cout << "Color: " << getPrimitiveColor(UUID) << std::endl;
3493 std::cout << "Texture File: " << getPrimitiveTextureFile(UUID) << std::endl;
3494 std::cout << "Texture Size: " << getPrimitiveTextureSize(UUID) << std::endl;
3495 std::cout << "Texture UV: " << std::endl;
3496 std::vector<vec2> uv = getPrimitiveTextureUV(UUID);
3497 for (uint i = 0; i < uv.size(); i++) {
3498 std::cout << " " << uv.at(i) << std::endl;
3499 }
3500
3501 std::cout << "Texture Transparency: " << primitiveTextureHasTransparencyChannel(UUID) << std::endl;
3502 std::cout << "Color Overridden: " << isPrimitiveTextureColorOverridden(UUID) << std::endl;
3503 std::cout << "Solid Fraction: " << getPrimitiveSolidFraction(UUID) << std::endl;
3504
3505
3506 std::cout << "Primitive Data: " << std::endl;
3507 // Primitive* pointer = getPrimitivePointer_private(UUID);
3508 std::vector<std::string> pd = listPrimitiveData(UUID);
3509 for (uint i = 0; i < pd.size(); i++) {
3510 uint dsize = getPrimitiveDataSize(UUID, pd.at(i).c_str());
3511 HeliosDataType dtype = getPrimitiveDataType(pd.at(i).c_str());
3512 std::string dstype;
3513
3514 if (dtype == HELIOS_TYPE_INT) {
3515 dstype = "HELIOS_TYPE_INT";
3516 } else if (dtype == HELIOS_TYPE_UINT) {
3517 dstype = "HELIOS_TYPE_UINT";
3518 } else if (dtype == HELIOS_TYPE_FLOAT) {
3519 dstype = "HELIOS_TYPE_FLOAT";
3520 } else if (dtype == HELIOS_TYPE_DOUBLE) {
3521 dstype = "HELIOS_TYPE_DOUBLE";
3522 } else if (dtype == HELIOS_TYPE_VEC2) {
3523 dstype = "HELIOS_TYPE_VEC2";
3524 } else if (dtype == HELIOS_TYPE_VEC3) {
3525 dstype = "HELIOS_TYPE_VEC3";
3526 } else if (dtype == HELIOS_TYPE_VEC4) {
3527 dstype = "HELIOS_TYPE_VEC4";
3528 } else if (dtype == HELIOS_TYPE_INT2) {
3529 dstype = "HELIOS_TYPE_INT2";
3530 } else if (dtype == HELIOS_TYPE_INT3) {
3531 dstype = "HELIOS_TYPE_INT3";
3532 } else if (dtype == HELIOS_TYPE_INT4) {
3533 dstype = "HELIOS_TYPE_INT4";
3534 } else if (dtype == HELIOS_TYPE_STRING) {
3535 dstype = "HELIOS_TYPE_STRING";
3536 } else {
3537 assert(false);
3538 }
3539
3540
3541 std::cout << " " << "[name: " << pd.at(i) << ", type: " << dstype << ", size: " << dsize << "]:" << std::endl;
3542
3543
3544 if (dtype == HELIOS_TYPE_INT) {
3545 std::vector<int> pdata;
3546 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3547 for (uint j = 0; j < dsize; j++) {
3548 if (j < 10) {
3549 std::cout << " " << pdata.at(j) << std::endl;
3550 } else {
3551 std::cout << " ..." << std::endl;
3552 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3553 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3554 break;
3555 }
3556 }
3557 } else if (dtype == HELIOS_TYPE_UINT) {
3558 std::vector<uint> pdata;
3559 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3560 for (uint j = 0; j < dsize; j++) {
3561 if (j < 10) {
3562 std::cout << " " << pdata.at(j) << std::endl;
3563 } else {
3564 std::cout << " ..." << std::endl;
3565 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3566 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3567 break;
3568 }
3569 }
3570 } else if (dtype == HELIOS_TYPE_FLOAT) {
3571 std::vector<float> pdata;
3572 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3573 for (uint j = 0; j < dsize; j++) {
3574 if (j < 10) {
3575 std::cout << " " << pdata.at(j) << std::endl;
3576 } else {
3577 std::cout << " ..." << std::endl;
3578 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3579 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3580 break;
3581 }
3582 }
3583 } else if (dtype == HELIOS_TYPE_DOUBLE) {
3584 std::vector<double> pdata;
3585 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3586 for (uint j = 0; j < dsize; j++) {
3587 if (j < 10) {
3588 std::cout << " " << pdata.at(j) << std::endl;
3589 } else {
3590 std::cout << " ..." << std::endl;
3591 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3592 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3593 break;
3594 }
3595 }
3596 } else if (dtype == HELIOS_TYPE_VEC2) {
3597 std::vector<vec2> pdata;
3598 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3599 for (uint j = 0; j < dsize; j++) {
3600 if (j < 10) {
3601 std::cout << " " << pdata.at(j) << std::endl;
3602 } else {
3603 std::cout << " ..." << std::endl;
3604 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3605 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3606 break;
3607 }
3608 }
3609 } else if (dtype == HELIOS_TYPE_VEC3) {
3610 std::vector<vec3> pdata;
3611 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3612 for (uint j = 0; j < dsize; j++) {
3613 if (j < 10) {
3614 std::cout << " " << pdata.at(j) << std::endl;
3615 } else {
3616 std::cout << " ..." << std::endl;
3617 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3618 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3619 break;
3620 }
3621 }
3622 } else if (dtype == HELIOS_TYPE_VEC4) {
3623 std::vector<vec4> pdata;
3624 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3625 for (uint j = 0; j < dsize; j++) {
3626 if (j < 10) {
3627 std::cout << " " << pdata.at(j) << std::endl;
3628 } else {
3629 std::cout << " ..." << std::endl;
3630 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3631 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3632 break;
3633 }
3634 }
3635 } else if (dtype == HELIOS_TYPE_INT2) {
3636 std::vector<int2> pdata;
3637 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3638 for (uint j = 0; j < dsize; j++) {
3639 if (j < 10) {
3640 std::cout << " " << pdata.at(j) << std::endl;
3641 } else {
3642 std::cout << " ..." << std::endl;
3643 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3644 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3645 break;
3646 }
3647 }
3648 } else if (dtype == HELIOS_TYPE_INT3) {
3649 std::vector<int3> pdata;
3650 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3651 for (uint j = 0; j < dsize; j++) {
3652 if (j < 10) {
3653 std::cout << " " << pdata.at(j) << std::endl;
3654 } else {
3655 std::cout << " ..." << std::endl;
3656 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3657 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3658 break;
3659 }
3660 }
3661 } else if (dtype == HELIOS_TYPE_INT4) {
3662 std::vector<int4> pdata;
3663 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3664 for (uint j = 0; j < dsize; j++) {
3665 if (j < 10) {
3666 std::cout << " " << pdata.at(j) << std::endl;
3667 } else {
3668 std::cout << " ..." << std::endl;
3669 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3670 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3671 break;
3672 }
3673 }
3674 } else if (dtype == HELIOS_TYPE_STRING) {
3675 std::vector<std::string> pdata;
3676 getPrimitiveData(UUID, pd.at(i).c_str(), pdata);
3677 for (uint j = 0; j < dsize; j++) {
3678 if (j < 10) {
3679 std::cout << " " << pdata.at(j) << std::endl;
3680 } else {
3681 std::cout << " ..." << std::endl;
3682 std::cout << " " << pdata.at(dsize - 2) << std::endl;
3683 std::cout << " " << pdata.at(dsize - 1) << std::endl;
3684 break;
3685 }
3686 }
3687 } else {
3688 assert(false);
3689 }
3690 }
3691 std::cout << "-------------------------------------------" << std::endl;
3692}
3693
3694//========== MATERIAL MANAGEMENT METHODS ==========//
3695
3696uint Context::getMaterialIDFromLabel(const std::string &material_label) const {
3697 auto it = material_label_to_id.find(material_label);
3698 if (it == material_label_to_id.end()) {
3699 helios_runtime_error("ERROR (Context::getMaterialIDFromLabel): Material with label '" + material_label + "' does not exist.");
3700 }
3701 return it->second;
3702}
3703
3704void Context::addMaterial(const std::string &material_label) {
3705 if (material_label.empty()) {
3706 helios_runtime_error("ERROR (Context::addMaterial): Material label cannot be empty.");
3707 }
3708
3709 // Check for reserved label prefix
3710 if (material_label.substr(0, 2) == "__" && material_label != DEFAULT_MATERIAL_LABEL) {
3711 helios_runtime_error("ERROR (Context::addMaterial): Material labels starting with '__' are reserved for internal use.");
3712 }
3713
3714 // Check if label already exists - overwrite with warning
3715 if (material_label_to_id.find(material_label) != material_label_to_id.end()) {
3716 std::cerr << "WARNING (Context::addMaterial): Material with label '" << material_label << "' already exists. Overwriting." << std::endl;
3717 // Remove old material
3718 uint oldID = material_label_to_id[material_label];
3719 materials.erase(oldID);
3720 }
3721
3722 // Create new material with default properties
3723 uint newID = currentMaterialID++;
3724 Material newMaterial(newID, material_label, make_RGBAcolor(0, 0, 0, 1), "", false);
3725 materials[newID] = newMaterial;
3726 material_label_to_id[material_label] = newID;
3727}
3728
3729uint Context::addMaterial_internal(const std::string &label, const RGBAcolor &color, const std::string &texture) {
3730 // Internal method - no check for __ prefix reservation
3731 if (label.empty()) {
3732 helios_runtime_error("ERROR (Context::addMaterial_internal): Material label cannot be empty.");
3733 }
3734
3735 // Check if label already exists - silently overwrite for internal use
3736 if (material_label_to_id.find(label) != material_label_to_id.end()) {
3737 uint oldID = material_label_to_id[label];
3738 materials.erase(oldID);
3739 }
3740
3741 // Create new material with specified properties
3742 uint newID = currentMaterialID++;
3743 Material newMaterial(newID, label, color, texture, false);
3744 materials[newID] = newMaterial;
3745 material_label_to_id[label] = newID;
3746 return newID;
3747}
3748
3749std::string Context::generateMaterialLabel(const RGBAcolor &color, const std::string &texture, bool texture_override) const {
3750 // Generate hash from all material properties for de-duplication
3751 size_t hash = 0;
3752 hash ^= std::hash<float>{}(color.r) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3753 hash ^= std::hash<float>{}(color.g) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3754 hash ^= std::hash<float>{}(color.b) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3755 hash ^= std::hash<float>{}(color.a) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3756 hash ^= std::hash<std::string>{}(texture) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3757 hash ^= std::hash<bool>{}(texture_override) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
3758 return "__auto_" + std::to_string(hash);
3759}
3760
3761bool Context::isMaterialShared(uint materialID) const {
3762 if (materials.find(materialID) == materials.end()) {
3763 return false;
3764 }
3765 // Use reference count for O(1) lookup instead of O(n) scan
3766 return materials.at(materialID).reference_count > 1;
3767}
3768
3769uint Context::copyMaterialForPrimitive(uint primitiveUUID) {
3770 Primitive *prim = getPrimitivePointer_private(primitiveUUID);
3771 uint oldMaterialID = prim->materialID;
3772 const Material &oldMaterial = materials.at(oldMaterialID);
3773
3774 // Generate unique label using primitive UUID
3775 std::string newLabel = "__copy_" + std::to_string(currentMaterialID) + "_" + std::to_string(primitiveUUID);
3776
3777 // Create new material with same base properties
3778 uint newMaterialID = addMaterial_internal(newLabel, oldMaterial.color, oldMaterial.texture_file);
3779
3780 // Copy all material properties
3781 materials[newMaterialID].texture_color_overridden = oldMaterial.texture_color_overridden;
3782 materials[newMaterialID].twosided_flag = oldMaterial.twosided_flag;
3783
3784 // Copy all material data (all types)
3785 materials[newMaterialID].material_data_types = oldMaterial.material_data_types;
3786 materials[newMaterialID].material_data_int = oldMaterial.material_data_int;
3787 materials[newMaterialID].material_data_uint = oldMaterial.material_data_uint;
3788 materials[newMaterialID].material_data_float = oldMaterial.material_data_float;
3789 materials[newMaterialID].material_data_double = oldMaterial.material_data_double;
3790 materials[newMaterialID].material_data_vec2 = oldMaterial.material_data_vec2;
3791 materials[newMaterialID].material_data_vec3 = oldMaterial.material_data_vec3;
3792 materials[newMaterialID].material_data_vec4 = oldMaterial.material_data_vec4;
3793 materials[newMaterialID].material_data_int2 = oldMaterial.material_data_int2;
3794 materials[newMaterialID].material_data_int3 = oldMaterial.material_data_int3;
3795 materials[newMaterialID].material_data_int4 = oldMaterial.material_data_int4;
3796 materials[newMaterialID].material_data_string = oldMaterial.material_data_string;
3797 materials[newMaterialID].material_data_bool = oldMaterial.material_data_bool;
3798
3799 // Update material reference counts
3800 materials[oldMaterialID].reference_count--; // Decrement old material
3801 materials[newMaterialID].reference_count = 1; // New material has one user (this primitive)
3802
3803 // Update primitive to use new material
3804 prim->materialID = newMaterialID;
3805
3806 return newMaterialID;
3807}
3808
3809void Context::renameMaterial(const std::string &old_label, const std::string &new_label) {
3810 if (material_label_to_id.find(old_label) == material_label_to_id.end()) {
3811 helios_runtime_error("ERROR (Context::renameMaterial): Material with label '" + old_label + "' does not exist.");
3812 }
3813 if (new_label.empty()) {
3814 helios_runtime_error("ERROR (Context::renameMaterial): New material label cannot be empty.");
3815 }
3816 if (new_label.substr(0, 2) == "__") {
3817 helios_runtime_error("ERROR (Context::renameMaterial): Material labels starting with '__' are reserved for internal use.");
3818 }
3819 if (material_label_to_id.find(new_label) != material_label_to_id.end()) {
3820 helios_runtime_error("ERROR (Context::renameMaterial): Material with label '" + new_label + "' already exists.");
3821 }
3822
3823 uint materialID = material_label_to_id.at(old_label);
3824 materials.at(materialID).label = new_label;
3825 // Keep auto-generated labels in the lookup map so that future primitives with the same
3826 // hash-based label will find and reuse the existing material (preserving deduplication).
3827 if (old_label.substr(0, 7) != "__auto_") {
3828 material_label_to_id.erase(old_label);
3829 }
3830 material_label_to_id[new_label] = materialID;
3831}
3832
3833bool Context::doesMaterialExist(const std::string &material_label) const {
3834 return material_label_to_id.find(material_label) != material_label_to_id.end();
3835}
3836
3837std::vector<std::string> Context::listMaterials() const {
3838 std::vector<std::string> labels;
3839 labels.reserve(material_label_to_id.size());
3840 for (const auto &pair: material_label_to_id) {
3841 // Don't include the default material or auto-generated materials in the list
3842 if (pair.first != DEFAULT_MATERIAL_LABEL && pair.first.substr(0, 7) != "__auto_") {
3843 labels.push_back(pair.first);
3844 }
3845 }
3846 return labels;
3847}
3848
3849RGBAcolor Context::getMaterialColor(const std::string &material_label) const {
3850 uint matID = getMaterialIDFromLabel(material_label);
3851 return materials.at(matID).color;
3852}
3853
3854std::string Context::getMaterialTexture(const std::string &material_label) const {
3855 uint matID = getMaterialIDFromLabel(material_label);
3856 return materials.at(matID).texture_file;
3857}
3858
3859bool Context::isMaterialTextureColorOverridden(const std::string &material_label) const {
3860 uint matID = getMaterialIDFromLabel(material_label);
3861 return materials.at(matID).texture_color_overridden;
3862}
3863
3864void Context::setMaterialColor(const std::string &material_label, const RGBAcolor &color) {
3865 uint matID = getMaterialIDFromLabel(material_label);
3866 materials[matID].color = color;
3867}
3868
3869void Context::setMaterialTexture(const std::string &material_label, const std::string &texture_file) {
3870 uint matID = getMaterialIDFromLabel(material_label);
3871 // Add texture to context if it has a file
3872 if (!texture_file.empty()) {
3873 addTexture(texture_file.c_str());
3874 }
3875 materials[matID].texture_file = texture_file;
3876}
3877
3878void Context::setMaterialTextureColorOverride(const std::string &material_label, bool override) {
3879 uint matID = getMaterialIDFromLabel(material_label);
3880 materials[matID].texture_color_overridden = override;
3881}
3882
3883uint Context::getMaterialTwosidedFlag(const std::string &material_label) const {
3884 uint matID = getMaterialIDFromLabel(material_label);
3885 return materials.at(matID).twosided_flag;
3886}
3887
3888void Context::setMaterialTwosidedFlag(const std::string &material_label, uint twosided_flag) {
3889 uint matID = getMaterialIDFromLabel(material_label);
3890 materials[matID].twosided_flag = twosided_flag;
3891}
3892
3894 std::string mat_label = getPrimitiveMaterialLabel(UUID);
3895 bool has_user_material = (mat_label.substr(0, 7) != "__auto_" && mat_label != DEFAULT_MATERIAL_LABEL);
3896
3897 if (has_user_material) {
3898 return getMaterialTwosidedFlag(mat_label);
3899 }
3900
3901 if (doesPrimitiveDataExist(UUID, "twosided_flag")) {
3902 uint flag;
3903 getPrimitiveData(UUID, "twosided_flag", flag);
3904 return flag;
3905 }
3906
3907 return default_value;
3908}
3909
3910void Context::assignMaterialToPrimitive(uint UUID, const std::string &material_label) {
3911 uint materialID = getMaterialIDFromLabel(material_label);
3912 Primitive *prim = getPrimitivePointer_private(UUID);
3913 uint oldMaterialID = prim->materialID;
3914 // Update reference counts
3915 materials[oldMaterialID].reference_count--;
3916 materials[materialID].reference_count++;
3917 prim->materialID = materialID;
3918}
3919
3920void Context::assignMaterialToPrimitive(const std::vector<uint> &UUIDs, const std::string &material_label) {
3921 uint materialID = getMaterialIDFromLabel(material_label);
3922 for (uint UUID: UUIDs) {
3923 Primitive *prim = getPrimitivePointer_private(UUID);
3924 uint oldMaterialID = prim->materialID;
3925 // Update reference counts
3926 materials[oldMaterialID].reference_count--;
3927 materials[materialID].reference_count++;
3928 prim->materialID = materialID;
3929 }
3930}
3931
3932void Context::assignMaterialToObject(uint ObjID, const std::string &material_label) {
3933 std::vector<uint> UUIDs = getObjectPrimitiveUUIDs(ObjID);
3934 assignMaterialToPrimitive(UUIDs, material_label);
3935}
3936
3937void Context::assignMaterialToObject(const std::vector<uint> &ObjIDs, const std::string &material_label) {
3938 for (uint ObjID: ObjIDs) {
3939 assignMaterialToObject(ObjID, material_label);
3940 }
3941}
3942
3944 Primitive *prim = getPrimitivePointer_private(UUID);
3945 uint materialID = prim->materialID;
3946 // Find the label for this material ID
3947 if (materials.find(materialID) != materials.end()) {
3948 return materials.at(materialID).label;
3949 }
3950 return DEFAULT_MATERIAL_LABEL;
3951}
3952
3953std::vector<uint> Context::getPrimitivesUsingMaterial(const std::string &material_label) const {
3954 uint materialID = getMaterialIDFromLabel(material_label);
3955 std::vector<uint> result;
3956 for (const auto &pair: primitives) {
3957 if (pair.second->materialID == materialID) {
3958 result.push_back(pair.first);
3959 }
3960 }
3961 return result;
3962}
3963
3964void Context::deleteMaterial(const std::string &material_label) {
3965 if (material_label == DEFAULT_MATERIAL_LABEL) {
3966 helios_runtime_error("ERROR (Context::deleteMaterial): Cannot delete the default material.");
3967 }
3968
3969 auto it = material_label_to_id.find(material_label);
3970 if (it == material_label_to_id.end()) {
3971 helios_runtime_error("ERROR (Context::deleteMaterial): Material with label '" + material_label + "' does not exist.");
3972 }
3973
3974 uint materialID = it->second;
3975
3976 // Check if any primitives are using this material
3977 std::vector<uint> users = getPrimitivesUsingMaterial(material_label);
3978 if (!users.empty()) {
3979 std::cerr << "WARNING (Context::deleteMaterial): Material '" << material_label << "' is in use by " << users.size() << " primitives. They will be reassigned to the default material." << std::endl;
3980 // Reassign primitives to default material
3981 for (uint UUID: users) {
3982 Primitive *prim = getPrimitivePointer_private(UUID);
3983 // Update material reference counts
3984 materials[materialID].reference_count--; // Decrement deleted material
3985 prim->materialID = 0; // Default material ID
3986 materials[0].reference_count++; // Increment default material
3987 }
3988 }
3989
3990 // Remove the material
3991 materials.erase(materialID);
3992 material_label_to_id.erase(material_label);
3993}
3994
3995bool Context::doesMaterialDataExist(const std::string &material_label, const char *data_label) const {
3996 uint matID = getMaterialIDFromLabel(material_label);
3997 return materials.at(matID).doesMaterialDataExist(data_label);
3998}
3999
4000HeliosDataType Context::getMaterialDataType(const std::string &material_label, const char *data_label) const {
4001 uint matID = getMaterialIDFromLabel(material_label);
4002 return materials.at(matID).getMaterialDataType(data_label);
4003}
4004
4005void Context::clearMaterialData(const std::string &material_label, const char *data_label) {
4006 uint matID = getMaterialIDFromLabel(material_label);
4007 materials[matID].clearMaterialData(data_label);
4008}
4009
4011 Primitive *prim = getPrimitivePointer_private(UUID);
4012 return prim->materialID;
4013}
4014
4015const Material &Context::getMaterial(uint materialID) const {
4016 if (materials.find(materialID) == materials.end()) {
4017 helios_runtime_error("ERROR (Context::getMaterial): Material ID " + std::to_string(materialID) + " does not exist.");
4018 }
4019 return materials.at(materialID);
4020}
4021
4023 // Don't count the default material or auto-generated primitive materials
4024 uint count = 0;
4025 for (const auto &pair: material_label_to_id) {
4026 // Skip default and auto-generated materials
4027 if (pair.first != DEFAULT_MATERIAL_LABEL && pair.first.substr(0, 7) != "__auto_") {
4028 count++;
4029 }
4030 }
4031 return count;
4032}
4033
4035 std::cout << "-------------------------------------------" << std::endl;
4036 std::cout << "Info for ObjID " << ObjID << std::endl;
4037 std::cout << "-------------------------------------------" << std::endl;
4038
4039 ObjectType otype = getObjectType(ObjID);
4040 std::string ostype;
4041 if (otype == 0) {
4042 ostype = "OBJECT_TYPE_TILE";
4043 } else if (otype == 1) {
4044 ostype = "OBJECT_TYPE_SPHERE";
4045 } else if (otype == 2) {
4046 ostype = "OBJECT_TYPE_TUBE";
4047 } else if (otype == 3) {
4048 ostype = "OBJECT_TYPE_BOX";
4049 } else if (otype == 4) {
4050 ostype = "OBJECT_TYPE_DISK";
4051 } else if (otype == 5) {
4052 ostype = "OBJECT_TYPE_POLYMESH";
4053 } else if (otype == 6) {
4054 ostype = "OBJECT_TYPE_CONE";
4055 }
4056
4057 std::cout << "Type: " << ostype << std::endl;
4058 std::cout << "Object Bounding Box Center: " << getObjectCenter(ObjID) << std::endl;
4059 std::cout << "One-sided Surface Area: " << getObjectArea(ObjID) << std::endl;
4060
4061 std::cout << "Primitive Count: " << getObjectPrimitiveCount(ObjID) << std::endl;
4062
4063 if (areObjectPrimitivesComplete(ObjID)) {
4064 std::cout << "Object Primitives Complete" << std::endl;
4065 } else {
4066 std::cout << "Object Primitives Incomplete" << std::endl;
4067 }
4068
4069 std::cout << "Primitive UUIDs: " << std::endl;
4070 std::vector<uint> primitive_UUIDs = getObjectPrimitiveUUIDs(ObjID);
4071 for (uint i = 0; i < primitive_UUIDs.size(); i++) {
4072 if (i < 5) {
4073 PrimitiveType ptype = getPrimitiveType(primitive_UUIDs.at(i));
4074 std::string pstype;
4075 if (ptype == 0) {
4076 pstype = "PRIMITIVE_TYPE_PATCH";
4077 } else if (ptype == 1) {
4078 pstype = "PRIMITIVE_TYPE_TRIANGLE";
4079 }
4080 std::cout << " " << primitive_UUIDs.at(i) << " (" << pstype << ")" << std::endl;
4081 } else {
4082 std::cout << " ..." << std::endl;
4083 PrimitiveType ptype = getPrimitiveType(primitive_UUIDs.at(primitive_UUIDs.size() - 2));
4084 std::string pstype;
4085 if (ptype == 0) {
4086 pstype = "PRIMITIVE_TYPE_PATCH";
4087 } else if (ptype == 1) {
4088 pstype = "PRIMITIVE_TYPE_TRIANGLE";
4089 }
4090 std::cout << " " << primitive_UUIDs.at(primitive_UUIDs.size() - 2) << " (" << pstype << ")" << std::endl;
4091 ptype = getPrimitiveType(primitive_UUIDs.at(primitive_UUIDs.size() - 1));
4092 if (ptype == 0) {
4093 pstype = "PRIMITIVE_TYPE_PATCH";
4094 } else if (ptype == 1) {
4095 pstype = "PRIMITIVE_TYPE_TRIANGLE";
4096 }
4097 std::cout << " " << primitive_UUIDs.at(primitive_UUIDs.size() - 1) << " (" << pstype << ")" << std::endl;
4098 break;
4099 }
4100 }
4101
4102 if (otype == OBJECT_TYPE_TILE) {
4103 std::cout << "Tile Center: " << getTileObjectCenter(ObjID) << std::endl;
4104 std::cout << "Tile Size: " << getTileObjectSize(ObjID) << std::endl;
4105 std::cout << "Tile Subdivision Count: " << getTileObjectSubdivisionCount(ObjID) << std::endl;
4106 std::cout << "Tile Normal: " << getTileObjectNormal(ObjID) << std::endl;
4107
4108 std::cout << "Tile Texture UV: " << std::endl;
4109 std::vector<vec2> uv = getTileObjectTextureUV(ObjID);
4110 for (uint i = 0; i < uv.size(); i++) {
4111 std::cout << " " << uv.at(i) << std::endl;
4112 }
4113
4114 std::cout << "Tile Vertices: " << std::endl;
4115 std::vector<vec3> primitive_vertices = getTileObjectVertices(ObjID);
4116 for (uint i = 0; i < primitive_vertices.size(); i++) {
4117 std::cout << " " << primitive_vertices.at(i) << std::endl;
4118 }
4119 } else if (otype == OBJECT_TYPE_SPHERE) {
4120 std::cout << "Sphere Center: " << getSphereObjectCenter(ObjID) << std::endl;
4121 std::cout << "Sphere Radius: " << getSphereObjectRadius(ObjID) << std::endl;
4122 std::cout << "Sphere Subdivision Count: " << getSphereObjectSubdivisionCount(ObjID) << std::endl;
4123 } else if (otype == OBJECT_TYPE_TUBE) {
4124 std::cout << "Tube Subdivision Count: " << getTubeObjectSubdivisionCount(ObjID) << std::endl;
4125 std::cout << "Tube Nodes: " << std::endl;
4126 std::vector<vec3> nodes = getTubeObjectNodes(ObjID);
4127 for (uint i = 0; i < nodes.size(); i++) {
4128 if (i < 10) {
4129 std::cout << " " << nodes.at(i) << std::endl;
4130 } else {
4131 std::cout << " ..." << std::endl;
4132 std::cout << " " << nodes.at(nodes.size() - 2) << std::endl;
4133 std::cout << " " << nodes.at(nodes.size() - 1) << std::endl;
4134 break;
4135 }
4136 }
4137 std::cout << "Tube Node Radii: " << std::endl;
4138 std::vector<float> noderadii = getTubeObjectNodeRadii(ObjID);
4139 for (uint i = 0; i < noderadii.size(); i++) {
4140 if (i < 10) {
4141 std::cout << " " << noderadii.at(i) << std::endl;
4142 } else {
4143 std::cout << " ..." << std::endl;
4144 std::cout << " " << noderadii.at(noderadii.size() - 2) << std::endl;
4145 std::cout << " " << noderadii.at(noderadii.size() - 1) << std::endl;
4146 break;
4147 }
4148 }
4149 std::cout << "Tube Node Colors: " << std::endl;
4150 std::vector<helios::RGBcolor> nodecolors = getTubeObjectNodeColors(ObjID);
4151 for (uint i = 0; i < nodecolors.size(); i++) {
4152 if (i < 10) {
4153 std::cout << " " << nodecolors.at(i) << std::endl;
4154 } else {
4155 std::cout << " ..." << std::endl;
4156 std::cout << " " << nodecolors.at(nodecolors.size() - 2) << std::endl;
4157 std::cout << " " << nodecolors.at(nodecolors.size() - 1) << std::endl;
4158 break;
4159 }
4160 }
4161 } else if (otype == OBJECT_TYPE_BOX) {
4162 std::cout << "Box Center: " << getBoxObjectCenter(ObjID) << std::endl;
4163 std::cout << "Box Size: " << getBoxObjectSize(ObjID) << std::endl;
4164 std::cout << "Box Subdivision Count: " << getBoxObjectSubdivisionCount(ObjID) << std::endl;
4165 } else if (otype == OBJECT_TYPE_DISK) {
4166 std::cout << "Disk Center: " << getDiskObjectCenter(ObjID) << std::endl;
4167 std::cout << "Disk Size: " << getDiskObjectSize(ObjID) << std::endl;
4168 std::cout << "Disk Subdivision Count: " << getDiskObjectSubdivisionCount(ObjID) << std::endl;
4169
4170 // }else if(type == OBJECT_TYPE_POLYMESH){
4171 // nothing for now
4172 } else if (otype == OBJECT_TYPE_CONE) {
4173 std::cout << "Cone Length: " << getConeObjectLength(ObjID) << std::endl;
4174 std::cout << "Cone Axis Unit Vector: " << getConeObjectAxisUnitVector(ObjID) << std::endl;
4175 std::cout << "Cone Subdivision Count: " << getConeObjectSubdivisionCount(ObjID) << std::endl;
4176 std::cout << "Cone Nodes: " << std::endl;
4177 std::vector<vec3> nodes = getConeObjectNodes(ObjID);
4178 for (uint i = 0; i < nodes.size(); i++) {
4179 std::cout << " " << nodes.at(i) << std::endl;
4180 }
4181 std::cout << "Cone Node Radii: " << std::endl;
4182 std::vector<float> noderadii = getConeObjectNodeRadii(ObjID);
4183 for (uint i = 0; i < noderadii.size(); i++) {
4184 std::cout << " " << noderadii.at(i) << std::endl;
4185 }
4186 }
4187
4188
4189 float T[16];
4191 std::cout << "Transform: " << std::endl;
4192 std::cout << " " << T[0] << " " << T[1] << " " << T[2] << " " << T[3] << std::endl;
4193 std::cout << " " << T[4] << " " << T[5] << " " << T[6] << " " << T[7] << std::endl;
4194 std::cout << " " << T[8] << " " << T[9] << " " << T[10] << " " << T[11] << std::endl;
4195 std::cout << " " << T[12] << " " << T[13] << " " << T[14] << " " << T[15] << std::endl;
4196
4197 std::cout << "Texture File: " << getObjectTextureFile(ObjID) << std::endl;
4198
4199 std::cout << "Object Data: " << std::endl;
4200 // Primitive* pointer = getPrimitivePointer_private(ObjID);
4201 std::vector<std::string> pd = listObjectData(ObjID);
4202 for (uint i = 0; i < pd.size(); i++) {
4203 uint dsize = getObjectDataSize(ObjID, pd.at(i).c_str());
4204 HeliosDataType dtype = getObjectDataType(pd.at(i).c_str());
4205 std::string dstype;
4206
4207 if (dtype == HELIOS_TYPE_INT) {
4208 dstype = "HELIOS_TYPE_INT";
4209 } else if (dtype == HELIOS_TYPE_UINT) {
4210 dstype = "HELIOS_TYPE_UINT";
4211 } else if (dtype == HELIOS_TYPE_FLOAT) {
4212 dstype = "HELIOS_TYPE_FLOAT";
4213 } else if (dtype == HELIOS_TYPE_DOUBLE) {
4214 dstype = "HELIOS_TYPE_DOUBLE";
4215 } else if (dtype == HELIOS_TYPE_VEC2) {
4216 dstype = "HELIOS_TYPE_VEC2";
4217 } else if (dtype == HELIOS_TYPE_VEC3) {
4218 dstype = "HELIOS_TYPE_VEC3";
4219 } else if (dtype == HELIOS_TYPE_VEC4) {
4220 dstype = "HELIOS_TYPE_VEC4";
4221 } else if (dtype == HELIOS_TYPE_INT2) {
4222 dstype = "HELIOS_TYPE_INT2";
4223 } else if (dtype == HELIOS_TYPE_INT3) {
4224 dstype = "HELIOS_TYPE_INT3";
4225 } else if (dtype == HELIOS_TYPE_INT4) {
4226 dstype = "HELIOS_TYPE_INT4";
4227 } else if (dtype == HELIOS_TYPE_STRING) {
4228 dstype = "HELIOS_TYPE_STRING";
4229 } else {
4230 assert(false);
4231 }
4232
4233
4234 std::cout << " " << "[name: " << pd.at(i) << ", type: " << dstype << ", size: " << dsize << "]:" << std::endl;
4235
4236
4237 if (dtype == HELIOS_TYPE_INT) {
4238 std::vector<int> pdata;
4239 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4240 for (uint j = 0; j < dsize; j++) {
4241 if (j < 10) {
4242 std::cout << " " << pdata.at(j) << std::endl;
4243 } else {
4244 std::cout << " ..." << std::endl;
4245 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4246 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4247 break;
4248 }
4249 }
4250 } else if (dtype == HELIOS_TYPE_UINT) {
4251 std::vector<uint> pdata;
4252 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4253 for (uint j = 0; j < dsize; j++) {
4254 if (j < 10) {
4255 std::cout << " " << pdata.at(j) << std::endl;
4256 } else {
4257 std::cout << " ..." << std::endl;
4258 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4259 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4260 break;
4261 }
4262 }
4263 } else if (dtype == HELIOS_TYPE_FLOAT) {
4264 std::vector<float> pdata;
4265 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4266 for (uint j = 0; j < dsize; j++) {
4267 if (j < 10) {
4268 std::cout << " " << pdata.at(j) << std::endl;
4269 } else {
4270 std::cout << " ..." << std::endl;
4271 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4272 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4273 break;
4274 }
4275 }
4276 } else if (dtype == HELIOS_TYPE_DOUBLE) {
4277 std::vector<double> pdata;
4278 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4279 for (uint j = 0; j < dsize; j++) {
4280 if (j < 10) {
4281 std::cout << " " << pdata.at(j) << std::endl;
4282 } else {
4283 std::cout << " ..." << std::endl;
4284 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4285 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4286 break;
4287 }
4288 }
4289 } else if (dtype == HELIOS_TYPE_VEC2) {
4290 std::vector<vec2> pdata;
4291 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4292 for (uint j = 0; j < dsize; j++) {
4293 if (j < 10) {
4294 std::cout << " " << pdata.at(j) << std::endl;
4295 } else {
4296 std::cout << " ..." << std::endl;
4297 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4298 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4299 break;
4300 }
4301 }
4302 } else if (dtype == HELIOS_TYPE_VEC3) {
4303 std::vector<vec3> pdata;
4304 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4305 for (uint j = 0; j < dsize; j++) {
4306 if (j < 10) {
4307 std::cout << " " << pdata.at(j) << std::endl;
4308 } else {
4309 std::cout << " ..." << std::endl;
4310 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4311 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4312 break;
4313 }
4314 }
4315 } else if (dtype == HELIOS_TYPE_VEC4) {
4316 std::vector<vec4> pdata;
4317 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4318 for (uint j = 0; j < dsize; j++) {
4319 if (j < 10) {
4320 std::cout << " " << pdata.at(j) << std::endl;
4321 } else {
4322 std::cout << " ..." << std::endl;
4323 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4324 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4325 break;
4326 }
4327 }
4328 } else if (dtype == HELIOS_TYPE_INT2) {
4329 std::vector<int2> pdata;
4330 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4331 for (uint j = 0; j < dsize; j++) {
4332 if (j < 10) {
4333 std::cout << " " << pdata.at(j) << std::endl;
4334 } else {
4335 std::cout << " ..." << std::endl;
4336 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4337 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4338 break;
4339 }
4340 }
4341 } else if (dtype == HELIOS_TYPE_INT3) {
4342 std::vector<int3> pdata;
4343 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4344 for (uint j = 0; j < dsize; j++) {
4345 if (j < 10) {
4346 std::cout << " " << pdata.at(j) << std::endl;
4347 } else {
4348 std::cout << " ..." << std::endl;
4349 std::cout << " " << pdata.at(dsize - 2) << std::endl;
4350 std::cout << " " << pdata.at(dsize - 1) << std::endl;
4351 break;
4352 }
4353 }
4354 } else if (dtype == HELIOS_TYPE_INT4) {
4355 std::vector<int4> pdata;
4356 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4357 for (uint j = 0; j < dsize; j++) {
4358 if (j < 10) {
4359 std::cout << " " << pdata.at(j) << std::endl;
4360 } else {
4361 std::cout << " ..." << std::endl;
4362 break;
4363 }
4364 }
4365 } else if (dtype == HELIOS_TYPE_STRING) {
4366 std::vector<std::string> pdata;
4367 getObjectData(ObjID, pd.at(i).c_str(), pdata);
4368 for (uint j = 0; j < dsize; j++) {
4369 if (j < 10) {
4370 std::cout << " " << pdata.at(j) << std::endl;
4371 } else {
4372 std::cout << " ..." << std::endl;
4373 break;
4374 }
4375 }
4376 } else {
4377 assert(false);
4378 }
4379 }
4380 std::cout << "-------------------------------------------" << std::endl;
4381}
4382
4383CompoundObject *Context::getObjectPointer_private(uint ObjID) const {
4384#ifdef HELIOS_DEBUG
4385 if (objects.find(ObjID) == objects.end()) {
4386 helios_runtime_error("ERROR (Context::getObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4387 }
4388#endif
4389 return objects.at(ObjID);
4390}
4391
4393#ifdef HELIOS_DEBUG
4394 if (!doesObjectExist(ObjID)) {
4395 helios_runtime_error("ERROR (Context::hideObject): Object ID of " + std::to_string(ObjID) + " does not exist in the Context.");
4396 }
4397#endif
4398 objects.at(ObjID)->ishidden = true;
4399 for (uint UUID: objects.at(ObjID)->getPrimitiveUUIDs()) {
4400#ifdef HELIOS_DEBUG
4401 if (!doesPrimitiveExist(UUID)) {
4402 helios_runtime_error("ERROR (Context::hideObject): Primitive UUID of " + std::to_string(UUID) + " does not exist in the Context.");
4403 }
4404#endif
4405 primitives.at(UUID)->ishidden = true;
4406 }
4407}
4408
4409void Context::hideObject(const std::vector<uint> &ObjIDs) {
4410 for (uint ObjID: ObjIDs) {
4411 hideObject(ObjID);
4412 }
4413}
4414
4416#ifdef HELIOS_DEBUG
4417 if (!doesObjectExist(ObjID)) {
4418 helios_runtime_error("ERROR (Context::showObject): Object ID of " + std::to_string(ObjID) + " does not exist in the Context.");
4419 }
4420#endif
4421 objects.at(ObjID)->ishidden = false;
4422 for (uint UUID: objects.at(ObjID)->getPrimitiveUUIDs()) {
4423#ifdef HELIOS_DEBUG
4424 if (!doesPrimitiveExist(UUID)) {
4425 helios_runtime_error("ERROR (Context::showObject): Primitive UUID of " + std::to_string(UUID) + " does not exist in the Context.");
4426 }
4427#endif
4428 primitives.at(UUID)->ishidden = false;
4429 }
4430}
4431
4432void Context::showObject(const std::vector<uint> &ObjIDs) {
4433 for (uint ObjID: ObjIDs) {
4434 showObject(ObjID);
4435 }
4436}
4437
4439 if (!doesObjectExist(ObjID)) {
4440 helios_runtime_error("ERROR (Context::isObjectHidden): Object ID of " + std::to_string(ObjID) + " does not exist in the Context.");
4441 }
4442 return objects.at(ObjID)->ishidden;
4443}
4444
4445float Context::getObjectArea(uint ObjID) const {
4446 return getObjectPointer_private(ObjID)->getArea();
4447}
4448
4450#ifdef HELIOS_DEBUG
4451 if (objects.find(ObjID) == objects.end()) {
4452 helios_runtime_error("ERROR (Context::getObjectAverageNormal): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4453 }
4454#endif
4455
4456 const std::vector<uint> &UUIDs = objects.at(ObjID)->getPrimitiveUUIDs();
4457
4458 vec3 norm_avg;
4459 for (uint UUID: UUIDs) {
4460 norm_avg += getPrimitiveNormal(UUID);
4461 }
4462 norm_avg.normalize();
4463
4464 return norm_avg;
4465}
4466
4468 return getObjectPointer_private(ObjID)->getPrimitiveCount();
4469}
4470
4472 return getObjectPointer_private(ObjID)->getObjectCenter();
4473}
4474
4475std::string Context::getObjectTextureFile(uint ObjID) const {
4476 return getObjectPointer_private(ObjID)->getTextureFile();
4477}
4478
4479void Context::getObjectTransformationMatrix(uint ObjID, float (&T)[16]) const {
4480 getObjectPointer_private(ObjID)->getTransformationMatrix(T);
4481}
4482
4483void Context::setObjectTransformationMatrix(uint ObjID, float (&T)[16]) const {
4484 getObjectPointer_private(ObjID)->setTransformationMatrix(T);
4485}
4486
4487void Context::setObjectTransformationMatrix(const std::vector<uint> &ObjIDs, float (&T)[16]) const {
4488 for (uint ObjID: ObjIDs) {
4489 getObjectPointer_private(ObjID)->setTransformationMatrix(T);
4490 }
4491}
4492
4493void Context::setObjectAverageNormal(uint ObjID, const vec3 &origin, const vec3 &new_normal) const {
4494#ifdef HELIOS_DEBUG
4495 if (!doesObjectExist(ObjID)) {
4496 helios_runtime_error("setObjectAverageNormal: invalid objectID");
4497 }
4498#endif
4499
4500 // 1) Compute unit old & new normals
4501 vec3 oldN = normalize(getObjectAverageNormal(ObjID));
4502 vec3 newN = normalize(new_normal);
4503
4504 // 2) Minimal‐angle axis & angle
4505 float d = std::clamp(oldN * newN, -1.f, 1.f);
4506 float angle = acosf(d);
4507 vec3 axis = cross(oldN, newN);
4508 if (axis.magnitude() < 1e-6f) {
4509 // pick any vector ⟂ oldN
4510 axis = (std::abs(oldN.x) < std::abs(oldN.z)) ? cross(oldN, {1, 0, 0}) : cross(oldN, {0, 0, 1});
4511 }
4512 axis = axis.normalize();
4513
4514 // 3) Apply that minimal‐angle rotation to the compound (no pizza‐spin yet)
4515 // NOTE: correct argument order is (objectID, angle, origin, axis)
4516 rotateObject(ObjID, angle, origin, axis);
4517
4518 // 4) Fetch the updated transform and extract the world‐space “forward” (local +X)
4519 float M_mid[16];
4520 getObjectPointer_private(ObjID)->getTransformationMatrix(M_mid);
4521
4522 vec3 localX{1, 0, 0};
4523 vec3 t1;
4524 // vecmult multiplies the 4×4 M_mid by v3 (w=0), writing into t1
4525 vecmult(M_mid, localX, t1);
4526 t1 = normalize(t1);
4527
4528 // 5) Compute desired forward = world‐X projected into the new plane
4529 vec3 worldX{1, 0, 0};
4530 vec3 targ = worldX - newN * (newN * worldX);
4531 targ = normalize(targ);
4532
4533 // 6) Compute signed twist about newN that carries t1→targ
4534 float twist = atan2f(newN * cross(t1, targ), // dot(newN, t1×targ)
4535 t1 * targ // dot(t1, targ)
4536 );
4537
4538 // 7) Apply that compensating twist about the same origin
4539 rotateObject(ObjID, twist, origin, newN);
4540}
4541
4542void Context::setObjectOrigin(uint ObjID, const vec3 &origin) const {
4543#ifdef HELIOS_DEBUG
4544 if (!doesObjectExist(ObjID)) {
4545 helios_runtime_error("ERROR (Context::setObjectOrigin): invalid objectID");
4546 }
4547#endif
4548 objects.at(ObjID)->object_origin = origin;
4549}
4550
4552 return getObjectPointer_private(ObjID)->hasTexture();
4553}
4554
4555void Context::setObjectColor(uint ObjID, const RGBcolor &color) const {
4556 getObjectPointer_private(ObjID)->setColor(color);
4557}
4558
4559void Context::setObjectColor(const std::vector<uint> &ObjIDs, const RGBcolor &color) const {
4560 for (const uint ObjID: ObjIDs) {
4561 getObjectPointer_private(ObjID)->setColor(color);
4562 }
4563}
4564
4565void Context::setObjectColor(uint ObjID, const RGBAcolor &color) const {
4566 getObjectPointer_private(ObjID)->setColor(color);
4567}
4568
4569void Context::setObjectColor(const std::vector<uint> &ObjIDs, const RGBAcolor &color) const {
4570 for (const uint ObjID: ObjIDs) {
4571 getObjectPointer_private(ObjID)->setColor(color);
4572 }
4573}
4574
4576 return getObjectPointer_private(ObjID)->doesObjectContainPrimitive(UUID);
4577}
4578
4580 getObjectPointer_private(ObjID)->overrideTextureColor();
4581}
4582
4583void Context::overrideObjectTextureColor(const std::vector<uint> &ObjIDs) const {
4584 for (uint ObjID: ObjIDs) {
4585 getObjectPointer_private(ObjID)->overrideTextureColor();
4586 }
4587}
4588
4590 getObjectPointer_private(ObjID)->useTextureColor();
4591}
4592
4593void Context::useObjectTextureColor(const std::vector<uint> &ObjIDs) {
4594 for (uint ObjID: ObjIDs) {
4595 getObjectPointer_private(ObjID)->useTextureColor();
4596 }
4597}
4598
4599void Context::getObjectBoundingBox(uint ObjID, vec3 &min_corner, vec3 &max_corner) const {
4600 const std::vector ObjIDs{ObjID};
4601 getObjectBoundingBox(ObjIDs, min_corner, max_corner);
4602}
4603
4604void Context::getObjectBoundingBox(const std::vector<uint> &ObjIDs, vec3 &min_corner, vec3 &max_corner) const {
4605 uint o = 0;
4606 for (uint ObjID: ObjIDs) {
4607 if (objects.find(ObjID) == objects.end()) {
4608 helios_runtime_error("ERROR (Context::getObjectBoundingBox): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4609 }
4610
4611 const std::vector<uint> &UUIDs = objects.at(ObjID)->getPrimitiveUUIDs();
4612
4613 uint p = 0;
4614 for (const uint UUID: UUIDs) {
4615 const std::vector<vec3> &vertices = getPrimitiveVertices(UUID);
4616
4617 if (p == 0 && o == 0) {
4618 min_corner = vertices.front();
4619 max_corner = min_corner;
4620 p++;
4621 continue;
4622 }
4623
4624 for (const vec3 &vert: vertices) {
4625 if (vert.x < min_corner.x) {
4626 min_corner.x = vert.x;
4627 }
4628 if (vert.y < min_corner.y) {
4629 min_corner.y = vert.y;
4630 }
4631 if (vert.z < min_corner.z) {
4632 min_corner.z = vert.z;
4633 }
4634 if (vert.x > max_corner.x) {
4635 max_corner.x = vert.x;
4636 }
4637 if (vert.y > max_corner.y) {
4638 max_corner.y = vert.y;
4639 }
4640 if (vert.z > max_corner.z) {
4641 max_corner.z = vert.z;
4642 }
4643 }
4644 }
4645
4646 o++;
4647 }
4648}
4649
4650Tile *Context::getTileObjectPointer_private(uint ObjID) const {
4651#ifdef HELIOS_DEBUG
4652 if (objects.find(ObjID) == objects.end()) {
4653 helios_runtime_error("ERROR (Context::getTileObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4654 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_TILE) {
4655 helios_runtime_error("ERROR (Context::getTileObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Tile Object.");
4656 }
4657#endif
4658 return dynamic_cast<Tile *>(objects.at(ObjID));
4659}
4660
4661Sphere *Context::getSphereObjectPointer_private(uint ObjID) const {
4662#ifdef HELIOS_DEBUG
4663 if (objects.find(ObjID) == objects.end()) {
4664 helios_runtime_error("ERROR (Context::getSphereObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4665 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_SPHERE) {
4666 helios_runtime_error("ERROR (Context::getSphereObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Sphere Object.");
4667 }
4668#endif
4669 return dynamic_cast<Sphere *>(objects.at(ObjID));
4670}
4671
4672Tube *Context::getTubeObjectPointer_private(uint ObjID) const {
4673#ifdef HELIOS_DEBUG
4674 if (objects.find(ObjID) == objects.end()) {
4675 helios_runtime_error("ERROR (Context::getTubeObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4676 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_TUBE) {
4677 helios_runtime_error("ERROR (Context::getTubeObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Tube Object.");
4678 }
4679#endif
4680 return dynamic_cast<Tube *>(objects.at(ObjID));
4681}
4682
4683Box *Context::getBoxObjectPointer_private(uint ObjID) const {
4684#ifdef HELIOS_DEBUG
4685 if (objects.find(ObjID) == objects.end()) {
4686 helios_runtime_error("ERROR (Context::getBoxObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4687 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_BOX) {
4688 helios_runtime_error("ERROR (Context::getBoxObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Box Object.");
4689 }
4690#endif
4691 return dynamic_cast<Box *>(objects.at(ObjID));
4692}
4693
4694Disk *Context::getDiskObjectPointer_private(uint ObjID) const {
4695#ifdef HELIOS_DEBUG
4696 if (objects.find(ObjID) == objects.end()) {
4697 helios_runtime_error("ERROR (Context::getDiskObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4698 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_DISK) {
4699 helios_runtime_error("ERROR (Context::getDiskObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Disk Object.");
4700 }
4701#endif
4702 return dynamic_cast<Disk *>(objects.at(ObjID));
4703}
4704
4705Polymesh *Context::getPolymeshObjectPointer_private(uint ObjID) const {
4706#ifdef HELIOS_DEBUG
4707 if (objects.find(ObjID) == objects.end()) {
4708 helios_runtime_error("ERROR (Context::getPolymeshObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4709 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_POLYMESH) {
4710 helios_runtime_error("ERROR (Context::getPolymeshObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Polymesh Object.");
4711 }
4712#endif
4713 return dynamic_cast<Polymesh *>(objects.at(ObjID));
4714}
4715
4716Cone *Context::getConeObjectPointer_private(uint ObjID) const {
4717#ifdef HELIOS_DEBUG
4718 if (objects.find(ObjID) == objects.end()) {
4719 helios_runtime_error("ERROR (Context::getConeObjectPointer): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4720 } else if (objects.at(ObjID)->getObjectType() != OBJECT_TYPE_CONE) {
4721 helios_runtime_error("ERROR (Context::getConeObjectPointer): ObjectID of " + std::to_string(ObjID) + " is not a Cone Object.");
4722 }
4723#endif
4724 return dynamic_cast<Cone *>(objects.at(ObjID));
4725}
4726
4728 return getTileObjectPointer_private(ObjID)->getCenter();
4729}
4730
4732 return getTileObjectPointer_private(ObjID)->getSize();
4733}
4734
4736 return getTileObjectPointer_private(ObjID)->getSubdivisionCount();
4737}
4738
4740 return getTileObjectPointer_private(ObjID)->getNormal();
4741}
4742
4743std::vector<helios::vec2> Context::getTileObjectTextureUV(uint ObjID) const {
4744 return getTileObjectPointer_private(ObjID)->getTextureUV();
4745}
4746
4747std::vector<helios::vec3> Context::getTileObjectVertices(uint ObjID) const {
4748 return getTileObjectPointer_private(ObjID)->getVertices();
4749}
4750
4752 return getSphereObjectPointer_private(ObjID)->getCenter();
4753}
4754
4756 return getSphereObjectPointer_private(ObjID)->getRadius();
4757}
4758
4760 return getSphereObjectPointer_private(ObjID)->getSubdivisionCount();
4761}
4762
4764 return getSphereObjectPointer_private(ObjID)->getVolume();
4765}
4766
4768 return getTubeObjectPointer_private(ObjID)->getSubdivisionCount();
4769}
4770
4771std::vector<helios::vec3> Context::getTubeObjectNodes(uint ObjID) const {
4772 return getTubeObjectPointer_private(ObjID)->getNodes();
4773}
4774
4776 return getTubeObjectPointer_private(ObjID)->getNodeCount();
4777}
4778
4779std::vector<float> Context::getTubeObjectNodeRadii(uint ObjID) const {
4780 return getTubeObjectPointer_private(ObjID)->getNodeRadii();
4781}
4782
4783std::vector<RGBcolor> Context::getTubeObjectNodeColors(uint ObjID) const {
4784 return getTubeObjectPointer_private(ObjID)->getNodeColors();
4785}
4786
4788 return getTubeObjectPointer_private(ObjID)->getVolume();
4789}
4790
4791float Context::getTubeObjectSegmentVolume(uint ObjID, uint segment_index) const {
4792 return getTubeObjectPointer_private(ObjID)->getSegmentVolume(segment_index);
4793}
4794
4795void Context::appendTubeSegment(uint ObjID, const helios::vec3 &node_position, float node_radius, const RGBcolor &node_color) {
4796#ifdef HELIOS_DEBUG
4797 if (objects.find(ObjID) == objects.end()) {
4798 helios_runtime_error("ERROR (Context::appendTubeSegment): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4799 }
4800#endif
4801 dynamic_cast<Tube *>(objects.at(ObjID))->appendTubeSegment(node_position, node_radius, node_color);
4802}
4803
4804void Context::appendTubeSegment(uint ObjID, const helios::vec3 &node_position, float node_radius, const char *texturefile, const helios::vec2 &textureuv_ufrac) {
4805#ifdef HELIOS_DEBUG
4806 if (objects.find(ObjID) == objects.end()) {
4807 helios_runtime_error("ERROR (Context::appendTubeSegment): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4808 }
4809#endif
4810 dynamic_cast<Tube *>(objects.at(ObjID))->appendTubeSegment(node_position, node_radius, texturefile, textureuv_ufrac);
4811}
4812
4813void Context::scaleTubeGirth(uint ObjID, float scale_factor) {
4814#ifdef HELIOS_DEBUG
4815 if (objects.find(ObjID) == objects.end()) {
4816 helios_runtime_error("ERROR (Context::scaleTubeGirth): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4817 }
4818#endif
4819 dynamic_cast<Tube *>(objects.at(ObjID))->scaleTubeGirth(scale_factor);
4820}
4821
4822void Context::setTubeRadii(uint ObjID, const std::vector<float> &node_radii) {
4823#ifdef HELIOS_DEBUG
4824 if (objects.find(ObjID) == objects.end()) {
4825 helios_runtime_error("ERROR (Context::setTubeRadii): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4826 }
4827#endif
4828 dynamic_cast<Tube *>(objects.at(ObjID))->setTubeRadii(node_radii);
4829}
4830
4831void Context::scaleTubeLength(uint ObjID, float scale_factor) {
4832#ifdef HELIOS_DEBUG
4833 if (objects.find(ObjID) == objects.end()) {
4834 helios_runtime_error("ERROR (Context::scaleTubeLength): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4835 }
4836#endif
4837 dynamic_cast<Tube *>(objects.at(ObjID))->scaleTubeLength(scale_factor);
4838}
4839
4840void Context::pruneTubeNodes(uint ObjID, uint node_index) {
4841#ifdef HELIOS_DEBUG
4842 if (objects.find(ObjID) == objects.end()) {
4843 helios_runtime_error("ERROR (Context::pruneTubeNodes): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4844 }
4845#endif
4846 dynamic_cast<Tube *>(objects.at(ObjID))->pruneTubeNodes(node_index);
4847}
4848
4849void Context::setTubeNodes(uint ObjID, const std::vector<helios::vec3> &node_xyz) {
4850#ifdef HELIOS_DEBUG
4851 if (objects.find(ObjID) == objects.end()) {
4852 helios_runtime_error("ERROR (Context::setTubeNodes): ObjectID of " + std::to_string(ObjID) + " does not exist in the Context.");
4853 }
4854#endif
4855 dynamic_cast<Tube *>(objects.at(ObjID))->setTubeNodes(node_xyz);
4856}
4857
4859 return getBoxObjectPointer_private(ObjID)->getCenter();
4860}
4861
4863 return getBoxObjectPointer_private(ObjID)->getSize();
4864}
4865
4867 return getBoxObjectPointer_private(ObjID)->getSubdivisionCount();
4868}
4869
4871 return getBoxObjectPointer_private(ObjID)->getVolume();
4872}
4873
4875 return getDiskObjectPointer_private(ObjID)->getCenter();
4876}
4877
4879 return getDiskObjectPointer_private(ObjID)->getSize();
4880}
4881
4883 return getDiskObjectPointer_private(ObjID)->getSubdivisionCount().x;
4884}
4885
4887 return getConeObjectPointer_private(ObjID)->getSubdivisionCount();
4888}
4889
4890std::vector<helios::vec3> Context::getConeObjectNodes(uint ObjID) const {
4891 return getConeObjectPointer_private(ObjID)->getNodeCoordinates();
4892}
4893
4894std::vector<float> Context::getConeObjectNodeRadii(uint ObjID) const {
4895 return getConeObjectPointer_private(ObjID)->getNodeRadii();
4896}
4897
4899 return getConeObjectPointer_private(ObjID)->getNodeCoordinate(number);
4900}
4901
4902float Context::getConeObjectNodeRadius(uint ObjID, int number) const {
4903 return getConeObjectPointer_private(ObjID)->getNodeRadius(number);
4904}
4905
4907 return getConeObjectPointer_private(ObjID)->getAxisUnitVector();
4908}
4909
4911 return getConeObjectPointer_private(ObjID)->getLength();
4912}
4913
4915 return getConeObjectPointer_private(ObjID)->getVolume();
4916}
4917
4918void Context::scaleConeObjectLength(uint ObjID, float scale_factor) {
4919 getConeObjectPointer_private(ObjID)->scaleLength(scale_factor);
4920}
4921
4922void Context::scaleConeObjectGirth(uint ObjID, float scale_factor) {
4923 getConeObjectPointer_private(ObjID)->scaleGirth(scale_factor);
4924}
4925
4927 return getPolymeshObjectPointer_private(ObjID)->getVolume();
4928}
4929
4931 api_warnings.report(std::cerr);
4932}