Geometry Nodes: support randomly picking instances from collection
This uses the "id" attribute to randomly pick instances from a collection for each point. There is one issue. When the collection is updated (e.g. when an object is added to it), the nodes modifier is not automatically updated. It seems like we don't have the infrastructure to support this dependency yet. The same issue exists in the Boolean modifier and with collision collections. This should be solved separately soonish. When "Whole Collection" is disabled, one direct child of the input collection is instanced at each point. A direct child can be an object or a collection. Currently, all objects are picked approximately equally often. In the future, we will provide more control over which point gets which instance. Differential Revision: https://developer.blender.org/D9884 Ref T82372.
This commit is contained in:
parent
1be465cccb
commit
3deb21055a
Notes:
blender-bot
2023-02-14 11:00:17 +01:00
Referenced by issue #82372, Point Instancer Node to instance Collections for the trees and flowers use case
|
@ -3196,6 +3196,9 @@ static void node_geometry_buts_point_instance(uiLayout *layout,
|
|||
PointerRNA *ptr)
|
||||
{
|
||||
uiItemR(layout, ptr, "instance_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, NULL, ICON_NONE);
|
||||
if (RNA_enum_get(ptr, "instance_type") == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) {
|
||||
uiItemR(layout, ptr, "use_whole_collection", DEFAULT_FLAGS, NULL, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void node_geometry_buts_attribute_fill(uiLayout *layout,
|
||||
|
|
|
@ -8502,6 +8502,11 @@ static void def_geo_point_instance(StructRNA *srna)
|
|||
RNA_def_property_enum_default(prop, GEO_NODE_POINT_INSTANCE_TYPE_OBJECT);
|
||||
RNA_def_property_ui_text(prop, "Instance Type", "");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
|
||||
|
||||
prop = RNA_def_property(srna, "use_whole_collection", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_negative_sdna(prop, NULL, "custom2", 1);
|
||||
RNA_def_property_ui_text(prop, "Whole Collection", "Instance entire collection on each point");
|
||||
RNA_def_property_update(prop, 0, "rna_Node_socket_update");
|
||||
}
|
||||
|
||||
static void def_geo_attribute_mix(StructRNA *srna)
|
||||
|
|
|
@ -154,6 +154,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
|
|||
}
|
||||
|
||||
/* TODO: Add relations for IDs in settings. */
|
||||
/* TODO: Add dependency for collection changes. */
|
||||
}
|
||||
|
||||
static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
|
||||
|
|
|
@ -50,4 +50,7 @@ void poisson_disk_point_elimination(Vector<float3> const *input_points,
|
|||
float maximum_distance,
|
||||
float3 boundbox);
|
||||
|
||||
Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component,
|
||||
const AttributeDomain domain);
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
|
|
@ -74,54 +74,57 @@ static float noise_from_index(const int seed, const int hash)
|
|||
return BLI_hash_int_01(combined_hash);
|
||||
}
|
||||
|
||||
static void randomize_attribute(BooleanWriteAttribute &attribute, Span<int> hashes, const int seed)
|
||||
static void randomize_attribute(BooleanWriteAttribute &attribute,
|
||||
Span<uint32_t> hashes,
|
||||
const int seed)
|
||||
{
|
||||
MutableSpan<bool> attribute_span = attribute.get_span();
|
||||
for (const int i : IndexRange(attribute.size())) {
|
||||
const bool value = noise_from_index(seed, hashes[i]) > 0.5f;
|
||||
const bool value = noise_from_index(seed, (int)hashes[i]) > 0.5f;
|
||||
attribute_span[i] = value;
|
||||
}
|
||||
attribute.apply_span();
|
||||
}
|
||||
|
||||
static void randomize_attribute(
|
||||
FloatWriteAttribute &attribute, float min, float max, Span<int> hashes, const int seed)
|
||||
FloatWriteAttribute &attribute, float min, float max, Span<uint32_t> hashes, const int seed)
|
||||
{
|
||||
MutableSpan<float> attribute_span = attribute.get_span();
|
||||
for (const int i : IndexRange(attribute.size())) {
|
||||
const float value = noise_from_index(seed, hashes[i]) * (max - min) + min;
|
||||
const float value = noise_from_index(seed, (int)hashes[i]) * (max - min) + min;
|
||||
attribute_span[i] = value;
|
||||
}
|
||||
attribute.apply_span();
|
||||
}
|
||||
|
||||
static void randomize_attribute(
|
||||
Float3WriteAttribute &attribute, float3 min, float3 max, Span<int> hashes, const int seed)
|
||||
Float3WriteAttribute &attribute, float3 min, float3 max, Span<uint32_t> hashes, const int seed)
|
||||
{
|
||||
MutableSpan<float3> attribute_span = attribute.get_span();
|
||||
for (const int i : IndexRange(attribute.size())) {
|
||||
const float x = noise_from_index_and_mutator(seed, hashes[i], 47);
|
||||
const float y = noise_from_index_and_mutator(seed, hashes[i], 8);
|
||||
const float z = noise_from_index_and_mutator(seed, hashes[i], 64);
|
||||
const float x = noise_from_index_and_mutator(seed, (int)hashes[i], 47);
|
||||
const float y = noise_from_index_and_mutator(seed, (int)hashes[i], 8);
|
||||
const float z = noise_from_index_and_mutator(seed, (int)hashes[i], 64);
|
||||
const float3 value = float3(x, y, z) * (max - min) + min;
|
||||
attribute_span[i] = value;
|
||||
}
|
||||
attribute.apply_span();
|
||||
}
|
||||
|
||||
static Array<int> get_element_hashes(GeometryComponent &component,
|
||||
const AttributeDomain domain,
|
||||
const int attribute_size)
|
||||
Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component,
|
||||
const AttributeDomain domain)
|
||||
{
|
||||
const int domain_size = component.attribute_domain_size(domain);
|
||||
|
||||
/* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */
|
||||
ReadAttributePtr hash_attribute = component.attribute_try_get_for_read("id", domain);
|
||||
Array<int> hashes(attribute_size);
|
||||
Array<uint32_t> hashes(domain_size);
|
||||
if (hash_attribute) {
|
||||
BLI_assert(hashes.size() == hash_attribute->size());
|
||||
const CPPType &cpp_type = hash_attribute->cpp_type();
|
||||
fn::GSpan items = hash_attribute->get_span();
|
||||
for (const int i : hashes.index_range()) {
|
||||
hashes[i] = (int)cpp_type.hash(items[i]);
|
||||
hashes[i] = cpp_type.hash(items[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -129,7 +132,7 @@ static Array<int> get_element_hashes(GeometryComponent &component,
|
|||
RandomNumberGenerator rng;
|
||||
rng.seed(0);
|
||||
for (const int i : hashes.index_range()) {
|
||||
hashes[i] = rng.get_int32();
|
||||
hashes[i] = rng.get_uint32();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +157,7 @@ static void randomize_attribute(GeometryComponent &component,
|
|||
return;
|
||||
}
|
||||
|
||||
Array<int> hashes = get_element_hashes(component, domain, attribute->size());
|
||||
Array<uint32_t> hashes = get_geometry_element_ids_as_uints(component, domain);
|
||||
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT: {
|
||||
|
|
|
@ -16,16 +16,21 @@
|
|||
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_persistent_data_handle.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BLI_hash.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
static bNodeSocketTemplate geo_node_point_instance_in[] = {
|
||||
{SOCK_GEOMETRY, N_("Geometry")},
|
||||
{SOCK_OBJECT, N_("Object")},
|
||||
{SOCK_COLLECTION, N_("Collection")},
|
||||
{SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000},
|
||||
{-1, ""},
|
||||
};
|
||||
|
||||
|
@ -40,70 +45,141 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node)
|
|||
{
|
||||
bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1);
|
||||
bNodeSocket *collection_socket = object_socket->next;
|
||||
bNodeSocket *seed_socket = collection_socket->next;
|
||||
|
||||
GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node->custom1;
|
||||
const bool use_whole_collection = node->custom2 == 0;
|
||||
|
||||
nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT);
|
||||
nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION);
|
||||
nodeSetSocketAvailability(
|
||||
seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection);
|
||||
}
|
||||
|
||||
static void get_instanced_data__object(const GeoNodeExecParams ¶ms,
|
||||
MutableSpan<std::optional<InstancedData>> r_instances_data)
|
||||
{
|
||||
bke::PersistentObjectHandle object_handle = params.get_input<bke::PersistentObjectHandle>(
|
||||
"Object");
|
||||
Object *object = params.handle_map().lookup(object_handle);
|
||||
if (object == params.self_object()) {
|
||||
object = nullptr;
|
||||
}
|
||||
if (object != nullptr) {
|
||||
InstancedData instance;
|
||||
instance.type = INSTANCE_DATA_TYPE_OBJECT;
|
||||
instance.data.object = object;
|
||||
r_instances_data.fill(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static void get_instanced_data__collection(
|
||||
const GeoNodeExecParams ¶ms,
|
||||
const GeometryComponent &component,
|
||||
MutableSpan<std::optional<InstancedData>> r_instances_data)
|
||||
{
|
||||
const bNode &node = params.node();
|
||||
bke::PersistentCollectionHandle collection_handle =
|
||||
params.get_input<bke::PersistentCollectionHandle>("Collection");
|
||||
Collection *collection = params.handle_map().lookup(collection_handle);
|
||||
if (collection != nullptr) {
|
||||
const bool use_whole_collection = node.custom2 == 0;
|
||||
if (use_whole_collection) {
|
||||
InstancedData instance;
|
||||
instance.type = INSTANCE_DATA_TYPE_COLLECTION;
|
||||
instance.data.collection = collection;
|
||||
r_instances_data.fill(instance);
|
||||
}
|
||||
else {
|
||||
Vector<InstancedData> possible_instances;
|
||||
/* Direct child objects are instanced as objects. */
|
||||
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
|
||||
Object *object = cob->ob;
|
||||
InstancedData instance;
|
||||
instance.type = INSTANCE_DATA_TYPE_OBJECT;
|
||||
instance.data.object = object;
|
||||
possible_instances.append(instance);
|
||||
}
|
||||
/* Direct child collections are instanced as collections. */
|
||||
LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
|
||||
Collection *child_collection = child->collection;
|
||||
InstancedData instance;
|
||||
instance.type = INSTANCE_DATA_TYPE_COLLECTION;
|
||||
instance.data.collection = child_collection;
|
||||
possible_instances.append(instance);
|
||||
}
|
||||
|
||||
if (!possible_instances.is_empty()) {
|
||||
const int seed = params.get_input<int>("Seed");
|
||||
Array<uint32_t> ids = get_geometry_element_ids_as_uints(component, ATTR_DOMAIN_POINT);
|
||||
for (const int i : r_instances_data.index_range()) {
|
||||
const int index = BLI_hash_int_2d(ids[i], seed) % possible_instances.size();
|
||||
r_instances_data[i] = possible_instances[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Array<std::optional<InstancedData>> get_instanced_data(const GeoNodeExecParams ¶ms,
|
||||
const GeometryComponent &component,
|
||||
const int amount)
|
||||
{
|
||||
const bNode &node = params.node();
|
||||
const GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node.custom1;
|
||||
|
||||
Array<std::optional<InstancedData>> instances_data(amount);
|
||||
|
||||
switch (type) {
|
||||
case GEO_NODE_POINT_INSTANCE_TYPE_OBJECT: {
|
||||
get_instanced_data__object(params, instances_data);
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: {
|
||||
get_instanced_data__collection(params, component, instances_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return instances_data;
|
||||
}
|
||||
|
||||
static void add_instances_from_geometry_component(InstancesComponent &instances,
|
||||
const GeometryComponent &src_geometry,
|
||||
Object *object,
|
||||
Collection *collection)
|
||||
const GeoNodeExecParams ¶ms)
|
||||
{
|
||||
Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>(
|
||||
"position", ATTR_DOMAIN_POINT, {0, 0, 0});
|
||||
Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>(
|
||||
"rotation", ATTR_DOMAIN_POINT, {0, 0, 0});
|
||||
Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>(
|
||||
"scale", ATTR_DOMAIN_POINT, {1, 1, 1});
|
||||
const AttributeDomain domain = ATTR_DOMAIN_POINT;
|
||||
|
||||
for (const int i : IndexRange(positions.size())) {
|
||||
if (object != nullptr) {
|
||||
instances.add_instance(object, positions[i], rotations[i], scales[i]);
|
||||
}
|
||||
if (collection != nullptr) {
|
||||
instances.add_instance(collection, positions[i], rotations[i], scales[i]);
|
||||
const int domain_size = src_geometry.attribute_domain_size(domain);
|
||||
Array<std::optional<InstancedData>> instances_data = get_instanced_data(
|
||||
params, src_geometry, domain_size);
|
||||
|
||||
Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>(
|
||||
"position", domain, {0, 0, 0});
|
||||
Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>(
|
||||
"rotation", domain, {0, 0, 0});
|
||||
Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>(
|
||||
"scale", domain, {1, 1, 1});
|
||||
|
||||
for (const int i : IndexRange(domain_size)) {
|
||||
if (instances_data[i].has_value()) {
|
||||
instances.add_instance(*instances_data[i], positions[i], rotations[i], scales[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void geo_node_point_instance_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)params.node().custom1;
|
||||
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
|
||||
GeometrySet geometry_set_out;
|
||||
|
||||
Object *object = nullptr;
|
||||
Collection *collection = nullptr;
|
||||
|
||||
if (type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT) {
|
||||
bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>(
|
||||
"Object");
|
||||
object = params.handle_map().lookup(object_handle);
|
||||
/* Avoid accidental recursion of instances. */
|
||||
if (object == params.self_object()) {
|
||||
object = nullptr;
|
||||
}
|
||||
}
|
||||
else if (type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION) {
|
||||
bke::PersistentCollectionHandle collection_handle =
|
||||
params.extract_input<bke::PersistentCollectionHandle>("Collection");
|
||||
collection = params.handle_map().lookup(collection_handle);
|
||||
}
|
||||
|
||||
InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>();
|
||||
if (geometry_set.has<MeshComponent>()) {
|
||||
add_instances_from_geometry_component(
|
||||
instances, *geometry_set.get_component_for_read<MeshComponent>(), object, collection);
|
||||
instances, *geometry_set.get_component_for_read<MeshComponent>(), params);
|
||||
}
|
||||
if (geometry_set.has<PointCloudComponent>()) {
|
||||
add_instances_from_geometry_component(
|
||||
instances,
|
||||
*geometry_set.get_component_for_read<PointCloudComponent>(),
|
||||
object,
|
||||
collection);
|
||||
instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params);
|
||||
}
|
||||
|
||||
params.set_output("Geometry", std::move(geometry_set_out));
|
||||
|
|
Loading…
Reference in New Issue