Geometry: Move mesh primitives from nodes to geometry module

This makes the code reusable in a few more places,
particularly by tests for a new boolean implementation
also defined in the geometry module.

It also makes the way some primitives are reused among
different nodes a bit clearer.

Pull Request: https://projects.blender.org/blender/blender/pulls/112255
This commit is contained in:
Hans Goudey
2023-09-11 22:49:41 +02:00
committed by Hans Goudey
parent 67aad1f72d
commit 7344c7875a
17 changed files with 1383 additions and 1301 deletions

View File

@@ -21,6 +21,10 @@ set(SRC
intern/mesh_copy_selection.cc
intern/mesh_merge_by_distance.cc
intern/mesh_primitive_cuboid.cc
intern/mesh_primitive_cylinder_cone.cc
intern/mesh_primitive_grid.cc
intern/mesh_primitive_line.cc
intern/mesh_primitive_uv_sphere.cc
intern/mesh_split_edges.cc
intern/mesh_to_curve_convert.cc
intern/mesh_to_volume.cc
@@ -41,6 +45,10 @@ set(SRC
GEO_mesh_copy_selection.hh
GEO_mesh_merge_by_distance.hh
GEO_mesh_primitive_cuboid.hh
GEO_mesh_primitive_cylinder_cone.hh
GEO_mesh_primitive_grid.hh
GEO_mesh_primitive_line.hh
GEO_mesh_primitive_uv_sphere.hh
GEO_mesh_split_edges.hh
GEO_mesh_to_curve.hh
GEO_mesh_to_volume.hh

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_attribute.hh"
struct Mesh;
namespace blender::geometry {
struct ConeAttributeOutputs {
bke::AnonymousAttributeIDPtr top_id;
bke::AnonymousAttributeIDPtr bottom_id;
bke::AnonymousAttributeIDPtr side_id;
bke::AnonymousAttributeIDPtr uv_map_id;
};
enum class ConeFillType {
None = 0,
NGon = 1,
Triangles = 2,
};
Mesh *create_cylinder_or_cone_mesh(float radius_top,
float radius_bottom,
float depth,
int circle_segments,
int side_segments,
int fill_segments,
ConeFillType fill_type,
ConeAttributeOutputs &attribute_outputs);
} // namespace blender::geometry

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
struct Mesh;
namespace blender::bke {
class AttributeIDRef;
} // namespace blender::bke
namespace blender::geometry {
Mesh *create_grid_mesh(
int verts_x, int verts_y, float size_x, float size_y, const bke::AttributeIDRef &uv_map_id);
} // namespace blender::geometry

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_math_vector_types.hh"
namespace blender::geometry {
Mesh *create_line_mesh(float3 start, float3 delta, int count);
} // namespace blender::geometry

View File

@@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_bounds_types.hh"
#include "BLI_math_vector_types.hh"
struct Mesh;
namespace blender::bke {
class AttributeIDRef;
} // namespace blender::bke
namespace blender::geometry {
/**
* Calculates the bounds of a radial primitive.
* The algorithm assumes X-axis symmetry of primitives.
*/
Bounds<float3> calculate_bounds_radial_primitive(float radius_top,
float radius_bottom,
int segments,
float height);
Mesh *create_uv_sphere_mesh(float radius,
int segments,
int rings,
const bke::AttributeIDRef &uv_map_id);
} // namespace blender::geometry

View File

@@ -0,0 +1,706 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_cylinder_cone.hh"
#include "GEO_mesh_primitive_line.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
namespace blender::geometry {
struct ConeConfig {
float radius_top;
float radius_bottom;
float height;
int circle_segments;
int side_segments;
int fill_segments;
ConeFillType fill_type;
bool top_is_point;
bool bottom_is_point;
/* The cone tip and a triangle fan filling are topologically identical.
* This simplifies the logic in some cases. */
bool top_has_center_vert;
bool bottom_has_center_vert;
/* Helpful quantities. */
int tot_quad_rings;
int tot_edge_rings;
int tot_verts;
int tot_edges;
int tot_corners;
int tot_faces;
/* Helpful vertex indices. */
int first_vert;
int first_ring_verts_start;
int last_ring_verts_start;
int last_vert;
/* Helpful edge indices. */
int first_ring_edges_start;
int last_ring_edges_start;
int last_fan_edges_start;
int last_edge;
/* Helpful face indices. */
int top_faces_start;
int top_faces_len;
int side_faces_start;
int side_faces_len;
int bottom_faces_start;
int bottom_faces_len;
ConeConfig(float radius_top,
float radius_bottom,
float depth,
int circle_segments,
int side_segments,
int fill_segments,
ConeFillType fill_type)
: radius_top(radius_top),
radius_bottom(radius_bottom),
height(0.5f * depth),
circle_segments(circle_segments),
side_segments(side_segments),
fill_segments(fill_segments),
fill_type(fill_type)
{
this->top_is_point = this->radius_top == 0.0f;
this->bottom_is_point = this->radius_bottom == 0.0f;
this->top_has_center_vert = this->top_is_point || this->fill_type == ConeFillType::Triangles;
this->bottom_has_center_vert = this->bottom_is_point ||
this->fill_type == ConeFillType::Triangles;
this->tot_quad_rings = this->calculate_total_quad_rings();
this->tot_edge_rings = this->calculate_total_edge_rings();
this->tot_verts = this->calculate_total_verts();
this->tot_edges = this->calculate_total_edges();
this->tot_corners = this->calculate_total_corners();
this->first_vert = 0;
this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert;
this->last_vert = this->tot_verts - 1;
this->last_ring_verts_start = this->last_vert - this->circle_segments;
this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0;
this->last_ring_edges_start = this->first_ring_edges_start +
this->tot_quad_rings * this->circle_segments * 2;
this->last_fan_edges_start = this->tot_edges - this->circle_segments;
this->last_edge = this->tot_edges - 1;
this->top_faces_start = 0;
if (!this->top_is_point) {
this->top_faces_len = (fill_segments - 1) * circle_segments;
this->top_faces_len += this->top_has_center_vert ? circle_segments : 0;
this->top_faces_len += this->fill_type == ConeFillType::NGon ? 1 : 0;
}
else {
this->top_faces_len = 0;
}
this->side_faces_start = this->top_faces_len;
if (this->top_is_point && this->bottom_is_point) {
this->side_faces_len = 0;
}
else {
this->side_faces_len = side_segments * circle_segments;
}
if (!this->bottom_is_point) {
this->bottom_faces_len = (fill_segments - 1) * circle_segments;
this->bottom_faces_len += this->bottom_has_center_vert ? circle_segments : 0;
this->bottom_faces_len += this->fill_type == ConeFillType::NGon ? 1 : 0;
}
else {
this->bottom_faces_len = 0;
}
this->bottom_faces_start = this->side_faces_start + this->side_faces_len;
this->tot_faces = this->top_faces_len + this->side_faces_len + this->bottom_faces_len;
}
private:
int calculate_total_quad_rings();
int calculate_total_edge_rings();
int calculate_total_verts();
int calculate_total_edges();
int calculate_total_corners();
};
int ConeConfig::calculate_total_quad_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int quad_rings = 0;
if (!top_is_point) {
quad_rings += fill_segments - 1;
}
quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1);
if (!bottom_is_point) {
quad_rings += fill_segments - 1;
}
return quad_rings;
}
int ConeConfig::calculate_total_edge_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int edge_rings = 0;
if (!top_is_point) {
edge_rings += fill_segments;
}
edge_rings += side_segments - 1;
if (!bottom_is_point) {
edge_rings += fill_segments;
}
return edge_rings;
}
int ConeConfig::calculate_total_verts()
{
if (top_is_point && bottom_is_point) {
return side_segments + 1;
}
int vert_total = 0;
if (top_has_center_vert) {
vert_total++;
}
if (!top_is_point) {
vert_total += circle_segments * fill_segments;
}
vert_total += circle_segments * (side_segments - 1);
if (!bottom_is_point) {
vert_total += circle_segments * fill_segments;
}
if (bottom_has_center_vert) {
vert_total++;
}
return vert_total;
}
int ConeConfig::calculate_total_edges()
{
if (top_is_point && bottom_is_point) {
return side_segments;
}
int edge_total = 0;
if (top_has_center_vert) {
edge_total += circle_segments;
}
edge_total += circle_segments * (tot_quad_rings * 2 + 1);
if (bottom_has_center_vert) {
edge_total += circle_segments;
}
return edge_total;
}
int ConeConfig::calculate_total_corners()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int corner_total = 0;
if (top_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!top_is_point && fill_type == ConeFillType::NGon) {
corner_total += circle_segments;
}
corner_total += tot_quad_rings * (circle_segments * 4);
if (bottom_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!bottom_is_point && fill_type == ConeFillType::NGon) {
corner_total += circle_segments;
}
return corner_total;
}
static void calculate_cone_verts(const ConeConfig &config, MutableSpan<float3> positions)
{
Array<float2> circle(config.circle_segments);
const float angle_delta = 2.0f * (M_PI / float(config.circle_segments));
float angle = 0.0f;
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle);
circle[i].y = std::sin(angle);
angle += angle_delta;
}
int vert_index = 0;
/* Top cone tip or triangle fan center. */
if (config.top_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, config.height};
}
/* Top fill including the outer edge of the fill. */
if (!config.top_is_point) {
const float top_fill_radius_delta = config.radius_top / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float top_fill_radius = top_fill_radius_delta * (i + 1);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * top_fill_radius;
const float y = circle[j].y * top_fill_radius;
positions[vert_index++] = {x, y, config.height};
}
}
}
/* Rings along the side. */
const float side_radius_delta = (config.radius_bottom - config.radius_top) /
float(config.side_segments);
const float height_delta = 2.0f * config.height / float(config.side_segments);
for (const int i : IndexRange(config.side_segments - 1)) {
const float ring_radius = config.radius_top + (side_radius_delta * (i + 1));
const float ring_height = config.height - (height_delta * (i + 1));
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * ring_radius;
const float y = circle[j].y * ring_radius;
positions[vert_index++] = {x, y, ring_height};
}
}
/* Bottom fill including the outer edge of the fill. */
if (!config.bottom_is_point) {
const float bottom_fill_radius_delta = config.radius_bottom / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * bottom_fill_radius;
const float y = circle[j].y * bottom_fill_radius;
positions[vert_index++] = {x, y, -config.height};
}
}
}
/* Bottom cone tip or triangle fan center. */
if (config.bottom_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, -config.height};
}
}
static void calculate_cone_edges(const ConeConfig &config, MutableSpan<int2> edges)
{
int edge_index = 0;
/* Edges for top cone tip or triangle fan */
if (config.top_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.first_vert;
edge[1] = config.first_ring_verts_start + i;
}
}
/* Rings and connecting edges between the rings. */
for (const int i : IndexRange(config.tot_edge_rings)) {
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
/* Edge rings. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = this_ring_vert_start + ((j + 1) % config.circle_segments);
}
if (i == config.tot_edge_rings - 1) {
/* There is one fewer ring of connecting edges. */
break;
}
/* Connecting edges. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = next_ring_vert_start + j;
}
}
/* Edges for bottom triangle fan or tip. */
if (config.bottom_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.last_ring_verts_start + i;
edge[1] = config.last_vert;
}
}
}
static void calculate_cone_faces(const ConeConfig &config,
MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
MutableSpan<int> face_sizes)
{
int rings_face_start = 0;
int rings_loop_start = 0;
if (config.top_has_center_vert) {
rings_face_start = config.circle_segments;
rings_loop_start = config.circle_segments * 3;
face_sizes.take_front(config.circle_segments).fill(3);
/* Top cone tip or center triangle fan in the fill. */
const int top_center_vert = 0;
const int top_fan_edges_start = 0;
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = i * 3;
corner_verts[loop_start + 0] = config.first_ring_verts_start + i;
corner_edges[loop_start + 0] = config.first_ring_edges_start + i;
corner_verts[loop_start + 1] = config.first_ring_verts_start +
((i + 1) % config.circle_segments);
corner_edges[loop_start + 1] = top_fan_edges_start + ((i + 1) % config.circle_segments);
corner_verts[loop_start + 2] = top_center_vert;
corner_edges[loop_start + 2] = top_fan_edges_start + i;
}
}
else if (config.fill_type == ConeFillType::NGon) {
rings_face_start = 1;
rings_loop_start = config.circle_segments;
/* Center n-gon in the fill. */
face_sizes.first() = config.circle_segments;
for (const int i : IndexRange(config.circle_segments)) {
corner_verts[i] = i;
corner_edges[i] = i;
}
}
/* Quads connect one edge ring to the next one. */
const int ring_faces_num = config.tot_quad_rings * config.circle_segments;
face_sizes.slice(rings_face_start, ring_faces_num).fill(4);
for (const int i : IndexRange(config.tot_quad_rings)) {
const int this_ring_loop_start = rings_loop_start + i * config.circle_segments * 4;
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
const int this_ring_edges_start = config.first_ring_edges_start +
(i * 2 * config.circle_segments);
const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments);
const int ring_connections_start = this_ring_edges_start + config.circle_segments;
for (const int j : IndexRange(config.circle_segments)) {
const int loop_start = this_ring_loop_start + j * 4;
corner_verts[loop_start + 0] = this_ring_vert_start + j;
corner_edges[loop_start + 0] = ring_connections_start + j;
corner_verts[loop_start + 1] = next_ring_vert_start + j;
corner_edges[loop_start + 1] = next_ring_edges_start + j;
corner_verts[loop_start + 2] = next_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 2] = ring_connections_start + ((j + 1) % config.circle_segments);
corner_verts[loop_start + 3] = this_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 3] = this_ring_edges_start + j;
}
}
const int bottom_face_start = rings_face_start + ring_faces_num;
const int bottom_loop_start = rings_loop_start + ring_faces_num * 4;
if (config.bottom_has_center_vert) {
face_sizes.slice(bottom_face_start, config.circle_segments).fill(3);
/* Bottom cone tip or center triangle fan in the fill. */
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = bottom_loop_start + i * 3;
corner_verts[loop_start + 0] = config.last_ring_verts_start + i;
corner_edges[loop_start + 0] = config.last_fan_edges_start + i;
corner_verts[loop_start + 1] = config.last_vert;
corner_edges[loop_start + 1] = config.last_fan_edges_start +
(i + 1) % config.circle_segments;
corner_verts[loop_start + 2] = config.last_ring_verts_start +
(i + 1) % config.circle_segments;
corner_edges[loop_start + 2] = config.last_ring_edges_start + i;
}
}
else if (config.fill_type == ConeFillType::NGon) {
/* Center n-gon in the fill. */
face_sizes[bottom_face_start] = config.circle_segments;
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards to reverse surface normal. */
corner_verts[bottom_loop_start + i] = config.last_vert - i;
corner_edges[bottom_loop_start + i] = config.last_edge - ((i + 1) % config.circle_segments);
}
}
}
static void calculate_selection_outputs(const ConeConfig &config,
const ConeAttributeOutputs &attribute_outputs,
bke::MutableAttributeAccessor attributes)
{
/* Populate "Top" selection output. */
if (attribute_outputs.top_id) {
const bool face = !config.top_is_point && config.fill_type != ConeFillType::None;
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.top_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT);
if (config.top_is_point) {
selection.span[config.first_vert] = true;
}
else {
selection.span.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Bottom" selection output. */
if (attribute_outputs.bottom_id) {
const bool face = !config.bottom_is_point && config.fill_type != ConeFillType::None;
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.bottom_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT);
if (config.bottom_is_point) {
selection.span[config.last_vert] = true;
}
else if (face) {
selection.span.slice(config.bottom_faces_start, config.bottom_faces_len).fill(true);
}
else {
selection.span.slice(config.last_ring_verts_start + 1, config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Side" selection output. */
if (attribute_outputs.side_id) {
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE);
selection.span.slice(config.side_faces_start, config.side_faces_len).fill(true);
selection.finish();
}
}
/**
* If the top is the cone tip or has a fill, it is unwrapped into a circle in the
* lower left quadrant of the UV.
* Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle
* in the lower right quadrant of the UV.
* If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into
* a rectangle that fills the top half of the UV (or the entire UV, if there are no fills).
*/
static void calculate_cone_uvs(const ConeConfig &config,
Mesh *mesh,
const bke::AttributeIDRef &uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<float2> uv_attribute =
attributes.lookup_or_add_for_write_only_span<float2>(uv_map_id, ATTR_DOMAIN_CORNER);
MutableSpan<float2> uvs = uv_attribute.span;
Array<float2> circle(config.circle_segments);
float angle = 0.0f;
const float angle_delta = 2.0f * M_PI / float(config.circle_segments);
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle) * 0.225f;
circle[i].y = std::sin(angle) * 0.225f;
angle += angle_delta;
}
int loop_index = 0;
/* Left circle of the UV representing the top fill or top cone tip. */
if (config.top_is_point || config.fill_type != ConeFillType::None) {
const float2 center_left(0.25f, 0.25f);
const float radius_factor_delta = 1.0f / (config.top_is_point ? float(config.side_segments) :
float(config.fill_segments));
const int left_circle_segment_count = config.top_is_point ? config.side_segments :
config.fill_segments;
if (config.top_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_left;
uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_left;
uvs[loop_index++] = center_left;
}
}
else if (!config.top_is_point && config.fill_type == ConeFillType::NGon) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_left;
}
}
/* The rest of the top fill is made out of quad rings. */
for (const int i : IndexRange(1, left_circle_segment_count - 1)) {
const float inner_radius_factor = i * radius_factor_delta;
const float outer_radius_factor = (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = inner_radius_factor * circle[j] + center_left;
uvs[loop_index++] = outer_radius_factor * circle[j] + center_left;
uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
}
}
}
if (!config.top_is_point && !config.bottom_is_point) {
/* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */
const float bottom = (config.fill_type == ConeFillType::None) ? 0.0f : 0.5f;
const float x_delta = 1.0f / float(config.circle_segments);
const float y_delta = (1.0f - bottom) / float(config.side_segments);
for (const int i : IndexRange(config.side_segments)) {
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = float2(j * x_delta, i * y_delta + bottom);
uvs[loop_index++] = float2(j * x_delta, (i + 1) * y_delta + bottom);
uvs[loop_index++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom);
uvs[loop_index++] = float2((j + 1) * x_delta, i * y_delta + bottom);
}
}
}
/* Right circle of the UV representing the bottom fill or bottom cone tip. */
if (config.bottom_is_point || config.fill_type != ConeFillType::None) {
const float2 center_right(0.75f, 0.25f);
const float radius_factor_delta = 1.0f / (config.bottom_is_point ?
float(config.side_segments) :
float(config.fill_segments));
const int right_circle_segment_count = config.bottom_is_point ? config.side_segments :
config.fill_segments;
/* The bottom circle has to be created outside in to match the loop order. */
for (const int i : IndexRange(right_circle_segment_count - 1)) {
const float outer_radius_factor = 1.0f - i * radius_factor_delta;
const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = outer_radius_factor * circle[j] + center_right;
uvs[loop_index++] = inner_radius_factor * circle[j] + center_right;
uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
}
}
if (config.bottom_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_right;
uvs[loop_index++] = center_right;
uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_right;
}
}
else if (!config.bottom_is_point && config.fill_type == ConeFillType::NGon) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards because of reversed face normal. */
uvs[loop_index++] = radius_factor_delta * circle[config.circle_segments - 1 - i] +
center_right;
}
}
}
uv_attribute.finish();
}
static Mesh *create_vertex_mesh()
{
/* Returns a mesh with a single vertex at the origin. */
Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0);
mesh->vert_positions_for_write().first() = float3(0);
return mesh;
}
static Bounds<float3> calculate_bounds_cylinder(const ConeConfig &config)
{
return geometry::calculate_bounds_radial_primitive(
config.radius_top, config.radius_bottom, config.circle_segments, config.height);
}
Mesh *create_cylinder_or_cone_mesh(const float radius_top,
const float radius_bottom,
const float depth,
const int circle_segments,
const int side_segments,
const int fill_segments,
const ConeFillType fill_type,
ConeAttributeOutputs &attribute_outputs)
{
const ConeConfig config(
radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type);
/* Handle the case of a line / single point before everything else to avoid
* the need to check for it later. */
if (config.top_is_point && config.bottom_is_point) {
if (config.height == 0.0f) {
return create_vertex_mesh();
}
const float z_delta = -2.0f * config.height / float(config.side_segments);
const float3 start(0.0f, 0.0f, config.height);
const float3 delta(0.0f, 0.0f, z_delta);
return create_line_mesh(start, delta, config.tot_verts);
}
Mesh *mesh = BKE_mesh_new_nomain(
config.tot_verts, config.tot_edges, config.tot_faces, config.tot_corners);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
calculate_cone_verts(config, positions);
calculate_cone_edges(config, edges);
calculate_cone_faces(config, corner_verts, corner_edges, face_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(face_offsets);
if (attribute_outputs.uv_map_id) {
calculate_cone_uvs(config, mesh, attribute_outputs.uv_map_id.get());
}
calculate_selection_outputs(config, attribute_outputs, mesh->attributes_for_write());
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->bounds_set_eager(calculate_bounds_cylinder(config));
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,146 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_grid.hh"
namespace blender::geometry {
static void calculate_uvs(Mesh *mesh,
const Span<float3> positions,
const Span<int> corner_verts,
const float size_x,
const float size_y,
const bke::AttributeIDRef &uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<float2> uv_attribute =
attributes.lookup_or_add_for_write_only_span<float2>(uv_map_id, ATTR_DOMAIN_CORNER);
const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x;
const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y;
threading::parallel_for(corner_verts.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const float3 &co = positions[corner_verts[i]];
uv_attribute.span[i].x = (co.x + size_x * 0.5f) * dx;
uv_attribute.span[i].y = (co.y + size_y * 0.5f) * dy;
}
});
uv_attribute.finish();
}
Mesh *create_grid_mesh(const int verts_x,
const int verts_y,
const float size_x,
const float size_y,
const bke::AttributeIDRef &uv_map_id)
{
BLI_assert(verts_x > 0 && verts_y > 0);
const int edges_x = verts_x - 1;
const int edges_y = verts_y - 1;
Mesh *mesh = BKE_mesh_new_nomain(verts_x * verts_y,
edges_x * verts_y + edges_y * verts_x,
edges_x * edges_y,
edges_x * edges_y * 4);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
offset_indices::fill_constant_group_size(4, 0, mesh->face_offsets_for_write());
{
const float dx = edges_x == 0 ? 0.0f : size_x / edges_x;
const float dy = edges_y == 0 ? 0.0f : size_y / edges_y;
const float x_shift = edges_x / 2.0f;
const float y_shift = edges_y / 2.0f;
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * verts_y;
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert_index = y_offset + y;
positions[vert_index].x = (x - x_shift) * dx;
positions[vert_index].y = (y - y_shift) * dy;
positions[vert_index].z = 0.0f;
}
});
}
});
}
const int y_edges_start = 0;
const int x_edges_start = verts_x * edges_y;
/* Build the horizontal edges in the X direction. */
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_vert_offset = x * verts_y;
const int y_edge_offset = y_edges_start + x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert_index = y_vert_offset + y;
edges[y_edge_offset + y] = int2(vert_index, vert_index + 1);
}
});
}
});
/* Build the vertical edges in the Y direction. */
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int x_edge_offset = x_edges_start + y * edges_x;
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int vert_index = x * verts_y + y;
edges[x_edge_offset + x] = int2(vert_index, vert_index + verts_y);
}
});
}
});
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int face_index = y_offset + y;
const int loop_index = face_index * 4;
const int vert_index = x * verts_y + y;
corner_verts[loop_index] = vert_index;
corner_edges[loop_index] = x_edges_start + edges_x * y + x;
corner_verts[loop_index + 1] = vert_index + verts_y;
corner_edges[loop_index + 1] = y_edges_start + edges_y * (x + 1) + y;
corner_verts[loop_index + 2] = vert_index + verts_y + 1;
corner_edges[loop_index + 2] = x_edges_start + edges_x * (y + 1) + x;
corner_verts[loop_index + 3] = vert_index + 1;
corner_edges[loop_index + 3] = y_edges_start + edges_y * x + y;
}
});
}
});
if (uv_map_id && mesh->faces_num != 0) {
calculate_uvs(mesh, positions, corner_verts, size_x, size_y, uv_map_id);
}
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
const float3 bounds = float3(size_x * 0.5f, size_y * 0.5f, 0.0f);
mesh->bounds_set_eager({-bounds, bounds});
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_line.hh"
namespace blender::geometry {
Mesh *create_line_mesh(const float3 start, const float3 delta, const int count)
{
if (count < 1) {
return nullptr;
}
Mesh *mesh = BKE_mesh_new_nomain(count, count - 1, 0, 0);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
threading::parallel_invoke(
1024 < count,
[&]() {
threading::parallel_for(positions.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
positions[i] = start + delta * i;
}
});
},
[&]() {
threading::parallel_for(edges.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
edges[i][0] = i;
edges[i][1] = i + 1;
}
});
});
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,335 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
namespace blender::geometry {
Bounds<float3> calculate_bounds_radial_primitive(const float radius_top,
const float radius_bottom,
const int segments,
const float height)
{
const float radius = std::max(radius_top, radius_bottom);
const float delta_phi = (2.0f * M_PI) / float(segments);
const float x_max = radius;
const float x_min = std::cos(std::round(0.5f * segments) * delta_phi) * radius;
const float y_max = std::sin(std::round(0.25f * segments) * delta_phi) * radius;
const float y_min = -y_max;
const float3 bounds_min(x_min, y_min, -height);
const float3 bounds_max(x_max, y_max, height);
return {bounds_min, bounds_max};
}
static int sphere_vert_total(const int segments, const int rings)
{
return segments * (rings - 1) + 2;
}
static int sphere_edge_total(const int segments, const int rings)
{
return segments * (rings * 2 - 1);
}
static int sphere_corner_total(const int segments, const int rings)
{
const int quad_corners = 4 * segments * (rings - 2);
const int tri_corners = 3 * segments * 2;
return quad_corners + tri_corners;
}
static int sphere_face_total(const int segments, const int rings)
{
const int quads = segments * (rings - 2);
const int triangles = segments * 2;
return quads + triangles;
}
/**
* Also calculate vertex normals here, since the calculation is trivial, and it allows avoiding the
* calculation later, if it's necessary. The vertex normals are just the normalized positions.
*/
BLI_NOINLINE static void calculate_sphere_vertex_data(MutableSpan<float3> positions,
MutableSpan<float3> vert_normals,
const float radius,
const int segments,
const int rings)
{
const float delta_theta = M_PI / rings;
const float delta_phi = (2.0f * M_PI) / segments;
Array<float, 64> segment_cosines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_cosines[segment] = std::cos(phi);
}
Array<float, 64> segment_sines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_sines[segment] = std::sin(phi);
}
positions[0] = float3(0.0f, 0.0f, radius);
vert_normals.first() = float3(0.0f, 0.0f, 1.0f);
int vert_index = 1;
for (const int ring : IndexRange(1, rings - 1)) {
const float theta = ring * delta_theta;
const float sin_theta = std::sin(theta);
const float z = std::cos(theta);
for (const int segment : IndexRange(1, segments)) {
const float x = sin_theta * segment_cosines[segment];
const float y = sin_theta * segment_sines[segment];
positions[vert_index] = float3(x, y, z) * radius;
vert_normals[vert_index] = float3(x, y, z);
vert_index++;
}
}
positions.last() = float3(0.0f, 0.0f, -radius);
vert_normals.last() = float3(0.0f, 0.0f, -1.0f);
}
BLI_NOINLINE static void calculate_sphere_edge_indices(MutableSpan<int2> edges,
const int segments,
const int rings)
{
int edge_index = 0;
/* Add the edges connecting the top vertex to the first ring. */
const int first_vert_ring_index_start = 1;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = 0;
edge[1] = first_vert_ring_index_start + segment;
}
int ring_vert_index_start = 1;
for (const int ring : IndexRange(rings - 1)) {
const int next_ring_vert_index_start = ring_vert_index_start + segments;
/* Add the edges running along each ring. */
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = ring_vert_index_start + ((segment + 1) % segments);
}
/* Add the edges connecting to the next ring. */
if (ring < rings - 2) {
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = next_ring_vert_index_start + segment;
}
}
ring_vert_index_start += segments;
}
/* Add the edges connecting the last ring to the bottom vertex. */
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = last_vert_index;
edge[1] = last_vert_ring_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_faces(MutableSpan<int> face_offsets, const int segments)
{
MutableSpan<int> face_sizes = face_offsets.drop_back(1);
/* Add the triangles connected to the top vertex. */
face_sizes.take_front(segments).fill(3);
/* Add the middle quads. */
face_sizes.drop_front(segments).drop_back(segments).fill(4);
/* Add the triangles connected to the bottom vertex. */
face_sizes.take_back(segments).fill(3);
offset_indices::accumulate_counts_to_offsets(face_offsets);
}
BLI_NOINLINE static void calculate_sphere_corners(MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
const int segments,
const int rings)
{
auto segment_next_or_first = [&](const int segment) {
return segment == segments - 1 ? 0 : segment + 1;
};
/* Add the triangles connected to the top vertex. */
const int first_vert_ring_start = 1;
for (const int segment : IndexRange(segments)) {
const int loop_start = segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = 0;
corner_edges[loop_start + 0] = segment;
corner_verts[loop_start + 1] = first_vert_ring_start + segment;
corner_edges[loop_start + 1] = segments + segment;
corner_verts[loop_start + 2] = first_vert_ring_start + segment_next;
corner_edges[loop_start + 2] = segment_next;
}
const int rings_vert_start = 1;
const int rings_edge_start = segments;
const int rings_loop_start = segments * 3;
for (const int ring : IndexRange(1, rings - 2)) {
const int ring_vert_start = rings_vert_start + (ring - 1) * segments;
const int ring_edge_start = rings_edge_start + (ring - 1) * segments * 2;
const int ring_loop_start = rings_loop_start + (ring - 1) * segments * 4;
const int next_ring_vert_start = ring_vert_start + segments;
const int next_ring_edge_start = ring_edge_start + segments * 2;
const int ring_vertical_edge_start = ring_edge_start + segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + segment * 4;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = ring_vert_start + segment;
corner_edges[loop_start + 0] = ring_vertical_edge_start + segment;
corner_verts[loop_start + 1] = next_ring_vert_start + segment;
corner_edges[loop_start + 1] = next_ring_edge_start + segment;
corner_verts[loop_start + 2] = next_ring_vert_start + segment_next;
corner_edges[loop_start + 2] = ring_vertical_edge_start + segment_next;
corner_verts[loop_start + 3] = ring_vert_start + segment_next;
corner_edges[loop_start + 3] = ring_edge_start + segment;
}
}
/* Add the triangles connected to the bottom vertex. */
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
const int last_edge_ring_start = segments * (rings - 2) * 2 + segments;
const int bottom_edge_fan_start = last_edge_ring_start + segments;
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = last_vert_index;
corner_edges[loop_start + 0] = bottom_edge_fan_start + segment_next;
corner_verts[loop_start + 1] = last_vert_ring_start + segment_next;
corner_edges[loop_start + 1] = last_edge_ring_start + segment;
corner_verts[loop_start + 2] = last_vert_ring_start + segment;
corner_edges[loop_start + 2] = bottom_edge_fan_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_uvs(Mesh *mesh,
const float segments,
const float rings,
const bke::AttributeIDRef &uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<float2> uv_attribute =
attributes.lookup_or_add_for_write_only_span<float2>(uv_map_id, ATTR_DOMAIN_CORNER);
MutableSpan<float2> uvs = uv_attribute.span;
const float dy = 1.0f / rings;
const float segments_inv = 1.0f / segments;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 0.0f);
uvs[loop_start + 1] = float2(segment * segments_inv, dy);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, dy);
}
const int rings_loop_start = segments * 3;
for (const int i_ring : IndexRange(1, rings - 2)) {
const int ring_loop_start = rings_loop_start + (i_ring - 1) * segments * 4;
const float ring = float(i_ring);
for (const int i_segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + i_segment * 4;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2(segment * segments_inv, ring / rings);
uvs[loop_start + 1] = float2(segment * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 3] = float2((segment + 1.0f) * segments_inv, ring / rings);
}
}
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 1.0f);
uvs[loop_start + 1] = float2((segment + 1.0f) * segments_inv, 1.0f - dy);
uvs[loop_start + 2] = float2(segment * segments_inv, 1.0f - dy);
}
uv_attribute.finish();
}
static Bounds<float3> calculate_bounds_uv_sphere(const float radius,
const int segments,
const int rings)
{
const float delta_theta = M_PI / float(rings);
const float sin_equator = std::sin(std::round(0.5f * rings) * delta_theta);
return calculate_bounds_radial_primitive(0.0f, radius * sin_equator, segments, radius);
}
Mesh *create_uv_sphere_mesh(const float radius,
const int segments,
const int rings,
const bke::AttributeIDRef &uv_map_id)
{
Mesh *mesh = BKE_mesh_new_nomain(sphere_vert_total(segments, rings),
sphere_edge_total(segments, rings),
sphere_face_total(segments, rings),
sphere_corner_total(segments, rings));
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
threading::parallel_invoke(
1024 < segments * rings,
[&]() {
Vector<float3> vert_normals(mesh->totvert);
calculate_sphere_vertex_data(positions, vert_normals, radius, segments, rings);
bke::mesh_vert_normals_assign(*mesh, std::move(vert_normals));
},
[&]() { calculate_sphere_edge_indices(edges, segments, rings); },
[&]() { calculate_sphere_faces(face_offsets, segments); },
[&]() { calculate_sphere_corners(corner_verts, corner_edges, segments, rings); },
[&]() {
if (uv_map_id) {
calculate_sphere_uvs(mesh, segments, rings, uv_map_id);
}
});
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->bounds_set_eager(calculate_bounds_uv_sphere(radius, segments, rings));
return mesh;
}
} // namespace blender::geometry

