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