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>
33#include <lagrange/utils/fmt/format.h>
44template <
typename Derived>
45bool is_path_closed(
const Eigen::MatrixBase<Derived>& path)
47 if (path.rows() <= 2)
return false;
49 using Scalar =
typename Derived::Scalar;
50 constexpr Scalar TOL = std::numeric_limits<Scalar>::epsilon() * 10;
51 return (path.row(0) - path.row(path.rows() - 1)).squaredNorm() < TOL;
67template <
typename Derived>
68int compute_profile_breaks(
69 const Eigen::MatrixBase<Derived>& profile,
70 const std::vector<ScalarOf<Derived>>& arc_lengths,
71 const std::vector<ScalarOf<Derived>>& turning_angles,
72 typename Derived::Scalar max_len,
73 std::vector<bool>& breaks)
75 using Scalar =
typename Derived::Scalar;
77 const Index N =
static_cast<Index
>(profile.rows());
80 breaks.resize(N,
false);
82 const Index num_pieces = std::max<Index>(
83 (max_len > 0 ?
static_cast<Index
>(std::ceil(arc_lengths[N - 1] / max_len)) : 1),
85 const Scalar ave_len = arc_lengths[N - 1] / num_pieces;
87 constexpr Scalar EPSILON = std::numeric_limits<Scalar>::epsilon() * 100;
88 Index strip_index = 0;
89 Scalar prev_arc_length = 0;
90 for (Index i = 1; i < N - 1; i++) {
91 if (std::abs(turning_angles[i]) > lagrange::internal::pi / 4 ||
92 arc_lengths[i] - prev_arc_length > ave_len + EPSILON) {
94 prev_arc_length = arc_lengths[i];
99 return strip_index + 1;
105template <
typename Derived>
106std::vector<typename Derived::Scalar> compute_turning_angles(
107 const Eigen::PlainObjectBase<Derived>& profile)
109 using Scalar =
typename Derived::Scalar;
112 const bool profile_closed = is_path_closed(profile);
113 std::vector<Scalar> profile_turning_angles(N, 0);
115 Eigen::Matrix<Scalar, 1, 3> v0, v1;
116 for (
size_t i = 1; i < N - 1; i++) {
117 v0 = profile.row(i) - profile.row(i - 1);
118 v1 = profile.row(i + 1) - profile.row(i);
119 profile_turning_angles[i] = std::atan2(v1.cross(v0).norm(), v1.dot(v0));
121 if (profile_closed) {
122 v0 = profile.row(N - 1) - profile.row(N - 2);
123 v1 = profile.row(1) - profile.row(0);
124 Scalar angle = std::atan2(v1.cross(v0).norm(), v1.dot(v0));
125 profile_turning_angles[0] = angle;
126 profile_turning_angles[N - 1] = angle;
129 return profile_turning_angles;
149template <
typename MeshType>
152 const VertexArrayOf<MeshType>& profile,
153 const std::vector<Eigen::Transform<ScalarOf<MeshType>, 3, Eigen::AffineCompact>>& transforms,
154 ScalarOf<MeshType> max_strip_len,
155 const std::vector<ScalarOf<MeshType>>& profile_turning_angles)
157 using Scalar = ScalarOf<MeshType>;
158 using Index = IndexOf<MeshType>;
159 using UVArray =
typename MeshType::UVArray;
160 using UVIndices =
typename MeshType::UVIndices;
166 std::vector<Scalar> profile_arc_lengths(N);
167 profile_arc_lengths[0] = 0;
168 for (Index i = 1; i < N; i++) {
169 profile_arc_lengths[i] =
170 profile_arc_lengths[i - 1] + (profile.row(i) - profile.row(i - 1)).norm();
173 std::vector<Scalar> sweep_path_arc_lengths(M);
174 sweep_path_arc_lengths[0] = 0;
176 Eigen::Matrix<Scalar, 1, 3> c = profile.colwise().mean();
177 for (Index i = 1; i < M; i++) {
178 sweep_path_arc_lengths[i] =
179 sweep_path_arc_lengths[i - 1] +
180 (transforms[i] * c.transpose() - transforms[i - 1] * c.transpose()).norm();
184 const Index num_quads = (N - 1) * (M - 1);
185 std::vector<bool> breaks;
186 const Index num_strips = compute_profile_breaks(
189 profile_turning_angles,
192 const Index L = N + num_strips - 1;
193 const Index num_uvs = L * M + num_quads;
194 UVArray uvs(num_uvs, 2);
195 UVIndices uv_indices(mesh.get_num_facets(), 3);
197 for (Index i = 0; i < M; i++) {
198 Index strip_index = 0;
199 for (Index j = 0; j < N; j++) {
200 uvs.row(i * L + j + strip_index) << profile_arc_lengths[j], sweep_path_arc_lengths[i];
202 if (i != 0 && j != 0) {
203 Index
id = (i - 1) * (N - 1) + j - 1;
204 Index v0 = (i - 1) * L + (j - 1) + strip_index;
205 Index v1 = (i - 1) * L + (j - 1) + strip_index + 1;
206 Index v2 = i * L + (j - 1) + strip_index;
207 Index v3 = i * L + (j - 1) + strip_index + 1;
208 Index c = L * M + id;
210 uvs.row(c) = (uvs.row(v0) + uvs.row(v1) + uvs.row(v2) + uvs.row(v3)) / 4;
211 uv_indices.row(
id * 4) << c, v0, v1;
212 uv_indices.row(
id * 4 + 1) << c, v1, v3;
213 uv_indices.row(
id * 4 + 2) << c, v3, v2;
214 uv_indices.row(
id * 4 + 3) << c, v2, v0;
220 uvs.row(i * L + j + strip_index) << profile_arc_lengths[j],
221 sweep_path_arc_lengths[i];
226 mesh.initialize_uv(uvs, uv_indices);
246template <
typename MeshType>
250 ScalarOf<MeshType> angle_threshold,
251 const std::vector<ScalarOf<MeshType>>& profile_turning_angles)
253 using Scalar = ScalarOf<MeshType>;
254 using Index = IndexOf<MeshType>;
258 if (!mesh.has_facet_attribute(
"normal")) {
259 compute_triangle_normal(mesh);
261 const auto& facet_normals = mesh.get_facet_attribute(
"normal");
262 const Scalar cos_threshold = std::cos(angle_threshold);
265 Index quad0 = f0 / 4;
266 Index quad1 = f1 / 4;
268 Index row0 = quad0 / (N - 1);
269 Index row1 = quad1 / (N - 1);
270 Index col0 = quad0 % (N - 1);
271 Index col1 = quad1 % (N - 1);
272 if (row0 != row1 || quad0 == quad1) {
274 return facet_normals.row(f0).dot(facet_normals.row(f1)) > cos_threshold;
276 if (col0 + 1 == col1 || (col0 == N - 2 && col1 == 0)) {
277 return profile_turning_angles[col1] <= angle_threshold;
278 }
else if (col1 + 1 == col0 || (col1 == N - 2 && col0 == 0)) {
279 return profile_turning_angles[col0] <= angle_threshold;
281 logger().debug(
"f0: {} quad0: {} row0: {} col0: {}", f0, quad0, row0, col0);
282 logger().debug(
"f1: {} quad1: {} row1: {} col1: {}", f1, quad1, row1, col1);
283 throw Error(format(
"Facet {} and {} are not adjacent!", f0, f1));
289template <
typename VertexArray>
290VertexArray compute_offset_directions(
const VertexArray& profile)
292 using Scalar =
typename VertexArray::Scalar;
293 using Index = Eigen::Index;
295 bool closed = is_path_closed(profile);
296 const auto N = profile.rows();
298 VertexArray dirs(N, 3);
300 for (
auto i :
range(N)) {
302 Index v_next = closed ? (i + 1) % (N - 1) : std::min<Index>(i + 1, N - 1);
303 Index v_prev = closed ? (i + N - 2) % (N - 1) : std::max<Index>(1, i) - 1;
304 Eigen::Matrix<Scalar, 3, 1> n0;
305 Eigen::Matrix<Scalar, 3, 1> n1;
306 n0 = profile.row(v_curr) - profile.row(v_prev);
307 n1 = profile.row(v_next) - profile.row(v_curr);
308 std::swap(n0[0], n0[1]);
309 std::swap(n1[0], n1[1]);
313 if (i == 0 && !closed) {
314 dirs.row(i) = n1.normalized();
315 }
else if (i == N - 1 && !closed) {
316 dirs.row(i) = n0.normalized();
320 Scalar l = 1 / std::sqrt(1 + n0.dot(n1) / 2 + (
Scalar)1e-6);
324 l = std::max((
Scalar)11.4737132467, l);
325 dirs.row(i) = (n0 + n1).normalized() * l;
329 if (!dirs.array().isFinite().all()) {
365template <
typename MeshType>
366std::unique_ptr<MeshType> generate_swept_surface(
367 const VertexArrayOf<MeshType>& profile,
368 const std::vector<Eigen::Transform<ScalarOf<MeshType>, 3, Eigen::AffineCompact>>& transforms,
369 const std::vector<ScalarOf<MeshType>>& offsets,
370 ScalarOf<MeshType> max_strip_len = 0,
371 bool path_closed =
false)
373 using Scalar =
typename MeshType::Scalar;
374 using Index =
typename MeshType::Index;
375 using VertexArray =
typename MeshType::VertexArray;
376 using FacetArray =
typename MeshType::FacetArray;
377 using AttributeArray =
typename MeshType::AttributeArray;
378 using IndexArray =
typename MeshType::IndexArray;
382 logger().debug(
"N: {} M: {}", N, M);
385 const bool profile_closed = internal::is_path_closed(profile);
386 const Index n = profile_closed ? (N - 1) : N;
387 const Index m = path_closed ? (M - 1) : M;
389 const Index num_quads = (N - 1) * (M - 1);
390 const Index num_vertices = n * m + num_quads;
391 const Index num_facets = 4 * num_quads;
392 VertexArray vertices(num_vertices, 3);
393 FacetArray facets(num_facets, 3);
396 std::function<VertexArray(Index)> offset_profile;
397 VertexArray offset_dirs;
398 if (!offsets.empty()) {
400 static_cast<Index
>(offsets.size()) == M,
401 "Transforms and offsets must be sampled consistently");
402 offset_dirs = internal::compute_offset_directions(profile);
403 offset_profile = [&](Index i) -> VertexArray {
404 const Scalar offset = offsets[i];
405 return profile.topRows(n) + offset_dirs.topRows(n) * offset;
408 offset_profile = [&](Index) -> VertexArray {
return profile.topRows(n); };
413 for (Index i = 0; i < m; i++) {
414 const auto& T = transforms[i];
415 vertices.block(i * n, 0, n, 3).transpose() = T * offset_profile(i).transpose();
419 for (Index i = 0; i < M - 1; i++) {
420 for (Index j = 0; j < N - 1; j++) {
421 const Index
id = i * (N - 1) + j;
422 const Index v0 = i * n + j;
423 const Index v1 = i * n + (j + 1) % n;
424 const Index v2 = ((i + 1) % m) * n + j;
425 const Index v3 = ((i + 1) % m) * n + (j + 1) % n;
426 const Index c = n * m + id;
430 (vertices.row(v0) + vertices.row(v1) + vertices.row(v2) + vertices.row(v3)) / 4;
432 facets.row(
id * 4) << c, v0, v1;
433 facets.row(
id * 4 + 1) << c, v1, v3;
434 facets.row(
id * 4 + 2) << c, v3, v2;
435 facets.row(
id * 4 + 3) << c, v2, v0;
440 std::vector<Scalar> profile_arc_lengths(N, 0), path_arc_lengths(M, 0);
441 for (
auto i :
range(N - 1)) {
442 profile_arc_lengths[i + 1] = (profile.row(i + 1) - profile.row(i)).norm();
445 profile_arc_lengths.begin(),
446 profile_arc_lengths.end(),
447 profile_arc_lengths.begin());
448 if (profile_arc_lengths.back() > 0) {
449 for (
auto& l : profile_arc_lengths) {
450 l /= profile_arc_lengths.back();
453 path_arc_lengths[0] = 0;
455 Eigen::Matrix<Scalar, 1, 3> c = profile.colwise().mean();
456 for (Index i = 1; i < M; i++) {
457 path_arc_lengths[i] =
458 path_arc_lengths[i - 1] +
459 (transforms[i] * c.transpose() - transforms[i - 1] * c.transpose()).norm();
462 if (path_arc_lengths.back() > 0) {
463 for (
auto& l : path_arc_lengths) {
464 l /= path_arc_lengths.back();
469 AttributeArray latitude(N * M + num_quads, 1);
470 AttributeArray longitude(N * M + num_quads, 1);
471 IndexArray feature_indices(num_facets, 3);
473 for (Index i = 0; i < M; i++) {
474 for (Index j = 0; j < N; j++) {
475 latitude(i * N + j) = path_arc_lengths[i];
476 longitude(i * N + j) = profile_arc_lengths[j];
478 if (i < M - 1 && j < N - 1) {
479 const Index offset = i * (N - 1) + j;
480 latitude(M * N + offset) = (path_arc_lengths[i + 1] + path_arc_lengths[i]) / 2;
481 longitude(M * N + offset) =
482 (profile_arc_lengths[j + 1] + profile_arc_lengths[j]) / 2;
484 const Index v0 = i * N + j;
485 const Index v1 = i * N + j + 1;
486 const Index v2 = (i + 1) * N + j;
487 const Index v3 = (i + 1) * N + j + 1;
488 const Index c = M * N + offset;
490 feature_indices.row(offset * 4) << c, v0, v1;
491 feature_indices.row(offset * 4 + 1) << c, v1, v3;
492 feature_indices.row(offset * 4 + 2) << c, v3, v2;
493 feature_indices.row(offset * 4 + 3) << c, v2, v0;
498 auto mesh =
create_mesh(std::move(vertices), std::move(facets));
500 std::vector<Scalar> profile_turning_angles = internal::compute_turning_angles(profile);
501 internal::generate_uv(*mesh, profile, transforms, max_strip_len, profile_turning_angles);
502 internal::generate_normal(*mesh, N, lagrange::internal::pi / 4, profile_turning_angles);
504 mesh->add_indexed_attribute(
"latitude");
505 mesh->set_indexed_attribute(
"latitude", latitude, feature_indices);
506 mesh->add_indexed_attribute(
"longitude");
507 mesh->set_indexed_attribute(
"longitude", longitude, feature_indices);
510 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::SIDE);
534template <
typename MeshType>
535std::unique_ptr<MeshType> generate_swept_surface(
536 const VertexArrayOf<MeshType>& profile,
537 const VertexArrayOf<MeshType>& sweep_path,
538 ScalarOf<MeshType> max_strip_len = 0)
540 typename MeshType::VertexType profile_center =
541 (profile.colwise().minCoeff() + profile.colwise().maxCoeff()) / 2;
544 path.set_pivot(profile_center);
546 const auto& transforms = path.get_transforms();
547 return generate_swept_surface<MeshType>(
570template <
typename MeshType>
571std::unique_ptr<MeshType> generate_swept_surface(
572 const VertexArrayOf<MeshType>& profile,
573 const SweepPath<ScalarOf<MeshType>>& sweep_path,
574 ScalarOf<MeshType> max_strip_len = 0)
577 sweep_path.get_num_samples() >= 2,
578 "Please make sure sweep_path obj is sufficiently sampled.");
579 return generate_swept_surface<MeshType>(
581 sweep_path.get_transforms(),
582 sweep_path.get_offsets(),
584 sweep_path.is_closed());
587template <
typename VertexArray>
588std::vector<VertexArray> generate_swept_surface_latitude(
589 const VertexArray& profile,
590 const std::vector<Eigen::Transform<ScalarOf<VertexArray>, 3, Eigen::AffineCompact>>& transforms,
591 const std::vector<ScalarOf<VertexArray>>& offsets = {})
593 using Scalar = ScalarOf<VertexArray>;
594 std::function<VertexArray(
size_t)> offset_profile;
595 VertexArray offset_dirs;
596 if (!offsets.empty()) {
598 offsets.size() == transforms.size(),
599 "Transforms and offsets must be sampled consistently");
600 offset_dirs = internal::compute_offset_directions(profile);
601 offset_profile = [&](
size_t i) -> VertexArray {
602 const Scalar offset = offsets[i];
603 return profile + offset_dirs * offset;
606 offset_profile = [&](size_t) -> VertexArray {
return profile; };
609 const size_t num_transforms = transforms.size();
610 std::vector<VertexArray> latitudes(num_transforms);
612 tbb::parallel_for(
size_t(0), num_transforms, [&](
size_t i) {
613 const auto& T = transforms[i];
614 latitudes[i] = (T * offset_profile(i).transpose()).transpose();
619template <
typename VertexArray>
620std::vector<VertexArray> generate_swept_surface_latitude(
621 const VertexArray& profile,
622 SweepPath<ScalarOf<VertexArray>>& sweep_path)
624 return generate_swept_surface_latitude(
626 sweep_path.get_transforms(),
627 sweep_path.get_offsets());
630template <
typename VertexArray>
631std::vector<VertexArray> generate_swept_surface_longitude(
632 const VertexArray& profile,
633 const std::vector<Eigen::Transform<ScalarOf<VertexArray>, 3, Eigen::AffineCompact>>& transforms,
634 const std::vector<ScalarOf<VertexArray>>& offsets = {})
636 using Scalar = ScalarOf<VertexArray>;
637 using Index = size_t;
638 using VertexType = Eigen::Matrix<Scalar, 1, 3>;
640 std::function<VertexType(Index, Index)> offset_profile;
641 VertexArray offset_dirs;
642 if (!offsets.empty()) {
644 offsets.size() == transforms.size(),
645 "Transforms and offsets must be sampled consistently");
646 offset_dirs = internal::compute_offset_directions(profile);
647 offset_profile = [&](Index i, Index j) -> VertexType {
648 const Scalar offset = offsets[i];
649 return profile.row(j) + offset_dirs.row(j) * offset;
652 offset_profile = [&](Index, Index j) -> VertexType {
return profile.row(j); };
655 Index num_transforms = transforms.size();
656 Index profile_size =
static_cast<Index
>(profile.rows());
659 if ((profile.row(0) - profile.row(profile_size - 1)).norm() == 0) {
664 std::vector<VertexArray> longitudes(profile_size);
665 for (
auto i :
range(profile_size)) {
666 longitudes[i].resize(num_transforms, 3);
667 for (
auto j :
range(num_transforms)) {
668 longitudes[i].row(j) = (transforms[j] * offset_profile(j, i).transpose()).transpose();
675template <
typename VertexArray>
676std::vector<VertexArray> generate_swept_surface_longitude(
677 const VertexArray& profile,
678 SweepPath<ScalarOf<VertexArray>>& sweep_path)
680 return generate_swept_surface_longitude(
682 sweep_path.get_transforms(),
683 sweep_path.get_offsets());
Create sweep path based on a polyline.
Definition SweepPath.h:540
Abstract base class for sweep path.
Definition SweepPath.h:71
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:175
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:51
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