View File

@@ -46,36 +46,6 @@ void transform_geometry_set(GeoNodeExecParams &params,
const float4x4 &transform,
const Depsgraph &depsgraph);
Mesh *create_line_mesh(const float3 start, const float3 delta, int count);
Mesh *create_grid_mesh(
int verts_x, int verts_y, float size_x, float size_y, const AttributeIDRef &uv_map_id);
struct ConeAttributeOutputs {
AnonymousAttributeIDPtr top_id;
AnonymousAttributeIDPtr bottom_id;
AnonymousAttributeIDPtr side_id;
AnonymousAttributeIDPtr uv_map_id;
};
Mesh *create_cylinder_or_cone_mesh(float radius_top,
float radius_bottom,
float depth,
int circle_segments,
int side_segments,
int fill_segments,
GeometryNodeMeshCircleFillType fill_type,
ConeAttributeOutputs &attribute_outputs);
/**
* Calculates the bounds of a radial primitive.
* The algorithm assumes X-axis symmetry of primitives.
*/
Bounds<float3> calculate_bounds_radial_primitive(float radius_top,
float radius_bottom,
int segments,
float height);
/**
* Returns the parts of the geometry that are on the selection for the given domain. If the domain
* is not applicable for the component, e.g. face domain for point cloud, nothing happens to that

View File

@@ -13,6 +13,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
#include "node_geometry_util.hh"
#include "RNA_enum_types.hh"
@@ -107,7 +109,7 @@ static int circle_face_total(const GeometryNodeMeshCircleFillType fill_type, con
static Bounds<float3> calculate_bounds_circle(const float radius, const int verts_num)
{
return calculate_bounds_radial_primitive(0.0f, radius, verts_num, 0.0f);
return geometry::calculate_bounds_radial_primitive(0.0f, radius, verts_num, 0.0f);
}
static Mesh *create_circle_mesh(const float radius,

View File

@@ -2,9 +2,6 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_material.h"
#include "BKE_mesh.hh"
@@ -13,731 +10,12 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_mesh_primitive_cylinder_cone.hh"
#include "node_geometry_util.hh"
#include "RNA_enum_types.hh"
#include <cmath>
namespace blender::nodes {
struct ConeConfig {
float radius_top;
float radius_bottom;
float height;
int circle_segments;
int side_segments;
int fill_segments;
GeometryNodeMeshCircleFillType fill_type;
bool top_is_point;
bool bottom_is_point;
/* The cone tip and a triangle fan filling are topologically identical.
* This simplifies the logic in some cases. */
bool top_has_center_vert;
bool bottom_has_center_vert;
/* Helpful quantities. */
int tot_quad_rings;
int tot_edge_rings;
int tot_verts;
int tot_edges;
int tot_corners;
int tot_faces;
/* Helpful vertex indices. */
int first_vert;
int first_ring_verts_start;
int last_ring_verts_start;
int last_vert;
/* Helpful edge indices. */
int first_ring_edges_start;
int last_ring_edges_start;
int last_fan_edges_start;
int last_edge;
/* Helpful face indices. */
int top_faces_start;
int top_faces_len;
int side_faces_start;
int side_faces_len;
int bottom_faces_start;
int bottom_faces_len;
ConeConfig(float radius_top,
float radius_bottom,
float depth,
int circle_segments,
int side_segments,
int fill_segments,
GeometryNodeMeshCircleFillType fill_type)
: radius_top(radius_top),
radius_bottom(radius_bottom),
height(0.5f * depth),
circle_segments(circle_segments),
side_segments(side_segments),
fill_segments(fill_segments),
fill_type(fill_type)
{
this->top_is_point = this->radius_top == 0.0f;
this->bottom_is_point = this->radius_bottom == 0.0f;
this->top_has_center_vert = this->top_is_point ||
this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN;
this->bottom_has_center_vert = this->bottom_is_point ||
this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN;
this->tot_quad_rings = this->calculate_total_quad_rings();
this->tot_edge_rings = this->calculate_total_edge_rings();
this->tot_verts = this->calculate_total_verts();
this->tot_edges = this->calculate_total_edges();
this->tot_corners = this->calculate_total_corners();
this->first_vert = 0;
this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert;
this->last_vert = this->tot_verts - 1;
this->last_ring_verts_start = this->last_vert - this->circle_segments;
this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0;
this->last_ring_edges_start = this->first_ring_edges_start +
this->tot_quad_rings * this->circle_segments * 2;
this->last_fan_edges_start = this->tot_edges - this->circle_segments;
this->last_edge = this->tot_edges - 1;
this->top_faces_start = 0;
if (!this->top_is_point) {
this->top_faces_len = (fill_segments - 1) * circle_segments;
this->top_faces_len += this->top_has_center_vert ? circle_segments : 0;
this->top_faces_len += this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON ? 1 : 0;
}
else {
this->top_faces_len = 0;
}
this->side_faces_start = this->top_faces_len;
if (this->top_is_point && this->bottom_is_point) {
this->side_faces_len = 0;
}
else {
this->side_faces_len = side_segments * circle_segments;
}
if (!this->bottom_is_point) {
this->bottom_faces_len = (fill_segments - 1) * circle_segments;
this->bottom_faces_len += this->bottom_has_center_vert ? circle_segments : 0;
this->bottom_faces_len += this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON ? 1 : 0;
}
else {
this->bottom_faces_len = 0;
}
this->bottom_faces_start = this->side_faces_start + this->side_faces_len;
this->tot_faces = this->top_faces_len + this->side_faces_len + this->bottom_faces_len;
}
private:
int calculate_total_quad_rings();
int calculate_total_edge_rings();
int calculate_total_verts();
int calculate_total_edges();
int calculate_total_corners();
};
int ConeConfig::calculate_total_quad_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int quad_rings = 0;
if (!top_is_point) {
quad_rings += fill_segments - 1;
}
quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1);
if (!bottom_is_point) {
quad_rings += fill_segments - 1;
}
return quad_rings;
}
int ConeConfig::calculate_total_edge_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int edge_rings = 0;
if (!top_is_point) {
edge_rings += fill_segments;
}
edge_rings += side_segments - 1;
if (!bottom_is_point) {
edge_rings += fill_segments;
}
return edge_rings;
}
int ConeConfig::calculate_total_verts()
{
if (top_is_point && bottom_is_point) {
return side_segments + 1;
}
int vert_total = 0;
if (top_has_center_vert) {
vert_total++;
}
if (!top_is_point) {
vert_total += circle_segments * fill_segments;
}
vert_total += circle_segments * (side_segments - 1);
if (!bottom_is_point) {
vert_total += circle_segments * fill_segments;
}
if (bottom_has_center_vert) {
vert_total++;
}
return vert_total;
}
int ConeConfig::calculate_total_edges()
{
if (top_is_point && bottom_is_point) {
return side_segments;
}
int edge_total = 0;
if (top_has_center_vert) {
edge_total += circle_segments;
}
edge_total += circle_segments * (tot_quad_rings * 2 + 1);
if (bottom_has_center_vert) {
edge_total += circle_segments;
}
return edge_total;
}
int ConeConfig::calculate_total_corners()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int corner_total = 0;
if (top_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
corner_total += circle_segments;
}
corner_total += tot_quad_rings * (circle_segments * 4);
if (bottom_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
corner_total += circle_segments;
}
return corner_total;
}
static void calculate_cone_verts(const ConeConfig &config, MutableSpan<float3> positions)
{
Array<float2> circle(config.circle_segments);
const float angle_delta = 2.0f * (M_PI / float(config.circle_segments));
float angle = 0.0f;
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle);
circle[i].y = std::sin(angle);
angle += angle_delta;
}
int vert_index = 0;
/* Top cone tip or triangle fan center. */
if (config.top_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, config.height};
}
/* Top fill including the outer edge of the fill. */
if (!config.top_is_point) {
const float top_fill_radius_delta = config.radius_top / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float top_fill_radius = top_fill_radius_delta * (i + 1);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * top_fill_radius;
const float y = circle[j].y * top_fill_radius;
positions[vert_index++] = {x, y, config.height};
}
}
}
/* Rings along the side. */
const float side_radius_delta = (config.radius_bottom - config.radius_top) /
float(config.side_segments);
const float height_delta = 2.0f * config.height / float(config.side_segments);
for (const int i : IndexRange(config.side_segments - 1)) {
const float ring_radius = config.radius_top + (side_radius_delta * (i + 1));
const float ring_height = config.height - (height_delta * (i + 1));
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * ring_radius;
const float y = circle[j].y * ring_radius;
positions[vert_index++] = {x, y, ring_height};
}
}
/* Bottom fill including the outer edge of the fill. */
if (!config.bottom_is_point) {
const float bottom_fill_radius_delta = config.radius_bottom / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * bottom_fill_radius;
const float y = circle[j].y * bottom_fill_radius;
positions[vert_index++] = {x, y, -config.height};
}
}
}
/* Bottom cone tip or triangle fan center. */
if (config.bottom_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, -config.height};
}
}
static void calculate_cone_edges(const ConeConfig &config, MutableSpan<int2> edges)
{
int edge_index = 0;
/* Edges for top cone tip or triangle fan */
if (config.top_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.first_vert;
edge[1] = config.first_ring_verts_start + i;
}
}
/* Rings and connecting edges between the rings. */
for (const int i : IndexRange(config.tot_edge_rings)) {
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
/* Edge rings. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = this_ring_vert_start + ((j + 1) % config.circle_segments);
}
if (i == config.tot_edge_rings - 1) {
/* There is one fewer ring of connecting edges. */
break;
}
/* Connecting edges. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = next_ring_vert_start + j;
}
}
/* Edges for bottom triangle fan or tip. */
if (config.bottom_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.last_ring_verts_start + i;
edge[1] = config.last_vert;
}
}
}
static void calculate_cone_faces(const ConeConfig &config,
MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
MutableSpan<int> face_sizes)
{
int rings_face_start = 0;
int rings_loop_start = 0;
if (config.top_has_center_vert) {
rings_face_start = config.circle_segments;
rings_loop_start = config.circle_segments * 3;
face_sizes.take_front(config.circle_segments).fill(3);
/* Top cone tip or center triangle fan in the fill. */
const int top_center_vert = 0;
const int top_fan_edges_start = 0;
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = i * 3;
corner_verts[loop_start + 0] = config.first_ring_verts_start + i;
corner_edges[loop_start + 0] = config.first_ring_edges_start + i;
corner_verts[loop_start + 1] = config.first_ring_verts_start +
((i + 1) % config.circle_segments);
corner_edges[loop_start + 1] = top_fan_edges_start + ((i + 1) % config.circle_segments);
corner_verts[loop_start + 2] = top_center_vert;
corner_edges[loop_start + 2] = top_fan_edges_start + i;
}
}
else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
rings_face_start = 1;
rings_loop_start = config.circle_segments;
/* Center n-gon in the fill. */
face_sizes.first() = config.circle_segments;
for (const int i : IndexRange(config.circle_segments)) {
corner_verts[i] = i;
corner_edges[i] = i;
}
}
/* Quads connect one edge ring to the next one. */
const int ring_faces_num = config.tot_quad_rings * config.circle_segments;
face_sizes.slice(rings_face_start, ring_faces_num).fill(4);
for (const int i : IndexRange(config.tot_quad_rings)) {
const int this_ring_loop_start = rings_loop_start + i * config.circle_segments * 4;
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
const int this_ring_edges_start = config.first_ring_edges_start +
(i * 2 * config.circle_segments);
const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments);
const int ring_connections_start = this_ring_edges_start + config.circle_segments;
for (const int j : IndexRange(config.circle_segments)) {
const int loop_start = this_ring_loop_start + j * 4;
corner_verts[loop_start + 0] = this_ring_vert_start + j;
corner_edges[loop_start + 0] = ring_connections_start + j;
corner_verts[loop_start + 1] = next_ring_vert_start + j;
corner_edges[loop_start + 1] = next_ring_edges_start + j;
corner_verts[loop_start + 2] = next_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 2] = ring_connections_start + ((j + 1) % config.circle_segments);
corner_verts[loop_start + 3] = this_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 3] = this_ring_edges_start + j;
}
}
const int bottom_face_start = rings_face_start + ring_faces_num;
const int bottom_loop_start = rings_loop_start + ring_faces_num * 4;
if (config.bottom_has_center_vert) {
face_sizes.slice(bottom_face_start, config.circle_segments).fill(3);
/* Bottom cone tip or center triangle fan in the fill. */
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = bottom_loop_start + i * 3;
corner_verts[loop_start + 0] = config.last_ring_verts_start + i;
corner_edges[loop_start + 0] = config.last_fan_edges_start + i;
corner_verts[loop_start + 1] = config.last_vert;
corner_edges[loop_start + 1] = config.last_fan_edges_start +
(i + 1) % config.circle_segments;
corner_verts[loop_start + 2] = config.last_ring_verts_start +
(i + 1) % config.circle_segments;
corner_edges[loop_start + 2] = config.last_ring_edges_start + i;
}
}
else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
/* Center n-gon in the fill. */
face_sizes[bottom_face_start] = config.circle_segments;
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards to reverse surface normal. */
corner_verts[bottom_loop_start + i] = config.last_vert - i;
corner_edges[bottom_loop_start + i] = config.last_edge - ((i + 1) % config.circle_segments);
}
}
}
static void calculate_selection_outputs(const ConeConfig &config,
const ConeAttributeOutputs &attribute_outputs,
MutableAttributeAccessor attributes)
{
/* Populate "Top" selection output. */
if (attribute_outputs.top_id) {
const bool face = !config.top_is_point && config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE;
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.top_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT);
if (config.top_is_point) {
selection.span[config.first_vert] = true;
}
else {
selection.span.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Bottom" selection output. */
if (attribute_outputs.bottom_id) {
const bool face = !config.bottom_is_point &&
config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE;
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.bottom_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT);
if (config.bottom_is_point) {
selection.span[config.last_vert] = true;
}
else if (face) {
selection.span.slice(config.bottom_faces_start, config.bottom_faces_len).fill(true);
}
else {
selection.span.slice(config.last_ring_verts_start + 1, config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Side" selection output. */
if (attribute_outputs.side_id) {
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE);
selection.span.slice(config.side_faces_start, config.side_faces_len).fill(true);
selection.finish();
}
}
/**
* If the top is the cone tip or has a fill, it is unwrapped into a circle in the
* lower left quadrant of the UV.
* Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle
* in the lower right quadrant of the UV.
* If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into
* a rectangle that fills the top half of the UV (or the entire UV, if there are no fills).
*/
static void calculate_cone_uvs(const ConeConfig &config,
Mesh *mesh,
const AttributeIDRef &uv_map_id)
{
MutableAttributeAccessor attributes = mesh->attributes_for_write();
SpanAttributeWriter<float2> uv_attribute = attributes.lookup_or_add_for_write_only_span<float2>(
uv_map_id, ATTR_DOMAIN_CORNER);
MutableSpan<float2> uvs = uv_attribute.span;
Array<float2> circle(config.circle_segments);
float angle = 0.0f;
const float angle_delta = 2.0f * M_PI / float(config.circle_segments);
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle) * 0.225f;
circle[i].y = std::sin(angle) * 0.225f;
angle += angle_delta;
}
int loop_index = 0;
/* Left circle of the UV representing the top fill or top cone tip. */
if (config.top_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) {
const float2 center_left(0.25f, 0.25f);
const float radius_factor_delta = 1.0f / (config.top_is_point ? float(config.side_segments) :
float(config.fill_segments));
const int left_circle_segment_count = config.top_is_point ? config.side_segments :
config.fill_segments;
if (config.top_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_left;
uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_left;
uvs[loop_index++] = center_left;
}
}
else if (!config.top_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_left;
}
}
/* The rest of the top fill is made out of quad rings. */
for (const int i : IndexRange(1, left_circle_segment_count - 1)) {
const float inner_radius_factor = i * radius_factor_delta;
const float outer_radius_factor = (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = inner_radius_factor * circle[j] + center_left;
uvs[loop_index++] = outer_radius_factor * circle[j] + center_left;
uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
}
}
}
if (!config.top_is_point && !config.bottom_is_point) {
/* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */
const float bottom = (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f;
const float x_delta = 1.0f / float(config.circle_segments);
const float y_delta = (1.0f - bottom) / float(config.side_segments);
for (const int i : IndexRange(config.side_segments)) {
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = float2(j * x_delta, i * y_delta + bottom);
uvs[loop_index++] = float2(j * x_delta, (i + 1) * y_delta + bottom);
uvs[loop_index++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom);
uvs[loop_index++] = float2((j + 1) * x_delta, i * y_delta + bottom);
}
}
}
/* Right circle of the UV representing the bottom fill or bottom cone tip. */
if (config.bottom_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) {
const float2 center_right(0.75f, 0.25f);
const float radius_factor_delta = 1.0f / (config.bottom_is_point ?
float(config.side_segments) :
float(config.fill_segments));
const int right_circle_segment_count = config.bottom_is_point ? config.side_segments :
config.fill_segments;
/* The bottom circle has to be created outside in to match the loop order. */
for (const int i : IndexRange(right_circle_segment_count - 1)) {
const float outer_radius_factor = 1.0f - i * radius_factor_delta;
const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[loop_index++] = outer_radius_factor * circle[j] + center_right;
uvs[loop_index++] = inner_radius_factor * circle[j] + center_right;
uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
}
}
if (config.bottom_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[loop_index++] = radius_factor_delta * circle[i] + center_right;
uvs[loop_index++] = center_right;
uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_right;
}
}
else if (!config.bottom_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards because of reversed face normal. */
uvs[loop_index++] = radius_factor_delta * circle[config.circle_segments - 1 - i] +
center_right;
}
}
}
uv_attribute.finish();
}
static Mesh *create_vertex_mesh()
{
/* Returns a mesh with a single vertex at the origin. */
Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0);
mesh->vert_positions_for_write().first() = float3(0);
return mesh;
}
Bounds<float3> calculate_bounds_radial_primitive(const float radius_top,
const float radius_bottom,
const int segments,
const float height)
{
const float radius = std::max(radius_top, radius_bottom);
const float delta_phi = (2.0f * M_PI) / float(segments);
const float x_max = radius;
const float x_min = std::cos(std::round(0.5f * segments) * delta_phi) * radius;
const float y_max = std::sin(std::round(0.25f * segments) * delta_phi) * radius;
const float y_min = -y_max;
const float3 bounds_min(x_min, y_min, -height);
const float3 bounds_max(x_max, y_max, height);
return {bounds_min, bounds_max};
}
static Bounds<float3> calculate_bounds_cylinder(const ConeConfig &config)
{
return calculate_bounds_radial_primitive(
config.radius_top, config.radius_bottom, config.circle_segments, config.height);
}
Mesh *create_cylinder_or_cone_mesh(const float radius_top,
const float radius_bottom,
const float depth,
const int circle_segments,
const int side_segments,
const int fill_segments,
const GeometryNodeMeshCircleFillType fill_type,
ConeAttributeOutputs &attribute_outputs)
{
const ConeConfig config(
radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type);
/* Handle the case of a line / single point before everything else to avoid
* the need to check for it later. */
if (config.top_is_point && config.bottom_is_point) {
if (config.height == 0.0f) {
return create_vertex_mesh();
}
const float z_delta = -2.0f * config.height / float(config.side_segments);
const float3 start(0.0f, 0.0f, config.height);
const float3 delta(0.0f, 0.0f, z_delta);
return create_line_mesh(start, delta, config.tot_verts);
}
Mesh *mesh = BKE_mesh_new_nomain(
config.tot_verts, config.tot_edges, config.tot_faces, config.tot_corners);
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
calculate_cone_verts(config, positions);
calculate_cone_edges(config, edges);
calculate_cone_faces(config, corner_verts, corner_edges, face_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(face_offsets);
if (attribute_outputs.uv_map_id) {
calculate_cone_uvs(config, mesh, attribute_outputs.uv_map_id.get());
}
calculate_selection_outputs(config, attribute_outputs, mesh->attributes_for_write());
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->bounds_set_eager(calculate_bounds_cylinder(config));
return mesh;
}
} // namespace blender::nodes
namespace blender::nodes::node_geo_mesh_primitive_cone_cc {
NODE_STORAGE_FUNCS(NodeGeometryMeshCone)
@@ -839,20 +117,21 @@ static void node_geo_exec(GeoNodeExecParams params)
const float radius_bottom = params.extract_input<float>("Radius Bottom");
const float depth = params.extract_input<float>("Depth");
ConeAttributeOutputs attribute_outputs;
geometry::ConeAttributeOutputs attribute_outputs;
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom");
attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
Mesh *mesh = create_cylinder_or_cone_mesh(radius_top,
radius_bottom,
depth,
circle_segments,
side_segments,
fill_segments,
fill,
attribute_outputs);
Mesh *mesh = geometry::create_cylinder_or_cone_mesh(radius_top,
radius_bottom,
depth,
circle_segments,
side_segments,
fill_segments,
geometry::ConeFillType(fill),
attribute_outputs);
BKE_id_material_eval_ensure_default_slot(&mesh->id);
/* Transform the mesh so that the base of the cone is at the origin. */
BKE_mesh_translate(mesh, float3(0.0f, 0.0f, depth * 0.5f), false);

View File

@@ -9,6 +9,8 @@
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_cuboid.hh"
#include "GEO_mesh_primitive_grid.hh"
#include "GEO_mesh_primitive_line.hh"
#include "node_geometry_util.hh"
@@ -40,17 +42,6 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Vector>("UV Map").field_on_all();
}
static Mesh *create_cuboid_mesh(const float3 &size,
const int verts_x,
const int verts_y,
const int verts_z,
const AttributeIDRef &uv_map_id)
{
Mesh *mesh = geometry::create_cuboid_mesh(size, verts_x, verts_y, verts_z, uv_map_id);
BKE_id_material_eval_ensure_default_slot(&mesh->id);
return mesh;
}
static Mesh *create_cube_mesh(const float3 size,
const int verts_x,
const int verts_y,
@@ -59,7 +50,7 @@ static Mesh *create_cube_mesh(const float3 size,
{
const int dimensions = (verts_x - 1 > 0) + (verts_y - 1 > 0) + (verts_z - 1 > 0);
if (dimensions == 0) {
return create_line_mesh(float3(0), float3(0), 1);
return geometry::create_line_mesh(float3(0), float3(0), 1);
}
if (dimensions == 1) {
float3 start;
@@ -77,24 +68,24 @@ static Mesh *create_cube_mesh(const float3 size,
delta = {0, 0, size.z / (verts_z - 1)};
}
return create_line_mesh(start, delta, verts_x * verts_y * verts_z);
return geometry::create_line_mesh(start, delta, verts_x * verts_y * verts_z);
}
if (dimensions == 2) {
if (verts_z == 1) { /* XY plane. */
return create_grid_mesh(verts_x, verts_y, size.x, size.y, uv_map_id);
return geometry::create_grid_mesh(verts_x, verts_y, size.x, size.y, uv_map_id);
}
if (verts_y == 1) { /* XZ plane. */
Mesh *mesh = create_grid_mesh(verts_x, verts_z, size.x, size.z, uv_map_id);
Mesh *mesh = geometry::create_grid_mesh(verts_x, verts_z, size.x, size.z, uv_map_id);
transform_mesh(*mesh, float3(0), float3(M_PI_2, 0.0f, 0.0f), float3(1));
return mesh;
}
/* YZ plane. */
Mesh *mesh = create_grid_mesh(verts_z, verts_y, size.z, size.y, uv_map_id);
Mesh *mesh = geometry::create_grid_mesh(verts_z, verts_y, size.z, size.y, uv_map_id);
transform_mesh(*mesh, float3(0), float3(0.0f, M_PI_2, 0.0f), float3(1));
return mesh;
}
return create_cuboid_mesh(size, verts_x, verts_y, verts_z, uv_map_id);
return geometry::create_cuboid_mesh(size, verts_x, verts_y, verts_z, uv_map_id);
}
static void node_geo_exec(GeoNodeExecParams params)
@@ -112,6 +103,7 @@ static void node_geo_exec(GeoNodeExecParams params)
AnonymousAttributeIDPtr uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
Mesh *mesh = create_cube_mesh(size, verts_x, verts_y, verts_z, uv_map_id.get());
BKE_id_material_eval_ensure_default_slot(&mesh->id);
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
}

View File

@@ -2,14 +2,12 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "NOD_rna_define.hh"
#include "GEO_mesh_primitive_cylinder_cone.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
@@ -112,21 +110,22 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
ConeAttributeOutputs attribute_outputs;
geometry::ConeAttributeOutputs attribute_outputs;
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom");
attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
/* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */
Mesh *mesh = create_cylinder_or_cone_mesh(radius,
radius,
depth,
circle_segments,
side_segments,
fill_segments,
fill,
attribute_outputs);
Mesh *mesh = geometry::create_cylinder_or_cone_mesh(radius,
radius,
depth,
circle_segments,
side_segments,
fill_segments,
geometry::ConeFillType(fill),
attribute_outputs);
BKE_id_material_eval_ensure_default_slot(reinterpret_cast<ID *>(mesh));
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
}

