diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc | 107 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc | 141 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc | 221 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc (renamed from source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc) | 112 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc | 4 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc | 206 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_distribute_poisson_disk.cc | 281 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_instance.cc | 63 |
8 files changed, 1081 insertions, 54 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc new file mode 100644 index 00000000000..3f7023ba88f --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -0,0 +1,107 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BKE_colorband.h" + +static bNodeSocketTemplate geo_node_attribute_color_ramp_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_color_ramp_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &bnode = params.node(); + NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)bnode.storage; + + const std::string result_name = params.get_input<std::string>("Result"); + /* Once we support more domains at the user level, we have to decide how the result domain is + * choosen. */ + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + const CustomDataType result_type = CD_PROP_COLOR; + + WriteAttributePtr attribute_result = component.attribute_try_ensure_for_write( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + Color4fWriteAttribute attribute_out = std::move(attribute_result); + + const std::string input_name = params.get_input<std::string>("Attribute"); + FloatReadAttribute attribute_in = component.attribute_get_for_read<float>( + input_name, result_domain, 0.0f); + + Span<float> data_in = attribute_in.get_span(); + MutableSpan<Color4f> data_out = attribute_out.get_span(); + + ColorBand *color_ramp = &node_storage->color_ramp; + for (const int i : data_in.index_range()) { + BKE_colorband_evaluate(color_ramp, data_in[i], data_out[i]); + } + + attribute_out.apply_span(); +} + +static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( + sizeof(NodeAttributeColorRamp), __func__); + BKE_colorband_init(&node_storage->color_ramp, true); + node->storage = node_storage; +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_color_ramp() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_COLOR_RAMP, "Attribute Color Ramp", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_color_ramp_in, geo_node_attribute_color_ramp_out); + node_type_storage( + &ntype, "NodeAttributeColorRamp", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::geo_node_attribute_color_ramp_init); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_color_ramp_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc new file mode 100644 index 00000000000..5cdbd18ecc8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -0,0 +1,141 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BLI_rand.hh" + +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +static bNodeSocketTemplate geo_node_attribute_fill_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_VECTOR, N_("Value"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Value"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_RGBA, N_("Value"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_BOOLEAN, N_("Value"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_fill_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_fill_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; +} + +static void geo_node_attribute_fill_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *socket_value_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); + bNodeSocket *socket_value_float = socket_value_vector->next; + bNodeSocket *socket_value_color4f = socket_value_float->next; + bNodeSocket *socket_value_boolean = socket_value_color4f->next; + + const CustomDataType data_type = static_cast<CustomDataType>(node->custom1); + + nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL); +} + +namespace blender::nodes { + +static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + const std::string attribute_name = params.get_input<std::string>("Attribute"); + if (attribute_name.empty()) { + return; + } + + WriteAttributePtr attribute = component.attribute_try_ensure_for_write( + attribute_name, domain, data_type); + if (!attribute) { + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + FloatWriteAttribute float_attribute = std::move(attribute); + const float value = params.get_input<float>("Value_001"); + MutableSpan<float> attribute_span = float_attribute.get_span(); + attribute_span.fill(value); + float_attribute.apply_span(); + break; + } + case CD_PROP_FLOAT3: { + Float3WriteAttribute float3_attribute = std::move(attribute); + const float3 value = params.get_input<float3>("Value"); + MutableSpan<float3> attribute_span = float3_attribute.get_span(); + attribute_span.fill(value); + float3_attribute.apply_span(); + break; + } + case CD_PROP_COLOR: { + Color4fWriteAttribute color4f_attribute = std::move(attribute); + const Color4f value = params.get_input<Color4f>("Value_002"); + MutableSpan<Color4f> attribute_span = color4f_attribute.get_span(); + attribute_span.fill(value); + color4f_attribute.apply_span(); + break; + } + case CD_PROP_BOOL: { + BooleanWriteAttribute boolean_attribute = std::move(attribute); + const bool value = params.get_input<bool>("Value_003"); + MutableSpan<bool> attribute_span = boolean_attribute.get_span(); + attribute_span.fill(value); + boolean_attribute.apply_span(); + break; + } + default: + break; + } +} + +static void geo_node_attribute_fill_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + fill_attribute(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + fill_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_fill() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_FILL, "Attribute Fill", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates(&ntype, geo_node_attribute_fill_in, geo_node_attribute_fill_out); + node_type_init(&ntype, geo_node_attribute_fill_init); + node_type_update(&ntype, geo_node_attribute_fill_update); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_fill_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc new file mode 100644 index 00000000000..2f2558a2d53 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -0,0 +1,221 @@ +/* + * 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 "BKE_material.h" + +#include "DNA_material_types.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_mix_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Factor")}, + {SOCK_FLOAT, N_("Factor"), 0.5, 0.0, 0.0, 0.0, 0.0, 1.0, PROP_FACTOR}, + {SOCK_STRING, N_("A")}, + {SOCK_FLOAT, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_RGBA, N_("A"), 0.5, 0.5, 0.5, 1.0}, + {SOCK_STRING, N_("B")}, + {SOCK_FLOAT, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_RGBA, N_("B"), 0.5, 0.5, 0.5, 1.0}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_mix_attribute_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void do_mix_operation_float(const int blend_mode, + const FloatReadAttribute &factors, + const FloatReadAttribute &inputs_a, + const FloatReadAttribute &inputs_b, + FloatWriteAttribute &results) +{ + const int size = results.size(); + for (const int i : IndexRange(size)) { + const float factor = factors[i]; + float3 a{inputs_a[i]}; + const float3 b{inputs_b[i]}; + ramp_blend(blend_mode, a, factor, b); + const float result = a.length(); + results.set(i, result); + } +} + +static void do_mix_operation_float3(const int blend_mode, + const FloatReadAttribute &factors, + const Float3ReadAttribute &inputs_a, + const Float3ReadAttribute &inputs_b, + Float3WriteAttribute &results) +{ + const int size = results.size(); + for (const int i : IndexRange(size)) { + const float factor = factors[i]; + float3 a = inputs_a[i]; + const float3 b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } +} + +static void do_mix_operation_color4f(const int blend_mode, + const FloatReadAttribute &factors, + const Color4fReadAttribute &inputs_a, + const Color4fReadAttribute &inputs_b, + Color4fWriteAttribute &results) +{ + const int size = results.size(); + for (const int i : IndexRange(size)) { + const float factor = factors[i]; + Color4f a = inputs_a[i]; + const Color4f b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } +} + +static void do_mix_operation(const CustomDataType result_type, + int blend_mode, + const FloatReadAttribute &attribute_factor, + ReadAttributePtr attribute_a, + ReadAttributePtr attribute_b, + WriteAttributePtr attribute_result) +{ + if (result_type == CD_PROP_FLOAT) { + FloatReadAttribute attribute_a_float = std::move(attribute_a); + FloatReadAttribute attribute_b_float = std::move(attribute_b); + FloatWriteAttribute attribute_result_float = std::move(attribute_result); + do_mix_operation_float(blend_mode, + attribute_factor, + attribute_a_float, + attribute_b_float, + attribute_result_float); + } + else if (result_type == CD_PROP_FLOAT3) { + Float3ReadAttribute attribute_a_float3 = std::move(attribute_a); + Float3ReadAttribute attribute_b_float3 = std::move(attribute_b); + Float3WriteAttribute attribute_result_float3 = std::move(attribute_result); + do_mix_operation_float3(blend_mode, + attribute_factor, + attribute_a_float3, + attribute_b_float3, + attribute_result_float3); + } + else if (result_type == CD_PROP_COLOR) { + Color4fReadAttribute attribute_a_color4f = std::move(attribute_a); + Color4fReadAttribute attribute_b_color4f = std::move(attribute_b); + Color4fWriteAttribute attribute_result_color4f = std::move(attribute_result); + do_mix_operation_color4f(blend_mode, + attribute_factor, + attribute_a_color4f, + attribute_b_color4f, + attribute_result_color4f); + } +} + +static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + const NodeAttributeMix *node_storage = (const NodeAttributeMix *)node.storage; + + CustomDataType result_type = CD_PROP_COLOR; + AttributeDomain result_domain = ATTR_DOMAIN_POINT; + + /* Use type and domain from the result attribute, if it exists already. */ + const std::string result_name = params.get_input<std::string>("Result"); + const ReadAttributePtr result_attribute_read = component.attribute_try_get_for_read(result_name); + if (result_attribute_read) { + result_type = result_attribute_read->custom_data_type(); + result_domain = result_attribute_read->domain(); + } + + WriteAttributePtr attribute_result = component.attribute_try_ensure_for_write( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + FloatReadAttribute attribute_factor = params.get_input_attribute<float>( + "Factor", component, result_domain, 0.5f); + ReadAttributePtr attribute_a = params.get_input_attribute( + "A", component, result_domain, result_type, nullptr); + ReadAttributePtr attribute_b = params.get_input_attribute( + "B", component, result_domain, result_type, nullptr); + + do_mix_operation(result_type, + node_storage->blend_type, + attribute_factor, + std::move(attribute_a), + std::move(attribute_b), + std::move(attribute_result)); +} + +static void geo_node_attribute_mix_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + attribute_mix_calc(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + attribute_mix_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), + "attribute mix node"); + data->blend_type = MA_RAMP_BLEND; + data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node->storage = data; +} + +static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + update_attribute_input_socket_availabilities( + *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_mix() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MIX, "Attribute Mix", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates(&ntype, geo_node_attribute_mix_in, geo_node_mix_attribute_out); + node_type_init(&ntype, blender::nodes::geo_node_attribute_mix_init); + node_type_update(&ntype, blender::nodes::geo_node_attribute_mix_update); + node_type_storage( + &ntype, "NodeAttributeMix", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_mix_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 5cacb96412c..2c3acfc9735 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -16,12 +16,13 @@ #include "node_geometry_util.hh" +#include "BLI_hash.h" #include "BLI_rand.hh" #include "DNA_mesh_types.h" #include "DNA_pointcloud_types.h" -static bNodeSocketTemplate geo_node_random_attribute_in[] = { +static bNodeSocketTemplate geo_node_attribute_randomize_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_STRING, N_("Attribute")}, {SOCK_VECTOR, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, @@ -32,17 +33,17 @@ static bNodeSocketTemplate geo_node_random_attribute_in[] = { {-1, ""}, }; -static bNodeSocketTemplate geo_node_random_attribute_out[] = { +static bNodeSocketTemplate geo_node_attribute_randomize_out[] = { {SOCK_GEOMETRY, N_("Geometry")}, {-1, ""}, }; -static void geo_node_random_attribute_init(bNodeTree *UNUSED(tree), bNode *node) +static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) { node->custom1 = CD_PROP_FLOAT; } -static void geo_node_random_attribute_update(bNodeTree *UNUSED(ntree), bNode *node) +static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) { bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *sock_max_vector = sock_min_vector->next; @@ -59,38 +60,85 @@ static void geo_node_random_attribute_update(bNodeTree *UNUSED(ntree), bNode *no namespace blender::nodes { -static void randomize_attribute(FloatWriteAttribute &attribute, - float min, - float max, - RandomNumberGenerator &rng) +/** Rehash to combine the seed with the "id" hash and a mutator for each dimension. */ +static float noise_from_index_and_mutator(const int seed, const int hash, const int mutator) +{ + const int combined_hash = BLI_hash_int_3d(seed, hash, mutator); + return BLI_hash_int_01(combined_hash); +} + +/** Rehash to combine the seed with the "id" hash. */ +static float noise_from_index(const int seed, const int hash) +{ + const int combined_hash = BLI_hash_int_2d(seed, hash); + return BLI_hash_int_01(combined_hash); +} + +static void randomize_attribute(BooleanWriteAttribute &attribute, Span<int> 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; + attribute_span[i] = value; + } + attribute.apply_span(); +} + +static void randomize_attribute( + FloatWriteAttribute &attribute, float min, float max, Span<int> hashes, const int seed) { MutableSpan<float> attribute_span = attribute.get_span(); for (const int i : IndexRange(attribute.size())) { - const float value = rng.get_float() * (max - min) + min; + const float value = noise_from_index(seed, hashes[i]) * (max - min) + min; attribute_span[i] = value; } attribute.apply_span(); } -static void randomize_attribute(Float3WriteAttribute &attribute, - float3 min, - float3 max, - RandomNumberGenerator &rng) +static void randomize_attribute( + Float3WriteAttribute &attribute, float3 min, float3 max, Span<int> hashes, const int seed) { MutableSpan<float3> attribute_span = attribute.get_span(); for (const int i : IndexRange(attribute.size())) { - const float x = rng.get_float(); - const float y = rng.get_float(); - const float z = rng.get_float(); + 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 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) +{ + /* 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); + 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]); + } + } + else { + /* If there is no "id" attribute for per-point variation, just create it here. */ + RandomNumberGenerator rng; + rng.seed(0); + for (const int i : hashes.index_range()) { + hashes[i] = rng.get_int32(); + } + } + + return hashes; +} + static void randomize_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms, - RandomNumberGenerator &rng) + const int seed) { const bNode &node = params.node(); const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); @@ -106,19 +154,26 @@ static void randomize_attribute(GeometryComponent &component, return; } + Array<int> hashes = get_element_hashes(component, domain, attribute->size()); + switch (data_type) { case CD_PROP_FLOAT: { FloatWriteAttribute float_attribute = std::move(attribute); const float min_value = params.get_input<float>("Min_001"); const float max_value = params.get_input<float>("Max_001"); - randomize_attribute(float_attribute, min_value, max_value, rng); + randomize_attribute(float_attribute, min_value, max_value, hashes, seed); break; } case CD_PROP_FLOAT3: { Float3WriteAttribute float3_attribute = std::move(attribute); const float3 min_value = params.get_input<float3>("Min"); const float3 max_value = params.get_input<float3>("Max"); - randomize_attribute(float3_attribute, min_value, max_value, rng); + randomize_attribute(float3_attribute, min_value, max_value, hashes, seed); + break; + } + case CD_PROP_BOOL: { + BooleanWriteAttribute boolean_attribute = std::move(attribute); + randomize_attribute(boolean_attribute, hashes, seed); break; } default: @@ -132,14 +187,10 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) const int seed = params.get_input<int>("Seed"); if (geometry_set.has<MeshComponent>()) { - RandomNumberGenerator rng; - rng.seed_random(seed); - randomize_attribute(geometry_set.get_component_for_write<MeshComponent>(), params, rng); + randomize_attribute(geometry_set.get_component_for_write<MeshComponent>(), params, seed); } if (geometry_set.has<PointCloudComponent>()) { - RandomNumberGenerator rng; - rng.seed_random(seed + 3245231); - randomize_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params, rng); + randomize_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params, seed); } params.set_output("Geometry", geometry_set); @@ -147,15 +198,16 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_random_attribute() +void register_node_type_geo_attribute_randomize() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_RANDOM_ATTRIBUTE, "Random Attribute", NODE_CLASS_ATTRIBUTE, 0); - node_type_socket_templates(&ntype, geo_node_random_attribute_in, geo_node_random_attribute_out); - node_type_init(&ntype, geo_node_random_attribute_init); - node_type_update(&ntype, geo_node_random_attribute_update); + &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_randomize_in, geo_node_attribute_randomize_out); + node_type_init(&ntype, geo_node_attribute_randomize_init); + node_type_update(&ntype, geo_node_attribute_randomize_update); ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index dedc3213a1f..80ac45aed4e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -218,12 +218,12 @@ static void join_components(Span<const InstancesComponent *> src_components, Geo InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); for (const InstancesComponent *component : src_components) { const int size = component->instances_amount(); - Span<const Object *> objects = component->objects(); + Span<InstancedData> instanced_data = component->instanced_data(); Span<float3> positions = component->positions(); Span<float3> rotations = component->rotations(); Span<float3> scales = component->scales(); for (const int i : IndexRange(size)) { - dst_component.add_instance(objects[i], positions[i], rotations[i], scales[i]); + dst_component.add_instance(instanced_data[i], positions[i], rotations[i], scales[i]); } } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index 2f5f7e264bc..1d3fbae5b2e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -24,6 +24,7 @@ #include "DNA_meshdata_types.h" #include "DNA_pointcloud_types.h" +#include "BKE_bvhutils.h" #include "BKE_deform.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" @@ -33,8 +34,10 @@ static bNodeSocketTemplate geo_node_point_distribute_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_FLOAT, N_("Density"), 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_FLOAT, N_("Distance Min"), 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_FLOAT, N_("Density Max"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, {SOCK_STRING, N_("Density Attribute")}, + {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; @@ -43,11 +46,20 @@ static bNodeSocketTemplate geo_node_point_distribute_out[] = { {-1, ""}, }; +static void node_point_distribute_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_min_dist = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + + nodeSetSocketAvailability(sock_min_dist, ELEM(node->custom1, GEO_NODE_POINT_DISTRIBUTE_POISSON)); +} + namespace blender::nodes { -static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, - const float density, - const FloatReadAttribute &density_factors) +static Vector<float3> random_scatter_points_from_mesh(const Mesh *mesh, + const float density, + const FloatReadAttribute &density_factors, + Vector<int> &r_ids, + const int seed) { /* This only updates a cache and can be considered to be logically const. */ const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(mesh)); @@ -71,7 +83,7 @@ static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, 3.0f; const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); - const int looptri_seed = BLI_hash_int(looptri_index); + const int looptri_seed = BLI_hash_int(looptri_index + seed); RandomNumberGenerator looptri_rng(looptri_seed); const float points_amount_fl = area * density * looptri_density_factor; @@ -84,23 +96,178 @@ static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, float3 point_pos; interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coords); points.append(point_pos); + + /* Build a hash stable even when the mesh is deformed. */ + r_ids.append(((int)(bary_coords.hash()) + looptri_index)); } } return points; } +struct RayCastAll_Data { + void *bvhdata; + + BVHTree_RayCastCallback raycast_callback; + + /** The original coordinate the result point was projected from. */ + float2 raystart; + + const Mesh *mesh; + float base_weight; + FloatReadAttribute *density_factors; + Vector<float3> *projected_points; + Vector<int> *stable_ids; + float cur_point_weight; +}; + +static void project_2d_bvh_callback(void *userdata, + int index, + const BVHTreeRay *ray, + BVHTreeRayHit *hit) +{ + struct RayCastAll_Data *data = (RayCastAll_Data *)userdata; + data->raycast_callback(data->bvhdata, index, ray, hit); + if (hit->index != -1) { + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(data->mesh)); + const MVert *mvert = data->mesh->mvert; + + const MLoopTri &looptri = looptris[index]; + const FloatReadAttribute &density_factors = data->density_factors[0]; + + const int v0_index = data->mesh->mloop[looptri.tri[0]].v; + const int v1_index = data->mesh->mloop[looptri.tri[1]].v; + const int v2_index = data->mesh->mloop[looptri.tri[2]].v; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_index]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_index]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_index]); + + /* Calculate barycentric weights for hit point. */ + float3 weights; + interp_weights_tri_v3( + weights, mvert[v0_index].co, mvert[v1_index].co, mvert[v2_index].co, hit->co); + + float point_weight = weights[0] * v0_density_factor + weights[1] * v1_density_factor + + weights[2] * v2_density_factor; + + point_weight *= data->base_weight; + + if (point_weight >= FLT_EPSILON && data->cur_point_weight <= point_weight) { + data->projected_points->append(hit->co); + + /* Build a hash stable even when the mesh is deformed. */ + data->stable_ids->append((int)data->raystart.hash()); + } + } +} + +static Vector<float3> poisson_scatter_points_from_mesh(const Mesh *mesh, + const float density, + const float minimum_distance, + const FloatReadAttribute &density_factors, + Vector<int> &r_ids, + const int seed) +{ + Vector<float3> points; + + if (minimum_distance <= FLT_EPSILON || density <= FLT_EPSILON) { + return points; + } + + /* Scatter points randomly on the mesh with higher density (5-7) times higher than desired for + * good quality possion disk distributions. */ + int quality = 5; + const int output_points_target = 1000; + points.resize(output_points_target * quality); + + const float required_area = output_points_target * + (2.0f * sqrtf(3.0f) * minimum_distance * minimum_distance); + const float point_scale_multiplier = sqrtf(required_area); + + { + const int rnd_seed = BLI_hash_int(seed); + RandomNumberGenerator point_rng(rnd_seed); + + for (int i = 0; i < points.size(); i++) { + points[i].x = point_rng.get_float() * point_scale_multiplier; + points[i].y = point_rng.get_float() * point_scale_multiplier; + points[i].z = 0.0f; + } + } + + /* Eliminate the scattered points until we get a possion distribution. */ + Vector<float3> output_points(output_points_target); + + const float3 bounds_max = float3(point_scale_multiplier, point_scale_multiplier, 0); + poisson_disk_point_elimination(&points, &output_points, 2.0f * minimum_distance, bounds_max); + Vector<float3> final_points; + r_ids.reserve(output_points_target); + final_points.reserve(output_points_target); + + /* Check if we have any points we should remove from the final possion distribition. */ + BVHTreeFromMesh treedata; + BKE_bvhtree_from_mesh_get(&treedata, const_cast<Mesh *>(mesh), BVHTREE_FROM_LOOPTRI, 2); + + float3 bb_min, bb_max; + BLI_bvhtree_get_bounding_box(treedata.tree, bb_min, bb_max); + + struct RayCastAll_Data data; + data.bvhdata = &treedata; + data.raycast_callback = treedata.raycast_callback; + data.mesh = mesh; + data.projected_points = &final_points; + data.stable_ids = &r_ids; + data.density_factors = const_cast<FloatReadAttribute *>(&density_factors); + data.base_weight = std::min( + 1.0f, density / (output_points.size() / (point_scale_multiplier * point_scale_multiplier))); + + const float max_dist = bb_max[2] - bb_min[2] + 2.0f; + const float3 dir = float3(0, 0, -1); + float3 raystart; + raystart.z = bb_max[2] + 1.0f; + + float tile_start_x_coord = bb_min[0]; + int tile_repeat_x = ceilf((bb_max[0] - bb_min[0]) / point_scale_multiplier); + + float tile_start_y_coord = bb_min[1]; + int tile_repeat_y = ceilf((bb_max[1] - bb_min[1]) / point_scale_multiplier); + + for (int x = 0; x < tile_repeat_x; x++) { + float tile_curr_x_coord = x * point_scale_multiplier + tile_start_x_coord; + for (int y = 0; y < tile_repeat_y; y++) { + float tile_curr_y_coord = y * point_scale_multiplier + tile_start_y_coord; + for (int idx = 0; idx < output_points.size(); idx++) { + raystart.x = output_points[idx].x + tile_curr_x_coord; + raystart.y = output_points[idx].y + tile_curr_y_coord; + + data.cur_point_weight = (float)idx / (float)output_points.size(); + data.raystart = raystart; + + BLI_bvhtree_ray_cast_all( + treedata.tree, raystart, dir, 0.0f, max_dist, project_2d_bvh_callback, &data); + } + } + } + + return final_points; +} + static void geo_node_point_distribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet geometry_set_out; + GeometryNodePointDistributeMethod distribute_method = + static_cast<GeometryNodePointDistributeMethod>(params.node().custom1); + if (!geometry_set.has_mesh()) { params.set_output("Geometry", std::move(geometry_set_out)); return; } - const float density = params.extract_input<float>("Density"); + const float density = params.extract_input<float>("Density Max"); const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); if (density <= 0.0f) { @@ -113,8 +280,21 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( density_attribute, ATTR_DOMAIN_POINT, 1.0f); + const int seed = params.get_input<int>("Seed"); - Vector<float3> points = scatter_points_from_mesh(mesh_in, density, density_factors); + Vector<int> stable_ids; + Vector<float3> points; + switch (distribute_method) { + case GEO_NODE_POINT_DISTRIBUTE_RANDOM: + points = random_scatter_points_from_mesh( + mesh_in, density, density_factors, stable_ids, seed); + break; + case GEO_NODE_POINT_DISTRIBUTE_POISSON: + const float min_dist = params.extract_input<float>("Distance Min"); + points = poisson_scatter_points_from_mesh( + mesh_in, density, min_dist, density_factors, stable_ids, seed); + break; + } PointCloud *pointcloud = BKE_pointcloud_new_nomain(points.size()); memcpy(pointcloud->co, points.data(), sizeof(float3) * points.size()); @@ -123,7 +303,16 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) pointcloud->radius[i] = 0.05f; } - geometry_set_out.replace_pointcloud(pointcloud); + PointCloudComponent &point_component = + geometry_set_out.get_component_for_write<PointCloudComponent>(); + point_component.replace(pointcloud); + + Int32WriteAttribute stable_id_attribute = point_component.attribute_try_ensure_for_write( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + MutableSpan<int> stable_ids_span = stable_id_attribute.get_span(); + stable_ids_span.copy_from(stable_ids); + stable_id_attribute.apply_span(); + params.set_output("Geometry", std::move(geometry_set_out)); } } // namespace blender::nodes @@ -135,6 +324,7 @@ void register_node_type_geo_point_distribute() geo_node_type_base( &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_point_distribute_in, geo_node_point_distribute_out); + node_type_update(&ntype, node_point_distribute_update); ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute_poisson_disk.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute_poisson_disk.cc new file mode 100644 index 00000000000..47764efa15d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute_poisson_disk.cc @@ -0,0 +1,281 @@ +/* + * 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. + */ + +/* + * Based on Cem Yuksel. 2015. Sample Elimination for Generating Poisson Disk Sample + * ! Sets. Computer Graphics Forum 34, 2 (May 2015), 25-32. + * ! http://www.cemyuksel.com/research/sampleelimination/ + * Copyright (c) 2016, Cem Yuksel <cem@cemyuksel.com> + * All rights reserved. + */ + +#include "BLI_inplace_priority_queue.hh" +#include "BLI_kdtree.h" + +#include "node_geometry_util.hh" + +#include <iostream> +#include <string.h> + +namespace blender::nodes { + +static void tile_point(Vector<float3> *tiled_points, + Vector<size_t> *indices, + const float maximum_distance, + const float3 boundbox, + float3 const &point, + size_t index, + int dimension = 0) +{ + for (int dimension_iter = dimension; dimension_iter < 3; dimension_iter++) { + if (boundbox[dimension_iter] - point[dimension_iter] < maximum_distance) { + float3 point_tiled = point; + point_tiled[dimension_iter] -= boundbox[dimension_iter]; + + tiled_points->append(point_tiled); + indices->append(index); + + tile_point(tiled_points, + indices, + maximum_distance, + boundbox, + point_tiled, + index, + dimension_iter + 1); + } + + if (point[dimension_iter] < maximum_distance) { + float3 point_tiled = point; + point_tiled[dimension_iter] += boundbox[dimension_iter]; + + tiled_points->append(point_tiled); + indices->append(index); + + tile_point(tiled_points, + indices, + maximum_distance, + boundbox, + point_tiled, + index, + dimension_iter + 1); + } + } +} + +/** + * Returns the weight the point gets based on the distance to another point. + */ +static float point_weight_influence_get(const float maximum_distance, + const float minimum_distance, + float distance) +{ + const float alpha = 8.0f; + + if (distance < minimum_distance) { + distance = minimum_distance; + } + + return std::pow(1.0f - distance / maximum_distance, alpha); +} + +/** + * Weight each point based on their proximity to its neighbors + * + * For each index in the weight array add a weight based on the proximity the + * corresponding point has with its neighboors. + **/ +static void points_distance_weight_calculate(Vector<float> *weights, + const size_t point_id, + const float3 *input_points, + const void *kd_tree, + const float minimum_distance, + const float maximum_distance, + InplacePriorityQueue<float> *heap) +{ + KDTreeNearest_3d *nearest_point = nullptr; + int neighbors = BLI_kdtree_3d_range_search( + (KDTree_3d *)kd_tree, input_points[point_id], &nearest_point, maximum_distance); + + for (int i = 0; i < neighbors; i++) { + size_t neighbor_point_id = nearest_point[i].index; + + if (neighbor_point_id >= weights->size()) { + continue; + } + + /* The point should not influence itself. */ + if (neighbor_point_id == point_id) { + continue; + } + + const float weight_influence = point_weight_influence_get( + maximum_distance, minimum_distance, nearest_point[i].dist); + + /* In the first pass we just the weights. */ + if (heap == nullptr) { + (*weights)[point_id] += weight_influence; + } + /* When we run again we need to update the weights and the heap. */ + else { + (*weights)[neighbor_point_id] -= weight_influence; + heap->priority_decreased(neighbor_point_id); + } + } + + if (nearest_point) { + MEM_freeN(nearest_point); + } +} + +/** + * Returns the minimum radius fraction used by the default weight function. + */ +static float weight_limit_fraction_get(const size_t input_size, const size_t output_size) +{ + const float beta = 0.65f; + const float gamma = 1.5f; + float ratio = float(output_size) / float(input_size); + return (1.0f - std::pow(ratio, gamma)) * beta; +} + +/** + * Tile the input points. + */ +static void points_tiling(const float3 *input_points, + const size_t input_size, + void **kd_tree, + const float maximum_distance, + const float3 boundbox) + +{ + Vector<float3> tiled_points(input_points, input_points + input_size); + Vector<size_t> indices(input_size); + + for (size_t i = 0; i < input_size; i++) { + indices[i] = i; + } + + /* Tile the tree based on the boundbox. */ + for (size_t i = 0; i < input_size; i++) { + tile_point(&tiled_points, &indices, maximum_distance, boundbox, input_points[i], i); + } + + /* Build a new tree with the new indices and tiled points. */ + *kd_tree = BLI_kdtree_3d_new(tiled_points.size()); + for (size_t i = 0; i < tiled_points.size(); i++) { + BLI_kdtree_3d_insert(*(KDTree_3d **)kd_tree, indices[i], tiled_points[i]); + } + BLI_kdtree_3d_balance(*(KDTree_3d **)kd_tree); +} + +static void weighted_sample_elimination(const float3 *input_points, + const size_t input_size, + float3 *output_points, + const size_t output_size, + const float maximum_distance, + const float3 boundbox, + const bool do_copy_eliminated) +{ + const float minimum_distance = maximum_distance * + weight_limit_fraction_get(input_size, output_size); + + void *kd_tree = nullptr; + points_tiling(input_points, input_size, &kd_tree, maximum_distance, boundbox); + + /* Assign weights to each sample. */ + Vector<float> weights(input_size, 0.0f); + for (size_t point_id = 0; point_id < weights.size(); point_id++) { + points_distance_weight_calculate( + &weights, point_id, input_points, kd_tree, minimum_distance, maximum_distance, nullptr); + } + + /* Remove the points based on their weight. */ + InplacePriorityQueue<float> heap(weights); + + size_t sample_size = input_size; + while (sample_size > output_size) { + /* For each sample around it, remove its weight contribution and update the heap. */ + size_t point_id = heap.pop_index(); + points_distance_weight_calculate( + &weights, point_id, input_points, kd_tree, minimum_distance, maximum_distance, &heap); + sample_size--; + } + + /* Copy the samples to the output array. */ + size_t target_size = do_copy_eliminated ? input_size : output_size; + for (size_t i = 0; i < target_size; i++) { + size_t index = heap.all_indices()[i]; + output_points[i] = input_points[index]; + } + + /* Cleanup. */ + BLI_kdtree_3d_free((KDTree_3d *)kd_tree); +} + +static void progressive_sampling_reorder(Vector<float3> *output_points, + float maximum_density, + float3 boundbox) +{ + /* Re-order the points for progressive sampling. */ + Vector<float3> temporary_points(output_points->size()); + float3 *source_points = output_points->data(); + float3 *dest_points = temporary_points.data(); + size_t source_size = output_points->size(); + size_t dest_size = 0; + + while (source_size >= 3) { + dest_size = source_size * 0.5f; + + /* Changes the weight function radius using half of the number of samples. + * It is used for progressive sampling. */ + maximum_density *= std::sqrt(2.0f); + weighted_sample_elimination( + source_points, source_size, dest_points, dest_size, maximum_density, boundbox, true); + + if (dest_points != output_points->data()) { + memcpy((*output_points)[dest_size], + dest_points[dest_size], + (source_size - dest_size) * sizeof(float3)); + } + + /* Swap the arrays around. */ + float3 *points_iter = source_points; + source_points = dest_points; + dest_points = points_iter; + source_size = dest_size; + } + if (source_points != output_points->data()) { + memcpy(output_points->data(), source_points, dest_size * sizeof(float3)); + } +} + +void poisson_disk_point_elimination(Vector<float3> const *input_points, + Vector<float3> *output_points, + float maximum_density, + float3 boundbox) +{ + weighted_sample_elimination(input_points->data(), + input_points->size(), + output_points->data(), + output_points->size(), + maximum_density, + boundbox, + false); + + progressive_sampling_reorder(output_points, maximum_density, boundbox); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 6d979e3b7da..e030bc3eec6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -25,6 +25,7 @@ static bNodeSocketTemplate geo_node_point_instance_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_OBJECT, N_("Object")}, + {SOCK_COLLECTION, N_("Collection")}, {-1, ""}, }; @@ -35,9 +36,21 @@ static bNodeSocketTemplate geo_node_point_instance_out[] = { namespace blender::nodes { +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; + + GeometryNodePointInstanceType type = (GeometryNodePointInstanceType)node->custom1; + + nodeSetSocketAvailability(object_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_OBJECT); + nodeSetSocketAvailability(collection_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION); +} + static void add_instances_from_geometry_component(InstancesComponent &instances, const GeometryComponent &src_geometry, - Object *object) + Object *object, + Collection *collection) { Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); @@ -47,30 +60,51 @@ static void add_instances_from_geometry_component(InstancesComponent &instances, "scale", ATTR_DOMAIN_POINT, {1, 1, 1}); for (const int i : IndexRange(positions.size())) { - instances.add_instance(object, positions[i], rotations[i], scales[i]); + 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]); + } } } 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; - bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = nullptr; + Collection *collection = nullptr; - if (object != nullptr && object != params.self_object()) { - 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); - } - if (geometry_set.has<PointCloudComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<PointCloudComponent>(), object); + 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); + } + if (geometry_set.has<PointCloudComponent>()) { + add_instances_from_geometry_component( + instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + object, + collection); + } params.set_output("Geometry", std::move(geometry_set_out)); } @@ -82,6 +116,7 @@ void register_node_type_geo_point_instance() geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out); + node_type_update(&ntype, blender::nodes::geo_node_point_instance_update); ntype.geometry_node_execute = blender::nodes::geo_node_point_instance_exec; nodeRegisterType(&ntype); } |