• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 21875369661

10 Feb 2026 05:26PM UTC coverage: 81.755% (-0.2%) from 81.905%
21875369661

Pull #3789

github

web-flow
Merge daf88bc9b into 3f20a5e22
Pull Request #3789: SolidRayTracePlot CAPI

17501 of 24590 branches covered (71.17%)

Branch coverage included in aggregate %.

469 of 624 new or added lines in 2 files covered. (75.16%)

1 existing line in 1 file now uncovered.

56684 of 66151 relevant lines covered (85.69%)

46447878.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.88
/src/plot.cpp
1
#include "openmc/plot.h"
2

3
#include <algorithm>
4
#define _USE_MATH_DEFINES // to make M_PI declared in Intel and MSVC compilers
5
#include <cmath>
6
#include <cstdio>
7
#include <fstream>
8
#include <sstream>
9

10
#include "xtensor/xmanipulation.hpp"
11
#include "xtensor/xview.hpp"
12
#include <fmt/core.h>
13
#include <fmt/ostream.h>
14
#ifdef USE_LIBPNG
15
#include <png.h>
16
#endif
17

18
#include "openmc/cell.h"
19
#include "openmc/constants.h"
20
#include "openmc/container_util.h"
21
#include "openmc/dagmc.h"
22
#include "openmc/error.h"
23
#include "openmc/file_utils.h"
24
#include "openmc/geometry.h"
25
#include "openmc/hdf5_interface.h"
26
#include "openmc/material.h"
27
#include "openmc/mesh.h"
28
#include "openmc/message_passing.h"
29
#include "openmc/openmp_interface.h"
30
#include "openmc/output.h"
31
#include "openmc/particle.h"
32
#include "openmc/progress_bar.h"
33
#include "openmc/random_lcg.h"
34
#include "openmc/settings.h"
35
#include "openmc/simulation.h"
36
#include "openmc/string_utils.h"
37

