17#include <lagrange/Mesh.h>
18#include <lagrange/attributes/attribute_utils.h>
19#include <lagrange/bvh/zip_boundary.h>
20#include <lagrange/combine_mesh_list.h>
21#include <lagrange/compute_normal.h>
22#include <lagrange/create_mesh.h>
23#include <lagrange/internal/constants.h>
24#include <lagrange/legacy/inline.h>
25#include <lagrange/mesh_cleanup/remove_degenerate_triangles.h>
26#include <lagrange/packing/compute_rectangle_packing.h>
27#include <lagrange/primitive/generation_utils.h>
28#include <lagrange/utils/safe_cast.h>
38 using Index = uint32_t;
45 Scalar bevel_radius = 0;
49 Scalar start_sweep_angle = 0;
50 Scalar end_sweep_angle =
static_cast<Scalar
>(2 * lagrange::internal::pi);
51 Eigen::Matrix<Scalar, 3, 1> center{0, 0, 0};
52 bool with_top_cap =
true;
53 bool with_bottom_cap =
true;
54 bool with_cross_section =
true;
59 bool output_normals =
true;
79 Scalar
epsilon =
static_cast<Scalar
>(1e-6);
90 radius = std::max(radius,
static_cast<Scalar
>(0.0));
91 height = std::max(height,
static_cast<Scalar
>(0.0));
92 bevel_radius = std::min(
93 std::max(bevel_radius,
static_cast<Scalar
>(0.0)),
94 std::min(radius, height *
static_cast<Scalar
>(0.5)));
109template <
typename MeshType>
110std::unique_ptr<MeshType> generate_rounded_cylinder(RoundedCylinderConfig config)
112 using AttributeArray =
typename MeshType::AttributeArray;
113 using VertexArray =
typename MeshType::VertexArray;
114 using FacetArray =
typename MeshType::FacetArray;
115 using UVArray =
typename MeshType::UVArray;
116 using Index =
typename MeshType::Index;
117 using Scalar =
typename MeshType::Scalar;
118 using Point = Eigen::Matrix<Scalar, 1, 3>;
119 using Generator_function = std::function<Point(
Scalar)>;
121 std::vector<std::shared_ptr<MeshType>> meshes;
122 std::vector<GeometricProfile<VertexArray, Index>> profiles;
124 config.project_to_valid_range();
129 if (config.height < config.dist_threshold || config.radius < config.dist_threshold) {
130 auto mesh = create_empty_mesh<VertexArray, FacetArray>();
131 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::SIDE);
146 Scalar r = config.bevel_radius;
147 Scalar torus_start_angle = 2 * lagrange::internal::pi * 3 / 4;
148 Scalar radius_post_bevel = config.radius - r;
149 Scalar torus_slice = (lagrange::internal::pi / 2);
150 Scalar sweep_angle = compute_sweep_angle(config.start_sweep_angle, config.end_sweep_angle);
152 sweep_angle = std::min<Scalar>(2 * lagrange::internal::pi, sweep_angle);
157 auto bottom_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
160 Eigen::Matrix<Scalar, 3, 1>(0.0, r, 0.0),
163 auto bottom_torus_profile = generate_profile<MeshType, Point>(
164 bottom_torus_generator,
170 auto top_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
173 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height - r, 0.0),
176 auto top_torus_profile = generate_profile<MeshType, Point>(
181 Generator_function cylinder_generator = [&](
Scalar t) -> Point {
183 vert << config.radius, (config.height - 2 * r) * t + r, 0.0;
187 auto cylinder_profile = generate_profile<MeshType, Point>(
192 if (r > config.dist_threshold) {
193 profiles.push_back(bottom_torus_profile);
196 if (config.height > 2 * r + config.dist_threshold) {
197 profiles.push_back(cylinder_profile);
200 if (r > config.dist_threshold) {
201 profiles.push_back(top_torus_profile);
204 auto final_profile = lagrange::combine_geometric_profiles<VertexArray, Index>(profiles);
205 auto cylinder = lagrange::sweep<MeshType>(
214 config.start_sweep_angle,
218 auto top_cap = lagrange::generate_disk<MeshType>(
221 config.start_sweep_angle,
223 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height, 0.0),
225 auto bottom_cap = lagrange::generate_disk<MeshType>(
228 config.start_sweep_angle,
231 lagrange::set_uniform_semantic_label(*top_cap, PrimitiveSemanticLabel::TOP);
232 lagrange::set_uniform_semantic_label(*cylinder, PrimitiveSemanticLabel::SIDE);
233 lagrange::set_uniform_semantic_label(*bottom_cap, PrimitiveSemanticLabel::BOTTOM);
236 if (config.height > config.dist_threshold) {
237 meshes.push_back(std::move(cylinder));
240 if (radius_post_bevel > config.dist_threshold) {
241 if (config.with_top_cap) {
242 meshes.push_back(std::move(top_cap));
244 if (config.with_bottom_cap) {
245 meshes.push_back(std::move(bottom_cap));
250 if (config.with_cross_section && config.height > config.dist_threshold &&
251 sweep_angle < 2 * lagrange::internal::pi - config.epsilon) {
253 auto rotated_profile = lagrange::rotate_geometric_profile(
255 static_cast<Scalar>(config.start_sweep_angle));
258 auto num_samples = rotated_profile.num_samples;
259 auto end_profile = lagrange::rotate_geometric_profile(rotated_profile, sweep_angle);
262 VertexArray center_samples(num_samples, 3);
263 center_samples.block(0, 0, num_samples, 3) = rotated_profile.samples;
264 center_samples.col(0).array() = 0.0;
265 center_samples.col(2).array() = 0.0;
267 GeometricProfile<VertexArray, Index> center_profile(center_samples, num_samples);
268 std::vector<GeometricProfile<VertexArray, Index>> profiles_to_connect = {
273 lagrange::connect_geometric_profiles_with_facets<MeshType>(profiles_to_connect);
276 AttributeArray uvs(cross_section->get_num_vertices(), 2);
277 auto uv_samples = final_profile.samples.leftCols(2);
278 uvs.block(0, 0, num_samples, 2) = uv_samples;
279 uvs.col(0).array() *= -1.0;
280 uvs.block(num_samples, 0, num_samples, 2) = center_samples.leftCols(2);
281 uvs.block(2 * num_samples, 0, num_samples, 2) = uv_samples;
283 const auto xmin = uvs.col(0).minCoeff();
284 const auto ymin = uvs.col(1).minCoeff();
288 uvs.col(0).array() += -1.0 * xmin;
291 uvs.col(1).array() += -1.0 * ymin;
293 cross_section->initialize_uv(uvs, cross_section->get_facets());
295 lagrange::set_uniform_semantic_label(*cross_section, PrimitiveSemanticLabel::SIDE);
296 meshes.push_back(std::move(cross_section));
300 auto mesh = lagrange::combine_mesh_list(meshes,
true);
302 const auto bbox_diag = std::hypot(config.height, config.radius * 2);
303 mesh = lagrange::bvh::zip_boundary(*mesh, 1e-6 * bbox_diag);
307 if (config.radius > config.dist_threshold && config.height > config.dist_threshold) {
308 mesh = remove_degenerate_triangles(*mesh);
312 if (config.output_normals) {
319 typename MeshType::VertexArray vertices;
320 mesh->export_vertices(vertices);
321 vertices.col(1).array() -= config.height / 2;
322 vertices.rowwise() += config.center.transpose().template
cast<Scalar>();
323 mesh->import_vertices(vertices);
327 if (mesh->is_uv_initialized()) {
328 const auto uv_mesh = mesh->get_uv_mesh();
329 UVArray uvs = uv_mesh->get_vertices();
330 normalize_to_unit_box(uvs);
331 mesh->initialize_uv(uvs, uv_mesh->get_facets());
334 packing::compute_rectangle_packing(*mesh);
338template <
typename MeshType>
339std::unique_ptr<MeshType> generate_rounded_cylinder(
340 typename MeshType::Scalar radius,
341 typename MeshType::Scalar height,
342 typename MeshType::Scalar bevel_radius,
343 typename MeshType::Index num_radial_sections,
344 typename MeshType::Index num_bevel_segments,
345 typename MeshType::Scalar start_sweep_angle = 0.0,
346 typename MeshType::Scalar end_sweep_angle = 2 * lagrange::internal::pi,
347 typename MeshType::Index num_straight_segments = 1,
348 bool with_top_cap =
true,
349 bool with_bottom_cap =
true)
351 using Scalar =
typename RoundedCylinderConfig::Scalar;
352 using Index =
typename RoundedCylinderConfig::Index;
363 config.with_top_cap = with_top_cap;
364 config.with_bottom_cap = with_bottom_cap;
366 return generate_rounded_cylinder<MeshType>(std::move(config));
@ 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
SurfaceMesh< ToScalar, ToIndex > cast(const SurfaceMesh< FromScalar, FromIndex > &source_mesh, const AttributeFilter &convertible_attributes={}, std::vector< std::string > *converted_attributes_names=nullptr)
Cast a mesh to a mesh of different scalar and/or index type.
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
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.
Definition generate_rounded_cylinder.h:36
void project_to_valid_range()
Project config setting into valid range.
Definition generate_rounded_cylinder.h:88
Index num_radial_sections
Number of sections used for top/bottom disc.
Definition generate_rounded_cylinder.h:46
Scalar dist_threshold
Two vertices are considered coinciding if the distance between them is smaller than dist_threshold.
Definition generate_rounded_cylinder.h:68
Scalar angle_threshold
An edge is considered sharp if its dihedral angle is larger than angle_threshold.
Definition generate_rounded_cylinder.h:74
Scalar epsilon
Numerical tolerence used for comparing Scalar values.
Definition generate_rounded_cylinder.h:79
Index num_bevel_segments
Number of isolines used for rounded bevel.
Definition generate_rounded_cylinder.h:47
Index num_straight_segments
Number of isolines on the cylinder side.
Definition generate_rounded_cylinder.h:48