From d4fd06d6ce89113310bb769f4777c9a4d77cb155 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 12 Feb 2021 13:25:45 -0600 Subject: Geometry Nodes: Add operation setting to attribute randomize node This commit adds a drop-down to the attribute randomize node to support a few operations on the values of existing attributes: "Replace/Create" (the existing behavior), "Add", "Subtract", and "Multiply". At this point, the operations are limited by what is simple to implement. More could be added in the future, but there isn't a strong use case for more complex operations anyway, and a second math node can be used. Differential Revision: https://developer.blender.org/D10269 --- source/blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenlib/BLI_hash.h | 10 ++ source/blender/blenloader/intern/versioning_290.c | 19 ++ source/blender/makesdna/DNA_node_types.h | 18 ++ source/blender/makesrna/intern/rna_nodetree.c | 94 ++++++++-- .../geometry/nodes/node_geo_attribute_randomize.cc | 200 ++++++++++++++------- 6 files changed, 268 insertions(+), 75 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index c6fe02202a6..7db6980c91a 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 7 +#define BLENDER_FILE_SUBVERSION 8 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenlib/BLI_hash.h b/source/blender/blenlib/BLI_hash.h index d687e805323..cfd39ea3cf8 100644 --- a/source/blender/blenlib/BLI_hash.h +++ b/source/blender/blenlib/BLI_hash.h @@ -90,6 +90,16 @@ BLI_INLINE unsigned int BLI_hash_string(const char *str) return i; } +BLI_INLINE float BLI_hash_int_2d_to_float(uint32_t kx, uint32_t ky) +{ + return (float)BLI_hash_int_2d(kx, ky) / (float)0xFFFFFFFFu; +} + +BLI_INLINE float BLI_hash_int_3d_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +{ + return (float)BLI_hash_int_3d(kx, ky, kz) / (float)0xFFFFFFFFu; +} + BLI_INLINE unsigned int BLI_hash_int(unsigned int k) { return BLI_hash_int_2d(k, 0); diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 78d60715aa5..74b3827ffd0 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1704,6 +1704,25 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } FOREACH_NODETREE_END; } + + if (!MAIN_VERSION_ATLEAST(bmain, 293, 8)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == GEO_NODE_POINT_INSTANCE && node->storage == NULL) { + NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( + sizeof(NodeAttributeRandomize), __func__); + data->data_type = node->custom1; + data->operation = GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE; + node->storage = data; + } + } + } + FOREACH_NODETREE_END; + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 43c5fa81651..fd9fcfa3232 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1109,6 +1109,16 @@ typedef struct NodeAttributeMix { uint8_t input_type_b; } NodeAttributeMix; +typedef struct NodeAttributeRandomize { + /* CustomDataType. */ + uint8_t data_type; + /* AttributeDomain. */ + uint8_t domain; + /* GeometryNodeAttributeRandomizeMode. */ + uint8_t operation; + char _pad[1]; +} NodeAttributeRandomize; + typedef struct NodeAttributeVectorMath { /* NodeVectorMathOperation */ uint8_t operation; @@ -1637,6 +1647,7 @@ typedef enum GeometryNodeAttributeInputMode { GEO_NODE_ATTRIBUTE_INPUT_VECTOR = 2, GEO_NODE_ATTRIBUTE_INPUT_COLOR = 3, GEO_NODE_ATTRIBUTE_INPUT_BOOLEAN = 4, + GEO_NODE_ATTRIBUTE_INPUT_INTEGER = 5, } GeometryNodeAttributeInputMode; typedef enum GeometryNodePointDistributeMethod { @@ -1649,6 +1660,13 @@ typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, } GeometryNodeRotatePointsType; +typedef enum GeometryNodeAttributeRandomizeMode { + GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE = 0, + GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD = 1, + GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT = 2, + GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY = 3, +} GeometryNodeAttributeRandomizeMode; + typedef enum GeometryNodeRotatePointsSpace { GEO_NODE_POINT_ROTATE_SPACE_OBJECT = 0, GEO_NODE_POINT_ROTATE_SPACE_POINT = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 3a9d373e30e..75d7c7a4588 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -355,6 +355,30 @@ const EnumPropertyItem rna_enum_node_filter_items[] = { {0, NULL, 0, NULL, NULL}, }; +static const EnumPropertyItem rna_node_geometry_attribute_randomize_operation_items[] = { + {GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE, + "REPLACE_CREATE", + ICON_NONE, + "Replace/Create", + "Replace the value and data type of an existing attribute, or create a new one"}, + {GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD, + "ADD", + ICON_NONE, + "Add", + "Add the random values to the existing attribute values"}, + {GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT, + "SUBTRACT", + ICON_NONE, + "Subtract", + "Subtract random values from the existing attribute values"}, + {GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY, + "MULTIPLY", + ICON_NONE, + "Multiply", + "Multiply the existing attribute values with the random values"}, + {0, NULL, 0, NULL, NULL}, +}; + #ifndef RNA_RUNTIME static const EnumPropertyItem node_sampler_type_items[] = { {0, "NEAREST", 0, "Nearest", ""}, @@ -1921,7 +1945,7 @@ static const EnumPropertyItem *itemf_function_check( static bool attribute_random_type_supported(const EnumPropertyItem *item) { - return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_BOOL); + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_BOOL, CD_PROP_INT32); } static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_type_itemf( bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) @@ -1930,15 +1954,47 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_type_itemf( return itemf_function_check(rna_enum_attribute_type_items, attribute_random_type_supported); } -static bool attribute_random_domain_supported(const EnumPropertyItem *item) +static const EnumPropertyItem *rna_GeometryNodeAttributeRandomize_operation_itemf( + bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { - return item->value == ATTR_DOMAIN_POINT; + bNode *node = ptr->data; + const NodeAttributeRandomize *node_storage = (NodeAttributeRandomize *)node->storage; + const CustomDataType data_type = (CustomDataType)node_storage->data_type; + + EnumPropertyItem *item_array = NULL; + int items_len = 0; + for (const EnumPropertyItem *item = rna_node_geometry_attribute_randomize_operation_items; + item->identifier != NULL; + item++) { + if (data_type == CD_PROP_BOOL) { + if (item->value == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) { + RNA_enum_item_add(&item_array, &items_len, item); + } + } + else { + RNA_enum_item_add(&item_array, &items_len, item); + } + } + RNA_enum_item_end(&item_array, &items_len); + + *r_free = true; + return item_array; } -static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_domain_itemf( - bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) + +static void rna_GeometryNodeAttributeRandomize_data_type_update(Main *bmain, + Scene *scene, + PointerRNA *ptr) { - *r_free = true; - return itemf_function_check(rna_enum_attribute_domain_items, attribute_random_domain_supported); + bNode *node = ptr->data; + NodeAttributeRandomize *node_storage = (NodeAttributeRandomize *)node->storage; + + /* The boolean data type has no extra operations besides, + * replace, so make sure the enum value is set properly. */ + if (node_storage->data_type == CD_PROP_BOOL) { + node_storage->operation = GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE; + } + + rna_Node_socket_update(bmain, scene, ptr); } static bool attribute_fill_type_supported(const EnumPropertyItem *item) @@ -8528,9 +8584,27 @@ static void def_geo_attribute_create_common(StructRNA *srna, static void def_geo_attribute_randomize(StructRNA *srna) { - def_geo_attribute_create_common(srna, - "rna_GeometryNodeAttributeRandom_type_itemf", - "rna_GeometryNodeAttributeRandom_domain_itemf"); + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeAttributeRandomize", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "data_type"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeRandom_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_GeometryNodeAttributeRandomize_data_type_update"); + + prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "operation"); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_randomize_operation_items); + RNA_def_property_enum_funcs( + prop, NULL, NULL, "rna_GeometryNodeAttributeRandomize_operation_itemf"); + RNA_def_property_enum_default(prop, GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); + RNA_def_property_ui_text(prop, "Operation", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_attribute_fill(StructRNA *srna) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 7e95714a44b..dc924ede3a1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -32,6 +32,8 @@ static bNodeSocketTemplate geo_node_attribute_randomize_in[] = { {SOCK_VECTOR, N_("Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_INT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -100000, 100000}, + {SOCK_INT, N_("Max"), 100.0f, 0.0f, 0.0f, 0.0f, -100000, 100000}, {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; @@ -46,11 +48,17 @@ static void geo_node_attribute_random_layout(uiLayout *layout, PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) { - node->custom1 = CD_PROP_FLOAT; + NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( + sizeof(NodeAttributeRandomize), __func__); + data->data_type = CD_PROP_FLOAT; + data->domain = ATTR_DOMAIN_POINT; + data->operation = GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE; + node->storage = data; } static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) @@ -59,71 +67,97 @@ static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode bNodeSocket *sock_max_vector = sock_min_vector->next; bNodeSocket *sock_min_float = sock_max_vector->next; bNodeSocket *sock_max_float = sock_min_float->next; + bNodeSocket *sock_min_int = sock_max_float->next; + bNodeSocket *sock_max_int = sock_min_int->next; - const CustomDataType data_type = static_cast(node->custom1); + const NodeAttributeRandomize &storage = *(const NodeAttributeRandomize *)node->storage; + const CustomDataType data_type = static_cast(storage.data_type); nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); nodeSetSocketAvailability(sock_max_vector, data_type == CD_PROP_FLOAT3); nodeSetSocketAvailability(sock_min_float, data_type == CD_PROP_FLOAT); nodeSetSocketAvailability(sock_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_max_int, data_type == CD_PROP_INT32); } namespace blender::nodes { -/** 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) +template T get_random_value(const uint32_t id, const uint32_t seed); + +template<> inline bool get_random_value(const uint32_t id, const uint32_t seed) { - const int combined_hash = BLI_hash_int_3d(seed, hash, mutator); - return BLI_hash_int_01(combined_hash); + return BLI_hash_int_2d_to_float(id, seed) > 0.5f; } -/** Rehash to combine the seed with the "id" hash. */ -static float noise_from_index(const int seed, const int hash) +template<> inline int get_random_value(const uint32_t id, const uint32_t seed) { - const int combined_hash = BLI_hash_int_2d(seed, hash); - return BLI_hash_int_01(combined_hash); + return BLI_hash_int_2d(id, seed); } -static void randomize_attribute_bool(BooleanWriteAttribute attribute, - Span hashes, - const int seed) +template<> inline float get_random_value(const uint32_t id, const uint32_t seed) { - MutableSpan attribute_span = attribute.get_span(); - for (const int i : IndexRange(attribute.size())) { - const bool value = noise_from_index(seed, (int)hashes[i]) > 0.5f; - attribute_span[i] = value; - } - attribute.apply_span(); + return BLI_hash_int_2d_to_float(id, seed); } -static void randomize_attribute_float(FloatWriteAttribute attribute, - const float min, - const float max, - Span hashes, - const int seed) +template<> inline float3 get_random_value(const uint32_t id, const uint32_t seed) { - MutableSpan attribute_span = attribute.get_span(); - for (const int i : IndexRange(attribute.size())) { - const float value = noise_from_index(seed, (int)hashes[i]) * (max - min) + min; - attribute_span[i] = value; + const float x = BLI_hash_int_3d_to_float(seed, id, 435109); + const float y = BLI_hash_int_3d_to_float(seed, id, 380867); + const float z = BLI_hash_int_3d_to_float(seed, id, 1059217); + + return float3(x, y, z); +} + +template +static void randomize_attribute(MutableSpan span, + const T min, + const T max, + Span ids, + const uint32_t seed, + const GeometryNodeAttributeRandomizeMode operation) +{ + /* The operations could be templated too, but it doesn't make the code much shorter. */ + switch (operation) { + case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: + for (const int i : span.index_range()) { + const T random_value = get_random_value(ids[i], seed) * (max - min) + min; + span[i] = random_value; + } + break; + case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: + for (const int i : span.index_range()) { + const T random_value = get_random_value(ids[i], seed) * (max - min) + min; + span[i] = span[i] + random_value; + } + break; + case GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT: + for (const int i : span.index_range()) { + const T random_value = get_random_value(ids[i], seed) * (max - min) + min; + span[i] = span[i] - random_value; + } + break; + case GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY: + for (const int i : span.index_range()) { + const T random_value = get_random_value(ids[i], seed) * (max - min) + min; + span[i] = span[i] * random_value; + } + break; + default: + BLI_assert(false); + break; } - attribute.apply_span(); } -static void randomize_attribute_float3(Float3WriteAttribute attribute, - const float3 min, - const float3 max, - Span hashes, - const int seed) +static void randomize_attribute_bool(MutableSpan span, + Span ids, + const uint32_t seed, + const GeometryNodeAttributeRandomizeMode operation) { - MutableSpan attribute_span = attribute.get_span(); - for (const int i : IndexRange(attribute.size())) { - const float x = noise_from_index_and_mutator(seed, (int)hashes[i], 47); - const float y = noise_from_index_and_mutator(seed, (int)hashes[i], 8); - const float z = noise_from_index_and_mutator(seed, (int)hashes[i], 64); - const float3 value = float3(x, y, z) * (max - min) + min; - attribute_span[i] = value; + BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); + for (const int i : span.index_range()) { + const bool random_value = get_random_value(ids[i], seed); + span[i] = random_value; } - attribute.apply_span(); } Array get_geometry_element_ids_as_uints(const GeometryComponent &component, @@ -168,16 +202,21 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, return static_cast(node.custom2); } -static void randomize_attribute(GeometryComponent &component, - const GeoNodeExecParams ¶ms, - const int seed) +static void randomize_attribute_on_component(GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef attribute_name, + const CustomDataType data_type, + const GeometryNodeAttributeRandomizeMode operation, + const int seed) { - const std::string attribute_name = params.get_input("Attribute"); - if (attribute_name.empty()) { - return; + /* If the node is not in "replace / create" mode and the attribute + * doesn't already exist, don't do the operation. */ + if (operation != GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) { + if (!component.attribute_exists(attribute_name)) { + return; + } } - const bNode &node = params.node(); - const CustomDataType data_type = static_cast(node.custom1); + const AttributeDomain domain = get_result_domain(component, params, attribute_name); OutputAttributePtr attribute = component.attribute_try_get_for_output( @@ -186,44 +225,75 @@ static void randomize_attribute(GeometryComponent &component, return; } + fn::GMutableSpan span = (operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) ? + attribute->get_span_for_write_only() : + attribute->get_span(); + Array hashes = get_geometry_element_ids_as_uints(component, domain); switch (data_type) { - case CD_PROP_FLOAT: { - const float min_value = params.get_input("Min_001"); - const float max_value = params.get_input("Max_001"); - randomize_attribute_float(*attribute, min_value, max_value, hashes, seed); + case CD_PROP_FLOAT3: { + const float3 min = params.get_input("Min"); + const float3 max = params.get_input("Max"); + randomize_attribute(span.typed(), min, max, hashes, seed, operation); break; } - case CD_PROP_FLOAT3: { - const float3 min_value = params.get_input("Min"); - const float3 max_value = params.get_input("Max"); - randomize_attribute_float3(*attribute, min_value, max_value, hashes, seed); + case CD_PROP_FLOAT: { + const float min = params.get_input("Min_001"); + const float max = params.get_input("Max_001"); + randomize_attribute(span.typed(), min, max, hashes, seed, operation); break; } case CD_PROP_BOOL: { - randomize_attribute_bool(*attribute, hashes, seed); + randomize_attribute_bool(span.typed(), hashes, seed, operation); break; } - default: + case CD_PROP_INT32: { + const int min = params.get_input("Min_002"); + const int max = params.get_input("Max_002"); + randomize_attribute(span.typed(), min, max, hashes, seed, operation); + break; + } + default: { + BLI_assert(false); break; + } } - attribute.save(); -} + attribute.apply_span_and_save(); +} // namespace blender::nodes static void geo_node_random_attribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); + const std::string attribute_name = params.get_input("Attribute"); + if (attribute_name.empty()) { + params.set_output("Geometry", geometry_set); + return; + } const int seed = params.get_input("Seed"); + const NodeAttributeRandomize &storage = *(const NodeAttributeRandomize *)params.node().storage; + const CustomDataType data_type = static_cast(storage.data_type); + const GeometryNodeAttributeRandomizeMode operation = + static_cast(storage.operation); geometry_set = geometry_set_realize_instances(geometry_set); if (geometry_set.has()) { - randomize_attribute(geometry_set.get_component_for_write(), params, seed); + randomize_attribute_on_component(geometry_set.get_component_for_write(), + params, + attribute_name, + data_type, + operation, + seed); } if (geometry_set.has()) { - randomize_attribute(geometry_set.get_component_for_write(), params, seed); + randomize_attribute_on_component(geometry_set.get_component_for_write(), + params, + attribute_name, + data_type, + operation, + seed); } params.set_output("Geometry", geometry_set); @@ -243,5 +313,7 @@ void register_node_type_geo_attribute_randomize() node_type_update(&ntype, geo_node_attribute_randomize_update); ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; ntype.draw_buttons = geo_node_attribute_random_layout; + node_type_storage( + &ntype, "NodeAttributeRandomize", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); } -- cgit v1.2.3