38
namespace openmc {
39

40
//==============================================================================
41
// Constants
42
//==============================================================================
43

44
constexpr int PLOT_LEVEL_LOWEST {-1}; //!< lower bound on plot universe level
45
constexpr int32_t NOT_FOUND {-2};
46
constexpr int32_t OVERLAP {-3};
47

48
IdData::IdData(size_t h_res, size_t v_res) : data_({v_res, h_res, 3}, NOT_FOUND)
5,140✔
49
{}
5,140✔
50

51
void IdData::set_value(size_t y, size_t x, const GeometryState& p, int level)
38,716,247✔
52
{
53
  // set cell data
54
  if (p.n_coord() <= level) {
38,716,247!
55
    data_(y, x, 0) = NOT_FOUND;
×
56
    data_(y, x, 1) = NOT_FOUND;
×
57
  } else {
58
    data_(y, x, 0) = model::cells.at(p.coord(level).cell())->id_;
38,716,247✔
59
    data_(y, x, 1) = level == p.n_coord() - 1
77,432,494✔
60
                       ? p.cell_instance()
38,716,247!
61
                       : cell_instance_at_level(p, level);
×
62
  }
63

64
  // set material data
65
  Cell* c = model::cells.at(p.lowest_coord().cell()).get();
38,716,247✔
66
  if (p.material() == MATERIAL_VOID) {
38,716,247✔
67
    data_(y, x, 2) = MATERIAL_VOID;
29,804,276✔
68
    return;
29,804,276✔
69
  } else if (c->type_ == Fill::MATERIAL) {
8,911,971!
70
    Material* m = model::materials.at(p.material()).get();
8,911,971✔
71
    data_(y, x, 2) = m->id_;
8,911,971✔
72
  }
73
}
74

75
void IdData::set_overlap(size_t y, size_t x)
374,308✔
76
{
77
  xt::view(data_, y, x, xt::all()) = OVERLAP;
374,308✔
78
}
374,308✔
79

80
PropertyData::PropertyData(size_t h_res, size_t v_res)
11✔
81
  : data_({v_res, h_res, 2}, NOT_FOUND)
11✔
82
{}
11✔
83

84
void PropertyData::set_value(
99✔
85
  size_t y, size_t x, const GeometryState& p, int level)
86
{
87
  Cell* c = model::cells.at(p.lowest_coord().cell()).get();
99✔
88
  data_(y, x, 0) = (p.sqrtkT() * p.sqrtkT()) / K_BOLTZMANN;
99✔
89
  if (c->type_ != Fill::UNIVERSE && p.material() != MATERIAL_VOID) {
99!
90
    Material* m = model::materials.at(p.material()).get();
99✔
91
    data_(y, x, 1) = m->density_gpcc_;
99✔
92
  }
93
}
99✔
94

95
void PropertyData::set_overlap(size_t y, size_t x)
×
96
{
97
  data_(y, x) = OVERLAP;
×
98
}
×
99

100
//==============================================================================
101
// Global variables
102
//==============================================================================
103

104
namespace model {
105

106
std::unordered_map<int, int> plot_map;
107
vector<std::unique_ptr<PlottableInterface>> plots;
108
uint64_t plotter_seed = 1;
109

110
} // namespace model
111

112
//==============================================================================
113
// RUN_PLOT controls the logic for making one or many plots
114
//==============================================================================
115

116
extern "C" int openmc_plot_geometry()
121✔
117
{
118

119
  for (auto& pl : model::plots) {
407✔
120
    write_message(5, "Processing plot {}: {}...", pl->id(), pl->path_plot());
286✔
121
    pl->create_output();
286✔
122
  }
123

124
  return 0;
121✔
125
}
126

127
void PlottableInterface::write_image(const ImageData& data) const
231✔
128
{
129
#ifdef USE_LIBPNG
130
  output_png(path_plot(), data);
231✔
131
#else
132
  output_ppm(path_plot(), data);
133
#endif
134
}
231✔
135

136
void Plot::create_output() const
198✔
137
{
138
  if (PlotType::slice == type_) {
198✔
139
    // create 2D image
140
    ImageData image = create_image();
143✔
141
    write_image(image);
143✔
142
  } else if (PlotType::voxel == type_) {
198!
143
    // create voxel file for 3D viewing
144
    create_voxel();
55✔
145
  }
146
}
198✔
147

148
void Plot::print_info() const
154✔
149
{
150
  // Plot type
151
  if (PlotType::slice == type_) {
154✔
152
    fmt::print("Plot Type: Slice\n");
121✔
153
  } else if (PlotType::voxel == type_) {
33!
154
    fmt::print("Plot Type: Voxel\n");
33✔
155
  }
156

157
  // Plot parameters
158
  fmt::print("Origin: {} {} {}\n", origin_[0], origin_[1], origin_[2]);
280✔
159

160
  if (PlotType::slice == type_) {
154✔
161
    fmt::print("Width: {:4} {:4}\n", width_[0], width_[1]);
242✔
162
  } else if (PlotType::voxel == type_) {
33!
163
    fmt::print("Width: {:4} {:4} {:4}\n", width_[0], width_[1], width_[2]);
66✔
164
  }
165

166
  if (PlotColorBy::cells == color_by_) {
154✔
167
    fmt::print("Coloring: Cells\n");
88✔
168
  } else if (PlotColorBy::mats == color_by_) {
66!
169
    fmt::print("Coloring: Materials\n");
66✔
170
  }
171

172
  if (PlotType::slice == type_) {
154✔
173
    switch (basis_) {
121!
174
    case PlotBasis::xy:
77✔
175
      fmt::print("Basis: XY\n");
63✔
176
      break;
77✔
177
    case PlotBasis::xz:
22✔
178
      fmt::print("Basis: XZ\n");
18✔
179
      break;
22✔
180
    case PlotBasis::yz:
22✔
181
      fmt::print("Basis: YZ\n");
18✔
182
      break;
22✔
183
    }
184
    fmt::print("Pixels: {} {}\n", pixels()[0], pixels()[1]);
242✔
185
  } else if (PlotType::voxel == type_) {
33!
186
    fmt::print("Voxels: {} {} {}\n", pixels()[0], pixels()[1], pixels()[2]);
66✔
187
  }
188
}
154✔
189

190
void read_plots_xml()
1,382✔
191
{
192
  // Check if plots.xml exists; this is only necessary when the plot runmode is
193
  // initiated. Otherwise, we want to read plots.xml because it may be called
194
  // later via the API. In that case, its ok for a plots.xml to not exist
195
  std::string filename = settings::path_input + "plots.xml";
1,382✔
196
  if (!file_exists(filename) && settings::run_mode == RunMode::PLOTTING) {
1,382!
197
    fatal_error(fmt::format("Plots XML file '{}' does not exist!", filename));
×
198
  }
199

200
  write_message("Reading plot XML file...", 5);
1,382✔
201

202
  // Parse plots.xml file
203
  pugi::xml_document doc;
1,382✔
204
  doc.load_file(filename.c_str());
1,382✔
205

206
  pugi::xml_node root = doc.document_element();
1,382✔
207

208
  read_plots_xml(root);
1,382✔
209
}
1,382✔
210

211
void read_plots_xml(pugi::xml_node root)
1,726✔
212
{
213
  for (auto node : root.children("plot")) {
2,559✔
214
    int id {C_NONE};
842✔
215
    if (check_for_node(node, "id")) {
842!
216
      id = std::stoi(get_node_value(node, "id", true));
842✔
217
    }
218
    std::string plot_desc = id == C_NONE ? "<auto>" : std::to_string(id);
842!
219

220
    if (check_for_node(node, "type")) {
842!
221
      std::string type_str = get_node_value(node, "type", true);
842✔
222
      if (type_str == "slice") {
842✔
223
        model::plots.emplace_back(
690✔
224
          std::make_unique<Plot>(node, Plot::PlotType::slice));
1,389✔
225
      } else if (type_str == "voxel") {
143✔
226
        model::plots.emplace_back(
55✔
227
          std::make_unique<Plot>(node, Plot::PlotType::voxel));
110✔
228
      } else if (type_str == "wireframe_raytrace") {
88✔
229
        model::plots.emplace_back(
55✔
230
          std::make_unique<WireframeRayTracePlot>(node));
110✔
231
      } else if (type_str == "solid_raytrace") {
33!
232
        model::plots.emplace_back(std::make_unique<SolidRayTracePlot>(node));
33✔
233
      } else {
NEW
234
        fatal_error(fmt::format(
×
235
          "Unsupported plot type '{}' in plot {}", type_str, plot_desc));
236
      }
237
      model::plot_map[model::plots.back()->id()] = model::plots.size() - 1;
833✔
238
    } else {
833✔
NEW
239
      fatal_error(fmt::format("Must specify plot type in plot {}", plot_desc));
×
240
    }
241
  }
833✔
242
}
1,717✔
243

244
void free_memory_plot()
8,382✔
245
{
246
  model::plots.clear();
8,382✔
247
  model::plot_map.clear();
8,382✔
248
}
8,382✔
249

250
// creates an image based on user input from a plots.xml <plot>
251
// specification in the PNG/PPM format
252
ImageData Plot::create_image() const
143✔
253
{
254
  size_t width = pixels()[0];
143✔
255
  size_t height = pixels()[1];
143✔
256

257
  ImageData data({width, height}, not_found_);
143✔
258

259
  // generate ids for the plot
260
  auto ids = get_map<IdData>();
143✔
261

262
  // assign colors
263
  for (size_t y = 0; y < height; y++) {
30,063✔
264
    for (size_t x = 0; x < width; x++) {
7,622,120✔
265
      int idx = color_by_ == PlotColorBy::cells ? 0 : 2;
7,592,200✔
266
      auto id = ids.data_(y, x, idx);
7,592,200✔
267
      // no setting needed if not found
268
      if (id == NOT_FOUND) {
7,592,200✔
269
        continue;
1,082,532✔
270
      }
271
      if (id == OVERLAP) {
6,537,916✔
272
        data(x, y) = overlap_color_;
28,248✔
273
        continue;
28,248✔
274
      }
275
      if (PlotColorBy::cells == color_by_) {
6,509,668✔
276
        data(x, y) = colors_[model::cell_map[id]];
3,011,668✔
277
      } else if (PlotColorBy::mats == color_by_) {
3,498,000!
278
        if (id == MATERIAL_VOID) {
3,498,000!
279
          data(x, y) = WHITE;
×
280
          continue;
×
281
        }
282
        data(x, y) = colors_[model::material_map[id]];
3,498,000✔
283
      } // color_by if-else
284
    }
285
  }
286

287
  // draw mesh lines if present
288
  if (index_meshlines_mesh_ >= 0) {
143✔
289
    draw_mesh_lines(data);
33✔
290
  }
291

292
  return data;
286✔
293
}
143✔
294

295
void PlottableInterface::set_id(pugi::xml_node plot_node)
842✔
296
{
297
  int id {C_NONE};
842✔
298
  if (check_for_node(plot_node, "id")) {
842!
299
    id = std::stoi(get_node_value(plot_node, "id"));
842✔
300
  }
301

302
  try {
303
    set_id(id);
842✔
NEW
304
  } catch (const std::runtime_error& e) {
×
NEW
305
    fatal_error(e.what());
×
UNCOV
306
  }
×
307
}
842✔
308

309
void PlottableInterface::set_id(int id)
853✔
310
{
311
  if (id < 0 && id != C_NONE) {
853!
NEW
312
    throw std::runtime_error {fmt::format("Invalid plot ID: {}", id)};
×
313
  }
314

315
  if (id == C_NONE) {
853✔
316
    id = 0;
11✔
317
    for (const auto& p : model::plots) {
22✔
318
      id = std::max(id, p->id());
11✔
319
    }
320
    ++id;
11✔
321
  }
322

323
  if (id_ == id)
853!
NEW
324
    return;
×
325

326
  // Check to make sure this ID doesn't already exist
327
  if (model::plot_map.find(id) != model::plot_map.end()) {
853!
NEW
328
    throw std::runtime_error {
×
NEW
329
      fmt::format("Two or more plots use the same unique ID: {}", id)};
×
330
  }
331

332
  id_ = id;
853✔
333
}
334

335
// Checks if png or ppm is already present
336
bool file_extension_present(
833✔
337
  const std::string& filename, const std::string& extension)
338
{
339
  std::string file_extension_if_present =
340
    filename.substr(filename.find_last_of(".") + 1);
833✔
341
  if (file_extension_if_present == extension)
833✔
342
    return true;
55✔
343
  return false;
778✔
344
}
833✔
345

346
void Plot::set_output_path(pugi::xml_node plot_node)
754✔
347
{
348
  // Set output file path
349
  std::string filename;
754✔
350

351
  if (check_for_node(plot_node, "filename")) {
754✔
352
    filename = get_node_value(plot_node, "filename");
248✔
353
  } else {
354
    filename = fmt::format("plot_{}", id());
1,012✔
355
  }
356
  const std::string dir_if_present =
357
    filename.substr(0, filename.find_last_of("/") + 1);
754✔
358
  if (dir_if_present.size() > 0 && !dir_exists(dir_if_present)) {
754✔
359
    fatal_error(fmt::format("Directory '{}' does not exist!", dir_if_present));
9✔
360
  }
361
  // add appropriate file extension to name
362
  switch (type_) {
745!
363
  case PlotType::slice:
690✔
364
#ifdef USE_LIBPNG
365
    if (!file_extension_present(filename, "png"))
690!
366
      filename.append(".png");
690✔
367
#else
368
    if (!file_extension_present(filename, "ppm"))
369
      filename.append(".ppm");
370
#endif
371
    break;
690✔
372
  case PlotType::voxel:
55✔
373
    if (!file_extension_present(filename, "h5"))
55!
374
      filename.append(".h5");
55✔
375
    break;
55✔
376
  }
377

378
  path_plot_ = filename;
745✔
379

380
  // Copy plot pixel size
381
  vector<int> pxls = get_node_array<int>(plot_node, "pixels");
1,490✔
382
  if (PlotType::slice == type_) {
745✔
383
    if (pxls.size() == 2) {
690!
384
      pixels()[0] = pxls[0];
690✔
385
      pixels()[1] = pxls[1];
690✔
386
    } else {
387
      fatal_error(
×
388
        fmt::format("<pixels> must be length 2 in slice plot {}", id()));
×
389
    }
390
  } else if (PlotType::voxel == type_) {
55!
391
    if (pxls.size() == 3) {
55!
392
      pixels()[0] = pxls[0];
55✔
393
      pixels()[1] = pxls[1];
55✔
394
      pixels()[2] = pxls[2];
55✔
395
    } else {
396
      fatal_error(
×
397
        fmt::format("<pixels> must be length 3 in voxel plot {}", id()));
×
398
    }
399
  }
400
}
745✔
401

402
void PlottableInterface::set_bg_color(pugi::xml_node plot_node)
842✔
403
{
404
  // Copy plot background color
405
  if (check_for_node(plot_node, "background")) {
842✔
406
    vector<int> bg_rgb = get_node_array<int>(plot_node, "background");
44✔
407
    if (bg_rgb.size() == 3) {
44!
408
      not_found_ = bg_rgb;
44✔
409
    } else {
410
      fatal_error(fmt::format("Bad background RGB in plot {}", id()));
×
411
    }
412
  }
44✔
413
}
842✔
414

415
void Plot::set_basis(pugi::xml_node plot_node)
745✔
416
{
417
  // Copy plot basis
418
  if (PlotType::slice == type_) {
745✔
419
    std::string pl_basis = "xy";
690✔
420
    if (check_for_node(plot_node, "basis")) {
690!
421
      pl_basis = get_node_value(plot_node, "basis", true);
690✔
422
    }
423
    if ("xy" == pl_basis) {
690✔
424
      basis_ = PlotBasis::xy;
614✔
425
    } else if ("xz" == pl_basis) {
76✔
426
      basis_ = PlotBasis::xz;
22✔
427
    } else if ("yz" == pl_basis) {
54!
428
      basis_ = PlotBasis::yz;
54✔
429
    } else {
430
      fatal_error(
×
431
        fmt::format("Unsupported plot basis '{}' in plot {}", pl_basis, id()));
×
432
    }
433
  }
690✔
434
}
745✔
435

436
void Plot::set_origin(pugi::xml_node plot_node)
745✔
437
{
438
  // Copy plotting origin
439
  auto pl_origin = get_node_array<double>(plot_node, "origin");
745✔
440
  if (pl_origin.size() == 3) {
745!
441
    origin_ = pl_origin;
745✔
442
  } else {
443
    fatal_error(fmt::format("Origin must be length 3 in plot {}", id()));
×
444
  }
445
}
745✔
446

447
void Plot::set_width(pugi::xml_node plot_node)
745✔
448
{
449
  // Copy plotting width
450
  vector<double> pl_width = get_node_array<double>(plot_node, "width");
745✔
451
  if (PlotType::slice == type_) {
745✔
452
    if (pl_width.size() == 2) {
690!
453
      width_.x = pl_width[0];
690✔
454
      width_.y = pl_width[1];
690✔
455
    } else {
456
      fatal_error(
×
457
        fmt::format("<width> must be length 2 in slice plot {}", id()));
×
458
    }
459
  } else if (PlotType::voxel == type_) {
55!
460
    if (pl_width.size() == 3) {
55!
461
      pl_width = get_node_array<double>(plot_node, "width");
55✔
462
      width_ = pl_width;
55✔
463
    } else {
464
      fatal_error(
×
465
        fmt::format("<width> must be length 3 in voxel plot {}", id()));
×
466
    }
467
  }
468
}
745✔
469

470
void PlottableInterface::set_universe(pugi::xml_node plot_node)
842✔
471
{
472
  // Copy plot universe level
473
  if (check_for_node(plot_node, "level")) {
842!
474
    level_ = std::stoi(get_node_value(plot_node, "level"));
×
475
    if (level_ < 0) {
×
476
      fatal_error(fmt::format("Bad universe level in plot {}", id()));
×
477
    }
478
  } else {
479
    level_ = PLOT_LEVEL_LOWEST;
842✔
480
  }
481
}
842✔
482

483
void PlottableInterface::set_color_by(pugi::xml_node plot_node)
842✔
484
{
485
  // Copy plot color type
486
  std::string pl_color_by = "cell";
842✔
487
  if (check_for_node(plot_node, "color_by")) {
842✔
488
    pl_color_by = get_node_value(plot_node, "color_by", true);
809✔
489
  }
490
  if ("cell" == pl_color_by) {
842✔
491
    color_by_ = PlotColorBy::cells;
290✔
492
  } else if ("material" == pl_color_by) {
552!
493
    color_by_ = PlotColorBy::mats;
552✔
494
  } else {
495
    fatal_error(fmt::format(
×
496
      "Unsupported plot color type '{}' in plot {}", pl_color_by, id()));
×
497
  }
498
}
842✔
499

500
void PlottableInterface::set_default_colors()
853✔
501
{
502
  // Copy plot color type and initialize all colors randomly
503
  if (PlotColorBy::cells == color_by_) {
853✔
504
    colors_.resize(model::cells.size());
290✔
505
  } else if (PlotColorBy::mats == color_by_) {
563!
506
    colors_.resize(model::materials.size());
563✔
507
  }
508

509
  for (auto& c : colors_) {
3,910✔
510
    c = random_color();
3,057✔
511
    // make sure we don't interfere with some default colors
512
    while (c == RED || c == WHITE) {
3,057!
513
      c = random_color();
×
514
    }
515
  }
516
}
853✔
517

518
void PlottableInterface::set_user_colors(pugi::xml_node plot_node)
842✔
519
{
520
  for (auto cn : plot_node.children("color")) {
1,029✔
521
    // Make sure 3 values are specified for RGB
522
    vector<int> user_rgb = get_node_array<int>(cn, "rgb");
187✔
523
    if (user_rgb.size() != 3) {
187!
524
      fatal_error(fmt::format("Bad RGB in plot {}", id()));
×
525
    }
526
    // Ensure that there is an id for this color specification
527
    int col_id;
528
    if (check_for_node(cn, "id")) {
187!
529
      col_id = std::stoi(get_node_value(cn, "id"));
187✔
530
    } else {
531
      fatal_error(fmt::format(
×
532
        "Must specify id for color specification in plot {}", id()));
×
533
    }
534
    // Add RGB
535
    if (PlotColorBy::cells == color_by_) {
187✔
536
      if (model::cell_map.find(col_id) != model::cell_map.end()) {
88!
537
        col_id = model::cell_map[col_id];
88✔
538
        colors_[col_id] = user_rgb;
88✔
539
      } else {
540
        warning(fmt::format(
×
541
          "Could not find cell {} specified in plot {}", col_id, id()));
×
542
      }
543
    } else if (PlotColorBy::mats == color_by_) {
99!
544
      if (model::material_map.find(col_id) != model::material_map.end()) {
99!
545
        col_id = model::material_map[col_id];
99✔
546
        colors_[col_id] = user_rgb;
99✔
547
      } else {
548
        warning(fmt::format(
×
549
          "Could not find material {} specified in plot {}", col_id, id()));
×
550
      }
551
    }
552
  } // color node loop
187✔
553
}
842✔
554

555
void Plot::set_meshlines(pugi::xml_node plot_node)
745✔
556
{
557
  // Deal with meshlines
558
  pugi::xpath_node_set mesh_line_nodes = plot_node.select_nodes("meshlines");
745✔
559

560
  if (!mesh_line_nodes.empty()) {
745✔
561
    if (PlotType::voxel == type_) {
33!
562
      warning(fmt::format("Meshlines ignored in voxel plot {}", id()));
×
563
    }
564

565
    if (mesh_line_nodes.size() == 1) {
33!
566
      // Get first meshline node
567
      pugi::xml_node meshlines_node = mesh_line_nodes[0].node();
33✔
568

569
      // Check mesh type
570
      std::string meshtype;
33✔
571
      if (check_for_node(meshlines_node, "meshtype")) {
33!
572
        meshtype = get_node_value(meshlines_node, "meshtype");
33✔
573
      } else {
574
        fatal_error(fmt::format(
×
575
          "Must specify a meshtype for meshlines specification in plot {}",
576
          id()));
×
577
      }
578

579
      // Ensure that there is a linewidth for this meshlines specification
580
      std::string meshline_width;
33✔
581
      if (check_for_node(meshlines_node, "linewidth")) {
33!
582
        meshline_width = get_node_value(meshlines_node, "linewidth");
33✔
583
        meshlines_width_ = std::stoi(meshline_width);
33✔
584
      } else {
585
        fatal_error(fmt::format(
×
586
          "Must specify a linewidth for meshlines specification in plot {}",
587
          id()));
×
588
      }
589

590
      // Check for color
591
      if (check_for_node(meshlines_node, "color")) {
33!
592
        // Check and make sure 3 values are specified for RGB
593
        vector<int> ml_rgb = get_node_array<int>(meshlines_node, "color");
×
594
        if (ml_rgb.size() != 3) {
×
595
          fatal_error(
×
596
            fmt::format("Bad RGB for meshlines color in plot {}", id()));
×
597
        }
598
        meshlines_color_ = ml_rgb;
×
599
      }
×
600

601
      // Set mesh based on type
602
      if ("ufs" == meshtype) {
33!
603
        if (!simulation::ufs_mesh) {
×
604
          fatal_error(
×
605
            fmt::format("No UFS mesh for meshlines on plot {}", id()));
×
606
        } else {
607
          for (int i = 0; i < model::meshes.size(); ++i) {
×
608
            if (const auto* m =
×
609
                  dynamic_cast<const RegularMesh*>(model::meshes[i].get())) {
×
610
              if (m == simulation::ufs_mesh) {
×
611
                index_meshlines_mesh_ = i;
×
612
              }
613
            }
614
          }
615
          if (index_meshlines_mesh_ == -1)
×
616
            fatal_error("Could not find the UFS mesh for meshlines plot");
×
617
        }
618
      } else if ("entropy" == meshtype) {
33✔
619
        if (!simulation::entropy_mesh) {
22!
620
          fatal_error(
×
621
            fmt::format("No entropy mesh for meshlines on plot {}", id()));
×
622
        } else {
623
          for (int i = 0; i < model::meshes.size(); ++i) {
55✔
624
            if (const auto* m =
33✔
625
                  dynamic_cast<const RegularMesh*>(model::meshes[i].get())) {
33!
626
              if (m == simulation::entropy_mesh) {
22!
627
                index_meshlines_mesh_ = i;
22✔
628
              }
629
            }
630
          }
631
          if (index_meshlines_mesh_ == -1)
22!
632
            fatal_error("Could not find the entropy mesh for meshlines plot");
×
633
        }
634
      } else if ("tally" == meshtype) {
11!
635
        // Ensure that there is a mesh id if the type is tally
636
        int tally_mesh_id;
637
        if (check_for_node(meshlines_node, "id")) {
11!
638
          tally_mesh_id = std::stoi(get_node_value(meshlines_node, "id"));
11✔
639
        } else {
640
          std::stringstream err_msg;
×
641
          fatal_error(fmt::format("Must specify a mesh id for meshlines tally "
×
642
                                  "mesh specification in plot {}",
643
            id()));
×
644
        }
×
645
        // find the tally index
646
        int idx;
647
        int err = openmc_get_mesh_index(tally_mesh_id, &idx);
11✔
648
        if (err != 0) {
11!
649
          fatal_error(fmt::format("Could not find mesh {} specified in "
×
650
                                  "meshlines for plot {}",
651
            tally_mesh_id, id()));
×
652
        }
653
        index_meshlines_mesh_ = idx;
11✔
654
      } else {
655
        fatal_error(fmt::format("Invalid type for meshlines on plot {}", id()));
×
656
      }
657
    } else {
33✔
658
      fatal_error(fmt::format("Mutliple meshlines specified in plot {}", id()));
×
659
    }
660
  }
661
}
745✔
662

663
void PlottableInterface::set_mask(pugi::xml_node plot_node)
842✔
664
{
665
  // Deal with masks
666
  pugi::xpath_node_set mask_nodes = plot_node.select_nodes("mask");
842✔
667

668
  if (!mask_nodes.empty()) {
842✔
669
    if (mask_nodes.size() == 1) {
33!
670
      // Get pointer to mask
671
      pugi::xml_node mask_node = mask_nodes[0].node();
33✔
672

673
      // Determine how many components there are and allocate
674
      vector<int> iarray = get_node_array<int>(mask_node, "components");
33✔
675
      if (iarray.size() == 0) {
33!
676
        fatal_error(
×
677
          fmt::format("Missing <components> in mask of plot {}", id()));
×
678
      }
679

680
      // First we need to change the user-specified identifiers to indices
681
      // in the cell and material arrays
682
      for (auto& col_id : iarray) {
99✔
683
        if (PlotColorBy::cells == color_by_) {
66!
684
          if (model::cell_map.find(col_id) != model::cell_map.end()) {
66!
685
            col_id = model::cell_map[col_id];
66✔
686
          } else {
687
            fatal_error(fmt::format("Could not find cell {} specified in the "
×
688
                                    "mask in plot {}",
689
              col_id, id()));
×
690
          }
691
        } else if (PlotColorBy::mats == color_by_) {
×
692
          if (model::material_map.find(col_id) != model::material_map.end()) {
×
693
            col_id = model::material_map[col_id];
×
694
          } else {
695
            fatal_error(fmt::format("Could not find material {} specified in "
×
696
                                    "the mask in plot {}",
697
              col_id, id()));
×
698
          }
699
        }
700
      }
701

702
      // Alter colors based on mask information
703
      for (int j = 0; j < colors_.size(); j++) {
132✔
704
        if (contains(iarray, j)) {
99✔
705
          if (check_for_node(mask_node, "background")) {
66!
706
            vector<int> bg_rgb = get_node_array<int>(mask_node, "background");
66✔
707
            colors_[j] = bg_rgb;
66✔
708
          } else {
66✔
709
            colors_[j] = WHITE;
×
710
          }
711
        }
712
      }
713

714
    } else {
33✔
715
      fatal_error(fmt::format("Mutliple masks specified in plot {}", id()));
×
716
    }
717
  }
718
}
842✔
719

720
void PlottableInterface::set_overlap_color(pugi::xml_node plot_node)
842✔
721
{
722
  color_overlaps_ = false;
842✔
723
  if (check_for_node(plot_node, "show_overlaps")) {
842✔
724
    color_overlaps_ = get_node_value_bool(plot_node, "show_overlaps");
22✔
725
    // check for custom overlap color
726
    if (check_for_node(plot_node, "overlap_color")) {
22✔
727
      if (!color_overlaps_) {
11!
728
        warning(fmt::format(
×
729
          "Overlap color specified in plot {} but overlaps won't be shown.",
730
          id()));
×
731
      }
732
      vector<int> olap_clr = get_node_array<int>(plot_node, "overlap_color");
11✔
733
      if (olap_clr.size() == 3) {
11!
734
        overlap_color_ = olap_clr;
11✔
735
      } else {
736
        fatal_error(fmt::format("Bad overlap RGB in plot {}", id()));
×
737
      }
738
    }
11✔
739
  }
740

741
  // make sure we allocate the vector for counting overlap checks if
742
  // they're going to be plotted
743
  if (color_overlaps_ && settings::run_mode == RunMode::PLOTTING) {
842!
744
    settings::check_overlaps = true;
22✔
745
    model::overlap_check_count.resize(model::cells.size(), 0);
22✔
746
  }
747
}
842✔
748

749
PlottableInterface::PlottableInterface(pugi::xml_node plot_node)
842✔
750
{
751
  set_id(plot_node);
842✔
752
  set_bg_color(plot_node);
842✔
753
  set_universe(plot_node);
842✔
754
  set_color_by(plot_node);
842✔
755
  set_default_colors();
842✔
756
  set_user_colors(plot_node);
842✔
757
  set_mask(plot_node);
842✔
758
  set_overlap_color(plot_node);
842✔
759
}
842✔
760

761
Plot::Plot(pugi::xml_node plot_node, PlotType type)
754✔
762
  : PlottableInterface(plot_node), type_(type), index_meshlines_mesh_ {-1}
754✔
763
{
764
  set_output_path(plot_node);
754✔
765
  set_basis(plot_node);
745✔
766
  set_origin(plot_node);
745✔
767
  set_width(plot_node);
745✔
768
  set_meshlines(plot_node);
745✔
769
  slice_level_ = level_; // Copy level employed in SlicePlotBase::get_map
745✔
770
  slice_color_overlaps_ = color_overlaps_;
745✔
771
}
745✔
772

773
//==============================================================================
774
// OUTPUT_PPM writes out a previously generated image to a PPM file
775
//==============================================================================
776

777
void output_ppm(const std::string& filename, const ImageData& data)
×
778
{
779
  // Open PPM file for writing
780
  std::string fname = filename;
×
781
  fname = strtrim(fname);
×
782
  std::ofstream of;
×
783

784
  of.open(fname);
×
785

786
  // Write header
787
  of << "P6\n";
×
788
  of << data.shape()[0] << " " << data.shape()[1] << "\n";
×
789
  of << "255\n";
×
790
  of.close();
×
791

792
  of.open(fname, std::ios::binary | std::ios::app);
×
793
  // Write color for each pixel
794
  for (int y = 0; y < data.shape()[1]; y++) {
×
795
    for (int x = 0; x < data.shape()[0]; x++) {
×
796
      RGBColor rgb = data(x, y);
×
797
      of << rgb.red << rgb.green << rgb.blue;
×
798
    }
799
  }
800
  of << "\n";
×
801
}
×
802

803
//==============================================================================
804
// OUTPUT_PNG writes out a previously generated image to a PNG file
805
//==============================================================================
806

807
#ifdef USE_LIBPNG
808
void output_png(const std::string& filename, const ImageData& data)
231✔
809
{
810
  // Open PNG file for writing
811
  std::string fname = filename;
231✔
812
  fname = strtrim(fname);
231✔
813
  auto fp = std::fopen(fname.c_str(), "wb");
231✔
814

815
  // Initialize write and info structures
816
  auto png_ptr =
817
    png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
231✔
818
  auto info_ptr = png_create_info_struct(png_ptr);
231✔
819

820
  // Setup exception handling
821
  if (setjmp(png_jmpbuf(png_ptr)))
231!
822
    fatal_error("Error during png creation");
×
823

824
  png_init_io(png_ptr, fp);
231✔
825

826
  // Write header (8 bit colour depth)
827
  int width = data.shape()[0];
231✔
828
  int height = data.shape()[1];
231✔
829
  png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
231✔
830
    PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
831
  png_write_info(png_ptr, info_ptr);
231✔
832

833
  // Allocate memory for one row (3 bytes per pixel - RGB)
834
  std::vector<png_byte> row(3 * width);
231✔
835

836
  // Write color for each pixel
837
  for (int y = 0; y < height; y++) {
47,751✔
838
    for (int x = 0; x < width; x++) {
11,159,720✔
839
      RGBColor rgb = data(x, y);
11,112,200✔
840
      row[3 * x] = rgb.red;
11,112,200✔
841
      row[3 * x + 1] = rgb.green;
11,112,200✔
842
      row[3 * x + 2] = rgb.blue;
11,112,200✔
843
    }
844
    png_write_row(png_ptr, row.data());
47,520✔
845
  }
846

847
  // End write
848
  png_write_end(png_ptr, nullptr);
231✔
849

850
  // Clean up data structures
851
  std::fclose(fp);
231✔
852
  png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
231✔
853
  png_destroy_write_struct(&png_ptr, &info_ptr);
231✔
854
}
231✔
855
#endif
856

857
//==============================================================================
858
// DRAW_MESH_LINES draws mesh line boundaries on an image
859
//==============================================================================
860

861
void Plot::draw_mesh_lines(ImageData& data) const
33✔
862
{
863
  RGBColor rgb;
33✔
864
  rgb = meshlines_color_;
33✔
865

866
  int ax1, ax2;
867
  switch (basis_) {
33!
868
  case PlotBasis::xy:
22✔
869
    ax1 = 0;
22✔
870
    ax2 = 1;
22✔
871
    break;
22✔
872
  case PlotBasis::xz:
11✔
873
    ax1 = 0;
11✔
874
    ax2 = 2;
11✔
875
    break;
11✔
876
  case PlotBasis::yz:
×
877
    ax1 = 1;
×
878
    ax2 = 2;
×
879
    break;
×
880
  default:
×
881
    UNREACHABLE();
×
882
  }
883

884
  Position ll_plot {origin_};
33✔
885
  Position ur_plot {origin_};
33✔
886

887
  ll_plot[ax1] -= width_[0] / 2.;
33✔
888
  ll_plot[ax2] -= width_[1] / 2.;
33✔
889
  ur_plot[ax1] += width_[0] / 2.;
33✔
890
  ur_plot[ax2] += width_[1] / 2.;
33✔
891

892
  Position width = ur_plot - ll_plot;
33✔
893

894
  // Find the (axis-aligned) lines of the mesh that intersect this plot.
895
  auto axis_lines =
896
    model::meshes[index_meshlines_mesh_]->plot(ll_plot, ur_plot);
33✔
897

898
  // Find the bounds along the second axis (accounting for low-D meshes).
899
  int ax2_min, ax2_max;
900
  if (axis_lines.second.size() > 0) {
33!
901
    double frac = (axis_lines.second.back() - ll_plot[ax2]) / width[ax2];
33✔
902
    ax2_min = (1.0 - frac) * pixels()[1];
33✔
903
    if (ax2_min < 0)
33!
904
      ax2_min = 0;
×
905
    frac = (axis_lines.second.front() - ll_plot[ax2]) / width[ax2];
33✔
906
    ax2_max = (1.0 - frac) * pixels()[1];
33✔
907
    if (ax2_max > pixels()[1])
33!
908
      ax2_max = pixels()[1];
×
909
  } else {
910
    ax2_min = 0;
×
911
    ax2_max = pixels()[1];
×
912
  }
913

914
  // Iterate across the first axis and draw lines.
915
  for (auto ax1_val : axis_lines.first) {
187✔
916
    double frac = (ax1_val - ll_plot[ax1]) / width[ax1];
154✔
917
    int ax1_ind = frac * pixels()[0];
154✔
918
    for (int ax2_ind = ax2_min; ax2_ind < ax2_max; ++ax2_ind) {
24,948✔
919
      for (int plus = 0; plus <= meshlines_width_; plus++) {
49,588✔
920
        if (ax1_ind + plus >= 0 && ax1_ind + plus < pixels()[0])
24,794!
921
          data(ax1_ind + plus, ax2_ind) = rgb;
24,794✔
922
        if (ax1_ind - plus >= 0 && ax1_ind - plus < pixels()[0])
24,794!
923
          data(ax1_ind - plus, ax2_ind) = rgb;
24,794✔
924
      }
925
    }
926
  }
927

928
  // Find the bounds along the first axis.
929
  int ax1_min, ax1_max;
930
  if (axis_lines.first.size() > 0) {
33!
931
    double frac = (axis_lines.first.front() - ll_plot[ax1]) / width[ax1];
33✔
932
    ax1_min = frac * pixels()[0];
33✔
933
    if (ax1_min < 0)
33!
934
      ax1_min = 0;
×
935
    frac = (axis_lines.first.back() - ll_plot[ax1]) / width[ax1];
33✔
936
    ax1_max = frac * pixels()[0];
33✔
937
    if (ax1_max > pixels()[0])
33!
938
      ax1_max = pixels()[0];
×
939
  } else {
940
    ax1_min = 0;
×
941
    ax1_max = pixels()[0];
×
942
  }
943

944
  // Iterate across the second axis and draw lines.
945
  for (auto ax2_val : axis_lines.second) {
209✔
946
    double frac = (ax2_val - ll_plot[ax2]) / width[ax2];
176✔
947
    int ax2_ind = (1.0 - frac) * pixels()[1];
176✔
948
    for (int ax1_ind = ax1_min; ax1_ind < ax1_max; ++ax1_ind) {
28,336✔
949
      for (int plus = 0; plus <= meshlines_width_; plus++) {
56,320✔
950
        if (ax2_ind + plus >= 0 && ax2_ind + plus < pixels()[1])
28,160!
951
          data(ax1_ind, ax2_ind + plus) = rgb;
28,160✔
952
        if (ax2_ind - plus >= 0 && ax2_ind - plus < pixels()[1])
28,160!
953
          data(ax1_ind, ax2_ind - plus) = rgb;
28,160✔
954
      }
955
    }
956
  }
957
}
33✔
958

959
/* outputs a binary file that can be input into silomesh for 3D geometry
960
 * visualization.  It works the same way as create_image by dragging a particle
961
 * across the geometry for the specified number of voxels. The first 3 int's in
962
 * the binary are the number of x, y, and z voxels.  The next 3 double's are
963
 * the widths of the voxels in the x, y, and z directions. The next 3 double's
964
 * are the x, y, and z coordinates of the lower left point. Finally the binary
965
 * is filled with entries of four int's each. Each 'row' in the binary contains
966
 * four int's: 3 for x,y,z position and 1 for cell or material id.  For 1
967
 * million voxels this produces a file of approximately 15MB.
968
 */
969
void Plot::create_voxel() const
55✔
970
{
971
  // compute voxel widths in each direction
972
  array<double, 3> vox;
973
  vox[0] = width_[0] / static_cast<double>(pixels()[0]);
55✔
974
  vox[1] = width_[1] / static_cast<double>(pixels()[1]);
55✔
975
  vox[2] = width_[2] / static_cast<double>(pixels()[2]);
55✔
976

977
  // initial particle position
978
  Position ll = origin_ - width_ / 2.;
55✔
979

980
  // Open binary plot file for writing
981
  std::ofstream of;
55✔
982
  std::string fname = std::string(path_plot_);
55✔
983
  fname = strtrim(fname);
55✔
984
  hid_t file_id = file_open(fname, 'w');
55✔
985

986
  // write header info
987
  write_attribute(file_id, "filetype", "voxel");
55✔
988
  write_attribute(file_id, "version", VERSION_VOXEL);
55✔
989
  write_attribute(file_id, "openmc_version", VERSION);
55✔
990

991
#ifdef GIT_SHA1
992
  write_attribute(file_id, "git_sha1", GIT_SHA1);
993
#endif
994

995
  // Write current date and time
996
  write_attribute(file_id, "date_and_time", time_stamp().c_str());
55✔
997
  array<int, 3> h5_pixels;
998
  std::copy(pixels().begin(), pixels().end(), h5_pixels.begin());
55✔
999
  write_attribute(file_id, "num_voxels", h5_pixels);
55✔
1000
  write_attribute(file_id, "voxel_width", vox);
55✔
1001
  write_attribute(file_id, "lower_left", ll);
55✔
1002

1003
  // Create dataset for voxel data -- note that the dimensions are reversed
1004
  // since we want the order in the file to be z, y, x
1005
  hsize_t dims[3];
1006
  dims[0] = pixels()[2];
55✔
1007
  dims[1] = pixels()[1];
55✔
1008
  dims[2] = pixels()[0];
55✔
1009
  hid_t dspace, dset, memspace;
1010
  voxel_init(file_id, &(dims[0]), &dspace, &dset, &memspace);
55✔
1011

1012
  SlicePlotBase pltbase;
55✔
1013
  pltbase.width_ = width_;
55✔
1014
  pltbase.origin_ = origin_;
55✔
1015
  pltbase.basis_ = PlotBasis::xy;
55✔
1016
  pltbase.pixels() = pixels();
55✔
1017
  pltbase.slice_color_overlaps_ = color_overlaps_;
55✔
1018

1019
  ProgressBar pb;
55✔
1020
  for (int z = 0; z < pixels()[2]; z++) {
4,785✔
1021
    // update z coordinate
1022
    pltbase.origin_.z = ll.z + z * vox[2];
4,730✔
1023

1024
    // generate ids using plotbase
1025
    IdData ids = pltbase.get_map<IdData>();
4,730✔
1026

1027
    // select only cell/material ID data and flip the y-axis
1028
    int idx = color_by_ == PlotColorBy::cells ? 0 : 2;
4,730!
1029
    xt::xtensor<int32_t, 2> data_slice =
1030
      xt::view(ids.data_, xt::all(), xt::all(), idx);
4,730✔
1031
    xt::xtensor<int32_t, 2> data_flipped = xt::flip(data_slice, 0);
4,730✔
1032

1033
    // Write to HDF5 dataset
1034
    voxel_write_slice(z, dspace, dset, memspace, data_flipped.data());
4,730✔
1035

1036
    // update progress bar
1037
    pb.set_value(
4,730✔
1038
      100. * static_cast<double>(z + 1) / static_cast<double>((pixels()[2])));
4,730✔
1039
  }
4,730✔
1040

1041
  voxel_finalize(dspace, dset, memspace);
55✔
1042
  file_close(file_id);
55✔
1043
}
55✔
1044

1045
void voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset,
55✔
1046
  hid_t* memspace)
