15#include <igl/boundary_facets.h>
27 const json &geometry_selection,
28 const std::string &root_path)
30 if (geometry_selection.is_object() && geometry_selection.contains(
"same_as_volume"))
32 if (!geometry_selection[
"same_as_volume"].get<bool>())
35 std::vector<int> geometry_ids(mesh.
n_elements());
46 Eigen::MatrixXd barycenters;
48 std::vector<int> geometry_ids(mesh.
n_elements(), 0);
52 for (
const auto &selection : selections)
54 if (selection->inside(e, vertices, barycenters.row(e)))
56 geometry_ids[e] = selection->id(e, vertices, barycenters.row(e));
67 const std::string &root_path,
68 const bool non_conforming)
73 if (j_mesh[
"extract"].get<std::string>() !=
"volume")
81 if (j_mesh[
"advanced"][
"normalize_mesh"])
87 mesh->bounding_box(bbox[0], bbox[1]);
89 const std::string unit = j_mesh[
"unit"];
90 double unit_scale = 1;
99 j_mesh[
"transformation"],
100 (bbox[1] - bbox[0]).cwiseAbs().transpose(),
102 mesh->apply_affine_transformation(A, b);
105 mesh->bounding_box(bbox[0], bbox[1]);
108 std::vector<std::shared_ptr<Selection>> surface_selections =
113 const int n_refs = j_mesh[
"n_refs"];
114 const double refinement_location = j_mesh[
"advanced"][
"refinement_location"];
125 if (mesh->has_boundary_ids() && surface_selections.empty())
126 log_and_throw_error(
"Unable to refine a mesh with stored surface selections; provide an explicit surface_selection to recompute them after refinement.");
129 assert(mesh->n_elements() > 0);
130 const int uniform_value = mesh->get_body_id(0);
131 for (
int i = 1; i < mesh->n_elements(); ++i)
132 if (mesh->get_body_id(i) != uniform_value)
133 log_and_throw_error(
"Unable to apply stored nonuniform volume_selection because n_refs={} > 0!", n_refs);
135 logger().info(
"Performing global h-refinement with {} refinements", n_refs);
136 mesh->refine(n_refs, refinement_location);
137 mesh->set_body_ids(std::vector<int>(mesh->n_elements(), uniform_value));
142 if (j_mesh[
"advanced"][
"min_component"].get<int>() != -1)
174 if (j_mesh[
"advanced"][
"force_linear_geometry"].get<bool>())
188 const std::vector<std::shared_ptr<Selection>> node_selections =
191 if (!node_selections.empty())
193 bool boundary_only =
true;
194 for (
const auto &selection : node_selections)
196 if (!selection->boundary_only())
198 boundary_only =
false;
203 mesh->compute_node_ids([&](
const size_t n_id,
const RowVectorNd &p,
bool is_boundary) {
204 if (boundary_only && !is_boundary)
207 const std::vector<int> tmp = {int(n_id)};
208 for (
const auto &selection : node_selections)
210 if (selection->boundary_only() && !is_boundary)
213 if (selection->inside(n_id, tmp, p))
214 return selection->id(n_id, tmp, p);
216 return std::numeric_limits<int>::max();
220 if (!j_mesh[
"curve_selection"].is_null())
225 if (!surface_selections.empty())
227 bool boundary_only =
true;
228 for (
const auto &selection : surface_selections)
230 if (!selection->boundary_only())
232 boundary_only =
false;
237 mesh->compute_boundary_ids([&](
const size_t p_id,
const std::vector<int> &vs,
const RowVectorNd &p,
bool is_boundary) {
238 if (boundary_only && !is_boundary)
241 for (
const auto &selection : surface_selections)
243 if (selection->boundary_only() && !is_boundary)
246 if (selection->inside(p_id, vs, p))
247 return selection->id(p_id, vs, p);
249 return std::numeric_limits<int>::max();
256 const json volume_selection = j_mesh[
"volume_selection"];
257 if (volume_selection.is_object()
258 && volume_selection.size() == 1
259 && volume_selection.contains(
"id_offset"))
261 const int id_offset = volume_selection[
"id_offset"].get<
int>();
264 const int n_body_ids = mesh->n_elements();
265 std::vector<int> body_ids(n_body_ids);
266 for (
int i = 0; i < n_body_ids; ++i)
267 body_ids[i] = mesh->get_body_id(i) + id_offset;
268 mesh->set_body_ids(body_ids);
274 std::vector<std::shared_ptr<Selection>> volume_selections =
278 if (mesh->has_body_ids())
279 volume_selections.push_back(std::make_shared<SpecifiedSelection>(mesh->get_body_ids()));
281 mesh->compute_body_ids([&](
const size_t cell_id,
const std::vector<int> &vs,
const RowVectorNd &p) ->
int {
282 for (
const auto &selection : volume_selections)
285 if (selection->inside(cell_id, vs, p))
286 return selection->id(cell_id, vs, p);
306 const json &geometry,
307 const std::string &root_path,
308 const std::vector<std::string> &_names,
309 const std::vector<Eigen::MatrixXd> &_vertices,
310 const std::vector<Eigen::MatrixXi> &_cells,
311 const bool non_conforming)
331 assert(_names.empty());
332 assert(_vertices.empty());
333 assert(_cells.empty());
337 if (geometry.empty())
344 std::unique_ptr<Mesh> mesh =
nullptr;
346 for (
const json &geometry : geometries)
348 if (!geometry[
"enabled"].get<bool>() || geometry[
"is_obstacle"].get<
bool>())
351 if (geometry[
"type"] !=
"mesh" && geometry[
"type"] !=
"mesh_array")
354 const std::unique_ptr<Mesh> tmp_mesh =
read_fem_mesh(units, geometry, root_path, non_conforming);
357 mesh = tmp_mesh->copy();
359 mesh->append(tmp_mesh);
361 if (geometry[
"type"] ==
"mesh_array")
364 tmp_mesh->bounding_box(bbox[0], bbox[1]);
366 const long dim = tmp_mesh->dimension();
367 const bool is_offset_relative = geometry[
"array"][
"relative"];
368 const double offset = geometry[
"array"][
"offset"];
369 const VectorNd dimensions = (bbox[1] - bbox[0]);
370 const VectorNi size = geometry[
"array"][
"size"];
372 for (
int i = 0; i < size[0]; ++i)
374 for (
int j = 0; j < size[1]; ++j)
376 for (
int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
378 if (i == 0 && j == 0 && k == 0)
381 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(dim);
382 if (is_offset_relative)
383 translation.array() *= dimensions.array();
385 const std::unique_ptr<Mesh> copy_mesh = tmp_mesh->copy();
386 copy_mesh->apply_affine_transformation(MatrixNd::Identity(dim, dim), translation);
387 mesh->append(copy_mesh);
404 const std::string &root_path,
406 Eigen::MatrixXd &vertices,
407 Eigen::VectorXi &codim_vertices,
408 Eigen::MatrixXi &codim_edges,
409 Eigen::MatrixXi &
faces)
414 const std::string mesh_path =
resolve_path(j_mesh[
"mesh"], root_path);
417 mesh_path, vertices, codim_vertices, codim_edges,
faces);
421 throw std::runtime_error(fmt::format(
"Unable to read mesh: {}", mesh_path));
423 const int prev_dim = vertices.cols();
424 vertices.conservativeResize(vertices.rows(), dim);
426 vertices.rightCols(dim - prev_dim).setZero();
431 const std::string unit = j_mesh[
"unit"];
432 double unit_scale = 1;
436 const VectorNd mesh_dimensions = (vertices.colwise().maxCoeff() - vertices.colwise().minCoeff()).cwiseAbs();
440 vertices = vertices * A.transpose();
441 vertices.rowwise() += b.transpose();
444 std::string extract = j_mesh[
"extract"];
446 if (extract ==
"volume")
449 if (extract ==
"points")
452 codim_edges.resize(0, 0);
454 codim_vertices.resize(vertices.rows());
455 for (
int i = 0; i < codim_vertices.size(); ++i)
456 codim_vertices[i] = i;
458 else if (extract ==
"edges" &&
faces.size() != 0)
461 Eigen::MatrixXi edges;
462 igl::edges(
faces, edges);
464 codim_edges.conservativeResize(codim_edges.rows() + edges.rows(), 2);
465 codim_edges.bottomRows(edges.rows()) = edges;
467 else if (extract ==
"surface" && dim == 2 &&
faces.size() != 0)
470 Eigen::MatrixXi boundary_edges;
471 igl::boundary_facets(
faces, boundary_edges);
472 codim_edges.conservativeResize(codim_edges.rows() + boundary_edges.rows(), 2);
473 codim_edges.bottomRows(boundary_edges.rows()) = boundary_edges;
478 else if (extract ==
"volume")
484 if (j_mesh[
"n_refs"].get<int>() != 0)
486 if (
faces.size() != 0)
489 const int n_refs = j_mesh[
"n_refs"];
490 const double refinement_location = j_mesh[
"advanced"][
"refinement_location"];
491 for (
int i = 0; i < n_refs; i++)
493 const size_t n_vertices = vertices.rows();
494 const size_t n_edges = codim_edges.rows();
495 vertices.conservativeResize(n_vertices + n_edges, vertices.cols());
496 codim_edges.conservativeResize(2 * n_edges, codim_edges.cols());
497 for (
size_t ei = 0; ei < n_edges; ei++)
499 const int v0i = codim_edges(ei, 0);
500 const int v1i = codim_edges(ei, 1);
501 const int v2i = n_vertices + ei;
502 vertices.row(v2i) = (vertices.row(v1i) - vertices.row(v0i)) * refinement_location + vertices.row(v0i);
503 codim_edges.row(ei) << v0i, v2i;
504 codim_edges.row(n_edges + ei) << v2i, v1i;
514 const json &geometry,
515 const std::vector<json> &displacements,
516 const std::vector<json> &dirichlets,
517 const std::string &root_path,
519 const std::vector<std::string> &_names,
520 const std::vector<Eigen::MatrixXd> &_vertices,
521 const std::vector<Eigen::MatrixXi> &_cells,
522 const bool non_conforming)
542 assert(_names.empty());
543 assert(_vertices.empty());
544 assert(_cells.empty());
548 if (geometry.empty())
553 for (
const json &geometry : geometries)
556 if (!geometry[
"is_obstacle"].get<bool>())
559 if (!geometry[
"enabled"].get<bool>())
562 if (geometry[
"type"] ==
"mesh" || geometry[
"type"] ==
"mesh_array")
564 Eigen::MatrixXd vertices;
565 Eigen::VectorXi codim_vertices;
566 Eigen::MatrixXi codim_edges;
567 Eigen::MatrixXi
faces;
569 geometry, root_path, dim, vertices, codim_vertices,
572 if (geometry[
"type"] ==
"mesh_array")
574 const Selection::BBox bbox{{vertices.colwise().minCoeff(), vertices.colwise().maxCoeff()}};
576 const bool is_offset_relative = geometry[
"array"][
"relative"];
577 const double offset = geometry[
"array"][
"offset"];
578 const VectorNd dimensions = (bbox[1] - bbox[0]);
579 const VectorNi size = geometry[
"array"][
"size"];
581 const int N = size.head(dim).prod();
582 const int nV = vertices.rows(), nCV = codim_vertices.rows(), nCE = codim_edges.rows(), nF =
faces.rows();
584 vertices.conservativeResize(N * nV, Eigen::NoChange);
585 codim_vertices.conservativeResize(N * nCV, Eigen::NoChange);
586 codim_edges.conservativeResize(N * nCE, Eigen::NoChange);
587 faces.conservativeResize(N * nF, Eigen::NoChange);
589 for (
int i = 0; i < size[0]; ++i)
591 for (
int j = 0; j < size[1]; ++j)
593 for (
int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
595 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(vertices.cols());
596 if (is_offset_relative)
597 translation.array() *= dimensions.array();
599 int n = i * size[1] + j;
605 vertices.middleRows(n * nV, nV) = vertices.topRows(nV).rowwise() + translation;
607 codim_vertices.segment(n * nV, nV) = codim_vertices.head(nV).array() + n * nV;
609 codim_edges.middleRows(n * nCE, nCE) = codim_edges.topRows(nCE).array() + n * nV;
611 faces.middleRows(n * nF, nF) =
faces.topRows(nF).array() + n * nV;
617 json displacement =
"{\"value\":[0, 0, 0]}"_json;
620 if (!geometry[
"surface_selection"].is_number())
623 const int id = geometry[
"surface_selection"];
624 for (
const json &disp : dirichlets)
626 if ((disp[
"id"].is_string() && disp[
"id"].get<std::string>() ==
"all")
627 || (disp[
"id"].is_number_integer() && disp[
"id"].get<
int>() ==
id))
632 else if (disp[
"id"].is_array())
634 for (
const json &disp_id : disp[
"id"])
636 assert(disp_id.is_number_integer());
637 if (disp_id.get<
int>() ==
id)
645 for (
const json &disp : displacements)
647 if ((disp[
"id"].is_string() && disp[
"id"].get<std::string>() ==
"all")
648 || (disp[
"id"].is_number_integer() && disp[
"id"].get<
int>() ==
id))
653 else if (disp[
"id"].is_array())
655 for (
const json &disp_id : disp[
"id"])
657 assert(disp_id.is_number_integer());
658 if (disp_id.get<
int>() ==
id)
669 vertices, codim_vertices, codim_edges,
faces, displacement, root_path);
671 else if (geometry[
"type"] ==
"plane")
673 obstacle.
append_plane(geometry[
"point"], geometry[
"normal"]);
675 else if (geometry[
"type"] ==
"ground")
677 VectorNd gravity = VectorNd::Zero(dim);
679 const double height = geometry[
"height"];
680 assert(gravity.norm() != 0);
681 const VectorNd normal = -gravity.normalized();
685 else if (geometry[
"type"] ==
"mesh_sequence")
687 namespace fs = std::filesystem;
688 std::vector<fs::path> mesh_files;
689 if (geometry[
"mesh_sequence"].is_array())
691 mesh_files = geometry[
"mesh_sequence"].get<std::vector<fs::path>>();
695 assert(geometry[
"mesh_sequence"].is_string());
696 const fs::path meshes(
resolve_path(geometry[
"mesh_sequence"], root_path));
698 if (fs::is_directory(meshes))
700 for (
const auto &entry : std::filesystem::directory_iterator(meshes))
702 if (entry.is_regular_file())
703 mesh_files.push_back(entry.path());
708 mesh_files = glob::rglob(meshes.string());
711 std::sort(mesh_files.begin(), mesh_files.end(), [](
const fs::path &p1,
const fs::path &p2) {
712 return strnatcmp(p1.string().c_str(), p2.string().c_str()) < 0;
716 std::vector<Eigen::MatrixXd> vertices(mesh_files.size());
717 Eigen::VectorXi codim_vertices;
718 Eigen::MatrixXi codim_edges;
719 Eigen::MatrixXi
faces;
721 for (
int i = 0; i < mesh_files.size(); ++i)
723 json jmesh = geometry;
724 jmesh[
"mesh"] = mesh_files[i];
727 Eigen::VectorXi tmp_codim_vertices;
728 Eigen::MatrixXi tmp_codim_edges;
729 Eigen::MatrixXi tmp_faces;
731 jmesh, root_path, dim, vertices[i],
732 tmp_codim_vertices, tmp_codim_edges, tmp_faces);
735 codim_vertices = tmp_codim_vertices;
736 codim_edges = tmp_codim_edges;
741 assert((codim_vertices.array() == tmp_codim_vertices.array()).all());
742 assert((codim_edges.array() == tmp_codim_edges.array()).all());
743 assert((
faces.array() == tmp_faces.array()).all());
748 vertices, codim_vertices, codim_edges,
faces, geometry[
"fps"]);
763 const double unit_scale,
764 const json &transform,
769 const int dim = mesh_dimensions.size();
776 if (transform[
"dimensions"].is_array())
779 (mesh_dimensions.array() == 0).select(1, mesh_dimensions);
781 scale = transform[
"dimensions"];
782 const int scale_size = scale.size();
783 scale.conservativeResize(dim);
784 if (scale_size < dim)
785 scale.tail(dim - scale_size).setZero();
787 scale.array() /= modified_dimensions.array();
789 else if (transform[
"scale"].is_number())
791 scale.setConstant(dim, transform[
"scale"].get<double>());
795 assert(transform[
"scale"].is_array());
796 scale = transform[
"scale"];
797 const int scale_size = scale.size();
798 scale.conservativeResize(dim);
799 if (scale_size < dim)
800 scale.tail(dim - scale_size).setZero();
806 A = (unit_scale * scale).asDiagonal();
814 MatrixNd R = MatrixNd::Identity(dim, dim);
815 if (!transform[
"rotation"].is_null())
819 if (transform[
"rotation"].is_number())
820 R = Eigen::Rotation2Dd(
deg2rad(transform[
"rotation"].get<double>()))
822 else if (!transform[
"rotation"].is_array() || !transform[
"rotation"].empty())
837 b = transform[
"translation"];
838 const int translation_size = b.size();
839 b.conservativeResize(dim);
840 if (translation_size < dim)
841 b.tail(dim - translation_size).setZero();
std::vector< Eigen::VectorXi > faces
static double convert(const json &val, const std::string &unit_type)
const std::string & length() const
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
virtual int get_body_id(const int primitive) const
Get the volume selection of an element (cell in 3d, face in 2d)
virtual void compute_element_barycenters(Eigen::MatrixXd &barycenters) const =0
utility for 2d/3d.
virtual void bounding_box(RowVectorNd &min, RowVectorNd &max) const =0
computes the bbox of the mesh
void set_geometry_ids(const std::vector< int > &geometry_ids)
Set the geometry selection, one ID per element.
std::vector< int > element_vertices(const int el_id) const
list of vids of an element
static std::unique_ptr< Mesh > create(const std::string &path, const bool non_conforming=false)
factory to build the proper mesh
void append_mesh(const Eigen::MatrixXd &vertices, const Eigen::VectorXi &codim_vertices, const Eigen::MatrixXi &codim_edges, const Eigen::MatrixXi &faces, const json &displacement, const std::string &root_path)
void append_plane(const VectorNd &point, const VectorNd &normal)
void append_mesh_sequence(const std::vector< Eigen::MatrixXd > &vertices, const Eigen::VectorXi &codim_vertices, const Eigen::MatrixXi &codim_edges, const Eigen::MatrixXi &faces, const int fps)
void set_units(const Units &units)
std::array< RowVectorNd, 2 > BBox
static std::vector< std::shared_ptr< utils::Selection > > build_selections(const json &j_selections, const BBox &mesh_bbox, const std::string &root_path)
Build a vector of selection objects from a JSON selection(s).
void read_obstacle_mesh(const Units &units, const json &j_mesh, const std::string &root_path, const int dim, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &codim_edges, Eigen::MatrixXi &faces)
read a obstacle mesh from a geometry JSON
void construct_affine_transformation(const double unit_scale, const json &transform, const VectorNd &mesh_dimensions, MatrixNd &A, VectorNd &b)
Construct an affine transformation .
Obstacle read_obstacle_geometry(const Units &units, const json &geometry, const std::vector< json > &displacements, const std::vector< json > &dirichlets, const std::string &root_path, const int dim, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read a FEM mesh from a geometry JSON
std::unique_ptr< Mesh > read_fem_geometry(const Units &units, const json &geometry, const std::string &root_path, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read FEM meshes from a geometry JSON array (or single)
std::unique_ptr< Mesh > read_fem_mesh(const Units &units, const json &j_mesh, const std::string &root_path, const bool non_conforming)
read a FEM mesh from a geometry JSON
bool read_surface_mesh(const std::string &mesh_path, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &codim_edges, Eigen::MatrixXi &faces)
read a surface mesh
void apply_geometry_selection(Mesh &mesh, const json &geometry_selection, const std::string &root_path)
Apply a geometry selection to a FEM mesh.
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
Eigen::Matrix3d to_rotation_matrix(const json &jr, std::string mode)
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
bool is_param_valid(const json ¶ms, const std::string &key)
Determine if a key exists and is non-null in a json object.
spdlog::logger & logger()
Retrieves the current logger.
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 3, 1 > VectorNd
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor, 3, 3 > MatrixNd
Eigen::Matrix< int, Eigen::Dynamic, 1, 0, 3, 1 > VectorNi
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
void log_and_throw_error(const std::string &msg)