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