1047
{
1048
  // Create dataspace/dataset for voxel data
1049
  *dspace = H5Screate_simple(3, dims, nullptr);
55✔
1050
  *dset = H5Dcreate(file_id, "data", H5T_NATIVE_INT, *dspace, H5P_DEFAULT,
55✔
1051
    H5P_DEFAULT, H5P_DEFAULT);
1052

1053
  // Create dataspace for a slice of the voxel
1054
  hsize_t dims_slice[2] {dims[1], dims[2]};
55✔
1055
  *memspace = H5Screate_simple(2, dims_slice, nullptr);
55✔
1056

1057
  // Select hyperslab in dataspace
1058
  hsize_t start[3] {0, 0, 0};
55✔
1059
  hsize_t count[3] {1, dims[1], dims[2]};
55✔
1060
  H5Sselect_hyperslab(*dspace, H5S_SELECT_SET, start, nullptr, count, nullptr);
55✔
1061
}
55✔
1062

1063
void voxel_write_slice(
4,730✔
1064
  int x, hid_t dspace, hid_t dset, hid_t memspace, void* buf)
1065
{
1066
  hssize_t offset[3] {x, 0, 0};
4,730✔
1067
  H5Soffset_simple(dspace, offset);
4,730✔
1068
  H5Dwrite(dset, H5T_NATIVE_INT, memspace, dspace, H5P_DEFAULT, buf);
4,730✔
1069
}
4,730✔
1070

