diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc | 1099 |
1 files changed, 702 insertions, 397 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 0d58476fc58..e0923344421 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 @@ -25,529 +25,797 @@ #include "node_geometry_util.hh" +#include <cmath> + namespace blender::nodes { -static void geo_node_mesh_primitive_cone_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Int>("Vertices").default_value(32).min(3); - b.add_input<decl::Float>("Radius Top").min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Radius Bottom").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_output<decl::Geometry>("Geometry"); -} +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; + } -static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) -{ - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); -} + 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; + } -static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( - sizeof(NodeGeometryMeshCone), __func__); + 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; - node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + this->tot_faces = this->top_faces_len + this->side_faces_len + this->bottom_faces_len; + } - node->storage = node_storage; -} + private: + int calculate_total_quad_rings(); + int calculate_total_edge_rings(); + int calculate_total_verts(); + int calculate_total_edges(); + int calculate_total_corners(); +}; -static int vert_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_quad_rings() { - int vert_total = 0; - if (!top_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + if (top_is_point && bottom_is_point) { + return 0; } - else { - vert_total++; + + 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) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } - } - else { - vert_total++; + quad_rings += fill_segments - 1; } - return vert_total; + return quad_rings; } -static int edge_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edge_rings() { if (top_is_point && bottom_is_point) { - return 1; + return 0; } - int edge_total = 0; + int edge_rings = 0; + if (!top_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + edge_rings += fill_segments; } - edge_total += verts_num; + edge_rings += side_segments - 1; if (!bottom_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + edge_rings += fill_segments; } - return edge_total; + return edge_rings; } -static int corner_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_verts() { if (top_is_point && bottom_is_point) { - return 0; + return side_segments + 1; + } + + int vert_total = 0; + + if (top_has_center_vert) { + vert_total++; } - int corner_total = 0; if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + vert_total += circle_segments * fill_segments; } - if (!top_is_point && !bottom_is_point) { - corner_total += verts_num * 4; + vert_total += circle_segments * (side_segments - 1); + + if (!bottom_is_point) { + vert_total += circle_segments * fill_segments; } - else { - corner_total += verts_num * 3; + + if (bottom_has_center_vert) { + vert_total++; } - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + return vert_total; +} + +int ConeConfig::calculate_total_edges() +{ + if (top_is_point && bottom_is_point) { + return side_segments; } - return corner_total; + 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; } -static int face_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_corners() { if (top_is_point && bottom_is_point) { return 0; } - int face_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + 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; } - face_total += verts_num; + corner_total += tot_quad_rings * (circle_segments * 4); - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + 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 face_total; + return corner_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) +static void calculate_cone_vertices(const MutableSpan<MVert> &verts, const ConeConfig &config) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed<float2> uv_attribute = - mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan<float2> uvs = uv_attribute.as_span(); - - Array<float2> circle(verts_num); + Array<float2> circle(config.circle_segments); + const float angle_delta = 2.0f * (M_PI / static_cast<float>(config.circle_segments)); 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; + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle); + circle[i].y = std::sin(angle); 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); - } - } - } + int vert_index = 0; - /* 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); - } + /* Top cone tip or triangle fan center. */ + if (config.top_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, config.height); } - 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); + + /* Top fill including the outer edge of the fill. */ + if (!config.top_is_point) { + const float top_fill_radius_delta = config.radius_top / + static_cast<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; + copy_v3_fl3(verts[vert_index++].co, x, y, config.height); } } } - /* 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); - } + /* Rings along the side. */ + const float side_radius_delta = (config.radius_bottom - config.radius_top) / + static_cast<float>(config.side_segments); + const float height_delta = 2.0f * config.height / static_cast<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; + copy_v3_fl3(verts[vert_index++].co, x, y, ring_height); } - 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); + } + + /* Bottom fill including the outer edge of the fill. */ + if (!config.bottom_is_point) { + const float bottom_fill_radius_delta = config.radius_bottom / + static_cast<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; + copy_v3_fl3(verts[vert_index++].co, x, y, -config.height); } } } - uv_attribute.save(); + /* Bottom cone tip or triangle fan center. */ + if (config.bottom_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, -config.height); + } } -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) +static void calculate_cone_edges(const MutableSpan<MEdge> &edges, const ConeConfig &config) { - 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_normals_tag_dirty(mesh); - return mesh; - } - - 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)); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - 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); - const float angle_delta = 2.0f * (M_PI / static_cast<float>(verts_num)); - for (const int i : IndexRange(verts_num)) { - const float angle = i * angle_delta; - 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)); + 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)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.first_vert; + edge.v2 = config.first_ring_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } - 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)); + /* 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)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = this_ring_vert_start + ((j + 1) % config.circle_segments); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height)); + if (i == config.tot_edge_rings - 1) { + /* There is one fewer ring of connecting edges. */ + break; } - } - - /* 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; + /* Connecting edges. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = next_ring_vert_start + j; 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; + /* Edges for bottom triangle fan or tip. */ + if (config.bottom_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.last_ring_verts_start + i; + edge.v2 = config.last_vert; 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. */ +static void calculate_cone_faces(const MutableSpan<MLoop> &loops, + const MutableSpan<MPoly> &polys, + const ConeConfig &config) +{ int loop_index = 0; int poly_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + + if (config.top_has_center_vert) { + /* 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)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = verts_num; + poly.totloop = 3; - for (const int i : IndexRange(verts_num)) { - MLoop &loop = loops[loop_index++]; - loop.v = top_verts_start + i; - loop.e = top_edges_start + i; - } + MLoop &loop_a = loops[loop_index++]; + loop_a.v = config.first_ring_verts_start + i; + loop_a.e = config.first_ring_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = config.first_ring_verts_start + ((i + 1) % config.circle_segments); + loop_b.e = top_fan_edges_start + ((i + 1) % config.circle_segments); + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_center_vert; + loop_c.e = top_fan_edges_start + i; + } + } + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; + for (const int i : IndexRange(config.circle_segments)) { + MLoop &loop = loops[loop_index++]; + loop.v = i; + loop.e = i; } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { + } + + /* Quads connect one edge ring to the next one. */ + if (config.tot_quad_rings > 0) { + for (const int i : IndexRange(config.tot_quad_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; + + 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)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 3; + poly.totloop = 4; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = top_edges_start + i; + loop_a.v = this_ring_vert_start + j; + loop_a.e = ring_connections_start + j; 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; + loop_b.v = next_ring_vert_start + j; + loop_b.e = next_ring_edges_start + j; MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_center_vert_index; - loop_c.e = top_fan_edges_start + i; + loop_c.v = next_ring_vert_start + ((j + 1) % config.circle_segments); + loop_c.e = ring_connections_start + ((j + 1) % config.circle_segments); + MLoop &loop_d = loops[loop_index++]; + loop_d.v = this_ring_vert_start + ((j + 1) % config.circle_segments); + loop_d.e = this_ring_edges_start + j; } } } - /* 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)) { + if (config.bottom_has_center_vert) { + /* Bottom cone tip or center triangle fan in the fill. */ + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 4; + poly.totloop = 3; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; + loop_a.v = config.last_ring_verts_start + i; + loop_a.e = config.last_fan_edges_start + i; MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + i; - loop_b.e = bottom_edges_start + i; + loop_b.v = config.last_vert; + loop_b.e = config.last_fan_edges_start + (i + 1) % config.circle_segments; 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; + loop_c.v = config.last_ring_verts_start + (i + 1) % config.circle_segments; + loop_c.e = config.last_ring_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; + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; - 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; - } + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards to reverse surface normal. */ + MLoop &loop = loops[loop_index++]; + loop.v = config.last_vert - i; + loop.e = config.last_edge - ((i + 1) % config.circle_segments); + } + } +} + +static void calculate_selection_outputs(Mesh *mesh, + const ConeConfig &config, + ConeAttributeOutputs &attribute_outputs) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + + /* 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; + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.top_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); + MutableSpan<bool> selection = attribute.as_span(); + + if (config.top_is_point) { + selection[config.first_vert] = true; } 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; + selection.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true); + } + attribute.save(); + } - 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; + /* 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; + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.bottom_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); + MutableSpan<bool> selection = attribute.as_span(); + + if (config.bottom_is_point) { + selection[config.last_vert] = true; + } + else { + selection + .slice(config.bottom_faces_start, + face ? config.bottom_faces_len : config.circle_segments) + .fill(true); + } + attribute.save(); + } + + /* Populate "Side" selection output. */ + if (attribute_outputs.side_id) { + OutputAttribute_Typed<bool> attribute = mesh_component.attribute_try_get_for_output_only<bool>( + attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE); + MutableSpan<bool> selection = attribute.as_span(); + + selection.slice(config.side_faces_start, config.side_faces_len).fill(true); + attribute.save(); + } +} + +/** + * 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(Mesh *mesh, const ConeConfig &config) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + + Array<float2> circle(config.circle_segments); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<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 ? + static_cast<float>(config.side_segments) : + static_cast<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; } } } - /* 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; + 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 / static_cast<float>(config.circle_segments); + const float y_delta = (1.0f - bottom) / static_cast<float>(config.side_segments); - 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; + 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); } } - 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; + /* 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 ? + static_cast<float>(config.side_segments) : + static_cast<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; } } } - BKE_mesh_normals_tag_dirty(mesh); + uv_attribute.save(); +} + +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, 0); + copy_v3_fl3(mesh->mvert[0].co, 0.0f, 0.0f, 0.0f); + return mesh; +} - calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type); +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 / static_cast<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, 0, config.tot_corners, config.tot_faces); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + + 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_cone_vertices(verts, config); + calculate_cone_edges(edges, config); + calculate_cone_faces(loops, polys, config); + calculate_cone_uvs(mesh, config); + calculate_selection_outputs(mesh, config, attribute_outputs); return mesh; } -static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) +} // namespace blender::nodes + +namespace blender::nodes::node_geo_mesh_primitive_cone_cc { + +NODE_STORAGE_FUNCS(NodeGeometryMeshCone) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Vertices")) + .default_value(32) + .min(3) + .max(512) + .description(N_("Number of points on the circle at the top and bottom")); + b.add_input<decl::Int>(N_("Side Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("The number of edges running vertically along the side of the cone")); + b.add_input<decl::Int>(N_("Fill Segments")) + .default_value(1) + .min(1) + .max(512) + .description(N_("Number of concentric rings used to fill the round face")); + b.add_input<decl::Float>(N_("Radius Top")) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the top circle of the cone")); + b.add_input<decl::Float>(N_("Radius Bottom")) + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Radius of the bottom circle of the cone")); + b.add_input<decl::Float>(N_("Depth")) + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description(N_("Height of the generated cone")); + b.add_output<decl::Geometry>(N_("Mesh")); + b.add_output<decl::Bool>(N_("Top")).field_source(); + b.add_output<decl::Bool>(N_("Bottom")).field_source(); + b.add_output<decl::Bool>(N_("Side")).field_source(); +} + +static void node_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryMeshCone *node_storage = MEM_cnew<NodeGeometryMeshCone>(__func__); + + node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + + node->storage = node_storage; +} + +static void node_update(bNodeTree *ntree, bNode *node) { - const bNode &node = params.node(); - const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node.storage; + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = node_storage(*node); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; + const bool has_fill = fill != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(ntree, fill_subdiv_socket, has_fill); +} - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) - storage.fill_type; +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); +} - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryMeshCone &storage = node_storage(params.node()); + const GeometryNodeMeshCircleFillType fill = (GeometryNodeMeshCircleFillType)storage.fill_type; + + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); - params.set_output("Geometry", GeometrySet()); + params.set_default_remaining_outputs(); + return; + } + + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_default_remaining_outputs(); + return; + } + + const bool no_fill = fill == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_default_remaining_outputs(); return; } @@ -555,27 +823,64 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) const float radius_bottom = params.extract_input<float>("Radius Bottom"); const float depth = params.extract_input<float>("Depth"); - Mesh *mesh = create_cylinder_or_cone_mesh( - radius_top, radius_bottom, depth, verts_num, fill_type); + ConeAttributeOutputs attribute_outputs; + if (params.output_is_required("Top")) { + attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection"); + } + if (params.output_is_required("Bottom")) { + attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection"); + } + if (params.output_is_required("Side")) { + attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection"); + } + + Mesh *mesh = create_cylinder_or_cone_mesh(radius_top, + radius_bottom, + depth, + circle_segments, + side_segments, + fill_segments, + fill, + attribute_outputs); /* 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)); + if (attribute_outputs.top_id) { + params.set_output("Top", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.top_id), params.attribute_producer_name())); + } + if (attribute_outputs.bottom_id) { + params.set_output( + "Bottom", + AnonymousAttributeFieldInput::Create<bool>(std::move(attribute_outputs.bottom_id), + params.attribute_producer_name())); + } + if (attribute_outputs.side_id) { + params.set_output("Side", + AnonymousAttributeFieldInput::Create<bool>( + std::move(attribute_outputs.side_id), params.attribute_producer_name())); + } + + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } -} // namespace blender::nodes +} // namespace blender::nodes::node_geo_mesh_primitive_cone_cc void register_node_type_geo_mesh_primitive_cone() { + namespace file_ns = blender::nodes::node_geo_mesh_primitive_cone_cc; + static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY, 0); - node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cone_init); + geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); node_type_storage( &ntype, "NodeGeometryMeshCone", node_free_standard_storage, node_copy_standard_storage); - ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cone_exec; - ntype.draw_buttons = blender::nodes::geo_node_mesh_primitive_cone_layout; - ntype.declare = blender::nodes::geo_node_mesh_primitive_cone_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); } |