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
This commit is contained in:
Hans Goudey 2021-09-28 12:14:13 -05:00
parent 797064544e
commit 262b211856
11 changed files with 368 additions and 1 deletions

View File

@ -582,6 +582,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeBoolean"),
NodeItem("GeometryNodeTriangulate"),
NodeItem("GeometryNodeMeshSubdivide"),
NodeItem("GeometryNodePointsToVertices", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_PRIMITIVES_MESH", "Mesh Primitives", items=[
NodeItem("GeometryNodeMeshCircle"),
@ -594,6 +595,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeMeshUVSphere"),
]),
GeometryNodeCategory("GEO_POINT", "Point", items=[
NodeItem("GeometryNodeMeshToPoints", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeInstanceOnPoints", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeDistributePointsOnFaces", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll),

View File

@ -1503,6 +1503,8 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090
#define GEO_NODE_STRING_TO_CURVES 1091
#define GEO_NODE_INSTANCE_ON_POINTS 1092
#define GEO_NODE_MESH_TO_POINTS 1093
#define GEO_NODE_POINTS_TO_VERTICES 1094
/** \} */

View File

@ -5778,6 +5778,7 @@ static void registerGeometryNodes()
register_node_type_geo_mesh_primitive_uv_sphere();
register_node_type_geo_mesh_subdivide();
register_node_type_geo_mesh_to_curve();
register_node_type_geo_mesh_to_points();
register_node_type_geo_object_info();
register_node_type_geo_point_distribute();
register_node_type_geo_point_instance();
@ -5785,6 +5786,7 @@ static void registerGeometryNodes()
register_node_type_geo_point_scale();
register_node_type_geo_point_separate();
register_node_type_geo_point_translate();
register_node_type_geo_points_to_vertices();
register_node_type_geo_points_to_volume();
register_node_type_geo_raycast();
register_node_type_geo_realize_instances();

View File

@ -1511,6 +1511,11 @@ typedef struct NodeGeometryCurveFill {
uint8_t mode;
} NodeGeometryCurveFill;
typedef struct NodeGeometryMeshToPoints {
/* GeometryNodeMeshToPointsMode */
uint8_t mode;
} NodeGeometryMeshToPoints;
typedef struct NodeGeometryAttributeCapture {
/* CustomDataType. */
int8_t data_type;
@ -2124,6 +2129,13 @@ typedef enum GeometryNodeCurveFillMode {
GEO_NODE_CURVE_FILL_MODE_NGONS = 1,
} GeometryNodeCurveFillMode;
typedef enum GeometryNodeMeshToPointsMode {
GEO_NODE_MESH_TO_POINTS_VERTICES = 0,
GEO_NODE_MESH_TO_POINTS_EDGES = 1,
GEO_NODE_MESH_TO_POINTS_FACES = 2,
GEO_NODE_MESH_TO_POINTS_CORNERS = 3,
} GeometryNodeMeshToPointsMode;
typedef enum GeometryNodeStringToCurvesOverflowMode {
GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW = 0,
GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT = 1,

View File

@ -10306,6 +10306,42 @@ static void def_geo_curve_to_points(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_mesh_to_points(StructRNA *srna)
{
PropertyRNA *prop;
static EnumPropertyItem mode_items[] = {
{GEO_NODE_MESH_TO_POINTS_VERTICES,
"VERTICES",
0,
"Vertices",
"Create a point in the point cloud for each selected vertex"},
{GEO_NODE_MESH_TO_POINTS_EDGES,
"EDGES",
0,
"Edges",
"Create a point in the point cloud for each selected edge"},
{GEO_NODE_MESH_TO_POINTS_FACES,
"FACES",
0,
"Faces",
"Create a point in the point cloud for each selected face"},
{GEO_NODE_MESH_TO_POINTS_CORNERS,
"CORNERS",
0,
"Corners",
"Create a point in the point cloud for each selected face corner"},
{0, NULL, 0, NULL, NULL},
};
RNA_def_struct_sdna_from(srna, "NodeGeometryMeshToPoints", "storage");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, mode_items);
RNA_def_property_ui_text(prop, "Mode", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_geo_curve_trim(StructRNA *srna)
{
PropertyRNA *prop;

View File

@ -329,7 +329,7 @@ static void get_socket_value(const SocketRef &socket, void *r_value)
if (bsocket.flag & SOCK_HIDE_VALUE) {
const bNode &bnode = *socket.bnode();
if (bsocket.type == SOCK_VECTOR) {
if (ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE)) {
if (ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE, GEO_NODE_MESH_TO_POINTS)) {
new (r_value) Field<float3>(
std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>()));
return;

View File

@ -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

View File

@ -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);

View File

@ -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", "")

View File

@ -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);
}

View File

@ -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);
}