Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
| Show All 26 Lines | |||||
| static bNodeSocketTemplate geo_node_attribute_randomize_in[] = { | static bNodeSocketTemplate geo_node_attribute_randomize_in[] = { | ||||
| {SOCK_GEOMETRY, N_("Geometry")}, | {SOCK_GEOMETRY, N_("Geometry")}, | ||||
| {SOCK_STRING, N_("Attribute")}, | {SOCK_STRING, N_("Attribute")}, | ||||
| {SOCK_VECTOR, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, | {SOCK_VECTOR, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, | ||||
| {SOCK_VECTOR, N_("Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, | {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_("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_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}, | {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, | ||||
| {-1, ""}, | {-1, ""}, | ||||
| }; | }; | ||||
| static bNodeSocketTemplate geo_node_attribute_randomize_out[] = { | static bNodeSocketTemplate geo_node_attribute_randomize_out[] = { | ||||
| {SOCK_GEOMETRY, N_("Geometry")}, | {SOCK_GEOMETRY, N_("Geometry")}, | ||||
| {-1, ""}, | {-1, ""}, | ||||
| }; | }; | ||||
| static void geo_node_attribute_random_layout(uiLayout *layout, | static void geo_node_attribute_random_layout(uiLayout *layout, | ||||
| bContext *UNUSED(C), | bContext *UNUSED(C), | ||||
| PointerRNA *ptr) | PointerRNA *ptr) | ||||
| { | { | ||||
| uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); | 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) | 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) | static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) | ||||
| { | { | ||||
| bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); | bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); | ||||
| bNodeSocket *sock_max_vector = sock_min_vector->next; | bNodeSocket *sock_max_vector = sock_min_vector->next; | ||||
| bNodeSocket *sock_min_float = sock_max_vector->next; | bNodeSocket *sock_min_float = sock_max_vector->next; | ||||
| bNodeSocket *sock_max_float = sock_min_float->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<CustomDataType>(node->custom1); | const NodeAttributeRandomize &storage = *(const NodeAttributeRandomize *)node->storage; | ||||
| const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); | |||||
| nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); | nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); | ||||
| nodeSetSocketAvailability(sock_max_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_min_float, data_type == CD_PROP_FLOAT); | ||||
| nodeSetSocketAvailability(sock_max_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 { | namespace blender::nodes { | ||||
| /** Rehash to combine the seed with the "id" hash and a mutator for each dimension. */ | template<typename T> T get_random_value(const uint32_t id, const uint32_t seed); | ||||
| static float noise_from_index_and_mutator(const int seed, const int hash, const int mutator) | |||||
| 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_2d_to_float(id, seed) > 0.5f; | ||||
| return BLI_hash_int_01(combined_hash); | |||||
| } | } | ||||
| /** Rehash to combine the seed with the "id" hash. */ | template<> inline int get_random_value(const uint32_t id, const uint32_t seed) | ||||
| 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_2d(id, seed); | ||||
| return BLI_hash_int_01(combined_hash); | |||||
| } | } | ||||
| static void randomize_attribute_bool(BooleanWriteAttribute attribute, | template<> inline float get_random_value(const uint32_t id, const uint32_t seed) | ||||
| Span<uint32_t> hashes, | |||||
| const int seed) | |||||
| { | { | ||||
| MutableSpan<bool> attribute_span = attribute.get_span(); | return BLI_hash_int_2d_to_float(id, seed); | ||||
| 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(); | |||||
| } | } | ||||
| static void randomize_attribute_float(FloatWriteAttribute attribute, | template<> inline float3 get_random_value(const uint32_t id, const uint32_t seed) | ||||
| const float min, | |||||
| const float max, | |||||
| Span<uint32_t> hashes, | |||||
| const int seed) | |||||
| { | { | ||||
| MutableSpan<float> attribute_span = attribute.get_span(); | const float x = BLI_hash_int_3d_to_float(seed, id, 435109); | ||||
| for (const int i : IndexRange(attribute.size())) { | const float y = BLI_hash_int_3d_to_float(seed, id, 380867); | ||||
| const float value = noise_from_index(seed, (int)hashes[i]) * (max - min) + min; | const float z = BLI_hash_int_3d_to_float(seed, id, 1059217); | ||||
| attribute_span[i] = value; | |||||
| } | return float3(x, y, z); | ||||
| attribute.apply_span(); | |||||
| } | } | ||||
| static void randomize_attribute_float3(Float3WriteAttribute attribute, | template<typename T> | ||||
| const float3 min, | static void randomize_attribute(MutableSpan<T> span, | ||||
| const float3 max, | const T min, | ||||
| Span<uint32_t> hashes, | const T max, | ||||
| const int seed) | Span<uint32_t> ids, | ||||
| const uint32_t seed, | |||||
| const GeometryNodeAttributeRandomizeMode operation) | |||||
| { | { | ||||
| MutableSpan<float3> attribute_span = attribute.get_span(); | /* The operations could be templated too, but it make the code much shorter. */ | ||||
| for (const int i : IndexRange(attribute.size())) { | switch (operation) { | ||||
| const float x = noise_from_index_and_mutator(seed, (int)hashes[i], 47); | case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: | ||||
| const float y = noise_from_index_and_mutator(seed, (int)hashes[i], 8); | for (const int i : span.index_range()) { | ||||
| const float z = noise_from_index_and_mutator(seed, (int)hashes[i], 64); | const T random_value = get_random_value<T>(ids[i], seed) * (max - min) + min; | ||||
| const float3 value = float3(x, y, z) * (max - min) + min; | span[i] = random_value; | ||||
| attribute_span[i] = value; | } | ||||
| break; | |||||
| case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: | |||||
| for (const int i : span.index_range()) { | |||||
| const T random_value = get_random_value<T>(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<T>(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<T>(ids[i], seed) * (max - min) + min; | |||||
| span[i] = span[i] * random_value; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| BLI_assert(false); | |||||
| break; | |||||
JacquesLucke: What would make the code much shorter? | |||||
Done Inline ActionsAgh, I forgot the word "doesn't" HooglyBoogly: Agh, I forgot the word "doesn't" | |||||
| } | |||||
| } | |||||
| /** | |||||
| * \note Boolean gets special treatment because it doesn't use a min and max. | |||||
| */ | |||||
| static void randomize_attribute_bool(MutableSpan<bool> span, | |||||
| Span<uint32_t> ids, | |||||
| const uint32_t seed, | |||||
| const GeometryNodeAttributeRandomizeMode operation) | |||||
| { | |||||
| BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); | |||||
| for (const int i : span.index_range()) { | |||||
| const bool random_value = get_random_value<bool>(ids[i], seed); | |||||
| span[i] = random_value; | |||||
| } | } | ||||
| attribute.apply_span(); | |||||
| } | } | ||||
| Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, | Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, | ||||
| const AttributeDomain domain) | const AttributeDomain domain) | ||||
| { | { | ||||
| const int domain_size = component.attribute_domain_size(domain); | const int domain_size = component.attribute_domain_size(domain); | ||||
| /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ | /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ | ||||
| Show All 13 Lines | else { | ||||
| for (const int i : hashes.index_range()) { | for (const int i : hashes.index_range()) { | ||||
| hashes[i] = rng.get_uint32(); | hashes[i] = rng.get_uint32(); | ||||
| } | } | ||||
| } | } | ||||
| return hashes; | return hashes; | ||||
| } | } | ||||
| static void randomize_attribute(GeometryComponent &component, | static void randomize_attribute_on_component(GeometryComponent &component, | ||||
| const GeoNodeExecParams ¶ms, | const GeoNodeExecParams ¶ms, | ||||
| StringRef attribute_name, | |||||
| const CustomDataType data_type, | |||||
| const AttributeDomain domain, | |||||
| const GeometryNodeAttributeRandomizeMode operation, | |||||
| const int seed) | const int seed) | ||||
| { | { | ||||
| const bNode &node = params.node(); | { | ||||
| const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); | /* If the node is not in "replace / create" mode and the attribute | ||||
| const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); | * doesn't already exist, don't do the operation. */ | ||||
| const std::string attribute_name = params.get_input<std::string>("Attribute"); | ReadAttributePtr read_attribute = component.attribute_try_get_for_read( | ||||
| if (attribute_name.empty()) { | attribute_name, domain, data_type); | ||||
| if (!read_attribute && operation != GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) { | |||||
| return; | return; | ||||
| } | } | ||||
| } | |||||
| OutputAttributePtr attribute = component.attribute_try_get_for_output( | OutputAttributePtr attribute = component.attribute_try_get_for_output( | ||||
| attribute_name, domain, data_type); | attribute_name, domain, data_type); | ||||
| if (!attribute) { | if (!attribute) { | ||||
| return; | return; | ||||
| } | } | ||||
| fn::GMutableSpan span = (operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) ? | |||||
| attribute->get_span_for_write_only() : | |||||
| attribute->get_span(); | |||||
| Array<uint32_t> hashes = get_geometry_element_ids_as_uints(component, domain); | Array<uint32_t> hashes = get_geometry_element_ids_as_uints(component, domain); | ||||
| switch (data_type) { | switch (data_type) { | ||||
| case CD_PROP_FLOAT: { | case CD_PROP_FLOAT3: { | ||||
| const float min_value = params.get_input<float>("Min_001"); | const float3 min = params.get_input<float3>("Min"); | ||||
| const float max_value = params.get_input<float>("Max_001"); | const float3 max = params.get_input<float3>("Max"); | ||||
| randomize_attribute_float(*attribute, min_value, max_value, hashes, seed); | randomize_attribute<float3>(span.typed<float3>(), min, max, hashes, seed, operation); | ||||
| break; | break; | ||||
| } | } | ||||
| case CD_PROP_FLOAT3: { | case CD_PROP_FLOAT: { | ||||
| const float3 min_value = params.get_input<float3>("Min"); | const float min = params.get_input<float>("Min_001"); | ||||
| const float3 max_value = params.get_input<float3>("Max"); | const float max = params.get_input<float>("Max_001"); | ||||
| randomize_attribute_float3(*attribute, min_value, max_value, hashes, seed); | randomize_attribute<float>(span.typed<float>(), min, max, hashes, seed, operation); | ||||
| break; | break; | ||||
| } | } | ||||
| case CD_PROP_BOOL: { | case CD_PROP_BOOL: { | ||||
| randomize_attribute_bool(*attribute, hashes, seed); | randomize_attribute_bool(span.typed<bool>(), hashes, seed, operation); | ||||
| break; | break; | ||||
| } | } | ||||
| default: | case CD_PROP_INT32: { | ||||
| const int min = params.get_input<int>("Min_002"); | |||||
| const int max = params.get_input<int>("Max_002"); | |||||
| randomize_attribute<int>(span.typed<int>(), min, max, hashes, seed, operation); | |||||
| return; | |||||
| } | |||||
| default: { | |||||
| BLI_assert(false); | |||||
| break; | break; | ||||
| } | } | ||||
| } | |||||
| attribute.save(); | attribute.apply_span_and_save(); | ||||
| } | } | ||||
| static void geo_node_random_attribute_exec(GeoNodeExecParams params) | static void geo_node_random_attribute_exec(GeoNodeExecParams params) | ||||
| { | { | ||||
| GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); | GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); | ||||
| const std::string attribute_name = params.get_input<std::string>("Attribute"); | |||||
| if (attribute_name.empty()) { | |||||
| params.set_output("Geometry", geometry_set); | |||||
| return; | |||||
| } | |||||
| const int seed = params.get_input<int>("Seed"); | const int seed = params.get_input<int>("Seed"); | ||||
| const NodeAttributeRandomize &storage = *(const NodeAttributeRandomize *)params.node().storage; | |||||
| const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); | |||||
| const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain); | |||||
| const GeometryNodeAttributeRandomizeMode operation = | |||||
| static_cast<GeometryNodeAttributeRandomizeMode>(storage.operation); | |||||
| if (geometry_set.has<MeshComponent>()) { | if (geometry_set.has<MeshComponent>()) { | ||||
| randomize_attribute(geometry_set.get_component_for_write<MeshComponent>(), params, seed); | randomize_attribute_on_component(geometry_set.get_component_for_write<MeshComponent>(), | ||||
| params, | |||||
| attribute_name, | |||||
| data_type, | |||||
| domain, | |||||
| operation, | |||||
| seed); | |||||
| } | } | ||||
| if (geometry_set.has<PointCloudComponent>()) { | if (geometry_set.has<PointCloudComponent>()) { | ||||
| randomize_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params, seed); | randomize_attribute_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), | ||||
| params, | |||||
| attribute_name, | |||||
| data_type, | |||||
| domain, | |||||
| operation, | |||||
| seed); | |||||
| } | } | ||||
| params.set_output("Geometry", geometry_set); | params.set_output("Geometry", geometry_set); | ||||
| } | } | ||||
| } // namespace blender::nodes | } // namespace blender::nodes | ||||
| void register_node_type_geo_attribute_randomize() | void register_node_type_geo_attribute_randomize() | ||||
| { | { | ||||
| static bNodeType ntype; | static bNodeType ntype; | ||||
| geo_node_type_base( | geo_node_type_base( | ||||
| &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); | &ntype, GEO_NODE_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); | ||||
| node_type_socket_templates( | node_type_socket_templates( | ||||
| &ntype, geo_node_attribute_randomize_in, geo_node_attribute_randomize_out); | &ntype, geo_node_attribute_randomize_in, geo_node_attribute_randomize_out); | ||||
| node_type_init(&ntype, geo_node_attribute_randomize_init); | node_type_init(&ntype, geo_node_attribute_randomize_init); | ||||
| node_type_update(&ntype, geo_node_attribute_randomize_update); | node_type_update(&ntype, geo_node_attribute_randomize_update); | ||||
| ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; | ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; | ||||
| ntype.draw_buttons = geo_node_attribute_random_layout; | ntype.draw_buttons = geo_node_attribute_random_layout; | ||||
| node_type_storage( | |||||
| &ntype, "NodeAttributeRandomize", node_free_standard_storage, node_copy_standard_storage); | |||||
| nodeRegisterType(&ntype); | nodeRegisterType(&ntype); | ||||
| } | } | ||||
What would make the code much shorter?