Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2021-04-07 21:15:43 +0300
committerHans Goudey <h.goudey@me.com>2021-04-07 21:15:43 +0300
commit22ba85b510210d623e06174d0b73996585bf36fc (patch)
tree67197b5a7c8e3f9238810ebddfb38e8eeb7b5f95
parent7c04ef210e0e688956b416c3ccc6570b5330b4b3 (diff)
Geometry Nodes: Greatly improve speed of some mesh primitives
Some of the BMesh primitive operators have a lot of overhead due to the fact that they use other operators like extrusion, removing doubles, and rotation, to build each shape rather than filling arrays directly. Implementing the primitives directly with the Mesh data structure is much more efficient, since it's simple to fill the result data based on the known inputs. It also allows also skip the conversion from BMesh to Mesh after the calculations. Speed matters more for procedural operations than for the existing operators accessible from the add menu, since they will be executed every evaluation rather than once before a destructive workflow. | Shape | Before (ms) | After (ms) | Speedup (x times faster) | | -------- | -------------| ------------| -------------------------| | Cylinder | 1.1676 | 0.31327 | 3.72 | | Cone | 4.5890 | 0.17762 | 25.8 | | Sphere | 64213.3 | 13.595 | 4720 | The downside of this change is that there will be two implementations for these three primitives, in these nodes and in `bmo_primitive.c`. One option would be re-implementing the BMesh primitives in terms of this code, but that would require `BMesh` to depend on `Mesh`, which is currently avoided. On the other hand, it will be easier to add new features to these nodes like different fill types for the top and the bottom of a cylinder, rings for a cylinder, and tagging the output with boolean attributes. Another factor to consider is that the add mesh object operator could be implemented with these nodes, just providing more benefit for a faster implementation. As a future cleanup, there is room to share code that generates the "rings" topology between different nodes that generate meshes. Differential Revision: https://developer.blender.org/D10730
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc370
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc250
2 files changed, 560 insertions, 60 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
index e9228a2942b..761d5d6c388 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
@@ -17,14 +17,11 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
-#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "UI_interface.h"
#include "UI_resources.h"
-#include "bmesh.h"
-
#include "node_geometry_util.hh"
static bNodeSocketTemplate geo_node_mesh_primitive_cone_in[] = {
@@ -189,45 +186,357 @@ static int face_total(const GeometryNodeMeshCircleFillType fill_type,
return face_total;
}
+static void calculate_uvs(Mesh *mesh,
+ const bool top_is_point,
+ const bool bottom_is_point,
+ const int verts_num,
+ const GeometryNodeMeshCircleFillType fill_type)
+{
+ MeshComponent mesh_component;
+ mesh_component.replace(mesh, GeometryOwnershipType::Editable);
+ OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output(
+ "uv_map", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr);
+ MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>();
+
+ Array<float2> circle(verts_num);
+ float angle = 0.0f;
+ const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num);
+ for (const int i : IndexRange(verts_num)) {
+ circle[i].x = std::cos(angle) * 0.225f + 0.25f;
+ circle[i].y = std::sin(angle) * 0.225f + 0.25f;
+ angle += angle_delta;
+ }
+
+ int loop_index = 0;
+ if (!top_is_point) {
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
+ for (const int i : IndexRange(verts_num)) {
+ uvs[loop_index++] = circle[i];
+ }
+ }
+ else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ uvs[loop_index++] = circle[i];
+ uvs[loop_index++] = circle[(i + 1) % verts_num];
+ uvs[loop_index++] = float2(0.25f, 0.25f);
+ }
+ }
+ }
+
+ /* Create side corners and faces. */
+ if (!top_is_point && !bottom_is_point) {
+ const float bottom = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f;
+ /* Quads connect the top and bottom. */
+ for (const int i : IndexRange(verts_num)) {
+ const float vert = static_cast<float>(i);
+ uvs[loop_index++] = float2(vert / verts_num, bottom);
+ uvs[loop_index++] = float2(vert / verts_num, 1.0f);
+ uvs[loop_index++] = float2((vert + 1.0f) / verts_num, 1.0f);
+ uvs[loop_index++] = float2((vert + 1.0f) / verts_num, bottom);
+ }
+ }
+ else {
+ /* Triangles connect the top and bottom section. */
+ if (!top_is_point) {
+ for (const int i : IndexRange(verts_num)) {
+ uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f);
+ uvs[loop_index++] = float2(0.75f, 0.25f);
+ uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f);
+ }
+ }
+ else {
+ BLI_assert(!bottom_is_point);
+ for (const int i : IndexRange(verts_num)) {
+ uvs[loop_index++] = circle[i];
+ uvs[loop_index++] = circle[(i + 1) % verts_num];
+ uvs[loop_index++] = float2(0.25f, 0.25f);
+ }
+ }
+ }
+
+ /* Create bottom corners and faces. */
+ if (!bottom_is_point) {
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
+ for (const int i : IndexRange(verts_num)) {
+ /* Go backwards because of reversed face normal. */
+ uvs[loop_index++] = circle[verts_num - 1 - i] + float2(0.5f, 0.0f);
+ }
+ }
+ else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f);
+ uvs[loop_index++] = float2(0.75f, 0.25f);
+ uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f);
+ }
+ }
+ }
+
+ uv_attribute.apply_span_and_save();
+}
+
Mesh *create_cylinder_or_cone_mesh(const float radius_top,
const float radius_bottom,
const float depth,
const int verts_num,
const GeometryNodeMeshCircleFillType fill_type)
{
- const float4x4 transform = float4x4::identity();
-
const bool top_is_point = radius_top == 0.0f;
const bool bottom_is_point = radius_bottom == 0.0f;
+ const float height = depth * 0.5f;
+ /* Handle the case of a line / single point before everything else to avoid
+ * the need to check for it later. */
+ if (top_is_point && bottom_is_point) {
+ const bool single_vertex = height == 0.0f;
+ Mesh *mesh = BKE_mesh_new_nomain(single_vertex ? 1 : 2, single_vertex ? 0 : 1, 0, 0, 0);
+ copy_v3_v3(mesh->mvert[0].co, float3(0.0f, 0.0f, height));
+ if (single_vertex) {
+ const short up[3] = {0, 0, SHRT_MAX};
+ copy_v3_v3_short(mesh->mvert[0].no, up);
+ return mesh;
+ }
+ copy_v3_v3(mesh->mvert[1].co, float3(0.0f, 0.0f, -height));
+ mesh->medge[0].v1 = 0;
+ mesh->medge[0].v2 = 1;
+ mesh->medge[0].flag |= ME_LOOSEEDGE;
+ BKE_mesh_calc_normals(mesh);
+ return mesh;
+ }
- const BMeshCreateParams bmcp = {true};
- const BMAllocTemplate allocsize = {
+ Mesh *mesh = BKE_mesh_new_nomain(
vert_total(fill_type, verts_num, top_is_point, bottom_is_point),
edge_total(fill_type, verts_num, top_is_point, bottom_is_point),
+ 0,
corner_total(fill_type, verts_num, top_is_point, bottom_is_point),
- face_total(fill_type, verts_num, top_is_point, bottom_is_point)};
- BMesh *bm = BM_mesh_create(&allocsize, &bmcp);
-
- const bool cap_end = (fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE);
- const bool cap_tri = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN);
- BMO_op_callf(bm,
- BMO_FLAG_DEFAULTS,
- "create_cone segments=%i diameter1=%f diameter2=%f cap_ends=%b "
- "cap_tris=%b depth=%f matrix=%m4 calc_uvs=%b",
- verts_num,
- radius_bottom,
- radius_top,
- cap_end,
- cap_tri,
- depth,
- transform.values,
- true);
-
- BMeshToMeshParams params{};
- params.calc_object_remap = false;
- Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
- BM_mesh_free(bm);
+ face_total(fill_type, verts_num, top_is_point, bottom_is_point));
+ MutableSpan<MVert> verts{mesh->mvert, mesh->totvert};
+ MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop};
+ MutableSpan<MEdge> edges{mesh->medge, mesh->totedge};
+ MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly};
+
+ /* Calculate vertex positions. */
+ const int top_verts_start = 0;
+ const int bottom_verts_start = top_verts_start + (!top_is_point ? verts_num : 1);
+ float angle = 0.0f;
+ const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num);
+ for (const int i : IndexRange(verts_num)) {
+ const float x = std::cos(angle);
+ const float y = std::sin(angle);
+ if (!top_is_point) {
+ copy_v3_v3(verts[top_verts_start + i].co, float3(x * radius_top, y * radius_top, height));
+ }
+ if (!bottom_is_point) {
+ copy_v3_v3(verts[bottom_verts_start + i].co,
+ float3(x * radius_bottom, y * radius_bottom, -height));
+ }
+ angle += angle_delta;
+ }
+ if (top_is_point) {
+ copy_v3_v3(verts[top_verts_start].co, float3(0.0f, 0.0f, height));
+ }
+ if (bottom_is_point) {
+ copy_v3_v3(verts[bottom_verts_start].co, float3(0.0f, 0.0f, -height));
+ }
+
+ /* Add center vertices for the triangle fans at the end. */
+ const int top_center_vert_index = bottom_verts_start + (bottom_is_point ? 1 : verts_num);
+ const int bottom_center_vert_index = top_center_vert_index + (top_is_point ? 0 : 1);
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ if (!top_is_point) {
+ copy_v3_v3(verts[top_center_vert_index].co, float3(0.0f, 0.0f, height));
+ }
+ if (!bottom_is_point) {
+ copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height));
+ }
+ }
+
+ /* Create top edges. */
+ const int top_edges_start = 0;
+ const int top_fan_edges_start = (!top_is_point &&
+ fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ?
+ top_edges_start + verts_num :
+ top_edges_start;
+ if (!top_is_point) {
+ for (const int i : IndexRange(verts_num)) {
+ MEdge &edge = edges[top_edges_start + i];
+ edge.v1 = top_verts_start + i;
+ edge.v2 = top_verts_start + (i + 1) % verts_num;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ MEdge &edge = edges[top_fan_edges_start + i];
+ edge.v1 = top_center_vert_index;
+ edge.v2 = top_verts_start + i;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+ }
+
+ /* Create connecting edges. */
+ const int connecting_edges_start = top_fan_edges_start + (!top_is_point ? verts_num : 0);
+ for (const int i : IndexRange(verts_num)) {
+ MEdge &edge = edges[connecting_edges_start + i];
+ edge.v1 = top_verts_start + (!top_is_point ? i : 0);
+ edge.v2 = bottom_verts_start + (!bottom_is_point ? i : 0);
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+
+ /* Create bottom edges. */
+ const int bottom_edges_start = connecting_edges_start + verts_num;
+ const int bottom_fan_edges_start = (!bottom_is_point &&
+ fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ?
+ bottom_edges_start + verts_num :
+ bottom_edges_start;
+ if (!bottom_is_point) {
+ for (const int i : IndexRange(verts_num)) {
+ MEdge &edge = edges[bottom_edges_start + i];
+ edge.v1 = bottom_verts_start + i;
+ edge.v2 = bottom_verts_start + (i + 1) % verts_num;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ MEdge &edge = edges[bottom_fan_edges_start + i];
+ edge.v1 = bottom_center_vert_index;
+ edge.v2 = bottom_verts_start + i;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+ }
+
+ /* Create top corners and faces. */
+ int loop_index = 0;
+ int poly_index = 0;
+ if (!top_is_point) {
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = verts_num;
+
+ for (const int i : IndexRange(verts_num)) {
+ MLoop &loop = loops[loop_index++];
+ loop.v = top_verts_start + i;
+ loop.e = top_edges_start + i;
+ }
+ }
+ else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = top_verts_start + i;
+ loop_a.e = top_edges_start + i;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = top_verts_start + (i + 1) % verts_num;
+ loop_b.e = top_fan_edges_start + (i + 1) % verts_num;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = top_center_vert_index;
+ loop_c.e = top_fan_edges_start + i;
+ }
+ }
+ }
+
+ /* Create side corners and faces. */
+ if (!top_is_point && !bottom_is_point) {
+ /* Quads connect the top and bottom. */
+ for (const int i : IndexRange(verts_num)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 4;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = top_verts_start + i;
+ loop_a.e = connecting_edges_start + i;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = bottom_verts_start + i;
+ loop_b.e = bottom_edges_start + i;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = bottom_verts_start + (i + 1) % verts_num;
+ loop_c.e = connecting_edges_start + (i + 1) % verts_num;
+ MLoop &loop_d = loops[loop_index++];
+ loop_d.v = top_verts_start + (i + 1) % verts_num;
+ loop_d.e = top_edges_start + i;
+ }
+ }
+ else {
+ /* Triangles connect the top and bottom section. */
+ if (!top_is_point) {
+ for (const int i : IndexRange(verts_num)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = top_verts_start + i;
+ loop_a.e = connecting_edges_start + i;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = bottom_verts_start;
+ loop_b.e = connecting_edges_start + (i + 1) % verts_num;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = top_verts_start + (i + 1) % verts_num;
+ loop_c.e = top_edges_start + i;
+ }
+ }
+ else {
+ BLI_assert(!bottom_is_point);
+ for (const int i : IndexRange(verts_num)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = bottom_verts_start + i;
+ loop_a.e = bottom_edges_start + i;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = bottom_verts_start + (i + 1) % verts_num;
+ loop_b.e = connecting_edges_start + (i + 1) % verts_num;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = top_verts_start;
+ loop_c.e = connecting_edges_start + i;
+ }
+ }
+ }
+
+ /* Create bottom corners and faces. */
+ if (!bottom_is_point) {
+ if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = verts_num;
+
+ for (const int i : IndexRange(verts_num)) {
+ /* Go backwards to reverse surface normal. */
+ MLoop &loop = loops[loop_index++];
+ loop.v = bottom_verts_start + verts_num - 1 - i;
+ loop.e = bottom_edges_start + verts_num - 1 - (i + 1) % verts_num;
+ }
+ }
+ else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
+ for (const int i : IndexRange(verts_num)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = bottom_verts_start + i;
+ loop_a.e = bottom_fan_edges_start + i;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = bottom_center_vert_index;
+ loop_b.e = bottom_fan_edges_start + (i + 1) % verts_num;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = bottom_verts_start + (i + 1) % verts_num;
+ loop_c.e = bottom_edges_start + i;
+ }
+ }
+ }
+
+ BKE_mesh_calc_normals(mesh);
+
+ calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type);
+
+ BLI_assert(BKE_mesh_is_valid(mesh));
return mesh;
}
@@ -253,6 +562,7 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params)
Mesh *mesh = create_cylinder_or_cone_mesh(
radius_top, radius_bottom, depth, verts_num, fill_type);
+ /* 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);
params.set_output("Geometry", GeometrySet::create_with_mesh(mesh));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
index 8efba91da1a..3c8d2b5e924 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
@@ -17,19 +17,16 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
-#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "UI_interface.h"
#include "UI_resources.h"
-#include "bmesh.h"
-
#include "node_geometry_util.hh"
static bNodeSocketTemplate geo_node_mesh_primitive_uv_sphere_in[] = {
{SOCK_INT, N_("Segments"), 32, 0.0f, 0.0f, 0.0f, 3, 1024},
- {SOCK_INT, N_("Rings"), 16, 0.0f, 0.0f, 0.0f, 3, 1024},
+ {SOCK_INT, N_("Rings"), 16, 0.0f, 0.0f, 0.0f, 2, 1024},
{SOCK_FLOAT, N_("Radius"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE},
{-1, ""},
};
@@ -65,31 +62,224 @@ static int sphere_face_total(const int segments, const int rings)
return quads + triangles;
}
-static Mesh *create_uv_sphere_mesh_bmesh(const float radius, const int segments, const int rings)
+static void calculate_sphere_vertex_data(MutableSpan<MVert> verts,
+ const float radius,
+ const int segments,
+ const int rings)
+{
+ const float delta_theta = M_PI / rings;
+ const float delta_phi = (2 * M_PI) / segments;
+
+ copy_v3_v3(verts[0].co, float3(0.0f, 0.0f, radius));
+ normal_float_to_short_v3(verts[0].no, float3(0.0f, 0.0f, 1.0f));
+
+ int vert_index = 1;
+ float theta = delta_theta;
+ for (const int UNUSED(ring) : IndexRange(rings - 1)) {
+ float phi = 0.0f;
+ const float z = cosf(theta);
+ for (const int UNUSED(segment) : IndexRange(segments)) {
+ const float sin_theta = std::sin(theta);
+ const float x = sin_theta * std::cos(phi);
+ const float y = sin_theta * std::sin(phi);
+ copy_v3_v3(verts[vert_index].co, float3(x, y, z) * radius);
+ normal_float_to_short_v3(verts[vert_index].no, float3(x, y, z));
+ phi += delta_phi;
+ vert_index++;
+ }
+ theta += delta_theta;
+ }
+
+ copy_v3_v3(verts.last().co, float3(0.0f, 0.0f, -radius));
+ normal_float_to_short_v3(verts.last().no, float3(0.0f, 0.0f, -1.0f));
+}
+
+static void calculate_sphere_edge_indices(MutableSpan<MEdge> 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)) {
+ MEdge &edge = edges[edge_index++];
+ edge.v1 = 0;
+ edge.v2 = first_vert_ring_index_start + segment;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+
+ 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)) {
+ MEdge &edge = edges[edge_index++];
+ edge.v1 = ring_vert_index_start + segment;
+ edge.v2 = ring_vert_index_start + ((segment + 1) % segments);
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+
+ /* Add the edges connecting to the next ring. */
+ if (ring < rings - 2) {
+ for (const int segment : IndexRange(segments)) {
+ MEdge &edge = edges[edge_index++];
+ edge.v1 = ring_vert_index_start + segment;
+ edge.v2 = next_ring_vert_index_start + segment;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+ 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)) {
+ MEdge &edge = edges[edge_index++];
+ edge.v1 = last_vert_index;
+ edge.v2 = last_vert_ring_start + segment;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+}
+
+static void calculate_sphere_faces(MutableSpan<MLoop> loops,
+ MutableSpan<MPoly> polys,
+ const int segments,
+ const int rings)
{
- const float4x4 transform = float4x4::identity();
-
- const BMeshCreateParams bmcp = {true};
- const BMAllocTemplate allocsize = {sphere_vert_total(segments, rings),
- sphere_edge_total(segments, rings),
- sphere_corner_total(segments, rings),
- sphere_face_total(segments, rings)};
- BMesh *bm = BM_mesh_create(&allocsize, &bmcp);
-
- BMO_op_callf(bm,
- BMO_FLAG_DEFAULTS,
- "create_uvsphere u_segments=%i v_segments=%i diameter=%f matrix=%m4 calc_uvs=%b",
- segments,
- rings,
- radius,
- transform.values,
- true);
-
- BMeshToMeshParams params{};
- params.calc_object_remap = false;
- Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
- BM_mesh_bm_to_me(nullptr, bm, mesh, &params);
- BM_mesh_free(bm);
+ int loop_index = 0;
+ int poly_index = 0;
+
+ /* Add the triangles conntected to the top vertex. */
+ const int first_vert_ring_index_start = 1;
+ for (const int segment : IndexRange(segments)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = 0;
+ loop_a.e = segment;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = first_vert_ring_index_start + segment;
+ loop_b.e = segments + segment;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = first_vert_ring_index_start + (segment + 1) % segments;
+ loop_c.e = (segment + 1) % segments;
+ }
+
+ int ring_vert_index_start = 1;
+ int ring_edge_index_start = segments;
+ for (const int UNUSED(ring) : IndexRange(1, rings - 2)) {
+ const int next_ring_vert_index_start = ring_vert_index_start + segments;
+ const int next_ring_edge_index_start = ring_edge_index_start + segments * 2;
+ const int ring_vertical_edge_index_start = ring_edge_index_start + segments;
+
+ for (const int segment : IndexRange(segments)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 4;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = ring_vert_index_start + segment;
+ loop_a.e = ring_vertical_edge_index_start + segment;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = next_ring_vert_index_start + segment;
+ loop_b.e = next_ring_edge_index_start + segment;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = next_ring_vert_index_start + (segment + 1) % segments;
+ loop_c.e = ring_vertical_edge_index_start + (segment + 1) % segments;
+ MLoop &loop_d = loops[loop_index++];
+ loop_d.v = ring_vert_index_start + (segment + 1) % segments;
+ loop_d.e = ring_edge_index_start + segment;
+ }
+ ring_vert_index_start += segments;
+ ring_edge_index_start += segments * 2;
+ }
+
+ /* Add the triangles connected to the bottom vertex. */
+ 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)) {
+ MPoly &poly = polys[poly_index++];
+ poly.loopstart = loop_index;
+ poly.totloop = 3;
+
+ MLoop &loop_a = loops[loop_index++];
+ loop_a.v = last_vert_index;
+ loop_a.e = bottom_edge_fan_start + (segment + 1) % segments;
+ MLoop &loop_b = loops[loop_index++];
+ loop_b.v = last_vert_ring_start + (segment + 1) % segments;
+ loop_b.e = last_edge_ring_start + segment;
+ MLoop &loop_c = loops[loop_index++];
+ loop_c.v = last_vert_ring_start + segment;
+ loop_c.e = bottom_edge_fan_start + segment;
+ }
+}
+
+static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float rings)
+{
+ MeshComponent mesh_component;
+ mesh_component.replace(mesh, GeometryOwnershipType::Editable);
+ OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output(
+ "uv_map", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr);
+ MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>();
+
+ int loop_index = 0;
+ const float dy = 1.0f / rings;
+
+ for (const int i_segment : IndexRange(segments)) {
+ const float segment = static_cast<float>(i_segment);
+ uvs[loop_index++] = float2((segment + 0.5f) / segments, 0.0f);
+ uvs[loop_index++] = float2(segment / segments, dy);
+ uvs[loop_index++] = float2((segment + 1.0f) / segments, dy);
+ }
+
+ for (const int i_ring : IndexRange(1, rings - 2)) {
+ const float ring = static_cast<float>(i_ring);
+ for (const int i_segment : IndexRange(segments)) {
+ const float segment = static_cast<float>(i_segment);
+ uvs[loop_index++] = float2(segment / segments, ring / rings);
+ uvs[loop_index++] = float2(segment / segments, (ring + 1.0f) / rings);
+ uvs[loop_index++] = float2((segment + 1.0f) / segments, (ring + 1.0f) / rings);
+ uvs[loop_index++] = float2((segment + 1.0f) / segments, ring / rings);
+ }
+ }
+
+ for (const int i_segment : IndexRange(segments)) {
+ const float segment = static_cast<float>(i_segment);
+ uvs[loop_index++] = float2((segment + 0.5f) / segments, 1.0f);
+ uvs[loop_index++] = float2((segment + 1.0f) / segments, 1.0f - dy);
+ uvs[loop_index++] = float2(segment / segments, 1.0f - dy);
+ }
+
+ uv_attribute.apply_span_and_save();
+}
+
+static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const int rings)
+{
+ Mesh *mesh = BKE_mesh_new_nomain(sphere_vert_total(segments, rings),
+ sphere_edge_total(segments, rings),
+ 0,
+ sphere_corner_total(segments, rings),
+ sphere_face_total(segments, rings));
+ MutableSpan<MVert> verts{mesh->mvert, mesh->totvert};
+ MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop};
+ MutableSpan<MEdge> edges{mesh->medge, mesh->totedge};
+ MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly};
+
+ calculate_sphere_vertex_data(verts, radius, segments, rings);
+
+ calculate_sphere_edge_indices(edges, segments, rings);
+
+ calculate_sphere_faces(loops, polys, segments, rings);
+
+ calculate_sphere_uvs(mesh, segments, rings);
+
+ BLI_assert(BKE_mesh_is_valid(mesh));
return mesh;
}
@@ -98,14 +288,14 @@ static void geo_node_mesh_primitive_uv_sphere_exec(GeoNodeExecParams params)
{
const int segments_num = params.extract_input<int>("Segments");
const int rings_num = params.extract_input<int>("Rings");
- if (segments_num < 3 || rings_num < 3) {
+ if (segments_num < 3 || rings_num < 2) {
params.set_output("Geometry", GeometrySet());
return;
}
const float radius = params.extract_input<float>("Radius");
- Mesh *mesh = create_uv_sphere_mesh_bmesh(radius, segments_num, rings_num);
+ Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num);
params.set_output("Geometry", GeometrySet::create_with_mesh(mesh));
}