1071
void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace)
55✔
1072
{
1073
  H5Dclose(dset);
55✔
1074
  H5Sclose(dspace);
55✔
1075
  H5Sclose(memspace);
55✔
1076
}
55✔
1077

1078
RGBColor random_color(void)
3,057✔
1079
{
1080
  return {int(prn(&model::plotter_seed) * 255),
3,057✔
1081
    int(prn(&model::plotter_seed) * 255), int(prn(&model::plotter_seed) * 255)};
3,057✔
1082
}
1083

1084
RayTracePlot::RayTracePlot(pugi::xml_node node) : PlottableInterface(node)
88✔
1085
{
1086
  set_look_at(node);
88✔
1087
  set_camera_position(node);
88✔
1088
  set_field_of_view(node);
88✔
1089
  set_pixels(node);
88✔
1090
  set_orthographic_width(node);
88✔
1091
  set_output_path(node);
88✔
1092

1093
  if (check_for_node(node, "orthographic_width") &&
99!
1094
      check_for_node(node, "field_of_view"))
11!
1095
    fatal_error("orthographic_width and field_of_view are mutually exclusive "
×
1096
                "parameters.");
1097
}
88✔
1098

1099
void RayTracePlot::update_view()
110✔
1100
{
1101
  // Get centerline vector for camera-to-model. We create vectors around this
1102
  // that form a pixel array, and then trace rays along that.
1103
  auto up = up_ / up_.norm();
110✔
1104
  Direction looking_direction = look_at_ - camera_position_;
110✔
1105
  looking_direction /= looking_direction.norm();
110✔
1106
  if (std::abs(std::abs(looking_direction.dot(up)) - 1.0) < 1e-9)
110!
1107
    fatal_error("Up vector cannot align with vector between camera position "
×
1108
                "and look_at!");
1109
  Direction cam_yaxis = looking_direction.cross(up);
110✔
1110
  cam_yaxis /= cam_yaxis.norm();
110✔
1111
  Direction cam_zaxis = cam_yaxis.cross(looking_direction);
110✔
1112
  cam_zaxis /= cam_zaxis.norm();
110✔
1113

1114
  // Cache the camera-to-model matrix
1115
  camera_to_model_ = {looking_direction.x, cam_yaxis.x, cam_zaxis.x,
110✔
1116
    looking_direction.y, cam_yaxis.y, cam_zaxis.y, looking_direction.z,
110✔
1117
    cam_yaxis.z, cam_zaxis.z};
110✔
1118
}
110✔
1119

1120
WireframeRayTracePlot::WireframeRayTracePlot(pugi::xml_node node)
55✔
1121
  : RayTracePlot(node)
55✔
1122
{
1123
  set_opacities(node);
55✔
1124
  set_wireframe_thickness(node);
55✔
1125
  set_wireframe_ids(node);
55✔
1126
  set_wireframe_color(node);
55✔
1127
  update_view();
55✔
1128
}
55✔
1129

1130
void WireframeRayTracePlot::set_wireframe_color(pugi::xml_node plot_node)
55✔
1131
{
1132
  // Copy plot wireframe color
1133
  if (check_for_node(plot_node, "wireframe_color")) {
55!
1134
    vector<int> w_rgb = get_node_array<int>(plot_node, "wireframe_color");
×
1135
    if (w_rgb.size() == 3) {
×
1136
      wireframe_color_ = w_rgb;
×
1137
    } else {
1138
      fatal_error(fmt::format("Bad wireframe RGB in plot {}", id()));
×
1139
    }
1140
  }
×
1141
}
55✔
1142

1143
void RayTracePlot::set_output_path(pugi::xml_node node)
88✔
1144
{
1145
  // Set output file path
1146
  std::string filename;
88✔
1147

1148
  if (check_for_node(node, "filename")) {
88✔
1149
    filename = get_node_value(node, "filename");
77✔
1150
  } else {
1151
    filename = fmt::format("plot_{}", id());
22✔
1152
  }
1153

1154
#ifdef USE_LIBPNG
1155
  if (!file_extension_present(filename, "png"))
88✔
1156
    filename.append(".png");
33✔
1157
#else
1158
  if (!file_extension_present(filename, "ppm"))
1159
    filename.append(".ppm");
1160
#endif
1161
  path_plot_ = filename;
88✔
1162
}
88✔
1163

1164
bool WireframeRayTracePlot::trackstack_equivalent(
3,041,159✔
1165
  const std::vector<TrackSegment>& track1,
1166
  const std::vector<TrackSegment>& track2) const
1167
{
1168
  if (wireframe_ids_.empty()) {
3,041,159✔
1169
    // Draw wireframe for all surfaces/cells/materials
1170
    if (track1.size() != track2.size())
2,545,070✔
1171
      return false;
53,977✔
1172
    for (int i = 0; i < track1.size(); ++i) {
6,707,954✔
1173
      if (track1[i].id != track2[i].id ||
8,473,410✔
1174
          track1[i].surface_index != track2[i].surface_index) {
4,236,639✔
1175
        return false;
19,910✔
1176
      }
1177
    }
1178
    return true;
2,471,183✔
1179
  } else {
1180
    // This runs in O(nm) where n is the intersection stack size
1181
    // and m is the number of IDs we are wireframing. A simpler
1182
    // algorithm can likely be found.
1183
    for (const int id : wireframe_ids_) {
986,194✔
1184
      int t1_i = 0;
496,089✔
1185
      int t2_i = 0;
496,089✔
1186

1187
      // Advance to first instance of the ID
1188
      while (t1_i < track1.size() && t2_i < track2.size()) {
562,430✔
1189
        while (t1_i < track1.size() && track1[t1_i].id != id)
392,832✔
1190
          t1_i++;
229,053✔
1191
        while (t2_i < track2.size() && track2[t2_i].id != id)
393,668✔
1192
          t2_i++;
229,889✔
1193

1194
        // This one is really important!
1195
        if ((t1_i == track1.size() && t2_i != track2.size()) ||
396,517✔
1196
            (t1_i != track1.size() && t2_i == track2.size()))
232,738✔
1197
          return false;
5,984✔
1198
        if (t1_i == track1.size() && t2_i == track2.size())
160,061!
1199
          break;
91,454✔
1200
        // Check if surface different
1201
        if (track1[t1_i].surface_index != track2[t2_i].surface_index)
68,607✔
1202
          return false;
1,485✔
1203

1204
        // Pretty sure this should not be used:
1205
        // if (t2_i != track2.size() - 1 &&
1206
        //     t1_i != track1.size() - 1 &&
1207
        //     track1[t1_i+1].id != track2[t2_i+1].id) return false;
1208
        if (t2_i != 0 && t1_i != 0 &&
121,066!
1209
            track1[t1_i - 1].surface_index != track2[t2_i - 1].surface_index)
53,944✔
1210
          return false;
781✔
1211

1212
        // Check if neighboring cells are different
1213
        // if (track1[t1_i ? t1_i - 1 : 0].id != track2[t2_i ? t2_i - 1 : 0].id)
1214
        // return false; if (track1[t1_i < track1.size() - 1 ? t1_i + 1 : t1_i
1215
        // ].id !=
1216
        //    track2[t2_i < track2.size() - 1 ? t2_i + 1 : t2_i].id) return
1217
        //    false;
1218
        t1_i++, t2_i++;
66,341✔
1219
      }
1220
    }
1221
    return true;
490,105✔
1222
  }
1223
}
1224

1225
std::pair<Position, Direction> RayTracePlot::get_pixel_ray(
3,521,056✔
1226
  int horiz, int vert) const
1227
{
1228
  // Compute field of view in radians
1229
  constexpr double DEGREE_TO_RADIAN = M_PI / 180.0;
3,521,056✔
1230
  double horiz_fov_radians = horizontal_field_of_view_ * DEGREE_TO_RADIAN;
3,521,056✔
1231
  double p0 = static_cast<double>(pixels()[0]);
3,521,056✔
1232
  double p1 = static_cast<double>(pixels()[1]);
3,521,056✔
1233
  double vert_fov_radians = horiz_fov_radians * p1 / p0;
3,521,056✔
1234

1235
  // focal_plane_dist can be changed to alter the perspective distortion
1236
  // effect. This is in units of cm. This seems to look good most of the
1237
  // time. TODO let this variable be set through XML.
1238
  constexpr double focal_plane_dist = 10.0;
3,521,056✔
1239
  const double dx = 2.0 * focal_plane_dist * std::tan(0.5 * horiz_fov_radians);
3,521,056✔
1240
  const double dy = p1 / p0 * dx;
3,521,056✔
1241

1242
  std::pair<Position, Direction> result;
3,521,056✔
1243

1244
  // Generate the starting position/direction of the ray
1245
  if (orthographic_width_ == C_NONE) { // perspective projection
3,521,056✔
1246
    Direction camera_local_vec;
3,081,056✔
1247
    camera_local_vec.x = focal_plane_dist;
3,081,056✔
1248
    camera_local_vec.y = -0.5 * dx + horiz * dx / p0;
3,081,056✔
1249
    camera_local_vec.z = 0.5 * dy - vert * dy / p1;
3,081,056✔
1250
    camera_local_vec /= camera_local_vec.norm();
3,081,056✔
1251

1252
    result.first = camera_position_;
3,081,056✔
1253
    result.second = camera_local_vec.rotate(camera_to_model_);
3,081,056✔
1254
  } else { // orthographic projection
1255

1256
    double x_pix_coord = (static_cast<double>(horiz) - p0 / 2.0) / p0;
440,000✔
1257
    double y_pix_coord = (static_cast<double>(vert) - p1 / 2.0) / p1;
440,000✔
1258

1259
    result.first = camera_position_ +
1260
                   camera_y_axis() * x_pix_coord * orthographic_width_ +
440,000✔
1261
                   camera_z_axis() * y_pix_coord * orthographic_width_;
440,000✔
1262
    result.second = camera_x_axis();
440,000✔
1263
  }
1264

1265
  return result;
3,521,056✔
1266
}
1267

