Lagrange
Loading...
Searching...
No Matches
generate_rounded_cone.h
1/*
2 * Copyright 2019 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <algorithm>
15#include <limits>
16#include <vector>
17
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>
30
31
32namespace lagrange {
33namespace primitive {
34LAGRANGE_LEGACY_INLINE
35namespace legacy {
36
37namespace {
38
39template <typename ScalarT>
40std::pair<ScalarT, ScalarT>
41get_max_cone_bevel(ScalarT radius_top, ScalarT radius_bottom, ScalarT height)
42{
43 // angle between the cone slope and the vertical line (0 for cylinders)
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;
47
48 ScalarT max_bevel_bottom = radius_bottom * std::tan(a1); // max bevel against radius and slope
49 max_bevel_bottom = std::min(height * .5f, max_bevel_bottom); // also check against height
50
51 ScalarT max_bevel_top =
52 radius_top * std::tan(a2); // max bevel against other radius and inverse slope
53 max_bevel_top = std::min(height * .5f, max_bevel_top); // also check against height
54
55 return std::pair<ScalarT, ScalarT>{max_bevel_top, max_bevel_bottom};
56}
57
58} // namespace
59
61{
62 using Scalar = float;
63 using Index = uint32_t;
64
65 // Shape parameters.
66 Scalar radius_top = 0;
67 Scalar radius_bottom = 1;
68 Scalar height = 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;
79
80 // Cap parameters
81 bool with_top_cap = true;
82 bool with_bottom_cap = true;
83
84 // Output parameters.
85 bool output_normals = true;
86
87 // Tolerances.
92 Scalar dist_threshold = static_cast<Scalar>(1e-6);
93
98 Scalar angle_threshold = static_cast<Scalar>(11 * lagrange::internal::pi / 180);
99
103 Scalar epsilon = static_cast<Scalar>(1e-6);
104
105 void project_to_valid_range()
106 {
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);
112 bevel_radius_top =
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));
120 }
121};
122
123template <typename MeshType>
124std::unique_ptr<MeshType> generate_rounded_cone(RoundedConeConfig config)
125{
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)>;
134
135 std::vector<std::shared_ptr<MeshType>> meshes;
136 std::vector<GeometricProfile<VertexArray, Index>> profiles;
137
138 config.project_to_valid_range();
139
140 /*
141 * Handle Empty Mesh
142 */
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);
147 return mesh;
148 }
149
150 /*
151 /\
152 / \
153 / \
154 r₁(______)r₁
155
156 */
157
158 /*
159 * Generate Torus for base with radius = r₁ starting from 270 degrees
160 */
161 Scalar r1 = config.bevel_radius_bottom;
162 Scalar theta = std::atan2(config.height, fabs(config.radius_bottom - config.radius_top));
163 Scalar base_angle =
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);
170
171 auto bottom_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
172 base_radius_post_bevel,
173 r1,
174 Eigen::Matrix<Scalar, 3, 1>(0.0, r1, 0.0),
175 base_start_angle,
176 base_slice);
177 auto bottom_torus_profile = generate_profile<MeshType, Point>(
178 bottom_torus_generator,
179 static_cast<Scalar>(config.num_segments_bottom));
180
181 /*
182 * Generate Torus for top with radius = r₂ ending at 90 degrees
183 */
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);
190
191 // Generate Cone with bevel parameters
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);
196
197 auto top_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
198 top_radius_post_bevel,
199 r2,
200 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height - r2, 0.0),
201 top_start_angle,
202 top_slice);
203 auto top_torus_profile = generate_profile<MeshType, Point>(
204 top_torus_generator,
205 safe_cast<Index>(config.num_segments_top));
206
207
208 // Bottom of cone is origin. Cone is created around y axis
209 Generator_function truncated_cone_generator = [&](Scalar t) -> Point {
210 // Calculate the radius of the current row
211 Point vert;
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,
215 0.0;
216 return vert;
217 };
218
219 auto truncated_cone_profile = generate_profile<MeshType, Point>(
220 truncated_cone_generator,
221 safe_cast<Index>(config.num_straight_segments));
222
223 // Stitch profiles for different parts top-down
224 if (r1 > config.dist_threshold) {
225 profiles.push_back(bottom_torus_profile);
226 }
227
228 profiles.push_back(truncated_cone_profile);
229
230 if (r2 > config.dist_threshold) {
231 profiles.push_back(top_torus_profile);
232 }
233
234 auto final_profile = lagrange::combine_geometric_profiles<VertexArray, Index>(profiles);
235
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);
238
239 auto truncated_cone = lagrange::sweep<MeshType>(
240 final_profile,
241 safe_cast<Index>(config.num_radial_sections),
242 config.radius_top,
243 config.radius_bottom,
244 r2,
245 r1,
246 top_slice,
247 base_slice,
248 config.start_sweep_angle,
249 sweep_angle);
250
251 // Disk is in x-z plane (z is negative in clockwise direction)
252 auto top_cap = lagrange::generate_disk<MeshType>(
253 top_radius_post_bevel,
254 safe_cast<Index>(config.num_radial_sections),
255 config.start_sweep_angle,
256 sweep_angle,
257 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height, 0.0),
258 false);
259 auto bottom_cap = lagrange::generate_disk<MeshType>(
260 base_radius_post_bevel,
261 safe_cast<Index>(config.num_radial_sections),
262 config.start_sweep_angle,
263 sweep_angle);
264
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);
268
269 // Avoid generating degenerate geometry
270 if (config.height > config.dist_threshold) {
271 meshes.push_back(std::move(truncated_cone));
272 }
273
274 if (config.radius_top > config.dist_threshold && config.with_top_cap) {
275 meshes.push_back(std::move(top_cap));
276 }
277
278 if (config.radius_bottom > config.dist_threshold && config.with_bottom_cap) {
279 meshes.push_back(std::move(bottom_cap));
280 }
281
282 // Again allow some tolerance when comparing to 2*lagrange::internal::pi
283 if (config.with_cross_section && config.height > config.dist_threshold &&
284 sweep_angle < 2 * lagrange::internal::pi - config.epsilon) {
285 // Rotate profile by start angle for slicing
286 auto rotated_profile = lagrange::rotate_geometric_profile(
287 final_profile,
288 static_cast<Scalar>(config.start_sweep_angle));
289
290 // It's a slice, close cross sections of the mesh
291 auto num_samples = rotated_profile.num_samples;
292 auto end_profile = lagrange::rotate_geometric_profile(rotated_profile, sweep_angle);
293
294 // Project cone profile on Y axis
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;
299
300 GeometricProfile<VertexArray, Index> center_profile(center_samples, num_samples);
301 std::vector<GeometricProfile<VertexArray, Index>> profiles_to_connect = {
302 end_profile,
303 center_profile,
304 rotated_profile};
305 auto cross_section =
306 lagrange::connect_geometric_profiles_with_facets<MeshType>(profiles_to_connect);
307
308 // UV mapping
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;
315
316 const auto xmin = uvs.col(0).minCoeff();
317 const auto ymin = uvs.col(1).minCoeff();
318
319 // Recenter uv coordinates to begin from (0,0)
320 if (xmin < 0) {
321 uvs.col(0).array() += -1.0f * xmin;
322 }
323 if (ymin < 0) {
324 uvs.col(1).array() += -1.0f * ymin;
325 }
326 cross_section->initialize_uv(uvs, cross_section->get_facets());
327
328 lagrange::set_uniform_semantic_label(*cross_section, PrimitiveSemanticLabel::SIDE);
329 meshes.push_back(std::move(cross_section));
330 }
331 // Combine all meshes
332 auto mesh = lagrange::combine_mesh_list(meshes, true);
333
334 // Zip boundary.
335 {
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);
339 }
340
341 // Clean up mesh
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);
346 }
347
348 // Add corner normals
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;
355 for (auto vi : range(mesh->get_num_vertices())) {
356 if (vertices(vi, 1) > max_y_value - config.dist_threshold) {
357 cone_vertices.push_back(vi);
358 }
359 }
360 if (cone_vertices.size() != 1) {
361 logger().warn(
362 "Generated cone has {} apexes. Expecting only 1.",
363 cone_vertices.size());
364 }
365 compute_normal(*mesh, config.angle_threshold, cone_vertices);
366 } else {
367 compute_normal(*mesh, config.angle_threshold);
368 }
369 la_runtime_assert(mesh->has_indexed_attribute("normal"));
370 }
371
372 // Transform center
373 {
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);
379 }
380
381 // Normalize UVs
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());
387 }
388
389 packing::compute_rectangle_packing(*mesh);
390 return mesh;
391}
392
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)
408{
409 using Scalar = typename RoundedConeConfig::Scalar;
410 using Index = typename RoundedConeConfig::Index;
411
412 RoundedConeConfig config;
413 config.radius_top = safe_cast<Scalar>(radius_top);
414 config.radius_bottom = safe_cast<Scalar>(radius_bottom);
415 config.height = safe_cast<Scalar>(height);
416 config.bevel_radius_top = safe_cast<Scalar>(bevel_radius_top);
417 config.bevel_radius_bottom = safe_cast<Scalar>(bevel_radius_bottom);
418 config.num_radial_sections = safe_cast<Index>(num_radial_sections);
419 config.num_segments_top = safe_cast<Index>(num_segments_top);
420 config.num_segments_bottom = safe_cast<Index>(num_segments_bottom);
421 config.start_sweep_angle = safe_cast<Scalar>(start_sweep_angle);
422 config.end_sweep_angle = safe_cast<Scalar>(end_sweep_angle);
423 config.num_straight_segments = safe_cast<Index>(num_straight_segments);
424 config.with_top_cap = with_top_cap;
425 config.with_bottom_cap = with_bottom_cap;
426
427 return generate_rounded_cone<MeshType>(std::move(config));
428}
429} // namespace legacy
430} // namespace primitive
431} // namespace lagrange
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