1
1

Compare commits

...

9 Commits

Author SHA1 Message Date
69582bf5b7 Change default to "Count", divide radius by 10 2021-06-10 09:42:30 -05:00
4c6e29a8e7 Merge branch 'master' into geometry-nodes-curve-to-points-node 2021-06-10 09:14:59 -05:00
91f141f970 Merge branch 'master' into geometry-nodes-curve-to-points-node 2021-06-09 15:15:40 -05:00
79743803fe Merge branch 'master' into geometry-nodes-curve-to-points-node 2021-06-09 12:32:09 -05:00
fb7540405e Check for size==0, reduce duplication 2021-06-09 08:54:41 -05:00
1e80d0e732 Add comment 2021-06-09 08:42:00 -05:00
9dafd4a97b Merge branch 'master' into geometry-nodes-curve-to-points-node 2021-06-09 08:40:11 -05:00
51f10a3143 Fix RNA struct type 2021-06-08 17:51:31 -05:00
003fad9431 Geometry Nodes: Curve to Points Node
This node implements the second option of T87429, creating points
along the input splines with the necessary evaluated information
for instancing: `tangent`, `normal`, and `rotation` attributes.
All generic curve point and spline attributes are copied to the
result points as well.

I'm actually quite happy with the implementation right now. It's
readable enough and there isn't too much boilerplate code. The thing
I expect could use improvement is that there is a lot of temporary
memory allocation. Sharing a buffer for each thread would be a nice
improvement in the future.

The patch currently includes some refactoring of the curve resample
node with some newly abstracted functions. I may commit that separately.
The "Evaluated" mode for the resample node will be a separate commit.

Differential Revision: https://developer.blender.org/D11539
2021-06-08 17:32:37 -05:00
9 changed files with 438 additions and 0 deletions

View File