1268
ImageData WireframeRayTracePlot::create_image() const
55✔
1269
{
1270
  size_t width = pixels()[0];
55✔
1271
  size_t height = pixels()[1];
55✔
1272
  ImageData data({width, height}, not_found_);
55✔
1273

1274
  // This array marks where the initial wireframe was drawn. We convolve it with
1275
  // a filter that gets adjusted with the wireframe thickness in order to
1276
  // thicken the lines.
1277
  xt::xtensor<int, 2> wireframe_initial({width, height}, 0);
55✔
1278

1279
  /* Holds all of the track segments for the current rendered line of pixels.
1280
   * old_segments holds a copy of this_line_segments from the previous line.
1281
   * By holding both we can check if the cell/material intersection stack
1282
   * differs from the left or upper neighbor. This allows a robustly drawn
1283
   * wireframe. If only checking the left pixel (which requires substantially
1284
   * less memory), the wireframe tends to be spotty and be disconnected for
1285
   * surface edges oriented horizontally in the rendering.
1286
   *
1287
   * Note that a vector of vectors is required rather than a 2-tensor,
1288
   * since the stack size varies within each column.
1289
   */
1290
  const int n_threads = num_threads();
55✔
1291
  std::vector<std::vector<std::vector<TrackSegment>>> this_line_segments(
1292
    n_threads);
55✔
1293
  for (int t = 0; t < n_threads; ++t) {
140✔
1294
    this_line_segments[t].resize(pixels()[0]);
85✔
1295
  }
1296

1297
  // The last thread writes to this, and the first thread reads from it.
1298
  std::vector<std::vector<TrackSegment>> old_segments(pixels()[0]);
55✔
1299

1300
#pragma omp parallel
30✔
1301
  {
1302
    const int n_threads = num_threads();
25✔
1303
    const int tid = thread_num();
25✔
1304

1305
    int vert = tid;
25✔
1306
    for (int iter = 0; iter <= pixels()[1] / n_threads; iter++) {
5,050✔
1307

1308
      // Save bottom line of current work chunk to compare against later. This
1309
      // used to be inside the below if block, but it causes a spurious line to
1310
      // be drawn at the bottom of the image. Not sure why, but moving it here
1311
      // fixes things.
1312
      if (tid == n_threads - 1)
5,025!
1313
        old_segments = this_line_segments[n_threads - 1];
5,025✔
1314

1315
      if (vert < pixels()[1]) {
5,025✔
1316

1317
        for (int horiz = 0; horiz < pixels()[0]; ++horiz) {
1,005,000✔
1318

1319
          // RayTracePlot implements camera ray generation
1320
          std::pair<Position, Direction> ru = get_pixel_ray(horiz, vert);
1,000,000✔
1321

1322
          this_line_segments[tid][horiz].clear();
1,000,000✔
1323
          ProjectionRay ray(
1324
            ru.first, ru.second, *this, this_line_segments[tid][horiz]);
1,000,000✔
1325

1326
          ray.trace();
1,000,000✔
1327

1328
          // Now color the pixel based on what we have intersected...
1329
          // Loops backwards over intersections.
1330
          Position current_color(
1331
            not_found_.red, not_found_.green, not_found_.blue);
1,000,000✔
1332
          const auto& segments = this_line_segments[tid][horiz];
1,000,000✔
1333

1334
          // There must be at least two cell intersections to color, front and
1335
          // back of the cell. Maybe an infinitely thick cell could be present
1336
          // with no back, but why would you want to color that? It's easier to
1337
          // just skip that edge case and not even color it.
1338
          if (segments.size() <= 1)
1,000,000✔
1339
            continue;
616,655✔
1340

1341
          for (int i = segments.size() - 2; i >= 0; --i) {
1,072,335✔
1342
            int colormap_idx = segments[i].id;
688,990✔
1343
            RGBColor seg_color = colors_[colormap_idx];
688,990✔
1344
            Position seg_color_vec(
1345
              seg_color.red, seg_color.green, seg_color.blue);
688,990✔
1346
            double mixing =
1347
              std::exp(-xs_[colormap_idx] *
688,990✔
1348
                       (segments[i + 1].length - segments[i].length));
688,990✔
1349
            current_color =
1350
              current_color * mixing + (1.0 - mixing) * seg_color_vec;
688,990✔
1351
          }
1352

1353
          // save result converting from double-precision color coordinates to
1354
          // byte-sized
1355
          RGBColor result;
383,345✔
1356
          result.red = static_cast<uint8_t>(current_color.x);
383,345✔
1357
          result.green = static_cast<uint8_t>(current_color.y);
383,345✔
1358
          result.blue = static_cast<uint8_t>(current_color.z);
383,345✔
1359
          data(horiz, vert) = result;
383,345✔
1360

1361
          // Check to draw wireframe in horizontal direction. No inter-thread
1362
          // comm.
1363
          if (horiz > 0) {
383,345✔
1364
            if (!trackstack_equivalent(this_line_segments[tid][horiz],
382,345✔
1365
                  this_line_segments[tid][horiz - 1])) {
382,345✔
1366
              wireframe_initial(horiz, vert) = 1;
15,710✔
1367
            }
1368
          }
1369
        }
1,000,000✔
1370
      } // end "if" vert in correct range
1371

1372
      // We require a barrier before comparing vertical neighbors' intersection
1373
      // stacks. i.e. all threads must be done with their line.
1374
#pragma omp barrier
1375

1376
      // Now that the horizontal line has finished rendering, we can fill in
1377
      // wireframe entries that require comparison among all the threads. Hence
1378
      // the omp barrier being used. It has to be OUTSIDE any if blocks!
1379
      if (vert < pixels()[1]) {
5,025✔
1380
        // Loop over horizontal pixels, checking intersection stack of upper
1381
        // neighbor
1382

1383
        const std::vector<std::vector<TrackSegment>>* top_cmp = nullptr;
5,000✔
1384
        if (tid == 0)
5,000!
1385
          top_cmp = &old_segments;
5,000✔
1386
        else
1387
          top_cmp = &this_line_segments[tid - 1];
1388

1389
        for (int horiz = 0; horiz < pixels()[0]; ++horiz) {
1,005,000✔
1390
          if (!trackstack_equivalent(
1,000,000✔
1391
                this_line_segments[tid][horiz], (*top_cmp)[horiz])) {
1,000,000✔
1392
            wireframe_initial(horiz, vert) = 1;
20,595✔
1393
          }
1394
        }
1395
      }
1396

1397
      // We need another barrier to ensure threads don't proceed to modify their
1398
      // intersection stacks on that horizontal line while others are
1399
      // potentially still working on the above.
1400
#pragma omp barrier
1401
      vert += n_threads;
5,025✔
1402
    }
1403
  } // end omp parallel
1404

1405
  // Now thicken the wireframe lines and apply them to our image
1406
  for (int vert = 0; vert < pixels()[1]; ++vert) {
11,055✔
1407
    for (int horiz = 0; horiz < pixels()[0]; ++horiz) {
2,211,000✔
1408
      if (wireframe_initial(horiz, vert)) {
2,200,000✔
1409
        if (wireframe_thickness_ == 1)
70,983✔
1410
          data(horiz, vert) = wireframe_color_;
30,195✔
1411
        for (int i = -wireframe_thickness_ / 2; i < wireframe_thickness_ / 2;
195,723✔
1412
             ++i)
1413
          for (int j = -wireframe_thickness_ / 2; j < wireframe_thickness_ / 2;
546,876✔
1414
               ++j)
1415
            if (i * i + j * j < wireframe_thickness_ * wireframe_thickness_) {
422,136!
1416

1417
              // Check if wireframe pixel is out of bounds
1418
              int w_i = std::max(std::min(horiz + i, pixels()[0] - 1), 0);
422,136✔
1419
              int w_j = std::max(std::min(vert + j, pixels()[1] - 1), 0);
422,136✔
1420
              data(w_i, w_j) = wireframe_color_;
422,136✔
1421
            }
1422
      }
1423
    }
1424
  }
1425

1426
  return data;
110✔
1427
}
55✔
1428

1429
void WireframeRayTracePlot::create_output() const
55✔
1430
{
1431
  ImageData data = create_image();
55✔
1432
  write_image(data);
55✔
1433
}
55✔
1434

1435
void RayTracePlot::print_info() const
88✔
1436
{
1437
  fmt::print("Camera position: {} {} {}\n", camera_position_.x,
72✔
1438
    camera_position_.y, camera_position_.z);
88✔
1439
  fmt::print("Look at: {} {} {}\n", look_at_.x, look_at_.y, look_at_.z);
160✔
1440
  fmt::print(
72✔
1441
    "Horizontal field of view: {} degrees\n", horizontal_field_of_view_);
88✔
1442
  fmt::print("Pixels: {} {}\n", pixels()[0], pixels()[1]);
160✔
1443
}
88✔
1444

1445
void WireframeRayTracePlot::print_info() const
55✔
1446
{
1447
  fmt::print("Plot Type: Wireframe ray-traced\n");
55✔
1448
  RayTracePlot::print_info();
55✔
1449
}
55✔
1450

1451
void WireframeRayTracePlot::set_opacities(pugi::xml_node node)
55✔
1452
{
1453
  xs_.resize(colors_.size(), 1e6); // set to large value for opaque by default
55✔
1454

1455
  for (auto cn : node.children("color")) {
121✔
1456
    // Make sure 3 values are specified for RGB
1457
    double user_xs = std::stod(get_node_value(cn, "xs"));
66✔
1458
    int col_id = std::stoi(get_node_value(cn, "id"));
66✔
1459

1460
    // Add RGB
1461
    if (PlotColorBy::cells == color_by_) {
66!
1462
      if (model::cell_map.find(col_id) != model::cell_map.end()) {
66!
1463
        col_id = model::cell_map[col_id];
66✔
1464
        xs_[col_id] = user_xs;
66✔
1465
      } else {
1466
        warning(fmt::format(
×
1467
          "Could not find cell {} specified in plot {}", col_id, id()));
×
1468
      }
1469
    } else if (PlotColorBy::mats == color_by_) {
×
1470
      if (model::material_map.find(col_id) != model::material_map.end()) {
×
1471
        col_id = model::material_map[col_id];
×
1472
        xs_[col_id] = user_xs;
×
1473
      } else {
1474
        warning(fmt::format(
×
1475
          "Could not find material {} specified in plot {}", col_id, id()));
×
1476
      }
1477
    }
1478
  }
1479
}
55✔
1480

1481
void RayTracePlot::set_orthographic_width(pugi::xml_node node)
88✔
1482
{
1483
  if (check_for_node(node, "orthographic_width")) {
88✔
1484
    double orthographic_width =
1485
      std::stod(get_node_value(node, "orthographic_width", true));
11✔
1486
    if (orthographic_width < 0.0)
11!
1487
      fatal_error("Requires positive orthographic_width");
×
1488
    orthographic_width_ = orthographic_width;
11✔
1489
  }
1490
}
88✔
1491

1492
void WireframeRayTracePlot::set_wireframe_thickness(pugi::xml_node node)
55✔
1493
{
1494
  if (check_for_node(node, "wireframe_thickness")) {
55✔
1495
    int wireframe_thickness =
1496
      std::stoi(get_node_value(node, "wireframe_thickness", true));
22✔
1497
    if (wireframe_thickness < 0)
22!
1498
      fatal_error("Requires non-negative wireframe thickness");
×
1499
    wireframe_thickness_ = wireframe_thickness;
22✔
1500
  }
1501
}
55✔
1502

1503
void WireframeRayTracePlot::set_wireframe_ids(pugi::xml_node node)
55✔
1504
{
1505
  if (check_for_node(node, "wireframe_ids")) {
55✔
1506
    wireframe_ids_ = get_node_array<int>(node, "wireframe_ids");
11✔
1507
    // It is read in as actual ID values, but we have to convert to indices in
1508
    // mat/cell array
1509
    for (auto& x : wireframe_ids_)
22✔
1510
      x = color_by_ == PlotColorBy::mats ? model::material_map[x]
11!
1511
                                         : model::cell_map[x];
×
1512
  }
1513
  // We make sure the list is sorted in order to later use
1514
  // std::binary_search.
1515
  std::sort(wireframe_ids_.begin(), wireframe_ids_.end());
55✔
1516
}
55✔
1517

1518
void RayTracePlot::set_pixels(pugi::xml_node node)
88✔
1519
{
1520
  vector<int> pxls = get_node_array<int>(node, "pixels");
88✔
1521
  if (pxls.size() != 2)
88!
1522
    fatal_error(
×
1523
      fmt::format("<pixels> must be length 2 in projection plot {}", id()));
×
1524
  pixels()[0] = pxls[0];
88✔
1525
  pixels()[1] = pxls[1];
88✔
1526
}
88✔
1527

1528
void RayTracePlot::set_camera_position(pugi::xml_node node)
88✔
1529
{
1530
  vector<double> camera_pos = get_node_array<double>(node, "camera_position");
88✔
1531
  if (camera_pos.size() != 3) {
88!
1532
    fatal_error(fmt::format(
×
1533
      "camera_position element must have three floating point values"));
1534
  }
1535
  camera_position_.x = camera_pos[0];
88✔
1536
  camera_position_.y = camera_pos[1];
88✔
1537
  camera_position_.z = camera_pos[2];
88✔
1538
}
88✔
1539

1540
void RayTracePlot::set_look_at(pugi::xml_node node)
88✔
1541
{
1542
  vector<double> look_at = get_node_array<double>(node, "look_at");
88✔
1543
  if (look_at.size() != 3) {
88!
1544
    fatal_error("look_at element must have three floating point values");
×
1545
  }
1546
  look_at_.x = look_at[0];
88✔
1547
  look_at_.y = look_at[1];
88✔
1548
  look_at_.z = look_at[2];
88✔
1549
}
88✔
1550

1551
void RayTracePlot::set_field_of_view(pugi::xml_node node)
88✔
1552
{
1553
  // Defaults to 70 degree horizontal field of view (see .h file)
1554
  if (check_for_node(node, "horizontal_field_of_view")) {
88!
1555
    double fov =
1556
      std::stod(get_node_value(node, "horizontal_field_of_view", true));
×
1557
    if (fov < 180.0 && fov > 0.0) {
×
1558
      horizontal_field_of_view_ = fov;
×
1559
    } else {
1560
      fatal_error(fmt::format("Horizontal field of view for plot {} "
×
1561
                              "out-of-range. Must be in (0, 180) degrees.",
1562
        id()));
×
1563
    }
1564
  }
1565
}
88✔
1566

