PolyFEM
Loading...
Searching...
No Matches
GeometryLoader.cpp
Go to the documentation of this file.
1#include "GeometryLoader.hpp"
2
9
10#include <Eigen/Core>
11
12#include <igl/edges.h>
13#include <igl/boundary_facets.h>
14
15#include <strnatcmp.h>
16
17namespace polyfem::mesh
18{
19 using namespace polyfem::utils;
20
22 const json &geometry,
23 const std::vector<json> &obstacle_displacements,
24 const std::vector<json> &dirichlet_conditions,
25 const bool non_conforming) const
26 {
27 LoadedGeometry result;
28 result.fem = load_fem(geometry, non_conforming);
29 if (!result.fem)
30 log_and_throw_error("Configured geometry contains no enabled FEM mesh.");
31 result.obstacle = load_obstacles(
32 geometry, obstacle_displacements, dirichlet_conditions,
33 result.fem->dimension(), non_conforming);
34 return result;
35 }
36
38 Mesh &mesh,
39 const json &geometry_selection) const
40 {
41 if (geometry_selection.is_object() && geometry_selection.contains("same_as_volume"))
42 {
43 if (!geometry_selection["same_as_volume"].get<bool>())
44 return;
45
46 std::vector<int> geometry_ids(mesh.n_elements());
47 for (int e = 0; e < mesh.n_elements(); ++e)
48 geometry_ids[e] = mesh.get_body_id(e);
49 mesh.set_geometry_ids(geometry_ids);
50 return;
51 }
52
53 Selection::BBox bbox;
54 mesh.bounding_box(bbox[0], bbox[1]);
55 const auto selections = Selection::build_selections(geometry_selection, bbox, resources_);
56
57 Eigen::MatrixXd barycenters;
58 mesh.compute_element_barycenters(barycenters);
59 std::vector<int> geometry_ids(mesh.n_elements(), 0);
60 for (int e = 0; e < mesh.n_elements(); ++e)
61 {
62 const auto vertices = mesh.element_vertices(e);
63 for (const auto &selection : selections)
64 {
65 if (selection->inside(e, vertices, barycenters.row(e)))
66 {
67 geometry_ids[e] = selection->id(e, vertices, barycenters.row(e));
68 break;
69 }
70 }
71 }
72 mesh.set_geometry_ids(geometry_ids);
73 }
74
75 std::unique_ptr<Mesh> GeometryLoader::load_fem_entry(
76 const json &j_mesh,
77 const bool non_conforming) const
78 {
79 if (!is_param_valid(j_mesh, "mesh"))
80 log_and_throw_error("Mesh {} is mising a \"mesh\" field!", j_mesh);
81
82 if (j_mesh["extract"].get<std::string>() != "volume")
83 log_and_throw_error("Only volumetric elements are implemented for FEM meshes!");
84
85 std::unique_ptr<Mesh> mesh = MeshLoader(resources_).load_fem(j_mesh["mesh"].get<std::string>(), non_conforming);
86 // --------------------------------------------------------------------
87
88 // NOTE: Normaliziation is done before transformations are applied and/or any selection operators
89 if (j_mesh["advanced"]["normalize_mesh"])
90 mesh->normalize();
91
92 // --------------------------------------------------------------------
93
94 Selection::BBox bbox;
95 mesh->bounding_box(bbox[0], bbox[1]);
96
97 const std::string unit = j_mesh["unit"];
98 double unit_scale = 1;
99 if (!unit.empty())
100 unit_scale = Units::convert(1, unit, units_.length());
101
102 {
103 MatrixNd A;
104 VectorNd b;
106 unit_scale,
107 j_mesh["transformation"],
108 (bbox[1] - bbox[0]).cwiseAbs().transpose(),
109 A, b);
110 mesh->apply_affine_transformation(A, b);
111 }
112
113 mesh->bounding_box(bbox[0], bbox[1]);
114
115 // --------------------------------------------------------------------
116 std::vector<std::shared_ptr<Selection>> surface_selections =
117 is_param_valid(j_mesh, "surface_selection") ? Selection::build_selections(j_mesh["surface_selection"], bbox, resources_) : std::vector<std::shared_ptr<Selection>>();
118
119 // --------------------------------------------------------------------
120
121 const int n_refs = j_mesh["n_refs"];
122 const double refinement_location = j_mesh["advanced"]["refinement_location"];
123 // TODO: renable this
124 // if (n_refs <= 0 && args["poly_bases"] == "MFSHarmonic" && mesh->has_poly())
125 // {
126 // if (args["force_no_ref_for_harmonic"])
127 // logger().warn("Using harmonic bases without refinement");
128 // else
129 // n_refs = 1;
130 // }
131 if (n_refs > 0)
132 {
133 if (mesh->has_boundary_ids() && surface_selections.empty())
134 log_and_throw_error("Unable to refine a mesh with stored surface selections; provide an explicit surface_selection to recompute them after refinement.");
135
136 // Check if the stored volume selection is uniform.
137 assert(mesh->n_elements() > 0);
138 const int uniform_value = mesh->get_body_id(0);
139 for (int i = 1; i < mesh->n_elements(); ++i)
140 if (mesh->get_body_id(i) != uniform_value)
141 log_and_throw_error("Unable to apply stored nonuniform volume_selection because n_refs={} > 0!", n_refs);
142
143 logger().info("Performing global h-refinement with {} refinements", n_refs);
144 mesh->refine(n_refs, refinement_location);
145 mesh->set_body_ids(std::vector<int>(mesh->n_elements(), uniform_value));
146 }
147
148 // --------------------------------------------------------------------
149
150 if (j_mesh["advanced"]["min_component"].get<int>() != -1)
151 log_and_throw_error("Option \"min_component\" in geometry not implement yet!");
152 // TODO:
153 // if (args["min_component"] > 0) {
154 // Eigen::SparseMatrix<int> adj;
155 // igl::facet_adjacency_matrix(boundary_triangles, adj);
156 // Eigen::MatrixXi C, counts;
157 // igl::connected_components(adj, C, counts);
158 // std::vector<int> valid;
159 // const int min_count = args["min_component"];
160 // for (int i = 0; i < counts.size(); ++i) {
161 // if (counts(i) >= min_count) {
162 // valid.push_back(i);
163 // }
164 // }
165 // tris.clear();
166 // for (int i = 0; i < C.size(); ++i) {
167 // for (int v : valid) {
168 // if (v == C(i)) {
169 // tris.emplace_back(boundary_triangles(i, 0), boundary_triangles(i, 1), boundary_triangles(i, 2));
170 // break;
171 // }
172 // }
173 // }
174 // boundary_triangles.resize(tris.size(), 3);
175 // for (int i = 0; i < tris.size(); ++i) {
176 // boundary_triangles.row(i) << std::get<0>(tris[i]), std::get<1>(tris[i]), std::get<2>(tris[i]);
177 // }
178 // }
179
180 // --------------------------------------------------------------------
181
182 if (j_mesh["advanced"]["force_linear_geometry"].get<bool>())
183 log_and_throw_error("Option \"force_linear_geometry\" in geometry not implement yet!");
184 // TODO:
185 // if (!iso_parametric()) {
186 // if (args["force_linear_geometry"] || mesh->orders().size() <= 0) {
187 // geom_disc_orders.resizeLike(disc_orders);
188 // geom_disc_orders.setConstant(1);
189 // } else {
190 // geom_disc_orders = mesh->orders();
191 // }
192 // }
193
194 // --------------------------------------------------------------------
195
196 const std::vector<std::shared_ptr<Selection>> node_selections =
197 is_param_valid(j_mesh, "point_selection") ? Selection::build_selections(j_mesh["point_selection"], bbox, resources_) : std::vector<std::shared_ptr<Selection>>();
198
199 if (!node_selections.empty())
200 {
201 bool boundary_only = true;
202 for (const auto &selection : node_selections)
203 {
204 if (!selection->boundary_only())
205 {
206 boundary_only = false;
207 break;
208 }
209 }
210
211 mesh->compute_node_ids([&](const size_t n_id, const RowVectorNd &p, bool is_boundary) {
212 if (boundary_only && !is_boundary)
213 return -1;
214
215 const std::vector<int> tmp = {int(n_id)};
216 for (const auto &selection : node_selections)
217 {
218 if (selection->boundary_only() && !is_boundary)
219 continue;
220
221 if (selection->inside(n_id, tmp, p))
222 return selection->id(n_id, tmp, p);
223 }
224 return std::numeric_limits<int>::max(); // default for no selected boundary
225 });
226 }
227
228 if (!j_mesh["curve_selection"].is_null())
229 log_and_throw_error("Geometry curve selections are not implemented!");
230
231 // --------------------------------------------------------------------
232
233 if (!surface_selections.empty())
234 {
235 bool boundary_only = true;
236 for (const auto &selection : surface_selections)
237 {
238 if (!selection->boundary_only())
239 {
240 boundary_only = false;
241 break;
242 }
243 }
244
245 mesh->compute_boundary_ids([&](const size_t p_id, const std::vector<int> &vs, const RowVectorNd &p, bool is_boundary) {
246 if (boundary_only && !is_boundary)
247 return -1;
248
249 for (const auto &selection : surface_selections)
250 {
251 if (selection->boundary_only() && !is_boundary)
252 continue;
253
254 if (selection->inside(p_id, vs, p))
255 return selection->id(p_id, vs, p);
256 }
257 return std::numeric_limits<int>::max(); // default for no selected boundary
258 });
259 }
260
261 // --------------------------------------------------------------------
262
263 // If the selection is of the form {"id_offset": ...}
264 const json volume_selection = j_mesh["volume_selection"];
265 if (volume_selection.is_object()
266 && volume_selection.size() == 1
267 && volume_selection.contains("id_offset"))
268 {
269 const int id_offset = volume_selection["id_offset"].get<int>();
270 if (id_offset != 0)
271 {
272 const int n_body_ids = mesh->n_elements();
273 std::vector<int> body_ids(n_body_ids);
274 for (int i = 0; i < n_body_ids; ++i)
275 body_ids[i] = mesh->get_body_id(i) + id_offset;
276 mesh->set_body_ids(body_ids);
277 }
278 }
279 else
280 {
281 // Specified volume selection has priority over mesh's stored ids
282 std::vector<std::shared_ptr<Selection>> volume_selections =
283 Selection::build_selections(volume_selection, bbox, resources_);
284
285 // Append the mesh's stored ids to the volume selection as a lowest priority selection
286 if (mesh->has_body_ids())
287 volume_selections.push_back(std::make_shared<SpecifiedSelection>(mesh->get_body_ids()));
288
289 mesh->compute_body_ids([&](const size_t cell_id, const std::vector<int> &vs, const RowVectorNd &p) -> int {
290 for (const auto &selection : volume_selections)
291 {
292 // TODO: add vs to compute_body_ids
293 if (selection->inside(cell_id, vs, p))
294 return selection->id(cell_id, vs, p);
295 }
296 return 0;
297 });
298 }
299
300 // --------------------------------------------------------------------
301
302 if (is_param_valid(j_mesh, "geometry_selection"))
303 apply_geometry_selection(*mesh, j_mesh["geometry_selection"]);
304
305 // --------------------------------------------------------------------
306
307 return mesh;
308 }
309
310 // ========================================================================
311
312 std::unique_ptr<Mesh> GeometryLoader::load_fem(
313 const json &geometry,
314 const bool non_conforming) const
315 {
316 // --------------------------------------------------------------------
317
318 if (geometry.empty())
319 log_and_throw_error("Provided geometry is empty!");
320
321 std::vector<json> geometries = utils::json_as_array(geometry);
322
323 // --------------------------------------------------------------------
324
325 std::unique_ptr<Mesh> mesh = nullptr;
326
327 for (const json &geometry : geometries)
328 {
329 if (!geometry["enabled"].get<bool>() || geometry["is_obstacle"].get<bool>())
330 continue;
331
332 if (geometry["type"] != "mesh" && geometry["type"] != "mesh_array")
333 log_and_throw_error("Invalid geometry type \"{}\" for FEM mesh!", geometry["type"]);
334
335 const std::unique_ptr<Mesh> tmp_mesh = load_fem_entry(geometry, non_conforming);
336
337 if (mesh == nullptr)
338 mesh = tmp_mesh->copy();
339 else
340 mesh->append(tmp_mesh);
341
342 if (geometry["type"] == "mesh_array")
343 {
344 Selection::BBox bbox;
345 tmp_mesh->bounding_box(bbox[0], bbox[1]);
346
347 const long dim = tmp_mesh->dimension();
348 const bool is_offset_relative = geometry["array"]["relative"];
349 const double offset = geometry["array"]["offset"];
350 const VectorNd dimensions = (bbox[1] - bbox[0]);
351 const VectorNi size = geometry["array"]["size"];
352
353 for (int i = 0; i < size[0]; ++i)
354 {
355 for (int j = 0; j < size[1]; ++j)
356 {
357 for (int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
358 {
359 if (i == 0 && j == 0 && k == 0)
360 continue;
361
362 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(dim);
363 if (is_offset_relative)
364 translation.array() *= dimensions.array();
365
366 const std::unique_ptr<Mesh> copy_mesh = tmp_mesh->copy();
367 copy_mesh->apply_affine_transformation(MatrixNd::Identity(dim, dim), translation);
368 mesh->append(copy_mesh);
369 }
370 }
371 }
372 }
373 }
374
375 // --------------------------------------------------------------------
376
377 return mesh;
378 }
379
380 // ========================================================================
381
383 const json &j_mesh,
384 const int dim) const
385 {
386 if (!is_param_valid(j_mesh, "mesh"))
387 log_and_throw_error("Mesh obstacle {} is mising a \"mesh\" field!", j_mesh);
388
389 SurfaceMesh result = MeshLoader(resources_).load_surface(j_mesh["mesh"].get<std::string>());
390 auto &vertices = result.vertices;
391 auto &codim_vertices = result.points;
392 auto &codim_edges = result.edges;
393 auto &faces = result.faces;
394
395 const int prev_dim = vertices.cols();
396 vertices.conservativeResize(vertices.rows(), dim);
397 if (prev_dim < dim)
398 vertices.rightCols(dim - prev_dim).setZero();
399
400 // --------------------------------------------------------------------
401
402 {
403 const std::string unit = j_mesh["unit"];
404 double unit_scale = 1;
405 if (!unit.empty())
406 unit_scale = Units::convert(1, unit, units_.length());
407
408 const VectorNd mesh_dimensions = (vertices.colwise().maxCoeff() - vertices.colwise().minCoeff()).cwiseAbs();
409 MatrixNd A;
410 VectorNd b;
411 construct_affine_transformation(unit_scale, j_mesh["transformation"], mesh_dimensions, A, b);
412 vertices = vertices * A.transpose();
413 vertices.rowwise() += b.transpose();
414 }
415
416 std::string extract = j_mesh["extract"];
417 // Default: "volume" clashes with defaults for non obstacle, here assume volume is suface
418 if (extract == "volume")
419 extract = "surface";
420
421 if (extract == "points")
422 {
423 // points -> vertices (drop edges and faces)
424 codim_edges.resize(0, 0);
425 faces.resize(0, 0);
426 codim_vertices.resize(vertices.rows());
427 for (int i = 0; i < codim_vertices.size(); ++i)
428 codim_vertices[i] = i;
429 }
430 else if (extract == "edges" && faces.size() != 0)
431 {
432 // edges -> edges (drop faces)
433 Eigen::MatrixXi edges;
434 igl::edges(faces, edges);
435 faces.resize(0, 0);
436 codim_edges.conservativeResize(codim_edges.rows() + edges.rows(), 2);
437 codim_edges.bottomRows(edges.rows()) = edges;
438 }
439 else if (extract == "surface" && dim == 2 && faces.size() != 0)
440 {
441 // surface (2D) -> boundary edges (drop faces and interior edges)
442 Eigen::MatrixXi boundary_edges;
443 igl::boundary_facets(faces, boundary_edges);
444 codim_edges.conservativeResize(codim_edges.rows() + boundary_edges.rows(), 2);
445 codim_edges.bottomRows(boundary_edges.rows()) = boundary_edges;
446 faces.resize(0, 0); // Clear faces
447 }
448 // surface (3D) -> boundary faces
449 // Surface extraction in 3D already has the desired faces from MeshLoader.
450 else if (extract == "volume")
451 {
452 // volume -> undefined
453 log_and_throw_error("Volumetric elements not supported for collision obstacles!");
454 }
455
456 if (j_mesh["n_refs"].get<int>() != 0)
457 {
458 if (faces.size() != 0)
459 log_and_throw_error("Option \"n_refs\" for triangle obstacles not implement yet!");
460
461 const int n_refs = j_mesh["n_refs"];
462 const double refinement_location = j_mesh["advanced"]["refinement_location"];
463 for (int i = 0; i < n_refs; i++)
464 {
465 const size_t n_vertices = vertices.rows();
466 const size_t n_edges = codim_edges.rows();
467 vertices.conservativeResize(n_vertices + n_edges, vertices.cols());
468 codim_edges.conservativeResize(2 * n_edges, codim_edges.cols());
469 for (size_t ei = 0; ei < n_edges; ei++)
470 {
471 const int v0i = codim_edges(ei, 0);
472 const int v1i = codim_edges(ei, 1);
473 const int v2i = n_vertices + ei;
474 vertices.row(v2i) = (vertices.row(v1i) - vertices.row(v0i)) * refinement_location + vertices.row(v0i);
475 codim_edges.row(ei) << v0i, v2i;
476 codim_edges.row(n_edges + ei) << v2i, v1i;
477 }
478 }
479 }
480 return result;
481 }
482
483 // ========================================================================
484
486 const json &geometry,
487 const std::vector<json> &displacements,
488 const std::vector<json> &dirichlets,
489 const int dim,
490 const bool) const
491 {
492 Obstacle obstacle;
493
494 if (geometry.empty())
495 return obstacle;
496
497 std::vector<json> geometries = utils::json_as_array(geometry);
498
499 for (const json &geometry : geometries)
500 {
501
502 if (!geometry["is_obstacle"].get<bool>())
503 continue;
504
505 if (!geometry["enabled"].get<bool>())
506 continue;
507
508 if (geometry["type"] == "mesh" || geometry["type"] == "mesh_array")
509 {
510 SurfaceMesh loaded = load_surface(geometry, dim);
511 Eigen::MatrixXd &vertices = loaded.vertices;
512 Eigen::VectorXi &codim_vertices = loaded.points;
513 Eigen::MatrixXi &codim_edges = loaded.edges;
514 Eigen::MatrixXi &faces = loaded.faces;
515
516 if (geometry["type"] == "mesh_array")
517 {
518 const Selection::BBox bbox{{vertices.colwise().minCoeff(), vertices.colwise().maxCoeff()}};
519
520 const bool is_offset_relative = geometry["array"]["relative"];
521 const double offset = geometry["array"]["offset"];
522 const VectorNd dimensions = (bbox[1] - bbox[0]);
523 const VectorNi size = geometry["array"]["size"];
524
525 const int N = size.head(dim).prod();
526 const int nV = vertices.rows(), nCV = codim_vertices.rows(), nCE = codim_edges.rows(), nF = faces.rows();
527
528 vertices.conservativeResize(N * nV, Eigen::NoChange);
529 codim_vertices.conservativeResize(N * nCV, Eigen::NoChange);
530 codim_edges.conservativeResize(N * nCE, Eigen::NoChange);
531 faces.conservativeResize(N * nF, Eigen::NoChange);
532
533 for (int i = 0; i < size[0]; ++i)
534 {
535 for (int j = 0; j < size[1]; ++j)
536 {
537 for (int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
538 {
539 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(vertices.cols());
540 if (is_offset_relative)
541 translation.array() *= dimensions.array();
542
543 int n = i * size[1] + j;
544 if (size.size() > 2)
545 n = n * size[2] + k;
546 if (n == 0)
547 continue;
548
549 vertices.middleRows(n * nV, nV) = vertices.topRows(nV).rowwise() + translation;
550 if (nCV)
551 codim_vertices.segment(n * nV, nV) = codim_vertices.head(nV).array() + n * nV;
552 if (nCE)
553 codim_edges.middleRows(n * nCE, nCE) = codim_edges.topRows(nCE).array() + n * nV;
554 if (nF)
555 faces.middleRows(n * nF, nF) = faces.topRows(nF).array() + n * nV;
556 }
557 }
558 }
559 }
560
561 json displacement = "{\"value\":[0, 0, 0]}"_json;
562 if (is_param_valid(geometry, "surface_selection"))
563 {
564 if (!geometry["surface_selection"].is_number())
565 log_and_throw_error("Invalid surface_selection for obstacle, needs to be an integer!");
566
567 const int id = geometry["surface_selection"];
568 for (const json &disp : dirichlets)
569 {
570 if ((disp["id"].is_string() && disp["id"].get<std::string>() == "all")
571 || (disp["id"].is_number_integer() && disp["id"].get<int>() == id))
572 {
573 displacement = disp;
574 break;
575 }
576 else if (disp["id"].is_array())
577 {
578 for (const json &disp_id : disp["id"])
579 {
580 assert(disp_id.is_number_integer());
581 if (disp_id.get<int>() == id)
582 {
583 displacement = disp;
584 break;
585 }
586 }
587 }
588 }
589 for (const json &disp : displacements)
590 {
591 if ((disp["id"].is_string() && disp["id"].get<std::string>() == "all")
592 || (disp["id"].is_number_integer() && disp["id"].get<int>() == id))
593 {
594 displacement = disp;
595 break;
596 }
597 else if (disp["id"].is_array())
598 {
599 for (const json &disp_id : disp["id"])
600 {
601 assert(disp_id.is_number_integer());
602 if (disp_id.get<int>() == id)
603 {
604 displacement = disp;
605 break;
606 }
607 }
608 }
609 }
610 }
611
612 obstacle.append_mesh(
613 vertices, codim_vertices, codim_edges, faces, displacement, resources_);
614 }
615 else if (geometry["type"] == "plane")
616 {
617 obstacle.append_plane(geometry["point"], geometry["normal"]);
618 }
619 else if (geometry["type"] == "ground")
620 {
621 VectorNd gravity = VectorNd::Zero(dim); // TODO: Expose as parameter
622 gravity[1] = -9.81;
623 const double height = geometry["height"];
624 assert(gravity.norm() != 0);
625 const VectorNd normal = -gravity.normalized();
626 const VectorNd point = height * normal; // origin + height * normal
627 obstacle.append_plane(point, normal);
628 }
629 else if (geometry["type"] == "mesh_sequence")
630 {
631 std::vector<std::string> mesh_files;
632 if (geometry["mesh_sequence"].is_array())
633 {
634 mesh_files = geometry["mesh_sequence"].get<std::vector<std::string>>();
635 }
636 else
637 {
638 assert(geometry["mesh_sequence"].is_string());
639 const std::string sequence = geometry["mesh_sequence"].get<std::string>();
640 mesh_files = resources_.is_group(sequence) ? resources_.list(sequence) : resources_.glob(sequence);
641 // Sort the file names naturally
642 std::sort(mesh_files.begin(), mesh_files.end(), [](const std::string &p1, const std::string &p2) {
643 return strnatcmp(p1.c_str(), p2.c_str()) < 0;
644 });
645 }
646
647 std::vector<Eigen::MatrixXd> vertices(mesh_files.size());
648 Eigen::VectorXi codim_vertices;
649 Eigen::MatrixXi codim_edges;
650 Eigen::MatrixXi faces;
651
652 for (int i = 0; i < mesh_files.size(); ++i)
653 {
654 json jmesh = geometry;
655 jmesh["mesh"] = mesh_files[i];
656 jmesh["n_refs"] = 0;
657
658 SurfaceMesh loaded = load_surface(jmesh, dim);
659 vertices[i] = std::move(loaded.vertices);
660 Eigen::VectorXi &tmp_codim_vertices = loaded.points;
661 Eigen::MatrixXi &tmp_codim_edges = loaded.edges;
662 Eigen::MatrixXi &tmp_faces = loaded.faces;
663 if (i == 0)
664 {
665 codim_vertices = tmp_codim_vertices;
666 codim_edges = tmp_codim_edges;
667 faces = tmp_faces;
668 }
669 else
670 {
671 assert((codim_vertices.array() == tmp_codim_vertices.array()).all());
672 assert((codim_edges.array() == tmp_codim_edges.array()).all());
673 assert((faces.array() == tmp_faces.array()).all());
674 }
675 }
676
677 obstacle.append_mesh_sequence(
678 vertices, codim_vertices, codim_edges, faces, geometry["fps"]);
679 }
680 else
681 {
682 log_and_throw_error("Invalid geometry type \"{}\" for obstacle!", geometry["type"]);
683 }
684 }
685
686 obstacle.set_units(units_);
687 return obstacle;
688 }
689
690 // ========================================================================
691
693 const double unit_scale,
694 const json &transform,
695 const VectorNd &mesh_dimensions,
696 MatrixNd &A,
697 VectorNd &b)
698 {
699 const int dim = mesh_dimensions.size();
700
701 // -----
702 // Scale
703 // -----
704
705 RowVectorNd scale;
706 if (transform["dimensions"].is_array()) // default is nullptr
707 {
708 VectorNd modified_dimensions =
709 (mesh_dimensions.array() == 0).select(1, mesh_dimensions);
710
711 scale = transform["dimensions"];
712 const int scale_size = scale.size();
713 scale.conservativeResize(dim);
714 if (scale_size < dim)
715 scale.tail(dim - scale_size).setZero();
716
717 scale.array() /= modified_dimensions.array();
718 }
719 else if (transform["scale"].is_number())
720 {
721 scale.setConstant(dim, transform["scale"].get<double>());
722 }
723 else
724 {
725 assert(transform["scale"].is_array());
726 scale = transform["scale"];
727 const int scale_size = scale.size();
728 scale.conservativeResize(dim);
729 if (scale_size < dim)
730 scale.tail(dim - scale_size).setZero();
731
732 if (scale_size == 0)
733 scale.setOnes();
734 }
735
736 A = (unit_scale * scale).asDiagonal();
737
738 // ------
739 // Rotate
740 // ------
741
742 // Rotate around the models origin NOT the bodies center of mass.
743 // We could expose this choice as a "rotate_around" field.
744 MatrixNd R = MatrixNd::Identity(dim, dim);
745 if (!transform["rotation"].is_null())
746 {
747 if (dim == 2)
748 {
749 if (transform["rotation"].is_number())
750 R = Eigen::Rotation2Dd(deg2rad(transform["rotation"].get<double>()))
751 .toRotationMatrix();
752 else if (!transform["rotation"].is_array() || !transform["rotation"].empty())
753 log_and_throw_error("Invalid 2D rotation; 2D rotations can only be a angle in degrees.");
754 }
755 else if (dim == 3)
756 {
757 R = to_rotation_matrix(transform["rotation"], transform["rotation_mode"]);
758 }
759 }
760
761 A = R * A; // Scale first, then rotate
762
763 // ---------
764 // Translate
765 // ---------
766
767 b = transform["translation"];
768 const int translation_size = b.size();
769 b.conservativeResize(dim);
770 if (translation_size < dim)
771 b.tail(dim - translation_size).setZero();
772 }
773
774} // namespace polyfem::mesh
Eigen::RowVectorXd point
std::vector< Eigen::VectorXi > faces
static double convert(const json &val, const std::string &unit_type)
Definition Units.cpp:35
const std::string & length() const
Definition Units.hpp:19
virtual std::vector< std::string > list(const std::string &path) const =0
std::vector< std::string > glob(const std::string &pattern) const
Glob normalized logical paths using POSIX '*' and '?' wildcards.
virtual bool is_group(const std::string &path) const =0
std::unique_ptr< Mesh > load_fem(const json &geometry, bool non_conforming=false) const
Load and combine the enabled FEM entries in a geometry configuration.
Obstacle load_obstacles(const json &geometry, const std::vector< json > &obstacle_displacements, const std::vector< json > &dirichlet_conditions, int dimension, bool non_conforming=false) const
const io::ResourceIO & resources_
LoadedGeometry load(const json &geometry, const std::vector< json > &obstacle_displacements, const std::vector< json > &dirichlet_conditions, bool non_conforming=false) const
static void construct_affine_transformation(double unit_scale, const json &transform, const VectorNd &mesh_dimensions, MatrixNd &A, VectorNd &b)
Construct the affine map used by geometry and collision-proxy inputs.
void apply_geometry_selection(Mesh &mesh, const json &geometry_selection) const
Apply configured geometry IDs to an already constructed mesh.
SurfaceMesh load_surface(const json &geometry, int dimension) const
Load and prepare one configured surface/codimensional geometry entry.
std::unique_ptr< Mesh > load_fem_entry(const json &geometry, bool non_conforming=false) const
std::unique_ptr< Mesh > fem
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:50
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
Definition Mesh.hpp:148
virtual int get_body_id(const int primitive) const
Get the volume selection of an element (cell in 3d, face in 2d)
Definition Mesh.hpp:492
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.
Definition Mesh.hpp:507
std::vector< int > element_vertices(const int el_id) const
list of vids of an element
Definition Mesh.hpp:216
The only non-legacy decoder from a logical resource path to mesh data.
std::unique_ptr< Mesh > load_fem(const std::string &path, bool non_conforming=false) const
SurfaceMesh load_surface(const std::string &path) const
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 io::ResourceIO &resources)
Definition Obstacle.cpp:96
void append_plane(const VectorNd &point, const VectorNd &normal)
Definition Obstacle.cpp:164
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)
Definition Obstacle.cpp:125
void set_units(const Units &units)
Definition Obstacle.cpp:250
static std::vector< std::shared_ptr< utils::Selection > > build_selections(const json &j_selections, const BBox &mesh_bbox, const io::ResourceIO &resources)
Build a vector of selection objects from a JSON selection(s).
Definition Selection.cpp:47
std::array< RowVectorNd, 2 > BBox
Definition Selection.hpp:14
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.
Definition JSONUtils.hpp:49
T deg2rad(T deg)
Definition JSONUtils.hpp:29
bool is_param_valid(const json &params, const std::string &key)
Determine if a key exists and is non-null in a json object.
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 3, 1 > VectorNd
Definition Types.hpp:11
nlohmann::json json
Definition Common.hpp:9
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor, 3, 3 > MatrixNd
Definition Types.hpp:14
Eigen::Matrix< int, Eigen::Dynamic, 1, 0, 3, 1 > VectorNi
Definition Types.hpp:12
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73