Lagrange
Loading...
Searching...
No Matches
generate_rounded_cylinder.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 <vector>
16
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>
29
30namespace lagrange {
31namespace primitive {
32LAGRANGE_LEGACY_INLINE
33namespace legacy {
34
36{
37 using Scalar = float;
38 using Index = uint32_t;
39
43 Scalar radius = 1;
44 Scalar height = 1;
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;
55
59 bool output_normals = true;
60
64
68 Scalar dist_threshold = static_cast<Scalar>(1e-6);
69
74 Scalar angle_threshold = static_cast<Scalar>(11 * lagrange::internal::pi / 180);
75
79 Scalar epsilon = static_cast<Scalar>(1e-6);
81
89 {
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)));
95 num_radial_sections = std::max(num_radial_sections, static_cast<Index>(1));
96 num_bevel_segments = std::max(num_bevel_segments, static_cast<Index>(1));
97 num_straight_segments = std::max(num_straight_segments, static_cast<Index>(1));
98 }
99};
100
109template <typename MeshType>
110std::unique_ptr<MeshType> generate_rounded_cylinder(RoundedCylinderConfig config)
111{
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)>;
120
121 std::vector<std::shared_ptr<MeshType>> meshes;
122 std::vector<GeometricProfile<VertexArray, Index>> profiles;
123
124 config.project_to_valid_range();
125
126 /*
127 * Handle Empty Mesh
128 */
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);
132 return mesh;
133 }
134
135 /*
136 r(¯¯¯¯¯¯)r
137 | |
138 | |
139 | |
140 | |
141 r(______)r, theta = pi/2
142
143 */
144
145
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);
151 // take into account numerical errors
152 sweep_angle = std::min<Scalar>(2 * lagrange::internal::pi, sweep_angle);
153
154 /*
155 *Generate Torus for base with radius = r starting from 270 degrees
156 */
157 auto bottom_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
158 radius_post_bevel,
159 r,
160 Eigen::Matrix<Scalar, 3, 1>(0.0, r, 0.0),
161 torus_start_angle,
162 torus_slice);
163 auto bottom_torus_profile = generate_profile<MeshType, Point>(
164 bottom_torus_generator,
165 safe_cast<Index>(config.num_bevel_segments));
166
167 /*
168 *Generate Torus for top with radius = r starting from 0 degrees
169 */
170 auto top_torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
171 radius_post_bevel,
172 r,
173 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height - r, 0.0),
174 0.0,
175 torus_slice);
176 auto top_torus_profile = generate_profile<MeshType, Point>(
177 top_torus_generator,
178 safe_cast<Index>(config.num_bevel_segments));
179
180 // Bottom of cylinder is origin. Cylinder is created around y axis
181 Generator_function cylinder_generator = [&](Scalar t) -> Point {
182 Point vert;
183 vert << config.radius, (config.height - 2 * r) * t + r, 0.0;
184 return vert;
185 };
186
187 auto cylinder_profile = generate_profile<MeshType, Point>(
188 cylinder_generator,
189 safe_cast<Index>(config.num_straight_segments));
190
191 // Stitch profiles for different parts top-down
192 if (r > config.dist_threshold) {
193 profiles.push_back(bottom_torus_profile);
194 }
195
196 if (config.height > 2 * r + config.dist_threshold) {
197 profiles.push_back(cylinder_profile);
198 }
199
200 if (r > config.dist_threshold) {
201 profiles.push_back(top_torus_profile);
202 }
203
204 auto final_profile = lagrange::combine_geometric_profiles<VertexArray, Index>(profiles);
205 auto cylinder = lagrange::sweep<MeshType>(
206 final_profile,
207 safe_cast<Index>(config.num_radial_sections),
208 config.radius,
209 config.radius,
210 r,
211 r,
212 torus_slice,
213 torus_slice,
214 config.start_sweep_angle,
215 sweep_angle);
216
217 // Disk is in x-z plane (z is negative in clockwise direction)
218 auto top_cap = lagrange::generate_disk<MeshType>(
219 radius_post_bevel,
220 safe_cast<Index>(config.num_radial_sections),
221 config.start_sweep_angle,
222 sweep_angle,
223 Eigen::Matrix<Scalar, 3, 1>(0.0, config.height, 0.0),
224 false);
225 auto bottom_cap = lagrange::generate_disk<MeshType>(
226 radius_post_bevel,
227 safe_cast<Index>(config.num_radial_sections),
228 config.start_sweep_angle,
229 sweep_angle);
230
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);
234
235 // Avoid generating degenerate geometry
236 if (config.height > config.dist_threshold) {
237 meshes.push_back(std::move(cylinder));
238 }
239
240 if (radius_post_bevel > config.dist_threshold) {
241 if (config.with_top_cap) {
242 meshes.push_back(std::move(top_cap));
243 }
244 if (config.with_bottom_cap) {
245 meshes.push_back(std::move(bottom_cap));
246 }
247 }
248
249 // Again allow some tolerance when comparing to 2*lagrange::internal::pi
250 if (config.with_cross_section && config.height > config.dist_threshold &&
251 sweep_angle < 2 * lagrange::internal::pi - config.epsilon) {
252 // Rotate profile by start angle for slicing
253 auto rotated_profile = lagrange::rotate_geometric_profile(
254 final_profile,
255 static_cast<Scalar>(config.start_sweep_angle));
256
257 // It's a slice, close cross sections of the mesh
258 auto num_samples = rotated_profile.num_samples;
259 auto end_profile = lagrange::rotate_geometric_profile(rotated_profile, sweep_angle);
260
261 // Project cylinder profile on Y axis
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;
266
267 GeometricProfile<VertexArray, Index> center_profile(center_samples, num_samples);
268 std::vector<GeometricProfile<VertexArray, Index>> profiles_to_connect = {
269 end_profile,
270 center_profile,
271 rotated_profile};
272 auto cross_section =
273 lagrange::connect_geometric_profiles_with_facets<MeshType>(profiles_to_connect);
274
275 // UV mapping
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;
282
283 const auto xmin = uvs.col(0).minCoeff();
284 const auto ymin = uvs.col(1).minCoeff();
285
286 // Recenter uv coordinates to begin from (0,0)
287 if (xmin < 0.0) {
288 uvs.col(0).array() += -1.0 * xmin;
289 }
290 if (ymin < 0.0) {
291 uvs.col(1).array() += -1.0 * ymin;
292 }
293 cross_section->initialize_uv(uvs, cross_section->get_facets());
294
295 lagrange::set_uniform_semantic_label(*cross_section, PrimitiveSemanticLabel::SIDE);
296 meshes.push_back(std::move(cross_section));
297 }
298
299 // Combine all meshes
300 auto mesh = lagrange::combine_mesh_list(meshes, true);
301 {
302 const auto bbox_diag = std::hypot(config.height, config.radius * 2);
303 mesh = lagrange::bvh::zip_boundary(*mesh, 1e-6 * bbox_diag);
304 }
305
306 // Clean up mesh
307 if (config.radius > config.dist_threshold && config.height > config.dist_threshold) {
308 mesh = remove_degenerate_triangles(*mesh);
309 }
310
311 // Add normals
312 if (config.output_normals) {
313 compute_normal(*mesh, config.angle_threshold);
314 la_runtime_assert(mesh->has_indexed_attribute("normal"));
315 }
316
317 // Transform center
318 {
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);
324 }
325
326 // Normalize UVs
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());
332 }
333
334 packing::compute_rectangle_packing(*mesh);
335 return mesh;
336}
337
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)
350{
351 using Scalar = typename RoundedCylinderConfig::Scalar;
352 using Index = typename RoundedCylinderConfig::Index;
353
355 config.radius = safe_cast<Scalar>(radius);
356 config.height = safe_cast<Scalar>(height);
357 config.bevel_radius = safe_cast<Scalar>(bevel_radius);
358 config.num_radial_sections = safe_cast<Index>(num_radial_sections);
359 config.num_bevel_segments = safe_cast<Index>(num_bevel_segments);
360 config.start_sweep_angle = safe_cast<Scalar>(start_sweep_angle);
361 config.end_sweep_angle = safe_cast<Scalar>(end_sweep_angle);
362 config.num_straight_segments = safe_cast<Index>(num_straight_segments);
363 config.with_top_cap = with_top_cap;
364 config.with_bottom_cap = with_bottom_cap;
365
366 return generate_rounded_cylinder<MeshType>(std::move(config));
367}
368} // namespace legacy
369} // namespace primitive
370} // namespace lagrange
@ 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