1567
SolidRayTracePlot::SolidRayTracePlot(pugi::xml_node node) : RayTracePlot(node)
33✔
1568
{
1569
  set_opaque_ids(node);
33✔
1570
  set_diffuse_fraction(node);
33✔
1571
  set_light_position(node);
33✔
1572
  update_view();
33✔
1573
}
33✔
1574

1575
void SolidRayTracePlot::print_info() const
33✔
1576
{
1577
  fmt::print("Plot Type: Solid ray-traced\n");
33✔
1578
  RayTracePlot::print_info();
33✔
1579
}
33✔
1580

1581
ImageData SolidRayTracePlot::create_image() const
55✔
1582
{
1583
  size_t width = pixels()[0];
55✔
1584
  size_t height = pixels()[1];
55✔
1585
  ImageData data({width, height}, not_found_);
55✔
1586

1587
#pragma omp parallel for schedule(dynamic) collapse(2)
30✔
1588
  for (int horiz = 0; horiz < pixels()[0]; ++horiz) {
3,105✔
1589
    for (int vert = 0; vert < pixels()[1]; ++vert) {
603,560✔
1590
      // RayTracePlot implements camera ray generation
1591
      std::pair<Position, Direction> ru = get_pixel_ray(horiz, vert);
600,480✔
1592
      PhongRay ray(ru.first, ru.second, *this);
600,480✔
1593
      ray.trace();
600,480✔
1594
      data(horiz, vert) = ray.result_color();
600,480✔
1595
    }
600,480✔
1596
  }
1597

1598
  return data;
55✔
1599
}
1600

1601
void SolidRayTracePlot::create_output() const
33✔
1602
{
1603
  ImageData data = create_image();
33✔
1604
  write_image(data);
33✔
1605
}
33✔
1606

1607
void SolidRayTracePlot::set_opaque_ids(pugi::xml_node node)
33✔
1608
{
1609
  if (check_for_node(node, "opaque_ids")) {
33!
1610
    auto opaque_ids_tmp = get_node_array<int>(node, "opaque_ids");
33✔
1611

1612
    // It is read in as actual ID values, but we have to convert to indices in
1613
    // mat/cell array
1614
    for (auto& x : opaque_ids_tmp)
99✔
1615
      x = color_by_ == PlotColorBy::mats ? model::material_map[x]
66!
1616
                                         : model::cell_map[x];
×
1617

1618
    opaque_ids_.insert(opaque_ids_tmp.begin(), opaque_ids_tmp.end());
33✔
1619
  }
33✔
1620
}
33✔
1621

1622
void SolidRayTracePlot::set_light_position(pugi::xml_node node)
33✔
1623
{
1624
  if (check_for_node(node, "light_position")) {
33✔
1625
    auto light_pos_tmp = get_node_array<double>(node, "light_position");
11✔
1626

1627
    if (light_pos_tmp.size() != 3)
11!
1628
      fatal_error("Light position must be given as 3D coordinates");
×
1629

1630
    light_location_.x = light_pos_tmp[0];
11✔
1631
    light_location_.y = light_pos_tmp[1];
11✔
1632
    light_location_.z = light_pos_tmp[2];
11✔
1633
  } else {
11✔
1634
    light_location_ = camera_position();
22✔
1635
  }
1636
}
33✔
1637

1638
void SolidRayTracePlot::set_diffuse_fraction(pugi::xml_node node)
33✔
1639
{
1640
  if (check_for_node(node, "diffuse_fraction")) {
33✔
1641
    diffuse_fraction_ = std::stod(get_node_value(node, "diffuse_fraction"));
11✔
1642
    if (diffuse_fraction_ < 0.0 || diffuse_fraction_ > 1.0) {
11!
1643
      fatal_error("Must have 0 <= diffuse fraction <= 1");
×
1644
    }
1645
  }
1646
}
33✔
1647

1648
void Ray::compute_distance()
3,014,638✔
1649
{
1650
  boundary() = distance_to_boundary(*this);
3,014,638✔
1651
}
3,014,638✔
1652

1653
void Ray::trace()
3,521,056✔
1654
{
1655
  // To trace the ray from its origin all the way through the model, we have
1656
  // to proceed in two phases. In the first, the ray may or may not be found
1657
  // inside the model. If the ray is already in the model, phase one can be
1658
  // skipped. Otherwise, the ray has to be advanced to the boundary of the
1659
  // model where all the cells are defined. Importantly, this is assuming that
1660
  // the model is convex, which is a very reasonable assumption for any
1661
  // radiation transport model.
1662
  //
1663
  // After phase one is done, we can starting tracing from cell to cell within
1664
  // the model. This step can use neighbor lists to accelerate the ray tracing.
1665

1666
  // Attempt to initialize the particle. We may have to enter a loop to move
1667
  // it up to the edge of the model.
1668
  bool inside_cell = exhaustive_find_cell(*this, settings::verbosity >= 10);
3,521,056✔
1669

1670
  // Advance to the boundary of the model
1671
  while (!inside_cell) {
15,618,438!
1672
    advance_to_boundary_from_void();
15,618,438✔
1673
    inside_cell = exhaustive_find_cell(*this, settings::verbosity >= 10);
15,618,438✔
1674

1675
    // If true this means no surface was intersected. See cell.cpp and search
1676
    // for numeric_limits to see where we return it.
1677
    if (surface() == std::numeric_limits<int>::max()) {
15,618,438!
1678
      warning(fmt::format("Lost a ray, r = {}, u = {}", r(), u()));
×
1679
      return;
×
1680
    }
1681

1682
    // Exit this loop and enter into cell-to-cell ray tracing (which uses
1683
    // neighbor lists)
1684
    if (inside_cell)
15,618,438✔
1685
      break;
1,549,834✔
1686

1687
    // if there is no intersection with the model, we're done
1688
    if (boundary().surface() == SURFACE_NONE)
14,068,604✔
1689
      return;
1,971,222✔
1690

1691
    event_counter_++;
12,097,382✔
1692
    if (event_counter_ > MAX_INTERSECTIONS) {
12,097,382!
1693
      warning("Likely infinite loop in ray traced plot");
×
1694
      return;
×
1695
    }
1696
  }
1697

1698
  // Call the specialized logic for this type of ray. This is for the
1699
  // intersection for the first intersection if we had one.
1700
  if (boundary().surface() != SURFACE_NONE) {
1,549,834!
1701
    // set the geometry state's surface attribute to be used for
1702
    // surface normal computation
1703
    surface() = boundary().surface();
1,549,834✔
1704
    on_intersection();
1,549,834✔
1705
    if (stop_)
1,549,834!
1706
      return;
×
1707
  }
1708

1709
  // reset surface attribute to zero after the first intersection so that it
1710
  // doesn't perturb surface crossing logic from here on out
1711
  surface() = 0;
1,549,834✔
1712

1713
  // This is the ray tracing loop within the model. It exits after exiting
1714
  // the model, which is equivalent to assuming that the model is convex.
1715
  // It would be nice to factor out the on_intersection at the end of this
1716
  // loop and then do "while (inside_cell)", but we can't guarantee it's
1717
  // on a surface in that case. There might be some other way to set it
1718
  // up that is perhaps a little more elegant, but this is what works just
1719
  // fine.
1720
  while (true) {
1721

1722
    compute_distance();
2,308,570✔
1723

1724
    // There are no more intersections to process
1725
    // if we hit the edge of the model, so stop
1726
    // the particle in that case. Also, just exit
1727
    // if a negative distance was somehow computed.
1728
    if (boundary().distance() == INFTY || boundary().distance() == INFINITY ||
4,617,140!
1729
        boundary().distance() < 0) {
2,308,570!
1730
      return;
×
1731
    }
1732

1733
    // See below comment where call_on_intersection is checked in an
1734
    // if statement for an explanation of this.
1735
    bool call_on_intersection {true};
2,308,570✔
1736
    if (boundary().distance() < 10 * TINY_BIT) {
2,308,570✔
1737
      call_on_intersection = false;
593,285✔
1738
    }
1739

1740
    // DAGMC surfaces expect us to go a little bit further than the advance
1741
    // distance to properly check cell inclusion.
1742
    boundary().distance() += TINY_BIT;
2,308,570✔
1743

1744
    // Advance particle, prepare for next intersection
1745
    for (int lev = 0; lev < n_coord(); ++lev) {
4,617,140✔
1746
      coord(lev).r() += boundary().distance() * coord(lev).u();
2,308,570✔
1747
    }
1748
    surface() = boundary().surface();
2,308,570✔
1749
    n_coord_last() = n_coord();
2,308,570✔
1750
    n_coord() = boundary().coord_level();
2,308,570✔
1751
    if (boundary().lattice_translation()[0] != 0 ||
2,308,570✔
1752
        boundary().lattice_translation()[1] != 0 ||
4,617,140!
1753
        boundary().lattice_translation()[2] != 0) {
2,308,570!
1754
      cross_lattice(*this, boundary(), settings::verbosity >= 10);
×
1755
    }
1756

1757
    // Record how far the ray has traveled
1758
    traversal_distance_ += boundary().distance();
2,308,570✔
1759
    inside_cell = neighbor_list_find_cell(*this, settings::verbosity >= 10);
2,308,570✔
1760

1761
    // Call the specialized logic for this type of ray. Note that we do not
1762
    // call this if the advance distance is very small. Unfortunately, it seems
1763
    // darn near impossible to get the particle advanced to the model boundary
1764
    // and through it without sometimes accidentally calling on_intersection
1765
    // twice. This incorrectly shades the region as occluded when it might not
1766
    // actually be. By screening out intersection distances smaller than a
1767
    // threshold 10x larger than the scoot distance used to advance up to the
1768
    // model boundary, we can avoid that situation.
1769
    if (call_on_intersection) {
2,308,570✔
1770
      on_intersection();
1,715,285✔
1771
      if (stop_)
1,715,285✔
1772
        return;
35,563✔
1773
    }
1774

1775
    if (!inside_cell)
2,273,007✔
1776
      return;
1,514,271✔
1777

1778
    event_counter_++;
758,736✔
1779
    if (event_counter_ > MAX_INTERSECTIONS) {
758,736!
1780
      warning("Likely infinite loop in ray traced plot");
×
1781
      return;
×
1782
    }
1783
  }
758,736✔
1784
}
1785

1786
void ProjectionRay::on_intersection()
2,359,148✔
1787
{
1788
  // This records a tuple with the following info
1789
  //
1790
  // 1) ID (material or cell depending on color_by_)
1791
  // 2) Distance traveled by the ray through that ID
1792
  // 3) Index of the intersected surface (starting from 1)
1793

1794
  line_segments_.emplace_back(
2,359,148✔
1795
    plot_.color_by_ == PlottableInterface::PlotColorBy::mats
2,359,148✔
1796
      ? material()
545,919✔
1797
      : lowest_coord().cell(),
1,813,229✔
1798
    traversal_distance_, boundary().surface_index());
2,359,148✔
1799
}
2,359,148✔
1800

1801
void PhongRay::on_intersection()
905,971✔
1802
{
1803
  // Check if we hit an opaque material or cell
1804
  int hit_id = plot_.color_by_ == PlottableInterface::PlotColorBy::mats
905,971✔
1805
                 ? material()
905,971!
1806
                 : lowest_coord().cell();
×
1807

1808
  // If we are reflected and have advanced beyond the camera,
1809
  // the ray is done. This is checked here because we should
1810
  // kill the ray even if the material is not opaque.
1811
  if (reflected_ && (r() - plot_.camera_position()).dot(u()) >= 0.0) {
905,971!
1812
    stop();
×
1813
    return;
164,340✔
1814
  }
1815

1816
  // Anything that's not opaque has zero impact on the plot.
1817
  if (plot_.opaque_ids_.find(hit_id) == plot_.opaque_ids_.end())
905,971✔
1818
    return;
164,340✔
1819

1820
  if (!reflected_) {
741,631✔
1821
    // reflect the particle and set the color to be colored by
1822
    // the normal or the diffuse lighting contribution
1823
    reflected_ = true;
706,068✔
1824
    result_color_ = plot_.colors_[hit_id];
706,068✔
1825
    Direction to_light = plot_.light_location_ - r();
706,068✔
1826
    to_light /= to_light.norm();
706,068✔
1827

1828
    // TODO
1829
    // Not sure what can cause a surface token to be invalid here, although it
1830
    // sometimes happens for a few pixels. It's very very rare, so proceed by
1831
    // coloring the pixel with the overlap color. It seems to happen only for a
1832
    // few pixels on the outer boundary of a hex lattice.
1833
    //
1834
    // We cannot detect it in the outer loop, and it only matters here, so
1835
    // that's why the error handling is a little different than for a lost
1836
    // ray.
1837
    if (surface() == 0) {
706,068!
1838
      result_color_ = plot_.overlap_color_;
×
1839
      stop();
×
1840
      return;
×
1841
    }
1842

1843
    // Get surface pointer
1844
    const auto& surf = model::surfaces.at(surface_index());
706,068✔
1845

1846
    Direction normal = surf->normal(r_local());
706,068✔
1847
    normal /= normal.norm();
706,068✔
1848

1849
    // Need to apply translations to find the normal vector in
1850
    // the base level universe's coordinate system.
1851
    for (int lev = n_coord() - 2; lev >= 0; --lev) {
706,068!
1852
      if (coord(lev + 1).rotated()) {
×
1853
        const Cell& c {*model::cells[coord(lev).cell()]};
×
1854
        normal = normal.inverse_rotate(c.rotation_);
×
1855
      }
1856
    }
1857

1858
    // use the normal opposed to the ray direction
1859
    if (normal.dot(u()) > 0.0) {
706,068✔
1860
      normal *= -1.0;
63,789✔
1861
    }
1862

1863
    // Facing away from the light means no lighting
1864
    double dotprod = normal.dot(to_light);
706,068✔
1865
    dotprod = std::max(0.0, dotprod);
706,068✔
1866

1867
    double modulation =
706,068✔
1868
      plot_.diffuse_fraction_ + (1.0 - plot_.diffuse_fraction_) * dotprod;
706,068✔
1869
    result_color_ *= modulation;
706,068✔
1870

1871
    // Now point the particle to the camera. We now begin
1872
    // checking to see if it's occluded by another surface
1873
    u() = to_light;
706,068✔
1874

1875
    orig_hit_id_ = hit_id;
706,068✔
1876

1877
    // OpenMC native CSG and DAGMC surfaces have some slight differences
1878
    // in how they interpret particles that are sitting on a surface.
1879
    // I don't know exactly why, but this makes everything work beautifully.
1880
    if (surf->geom_type() == GeometryType::DAG) {
706,068!
1881
      surface() = 0;
×
1882
    } else {
1883
      surface() = -surface(); // go to other side
706,068✔
1884
    }
1885

1886
    // Must fully restart coordinate search. Why? Not sure.
1887
    clear();
706,068✔
1888

1889
    // Note this could likely be faster if we cached the previous
1890
    // cell we were in before the reflection. This is the easiest
1891
    // way to fully initialize all the sub-universe coordinates and
1892
    // directions though.
1893
    bool found = exhaustive_find_cell(*this);
706,068✔
1894
    if (!found) {
706,068!
1895
      fatal_error("Lost particle after reflection.");
×
1896
    }
1897

1898
    // Must recalculate distance to boundary due to the
1899
    // direction change
1900
    compute_distance();
706,068✔
1901

1902
  } else {
1903
    // If it's not facing the light, we color with the diffuse contribution, so
1904
    // next we check if we're going to occlude the last reflected surface. if
1905
    // so, color by the diffuse contribution instead
1906

1907
    if (orig_hit_id_ == -1)
35,563!
1908
      fatal_error("somehow a ray got reflected but not original ID set?");
×
1909

1910
    result_color_ = plot_.colors_[orig_hit_id_];
35,563✔
1911
    result_color_ *= plot_.diffuse_fraction_;
35,563✔
1912
    stop();
35,563✔
1913
  }
1914
}
1915

