Geometry Nodes: new Instance on Points node

This adds a new Instance on Points node that is a replacement
for the old Point Instance node. Contrary to the old node,
it does not have a mode to instance objects or collections
directly. Instead, the node has to be used with an Object/
Collection Info to achieve the same effect.

Rotation and scale of the instances can be adjusted in the node
directly or can be controlled with a field to get some variation
between instances.

The node supports placing different instances on different points.
The user has control over which instance is placed on which point
using an Instance Index input. If that functionality is used, the
Instance Geometry has to contain multiple instances that can are
instanced separately.

Differential Revision: https://developer.blender.org/D12478
This commit is contained in:
Jacques Lucke 2021-09-27 10:16:38 +02:00
parent 547f7d23ca
commit 617954c143
Notes: blender-bot 2023-02-14 03:00:45 +01:00
Referenced by issue #91372, Point Instance node
13 changed files with 324 additions and 18 deletions

View File

@ -593,6 +593,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeMeshUVSphere"),
]),
GeometryNodeCategory("GEO_POINT", "Point", items=[
NodeItem("GeometryNodeInstanceOnPoints", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeDistributePointsOnFaces", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll),
NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_fields_legacy_poll),

View File

@ -323,6 +323,7 @@ struct GeometrySet {
bool has_instances() const;
bool has_volume() const;
bool has_curve() const;
bool has_realized_data() const;
const Mesh *get_mesh_for_read() const;
const PointCloud *get_pointcloud_for_read() const;
@ -478,7 +479,7 @@ class InstanceReference {
Type type_ = Type::None;
/** Depending on the type this is either null, an Object or Collection pointer. */
void *data_ = nullptr;
std::unique_ptr<GeometrySet> geometry_set_;
std::shared_ptr<GeometrySet> geometry_set_;
public:
InstanceReference() = default;
@ -493,17 +494,10 @@ class InstanceReference {
InstanceReference(GeometrySet geometry_set)
: type_(Type::GeometrySet),
geometry_set_(std::make_unique<GeometrySet>(std::move(geometry_set)))
geometry_set_(std::make_shared<GeometrySet>(std::move(geometry_set)))
{
}
InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_)
{
if (src.type_ == Type::GeometrySet) {
geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_);
}
}
Type type() const
{
return type_;
@ -595,6 +589,7 @@ class InstancesComponent : public GeometryComponent {
void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1);
blender::Span<InstanceReference> references() const;
void remove_unused_references();
void ensure_geometry_instances();
GeometrySet &geometry_set_from_reference(const int reference_index);

View File

@ -1502,6 +1502,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_FILLET 1089
#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090
#define GEO_NODE_STRING_TO_CURVES 1091
#define GEO_NODE_INSTANCE_ON_POINTS 1092
/** \} */

View File

@ -14,11 +14,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <mutex>
#include "BLI_float4x4.hh"
#include "BLI_map.hh"
#include "BLI_rand.hh"
#include "BLI_set.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "DNA_collection_types.h"
@ -182,6 +185,86 @@ blender::Span<InstanceReference> InstancesComponent::references() const
return references_;
}
void InstancesComponent::remove_unused_references()
{
using namespace blender;
using namespace blender::bke;
const int tot_instances = this->instances_amount();
const int tot_references_before = references_.size();
if (tot_instances == 0) {
/* If there are no instances, no reference is needed. */
references_.clear();
return;
}
if (tot_references_before == 1) {
/* There is only one reference and at least one instance. So the only existing reference is
* used. Nothing to do here. */
return;
}
Array<bool> usage_by_handle(tot_references_before, false);
std::mutex mutex;
/* Loop over all instances to see which references are used. */
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
/* Use local counter to avoid lock contention. */
Array<bool> local_usage_by_handle(tot_references_before, false);
for (const int i : range) {
const int handle = instance_reference_handles_[i];
BLI_assert(handle >= 0 && handle < tot_references_before);
local_usage_by_handle[handle] = true;
}
std::lock_guard lock{mutex};
for (const int i : IndexRange(tot_references_before)) {
usage_by_handle[i] |= local_usage_by_handle[i];
}
});
if (!usage_by_handle.as_span().contains(false)) {
/* All references are used. */
return;
}
/* Create new references and a mapping for the handles. */
Vector<int> handle_mapping;
VectorSet<InstanceReference> new_references;
int next_new_handle = 0;
bool handles_have_to_be_updated = false;
for (const int old_handle : IndexRange(tot_references_before)) {
if (!usage_by_handle[old_handle]) {
/* Add some dummy value. It won't be read again. */
handle_mapping.append(-1);
}
else {
const InstanceReference &reference = references_[old_handle];
handle_mapping.append(next_new_handle);
new_references.add_new(reference);
if (old_handle != next_new_handle) {
handles_have_to_be_updated = true;
}
next_new_handle++;
}
}
references_ = new_references;
if (!handles_have_to_be_updated) {
/* All remaining handles are the same as before, so they don't have to be updated. This happens
* when unused handles are only at the end. */
return;
}
/* Update handles of instances. */
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
for (const int i : range) {
instance_reference_handles_[i] = handle_mapping[instance_reference_handles_[i]];
}
});
}
int InstancesComponent::instances_amount() const
{
return instance_transforms_.size();

View File

@ -291,6 +291,19 @@ bool GeometrySet::has_curve() const
return component != nullptr && component->has_curve();
}
/* Returns true when the geometry set has any data that is not an instance. */
bool GeometrySet::has_realized_data() const
{
if (components_.is_empty()) {
return false;
}
if (components_.size() > 1) {
return true;
}
/* Check if the only component is an #InstancesComponent. */
return this->get_component_for_read<InstancesComponent>() == nullptr;
}
/* Create a new geometry set that only contains the given mesh. */
GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{

View File

@ -5764,6 +5764,7 @@ static void registerGeometryNodes()
register_node_type_geo_delete_geometry();
register_node_type_geo_distribute_points_on_faces();
register_node_type_geo_edge_split();
register_node_type_geo_instance_on_points();
register_node_type_geo_input_index();
register_node_type_geo_input_material();
register_node_type_geo_input_normal();

View File

@ -328,17 +328,21 @@ static void get_socket_value(const SocketRef &socket, void *r_value)
* more complex defaults (other than just single values) in their socket declarations. */
if (bsocket.flag & SOCK_HIDE_VALUE) {
const bNode &bnode = *socket.bnode();
if (bsocket.type == SOCK_VECTOR &&
ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE)) {
new (r_value) Field<float3>(
std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>()));
return;
if (bsocket.type == SOCK_VECTOR) {
if (ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE)) {
new (r_value) Field<float3>(
std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>()));
return;
}
}
if (bsocket.type == SOCK_INT && bnode.type == FN_NODE_RANDOM_VALUE) {
new (r_value) Field<int>(std::make_shared<fn::IndexFieldInput>());
return;
else if (bsocket.type == SOCK_INT) {
if (ELEM(bnode.type, FN_NODE_RANDOM_VALUE, GEO_NODE_INSTANCE_ON_POINTS)) {
new (r_value) Field<int>(std::make_shared<fn::IndexFieldInput>());
return;
}
}
}
const bNodeSocketType *typeinfo = socket.typeinfo();
typeinfo->get_geometry_nodes_cpp_value(*socket.bsocket(), r_value);
}

