diff options
-rw-r--r-- | release/scripts/startup/nodeitems_builtins.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_geometry_set.hh | 13 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/geometry_component_instances.cc | 83 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/geometry_set.cc | 13 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 1 | ||||
-rw-r--r-- | source/blender/modifiers/intern/MOD_nodes_evaluator.cc | 20 | ||||
-rw-r--r-- | source/blender/nodes/CMakeLists.txt | 3 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry_exec.hh | 2 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc (renamed from source/blender/nodes/geometry/nodes/node_geo_point_instance.cc) | 0 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc | 203 |
13 files changed, 324 insertions, 18 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index e658706a946..88a0782a102 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -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), diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 5fcdbc83e25..571c6c6a0a0 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -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); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 52f8a3d8136..5491f2a3de9 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -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 /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 9479d012cb8..f1f60266545 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -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(); diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 54e9fadf8ed..1ebdde75f46 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -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) { diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index c10aa3bbc5a..f1f643ffed7 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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(); diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index fd0205cffc5..6f18c4d40db 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -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); } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index e1cceae2964..844e838272c 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -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 diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index b37d4956e7a..bf780042600 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -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); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 6ce3d0f2ab5..962e1c3c48f 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -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; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 6af9a7b4e98..e9f7ec2c7ff 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -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", "") diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc index fb45c22ced4..fb45c22ced4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc new file mode 100644 index 00000000000..21a130da8f9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -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 ¶ms) +{ + 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); +} |