Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/nodes/geometry/nodes')
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc107
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc141
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc221
-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.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc206
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_distribute_poisson_disk.cc281
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_instance.cc63
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 &params, 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 &params)
+{
+ 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 &params)
+{
+ 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 &params,
- 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);
}