18#include <lagrange/Logger.h>
19#include <lagrange/Mesh.h>
20#include <lagrange/attributes/attribute_utils.h>
21#include <lagrange/bvh/zip_boundary.h>
22#include <lagrange/combine_mesh_list.h>
23#include <lagrange/compute_normal.h>
24#include <lagrange/create_mesh.h>
25#include <lagrange/internal/constants.h>
26#include <lagrange/legacy/inline.h>
27#include <lagrange/mesh_cleanup/remove_degenerate_triangles.h>
28#include <lagrange/packing/compute_rectangle_packing.h>
29#include <lagrange/primitive/generation_utils.h>
39template <
typename ScalarT>
40std::pair<ScalarT, ScalarT>
41get_max_cone_bevel(ScalarT radius_top, ScalarT radius_bottom, ScalarT height)
44 ScalarT psi = std::atan((radius_top - radius_bottom) / height);
45 ScalarT a1 = (ScalarT)(lagrange::internal::pi_2 + psi) * .5f;
46 ScalarT a2 = (ScalarT)(lagrange::internal::pi_2 - psi) * .5f;
48 ScalarT max_bevel_bottom = radius_bottom * std::tan(a1);
49 max_bevel_bottom = std::min(height * .5f, max_bevel_bottom);
51 ScalarT max_bevel_top =
52 radius_top * std::tan(a2);
53 max_bevel_top = std::min(height * .5f, max_bevel_top);
55 return std::pair<ScalarT, ScalarT>{max_bevel_top, max_bevel_bottom};
63 using Index = uint32_t;
66 Scalar radius_top = 0;
67 Scalar radius_bottom = 1;
69 Scalar bevel_radius_top = 0;
70 Scalar bevel_radius_bottom = 0;
71 Index num_radial_sections = 32;
72 Index num_segments_top = 1;
73 Index num_segments_bottom = 1;
74 Index num_straight_segments = 1;
75 Scalar start_sweep_angle = 0;
76 Scalar end_sweep_angle =
static_cast<Scalar
>(2 * lagrange::internal::pi);
77 Eigen::Matrix<Scalar, 3, 1> center{0, 0, 0};
78 bool with_cross_section =
true;
81 bool with_top_cap =
true;
82 bool with_bottom_cap =
true;
85 bool output_normals =
true;
105 void project_to_valid_range()
107 radius_top = std::max(radius_top, Scalar(0));
108 radius_bottom = std::max(radius_bottom, Scalar(0));
109 height = std::max(height, Scalar(0));
110 std::pair<Scalar, Scalar> max_acceptable_bevels =
111 get_max_cone_bevel(radius_top, radius_bottom, height);
113 std::min(std::max(bevel_radius_top, Scalar(0)), max_acceptable_bevels.first);
114 bevel_radius_bottom =
115 std::min(std::max(bevel_radius_bottom, Scalar(0)), max_acceptable_bevels.second);
116 num_radial_sections = std::max(num_radial_sections, Index(1));
117 num_segments_top = std::max(num_segments_top, Index(1));
118 num_segments_bottom = std::max(num_segments_bottom, Index(1));
119 num_straight_segments = std::max(num_straight_segments, Index(1));
123template <
typename MeshType>
126 using AttributeArray =
typename MeshType::AttributeArray;
127 using VertexArray =
typename MeshType::VertexArray;
128 using FacetArray =
typename MeshType::FacetArray;
129 using UVArray =
typename MeshType::UVArray;
130 using Index =
typename MeshType::Index;
131 using Scalar =
typename MeshType::Scalar;
132 using Point = Eigen::Matrix<Scalar, 1, 3>;
133 using Generator_function = std::function<Point(
Scalar)>;
135 std::vector<std::shared_ptr<MeshType>> meshes;
136 std::vector<GeometricProfile<VertexArray, Index>> profiles;
138 config.project_to_valid_range();
143 if (config.height < config.dist_threshold || (config.radius_top < config.dist_threshold &&
144 config.radius_bottom < config.dist_threshold)) {
145 auto mesh = create_empty_mesh<VertexArray, FacetArray>();
146 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::SIDE);
161 Scalar r1 = config.bevel_radius_bottom;
162 Scalar theta = std::atan2(config.height, fabs(config.radius_bottom - config.radius_top));
164 (
Scalar)(config.radius_bottom > config.radius_top ? 0.5 * theta
165 : 0.5 * (lagrange::internal::pi - theta));
166 Scalar base_start_angle = (
Scalar)(2 * lagrange::internal::pi * 3 / 4);
167 Scalar base_reduction = (
Scalar)(base_angle > 0.0 ? r1 / std::tan(base_angle) : 0.0);
168 Scalar base_radius_post_bevel = config.radius_bottom - base_reduction;
169 Scalar base_slice = (
Scalar)(lagrange::internal::pi - 2 * base_angle);
171 auto bottom_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
172 base_radius_post_bevel,
174 Eigen::Matrix<Scalar, 3, 1>(0.0, r1, 0.0),
177 auto bottom_torus_profile = generate_profile<MeshType, Point>(
178 bottom_torus_generator,
179 static_cast<Scalar>(config.num_segments_bottom));
184 Scalar r2 = config.bevel_radius_top;
185 Scalar top_angle = (
Scalar)(0.5 * (lagrange::internal::pi - 2 * base_angle));
186 Scalar top_start_angle = (
Scalar)(2 * top_angle - lagrange::internal::pi / 2);
187 Scalar top_reduction = (
Scalar)(top_angle > 0.0 ? r2 / std::tan(top_angle) : 0.0);
188 Scalar top_radius_post_bevel = config.radius_top - top_reduction;
189 Scalar top_slice = (
Scalar)(lagrange::internal::pi - 2 * top_angle);
192 Scalar base_height_offset = base_reduction * std::sin(2 * base_angle);
193 Scalar top_height_offset = top_reduction * std::sin(2 * base_angle);
194 Scalar cone_radius_bottom = config.radius_bottom - base_reduction * std::cos(2 * base_angle);
195 Scalar cone_radius_top = config.radius_top + top_reduction * std::cos(2 * base_angle);
197 auto top_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
198 top_radius_post_bevel,
200 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height - r2, 0.0),
203 auto top_torus_profile = generate_profile<MeshType, Point>(
209 Generator_function truncated_cone_generator = [&](
Scalar t) -> Point {
212 Scalar diff = cone_radius_top - cone_radius_bottom;
213 Scalar cone_radius = t * diff + cone_radius_bottom;
214 vert << cone_radius, (config.height - top_height_offset) * t + (1 - t) * base_height_offset,
219 auto truncated_cone_profile = generate_profile<MeshType, Point>(
220 truncated_cone_generator,
224 if (r1 > config.dist_threshold) {
225 profiles.push_back(bottom_torus_profile);
228 profiles.push_back(truncated_cone_profile);
230 if (r2 > config.dist_threshold) {
231 profiles.push_back(top_torus_profile);
234 auto final_profile = lagrange::combine_geometric_profiles<VertexArray, Index>(profiles);
236 Scalar sweep_angle = compute_sweep_angle(config.start_sweep_angle, config.end_sweep_angle);
237 sweep_angle = std::min((
Scalar)(2 * lagrange::internal::pi), sweep_angle);
239 auto truncated_cone = lagrange::sweep<MeshType>(
243 config.radius_bottom,
248 config.start_sweep_angle,
252 auto top_cap = lagrange::generate_disk<MeshType>(
253 top_radius_post_bevel,
255 config.start_sweep_angle,
257 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height, 0.0),
259 auto bottom_cap = lagrange::generate_disk<MeshType>(
260 base_radius_post_bevel,
262 config.start_sweep_angle,
265 lagrange::set_uniform_semantic_label(*top_cap, PrimitiveSemanticLabel::TOP);
266 lagrange::set_uniform_semantic_label(*truncated_cone, PrimitiveSemanticLabel::SIDE);
267 lagrange::set_uniform_semantic_label(*bottom_cap, PrimitiveSemanticLabel::BOTTOM);
270 if (config.height > config.dist_threshold) {
271 meshes.push_back(std::move(truncated_cone));
274 if (config.radius_top > config.dist_threshold && config.with_top_cap) {
275 meshes.push_back(std::move(top_cap));
278 if (config.radius_bottom > config.dist_threshold && config.with_bottom_cap) {
279 meshes.push_back(std::move(bottom_cap));
283 if (config.with_cross_section && config.height > config.dist_threshold &&
284 sweep_angle < 2 * lagrange::internal::pi - config.epsilon) {
286 auto rotated_profile = lagrange::rotate_geometric_profile(
288 static_cast<Scalar>(config.start_sweep_angle));
291 auto num_samples = rotated_profile.num_samples;
292 auto end_profile = lagrange::rotate_geometric_profile(rotated_profile, sweep_angle);
295 VertexArray center_samples(num_samples, 3);
296 center_samples.block(0, 0, num_samples, 3) = rotated_profile.samples;
297 center_samples.col(0).array() = 0.0;
298 center_samples.col(2).array() = 0.0;
300 GeometricProfile<VertexArray, Index> center_profile(center_samples, num_samples);
301 std::vector<GeometricProfile<VertexArray, Index>> profiles_to_connect = {
306 lagrange::connect_geometric_profiles_with_facets<MeshType>(profiles_to_connect);
309 AttributeArray uvs(cross_section->get_num_vertices(), 2);
310 auto uv_samples = final_profile.samples.leftCols(2);
311 uvs.block(0, 0, num_samples, 2) = uv_samples;
312 uvs.col(0).array() *= -1.0f;
313 uvs.block(num_samples, 0, num_samples, 2) = center_samples.leftCols(2);
314 uvs.block(2 * num_samples, 0, num_samples, 2) = uv_samples;
316 const auto xmin = uvs.col(0).minCoeff();
317 const auto ymin = uvs.col(1).minCoeff();
321 uvs.col(0).array() += -1.0f * xmin;
324 uvs.col(1).array() += -1.0f * ymin;
326 cross_section->initialize_uv(uvs, cross_section->get_facets());
328 lagrange::set_uniform_semantic_label(*cross_section, PrimitiveSemanticLabel::SIDE);
329 meshes.push_back(std::move(cross_section));
332 auto mesh = lagrange::combine_mesh_list(meshes,
true);
336 const auto bbox_diag =
337 std::hypot(config.height, std::max(config.radius_top, config.radius_bottom));
338 mesh = lagrange::bvh::zip_boundary(*mesh, 1e-6f * bbox_diag);
342 if ((config.radius_top > config.dist_threshold ||
343 config.radius_bottom > config.dist_threshold) &&
344 config.height > config.dist_threshold) {
345 mesh = remove_degenerate_triangles(*mesh);
349 if (config.output_normals) {
350 if (config.radius_top == 0 && config.bevel_radius_top == 0 &&
351 config.height > config.dist_threshold) {
352 const auto& vertices = mesh->get_vertices();
353 const auto max_y_value = vertices.col(1).maxCoeff();
354 std::vector<Index> cone_vertices;
356 if (vertices(vi, 1) > max_y_value - config.dist_threshold) {
357 cone_vertices.push_back(vi);
360 if (cone_vertices.size() != 1) {
362 "Generated cone has {} apexes. Expecting only 1.",
363 cone_vertices.size());
374 VertexArray vertices;
375 mesh->export_vertices(vertices);
376 vertices.col(1).array() -= config.height / 2;
377 vertices.rowwise() += config.center.transpose().template
cast<Scalar>();
378 mesh->import_vertices(vertices);
382 if (mesh->is_uv_initialized()) {
383 const auto uv_mesh = mesh->get_uv_mesh();
384 UVArray uvs = uv_mesh->get_vertices();
385 normalize_to_unit_box(uvs);
386 mesh->initialize_uv(uvs, uv_mesh->get_facets());
389 packing::compute_rectangle_packing(*mesh);
393template <
typename MeshType>
394std::unique_ptr<MeshType> generate_rounded_cone(
395 typename MeshType::Scalar radius_top,
396 typename MeshType::Scalar radius_bottom,
397 typename MeshType::Scalar height,
398 typename MeshType::Scalar bevel_radius_top,
399 typename MeshType::Scalar bevel_radius_bottom,
400 typename MeshType::Index num_radial_sections,
401 typename MeshType::Index num_segments_top,
402 typename MeshType::Index num_segments_bottom,
403 typename MeshType::Scalar start_sweep_angle = 0.0,
404 typename MeshType::Scalar end_sweep_angle = 2 * lagrange::internal::pi,
405 typename MeshType::Index num_straight_segments = 1,
406 bool with_top_cap =
true,
407 bool with_bottom_cap =
true)
409 using Scalar =
typename RoundedConeConfig::Scalar;
410 using Index =
typename RoundedConeConfig::Index;
424 config.with_top_cap = with_top_cap;
425 config.with_bottom_cap = with_bottom_cap;
427 return generate_rounded_cone<MeshType>(std::move(config));
Index get_num_vertices() const
Retrieves the number of vertices.
Definition SurfaceMesh.h:680
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
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
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
SurfaceMesh< Scalar, Index > generate_rounded_cone(RoundedConeOptions setting)
Generate a rounded cone mesh.
Definition generate_rounded_cone.cpp:149
Main namespace for Lagrange.
Definition generate_rounded_cone.h:61
Scalar dist_threshold
Two vertices are considered coinciding if the distance between them is smaller than dist_threshold.
Definition generate_rounded_cone.h:92
Scalar angle_threshold
An edge is considered sharp if its dihedral angle is larger than angle_threshold.
Definition generate_rounded_cone.h:98
Scalar epsilon
Numerical tolerence used for comparing Scalar values.
Definition generate_rounded_cone.h:103