Lagrange
Loading...
Searching...
No Matches
generate_torus.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
16#include <lagrange/Mesh.h>
17#include <lagrange/attributes/attribute_utils.h>
18#include <lagrange/bvh/zip_boundary.h>
19#include <lagrange/combine_mesh_list.h>
20#include <lagrange/compute_normal.h>
21#include <lagrange/internal/constants.h>
22#include <lagrange/legacy/inline.h>
23#include <lagrange/mesh_cleanup/remove_degenerate_triangles.h>
24#include <lagrange/packing/compute_rectangle_packing.h>
25#include <lagrange/primitive/generation_utils.h>
26#include <lagrange/utils/safe_cast.h>
27
28namespace lagrange {
29namespace primitive {
30LAGRANGE_LEGACY_INLINE
31namespace legacy {
32
34{
35 using Scalar = float;
36 using Index = uint32_t;
37
41 Scalar major_radius = 5;
42 Scalar minor_radius = 1;
43 Index ring_segments = 50;
44 Index pipe_segments = 50;
45 Eigen::Matrix<Scalar, 3, 1> center{0, 0, 0};
46 Scalar start_sweep_angle = 0;
47 Scalar end_sweep_angle = static_cast<Scalar>(2 * lagrange::internal::pi);
48 bool with_caps = true;
49
53 bool output_normals = true;
54
58
62 Scalar dist_threshold = static_cast<Scalar>(1e-6);
63
68 Scalar angle_threshold = static_cast<Scalar>(11 * lagrange::internal::pi / 180);
69
73 Scalar epsilon = static_cast<Scalar>(1e-6);
75
83 {
84 minor_radius = std::max(minor_radius, static_cast<Scalar>(0.0));
85 major_radius = std::max(major_radius, static_cast<Scalar>(minor_radius));
86 ring_segments = std::max(ring_segments, static_cast<Index>(3));
87 pipe_segments = std::max(pipe_segments, static_cast<Index>(3));
88 }
89};
90
91template <typename MeshType>
92std::unique_ptr<MeshType> generate_torus(TorusConfig config)
93{
94 using AttributeArray = typename MeshType::AttributeArray;
95 using VertexArray = typename MeshType::VertexArray;
96 using FacetArray = typename MeshType::FacetArray;
97 using UVArray = typename MeshType::UVArray;
98 using Scalar = typename MeshType::Scalar;
99 using Index = typename MeshType::Index;
100 using Point = Eigen::Matrix<Scalar, 1, 3>;
101 using Generator_function = std::function<Point(Scalar)>;
102
103 config.project_to_valid_range();
104
105 /*
106 * Handle Empty Mesh
107 */
108 if (config.major_radius < config.dist_threshold) {
109 auto mesh = create_empty_mesh<VertexArray, FacetArray>();
110 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::SIDE);
111 return mesh;
112 }
113
114 std::vector<std::shared_ptr<MeshType>> meshes;
115 // Init defaults
116 Scalar torus_start_angle = lagrange::internal::pi; // Inner UV Seam
117 Scalar torus_slice = 2 * lagrange::internal::pi;
118 Scalar sweep_angle = compute_sweep_angle(config.start_sweep_angle, config.end_sweep_angle);
119 // take into account numerical errors
120 sweep_angle = std::min<Scalar>(2 * lagrange::internal::pi, sweep_angle);
121
126 Generator_function torus_generator = lagrange::partial_torus_generator<Scalar, Point>(
127 config.major_radius,
128 config.minor_radius,
129 config.center.template cast<Scalar>(),
130 torus_start_angle,
131 torus_slice);
132 auto torus_profile = generate_profile<MeshType, Point>(torus_generator, config.pipe_segments);
133 auto torus_mesh = lagrange::sweep<MeshType>(
134 {torus_profile},
135 safe_cast<Index>(config.ring_segments),
136 config.major_radius,
137 config.major_radius,
138 0.0,
139 0.0,
140 torus_slice,
141 torus_slice,
142 config.start_sweep_angle,
143 sweep_angle);
144
145 meshes.push_back(std::move(torus_mesh));
146
147
148 // Again allow some tolerance when comparing to 2*lagrange::internal::pi
149 if (config.with_caps && config.major_radius > config.dist_threshold &&
150 sweep_angle < 2 * lagrange::internal::pi - config.epsilon) {
151 using Vector3S = Eigen::Matrix<Scalar, 3, 1>;
152 // Rotate profile by start angle for slicing
153 auto rotated_profile = lagrange::rotate_geometric_profile(
154 torus_profile,
155 static_cast<Scalar>(config.start_sweep_angle));
156 auto end_profile = lagrange::rotate_geometric_profile(rotated_profile, sweep_angle);
157 auto rotation_start = Eigen::AngleAxis<Scalar>(config.start_sweep_angle, Vector3S::UnitY());
158 auto rotation_subtended = Eigen::AngleAxis<Scalar>(sweep_angle, Vector3S::UnitY());
159
160 auto start_sample = torus_profile.samples.row(0);
161 auto cross_section_center =
162 Vector3S(start_sample[0] + config.minor_radius, start_sample[1], start_sample[2]);
163 auto center_start = rotation_start * cross_section_center;
164 auto center_end = rotation_subtended * center_start;
165
166 auto cross_section_start =
167 lagrange::fan_triangulate_profile<MeshType>(rotated_profile, center_start, true);
168 auto cross_section_end =
169 lagrange::fan_triangulate_profile<MeshType>(end_profile, center_end);
170
171 // UV mapping
172 AttributeArray uvs(cross_section_start->get_num_vertices(), 2);
173 auto uv_samples = torus_profile.samples.leftCols(2);
174 uvs.row(0) << cross_section_center[0], cross_section_center[1];
175 uvs.block(1, 0, torus_profile.num_samples, 2) = uv_samples;
176
177 const auto xmin = uvs.col(0).minCoeff();
178 const auto ymin = uvs.col(1).minCoeff();
179
180 // Recenter uv coordinates to begin from (0,0)
181 if (xmin < 0.0) {
182 uvs.col(0).array() += -1.0 * xmin;
183 }
184 if (ymin < 0.0) {
185 uvs.col(1).array() += -1.0 * ymin;
186 }
187
188 cross_section_start->initialize_uv(uvs, cross_section_start->get_facets());
189 cross_section_end->initialize_uv(uvs, cross_section_end->get_facets());
190
191 meshes.push_back(std::move(cross_section_start));
192 meshes.push_back(std::move(cross_section_end));
193 }
194
195 // Combine all meshes
196 auto mesh = lagrange::combine_mesh_list(meshes, true);
197 const auto& vertices = mesh->get_vertices();
198 const auto bbox_diag = (vertices.colwise().maxCoeff() - vertices.colwise().minCoeff()).norm();
199
200 mesh = lagrange::bvh::zip_boundary(
201 *mesh,
202 std::min(
203 static_cast<Scalar>(1e-6 * bbox_diag),
204 static_cast<Scalar>(config.dist_threshold)));
205
206 // Add normals
207 if (config.output_normals) {
208 compute_normal(*mesh, config.angle_threshold);
209 la_runtime_assert(mesh->has_indexed_attribute("normal"));
210 }
211
212 // Normalize UVs
213 if (mesh->is_uv_initialized()) {
214 const auto uv_mesh = mesh->get_uv_mesh();
215 UVArray uvs = uv_mesh->get_vertices();
216 normalize_to_unit_box(uvs);
217 mesh->initialize_uv(uvs, uv_mesh->get_facets());
218 }
219
220 // Clean up mesh
221 mesh = remove_degenerate_triangles(*mesh);
222
223 // Set uniform semantic label
224 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::TOP);
225
226 packing::compute_rectangle_packing(*mesh);
227 return mesh;
228}
229
230template <typename MeshType>
231std::unique_ptr<MeshType> generate_torus(
232 typename MeshType::Scalar major_radius,
233 typename MeshType::Scalar minor_radius,
234 typename MeshType::Index ring_segments = 50,
235 typename MeshType::Index pipe_segments = 50,
236 Eigen::Matrix<typename MeshType::Scalar, 3, 1> center =
237 Eigen::Matrix<typename MeshType::Scalar, 3, 1>(0.0, 0.0, 0.0),
238 typename MeshType::Scalar start_sweep_angle = 0.0,
239 typename MeshType::Scalar end_sweep_angle = 2 * lagrange::internal::pi)
240{
241 using Scalar = typename TorusConfig::Scalar;
242 using Index = typename TorusConfig::Index;
243
244 TorusConfig config;
245 config.major_radius = safe_cast<Scalar>(major_radius);
246 config.minor_radius = safe_cast<Scalar>(minor_radius);
247 config.ring_segments = safe_cast<Index>(ring_segments);
248 config.pipe_segments = safe_cast<Index>(pipe_segments);
249 config.center = center.template cast<Scalar>();
250 config.start_sweep_angle = safe_cast<Scalar>(start_sweep_angle);
251 config.end_sweep_angle = safe_cast<Scalar>(end_sweep_angle);
252
253 return generate_torus<MeshType>(std::move(config));
254}
255} // namespace legacy
256} // namespace primitive
257} // 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
SurfaceMesh< Scalar, Index > generate_torus(TorusOptions setting)
Generate a torus mesh.
Definition generate_torus.cpp:42
Main namespace for Lagrange.
Definition generate_torus.h:34
void project_to_valid_range()
Project config setting into valid range.
Definition generate_torus.h:82
Scalar dist_threshold
Two vertices are considered coinciding iff the distance between them is smaller than dist_threshold.
Definition generate_torus.h:62
Scalar angle_threshold
An edge is considered sharp if its dihedral angle is larger than angle_threshold.
Definition generate_torus.h:68
Scalar epsilon
Numerical tolerence used for comparing Scalar values.
Definition generate_torus.h:73