diff options
author | Hans Goudey <h.goudey@me.com> | 2021-09-28 20:14:13 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2021-09-28 20:14:13 +0300 |
commit | 262b2118565826177133013c324212c66d882456 (patch) | |
tree | 0be55bc95b3c79c712642aa6157f194baf54785a /source/blender/nodes | |
parent | 797064544ea3e176c511f7ff7c1303498a783182 (diff) |
Geometry Nodes: Mesh Point Cloud Conversion Nodes
This commit adds nodes to do direct conversion between meshes and point
clouds in geometry nodes. The conversion from mesh to points is helpful
to instance once per face, or once per edge, which was previously only
possibly with ugly work-arounds. Fields can be evaluated on the mesh
to pass them to the points with the attribute capture node.
The other conversion, point cloud to mesh vertices, is a bit less
obvious, though it is still a common request from users. It's helpful
for flexibility when passing data around, better visualization in the
viewport (and in the future, cycles), and the simplicity of points.
This is a step towards T91754, where point clouds are currently
combined with meshes when outputing to the next modifier after geometry
nodes. Since we're removing the implicit behavior for realizing
instances, it feels natural to use an explicit node to convert points
to vertices too.
Differential Revision: https://developer.blender.org/D12657
Diffstat (limited to 'source/blender/nodes')
-rw-r--r-- | source/blender/nodes/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 2 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 2 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc | 189 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc | 118 |
5 files changed, 313 insertions, 0 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 9ce3d59c121..d2ae91ee4bc 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -231,7 +231,9 @@ set(SRC geometry/nodes/node_geo_mesh_primitive_line.cc geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc geometry/nodes/node_geo_mesh_subdivide.cc + geometry/nodes/node_geo_mesh_to_points.cc geometry/nodes/node_geo_object_info.cc + geometry/nodes/node_geo_points_to_vertices.cc geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index bf780042600..6cbb45f80aa 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -101,6 +101,7 @@ void register_node_type_geo_mesh_primitive_line(void); void register_node_type_geo_mesh_primitive_uv_sphere(void); void register_node_type_geo_mesh_subdivide(void); void register_node_type_geo_mesh_to_curve(void); +void register_node_type_geo_mesh_to_points(void); void register_node_type_geo_object_info(void); void register_node_type_geo_point_distribute(void); void register_node_type_geo_point_instance(void); @@ -108,6 +109,7 @@ void register_node_type_geo_point_rotate(void); void register_node_type_geo_point_scale(void); void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); +void register_node_type_geo_points_to_vertices(void); void register_node_type_geo_points_to_volume(void); void register_node_type_geo_raycast(void); void register_node_type_geo_realize_instances(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index f94394d4986..d653400361a 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -358,7 +358,9 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Mesh Subdivide", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc new file mode 100644 index 00000000000..2f59a3c968b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -0,0 +1,189 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_mesh_to_points_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Mesh"); + b.add_input<decl::Vector>("Position").implicit_field(); + b.add_input<decl::Float>("Radius") + .default_value(0.05f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Points"); +} + +static void geo_node_mesh_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void geo_node_mesh_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryMeshToPoints *data = (NodeGeometryMeshToPoints *)MEM_callocN( + sizeof(NodeGeometryMeshToPoints), __func__); + data->mode = GEO_NODE_MESH_TO_POINTS_FACES; + node->storage = data; +} + +template<typename T> +static void copy_attribute_to_points(const VArray<T> &src, + const IndexMask mask, + MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +static void geometry_set_mesh_to_points(GeometrySet &geometry_set, + Field<float3> &position_field, + Field<float> &radius_field, + Field<bool> &selection_field, + const AttributeDomain domain) +{ + const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>(); + if (mesh_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + GeometryComponentFieldContext field_context{*mesh_component, domain}; + const int domain_size = mesh_component->attribute_domain_size(domain); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + geometry_set.replace_pointcloud(pointcloud); + PointCloudComponent &point_component = + geometry_set.get_component_for_write<PointCloudComponent>(); + + /* Evaluating directly into the point cloud doesn't work because we are not using the full + * "min_array_size" array but compressing the selected elements into the final array with no + * gaps. */ + fn::FieldEvaluator evaluator{field_context, &selection}; + evaluator.add(position_field); + evaluator.add(radius_field); + evaluator.evaluate(); + copy_attribute_to_points(evaluator.get_evaluated<float3>(0), + selection, + {(float3 *)pointcloud->co, pointcloud->totpoint}); + copy_attribute_to_points( + evaluator.get_evaluated<float>(1), selection, {pointcloud->radius, pointcloud->totpoint}); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + attributes.remove("position"); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); + OutputAttribute dst = point_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + copy_attribute_to_points(*src_typed, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + Field<float3> position = params.extract_input<Field<float3>>("Position"); + Field<float> radius = params.extract_input<Field<float>>("Radius"); + Field<bool> selection = params.extract_input<Field<bool>>("Selection"); + + /* Use another multi-function operation to make sure the input radius is greater than zero. + * TODO: Use mutable multi-function once that is supported. */ + static fn::CustomMF_SI_SO<float, float> max_zero_fn( + __func__, [](float value) { return std::max(0.0f, value); }); + auto max_zero_op = std::make_shared<FieldOperation>( + FieldOperation(max_zero_fn, {std::move(radius)})); + Field<float> positive_radius(std::move(max_zero_op), 0); + + const NodeGeometryMeshToPoints &storage = + *(const NodeGeometryMeshToPoints *)params.node().storage; + const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode; + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + switch (mode) { + case GEO_NODE_MESH_TO_POINTS_VERTICES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_POINT); + break; + case GEO_NODE_MESH_TO_POINTS_EDGES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_EDGE); + break; + case GEO_NODE_MESH_TO_POINTS_FACES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_FACE); + break; + case GEO_NODE_MESH_TO_POINTS_CORNERS: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_CORNER); + break; + } + }); + + params.set_output("Points", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_mesh_to_points() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_POINTS, "Mesh to Points", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_mesh_to_points_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_points_exec; + node_type_init(&ntype, blender::nodes::geo_node_mesh_to_points_init); + ntype.draw_buttons = blender::nodes::geo_node_mesh_to_points_layout; + node_type_storage( + &ntype, "NodeGeometryMeshToPoints", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc new file mode 100644 index 00000000000..afd0ced6360 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -0,0 +1,118 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_points_to_vertices_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Points"); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Mesh"); +} + +template<typename T> +static void copy_attribute_to_vertices(const Span<T> src, const IndexMask mask, MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +/* One improvement would be to move the attribute arrays directly to the mesh when possible. */ +static void geometry_set_points_to_vertices(GeometrySet &geometry_set, + Field<bool> &selection_field) +{ + const PointCloudComponent *point_component = + geometry_set.get_component_for_read<PointCloudComponent>(); + if (point_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT}; + const int domain_size = point_component->attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_MESH, false, attributes); + + Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0, 0); + geometry_set.replace_mesh(mesh); + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = point_component->attribute_get_for_read( + attribute_id, ATTR_DOMAIN_POINT, data_type); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + VArray_Span<T> src_typed_span{*src_typed}; + copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_points_to_vertices_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_points_to_vertices(geometry_set, selection_field); + }); + + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_points_to_vertices() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_POINTS_TO_VERTICES, "Points to Vertices", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_points_to_vertices_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_points_to_vertices_exec; + nodeRegisterType(&ntype); +} |