ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_EBIFBuildings.H
Go to the documentation of this file.
1 /**
2  * \file ERF_EBIFBuildings.H
3  * \brief Defines the implicit function wrapper for STL-based building geometry.
4  *
5  * This file provides a BuildingsIF class that wraps AMReX's STL infrastructure
6  * to enable 3D building geometry representation in ERF. Unlike the 2D height-based
7  * TerrainIF approach, this provides accurate representation of vertical walls and
8  * complex building structures.
9  */
10 
11 #ifndef ERF_BUILDINGS_IF_H_
12 #define ERF_BUILDINGS_IF_H_
13 
14 #include <AMReX_EB_STL_utils.H>
15 #include <AMReX_Geometry.H>
16 #include <AMReX_MultiFab.H>
17 #include <AMReX_FArrayBox.H>
18 #include <AMReX_BoxArray.H>
19 #include <AMReX_BoxList.H>
20 #include <AMReX_DistributionMapping.H>
21 
22 #include <string>
23 #include <memory>
24 #include <numeric> // for std::iota
25 
26 /**
27  * \brief Buildings implicit function backed by STL triangle mesh.
28  *
29  * This class provides an implicit function interface for building geometry
30  * loaded from STL files. It uses AMReX's STL infrastructure with BVH
31  * (Bounding Volume Hierarchy) acceleration for efficient signed distance queries.
32  *
33  * The operator evaluates signed distance at any 3D point, following the
34  * AMReX EB convention: positive = inside solid (building), zero = boundary,
35  * negative = fluid.
36  *
37  * Note: This class pre-samples the STL geometry onto a reference grid and
38  * interpolates at query points. For very large or complex geometries, consider
39  * using AMReX's IndexSpaceSTL directly instead.
40  */
42 {
43 public:
44  /**
45  * \brief Construct a buildings implicit function from an STL file.
46  *
47  * \param stl_file Path to the STL mesh file (binary or ASCII format).
48  * \param scale Uniform scaling factor applied to STL coordinates (typically 1.0).
49  * \param center Translation vector to recenter the mesh [m].
50  * \param reverse_normal Non-zero to flip triangle normals if needed.
51  * \param geom Domain geometry for grid setup and BVH optimization.
52  *
53  * The constructor:
54  * 1. Loads the STL file using AMReX::STLtools
55  * 2. Builds a BVH acceleration structure for fast distance queries
56  * 3. Pre-samples signed distances onto a reference grid
57  * 4. Stores interpolation data for operator() calls
58  */
59  BuildingsIF(std::string const& stl_file,
60  amrex::Real scale,
61  amrex::Array<amrex::Real, 3> const& center,
62  int reverse_normal,
63  amrex::Geometry const& geom)
64  : m_geom(geom)
65  {
66  using namespace amrex;
67 
68  // Load STL file with BVH optimization
69  // This broadcasts triangle data to all ranks internally
70  m_stl_tools.setBVHOptimization(true);
71  m_stl_tools.read_stl_file(stl_file, scale, center, reverse_normal);
72 
73  // Pre-sample signed distance field onto a reference grid
74  // This grid covers the entire domain with sufficient resolution
75  // to capture building features (match computational grid)
76  Box const& domain = geom.Domain();
77  IntVect ngrow_vec(2); // Ghost cells for interpolation
78 
79  Box sampled_box = domain;
80  sampled_box.grow(ngrow_vec);
81 
82  // Create distributed BoxArray and DistributionMapping for parallel computation
83  // Chunk the domain into reasonable-sized boxes for load balancing
84  BoxArray ba(sampled_box);
85  ba.maxSize(32);
86  DistributionMapping dm(ba);
87 
88  // Create distributed MultiFab to hold signed distance values
89  // Each rank will compute its portion in parallel
90  MultiFab mf_distributed(ba, dm, 1, ngrow_vec);
91 
92  // Compute signed distances in parallel across all ranks
93  // fillSignedDistance uses ParallelFor internally, distributing work
94  // across the MultiFab's boxes according to the DistributionMapping
95  m_stl_tools.fillSignedDistance(mf_distributed, ngrow_vec, geom);
96 
97  // Now create a replicated MultiFab where each rank has a copy of the full domain
98  // This is needed because operator() must access data locally (no MPI in GPU kernels)
99 
100  // Create BoxArray with one box per rank, all covering the same spatial region
101  int nprocs = ParallelDescriptor::NProcs();
102  Vector<Box> boxes(nprocs, sampled_box); // All boxes cover the full domain
103  BoxList bl;
104  for (const auto& bx : boxes) {
105  bl.push_back(bx);
106  }
107  BoxArray ba_replicated(std::move(bl));
108 
109  // Create DistributionMapping so each rank owns exactly one box
110  Vector<int> pmap(nprocs);
111  std::iota(pmap.begin(), pmap.end(), 0); // pmap = [0, 1, 2, ..., nprocs-1]
112  DistributionMapping dm_replicated(std::move(pmap));
113 
114  // Each rank will own one box covering the full domain
115  m_levelset_mf = std::make_shared<MultiFab>(ba_replicated, dm_replicated, 1, 0);
116 
117  // ParallelCopy from distributed to replicated
118  // Source: mf_distributed (each rank has different spatial portions)
119  // Dest: m_levelset_mf (each rank will receive the full domain)
120  IntVect dst_nghost(0);
121  m_levelset_mf->ParallelCopy(mf_distributed, 0, 0, 1, ngrow_vec, dst_nghost);
122 
123  // Store bounds for interpolation
124  m_data_box = sampled_box;
125 
126  // Cache geometry data for GPU-compatible access
127  const Real* problo_ptr = geom.ProbLo();
128  const Real* dx_ptr = geom.CellSize();
129  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
130  m_problo[d] = problo_ptr[d];
131  m_dx[d] = dx_ptr[d];
132  }
133 
134  // Set up local array view for operator() access
135  // Each rank iterates its own local box (which contains the full domain)
136  for (MFIter mfi(*m_levelset_mf); mfi.isValid(); ++mfi) {
137  m_local_array = (*m_levelset_mf)[mfi].const_array();
138  break; // Only one box per rank
139  }
140  }
141 
142  /**
143  * \brief Evaluate signed distance at a point.
144  *
145  * \param x Physical x-coordinate [m]
146  * \param y Physical y-coordinate [m]
147  * \param z Physical z-coordinate [m]
148  * \return Signed distance value: >0 inside building, <0 in fluid, =0 at boundary.
149  *
150  * This operator performs trilinear interpolation of the pre-sampled signed
151  * distance field. For points outside the sampled domain, it returns a
152  * negative value (fluid region).
153  *
154  * Note: The signed distance field is computed in parallel across ranks
155  * during construction, then replicated for fast operator() access.
156  */
157  AMREX_GPU_HOST_DEVICE inline
159  amrex::Real y,
160  amrex::Real z)) const noexcept
161  {
162  using namespace amrex;
163 
164  // Convert physical coordinates to cell indices (floating point)
165  Real i_real = (x - m_problo[0]) / m_dx[0];
166  Real j_real = (y - m_problo[1]) / m_dx[1];
167  Real k_real = (z - m_problo[2]) / m_dx[2];
168 
169  // Get integer cell indices
170  int i = static_cast<int>(std::floor(i_real));
171  int j = static_cast<int>(std::floor(j_real));
172  int k = static_cast<int>(std::floor(k_real));
173 
174  // Check if point is within data bounds
175  if (!m_data_box.contains(IntVect(AMREX_D_DECL(i, j, k))) ||
176  !m_data_box.contains(IntVect(AMREX_D_DECL(i + 1, j + 1, k + 1)))) {
177  // Outside sampled region - assume fluid (negative distance)
178  return -1.0;
179  }
180 
181  // Trilinear interpolation weights
182  Real wx = i_real - static_cast<Real>(i);
183  Real wy = j_real - static_cast<Real>(j);
184  Real wz = k_real - static_cast<Real>(k);
185 
186  // Access replicated levelset data
187  auto const& arr = m_local_array;
188 
189  // Trilinear interpolation
190  Real phi_000 = arr(i, j, k);
191  Real phi_100 = arr(i + 1, j, k);
192  Real phi_010 = arr(i, j + 1, k);
193  Real phi_110 = arr(i + 1, j + 1, k);
194  Real phi_001 = arr(i, j, k + 1);
195  Real phi_101 = arr(i + 1, j, k + 1);
196  Real phi_011 = arr(i, j + 1, k + 1);
197  Real phi_111 = arr(i + 1, j + 1, k + 1);
198 
199  Real phi_00 = phi_000 * (1.0 - wx) + phi_100 * wx;
200  Real phi_01 = phi_001 * (1.0 - wx) + phi_101 * wx;
201  Real phi_10 = phi_010 * (1.0 - wx) + phi_110 * wx;
202  Real phi_11 = phi_011 * (1.0 - wx) + phi_111 * wx;
203 
204  Real phi_0 = phi_00 * (1.0 - wy) + phi_10 * wy;
205  Real phi_1 = phi_01 * (1.0 - wy) + phi_11 * wy;
206 
207  Real phi = phi_0 * (1.0 - wz) + phi_1 * wz;
208 
209  return phi;
210  }
211 
212  /**
213  * \brief Evaluate signed distance at a point array.
214  *
215  * \param p Physical coordinate array [x, y, z] in meters.
216  * \return Signed distance value.
217  */
218  AMREX_GPU_HOST_DEVICE inline
219  amrex::Real operator()(const amrex::RealArray& p) const noexcept
220  {
221  return this->operator()(AMREX_D_DECL(p[0], p[1], p[2]));
222  }
223 
224 protected:
225  //! Domain geometry (for coordinate transformations)
226  amrex::Geometry m_geom;
227 
228  //! STL tools object (for loading and processing STL files)
229  amrex::STLtools m_stl_tools;
230 
231  //! Pre-sampled signed distance field (distributed MultiFab for parallel computation)
232  std::shared_ptr<amrex::MultiFab> m_levelset_mf;
233 
234  //! Bounding box of sampled data
235  amrex::Box m_data_box;
236 
237  //! Cached geometry data for GPU-compatible access
238  amrex::GpuArray<amrex::Real, AMREX_SPACEDIM> m_problo;
239  amrex::GpuArray<amrex::Real, AMREX_SPACEDIM> m_dx;
240 
241  //! Local array view for GPU-compatible operator() access
242  mutable amrex::Array4<const amrex::Real> m_local_array;
243 };
244 
245 #endif // ERF_BUILDINGS_IF_H_
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Buildings implicit function backed by STL triangle mesh.
Definition: ERF_EBIFBuildings.H:42
AMREX_GPU_HOST_DEVICE amrex::Real operator()(const amrex::RealArray &p) const noexcept
Evaluate signed distance at a point array.
Definition: ERF_EBIFBuildings.H:219
amrex::Box m_data_box
Bounding box of sampled data.
Definition: ERF_EBIFBuildings.H:235
amrex::Array4< const amrex::Real > m_local_array
Local array view for GPU-compatible operator() access.
Definition: ERF_EBIFBuildings.H:242
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > m_problo
Cached geometry data for GPU-compatible access.
Definition: ERF_EBIFBuildings.H:238
BuildingsIF(std::string const &stl_file, amrex::Real scale, amrex::Array< amrex::Real, 3 > const &center, int reverse_normal, amrex::Geometry const &geom)
Construct a buildings implicit function from an STL file.
Definition: ERF_EBIFBuildings.H:59
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > m_dx
Definition: ERF_EBIFBuildings.H:239
amrex::STLtools m_stl_tools
STL tools object (for loading and processing STL files)
Definition: ERF_EBIFBuildings.H:229
AMREX_GPU_HOST_DEVICE amrex::Real operator()(AMREX_D_DECL(amrex::Real x, amrex::Real y, amrex::Real z)) const noexcept
Evaluate signed distance at a point.
Definition: ERF_EBIFBuildings.H:158
amrex::Geometry m_geom
Domain geometry (for coordinate transformations)
Definition: ERF_EBIFBuildings.H:226
std::shared_ptr< amrex::MultiFab > m_levelset_mf
Pre-sampled signed distance field (distributed MultiFab for parallel computation)
Definition: ERF_EBIFBuildings.H:232
@ p
Definition: ERF_WSM6.H:280
Definition: ERF_ConsoleIO.cpp:15