1916
extern "C" int openmc_id_map(const void* plot, int32_t* data_out)
267✔
1917
{
1918

1919
  auto plt = reinterpret_cast<const SlicePlotBase*>(plot);
267✔
1920
  if (!plt) {
267!
1921
    set_errmsg("Invalid slice pointer passed to openmc_id_map");
×
1922
    return OPENMC_E_INVALID_ARGUMENT;
×
1923
  }
1924

1925
  if (plt->slice_color_overlaps_ && model::overlap_check_count.size() == 0) {
267!
1926
    model::overlap_check_count.resize(model::cells.size());
22✔
1927
  }
1928

1929
  auto ids = plt->get_map<IdData>();
267✔
1930

1931
  // write id data to array
1932
  std::copy(ids.data_.begin(), ids.data_.end(), data_out);
267✔
1933

1934
  return 0;
267✔
1935
}
267✔
1936

1937
extern "C" int openmc_property_map(const void* plot, double* data_out)
11✔
1938
{
1939

1940
  auto plt = reinterpret_cast<const SlicePlotBase*>(plot);
11✔
1941
  if (!plt) {
11!
1942
    set_errmsg("Invalid slice pointer passed to openmc_id_map");
×
1943
    return OPENMC_E_INVALID_ARGUMENT;
×
1944
  }
1945

1946
  if (plt->slice_color_overlaps_ && model::overlap_check_count.size() == 0) {
11!
1947
    model::overlap_check_count.resize(model::cells.size());
×
1948
  }
1949

1950
  auto props = plt->get_map<PropertyData>();
11✔
1951

1952
  // write id data to array
1953
  std::copy(props.data_.begin(), props.data_.end(), data_out);
11✔
1954

1955
  return 0;
11✔
1956
}
11✔
1957

1958
extern "C" int openmc_get_plot_index(int32_t id, int32_t* index)
22✔
1959
{
1960
  auto it = model::plot_map.find(id);
22✔
1961
  if (it == model::plot_map.end()) {
22!
NEW
1962
    set_errmsg("No plot exists with ID=" + std::to_string(id) + ".");
×
NEW
1963
    return OPENMC_E_INVALID_ID;
×
1964
  }
1965

1966
  *index = it->second;
22✔
1967
  return 0;
22✔
1968
}
1969

1970
extern "C" int openmc_plot_get_id(int32_t index, int32_t* id)
55✔
1971
{
1972
  if (index < 0 || index >= model::plots.size()) {
55!
NEW
1973
    set_errmsg("Index in plots array is out of bounds.");
×
NEW
1974
    return OPENMC_E_OUT_OF_BOUNDS;
×
1975
  }
1976

1977
  *id = model::plots[index]->id();
55✔
1978
  return 0;
55✔
1979
}
1980

NEW
1981
extern "C" int openmc_plot_set_id(int32_t index, int32_t id)
×
1982
{
NEW
1983
  if (index < 0 || index >= model::plots.size()) {
×
NEW
1984
    set_errmsg("Index in plots array is out of bounds.");
×
NEW
1985
    return OPENMC_E_OUT_OF_BOUNDS;
×
1986
  }
1987

NEW
1988
  if (id < 0 && id != C_NONE) {
×
NEW
1989
    set_errmsg("Invalid plot ID.");
×
NEW
1990
    return OPENMC_E_INVALID_ARGUMENT;
×
1991
  }
1992

NEW
1993
  auto* plot = model::plots[index].get();
×
NEW
1994
  int32_t old_id = plot->id();
×
NEW
1995
  if (id == old_id)
×
NEW
1996
    return 0;
×
1997

NEW
1998
  model::plot_map.erase(old_id);
×
1999
  try {
NEW
2000
    plot->set_id(id);
×
NEW
2001
  } catch (const std::runtime_error& e) {
×
NEW
2002
    model::plot_map[old_id] = index;
×
NEW
2003
    set_errmsg(e.what());
×
NEW
2004
    return OPENMC_E_INVALID_ID;
×
NEW
2005
  }
×
NEW
2006
  model::plot_map[plot->id()] = index;
×
NEW
2007
  return 0;
×
2008
}
2009

2010
extern "C" size_t openmc_plots_size()
22✔
2011
{
2012
  return model::plots.size();
22✔
2013
}
2014

2015
int map_phong_domain_id(
55✔
2016
  const SolidRayTracePlot* plot, int32_t id, int32_t* index_out)
2017
{
2018
  if (!plot || !index_out) {
55!
NEW
2019
    set_errmsg("Invalid plot pointer passed to map_phong_domain_id");
×
NEW
2020
    return OPENMC_E_INVALID_ARGUMENT;
×
2021
  }
2022

2023
  if (plot->color_by_ == PlottableInterface::PlotColorBy::mats) {
55!
2024
    auto it = model::material_map.find(id);
55✔
2025
    if (it == model::material_map.end()) {
55!
NEW
2026
      set_errmsg("Invalid material ID for SolidRayTracePlot");
×
NEW
2027
      return OPENMC_E_INVALID_ID;
×
2028
    }
2029
    *index_out = it->second;
55✔
2030
    return 0;
55✔
2031
  }
2032

NEW
2033
  if (plot->color_by_ == PlottableInterface::PlotColorBy::cells) {
×
NEW
2034
    auto it = model::cell_map.find(id);
×
NEW
2035
    if (it == model::cell_map.end()) {
×
NEW
2036
      set_errmsg("Invalid cell ID for SolidRayTracePlot");
×
NEW
2037
      return OPENMC_E_INVALID_ID;
×
2038
    }
NEW
2039
    *index_out = it->second;
×
NEW
2040
    return 0;
×
2041
  }
2042

NEW
2043
  set_errmsg("Unsupported color_by for SolidRayTracePlot");
×
NEW
2044
  return OPENMC_E_INVALID_TYPE;
×
2045
}
2046

2047
int get_solidraytrace_plot_by_index(int32_t index, SolidRayTracePlot** plot)
308✔
2048
{
2049
  if (!plot) {
308!
NEW
2050
    set_errmsg("Null output pointer passed to get_solidraytrace_plot_by_index");
×
NEW
2051
    return OPENMC_E_INVALID_ARGUMENT;
×
2052
  }
2053

2054
  if (index < 0 || index >= model::plots.size()) {
308!
NEW
2055
    set_errmsg("Index in plots array is out of bounds.");
×
NEW
2056
    return OPENMC_E_OUT_OF_BOUNDS;
×
2057
  }
2058

2059
  auto* plottable = model::plots[index].get();
308✔
2060
  auto* solid_plot = dynamic_cast<SolidRayTracePlot*>(plottable);
308!
2061
  if (!solid_plot) {
308!
NEW
2062
    set_errmsg("Plot at index=" + std::to_string(index) +
×
2063
               " is not a solid raytrace plot.");
NEW
2064
    return OPENMC_E_INVALID_TYPE;
×
2065
  }
2066

2067
  *plot = solid_plot;
308✔
2068
  return 0;
308✔
2069
}
2070

2071
extern "C" int openmc_solidraytrace_plot_create(int32_t* index)
11✔
2072
{
2073
  if (!index) {
11!
NEW
2074
    set_errmsg(
×
2075
      "Null output pointer passed to openmc_solidraytrace_plot_create");
NEW
2076
    return OPENMC_E_INVALID_ARGUMENT;
×
2077
  }
2078

2079
  try {
2080
    auto new_plot = std::make_unique<SolidRayTracePlot>();
11✔
2081
    new_plot->set_id();
11✔
2082
    int32_t new_plot_id = new_plot->id();
11✔
2083
    new_plot->color_by_ = PlottableInterface::PlotColorBy::mats;
11✔
2084
    new_plot->pixels()[0] = 400;
11✔
2085
    new_plot->pixels()[1] = 400;
11✔
2086
#ifdef USE_LIBPNG
2087
    new_plot->path_plot() = fmt::format("plot_{}.png", new_plot_id);
11✔
2088
#else
2089
    new_plot->path_plot() = fmt::format("plot_{}.ppm", new_plot_id);
2090
#endif
2091
    int32_t new_plot_index = model::plots.size();
11✔
2092
    model::plots.emplace_back(std::move(new_plot));
11✔
2093
    model::plot_map[new_plot_id] = new_plot_index;
11✔
2094
    *index = new_plot_index;
11✔
2095
  } catch (const std::exception& e) {
11!
NEW
2096
    set_errmsg(e.what());
×
NEW
2097
    return OPENMC_E_ALLOCATE;
×
NEW
2098
  }
×
2099

2100
  return 0;
11✔
2101
}
2102

2103
extern "C" int openmc_solidraytrace_plot_get_pixels(
33✔
2104
  int32_t index, int32_t* width, int32_t* height)
2105
{
2106
  if (!width || !height) {
33!
NEW
2107
    set_errmsg(
×
2108
      "Invalid arguments passed to openmc_solidraytrace_plot_get_pixels");
NEW
2109
    return OPENMC_E_INVALID_ARGUMENT;
×
2110
  }
2111

2112
  SolidRayTracePlot* plt = nullptr;
33✔
2113
  int err = get_solidraytrace_plot_by_index(index, &plt);
33✔
2114
  if (err)
33!
NEW
2115
    return err;
×
2116

2117
  *width = plt->pixels()[0];
33✔
2118
  *height = plt->pixels()[1];
33✔
2119
  return 0;
33✔
2120
}
2121

2122
extern "C" int openmc_solidraytrace_plot_set_pixels(
11✔
2123
  int32_t index, int32_t width, int32_t height)
2124
{
2125
  if (width <= 0 || height <= 0) {
11!
NEW
2126
    set_errmsg(
×
2127
      "Invalid arguments passed to openmc_solidraytrace_plot_set_pixels");
NEW
2128
    return OPENMC_E_INVALID_ARGUMENT;
×
2129
  }
2130

2131
  SolidRayTracePlot* plt = nullptr;
11✔
2132
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2133
  if (err)
11!
NEW
2134
    return err;
×
2135

2136
  plt->pixels()[0] = width;
11✔
2137
  plt->pixels()[1] = height;
11✔
2138
  return 0;
11✔
2139
}
2140

2141
extern "C" int openmc_solidraytrace_plot_get_color_by(
11✔
2142
  int32_t index, int32_t* color_by)
2143
{
2144
  if (!color_by) {
11!
NEW
2145
    set_errmsg(
×
2146
      "Invalid arguments passed to openmc_solidraytrace_plot_get_color_by");
NEW
2147
    return OPENMC_E_INVALID_ARGUMENT;
×
2148
  }
2149

2150
  SolidRayTracePlot* plt = nullptr;
11✔
2151
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2152
  if (err)
11!
NEW
2153
    return err;
×
2154

2155
  if (plt->color_by_ == PlottableInterface::PlotColorBy::mats) {
11!
2156
    *color_by = 0;
11✔
NEW
2157
  } else if (plt->color_by_ == PlottableInterface::PlotColorBy::cells) {
×
NEW
2158
    *color_by = 1;
×
2159
  } else {
NEW
2160
    set_errmsg("Unsupported color_by for SolidRayTracePlot");
×
NEW
2161
    return OPENMC_E_INVALID_TYPE;
×
2162
  }
2163

2164
  return 0;
11✔
2165
}
2166

2167
extern "C" int openmc_solidraytrace_plot_set_color_by(
11✔
2168
  int32_t index, int32_t color_by)
2169
{
2170
  SolidRayTracePlot* plt = nullptr;
11✔
2171
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2172
  if (err)
11!
NEW
2173
    return err;
×
2174

2175
  if (color_by == 0) {
11!
2176
    plt->color_by_ = PlottableInterface::PlotColorBy::mats;
11✔
NEW
2177
  } else if (color_by == 1) {
×
NEW
2178
    plt->color_by_ = PlottableInterface::PlotColorBy::cells;
×
2179
  } else {
NEW
2180
    set_errmsg("Invalid color_by value for SolidRayTracePlot");
×
NEW
2181
    return OPENMC_E_INVALID_ARGUMENT;
×
2182
  }
2183

2184
  return 0;
11✔
2185
}
2186

2187
extern "C" int openmc_solidraytrace_plot_set_default_colors(int32_t index)
11✔
2188
{
2189
  SolidRayTracePlot* plt = nullptr;
11✔
2190
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2191
  if (err)
11!
NEW
2192
    return err;
×
2193

2194
  plt->set_default_colors();
11✔
2195
  return 0;
11✔
2196
}
2197