View File

@@ -2,157 +2,12 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_mesh_primitive_grid.hh"
#include "node_geometry_util.hh"
namespace blender::nodes {
static void calculate_uvs(Mesh *mesh,
const Span<float3> positions,
const Span<int> corner_verts,
const float size_x,
const float size_y,
const AttributeIDRef &uv_map_id)
{
MutableAttributeAccessor attributes = mesh->attributes_for_write();
SpanAttributeWriter<float2> uv_attribute = attributes.lookup_or_add_for_write_only_span<float2>(
uv_map_id, ATTR_DOMAIN_CORNER);
const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x;
const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y;
threading::parallel_for(corner_verts.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const float3 &co = positions[corner_verts[i]];
uv_attribute.span[i].x = (co.x + size_x * 0.5f) * dx;
uv_attribute.span[i].y = (co.y + size_y * 0.5f) * dy;
}
});
uv_attribute.finish();
}
Mesh *create_grid_mesh(const int verts_x,
const int verts_y,
const float size_x,
const float size_y,
const AttributeIDRef &uv_map_id)
{
BLI_assert(verts_x > 0 && verts_y > 0);
const int edges_x = verts_x - 1;
const int edges_y = verts_y - 1;
Mesh *mesh = BKE_mesh_new_nomain(verts_x * verts_y,
edges_x * verts_y + edges_y * verts_x,
edges_x * edges_y,
edges_x * edges_y * 4);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
offset_indices::fill_constant_group_size(4, 0, mesh->face_offsets_for_write());
{
const float dx = edges_x == 0 ? 0.0f : size_x / edges_x;
const float dy = edges_y == 0 ? 0.0f : size_y / edges_y;
const float x_shift = edges_x / 2.0f;
const float y_shift = edges_y / 2.0f;
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * verts_y;
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert_index = y_offset + y;
positions[vert_index].x = (x - x_shift) * dx;
positions[vert_index].y = (y - y_shift) * dy;
positions[vert_index].z = 0.0f;
}
});
}
});
}
const int y_edges_start = 0;
const int x_edges_start = verts_x * edges_y;
/* Build the horizontal edges in the X direction. */
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_vert_offset = x * verts_y;
const int y_edge_offset = y_edges_start + x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert_index = y_vert_offset + y;
edges[y_edge_offset + y] = int2(vert_index, vert_index + 1);
}
});
}
});
/* Build the vertical edges in the Y direction. */
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int x_edge_offset = x_edges_start + y * edges_x;
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int vert_index = x * verts_y + y;
edges[x_edge_offset + x] = int2(vert_index, vert_index + verts_y);
}
});
}
});
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int face_index = y_offset + y;
const int loop_index = face_index * 4;
const int vert_index = x * verts_y + y;
corner_verts[loop_index] = vert_index;
corner_edges[loop_index] = x_edges_start + edges_x * y + x;
corner_verts[loop_index + 1] = vert_index + verts_y;
corner_edges[loop_index + 1] = y_edges_start + edges_y * (x + 1) + y;
corner_verts[loop_index + 2] = vert_index + verts_y + 1;
corner_edges[loop_index + 2] = x_edges_start + edges_x * (y + 1) + x;
corner_verts[loop_index + 3] = vert_index + 1;
corner_edges[loop_index + 3] = y_edges_start + edges_y * x + y;
}
});
}
});
if (uv_map_id && mesh->faces_num != 0) {
calculate_uvs(mesh, positions, corner_verts, size_x, size_y, uv_map_id);
}
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
const float3 bounds = float3(size_x * 0.5f, size_y * 0.5f, 0.0f);
mesh->bounds_set_eager({-bounds, bounds});
return mesh;
}
} // namespace blender::nodes
namespace blender::nodes::node_geo_mesh_primitive_grid_cc {
static void node_declare(NodeDeclarationBuilder &b)
@@ -194,8 +49,8 @@ static void node_geo_exec(GeoNodeExecParams params)
AnonymousAttributeIDPtr uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
Mesh *mesh = create_grid_mesh(verts_x, verts_y, size_x, size_y, uv_map_id.get());
BKE_id_material_eval_ensure_default_slot(&mesh->id);
Mesh *mesh = geometry::create_grid_mesh(verts_x, verts_y, size_x, size_y, uv_map_id.get());
BKE_id_material_eval_ensure_default_slot(reinterpret_cast<ID *>(mesh));
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
}

