diff --git a/include/openmc/plot.h b/include/openmc/plot.h index af65fb7c3..d9bea700d 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -290,6 +290,24 @@ private: void set_pixels(pugi::xml_node node); void set_opacities(pugi::xml_node node); + /* Used for drawing wireframe and colors. We record the list of + * surface/cell/material intersections and the corresponding lengths as a ray + * traverses the geometry, then color by iterating in reverse. + */ + struct TrackSegment { + int id; // material or cell ID (which is being colored) + double length; // length of this track intersection + + /* Recording this allows us to draw edges on the wireframe. For instance + * if two surfaces bound a single cell, it allows drawing that sharp edge + * where the surfaces intersect. + */ + int surface; // last surface ID intersected in this segment + TrackSegment(int id_a, double length_a, int surface_a) + : id(id_a), length(length_a), surface(surface_a) + {} + }; + std::array pixels_; // pixel dimension of resulting image double horizontal_field_of_view_ {70.0}; // horiz. f.o.v. in degrees Position camera_position_; // where camera is @@ -301,10 +319,18 @@ private: std::vector xs_; // macro cross section values for cell volume rendering - // If starting the particle from outside the geometry, we have to - // find a distance to the boundary in a non-standard surface intersection - // check. It's an exhaustive search over surfaces in the top-level universe. - static bool advance_to_boundary_from_void(Particle& p); + /* If starting the particle from outside the geometry, we have to + * find a distance to the boundary in a non-standard surface intersection + * check. It's an exhaustive search over surfaces in the top-level universe. + */ + static int advance_to_boundary_from_void(Particle& p); + + /* Checks if a vector of two TrackSegments is equivalent. We define this + * to mean not having matching intersection lengths, but rather having + * a matching sequence of surface/cell/material intersections. + */ + static bool trackstack_equivalent(const std::vector& track1, + const std::vector& track2); }; //=============================================================================== diff --git a/src/plot.cpp b/src/plot.cpp index 2d5d039cb..372c5a25b 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1025,26 +1025,46 @@ ProjectionPlot::ProjectionPlot(pugi::xml_node node) : PlottableInterface(node) } // Advances to the next boundary from outside the geometry -bool ProjectionPlot::advance_to_boundary_from_void(Particle& p) +// Returns -1 if no intersection found, and the surface index +// if an intersection was found. +int ProjectionPlot::advance_to_boundary_from_void(Particle& 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) + if (dist.first < min_dist) { min_dist = dist.first; + intersected_surface = dist.second; + } } if (min_dist > 1e300) - return false; + 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 true; + return intersected_surface; } } +bool ProjectionPlot::trackstack_equivalent( + const std::vector& track1, + const std::vector& track2) +{ + if (track1.size() != track2.size()) + return false; + for (int i = 0; i < track1.size(); ++i) { + if (track1[i].id != track2[i].id || + track1[i].surface != track2[i].surface) { + return false; + } + } + return true; +} + void ProjectionPlot::create_output() const { // Get centerline vector for camera-to-model. We create vectors around this @@ -1085,12 +1105,6 @@ void ProjectionPlot::create_output() const s.u.z = 0.0; p.from_source(&s); - struct TrackSegment { - int id; // material or cell ID (which is being colored) - double length; // length of this track intersection - TrackSegment(int id_a, double length_a) : id(id_a), length(length_a) {} - }; - /* Holds all of the track segments for the current rendered line of pixels. * old_segments holds a copy of this_line_segments from the previous line. * By holding both we can check if the cell/material intersection stack @@ -1120,16 +1134,26 @@ void ProjectionPlot::create_output() const const int max_intersections = 1000000; this_line_segments[horiz].clear(); + 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 = 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[horiz].emplace_back(0, 0.0, first_surface); + first_inside_model = false; + } + hitsomething = true; intersection_found = true; auto dist = distance_to_boundary(p); this_line_segments[horiz].emplace_back( color_by_ == PlotColorBy::mats ? p.material() : p.coord(p.n_coord() - 1).cell, - dist.distance); + dist.distance, dist.surface_index); // Advance particle for (int lev = 0; lev < p.n_coord(); ++lev) { @@ -1145,7 +1169,8 @@ void ProjectionPlot::create_output() const } } else { - intersection_found = advance_to_boundary_from_void(p); + 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) @@ -1169,19 +1194,6 @@ void ProjectionPlot::create_output() const data(horiz, vert) = result; } - auto trackstack_equivalent = - [](const std::vector& track1, - const std::vector& track2) -> bool { - if (track1.size() != track2.size()) - return false; - for (int i = 0; i < track1.size(); ++i) { - if (track1[i].id != track2[i].id) { - return false; - } - } - return true; - }; - // Check to draw wireframe bool draw_wireframe = false; if (horiz > 0) {