14#include <lagrange/Logger.h>
15#include <lagrange/Mesh.h>
16#include <lagrange/common.h>
17#include <lagrange/compute_normal.h>
18#include <lagrange/compute_triangle_normal.h>
19#include <lagrange/create_mesh.h>
20#include <lagrange/legacy/inline.h>
21#include <lagrange/primitive/SweepPath.h>
22#include <lagrange/primitive/generation_utils.h>
23#include <lagrange/utils/assert.h>
24#include <lagrange/utils/safe_cast.h>
26#include <Eigen/Geometry>
28#include <lagrange/utils/warnoff.h>
29#include <lagrange/utils/warnon.h>
30#include <tbb/parallel_for.h>
32#include <lagrange/internal/constants.h>
43template <
typename Derived>
44bool is_path_closed(
const Eigen::MatrixBase<Derived>& path)
46 if (path.rows() <= 2)
return false;
48 using Scalar =
typename Derived::Scalar;
49 constexpr Scalar TOL = std::numeric_limits<Scalar>::epsilon() * 10;
50 return (path.row(0) - path.row(path.rows() - 1)).squaredNorm() < TOL;
66template <
typename Derived>
67int compute_profile_breaks(
68 const Eigen::MatrixBase<Derived>& profile,
69 const std::vector<ScalarOf<Derived>>& arc_lengths,
70 const std::vector<ScalarOf<Derived>>& turning_angles,
71 typename Derived::Scalar max_len,
72 std::vector<bool>& breaks)
74 using Scalar =
typename Derived::Scalar;
76 const Index N =
static_cast<Index
>(profile.rows());
79 breaks.resize(N,
false);
81 const Index num_pieces = std::max<Index>(
82 (max_len > 0 ?
static_cast<Index
>(std::ceil(arc_lengths[N - 1] / max_len)) : 1),
84 const Scalar ave_len = arc_lengths[N - 1] / num_pieces;
86 constexpr Scalar EPSILON = std::numeric_limits<Scalar>::epsilon() * 100;
87 Index strip_index = 0;
88 Scalar prev_arc_length = 0;
89 for (Index i = 1; i < N - 1; i++) {
90 if (std::abs(turning_angles[i]) > lagrange::internal::pi / 4 ||
91 arc_lengths[i] - prev_arc_length > ave_len + EPSILON) {
93 prev_arc_length = arc_lengths[i];
98 return strip_index + 1;
104template <
typename Derived>
105std::vector<typename Derived::Scalar> compute_turning_angles(
106 const Eigen::PlainObjectBase<Derived>& profile)
108 using Scalar =
typename Derived::Scalar;
111 const bool profile_closed = is_path_closed(profile);
112 std::vector<Scalar> profile_turning_angles(N, 0);
114 Eigen::Matrix<Scalar, 1, 3> v0, v1;
115 for (
size_t i = 1; i < N - 1; i++) {
116 v0 = profile.row(i) - profile.row(i - 1);
117 v1 = profile.row(i + 1) - profile.row(i);
118 profile_turning_angles[i] = std::atan2(v1.cross(v0).norm(), v1.dot(v0));
120 if (profile_closed) {
121 v0 = profile.row(N - 1) - profile.row(N - 2);
122 v1 = profile.row(1) - profile.row(0);
123 Scalar angle = std::atan2(v1.cross(v0).norm(), v1.dot(v0));
124 profile_turning_angles[0] = angle;
125 profile_turning_angles[N - 1] = angle;
128 return profile_turning_angles;
148template <
typename MeshType>
151 const VertexArrayOf<MeshType>& profile,
152 const std::vector<Eigen::Transform<ScalarOf<MeshType>, 3, Eigen::AffineCompact>>& transforms,
153 ScalarOf<MeshType> max_strip_len,
154 const std::vector<ScalarOf<MeshType>>& profile_turning_angles)
156 using Scalar = ScalarOf<MeshType>;
157 using Index = IndexOf<MeshType>;
158 using UVArray =
typename MeshType::UVArray;
159 using UVIndices =
typename MeshType::UVIndices;
165 std::vector<Scalar> profile_arc_lengths(N);
166 profile_arc_lengths[0] = 0;
167 for (Index i = 1; i < N; i++) {
168 profile_arc_lengths[i] =
169 profile_arc_lengths[i - 1] + (profile.row(i) - profile.row(i - 1)).norm();
172 std::vector<Scalar> sweep_path_arc_lengths(M);
173 sweep_path_arc_lengths[0] = 0;
175 Eigen::Matrix<Scalar, 1, 3> c = profile.colwise().mean();
176 for (Index i = 1; i < M; i++) {
177 sweep_path_arc_lengths[i] =
178 sweep_path_arc_lengths[i - 1] +
179 (transforms[i] * c.transpose() - transforms[i - 1] * c.transpose()).norm();
183 const Index num_quads = (N - 1) * (M - 1);
184 std::vector<bool> breaks;
185 const Index num_strips = compute_profile_breaks(
188 profile_turning_angles,
191 const Index L = N + num_strips - 1;
192 const Index num_uvs = L * M + num_quads;
193 UVArray uvs(num_uvs, 2);
196 for (Index i = 0; i < M; i++) {
197 Index strip_index = 0;
198 for (Index j = 0; j < N; j++) {
199 uvs.row(i * L + j + strip_index) << profile_arc_lengths[j], sweep_path_arc_lengths[i];
201 if (i != 0 && j != 0) {
202 Index
id = (i - 1) * (N - 1) + j - 1;
203 Index v0 = (i - 1) * L + (j - 1) + strip_index;
204 Index v1 = (i - 1) * L + (j - 1) + strip_index + 1;
205 Index v2 = i * L + (j - 1) + strip_index;
206 Index v3 = i * L + (j - 1) + strip_index + 1;
207 Index c = L * M + id;
209 uvs.row(c) = (uvs.row(v0) + uvs.row(v1) + uvs.row(v2) + uvs.row(v3)) / 4;
210 uv_indices.row(
id * 4) << c, v0, v1;
211 uv_indices.row(
id * 4 + 1) << c, v1, v3;
212 uv_indices.row(
id * 4 + 2) << c, v3, v2;
213 uv_indices.row(
id * 4 + 3) << c, v2, v0;
219 uvs.row(i * L + j + strip_index) << profile_arc_lengths[j],
220 sweep_path_arc_lengths[i];
225 mesh.initialize_uv(uvs, uv_indices);
245template <
typename MeshType>
249 ScalarOf<MeshType> angle_threshold,
250 const std::vector<ScalarOf<MeshType>>& profile_turning_angles)
252 using Scalar = ScalarOf<MeshType>;
253 using Index = IndexOf<MeshType>;
257 if (!mesh.has_facet_attribute(
"normal")) {
258 compute_triangle_normal(mesh);
260 const auto& facet_normals = mesh.get_facet_attribute(
"normal");
261 const Scalar cos_threshold = std::cos(angle_threshold);
264 Index quad0 = f0 / 4;
265 Index quad1 = f1 / 4;
267 Index row0 = quad0 / (N - 1);
268 Index row1 = quad1 / (N - 1);
269 Index col0 = quad0 % (N - 1);
270 Index col1 = quad1 % (N - 1);
271 if (row0 != row1 || quad0 == quad1) {
273 return facet_normals.row(f0).dot(facet_normals.row(f1)) > cos_threshold;
275 if (col0 + 1 == col1 || (col0 == N - 2 && col1 == 0)) {
276 return profile_turning_angles[col1] <= angle_threshold;
277 }
else if (col1 + 1 == col0 || (col1 == N - 2 && col0 == 0)) {
278 return profile_turning_angles[col0] <= angle_threshold;
280 logger().debug(
"f0: {} quad0: {} row0: {} col0: {}", f0, quad0, row0, col0);
281 logger().debug(
"f1: {} quad1: {} row1: {} col1: {}", f1, quad1, row1, col1);
282 throw Error(fmt::format(
"Facet {} and {} are not adjacent!", f0, f1));
288template <
typename VertexArray>
289VertexArray compute_offset_directions(
const VertexArray& profile)
291 using Scalar =
typename VertexArray::Scalar;
292 using Index = Eigen::Index;
294 bool closed = is_path_closed(profile);
295 const auto N = profile.rows();
297 VertexArray dirs(N, 3);
299 for (
auto i :
range(N)) {
301 Index v_next = closed ? (i + 1) % (N - 1) : std::min<Index>(i + 1, N - 1);
302 Index v_prev = closed ? (i + N - 2) % (N - 1) : std::max<Index>(1, i) - 1;
303 Eigen::Matrix<Scalar, 3, 1> n0;
304 Eigen::Matrix<Scalar, 3, 1> n1;
305 n0 = profile.row(v_curr) - profile.row(v_prev);
306 n1 = profile.row(v_next) - profile.row(v_curr);
307 std::swap(n0[0], n0[1]);
308 std::swap(n1[0], n1[1]);
312 if (i == 0 && !closed) {
313 dirs.row(i) = n1.normalized();
314 }
else if (i == N - 1 && !closed) {
315 dirs.row(i) = n0.normalized();
319 Scalar l = 1 / std::sqrt(1 + n0.dot(n1) / 2 + (
Scalar)1e-6);
323 l = std::max((
Scalar)11.4737132467, l);
324 dirs.row(i) = (n0 + n1).normalized() * l;
328 if (!dirs.array().isFinite().all()) {
364template <
typename MeshType>
365std::unique_ptr<MeshType> generate_swept_surface(
366 const VertexArrayOf<MeshType>& profile,
367 const std::vector<Eigen::Transform<ScalarOf<MeshType>, 3, Eigen::AffineCompact>>& transforms,
368 const std::vector<ScalarOf<MeshType>>& offsets,
369 ScalarOf<MeshType> max_strip_len = 0,
370 bool path_closed =
false)
372 using Scalar =
typename MeshType::Scalar;
373 using Index =
typename MeshType::Index;
374 using VertexArray =
typename MeshType::VertexArray;
375 using FacetArray =
typename MeshType::FacetArray;
376 using AttributeArray =
typename MeshType::AttributeArray;
377 using IndexArray =
typename MeshType::IndexArray;
381 logger().debug(
"N: {} M: {}", N, M);
384 const bool profile_closed = internal::is_path_closed(profile);
385 const Index n = profile_closed ? (N - 1) : N;
386 const Index m = path_closed ? (M - 1) : M;
388 const Index num_quads = (N - 1) * (M - 1);
389 const Index num_vertices = n * m + num_quads;
390 const Index num_facets = 4 * num_quads;
391 VertexArray vertices(num_vertices, 3);
392 FacetArray facets(num_facets, 3);
395 std::function<VertexArray(Index)> offset_profile;
396 VertexArray offset_dirs;
397 if (!offsets.empty()) {
399 static_cast<Index
>(offsets.size()) == M,
400 "Transforms and offsets must be sampled consistently");
401 offset_dirs = internal::compute_offset_directions(profile);
402 offset_profile = [&](Index i) -> VertexArray {
403 const Scalar offset = offsets[i];
404 return profile.topRows(n) + offset_dirs.topRows(n) * offset;
407 offset_profile = [&](Index) -> VertexArray {
return profile.topRows(n); };
412 for (Index i = 0; i < m; i++) {
413 const auto& T = transforms[i];
414 vertices.block(i * n, 0, n, 3).transpose() = T * offset_profile(i).transpose();
418 for (Index i = 0; i < M - 1; i++) {
419 for (Index j = 0; j < N - 1; j++) {
420 const Index
id = i * (N - 1) + j;
421 const Index v0 = i * n + j;
422 const Index v1 = i * n + (j + 1) % n;
423 const Index v2 = ((i + 1) % m) * n + j;
424 const Index v3 = ((i + 1) % m) * n + (j + 1) % n;
425 const Index c = n * m + id;
429 (vertices.row(v0) + vertices.row(v1) + vertices.row(v2) + vertices.row(v3)) / 4;
431 facets.row(
id * 4) << c, v0, v1;
432 facets.row(
id * 4 + 1) << c, v1, v3;
433 facets.row(
id * 4 + 2) << c, v3, v2;
434 facets.row(
id * 4 + 3) << c, v2, v0;
439 std::vector<Scalar> profile_arc_lengths(N, 0), path_arc_lengths(M, 0);
440 for (
auto i :
range(N - 1)) {
441 profile_arc_lengths[i + 1] = (profile.row(i + 1) - profile.row(i)).norm();
444 profile_arc_lengths.begin(),
445 profile_arc_lengths.end(),
446 profile_arc_lengths.begin());
447 if (profile_arc_lengths.back() > 0) {
448 for (
auto& l : profile_arc_lengths) {
449 l /= profile_arc_lengths.back();
452 path_arc_lengths[0] = 0;
454 Eigen::Matrix<Scalar, 1, 3> c = profile.colwise().mean();
455 for (Index i = 1; i < M; i++) {
456 path_arc_lengths[i] =
457 path_arc_lengths[i - 1] +
458 (transforms[i] * c.transpose() - transforms[i - 1] * c.transpose()).norm();
461 if (path_arc_lengths.back() > 0) {
462 for (
auto& l : path_arc_lengths) {
463 l /= path_arc_lengths.back();
468 AttributeArray latitude(N * M + num_quads, 1);
469 AttributeArray longitude(N * M + num_quads, 1);
470 IndexArray feature_indices(num_facets, 3);
472 for (Index i = 0; i < M; i++) {
473 for (Index j = 0; j < N; j++) {
474 latitude(i * N + j) = path_arc_lengths[i];
475 longitude(i * N + j) = profile_arc_lengths[j];
477 if (i < M - 1 && j < N - 1) {
478 const Index offset = i * (N - 1) + j;
479 latitude(M * N + offset) = (path_arc_lengths[i + 1] + path_arc_lengths[i]) / 2;
480 longitude(M * N + offset) =
481 (profile_arc_lengths[j + 1] + profile_arc_lengths[j]) / 2;
483 const Index v0 = i * N + j;
484 const Index v1 = i * N + j + 1;
485 const Index v2 = (i + 1) * N + j;
486 const Index v3 = (i + 1) * N + j + 1;
487 const Index c = M * N + offset;
489 feature_indices.row(offset * 4) << c, v0, v1;
490 feature_indices.row(offset * 4 + 1) << c, v1, v3;
491 feature_indices.row(offset * 4 + 2) << c, v3, v2;
492 feature_indices.row(offset * 4 + 3) << c, v2, v0;
497 auto mesh =
create_mesh(std::move(vertices), std::move(facets));
499 std::vector<Scalar> profile_turning_angles = internal::compute_turning_angles(profile);
500 internal::generate_uv(*mesh, profile, transforms, max_strip_len, profile_turning_angles);
501 internal::generate_normal(*mesh, N, lagrange::internal::pi / 4, profile_turning_angles);
503 mesh->add_indexed_attribute(
"latitude");
504 mesh->set_indexed_attribute(
"latitude", latitude, feature_indices);
505 mesh->add_indexed_attribute(
"longitude");
506 mesh->set_indexed_attribute(
"longitude", longitude, feature_indices);
509 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::SIDE);
533template <
typename MeshType>
534std::unique_ptr<MeshType> generate_swept_surface(
535 const VertexArrayOf<MeshType>& profile,
536 const VertexArrayOf<MeshType>& sweep_path,
537 ScalarOf<MeshType> max_strip_len = 0)
539 typename MeshType::VertexType profile_center =
540 (profile.colwise().minCoeff() + profile.colwise().maxCoeff()) / 2;
543 path.set_pivot(profile_center);
545 const auto& transforms = path.get_transforms();
546 return generate_swept_surface<MeshType>(
569template <
typename MeshType>
570std::unique_ptr<MeshType> generate_swept_surface(
571 const VertexArrayOf<MeshType>& profile,
572 const SweepPath<ScalarOf<MeshType>>& sweep_path,
573 ScalarOf<MeshType> max_strip_len = 0)
576 sweep_path.get_num_samples() >= 2,
577 "Please make sure sweep_path obj is sufficiently sampled.");
578 return generate_swept_surface<MeshType>(
580 sweep_path.get_transforms(),
581 sweep_path.get_offsets(),
583 sweep_path.is_closed());
586template <
typename VertexArray>
587std::vector<VertexArray> generate_swept_surface_latitude(
588 const VertexArray& profile,
589 const std::vector<Eigen::Transform<ScalarOf<VertexArray>, 3, Eigen::AffineCompact>>& transforms,
590 const std::vector<ScalarOf<VertexArray>>& offsets = {})
592 using Scalar = ScalarOf<VertexArray>;
593 std::function<VertexArray(
size_t)> offset_profile;
594 VertexArray offset_dirs;
595 if (!offsets.empty()) {
597 offsets.size() == transforms.size(),
598 "Transforms and offsets must be sampled consistently");
599 offset_dirs = internal::compute_offset_directions(profile);
600 offset_profile = [&](
size_t i) -> VertexArray {
601 const Scalar offset = offsets[i];
602 return profile + offset_dirs * offset;
605 offset_profile = [&](size_t) -> VertexArray {
return profile; };
608 const size_t num_transforms = transforms.size();
609 std::vector<VertexArray> latitudes(num_transforms);
611 tbb::parallel_for(
size_t(0), num_transforms, [&](
size_t i) {
612 const auto& T = transforms[i];
613 latitudes[i] = (T * offset_profile(i).transpose()).transpose();
618template <
typename VertexArray>
619std::vector<VertexArray> generate_swept_surface_latitude(
620 const VertexArray& profile,
621 SweepPath<ScalarOf<VertexArray>>& sweep_path)
623 return generate_swept_surface_latitude(
625 sweep_path.get_transforms(),
626 sweep_path.get_offsets());
629template <
typename VertexArray>
630std::vector<VertexArray> generate_swept_surface_longitude(
631 const VertexArray& profile,
632 const std::vector<Eigen::Transform<ScalarOf<VertexArray>, 3, Eigen::AffineCompact>>& transforms,
633 const std::vector<ScalarOf<VertexArray>>& offsets = {})
635 using Scalar = ScalarOf<VertexArray>;
636 using Index = size_t;
637 using VertexType = Eigen::Matrix<Scalar, 1, 3>;
639 std::function<VertexType(Index, Index)> offset_profile;
640 VertexArray offset_dirs;
641 if (!offsets.empty()) {
643 offsets.size() == transforms.size(),
644 "Transforms and offsets must be sampled consistently");
645 offset_dirs = internal::compute_offset_directions(profile);
646 offset_profile = [&](Index i, Index j) -> VertexType {
647 const Scalar offset = offsets[i];
648 return profile.row(j) + offset_dirs.row(j) * offset;
651 offset_profile = [&](Index, Index j) -> VertexType {
return profile.row(j); };
654 Index num_transforms = transforms.size();
655 Index profile_size =
static_cast<Index
>(profile.rows());
658 if ((profile.row(0) - profile.row(profile_size - 1)).norm() == 0) {
663 std::vector<VertexArray> longitudes(profile_size);
664 for (
auto i :
range(profile_size)) {
665 longitudes[i].resize(num_transforms, 3);
666 for (
auto j :
range(num_transforms)) {
667 longitudes[i].row(j) = (transforms[j] * offset_profile(j, i).transpose()).transpose();
674template <
typename VertexArray>
675std::vector<VertexArray> generate_swept_surface_longitude(
676 const VertexArray& profile,
677 SweepPath<ScalarOf<VertexArray>>& sweep_path)
679 return generate_swept_surface_longitude(
681 sweep_path.get_transforms(),
682 sweep_path.get_offsets());
Index get_num_facets() const
Retrieves the number of facets.
Definition SurfaceMesh.h:687
Create sweep path based on a polyline.
Definition SweepPath.h:531
Abstract base class for sweep path.
Definition SweepPath.h:70
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
AttributeId compute_normal(SurfaceMesh< Scalar, Index > &mesh, function_ref< bool(Index)> is_edge_smooth, span< const Index > cone_vertices={}, NormalOptions options={})
Compute smooth normals based on specified sharp edges and cone vertices.
Definition compute_normal.cpp:198
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
internal::Range< Index > range(Index end)
Returns an iterable object representing the range [0, end).
Definition range.h:176
constexpr auto safe_cast(SourceType value) -> std::enable_if_t<!std::is_same< SourceType, TargetType >::value, TargetType >
Perform safe cast from SourceType to TargetType, where "safe" means:
Definition safe_cast.h:50
Main namespace for Lagrange.
auto create_mesh(const Eigen::MatrixBase< DerivedV > &vertices, const Eigen::MatrixBase< DerivedF > &facets)
This function create a new mesh given the vertex and facet arrays by copying data into the Mesh objec...
Definition create_mesh.h:39
@ Error
Throw an error if collision is detected.
Definition MappingPolicy.h:24