View File

@@ -2,14 +2,7 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "NOD_rna_define.hh"
@@ -18,6 +11,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_mesh_primitive_line.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
@@ -150,25 +145,27 @@ static void node_geo_exec(GeoNodeExecParams params)
const float resolution = std::max(params.extract_input<float>("Resolution"), 0.0001f);
const int count = math::length(total_delta) / resolution + 1;
const float3 delta = math::normalize(total_delta) * resolution;
mesh = create_line_mesh(start, delta, count);
mesh = geometry::create_line_mesh(start, delta, count);
}
else if (count_mode == GEO_NODE_MESH_LINE_COUNT_TOTAL) {
const int count = params.extract_input<int>("Count");
if (count == 1) {
mesh = create_line_mesh(start, float3(0), count);
mesh = geometry::create_line_mesh(start, float3(0), count);
}
else {
const float3 delta = total_delta / float(count - 1);
mesh = create_line_mesh(start, delta, count);
mesh = geometry::create_line_mesh(start, delta, count);
}
}
}
else if (mode == GEO_NODE_MESH_LINE_MODE_OFFSET) {
const float3 delta = params.extract_input<float3>("Offset");
const int count = params.extract_input<int>("Count");
mesh = create_line_mesh(start, delta, count);
mesh = geometry::create_line_mesh(start, delta, count);
}
BKE_id_material_eval_ensure_default_slot(reinterpret_cast<ID *>(mesh));
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
}
@@ -239,39 +236,3 @@ static void node_register()
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_mesh_primitive_line_cc
namespace blender::nodes {
Mesh *create_line_mesh(const float3 start, const float3 delta, const int count)
{
if (count < 1) {
return nullptr;
}
Mesh *mesh = BKE_mesh_new_nomain(count, count - 1, 0, 0);
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
threading::parallel_invoke(
1024 < count,
[&]() {
threading::parallel_for(positions.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
positions[i] = start + delta * i;
}
});
},
[&]() {
threading::parallel_for(edges.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
edges[i][0] = i;
edges[i][1] = i + 1;
}
});
});
return mesh;
}
} // namespace blender::nodes