NEW
2198
extern "C" int openmc_solidraytrace_plot_set_all_opaque(int32_t index)
×
2199
{
NEW
2200
  SolidRayTracePlot* plt = nullptr;
×
NEW
2201
  int err = get_solidraytrace_plot_by_index(index, &plt);
×
NEW
2202
  if (err)
×
NEW
2203
    return err;
×
2204

NEW
2205
  plt->opaque_ids().clear();
×
NEW
2206
  if (plt->color_by_ == PlottableInterface::PlotColorBy::mats) {
×
NEW
2207
    for (int32_t i = 0; i < model::materials.size(); ++i) {
×
NEW
2208
      plt->opaque_ids().insert(i);
×
2209
    }
NEW
2210
    return 0;
×
2211
  }
2212

NEW
2213
  if (plt->color_by_ == PlottableInterface::PlotColorBy::cells) {
×
NEW
2214
    for (int32_t i = 0; i < model::cells.size(); ++i) {
×
NEW
2215
      plt->opaque_ids().insert(i);
×
2216
    }
NEW
2217
    return 0;
×
2218
  }
2219

NEW
2220
  set_errmsg("Unsupported color_by for SolidRayTracePlot");
×
NEW
2221
  return OPENMC_E_INVALID_TYPE;
×
2222
}
2223

2224
extern "C" int openmc_solidraytrace_plot_set_opaque(
22✔
2225
  int32_t index, int32_t id, bool visible)
2226
{
2227
  SolidRayTracePlot* plt = nullptr;
22✔
2228
  int err = get_solidraytrace_plot_by_index(index, &plt);
22✔
2229
  if (err)
22!
NEW
2230
    return err;
×
2231

2232
  int32_t domain_index = -1;
22✔
2233
  err = map_phong_domain_id(plt, id, &domain_index);
22✔
2234
  if (err)
22!
NEW
2235
    return err;
×
2236

2237
  if (visible) {
22✔
2238
    plt->opaque_ids().insert(domain_index);
11✔
2239
  } else {
2240
    plt->opaque_ids().erase(domain_index);
11✔
2241
  }
2242

2243
  return 0;
22✔
2244
}
2245

2246
extern "C" int openmc_solidraytrace_plot_set_color(
22✔
2247
  int32_t index, int32_t id, uint8_t r, uint8_t g, uint8_t b)
2248
{
2249
  SolidRayTracePlot* plt = nullptr;
22✔
2250
  int err = get_solidraytrace_plot_by_index(index, &plt);
22✔
2251
  if (err)
22!
NEW
2252
    return err;
×
2253

2254
  int32_t domain_index = -1;
22✔
2255
  err = map_phong_domain_id(plt, id, &domain_index);
22✔
2256
  if (err)
22!
NEW
2257
    return err;
×
2258

2259
  if (domain_index < 0 ||
44!
2260
      static_cast<size_t>(domain_index) >= plt->colors_.size()) {
22✔
NEW
2261
    set_errmsg("Color index out of range for SolidRayTracePlot");
×
NEW
2262
    return OPENMC_E_OUT_OF_BOUNDS;
×
2263
  }
2264

2265
  plt->colors_[domain_index] = RGBColor(r, g, b);
22✔
2266
  return 0;
22✔
2267
}
2268

2269
extern "C" int openmc_solidraytrace_plot_get_camera_position(
11✔
2270
  int32_t index, double* x, double* y, double* z)
2271
{
2272
  if (!x || !y || !z) {
11!
NEW
2273
    set_errmsg("Invalid arguments passed to "
×
2274
               "openmc_solidraytrace_plot_get_camera_position");
NEW
2275
    return OPENMC_E_INVALID_ARGUMENT;
×
2276
  }
2277

2278
  SolidRayTracePlot* plt = nullptr;
11✔
2279
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2280
  if (err)
11!
NEW
2281
    return err;
×
2282

2283
  const auto& camera_position = plt->camera_position();
11✔
2284
  *x = camera_position.x;
11✔
2285
  *y = camera_position.y;
11✔
2286
  *z = camera_position.z;
11✔
2287
  return 0;
11✔
2288
}
2289

2290
extern "C" int openmc_solidraytrace_plot_set_camera_position(
11✔
2291
  int32_t index, double x, double y, double z)
2292
{
2293
  SolidRayTracePlot* plt = nullptr;
11✔
2294
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2295
  if (err)
11!
NEW
2296
    return err;
×
2297

2298
  plt->camera_position() = {x, y, z};
11✔
2299
  return 0;
11✔
2300
}
2301

2302
extern "C" int openmc_solidraytrace_plot_get_look_at(
11✔
2303
  int32_t index, double* x, double* y, double* z)
2304
{
2305
  if (!x || !y || !z) {
11!
NEW
2306
    set_errmsg(
×
2307
      "Invalid arguments passed to openmc_solidraytrace_plot_get_look_at");
NEW
2308
    return OPENMC_E_INVALID_ARGUMENT;
×
2309
  }
2310

2311
  SolidRayTracePlot* plt = nullptr;
11✔
2312
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2313
  if (err)
11!
NEW
2314
    return err;
×
2315

2316
  const auto& look_at = plt->look_at();
11✔
2317
  *x = look_at.x;
11✔
2318
  *y = look_at.y;
11✔
2319
  *z = look_at.z;
11✔
2320
  return 0;
11✔
2321
}
2322

2323
extern "C" int openmc_solidraytrace_plot_set_look_at(
11✔
2324
  int32_t index, double x, double y, double z)
2325
{
2326
  SolidRayTracePlot* plt = nullptr;
11✔
2327
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2328
  if (err)
11!
NEW
2329
    return err;
×
2330

2331
  plt->look_at() = {x, y, z};
11✔
2332
  return 0;
11✔
2333
}
2334

2335
extern "C" int openmc_solidraytrace_plot_get_up(
11✔
2336
  int32_t index, double* x, double* y, double* z)
2337
{
2338
  if (!x || !y || !z) {
11!
NEW
2339
    set_errmsg("Invalid arguments passed to openmc_solidraytrace_plot_get_up");
×
NEW
2340
    return OPENMC_E_INVALID_ARGUMENT;
×
2341
  }
2342

2343
  SolidRayTracePlot* plt = nullptr;
11✔
2344
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2345
  if (err)
11!
NEW
2346
    return err;
×
2347

2348
  const auto& up = plt->up();
11✔
2349
  *x = up.x;
11✔
2350
  *y = up.y;
11✔
2351
  *z = up.z;
11✔
2352
  return 0;
11✔
2353
}
2354

2355
extern "C" int openmc_solidraytrace_plot_set_up(
11✔
2356
  int32_t index, double x, double y, double z)
2357
{
2358
  SolidRayTracePlot* plt = nullptr;
11✔
2359
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2360
  if (err)
11!
NEW
2361
    return err;
×
2362

2363
  plt->up() = {x, y, z};
11✔
2364
  return 0;
11✔
2365
}
2366

2367
extern "C" int openmc_solidraytrace_plot_get_light_position(
11✔
2368
  int32_t index, double* x, double* y, double* z)
2369
{
2370
  if (!x || !y || !z) {
11!
NEW
2371
    set_errmsg("Invalid arguments passed to "
×
2372
               "openmc_solidraytrace_plot_get_light_position");
NEW
2373
    return OPENMC_E_INVALID_ARGUMENT;
×
2374
  }
2375

2376
  SolidRayTracePlot* plt = nullptr;
11✔
2377
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2378
  if (err)
11!
NEW
2379
    return err;
×
2380

2381
  const auto& light_position = plt->light_location();
11✔
2382
  *x = light_position.x;
11✔
2383
  *y = light_position.y;
11✔
2384
  *z = light_position.z;
11✔
2385
  return 0;
11✔
2386
}
2387

2388
extern "C" int openmc_solidraytrace_plot_set_light_position(
11✔
2389
  int32_t index, double x, double y, double z)
2390
{
2391
  SolidRayTracePlot* plt = nullptr;
11✔
2392
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2393
  if (err)
11!
NEW
2394
    return err;
×
2395

2396
  plt->light_location() = {x, y, z};
11✔
2397
  return 0;
11✔
2398
}
2399

2400
extern "C" int openmc_solidraytrace_plot_get_fov(int32_t index, double* fov)
11✔
2401
{
2402
  if (!fov) {
11!
NEW
2403
    set_errmsg("Invalid arguments passed to openmc_solidraytrace_plot_get_fov");
×
NEW
2404
    return OPENMC_E_INVALID_ARGUMENT;
×
2405
  }
2406

2407
  SolidRayTracePlot* plt = nullptr;
11✔
2408
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2409
  if (err)
11!
NEW
2410
    return err;
×
2411

2412
  *fov = plt->horizontal_field_of_view();
11✔
2413
  return 0;
11✔
2414
}
2415

2416
extern "C" int openmc_solidraytrace_plot_set_fov(int32_t index, double fov)
11✔
2417
{
2418
  SolidRayTracePlot* plt = nullptr;
11✔
2419
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2420
  if (err)
11!
NEW
2421
    return err;
×
2422

2423
  plt->horizontal_field_of_view() = fov;
11✔
2424
  return 0;
11✔
2425
}
2426

2427
extern "C" int openmc_solidraytrace_plot_update_view(int32_t index)
22✔
2428
{
2429
  SolidRayTracePlot* plt = nullptr;
22✔
2430
  int err = get_solidraytrace_plot_by_index(index, &plt);
22✔
2431
  if (err)
22!
NEW
2432
    return err;
×
2433

2434
  plt->update_view();
22✔
2435
  return 0;
22✔
2436
}
2437

2438
extern "C" int openmc_solidraytrace_plot_create_image(
22✔
2439
  int32_t index, uint8_t* data_out, int32_t width, int32_t height)
2440
{
2441
  if (!data_out || width <= 0 || height <= 0) {
22!
NEW
2442
    set_errmsg(
×
2443
      "Invalid arguments passed to openmc_solidraytrace_plot_create_image");
NEW
2444
    return OPENMC_E_INVALID_ARGUMENT;
×
2445
  }
2446

2447
  SolidRayTracePlot* plt = nullptr;
22✔
2448
  int err = get_solidraytrace_plot_by_index(index, &plt);
22✔
2449
  if (err)
22!
NEW
2450
    return err;
×
2451

2452
  if (plt->pixels()[0] != width || plt->pixels()[1] != height) {
22!
NEW
2453
    set_errmsg(
×
2454
      "Requested image size does not match SolidRayTracePlot pixel settings");
NEW
2455
    return OPENMC_E_INVALID_SIZE;
×
2456
  }
2457

2458
  ImageData data = plt->create_image();
22✔
2459
  if (static_cast<int32_t>(data.shape()[0]) != width ||
44!
2460
      static_cast<int32_t>(data.shape()[1]) != height) {
22!
NEW
2461
    set_errmsg("Unexpected image size from SolidRayTracePlot create_image");
×
NEW
2462
    return OPENMC_E_INVALID_SIZE;
×
2463
  }
2464

2465
  for (int32_t y = 0; y < height; ++y) {
154✔
2466
    for (int32_t x = 0; x < width; ++x) {
1,188✔
2467
      const auto& color = data(x, y);
1,056✔
2468
      size_t idx = (static_cast<size_t>(y) * width + x) * 3;
1,056✔
2469
      data_out[idx + 0] = color.red;
1,056✔
2470
      data_out[idx + 1] = color.green;
1,056✔
2471
      data_out[idx + 2] = color.blue;
1,056✔
2472
    }
2473
  }
2474

2475
  return 0;
22✔
2476
}
22✔
2477

2478
extern "C" int openmc_solidraytrace_plot_get_color(
11✔
2479
  int32_t index, int32_t id, uint8_t* r, uint8_t* g, uint8_t* b)
2480
{
2481
  if (!r || !g || !b) {
11!
NEW
2482
    set_errmsg(
×
2483
      "Invalid arguments passed to openmc_solidraytrace_plot_get_color");
NEW
2484
    return OPENMC_E_INVALID_ARGUMENT;
×
2485
  }
2486

2487
  SolidRayTracePlot* plt = nullptr;
11✔
2488
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2489
  if (err)
11!
NEW
2490
    return err;
×
2491

2492
  int32_t domain_index = -1;
11✔
2493
  err = map_phong_domain_id(plt, id, &domain_index);
11✔
2494
  if (err)
11!
NEW
2495
    return err;
×
2496

2497
  if (domain_index < 0 ||
22!
2498
      static_cast<size_t>(domain_index) >= plt->colors_.size()) {
11✔
NEW
2499
    set_errmsg("Color index out of range for SolidRayTracePlot");
×
NEW
2500
    return OPENMC_E_OUT_OF_BOUNDS;
×
2501
  }
2502

2503
  const auto& color = plt->colors_[domain_index];
11✔
2504
  *r = color.red;
11✔
2505
  *g = color.green;
11✔
2506
  *b = color.blue;
11✔
2507
  return 0;
11✔
2508
}
2509

2510
extern "C" int openmc_solidraytrace_plot_get_diffuse_fraction(
11✔
2511
  int32_t index, double* diffuse_fraction)
2512
{
2513
  if (!diffuse_fraction) {
11!
NEW
2514
    set_errmsg("Invalid arguments passed to "
×
2515
               "openmc_solidraytrace_plot_get_diffuse_fraction");
NEW
2516
    return OPENMC_E_INVALID_ARGUMENT;
×
2517
  }
2518

2519
  SolidRayTracePlot* plt = nullptr;
11✔
2520
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2521
  if (err)
11!
NEW
2522
    return err;
×
2523

2524
  *diffuse_fraction = plt->diffuse_fraction();
11✔
2525
  return 0;
11✔
2526
}
2527

2528
extern "C" int openmc_solidraytrace_plot_set_diffuse_fraction(
11✔
2529
  int32_t index, double diffuse_fraction)
2530
{
2531
  SolidRayTracePlot* plt = nullptr;
11✔
2532
  int err = get_solidraytrace_plot_by_index(index, &plt);
11✔
2533
  if (err)
11!
NEW
2534
    return err;
×
2535

2536
  if (diffuse_fraction < 0.0 || diffuse_fraction > 1.0) {
11!
NEW
2537
    set_errmsg("Diffuse fraction must be between 0 and 1");
×
NEW
2538
    return OPENMC_E_INVALID_ARGUMENT;
×
2539
  }
2540

2541
  plt->diffuse_fraction() = diffuse_fraction;
11✔
2542
  return 0;
11✔
2543
}
2544

2545
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc