Raytrace plots (#2655)

Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Gavin Ridley 2025-02-17 21:11:54 -06:00 committed by GitHub
parent a5b26de041
commit 81b7388624
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1487 additions and 476 deletions

View file

@ -204,18 +204,21 @@ void read_plots_xml(pugi::xml_node root)
int id = std::stoi(id_string);
if (check_for_node(node, "type")) {
std::string type_str = get_node_value(node, "type", true);
if (type_str == "slice")
if (type_str == "slice") {
model::plots.emplace_back(
std::make_unique<Plot>(node, Plot::PlotType::slice));
else if (type_str == "voxel")
} else if (type_str == "voxel") {
model::plots.emplace_back(
std::make_unique<Plot>(node, Plot::PlotType::voxel));
else if (type_str == "projection")
model::plots.emplace_back(std::make_unique<ProjectionPlot>(node));
else
} else if (type_str == "wireframe_raytrace") {
model::plots.emplace_back(
std::make_unique<WireframeRayTracePlot>(node));
} else if (type_str == "solid_raytrace") {
model::plots.emplace_back(std::make_unique<SolidRayTracePlot>(node));
} else {
fatal_error(
fmt::format("Unsupported plot type '{}' in plot {}", type_str, id));
}
model::plot_map[model::plots.back()->id()] = model::plots.size() - 1;
} else {
fatal_error(fmt::format("Must specify plot type in plot {}", id));
@ -264,8 +267,8 @@ void Plot::create_image() const
}
data(x, y) = colors_[model::material_map[id]];
} // color_by if-else
} // x for loop
} // y for loop
}
}
// draw mesh lines if present
if (index_meshlines_mesh_ >= 0) {
@ -1036,26 +1039,49 @@ RGBColor random_color(void)
int(prn(&model::plotter_seed) * 255), int(prn(&model::plotter_seed) * 255)};
}
ProjectionPlot::ProjectionPlot(pugi::xml_node node) : PlottableInterface(node)
RayTracePlot::RayTracePlot(pugi::xml_node node) : PlottableInterface(node)
{
set_output_path(node);
set_look_at(node);
set_camera_position(node);
set_field_of_view(node);
set_pixels(node);
set_opacities(node);
set_orthographic_width(node);
set_wireframe_thickness(node);
set_wireframe_ids(node);
set_wireframe_color(node);
set_output_path(node);
if (check_for_node(node, "orthographic_width") &&
check_for_node(node, "field_of_view"))
fatal_error("orthographic_width and field_of_view are mutually exclusive "
"parameters.");
// Get centerline vector for camera-to-model. We create vectors around this
// that form a pixel array, and then trace rays along that.
auto up = up_ / up_.norm();
Direction looking_direction = look_at_ - camera_position_;
looking_direction /= looking_direction.norm();
if (std::abs(std::abs(looking_direction.dot(up)) - 1.0) < 1e-9)
fatal_error("Up vector cannot align with vector between camera position "
"and look_at!");
Direction cam_yaxis = looking_direction.cross(up);
cam_yaxis /= cam_yaxis.norm();
Direction cam_zaxis = cam_yaxis.cross(looking_direction);
cam_zaxis /= cam_zaxis.norm();
// Cache the camera-to-model matrix
camera_to_model_ = {looking_direction.x, cam_yaxis.x, cam_zaxis.x,
looking_direction.y, cam_yaxis.y, cam_zaxis.y, looking_direction.z,
cam_yaxis.z, cam_zaxis.z};
}
void ProjectionPlot::set_wireframe_color(pugi::xml_node plot_node)
WireframeRayTracePlot::WireframeRayTracePlot(pugi::xml_node node)
: RayTracePlot(node)
{
set_opacities(node);
set_wireframe_thickness(node);
set_wireframe_ids(node);
set_wireframe_color(node);
}
void WireframeRayTracePlot::set_wireframe_color(pugi::xml_node plot_node)
{
// Copy plot background color
if (check_for_node(plot_node, "wireframe_color")) {
@ -1068,7 +1094,7 @@ void ProjectionPlot::set_wireframe_color(pugi::xml_node plot_node)
}
}
void ProjectionPlot::set_output_path(pugi::xml_node node)
void RayTracePlot::set_output_path(pugi::xml_node node)
{
// Set output file path
std::string filename;
@ -1089,33 +1115,7 @@ void ProjectionPlot::set_output_path(pugi::xml_node node)
path_plot_ = filename;
}
// Advances to the next boundary from outside the geometry
// Returns -1 if no intersection found, and the surface index
// if an intersection was found.
int ProjectionPlot::advance_to_boundary_from_void(GeometryState& p)
{
constexpr double scoot = 1e-5;
double min_dist = {INFINITY};
auto coord = p.coord(0);
Universe* uni = model::universes[model::root_universe].get();
int intersected_surface = -1;
for (auto c_i : uni->cells_) {
auto dist = model::cells.at(c_i)->distance(coord.r, coord.u, 0, &p);
if (dist.first < min_dist) {
min_dist = dist.first;
intersected_surface = dist.second;
}
}
if (min_dist > 1e300)
return -1;
else { // advance the particle
for (int j = 0; j < p.n_coord(); ++j)
p.coord(j).r += (min_dist + scoot) * p.coord(j).u;
return std::abs(intersected_surface);
}
}
bool ProjectionPlot::trackstack_equivalent(
bool WireframeRayTracePlot::trackstack_equivalent(
const std::vector<TrackSegment>& track1,
const std::vector<TrackSegment>& track2) const
{
@ -1125,7 +1125,7 @@ bool ProjectionPlot::trackstack_equivalent(
return false;
for (int i = 0; i < track1.size(); ++i) {
if (track1[i].id != track2[i].id ||
track1[i].surface != track2[i].surface) {
track1[i].surface_index != track2[i].surface_index) {
return false;
}
}
@ -1152,7 +1152,7 @@ bool ProjectionPlot::trackstack_equivalent(
if (t1_i == track1.size() && t2_i == track2.size())
break;
// Check if surface different
if (track1[t1_i].surface != track2[t2_i].surface)
if (track1[t1_i].surface_index != track2[t2_i].surface_index)
return false;
// Pretty sure this should not be used:
@ -1160,7 +1160,7 @@ bool ProjectionPlot::trackstack_equivalent(
// t1_i != track1.size() - 1 &&
// track1[t1_i+1].id != track2[t2_i+1].id) return false;
if (t2_i != 0 && t1_i != 0 &&
track1[t1_i - 1].surface != track2[t2_i - 1].surface)
track1[t1_i - 1].surface_index != track2[t2_i - 1].surface_index)
return false;
// Check if neighboring cells are different
@ -1176,44 +1176,58 @@ bool ProjectionPlot::trackstack_equivalent(
}
}
void ProjectionPlot::create_output() const
std::pair<Position, Direction> RayTracePlot::get_pixel_ray(
int horiz, int vert) const
{
// Get centerline vector for camera-to-model. We create vectors around this
// that form a pixel array, and then trace rays along that.
auto up = up_ / up_.norm();
Direction looking_direction = look_at_ - camera_position_;
looking_direction /= looking_direction.norm();
if (std::abs(std::abs(looking_direction.dot(up)) - 1.0) < 1e-9)
fatal_error("Up vector cannot align with vector between camera position "
"and look_at!");
Direction cam_yaxis = looking_direction.cross(up);
cam_yaxis /= cam_yaxis.norm();
Direction cam_zaxis = cam_yaxis.cross(looking_direction);
cam_zaxis /= cam_zaxis.norm();
// Transformation matrix for directions
std::vector<double> camera_to_model = {looking_direction.x, cam_yaxis.x,
cam_zaxis.x, looking_direction.y, cam_yaxis.y, cam_zaxis.y,
looking_direction.z, cam_yaxis.z, cam_zaxis.z};
// Now we convert to the polar coordinate system with the polar angle
// measuring the angle from the vector up_. Phi is the rotation about up_. For
// now, up_ is hard-coded to be +z.
// Compute field of view in radians
constexpr double DEGREE_TO_RADIAN = M_PI / 180.0;
double horiz_fov_radians = horizontal_field_of_view_ * DEGREE_TO_RADIAN;
double p0 = static_cast<double>(pixels_[0]);
double p1 = static_cast<double>(pixels_[1]);
double vert_fov_radians = horiz_fov_radians * p1 / p0;
double dphi = horiz_fov_radians / p0;
double dmu = vert_fov_radians / p1;
// focal_plane_dist can be changed to alter the perspective distortion
// effect. This is in units of cm. This seems to look good most of the
// time. TODO let this variable be set through XML.
constexpr double focal_plane_dist = 10.0;
const double dx = 2.0 * focal_plane_dist * std::tan(0.5 * horiz_fov_radians);
const double dy = p1 / p0 * dx;
std::pair<Position, Direction> result;
// Generate the starting position/direction of the ray
if (orthographic_width_ == C_NONE) { // perspective projection
Direction camera_local_vec;
camera_local_vec.x = focal_plane_dist;
camera_local_vec.y = -0.5 * dx + horiz * dx / p0;
camera_local_vec.z = 0.5 * dy - vert * dy / p1;
camera_local_vec /= camera_local_vec.norm();
result.first = camera_position_;
result.second = camera_local_vec.rotate(camera_to_model_);
} else { // orthographic projection
double x_pix_coord = (static_cast<double>(horiz) - p0 / 2.0) / p0;
double y_pix_coord = (static_cast<double>(vert) - p1 / 2.0) / p1;
result.first = camera_position_ +
camera_y_axis() * x_pix_coord * orthographic_width_ +
camera_z_axis() * y_pix_coord * orthographic_width_;
result.second = camera_x_axis();
}
return result;
}
void WireframeRayTracePlot::create_output() const
{
size_t width = pixels_[0];
size_t height = pixels_[1];
ImageData data({width, height}, not_found_);
// This array marks where the initial wireframe was drawn.
// We convolve it with a filter that gets adjusted with the
// wireframe thickness in order to thicken the lines.
// This array marks where the initial wireframe was drawn. We convolve it with
// a filter that gets adjusted with the wireframe thickness in order to
// thicken the lines.
xt::xtensor<int, 2> wireframe_initial({width, height}, 0);
/* Holds all of the track segments for the current rendered line of pixels.
@ -1242,16 +1256,13 @@ void ProjectionPlot::create_output() const
const int n_threads = num_threads();
const int tid = thread_num();
GeometryState p;
p.u() = {1.0, 0.0, 0.0};
int vert = tid;
for (int iter = 0; iter <= pixels_[1] / n_threads; iter++) {
// Save bottom line of current work chunk to compare against later
// I used to have this inside the below if block, but it causes a
// spurious line to be drawn at the bottom of the image. Not sure
// why, but moving it here fixes things.
// Save bottom line of current work chunk to compare against later. This
// used to be inside the below if block, but it causes a spurious line to
// be drawn at the bottom of the image. Not sure why, but moving it here
// fixes things.
if (tid == n_threads - 1)
old_segments = this_line_segments[n_threads - 1];
@ -1259,126 +1270,48 @@ void ProjectionPlot::create_output() const
for (int horiz = 0; horiz < pixels_[0]; ++horiz) {
// Projection mode below decides ray starting conditions
Position init_r;
Direction init_u;
// Generate the starting position/direction of the ray
if (orthographic_width_ == 0.0) { // perspective projection
double this_phi =
-horiz_fov_radians / 2.0 + dphi * horiz + 0.5 * dphi;
double this_mu =
-vert_fov_radians / 2.0 + dmu * vert + M_PI / 2.0 + 0.5 * dmu;
Direction camera_local_vec;
camera_local_vec.x = std::cos(this_phi) * std::sin(this_mu);
camera_local_vec.y = std::sin(this_phi) * std::sin(this_mu);
camera_local_vec.z = std::cos(this_mu);
init_u = camera_local_vec.rotate(camera_to_model);
init_r = camera_position_;
} else { // orthographic projection
init_u = looking_direction;
double x_pix_coord = (static_cast<double>(horiz) - p0 / 2.0) / p0;
double y_pix_coord = (static_cast<double>(vert) - p1 / 2.0) / p0;
init_r = camera_position_;
init_r += cam_yaxis * x_pix_coord * orthographic_width_;
init_r += cam_zaxis * y_pix_coord * orthographic_width_;
}
// Resets internal geometry state of particle
p.init_from_r_u(init_r, init_u);
bool hitsomething = false;
bool intersection_found = true;
int loop_counter = 0;
// RayTracePlot implements camera ray generation
std::pair<Position, Direction> ru = get_pixel_ray(horiz, vert);
this_line_segments[tid][horiz].clear();
ProjectionRay ray(
ru.first, ru.second, *this, this_line_segments[tid][horiz]);
int first_surface =
-1; // surface first passed when entering the model
bool first_inside_model = true; // false after entering the model
while (intersection_found) {
bool inside_cell = false;
int32_t i_surface = p.surface_index();
if (i_surface > 0 &&
model::surfaces[i_surface]->geom_type() == GeometryType::DAG) {
#ifdef DAGMC
int32_t i_cell = next_cell(i_surface,
p.cell_last(p.n_coord() - 1), p.lowest_coord().universe);
inside_cell = i_cell >= 0;
#else
fatal_error(
"Not compiled for DAGMC, but somehow you have a DAGCell!");
#endif
} else {
inside_cell = exhaustive_find_cell(p);
}
if (inside_cell) {
// This allows drawing wireframes with surface intersection
// edges on the model boundary for the same cell.
if (first_inside_model) {
this_line_segments[tid][horiz].emplace_back(
color_by_ == PlotColorBy::mats ? p.material()
: p.lowest_coord().cell,
0.0, first_surface);
first_inside_model = false;
}
hitsomething = true;
intersection_found = true;
auto dist = distance_to_boundary(p);
this_line_segments[tid][horiz].emplace_back(
color_by_ == PlotColorBy::mats ? p.material()
: p.lowest_coord().cell,
dist.distance, std::abs(dist.surface));
// Advance particle
for (int lev = 0; lev < p.n_coord(); ++lev) {
p.coord(lev).r += dist.distance * p.coord(lev).u;
}
p.surface() = dist.surface;
p.n_coord_last() = p.n_coord();
p.n_coord() = dist.coord_level;
if (dist.lattice_translation[0] != 0 ||
dist.lattice_translation[1] != 0 ||
dist.lattice_translation[2] != 0) {
cross_lattice(p, dist);
}
} else {
first_surface = advance_to_boundary_from_void(p);
intersection_found =
first_surface != -1; // -1 if no surface found
}
loop_counter++;
if (loop_counter > MAX_INTERSECTIONS)
fatal_error("Infinite loop in projection plot");
}
ray.trace();
// Now color the pixel based on what we have intersected...
// Loops backwards over intersections.
Position current_color(
not_found_.red, not_found_.green, not_found_.blue);
const auto& segments = this_line_segments[tid][horiz];
for (unsigned i = segments.size(); i-- > 0;) {
// There must be at least two cell intersections to color, front and
// back of the cell. Maybe an infinitely thick cell could be present
// with no back, but why would you want to color that? It's easier to
// just skip that edge case and not even color it.
if (segments.size() <= 1)
continue;
for (int i = segments.size() - 2; i >= 0; --i) {
int colormap_idx = segments[i].id;
RGBColor seg_color = colors_[colormap_idx];
Position seg_color_vec(
seg_color.red, seg_color.green, seg_color.blue);
double mixing = std::exp(-xs_[colormap_idx] * segments[i].length);
double mixing =
std::exp(-xs_[colormap_idx] *
(segments[i + 1].length - segments[i].length));
current_color =
current_color * mixing + (1.0 - mixing) * seg_color_vec;
RGBColor result;
result.red = static_cast<uint8_t>(current_color.x);
result.green = static_cast<uint8_t>(current_color.y);
result.blue = static_cast<uint8_t>(current_color.z);
data(horiz, vert) = result;
}
// save result converting from double-precision color coordinates to
// byte-sized
RGBColor result;
result.red = static_cast<uint8_t>(current_color.x);
result.green = static_cast<uint8_t>(current_color.y);
result.blue = static_cast<uint8_t>(current_color.z);
data(horiz, vert) = result;
// Check to draw wireframe in horizontal direction. No inter-thread
// comm.
if (horiz > 0) {
@ -1451,9 +1384,8 @@ void ProjectionPlot::create_output() const
#endif
}
void ProjectionPlot::print_info() const
void RayTracePlot::print_info() const
{
fmt::print("Plot Type: Projection\n");
fmt::print("Camera position: {} {} {}\n", camera_position_.x,
camera_position_.y, camera_position_.z);
fmt::print("Look at: {} {} {}\n", look_at_.x, look_at_.y, look_at_.z);
@ -1462,7 +1394,13 @@ void ProjectionPlot::print_info() const
fmt::print("Pixels: {} {}\n", pixels_[0], pixels_[1]);
}
void ProjectionPlot::set_opacities(pugi::xml_node node)
void WireframeRayTracePlot::print_info() const
{
fmt::print("Plot Type: Wireframe ray-traced\n");
RayTracePlot::print_info();
}
void WireframeRayTracePlot::set_opacities(pugi::xml_node node)
{
xs_.resize(colors_.size(), 1e6); // set to large value for opaque by default
@ -1492,7 +1430,7 @@ void ProjectionPlot::set_opacities(pugi::xml_node node)
}
}
void ProjectionPlot::set_orthographic_width(pugi::xml_node node)
void RayTracePlot::set_orthographic_width(pugi::xml_node node)
{
if (check_for_node(node, "orthographic_width")) {
double orthographic_width =
@ -1503,7 +1441,7 @@ void ProjectionPlot::set_orthographic_width(pugi::xml_node node)
}
}
void ProjectionPlot::set_wireframe_thickness(pugi::xml_node node)
void WireframeRayTracePlot::set_wireframe_thickness(pugi::xml_node node)
{
if (check_for_node(node, "wireframe_thickness")) {
int wireframe_thickness =
@ -1514,7 +1452,7 @@ void ProjectionPlot::set_wireframe_thickness(pugi::xml_node node)
}
}
void ProjectionPlot::set_wireframe_ids(pugi::xml_node node)
void WireframeRayTracePlot::set_wireframe_ids(pugi::xml_node node)
{
if (check_for_node(node, "wireframe_ids")) {
wireframe_ids_ = get_node_array<int>(node, "wireframe_ids");
@ -1529,7 +1467,7 @@ void ProjectionPlot::set_wireframe_ids(pugi::xml_node node)
std::sort(wireframe_ids_.begin(), wireframe_ids_.end());
}
void ProjectionPlot::set_pixels(pugi::xml_node node)
void RayTracePlot::set_pixels(pugi::xml_node node)
{
vector<int> pxls = get_node_array<int>(node, "pixels");
if (pxls.size() != 2)
@ -1539,19 +1477,19 @@ void ProjectionPlot::set_pixels(pugi::xml_node node)
pixels_[1] = pxls[1];
}
void ProjectionPlot::set_camera_position(pugi::xml_node node)
void RayTracePlot::set_camera_position(pugi::xml_node node)
{
vector<double> camera_pos = get_node_array<double>(node, "camera_position");
if (camera_pos.size() != 3) {
fatal_error(
fmt::format("look_at element must have three floating point values"));
fatal_error(fmt::format(
"camera_position element must have three floating point values"));
}
camera_position_.x = camera_pos[0];
camera_position_.y = camera_pos[1];
camera_position_.z = camera_pos[2];
}
void ProjectionPlot::set_look_at(pugi::xml_node node)
void RayTracePlot::set_look_at(pugi::xml_node node)
{
vector<double> look_at = get_node_array<double>(node, "look_at");
if (look_at.size() != 3) {
@ -1562,7 +1500,7 @@ void ProjectionPlot::set_look_at(pugi::xml_node node)
look_at_.z = look_at[2];
}
void ProjectionPlot::set_field_of_view(pugi::xml_node node)
void RayTracePlot::set_field_of_view(pugi::xml_node node)
{
// Defaults to 70 degree horizontal field of view (see .h file)
if (check_for_node(node, "field_of_view")) {
@ -1576,6 +1514,352 @@ void ProjectionPlot::set_field_of_view(pugi::xml_node node)
}
}
SolidRayTracePlot::SolidRayTracePlot(pugi::xml_node node) : RayTracePlot(node)
{
set_opaque_ids(node);
set_diffuse_fraction(node);
set_light_position(node);
}
void SolidRayTracePlot::print_info() const
{
fmt::print("Plot Type: Solid ray-traced\n");
RayTracePlot::print_info();
}
void SolidRayTracePlot::create_output() const
{
size_t width = pixels_[0];
size_t height = pixels_[1];
ImageData data({width, height}, not_found_);
#pragma omp parallel for schedule(dynamic) collapse(2)
for (int horiz = 0; horiz < pixels_[0]; ++horiz) {
for (int vert = 0; vert < pixels_[1]; ++vert) {
// RayTracePlot implements camera ray generation
std::pair<Position, Direction> ru = get_pixel_ray(horiz, vert);
PhongRay ray(ru.first, ru.second, *this);
ray.trace();
data(horiz, vert) = ray.result_color();
}
}
#ifdef USE_LIBPNG
output_png(path_plot(), data);
#else
output_ppm(path_plot(), data);
#endif
}
void SolidRayTracePlot::set_opaque_ids(pugi::xml_node node)
{
if (check_for_node(node, "opaque_ids")) {
auto opaque_ids_tmp = get_node_array<int>(node, "opaque_ids");
// It is read in as actual ID values, but we have to convert to indices in
// mat/cell array
for (auto& x : opaque_ids_tmp)
x = color_by_ == PlotColorBy::mats ? model::material_map[x]
: model::cell_map[x];
opaque_ids_.insert(opaque_ids_tmp.begin(), opaque_ids_tmp.end());
}
}
void SolidRayTracePlot::set_light_position(pugi::xml_node node)
{
if (check_for_node(node, "light_position")) {
auto light_pos_tmp = get_node_array<double>(node, "light_position");
if (light_pos_tmp.size() != 3)
fatal_error("Light position must be given as 3D coordinates");
light_location_.x = light_pos_tmp[0];
light_location_.y = light_pos_tmp[1];
light_location_.z = light_pos_tmp[2];
} else {
light_location_ = camera_position();
}
}
void SolidRayTracePlot::set_diffuse_fraction(pugi::xml_node node)
{
if (check_for_node(node, "diffuse_fraction")) {
diffuse_fraction_ = std::stod(get_node_value(node, "diffuse_fraction"));
if (diffuse_fraction_ < 0.0 || diffuse_fraction_ > 1.0) {
fatal_error("Must have 0 <= diffuse fraction <= 1");
}
}
}
void Ray::compute_distance()
{
boundary() = distance_to_boundary(*this);
}
void Ray::trace()
{
// To trace the ray from its origin all the way through the model, we have
// to proceed in two phases. In the first, the ray may or may not be found
// inside the model. If the ray is already in the model, phase one can be
// skipped. Otherwise, the ray has to be advanced to the boundary of the
// model where all the cells are defined. Importantly, this is assuming that
// the model is convex, which is a very reasonable assumption for any
// radiation transport model.
//
// After phase one is done, we can starting tracing from cell to cell within
// the model. This step can use neighbor lists to accelerate the ray tracing.
// Attempt to initialize the particle. We may have to enter a loop to move
// it up to the edge of the model.
bool inside_cell = exhaustive_find_cell(*this, settings::verbosity >= 10);
// Advance to the boundary of the model
while (!inside_cell) {
advance_to_boundary_from_void();
inside_cell = exhaustive_find_cell(*this, settings::verbosity >= 10);
// If true this means no surface was intersected. See cell.cpp and search
// for numeric_limits to see where we return it.
if (surface() == std::numeric_limits<int>::max()) {
warning(fmt::format("Lost a ray, r = {}, u = {}", r(), u()));
return;
}
// Exit this loop and enter into cell-to-cell ray tracing (which uses
// neighbor lists)
if (inside_cell)
break;
// if there is no intersection with the model, we're done
if (boundary().surface == SURFACE_NONE)
return;
event_counter_++;
if (event_counter_ > MAX_INTERSECTIONS) {
warning("Likely infinite loop in ray traced plot");
return;
}
}
// Call the specialized logic for this type of ray. This is for the
// intersection for the first intersection if we had one.
if (boundary().surface != SURFACE_NONE) {
// set the geometry state's surface attribute to be used for
// surface normal computation
surface() = boundary().surface;
on_intersection();
if (stop_)
return;
}
// reset surface attribute to zero after the first intersection so that it
// doesn't perturb surface crossing logic from here on out
surface() = 0;
// This is the ray tracing loop within the model. It exits after exiting
// the model, which is equivalent to assuming that the model is convex.
// It would be nice to factor out the on_intersection at the end of this
// loop and then do "while (inside_cell)", but we can't guarantee it's
// on a surface in that case. There might be some other way to set it
// up that is perhaps a little more elegant, but this is what works just
// fine.
while (true) {
compute_distance();
// There are no more intersections to process
// if we hit the edge of the model, so stop
// the particle in that case. Also, just exit
// if a negative distance was somehow computed.
if (boundary().distance == INFTY || boundary().distance == INFINITY ||
boundary().distance < 0) {
return;
}
// See below comment where call_on_intersection is checked in an
// if statement for an explanation of this.
bool call_on_intersection {true};
if (boundary().distance < 10 * TINY_BIT) {
call_on_intersection = false;
}
// DAGMC surfaces expect us to go a little bit further than the advance
// distance to properly check cell inclusion.
boundary().distance += TINY_BIT;
// Advance particle, prepare for next intersection
for (int lev = 0; lev < n_coord(); ++lev) {
coord(lev).r += boundary().distance * coord(lev).u;
}
surface() = boundary().surface;
n_coord_last() = n_coord();
n_coord() = boundary().coord_level;
if (boundary().lattice_translation[0] != 0 ||
boundary().lattice_translation[1] != 0 ||
boundary().lattice_translation[2] != 0) {
cross_lattice(*this, boundary(), settings::verbosity >= 10);
}
// Record how far the ray has traveled
traversal_distance_ += boundary().distance;
inside_cell = neighbor_list_find_cell(*this, settings::verbosity >= 10);
// Call the specialized logic for this type of ray. Note that we do not
// call this if the advance distance is very small. Unfortunately, it seems
// darn near impossible to get the particle advanced to the model boundary
// and through it without sometimes accidentally calling on_intersection
// twice. This incorrectly shades the region as occluded when it might not
// actually be. By screening out intersection distances smaller than a
// threshold 10x larger than the scoot distance used to advance up to the
// model boundary, we can avoid that situation.
if (call_on_intersection) {
on_intersection();
if (stop_)
return;
}
if (!inside_cell)
return;
event_counter_++;
if (event_counter_ > MAX_INTERSECTIONS) {
warning("Likely infinite loop in ray traced plot");
return;
}
}
}
void ProjectionRay::on_intersection()
{
// This records a tuple with the following info
//
// 1) ID (material or cell depending on color_by_)
// 2) Distance traveled by the ray through that ID
// 3) Index of the intersected surface (starting from 1)
line_segments_.emplace_back(
plot_.color_by_ == PlottableInterface::PlotColorBy::mats
? material()
: lowest_coord().cell,
traversal_distance_, boundary().surface_index());
}
void PhongRay::on_intersection()
{
// Check if we hit an opaque material or cell
int hit_id = plot_.color_by_ == PlottableInterface::PlotColorBy::mats
? material()
: lowest_coord().cell;
// If we are reflected and have advanced beyond the camera,
// the ray is done. This is checked here because we should
// kill the ray even if the material is not opaque.
if (reflected_ && (r() - plot_.camera_position()).dot(u()) >= 0.0) {
stop();
return;
}
// Anything that's not opaque has zero impact on the plot.
if (plot_.opaque_ids_.find(hit_id) == plot_.opaque_ids_.end())
return;
if (!reflected_) {
// reflect the particle and set the color to be colored by
// the normal or the diffuse lighting contribution
reflected_ = true;
result_color_ = plot_.colors_[hit_id];
Direction to_light = plot_.light_location_ - r();
to_light /= to_light.norm();
// TODO
// Not sure what can cause a surface token to be invalid here, although it
// sometimes happens for a few pixels. It's very very rare, so proceed by
// coloring the pixel with the overlap color. It seems to happen only for a
// few pixels on the outer boundary of a hex lattice.
//
// We cannot detect it in the outer loop, and it only matters here, so
// that's why the error handling is a little different than for a lost
// ray.
if (surface() == 0) {
result_color_ = plot_.overlap_color_;
stop();
return;
}
// Get surface pointer
const auto& surf = model::surfaces.at(surface_index());
Direction normal = surf->normal(r_local());
normal /= normal.norm();
// Need to apply translations to find the normal vector in
// the base level universe's coordinate system.
for (int lev = n_coord() - 2; lev >= 0; --lev) {
if (coord(lev + 1).rotated) {
const Cell& c {*model::cells[coord(lev).cell]};
normal = normal.inverse_rotate(c.rotation_);
}
}
// use the normal opposed to the ray direction
if (normal.dot(u()) > 0.0) {
normal *= -1.0;
}
// Facing away from the light means no lighting
double dotprod = normal.dot(to_light);
dotprod = std::max(0.0, dotprod);
double modulation =
plot_.diffuse_fraction_ + (1.0 - plot_.diffuse_fraction_) * dotprod;
result_color_ *= modulation;
// Now point the particle to the camera. We now begin
// checking to see if it's occluded by another surface
u() = to_light;
orig_hit_id_ = hit_id;
// OpenMC native CSG and DAGMC surfaces have some slight differences
// in how they interpret particles that are sitting on a surface.
// I don't know exactly why, but this makes everything work beautifully.
if (surf->geom_type() == GeometryType::DAG) {
surface() = 0;
} else {
surface() = -surface(); // go to other side
}
// Must fully restart coordinate search. Why? Not sure.
clear();
// Note this could likely be faster if we cached the previous
// cell we were in before the reflection. This is the easiest
// way to fully initialize all the sub-universe coordinates and
// directions though.
bool found = exhaustive_find_cell(*this);
if (!found) {
fatal_error("Lost particle after reflection.");
}
// Must recalculate distance to boundary due to the
// direction change
compute_distance();
} else {
// If it's not facing the light, we color with the diffuse contribution, so
// next we check if we're going to occlude the last reflected surface. if
// so, color by the diffuse contribution instead
if (orig_hit_id_ == -1)
fatal_error("somehow a ray got reflected but not original ID set?");
result_color_ = plot_.colors_[orig_hit_id_];
result_color_ *= plot_.diffuse_fraction_;
stop();
}
}
extern "C" int openmc_id_map(const void* plot, int32_t* data_out)
{