@@ -504,6 +504,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurveToMesh"),
NodeItem("GeometryNodeCurveResample"),
NodeItem("GeometryNodeMeshToCurve"),
NodeItem("GeometryNodeCurveToPoints"),
NodeItem("GeometryNodeCurveLength"),
]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[

View File

@@ -1434,6 +1434,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_LENGTH 1054
#define GEO_NODE_SELECT_BY_MATERIAL 1055
#define GEO_NODE_CONVEX_HULL 1056
#define GEO_NODE_CURVE_TO_POINTS 1057
/** \} */

View File

@@ -5057,6 +5057,7 @@ static void registerGeometryNodes()
register_node_type_geo_convex_hull();
register_node_type_geo_curve_length();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_to_points();
register_node_type_geo_curve_resample();
register_node_type_geo_delete_geometry();
register_node_type_geo_edge_split();

View File

@@ -1362,6 +1362,11 @@ typedef struct NodeGeometryCurveResample {
uint8_t mode;
} NodeGeometryCurveResample;
typedef struct NodeGeometryCurveToPoints {
/* GeometryNodeCurveSampleMode. */
uint8_t mode;
} NodeGeometryCurveToPoints;
typedef struct NodeGeometryAttributeTransfer {
/* AttributeDomain. */
int8_t domain;
@@ -1873,6 +1878,7 @@ typedef enum GeometryNodeMeshLineCountMode {
typedef enum GeometryNodeCurveSampleMode {
GEO_NODE_CURVE_SAMPLE_COUNT = 0,
GEO_NODE_CURVE_SAMPLE_LENGTH = 1,
GEO_NODE_CURVE_SAMPLE_EVALUATED = 2,
} GeometryNodeCurveSampleMode;
typedef enum GeometryNodeAttributeTransferMapMode {

View File

@@ -9840,6 +9840,38 @@ static void def_geo_curve_resample(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_curve_to_points(StructRNA *srna)
{
PropertyRNA *prop;
static EnumPropertyItem mode_items[] = {
{GEO_NODE_CURVE_SAMPLE_EVALUATED,
"EVALUATED",
0,
"Evaluated",
"Add points on the curve's evaluated points, based on the resolution for NURBS and Bezier "
"splines"},
{GEO_NODE_CURVE_SAMPLE_COUNT,
"COUNT",
0,
"Count",
"Sample each spline by evenly distributing the specified number of points"},
{GEO_NODE_CURVE_SAMPLE_LENGTH,
"LENGTH",
0,
"Length",
"Sample each spline by splitting it into segments with the specified length"},
{0, NULL, 0, NULL, NULL},
};
RNA_def_struct_sdna_from(srna, "NodeGeometryCurveToPoints", "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", "How to generate points from the input curve");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_attribute_transfer(StructRNA *srna)
{
static EnumPropertyItem mapping_items[] = {

View File

@@ -165,6 +165,7 @@ set(SRC
geometry/nodes/node_geo_convex_hull.cc
geometry/nodes/node_geo_curve_length.cc
geometry/nodes/node_geo_curve_to_mesh.cc
geometry/nodes/node_geo_curve_to_points.cc
geometry/nodes/node_geo_curve_resample.cc
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_edge_split.cc

View File

@@ -53,6 +53,7 @@ void register_node_type_geo_collection_info(void);
void register_node_type_geo_convex_hull(void);
void register_node_type_geo_curve_length(void);
void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_resample(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_edge_split(void);

View File

@@ -293,6 +293,7 @@ DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Conve
DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "")
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")

View File

@@ -0,0 +1,394 @@
/*
* 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_array.hh"
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "BKE_pointcloud.h"
#include "BKE_spline.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "node_geometry_util.hh"
static bNodeSocketTemplate geo_node_curve_to_points_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_INT, N_("Count"), 10, 0, 0, 0, 1, 100000},
{SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.001f, FLT_MAX, PROP_DISTANCE},
{-1, ""},
};
static bNodeSocketTemplate geo_node_curve_to_points_out[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{-1, ""},
};
static void geo_node_curve_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", 0, "", ICON_NONE);
}
static void geo_node_curve_to_points_init(bNodeTree *UNUSED(tree), bNode *node)
{
NodeGeometryCurveToPoints *data = (NodeGeometryCurveToPoints *)MEM_callocN(
sizeof(NodeGeometryCurveToPoints), __func__);
data->mode = GEO_NODE_CURVE_SAMPLE_COUNT;
node->storage = data;
}
static void geo_node_curve_to_points_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)node->storage;
const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode;
bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next;
bNodeSocket *length_socket = count_socket->next;
nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT);
nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
}
namespace blender::nodes {
/**
* Evaluate splines in parallel to speed up the rest of the node's execution.
*/
static void evaluate_splines(Span<SplinePtr> splines)
{
parallel_for_each(splines, [](const SplinePtr &spline) {
/* These functions fill the corresponding caches on each spline. */
spline->evaluated_positions();
spline->evaluated_tangents();
spline->evaluated_normals();
spline->evaluated_lengths();
});
}
static Array<int> calculate_spline_point_offsets(GeoNodeExecParams &params,
const GeometryNodeCurveSampleMode mode,
const CurveEval &curve,
const Span<SplinePtr> splines)
{
const int size = curve.splines().size();
switch (mode) {
case GEO_NODE_CURVE_SAMPLE_COUNT: {
const int count = params.extract_input<int>("Count");
if (count < 1) {
return {0};
}
Array<int> offsets(size + 1);
for (const int i : offsets.index_range()) {
offsets[i] = count * i;
}
return offsets;
}
case GEO_NODE_CURVE_SAMPLE_LENGTH: {
/* Don't allow asymptotic count increase for low resolution values. */
const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f);
Array<int> offsets(size + 1);
int offset = 0;
for (const int i : IndexRange(size)) {
offsets[i] = offset;
offset += splines[i]->length() / resolution;
}
offsets.last() = offset;
return offsets;
}
case GEO_NODE_CURVE_SAMPLE_EVALUATED: {
return curve.evaluated_point_offsets();
}
}
BLI_assert_unreachable();
return {0};
}
/**
* \note This doesn't store a map for spline domain attributes.
*/
struct ResultAttributes {
int result_size;
MutableSpan<float3> positions;
MutableSpan<float> radii;
MutableSpan<float> tilts;
Map<std::string, GMutableSpan> point_attributes;
MutableSpan<float3> tangents;
MutableSpan<float3> normals;
MutableSpan<float3> rotations;
};
static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points,
const StringRef name,
const CustomDataType data_type)
{
points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
WriteAttributeLookup attribute = points.attribute_try_get_for_write(name);
BLI_assert(attribute);
return attribute.varray->get_internal_span();
}
template<typename T>
static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points,
const StringRef name)
{
GMutableSpan attribute = create_attribute_and_retrieve_span(
points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
return attribute.typed<T>();
}
/**
* Create references for all result point cloud attributes to simplify accessing them later on.
*/
static ResultAttributes create_point_attributes(PointCloudComponent &points,
const CurveEval &curve)
{
ResultAttributes attributes;
attributes.result_size = points.attribute_domain_size(ATTR_DOMAIN_POINT);
attributes.positions = create_attribute_and_retrieve_span<float3>(points, "position");
attributes.radii = create_attribute_and_retrieve_span<float>(points, "radius");
attributes.tilts = create_attribute_and_retrieve_span<float>(points, "tilt");
/* Because of the invariants of the curve component, we use the attributes of the
* first spline as a representative for the attribute meta data all splines. */
curve.splines().first()->attributes.foreach_attribute(
[&](StringRefNull name, const AttributeMetaData &meta_data) {
attributes.point_attributes.add_new(
name, create_attribute_and_retrieve_span(points, name, meta_data.data_type));
return true;
},
ATTR_DOMAIN_POINT);
attributes.tangents = create_attribute_and_retrieve_span<float3>(points, "tangent");
attributes.normals = create_attribute_and_retrieve_span<float3>(points, "normal");
attributes.rotations = create_attribute_and_retrieve_span<float3>(points, "rotation");
return attributes;
}
/**
* TODO: For non-poly splines, this has double copies that could be avoided as part
* of a general look at optimizing uses of #interpolate_to_evaluated_points.
*/
static void copy_evaluated_point_attributes(Span<SplinePtr> splines,
Span<int> offsets,
ResultAttributes &data)
{
parallel_for(splines.index_range(), 64, [&](IndexRange range) {
for (const int i : range) {
const Spline &spline = *splines[i];
const int offset = offsets[i];
const int size = offsets[i + 1] - offsets[i];
data.positions.slice(offset, size).copy_from(spline.evaluated_positions());
spline.interpolate_to_evaluated_points(spline.radii())
->materialize(data.radii.slice(offset, size));
spline.interpolate_to_evaluated_points(spline.tilts())
->materialize(data.tilts.slice(offset, size));
for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
const StringRef name = item.key;
GMutableSpan point_span = item.value;
BLI_assert(spline.attributes.get_for_read(name));
GSpan spline_span = *spline.attributes.get_for_read(name);
spline.interpolate_to_evaluated_points(spline_span)
->materialize(point_span.slice(offset, size).data());
}
data.tangents.slice(offset, size).copy_from(spline.evaluated_tangents());
data.normals.slice(offset, size).copy_from(spline.evaluated_normals());
}
});
}
static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines,
Span<int> offsets,
ResultAttributes &data)
{
parallel_for(splines.index_range(), 64, [&](IndexRange range) {
for (const int i : range) {
const Spline &spline = *splines[i];
const int offset = offsets[i];
const int size = offsets[i + 1] - offsets[i];
if (size == 0) {
continue;
}
const Array<float> uniform_samples = spline.sample_uniform_index_factors(size);
spline.sample_based_on_index_factors<float3>(
spline.evaluated_positions(), uniform_samples, data.positions.slice(offset, size));
spline.sample_based_on_index_factors<float>(
spline.interpolate_to_evaluated_points(spline.radii()),
uniform_samples,
data.radii.slice(offset, size));
spline.sample_based_on_index_factors<float>(
spline.interpolate_to_evaluated_points(spline.tilts()),
uniform_samples,
data.tilts.slice(offset, size));
for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
const StringRef name = item.key;
GMutableSpan point_span = item.value;
BLI_assert(spline.attributes.get_for_read(name));
GSpan spline_span = *spline.attributes.get_for_read(name);
spline.sample_based_on_index_factors(*spline.interpolate_to_evaluated_points(spline_span),
uniform_samples,
point_span.slice(offset, size));
}
spline.sample_based_on_index_factors<float3>(
spline.evaluated_tangents(), uniform_samples, data.tangents.slice(offset, size));
for (float3 &tangent : data.tangents) {
tangent.normalize();
}
spline.sample_based_on_index_factors<float3>(
spline.evaluated_normals(), uniform_samples, data.normals.slice(offset, size));
for (float3 &normals : data.normals) {
normals.normalize();
}
}
});
}
/**
* \note Use attributes from the curve component rather than the attribute data directly on the
* attribute storage to allow reading the virtual spline attributes like "cyclic" and "resolution".
*/
static void copy_spline_domain_attributes(const CurveComponent &curve_component,
Span<int> offsets,
PointCloudComponent &points)
{
curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
if (meta_data.domain != ATTR_DOMAIN_CURVE) {
return true;
}
GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
name, ATTR_DOMAIN_CURVE, meta_data.data_type);
const CPPType &type = spline_attribute->type();
OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
name, ATTR_DOMAIN_POINT, meta_data.data_type);
GMutableSpan result = result_attribute.as_span();
for (const int i : IndexRange(spline_attribute->size())) {
const int offset = offsets[i];
const int size = offsets[i + 1] - offsets[i];
if (size != 0) {
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
spline_attribute->get(i, buffer);
type.fill_initialized(buffer, result[offset], size);
}
}
result_attribute.save();
return true;
});
}
static void create_default_rotation_attribute(ResultAttributes &data)
{
parallel_for(IndexRange(data.result_size), 512, [&](IndexRange range) {
for (const int i : range) {
data.rotations[i] = float4x4::from_normalized_axis_data(
{0, 0, 0}, data.normals[i], data.tangents[i])
.to_euler();
}
});
}
static void geo_node_curve_to_points_exec(GeoNodeExecParams params)
{
NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)params.node().storage;
const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode;
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
geometry_set = bke::geometry_set_realize_instances(geometry_set);
if (!geometry_set.has_curve()) {
params.set_output("Geometry", GeometrySet());
return;
}
const CurveComponent &curve_component = *geometry_set.get_component_for_read<CurveComponent>();
const CurveEval &curve = *curve_component.get_for_read();
const Span<SplinePtr> splines = curve.splines();
curve.assert_valid_point_attributes();
evaluate_splines(splines);
const Array<int> offsets = calculate_spline_point_offsets(params, mode, curve, splines);
const int total_size = offsets.last();
if (total_size == 0) {
params.set_output("Geometry", GeometrySet());
return;
}
GeometrySet result = GeometrySet::create_with_pointcloud(BKE_pointcloud_new_nomain(total_size));
PointCloudComponent &point_component = result.get_component_for_write<PointCloudComponent>();
ResultAttributes new_attributes = create_point_attributes(point_component, curve);
switch (mode) {
case GEO_NODE_CURVE_SAMPLE_COUNT:
case GEO_NODE_CURVE_SAMPLE_LENGTH:
copy_uniform_sample_point_attributes(splines, offsets, new_attributes);
break;
case GEO_NODE_CURVE_SAMPLE_EVALUATED:
copy_evaluated_point_attributes(splines, offsets, new_attributes);
break;
}
copy_spline_domain_attributes(curve_component, offsets, point_component);
create_default_rotation_attribute(new_attributes);
/* The default radius is way too large for points, divide by 10. */
for (float &radius : new_attributes.radii) {
radius *= 0.1f;
}
params.set_output("Geometry", std::move(result));
}
} // namespace blender::nodes
void register_node_type_geo_curve_to_points()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0);
node_type_socket_templates(&ntype, geo_node_curve_to_points_in, geo_node_curve_to_points_out);
ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_points_exec;
ntype.draw_buttons = geo_node_curve_to_points_layout;
node_type_storage(
&ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage);
node_type_init(&ntype, geo_node_curve_to_points_init);
node_type_update(&ntype, geo_node_curve_to_points_update);
nodeRegisterType(&ntype);
}