View File

@@ -2,16 +2,9 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
#include "node_geometry_util.hh"
@@ -35,309 +28,6 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Vector>("UV Map").field_on_all();
}
static int sphere_vert_total(const int segments, const int rings)
{
return segments * (rings - 1) + 2;
}
static int sphere_edge_total(const int segments, const int rings)
{
return segments * (rings * 2 - 1);
}
static int sphere_corner_total(const int segments, const int rings)
{
const int quad_corners = 4 * segments * (rings - 2);
const int tri_corners = 3 * segments * 2;
return quad_corners + tri_corners;
}
static int sphere_face_total(const int segments, const int rings)
{
const int quads = segments * (rings - 2);
const int triangles = segments * 2;
return quads + triangles;
}
/**
* Also calculate vertex normals here, since the calculation is trivial, and it allows avoiding the
* calculation later, if it's necessary. The vertex normals are just the normalized positions.
*/
BLI_NOINLINE static void calculate_sphere_vertex_data(MutableSpan<float3> positions,
MutableSpan<float3> vert_normals,
const float radius,
const int segments,
const int rings)
{
const float delta_theta = M_PI / rings;
const float delta_phi = (2.0f * M_PI) / segments;
Array<float, 64> segment_cosines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_cosines[segment] = std::cos(phi);
}
Array<float, 64> segment_sines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_sines[segment] = std::sin(phi);
}
positions[0] = float3(0.0f, 0.0f, radius);
vert_normals.first() = float3(0.0f, 0.0f, 1.0f);
int vert_index = 1;
for (const int ring : IndexRange(1, rings - 1)) {
const float theta = ring * delta_theta;
const float sin_theta = std::sin(theta);
const float z = std::cos(theta);
for (const int segment : IndexRange(1, segments)) {
const float x = sin_theta * segment_cosines[segment];
const float y = sin_theta * segment_sines[segment];
positions[vert_index] = float3(x, y, z) * radius;
vert_normals[vert_index] = float3(x, y, z);
vert_index++;
}
}
positions.last() = float3(0.0f, 0.0f, -radius);
vert_normals.last() = float3(0.0f, 0.0f, -1.0f);
}
BLI_NOINLINE static void calculate_sphere_edge_indices(MutableSpan<int2> edges,
const int segments,
const int rings)
{
int edge_index = 0;
/* Add the edges connecting the top vertex to the first ring. */
const int first_vert_ring_index_start = 1;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = 0;
edge[1] = first_vert_ring_index_start + segment;
}
int ring_vert_index_start = 1;
for (const int ring : IndexRange(rings - 1)) {
const int next_ring_vert_index_start = ring_vert_index_start + segments;
/* Add the edges running along each ring. */
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = ring_vert_index_start + ((segment + 1) % segments);
}
/* Add the edges connecting to the next ring. */
if (ring < rings - 2) {
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = next_ring_vert_index_start + segment;
}
}
ring_vert_index_start += segments;
}
/* Add the edges connecting the last ring to the bottom vertex. */
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = last_vert_index;
edge[1] = last_vert_ring_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_faces(MutableSpan<int> face_offsets, const int segments)
{
MutableSpan<int> face_sizes = face_offsets.drop_back(1);
/* Add the triangles connected to the top vertex. */
face_sizes.take_front(segments).fill(3);
/* Add the middle quads. */
face_sizes.drop_front(segments).drop_back(segments).fill(4);
/* Add the triangles connected to the bottom vertex. */
face_sizes.take_back(segments).fill(3);
offset_indices::accumulate_counts_to_offsets(face_offsets);
}
BLI_NOINLINE static void calculate_sphere_corners(MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
const int segments,
const int rings)
{
auto segment_next_or_first = [&](const int segment) {
return segment == segments - 1 ? 0 : segment + 1;
};
/* Add the triangles connected to the top vertex. */
const int first_vert_ring_start = 1;
for (const int segment : IndexRange(segments)) {
const int loop_start = segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = 0;
corner_edges[loop_start + 0] = segment;
corner_verts[loop_start + 1] = first_vert_ring_start + segment;
corner_edges[loop_start + 1] = segments + segment;
corner_verts[loop_start + 2] = first_vert_ring_start + segment_next;
corner_edges[loop_start + 2] = segment_next;
}
const int rings_vert_start = 1;
const int rings_edge_start = segments;
const int rings_loop_start = segments * 3;
for (const int ring : IndexRange(1, rings - 2)) {
const int ring_vert_start = rings_vert_start + (ring - 1) * segments;
const int ring_edge_start = rings_edge_start + (ring - 1) * segments * 2;
const int ring_loop_start = rings_loop_start + (ring - 1) * segments * 4;
const int next_ring_vert_start = ring_vert_start + segments;
const int next_ring_edge_start = ring_edge_start + segments * 2;
const int ring_vertical_edge_start = ring_edge_start + segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + segment * 4;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = ring_vert_start + segment;
corner_edges[loop_start + 0] = ring_vertical_edge_start + segment;
corner_verts[loop_start + 1] = next_ring_vert_start + segment;
corner_edges[loop_start + 1] = next_ring_edge_start + segment;
corner_verts[loop_start + 2] = next_ring_vert_start + segment_next;
corner_edges[loop_start + 2] = ring_vertical_edge_start + segment_next;
corner_verts[loop_start + 3] = ring_vert_start + segment_next;
corner_edges[loop_start + 3] = ring_edge_start + segment;
}
}
/* Add the triangles connected to the bottom vertex. */
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
const int last_edge_ring_start = segments * (rings - 2) * 2 + segments;
const int bottom_edge_fan_start = last_edge_ring_start + segments;
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = last_vert_index;
corner_edges[loop_start + 0] = bottom_edge_fan_start + segment_next;
corner_verts[loop_start + 1] = last_vert_ring_start + segment_next;
corner_edges[loop_start + 1] = last_edge_ring_start + segment;
corner_verts[loop_start + 2] = last_vert_ring_start + segment;
corner_edges[loop_start + 2] = bottom_edge_fan_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_uvs(Mesh *mesh,
const float segments,
const float rings,
const AttributeIDRef &uv_map_id)
{
MutableAttributeAccessor attributes = mesh->attributes_for_write();
SpanAttributeWriter<float2> uv_attribute = attributes.lookup_or_add_for_write_only_span<float2>(
uv_map_id, ATTR_DOMAIN_CORNER);
MutableSpan<float2> uvs = uv_attribute.span;
const float dy = 1.0f / rings;
const float segments_inv = 1.0f / segments;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 0.0f);
uvs[loop_start + 1] = float2(segment * segments_inv, dy);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, dy);
}
const int rings_loop_start = segments * 3;
for (const int i_ring : IndexRange(1, rings - 2)) {
const int ring_loop_start = rings_loop_start + (i_ring - 1) * segments * 4;
const float ring = float(i_ring);
for (const int i_segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + i_segment * 4;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2(segment * segments_inv, ring / rings);
uvs[loop_start + 1] = float2(segment * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 3] = float2((segment + 1.0f) * segments_inv, ring / rings);
}
}
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 1.0f);
uvs[loop_start + 1] = float2((segment + 1.0f) * segments_inv, 1.0f - dy);
uvs[loop_start + 2] = float2(segment * segments_inv, 1.0f - dy);
}
uv_attribute.finish();
}
static Bounds<float3> calculate_bounds_uv_sphere(const float radius,
const int segments,
const int rings)
{
const float delta_theta = M_PI / float(rings);
const float sin_equator = std::sin(std::round(0.5f * rings) * delta_theta);
return calculate_bounds_radial_primitive(0.0f, radius * sin_equator, segments, radius);
}
static Mesh *create_uv_sphere_mesh(const float radius,
const int segments,
const int rings,
const AttributeIDRef &uv_map_id)
{
Mesh *mesh = BKE_mesh_new_nomain(sphere_vert_total(segments, rings),
sphere_edge_total(segments, rings),
sphere_face_total(segments, rings),
sphere_corner_total(segments, rings));
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
BKE_mesh_smooth_flag_set(mesh, false);
threading::parallel_invoke(
1024 < segments * rings,
[&]() {
Vector<float3> vert_normals(mesh->totvert);
calculate_sphere_vertex_data(positions, vert_normals, radius, segments, rings);
bke::mesh_vert_normals_assign(*mesh, std::move(vert_normals));
},
[&]() { calculate_sphere_edge_indices(edges, segments, rings); },
[&]() { calculate_sphere_faces(face_offsets, segments); },
[&]() { calculate_sphere_corners(corner_verts, corner_edges, segments, rings); },
[&]() {
if (uv_map_id) {
calculate_sphere_uvs(mesh, segments, rings, uv_map_id);
}
});
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->bounds_set_eager(calculate_bounds_uv_sphere(radius, segments, rings));
return mesh;
}
static void node_geo_exec(GeoNodeExecParams params)
{
const int segments_num = params.extract_input<int>("Segments");
@@ -357,7 +47,8 @@ static void node_geo_exec(GeoNodeExecParams params)
AnonymousAttributeIDPtr uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map");
Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num, uv_map_id.get());
Mesh *mesh = geometry::create_uv_sphere_mesh(radius, segments_num, rings_num, uv_map_id.get());
BKE_id_material_eval_ensure_default_slot(reinterpret_cast<ID *>(mesh));
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
}