/* SPDX-FileCopyrightText: Copyright (c) 2016 - 2026, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #ifndef GEOMETRY_TEMPLATES_HH #define GEOMETRY_TEMPLATES_HH #include #include #include #include #include #include #include #include #include #include namespace adamantine { namespace { #if ARBORX_VERSION_MAJOR >= 2 using Point = ArborX::Point<3, double>; using Triangle = ArborX::Triangle<3, double>; #else using Point = ArborX::ExperimentalHyperGeometry::Point<3, double>; using Triangle = ArborX::ExperimentalHyperGeometry::Triangle<3, double>; #endif Point read_point(char const *facet, double const unit_scaling) { Point vertex; float value = 0; std::memcpy(&value, facet, sizeof value); vertex[0] = static_cast(value) * unit_scaling; std::memcpy(&value, facet + 4, sizeof value); vertex[1] = static_cast(value) * unit_scaling; std::memcpy(&value, facet + 8, sizeof value); vertex[2] = static_cast(value) * unit_scaling; return vertex; } #if ARBORX_VERSION_MAJOR >= 2 struct InsideCallBack { template KOKKOS_FUNCTION void operator()(const Predicate &predicate, const Value &value, const OutputFunctor &out) const { auto p = ArborX::getGeometry(predicate); #if ARBORX_VERSION_MINOR * 100 + ARBORX_VERSION_PATCH >= 99 auto a = ArborX::Experimental::closestPoint(p, value); #else auto a = ArborX::Details::Dispatch::distance< ArborX::GeometryTraits::PointTag, ArborX::GeometryTraits::TriangleTag, decltype(p), Value>::closest_point(p, value.a, value.b, value.c); #endif auto pa = a - p; auto e1 = value.b - value.a; auto e2 = value.c - value.a; double x = e1[1] * e2[2] - e2[1] * e1[2]; double y = -(e1[0] * e2[2] - e2[0] * e1[2]); double z = e1[0] * e2[1] - e2[0] * e1[1]; bool v = pa[0] * x + pa[1] * y + pa[2] * z > 0.; out(v); } }; #endif } // namespace template Geometry::Geometry( MPI_Comm const &communicator, boost::property_tree::ptree const &database, boost::optional const &units_optional_database) : _triangulation(communicator) { // PropertyTreeInput geometry.import_mesh bool import_mesh = database.get("import_mesh"); auto stl_filename = database.get_optional("stl_filename"); if (stl_filename) { _use_stl = true; std::string const stl_unit = units_optional_database ? units_optional_database.get().get("mesh", "meter") : "meter"; read_stl(*stl_filename, g_unit_scaling_factor[stl_unit]); #if ARBORX_VERSION_MAJOR >= 2 _bvh = std::make_unique>( Kokkos::DefaultHostExecutionSpace{}, _stl_triangles); #endif } if (import_mesh == true) { // PropertyTreeInput geometry.mesh_file std::string mesh_file = database.get("mesh_file"); // PropertyTreeInput geometry.mesh_format std::string mesh_format = database.get("mesh_format"); dealii::Triangulation serial_triangulation; dealii::GridIn grid_in; grid_in.attach_triangulation(serial_triangulation); typename dealii::GridIn::Format grid_in_format; if (mesh_format == "abaqus") { grid_in_format = dealii::GridIn::Format::abaqus; } else if (mesh_format == "assimp") { grid_in_format = dealii::GridIn::Format::assimp; } else if (mesh_format == "unv") { grid_in_format = dealii::GridIn::Format::unv; } else if (mesh_format == "ucd") { grid_in_format = dealii::GridIn::Format::ucd; } else if (mesh_format == "dbmesh") { grid_in_format = dealii::GridIn::Format::dbmesh; } else if (mesh_format == "gmsh") { grid_in_format = dealii::GridIn::Format::msh; } else if (mesh_format == "tecplot") { grid_in_format = dealii::GridIn::Format::tecplot; } else if (mesh_format == "xda") { grid_in_format = dealii::GridIn::Format::xda; } else if (mesh_format == "vtk") { grid_in_format = dealii::GridIn::Format::vtk; } else if (mesh_format == "vtu") { grid_in_format = dealii::GridIn::Format::vtu; } else if (mesh_format == "exodusii") { grid_in_format = dealii::GridIn::Format::exodusii; } else { grid_in_format = dealii::GridIn::Format::Default; } grid_in.read(mesh_file, grid_in_format); _triangulation.copy_triangulation(serial_triangulation); // Apply user-specified scaling to the mesh // PropertyTreeInput units.mesh std::string const mesh_unit = units_optional_database ? units_optional_database.get().get("mesh", "meter") : "meter"; double mesh_scaling = g_unit_scaling_factor[mesh_unit]; dealii::GridTools::scale(mesh_scaling, _triangulation); // Set the mesh material id to 0 if specified in the input // PropertyTreeInput geometry.reset_material_id auto reset_material_id = database.get("reset_material_id", false); if (reset_material_id) { for (auto cell : _triangulation.active_cell_iterators()) { cell->set_material_id(0); } } } else { std::vector repetitions(dim); // PropertyTreeInput geometry.length_divisions repetitions[axis::x] = database.get("length_divisions", 10); // PropertyTreeInput geometry.height_divisions repetitions[axis::z] = database.get("height_divisions", 10); // PropertyTreeInput geometry.width_divisions if (dim == 3) repetitions[axis::y] = database.get("width_divisions", 10); dealii::Point p1; dealii::Point p2; if (stl_filename) { double x_min = std::numeric_limits::max(); double x_max = -std::numeric_limits::max(); double y_min = std::numeric_limits::max(); double y_max = -std::numeric_limits::max(); double z_min = std::numeric_limits::max(); double z_max = -std::numeric_limits::max(); for (unsigned int i = 0; i < _stl_triangles.extent(0); ++i) { auto t = _stl_triangles(i); x_min = std::min({x_min, t.a[0], t.b[0], t.c[0]}); x_max = std::max({x_max, t.a[0], t.b[0], t.c[0]}); y_min = std::min({y_min, t.a[1], t.b[1], t.c[1]}); y_max = std::max({y_max, t.a[1], t.b[1], t.c[1]}); z_min = std::min({z_min, t.a[2], t.b[2], t.c[2]}); z_max = std::max({z_max, t.a[2], t.b[2], t.c[2]}); } p1[axis::x] = x_min; p2[axis::x] = x_max; p1[axis::z] = z_min; p2[axis::z] = z_max; // STL files always contain 3D data but the code is instantiated for dim = // 2 and dim = 3. if constexpr (dim == 3) { p1[axis::y] = y_min; p2[axis::y] = y_max; } } else { // PropertyTreeInput geometry.length p2[axis::x] = database.get("length"); // PropertyTreeInput geometry.length_origin p1[axis::x] = database.get("length_origin", 0.0); // PropertyTreeInput geometry.height p2[axis::z] = database.get("height"); // PropertyTreeInput geometry.height_origin p1[axis::z] = database.get("height_origin", 0.0); if (dim == 3) { // PropertyTreeInput geometry.width p2[axis::y] = database.get("width"); // PropertyTreeInput geometry.width_origin p1[axis::y] = database.get("width_origin", 0.0); } p2 = p2 + p1; } // For now we assume that the geometry is very simple. dealii::GridGenerator::subdivided_hyper_rectangle( _triangulation, repetitions, p1, p2, true); // Assign the MaterialID. for (auto cell : _triangulation.active_cell_iterators()) { cell->set_material_id(0); } } assign_material_state(database); } #if ARBORX_VERSION_MAJOR >= 2 template bool Geometry::is_within_stl( typename dealii::DoFHandler::active_cell_iterator const &cell) const { if constexpr (dim == 2) { return false; } else { ASSERT(_bvh, "BVH not initialized."); // First we need to filter out the cells that intersects the triangles. The // center of the cells may be within the STL shape which will create a false // positive. Kokkos::DefaultHostExecutionSpace space; Kokkos::View offset("offset", 0); Kokkos::View intersected_triangles( "intersected_triangles", 0); Kokkos::View[1], Kokkos::HostSpace> bounding_box( "bounding_box"); auto const boundary_points = cell->bounding_box().get_boundary_points(); auto const &point_a = boundary_points.first; auto const &point_b = boundary_points.second; bounding_box[0] = ArborX::Box<3, double>(Point({{point_a[0], point_a[1], point_a[2]}}), Point({{point_b[0], point_b[1], point_b[2]}})); _bvh->query(space, ArborX::Experimental::make_intersects(bounding_box), intersected_triangles, offset); if (intersected_triangles.extent(0) > 0) { return false; } // Now that the cells that intersect the STL have been filtered out, we // check the cells that the cells are within the STL. A cell is within the // STL, if the dot product between the normal of the closest triangle and // the vector from the cell center to the closest triangle is positive. Kokkos::View cell_center("cell_center"); cell_center(0) = { {cell->center()[0], cell->center()[1], cell->center()[2]}}; Kokkos::View inside("inside"); _bvh->query(space, ArborX::Experimental::make_nearest(cell_center, 1), InsideCallBack{}, inside, offset); return inside(0); } } #endif template void Geometry::assign_material_state( boost::property_tree::ptree const &database) { // PropertyTreeInput geometry.material_height double const material_height = database.get("material_height", 1e9); // PropertyTreeInput geometry.use_powder bool const use_powder = database.get("use_powder", false); if (use_powder) { // PropertyTreeInput geometry.powder_layer double const powder_layer = database.get("powder_layer"); double const solid_height = material_height - powder_layer; for (auto cell : _triangulation.active_cell_iterators()) { if (cell->center()[axis::z] < solid_height) { cell->set_user_index(static_cast(SolidLiquidPowder::State::solid)); } else { cell->set_user_index( static_cast(SolidLiquidPowder::State::powder)); } } } else { // Everything is made of solid material for (auto cell : _triangulation.active_cell_iterators()) { cell->set_user_index(static_cast(SolidLiquidPowder::State::solid)); } } } template void Geometry::read_stl(std::string const &filename, double const stl_scaling) { std::ifstream file(filename.c_str(), std::ios::binary); ASSERT_THROW(file.good(), "Unable to open STL file: " + filename); // Read 80 byte header char header_info[80] = ""; file.read(header_info, 80); // Read the number of triangles unsigned int n_triangles = 0; { char n_tri[4]; file.read(n_tri, 4); std::memcpy(&n_triangles, n_tri, sizeof n_triangles); } _stl_triangles = Kokkos::View( Kokkos::view_alloc(Kokkos::WithoutInitializing, "stl_triangles"), n_triangles); for (unsigned int i = 0; i < n_triangles; ++i) { char facet[50]; // Read one 50-byte triangle file.read(facet, 50); // Check that we really read 50-byte ASSERT_THROW(file.gcount() == 50, "Error reading triangle " + std::to_string(i) + " from STL file: " + filename); // Populate each point of the triangle // facet + 12 skips the triangle's unit normal auto p1 = read_point(facet + 12, stl_scaling); auto p2 = read_point(facet + 24, stl_scaling); auto p3 = read_point(facet + 36, stl_scaling); // Add a new triangle to the View _stl_triangles(i) = {p1, p2, p3}; } file.close(); } } // namespace adamantine INSTANTIATE_DIM(Geometry) #endif