View File

@ -151,6 +151,7 @@ set(SRC
geometry/nodes/legacy/node_geo_material_assign.cc
geometry/nodes/legacy/node_geo_select_by_material.cc
geometry/nodes/legacy/node_geo_point_distribute.cc
geometry/nodes/legacy/node_geo_point_instance.cc
geometry/nodes/node_geo_align_rotation_to_vector.cc
geometry/nodes/node_geo_attribute_capture.cc
@ -202,6 +203,7 @@ set(SRC
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_distribute_points_on_faces.cc
geometry/nodes/node_geo_edge_split.cc
geometry/nodes/node_geo_instance_on_points.cc
geometry/nodes/node_geo_input_material.cc
geometry/nodes/node_geo_input_normal.cc
geometry/nodes/node_geo_input_position.cc
@ -223,7 +225,6 @@ set(SRC
geometry/nodes/node_geo_mesh_subdivide.cc
geometry/nodes/node_geo_mesh_to_curve.cc
geometry/nodes/node_geo_object_info.cc
geometry/nodes/node_geo_point_instance.cc
geometry/nodes/node_geo_point_rotate.cc
geometry/nodes/node_geo_point_scale.cc
geometry/nodes/node_geo_point_separate.cc

View File

@ -80,6 +80,7 @@ void register_node_type_geo_curve_trim(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_distribute_points_on_faces(void);
void register_node_type_geo_edge_split(void);
void register_node_type_geo_instance_on_points(void);
void register_node_type_geo_input_index(void);
void register_node_type_geo_input_material(void);
void register_node_type_geo_input_normal(void);

View File

@ -46,6 +46,8 @@ using bke::WeakAnonymousAttributeID;
using bke::WriteAttributeLookup;
using fn::CPPType;
using fn::Field;
using fn::FieldContext;
using fn::FieldEvaluator;
using fn::FieldInput;
using fn::FieldOperation;
using fn::GField;

View File

@ -337,6 +337,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_
DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
DefNode(GeometryNode, GEO_NODE_INSTANCE_ON_POINTS, 0, "INSTANCE_ON_POINTS", InstanceOnPoints, "Instance on Points", "")
DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "")

View File

@ -0,0 +1,203 @@
/*
* 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_collection_types.h"
#include "BLI_hash.h"
#include "BLI_task.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "node_geometry_util.hh"
namespace blender::nodes {
static void geo_node_instance_on_points_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Points").description("Points to instance on");
b.add_input<decl::Geometry>("Instance").description("Geometry that is instanced on the points");
b.add_input<decl::Bool>("Pick Instance")
.supports_field()
.description("Place different instances on different points");
b.add_input<decl::Int>("Instance Index")
.implicit_field()
.description(
"Index of the instance that used for each point. This is only used when Pick Instances "
"is on. By default the point index is used");
b.add_input<decl::Vector>("Rotation")
.subtype(PROP_EULER)
.supports_field()
.description("Rotation of the instances");
b.add_input<decl::Vector>("Scale")
.default_value({1.0f, 1.0f, 1.0f})
.supports_field()
.description("Scale of the instances");
b.add_input<decl::Int>("Stable ID")
.supports_field()
.description(
"ID for every instance that is used to identify it over time even when the number of "
"instances changes. Used for example for motion blur");
b.add_output<decl::Geometry>("Instances");
}
static void add_instances_from_component(InstancesComponent &dst_component,
const GeometryComponent &src_component,
const GeoNodeExecParams &params)
{
GeometrySet instance = params.get_input<GeometrySet>("Instance");
const AttributeDomain domain = ATTR_DOMAIN_POINT;
const int domain_size = src_component.attribute_domain_size(domain);
/* The initial size of the component might be non-zero when this function is called for multiple
* component types. */
const int start_len = dst_component.instances_amount();
dst_component.resize(start_len + domain_size);
MutableSpan<int> dst_handles = dst_component.instance_reference_handles().slice(start_len,
domain_size);
MutableSpan<float4x4> dst_transforms = dst_component.instance_transforms().slice(start_len,
domain_size);
MutableSpan<int> dst_stable_ids = dst_component.instance_ids().slice(start_len, domain_size);
GeometryComponentFieldContext field_context{src_component, domain};
FieldEvaluator field_evaluator{field_context, domain_size};
const VArray<bool> *pick_instance = nullptr;
const VArray<int> *indices = nullptr;
const VArray<float3> *rotations = nullptr;
const VArray<float3> *scales = nullptr;
field_evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance);
field_evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices);
field_evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations);
field_evaluator.add(params.get_input<Field<float3>>("Scale"), &scales);
field_evaluator.add_with_destination(params.get_input<Field<int>>("Stable ID"), dst_stable_ids);
field_evaluator.evaluate();
GVArray_Typed<float3> positions = src_component.attribute_get_for_read<float3>(
"position", domain, {0, 0, 0});
const InstancesComponent *src_instances = instance.get_component_for_read<InstancesComponent>();
/* Maps handles from the source instances to handles on the new instance. */
Array<int> handle_mapping;
/* Only fill #handle_mapping when it may be used below. */
if (src_instances != nullptr &&
(!pick_instance->is_single() || pick_instance->get_internal_single())) {
Span<InstanceReference> src_references = src_instances->references();
handle_mapping.reinitialize(src_references.size());
for (const int src_instance_handle : src_references.index_range()) {
const InstanceReference &reference = src_references[src_instance_handle];
const int dst_instance_handle = dst_component.add_reference(reference);
handle_mapping[src_instance_handle] = dst_instance_handle;
}
}
const int full_instance_handle = dst_component.add_reference(instance);
/* Add this reference last, because it is the most likely one to be removed later on. */
const int empty_reference_handle = dst_component.add_reference(InstanceReference());
threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) {
for (const int i : range) {
/* Compute base transform for every instances. */
float4x4 &dst_transform = dst_transforms[i];
dst_transform = float4x4::from_loc_eul_scale(
positions[i], rotations->get(i), scales->get(i));
/* Reference that will be used by this new instance. */
int dst_handle = empty_reference_handle;
const bool use_individual_instance = pick_instance->get(i);
if (use_individual_instance) {
if (src_instances != nullptr) {
const int src_instances_amount = src_instances->instances_amount();
const int original_index = indices->get(i);
/* Use #mod_i instead of `%` to get the desirable wrap around behavior where -1 refers to
* the last element. */
const int index = mod_i(original_index, std::max(src_instances_amount, 1));
if (index < src_instances_amount) {
/* Get the reference to the source instance. */
const int src_handle = src_instances->instance_reference_handles()[index];
dst_handle = handle_mapping[src_handle];
/* Take transforms of the source instance into account. */
mul_m4_m4_post(dst_transform.values,
src_instances->instance_transforms()[index].values);
}
}
}
else {
/* Use entire source geometry as instance. */
dst_handle = full_instance_handle;
}
/* Set properties of new instance. */
dst_handles[i] = dst_handle;
}
});
if (pick_instance->is_single()) {
if (pick_instance->get_internal_single()) {
if (instance.has_realized_data()) {
params.error_message_add(
NodeWarningType::Info,
TIP_("Realized geometry is not used when pick instances is true"));
}
}
}
}
static void geo_node_instance_on_points_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Points");
GeometrySet geometry_set_out;
geometry_set = geometry_set_realize_instances(geometry_set);
InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>();
if (geometry_set.has<MeshComponent>()) {
add_instances_from_component(
instances, *geometry_set.get_component_for_read<MeshComponent>(), params);
}
if (geometry_set.has<PointCloudComponent>()) {
add_instances_from_component(
instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params);
}
if (geometry_set.has<CurveComponent>()) {
add_instances_from_component(
instances, *geometry_set.get_component_for_read<CurveComponent>(), params);
}
/* Unused references may have been added above. Remove those now so that other nodes don't
* process them needlessly. */
instances.remove_unused_references();
params.set_output("Instances", std::move(geometry_set_out));
}
} // namespace blender::nodes
void register_node_type_geo_instance_on_points()
{
static bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_INSTANCE_ON_POINTS, "Instance on Points", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_instance_on_points_declare;
ntype.geometry_node_execute = blender::nodes::geo_node_instance_on_points_exec;
nodeRegisterType(&ntype);
}