Lagrange
Loading...
Searching...
No Matches
generate_sphere.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/bvh/zip_boundary.h>
18#include <lagrange/compute_normal.h>
19#include <lagrange/create_mesh.h>
20#include <lagrange/internal/constants.h>
21#include <lagrange/legacy/inline.h>
22#include <lagrange/packing/compute_rectangle_packing.h>
23#include <lagrange/primitive/generation_utils.h>
24#include <lagrange/utils/safe_cast.h>
25#include <lagrange/utils/warning.h>
26
27namespace lagrange {
28namespace primitive {
29LAGRANGE_LEGACY_INLINE
30namespace legacy {
31
33
35{
36 using Scalar = float;
37 using Index = uint32_t;
38
42 Scalar radius = 1;
43
48 [[deprecated]] Scalar sweep_angle = static_cast<Scalar>(2 * lagrange::internal::pi);
49 Scalar start_sweep_angle = 0;
50 Scalar end_sweep_angle = static_cast<Scalar>(2 * lagrange::internal::pi);
51 Index num_longitude_sections = 32;
52 Index num_latitude_sections = 32;
53 Eigen::Matrix<Scalar, 3, 1> center{0, 0, 0};
54 bool with_cross_section = true;
55
59 bool output_normals = true;
60
64
68 Scalar angle_threshold = static_cast<Scalar>(11 * lagrange::internal::pi / 180);
69
73 Scalar epsilon = static_cast<Scalar>(1e-6);
75
83 {
84 radius = std::max(radius, static_cast<Scalar>(0.0));
85 num_longitude_sections = std::max(num_longitude_sections, static_cast<Index>(3));
86 num_latitude_sections = std::max(num_latitude_sections, static_cast<Index>(3));
87
89 // If sweep_angle is set but start/end angles are default, update them
90 if (sweep_angle != static_cast<Scalar>(2 * lagrange::internal::pi) &&
91 start_sweep_angle == 0 &&
92 end_sweep_angle == static_cast<Scalar>(2 * lagrange::internal::pi)) {
93 end_sweep_angle = start_sweep_angle + sweep_angle;
94 }
95 LA_IGNORE_DEPRECATION_WARNING_END
96 }
97
102 Scalar get_effective_sweep_angle() const { return end_sweep_angle - start_sweep_angle; }
103};
104
105template <typename MeshType>
106std::unique_ptr<MeshType> generate_sphere(SphereConfig config)
107{
108 using Scalar = typename MeshType::Scalar;
109 using Index = typename MeshType::Index;
110 using VertexArray = typename MeshType::VertexArray;
111 using FacetArray = typename MeshType::FacetArray;
112 using AttributeArray = typename MeshType::AttributeArray;
113 using UVArray = typename MeshType::UVArray;
114 using UVIndices = typename MeshType::UVIndices;
115
116 config.project_to_valid_range();
117
118 // Use get_effective_sweep_angle() instead of sweep_angle directly
119 bool is_closed =
120 config.get_effective_sweep_angle() >= (2 * lagrange::internal::pi - config.epsilon);
121
122 const Index repeat_x = safe_cast<Index>(config.num_longitude_sections);
123 const Index repeat_y = safe_cast<Index>(config.num_latitude_sections);
124 const Index row_size = is_closed ? repeat_x : (repeat_x + 1);
125 const Index num_vertices = row_size * (repeat_y - 1) + 2 + (is_closed ? 0 : 1);
126 const Index num_uvs = (repeat_x + 1) * (repeat_y + 1) - 2 +
127 (is_closed || !config.with_cross_section ? 0 : 2 * (repeat_y + 2));
128 const Index num_triangles = repeat_x * (repeat_y - 1) * 2 +
129 (is_closed || !config.with_cross_section ? 0 : repeat_y * 2);
130
131 VertexArray vertices(num_vertices, 3);
132 FacetArray facets(num_triangles, 3);
133 UVArray uvs(num_uvs, 2);
134 UVIndices uv_indices(num_triangles, 3);
135 AttributeArray normals(num_vertices, 3);
136
137 auto get_vertex_index = [&](Index rx, Index ry) {
138 Index vidx(0);
139 if (ry == 0) {
140 vidx = 0; // North pole.
141 } else if (ry == repeat_y) {
142 vidx = 1; // South pole.
143 } else {
144 vidx = 2 + row_size * (ry - 1) + (is_closed ? (rx % repeat_x) : rx);
145 }
146 return vidx;
147 };
148
149 auto get_uv_vertex_index = [&](Index rx, Index ry) {
150 // The UV coordines of a sphere forms a rectangular grid. However, due
151 // to triangulation, the top left and bottom right corner are not used
152 // (marked with "o").
153 //
154 // o + + + <--- ry == repeat_y
155 // / | / | / |
156 // +---+---+---+
157 // | / | / | / |
158 // +---+---+---+
159 // | / | / | / |
160 // +---+---+---+
161 // | / | / | /
162 // + + + o <--- ry == 0
163 //
164 // Thus, an adjustment to the index is used to skip those unused grid
165 // points. Because Index type can be unsigned, we opt to compute the
166 // positive adjutment and substract it from the result.
167 const Index adjustment = ry > 0 ? (ry == repeat_y && rx > 0 ? 2 : 1) : 0;
168 return ry * (repeat_x + 1) + rx - adjustment;
169 };
170
171 Scalar v_max = 1;
172 Scalar u_max = (config.get_effective_sweep_angle() / lagrange::internal::pi) * v_max;
173 for (auto ry : range(repeat_y + 1)) {
174 for (auto rx : range(repeat_x + 1)) {
175 const Scalar theta =
176 ((Scalar)rx / (Scalar)repeat_x) * config.get_effective_sweep_angle() +
177 config.start_sweep_angle;
178 const Scalar phi = ((Scalar)ry / (Scalar)repeat_y) * lagrange::internal::pi;
179 const Scalar x = std::cos(theta) * std::sin(phi);
180 const Scalar y = std::cos(phi);
181 const Scalar z = std::sin(theta) * std::sin(phi);
182
183 const Index vertex_index = get_vertex_index(rx, ry);
184
185 // vertices
186 vertices.row(vertex_index) << config.radius * x + config.center.x(),
187 config.radius * y + config.center.y(), config.radius * z + config.center.z();
188 // normals
189 normals.row(vertex_index) << x, y, z;
190
191 // uvs
192 const Index uv_index = get_uv_vertex_index(rx, ry);
193 uvs(uv_index, 0) = (Scalar)u_max * (Scalar)(repeat_x - rx) / (Scalar)repeat_x;
194 uvs(uv_index, 1) = (Scalar)v_max * (Scalar)(repeat_y - ry) / (Scalar)repeat_y;
195 }
196 }
197
198 Index triangle_id = 0;
199 for (auto ry : range(repeat_y)) {
200 for (auto rx : range(repeat_x)) {
201 const auto q0 = get_vertex_index(rx, ry);
202 const auto q1 = get_vertex_index(rx + 1, ry);
203 const auto q2 = get_vertex_index(rx + 1, ry + 1);
204 const auto q3 = get_vertex_index(rx, ry + 1);
205
206 const auto p0 = get_uv_vertex_index(rx, ry);
207 const auto p1 = get_uv_vertex_index(rx + 1, ry);
208 const auto p2 = get_uv_vertex_index(rx + 1, ry + 1);
209 const auto p3 = get_uv_vertex_index(rx, ry + 1);
210
211 if (q0 != q1) {
212 facets.row(triangle_id) << q0, q1, q2;
213 uv_indices.row(triangle_id) << p0, p1, p2;
214 triangle_id++;
215 }
216 if (q2 != q3) {
217 facets.row(triangle_id) << q0, q2, q3;
218 uv_indices.row(triangle_id) << p0, p2, p3;
219 triangle_id++;
220 }
221 }
222 }
223
224 if (!is_closed && config.with_cross_section) {
225 Index center_index = static_cast<Index>(vertices.rows() - 1);
226 vertices.row(center_index) = config.center.template cast<Scalar>();
227 Index base_uv_index = (repeat_x + 1) * (repeat_y + 1) - 2;
228
229 uvs.row(base_uv_index) << 0, 0;
230 for (auto ry : range(repeat_y + 1)) {
231 const Scalar phi = ((Scalar)ry / (Scalar)repeat_y) * lagrange::internal::pi;
232 uvs.row(base_uv_index + ry + 1) << std::cos(phi) / lagrange::internal::pi,
233 std::sin(phi) / lagrange::internal::pi;
234
235 if (ry != repeat_y) {
236 Index v0 = get_vertex_index(0, ry);
237 Index v1 = get_vertex_index(0, ry + 1);
238 facets.row(triangle_id) << v0, v1, center_index;
239 uv_indices.row(triangle_id) << base_uv_index + ry + 1, base_uv_index + ry + 2,
240 base_uv_index;
241 triangle_id++;
242 }
243 }
244
245 base_uv_index += repeat_y + 2;
246 uvs.row(base_uv_index) << 0, 0;
247 for (auto ry : range(repeat_y + 1)) {
248 const Scalar phi = -((Scalar)ry / (Scalar)repeat_y) * lagrange::internal::pi;
249 uvs.row(base_uv_index + ry + 1) << std::cos(phi) / lagrange::internal::pi,
250 std::sin(phi) / lagrange::internal::pi;
251
252 if (ry != repeat_y) {
253 Index v0 = get_vertex_index(repeat_x, ry);
254 Index v1 = get_vertex_index(repeat_x, ry + 1);
255 facets.row(triangle_id) << v1, v0, center_index;
256 uv_indices.row(triangle_id) << base_uv_index + ry + 2, base_uv_index + ry + 1,
257 base_uv_index;
258 triangle_id++;
259 }
260 }
261 }
262
263 assert(triangle_id == num_triangles);
264
265 auto mesh = lagrange::create_mesh(vertices, facets);
266
267 mesh->initialize_uv(uvs, uv_indices);
268
269 if (config.output_normals) {
270 compute_normal(*mesh, config.angle_threshold);
271 la_runtime_assert(mesh->has_indexed_attribute("normal"));
272 }
273
274 // Set uniform semantic label
275 lagrange::set_uniform_semantic_label(*mesh, PrimitiveSemanticLabel::TOP);
276
277 packing::compute_rectangle_packing(*mesh);
278 return mesh;
279}
280
281template <typename MeshType>
282std::unique_ptr<MeshType> generate_sphere(
283 typename MeshType::Scalar radius,
284 const Eigen::Matrix<typename MeshType::Scalar, 3, 1>& center = {0, 0, 0},
285 typename MeshType::Scalar sweep_angle = 2 * lagrange::internal::pi,
286 typename MeshType::Index num_radial_sections = 50,
287 typename MeshType::Index flat_shade_thresh = 20)
288{
289 using Scalar = typename SphereConfig::Scalar;
290 using Index = typename SphereConfig::Index;
291
292 SphereConfig config;
293 config.radius = safe_cast<Scalar>(radius);
294 config.center = center.template cast<Scalar>();
295 config.sweep_angle = safe_cast<Scalar>(sweep_angle);
296 config.end_sweep_angle = config.start_sweep_angle + config.sweep_angle;
297 config.num_longitude_sections = safe_cast<Index>(num_radial_sections);
298 config.num_latitude_sections = safe_cast<Index>(num_radial_sections);
299 config.angle_threshold =
300 safe_cast<Scalar>((num_radial_sections < flat_shade_thresh) ? 0 : config.angle_threshold);
301
302 return generate_sphere<MeshType>(std::move(config));
303}
304
305LA_IGNORE_DEPRECATION_WARNING_END
306
307} // namespace legacy
308} // namespace primitive
309} // 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
bool is_closed(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is closed.
Definition topology.cpp:51
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
#define LA_IGNORE_DEPRECATION_WARNING_BEGIN
Ignore deprecation warnings.
Definition warning.h:76
SurfaceMesh< Scalar, Index > generate_sphere(SphereOptions setting)
Generate a sphere mesh based on the specified settings.
Definition generate_sphere.cpp:59
Main namespace for Lagrange.
auto create_mesh(const Eigen::MatrixBase< DerivedV > &vertices, const Eigen::MatrixBase< DerivedF > &facets)
This function create a new mesh given the vertex and facet arrays by copying data into the Mesh objec...
Definition create_mesh.h:39
Definition generate_sphere.h:35
void project_to_valid_range()
Project config setting into valid range.
Definition generate_sphere.h:82
Scalar angle_threshold
An edge is considered sharp if its dihedral angle is larger than angle_threshold.
Definition generate_sphere.h:68
Scalar get_effective_sweep_angle() const
Get the effective sweep angle based on start and end angles.
Definition generate_sphere.h:102
Scalar epsilon
Numerical tolerence used for comparing Scalar values.
Definition generate_sphere.h:73
Scalar sweep_angle
Definition generate_sphere.h:48