diff options
Diffstat (limited to 'source/blender/nodes/geometry')
28 files changed, 1678 insertions, 237 deletions
diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index 93cada2982b..46e9d36c09c 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -47,6 +47,7 @@ void update_attribute_input_socket_availabilities(bNode &node, name_is_available && ((socket->type == SOCK_STRING && mode_ == GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE) || (socket->type == SOCK_FLOAT && mode_ == GEO_NODE_ATTRIBUTE_INPUT_FLOAT) || + (socket->type == SOCK_INT && mode_ == GEO_NODE_ATTRIBUTE_INPUT_INTEGER) || (socket->type == SOCK_VECTOR && mode_ == GEO_NODE_ATTRIBUTE_INPUT_VECTOR) || (socket->type == SOCK_RGBA && mode_ == GEO_NODE_ATTRIBUTE_INPUT_COLOR)); nodeSetSocketAvailability(socket, socket_is_available); diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index d1b71d6f2ba..9b6824fdb5c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -78,7 +78,7 @@ static void align_rotations_auto_pivot(const VArray<float3> &vectors, const float3 local_main_axis, const MutableSpan<float3> rotations) { - parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + threading::parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { for (const int i : range) { const float3 vector = vectors[i]; if (is_zero_v3(vector)) { @@ -129,7 +129,7 @@ static void align_rotations_fixed_pivot(const VArray<float3> &vectors, return; } - parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + threading::parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { for (const int i : range) { const float3 vector = vectors[i]; if (is_zero_v3(vector)) { 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 index 5293dd8c876..c5740395dfb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -95,7 +95,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon MutableSpan<ColorGeometry4f> results = attribute_result.as_span(); ColorBand *color_ramp = &node_storage->color_ramp; - parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { for (const int i : range) { BKE_colorband_evaluate(color_ramp, attribute_in[i], results[i]); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc index 599c9e58e52..06a4327a6c5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -144,7 +144,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, float(0.0f)); MutableSpan<float> results = attribute_result.as_span<float>(); - parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { for (const int i : range) { results[i] = BKE_curvemapping_evaluateF(cumap, 3, attribute_in[i]); } @@ -156,7 +156,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon GVArray_Typed<float3> attribute_in = component.attribute_get_for_read<float3>( input_name, result_domain, float3(0.0f)); MutableSpan<float3> results = attribute_result.as_span<float3>(); - parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { for (const int i : range) { BKE_curvemapping_evaluate3F(cumap, results[i], attribute_in[i]); } @@ -169,7 +169,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon component.attribute_get_for_read<ColorGeometry4f>( input_name, result_domain, ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f)); MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); - parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { for (const int i : range) { BKE_curvemapping_evaluateRGBF(cumap, results[i], attribute_in[i]); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc index 40fe675bd6c..00f38fb0c6b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -209,7 +209,7 @@ static void map_range_float(const VArray<float> &attribute_input, switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - parallel_for(span.index_range(), 2048, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 2048, [&](IndexRange range) { for (const int i : range) { results[i] = map_linear(span[i], min_from, max_from, min_to, max_to); } @@ -218,7 +218,7 @@ static void map_range_float(const VArray<float> &attribute_input, } case NODE_MAP_RANGE_STEPPED: { const float steps = params.get_input<float>("Steps"); - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); } @@ -226,7 +226,7 @@ static void map_range_float(const VArray<float> &attribute_input, break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); } @@ -234,7 +234,7 @@ static void map_range_float(const VArray<float> &attribute_input, break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); } @@ -249,7 +249,7 @@ static void map_range_float(const VArray<float> &attribute_input, const float clamp_min = min_to < max_to ? min_to : max_to; const float clamp_max = min_to < max_to ? max_to : min_to; - parallel_for(results.index_range(), 2048, [&](IndexRange range) { + threading::parallel_for(results.index_range(), 2048, [&](IndexRange range) { for (const int i : range) { results[i] = std::clamp(results[i], clamp_min, clamp_max); } @@ -273,7 +273,7 @@ static void map_range_float3(const VArray<float3> &attribute_input, switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); results[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); @@ -284,7 +284,7 @@ static void map_range_float3(const VArray<float3> &attribute_input, } case NODE_MAP_RANGE_STEPPED: { const float3 steps = params.get_input<float3>("Steps_001"); - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i].x = map_stepped( span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); @@ -297,7 +297,7 @@ static void map_range_float3(const VArray<float3> &attribute_input, break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); results[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); @@ -307,7 +307,7 @@ static void map_range_float3(const VArray<float3> &attribute_input, break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - parallel_for(span.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { results[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); results[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index ce0ca31cc2b..9309863f4e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -159,7 +159,7 @@ static void do_math_operation(const VArray<float> &span_a, { bool success = try_dispatch_float_math_fl_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(span_result.size()), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(span_result.size()), 512, [&](IndexRange range) { for (const int i : range) { span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); } @@ -176,7 +176,7 @@ static void do_math_operation(const VArray<float> &span_a, { bool success = try_dispatch_float_math_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { for (const int i : range) { span_result[i] = math_function(span_a[i], span_b[i]); } @@ -192,7 +192,7 @@ static void do_math_operation(const VArray<float> &span_input, { bool success = try_dispatch_float_math_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { for (const int i : range) { span_result[i] = math_function(span_input[i]); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index a6bd6c0ee32..931b7758a57 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -88,7 +88,7 @@ static void do_mix_operation_float(const int blend_mode, VMutableArray<float> &results) { const int size = results.size(); - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float factor = factors[i]; float3 a{inputs_a[i]}; @@ -107,7 +107,7 @@ static void do_mix_operation_float3(const int blend_mode, VMutableArray<float3> &results) { const int size = results.size(); - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float factor = factors[i]; float3 a = inputs_a[i]; @@ -125,7 +125,7 @@ static void do_mix_operation_color4f(const int blend_mode, VMutableArray<ColorGeometry4f> &results) { const int size = results.size(); - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float factor = factors[i]; ColorGeometry4f a = inputs_a[i]; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index 9c22b7fa87f..b7863d38fc2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -71,7 +71,7 @@ static void proximity_calc(MutableSpan<float> distance_span, const bool store_locations) { IndexRange range = positions.index_range(); - parallel_for(range, 512, [&](IndexRange range) { + threading::parallel_for(range, 512, [&](IndexRange range) { BVHTreeNearest nearest_from_mesh; BVHTreeNearest nearest_from_pointcloud; 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 286411b7d28..eeb77abd624 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -126,7 +126,7 @@ static void randomize_attribute(MutableSpan<T> span, /* The operations could be templated too, but it doesn't make the code much shorter. */ switch (operation) { case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: - parallel_for(span.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const T random_value = random_value_in_range<T>(ids[i], seed, min, max); span[i] = random_value; @@ -134,7 +134,7 @@ static void randomize_attribute(MutableSpan<T> span, }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: - parallel_for(span.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const T random_value = random_value_in_range<T>(ids[i], seed, min, max); span[i] = span[i] + random_value; @@ -142,7 +142,7 @@ static void randomize_attribute(MutableSpan<T> span, }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT: - parallel_for(span.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const T random_value = random_value_in_range<T>(ids[i], seed, min, max); span[i] = span[i] - random_value; @@ -150,7 +150,7 @@ static void randomize_attribute(MutableSpan<T> span, }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY: - parallel_for(span.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const T random_value = random_value_in_range<T>(ids[i], seed, min, max); span[i] = span[i] * random_value; @@ -170,7 +170,7 @@ static void randomize_attribute_bool(MutableSpan<bool> span, { BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); UNUSED_VARS_NDEBUG(operation); - parallel_for(span.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; span[i] = random_value; @@ -190,7 +190,7 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo BLI_assert(hashes.size() == hash_attribute->size()); const CPPType &cpp_type = hash_attribute->type(); GVArray_GSpan items{*hash_attribute}; - parallel_for(hashes.index_range(), 512, [&](IndexRange range) { + threading::parallel_for(hashes.index_range(), 512, [&](IndexRange range) { for (const int i : range) { hashes[i] = cpp_type.hash(items[i]); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index d6b1ad3e9e0..e0a3f5ad334 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -90,7 +90,7 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec mapping_name, result_domain, {0, 0, 0}); MutableSpan<ColorGeometry4f> colors = attribute_out.as_span(); - parallel_for(IndexRange(mapping_attribute.size()), 128, [&](IndexRange range) { + threading::parallel_for(IndexRange(mapping_attribute.size()), 128, [&](IndexRange range) { for (const int i : range) { TexResult texture_result = {0}; const float3 position = mapping_attribute[i]; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc index 4b677dc5c82..d1114713672 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -102,14 +102,6 @@ static void get_result_domain_and_data_type(const GeometrySet &src_geometry, } } -static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) -{ - /* 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)); - const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); - return {looptris, looptris_len}; -} - static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, const VArray<float3> &positions, const MutableSpan<int> r_indices, @@ -212,7 +204,7 @@ static void get_closest_mesh_polygons(const Mesh &mesh, Array<int> looptri_indices(positions.size()); get_closest_mesh_looptris(mesh, positions, looptri_indices, r_distances_sq, r_positions); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); + Span<MLoopTri> looptris = bke::mesh_surface_sample::get_mesh_looptris(mesh); for (const int i : positions.index_range()) { const MLoopTri &looptri = looptris[looptri_indices[i]]; r_poly_indices[i] = looptri.poly; @@ -262,32 +254,6 @@ static void get_closest_mesh_corners(const Mesh &mesh, } } -static void get_barycentric_coords(const Mesh &mesh, - const Span<int> looptri_indices, - const Span<float3> positions, - const MutableSpan<float3> r_bary_coords) -{ - BLI_assert(r_bary_coords.size() == positions.size()); - BLI_assert(r_bary_coords.size() == looptri_indices.size()); - - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : r_bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - - const int v0_index = mesh.mloop[looptri.tri[0]].v; - const int v1_index = mesh.mloop[looptri.tri[1]].v; - const int v2_index = mesh.mloop[looptri.tri[2]].v; - - interp_weights_tri_v3(r_bary_coords[i], - mesh.mvert[v0_index].co, - mesh.mvert[v1_index].co, - mesh.mvert[v2_index].co, - positions[i]); - } -} - static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_geometry, GeometryComponent &dst_component, const VArray<float3> &dst_positions, @@ -308,8 +274,11 @@ static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_ if (mesh->totpoly == 0) { return; } + ReadAttributeLookup src_attribute = component->attribute_try_get_for_read(src_name, data_type); - if (!src_attribute) { + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!src_attribute || !dst_attribute) { return; } @@ -318,45 +287,10 @@ static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_ Array<float3> positions(tot_samples); get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - dst_name, dst_domain, data_type); - if (!dst_attribute) { - return; - } - GMutableSpan dst_span = dst_attribute.as_span(); - Array<float3> bary_coords; + bke::mesh_surface_sample::MeshAttributeInterpolator interp(mesh, positions, looptri_indices); + interp.sample_attribute( + src_attribute, dst_attribute, bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED); - /* Compute barycentric coordinates only when they are needed. */ - if (src_attribute.domain != ATTR_DOMAIN_FACE) { - bary_coords.reinitialize(tot_samples); - get_barycentric_coords(*mesh, looptri_indices, positions, bary_coords); - } - /* Interpolate the source attribute on the surface. */ - switch (src_attribute.domain) { - case ATTR_DOMAIN_POINT: { - bke::mesh_surface_sample::sample_point_attribute( - *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_FACE: { - bke::mesh_surface_sample::sample_face_attribute( - *mesh, looptri_indices, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_CORNER: { - bke::mesh_surface_sample::sample_corner_attribute( - *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); - break; - } - case ATTR_DOMAIN_EDGE: { - /* Not yet supported. */ - break; - } - default: { - BLI_assert_unreachable(); - break; - } - } dst_attribute.save(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index b04e04d1cb7..e2cf6e8b480 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -186,7 +186,7 @@ static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 a = span_a[i]; const float3 b = span_b[i]; @@ -218,7 +218,7 @@ static void do_math_operation_fl3_fl3_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 a = span_a[i]; const float3 b = span_b[i]; @@ -251,7 +251,7 @@ static void do_math_operation_fl3_fl3_fl_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 a = span_a[i]; const float3 b = span_b[i]; @@ -282,7 +282,7 @@ static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 a = span_a[i]; const float3 b = span_b[i]; @@ -312,7 +312,7 @@ static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 a = span_a[i]; const float b = span_b[i]; @@ -340,7 +340,7 @@ static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 in = span_a[i]; const float3 out = math_function(in); @@ -367,7 +367,7 @@ static void do_math_operation_fl3_to_fl(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - parallel_for(IndexRange(size), 512, [&](IndexRange range) { + threading::parallel_for(IndexRange(size), 512, [&](IndexRange range) { for (const int i : range) { const float3 in = span_a[i]; const float out = math_function(in); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc index 4d568ab5c3a..da753dfc11b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -154,7 +154,7 @@ static void do_vector_rotate_around_axis(const VArray<float3> &vector, VArray_Span<float3> span_axis{axis}; VArray_Span<float> span_angle{angle}; - parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { for (const int i : range) { float angle = (invert) ? -span_angle[i] : span_angle[i]; results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], span_axis[i], angle); @@ -173,7 +173,7 @@ static void do_vector_rotate_around_fixed_axis(const VArray<float3> &vector, VArray_Span<float3> span_center{center}; VArray_Span<float> span_angle{angle}; - parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { for (const int i : range) { float angle = (invert) ? -span_angle[i] : span_angle[i]; results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], axis, angle); @@ -191,7 +191,7 @@ static void do_vector_rotate_euler(const VArray<float3> &vector, VArray_Span<float3> span_center{center}; VArray_Span<float3> span_rotation{rotation}; - parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { for (const int i : range) { results[i] = vector_rotate_euler(span_vector[i], span_center[i], span_rotation[i], invert); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_deform.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_deform.cc index 9b485284b35..dde7191fb69 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_deform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_deform.cc @@ -220,7 +220,7 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, const Bounds bounds = position_bounds(positions); const Bounds parameter_bounds = input.use_bounds ? bounds : dummy_parameter_bounds(deform_axis); - parallel_for(positions.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(positions.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { const float parameter = process_parameter( positions[i], axis_index, is_negative, input, parameter_bounds); @@ -267,7 +267,7 @@ static void geo_node_curve_deform_exec(GeoNodeExecParams params) spline.evaluated_positions(), spline.evaluated_tangents(), spline.evaluated_normals(), - spline.interpolate_to_evaluated_points(spline.radii()), + spline.interpolate_to_evaluated(spline.radii()), total_length, params.extract_input<bool>("Stretch"), params.extract_input<bool>("Use Bounds")}; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index e879ec624c0..fc65d1754e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -94,16 +94,16 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count) Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); - input_spline.sample_based_on_index_factors<float3>( + input_spline.sample_with_index_factors<float3>( input_spline.evaluated_positions(), uniform_samples, output_spline->positions()); - input_spline.sample_based_on_index_factors<float>( - input_spline.interpolate_to_evaluated_points(input_spline.radii()), + input_spline.sample_with_index_factors<float>( + input_spline.interpolate_to_evaluated(input_spline.radii()), uniform_samples, output_spline->radii()); - input_spline.sample_based_on_index_factors<float>( - input_spline.interpolate_to_evaluated_points(input_spline.tilts()), + input_spline.sample_with_index_factors<float>( + input_spline.interpolate_to_evaluated(input_spline.tilts()), uniform_samples, output_spline->tilts()); @@ -123,8 +123,8 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count) return false; } - input_spline.sample_based_on_index_factors( - *input_spline.interpolate_to_evaluated_points(*input_attribute), + input_spline.sample_with_index_factors( + *input_spline.interpolate_to_evaluated(*input_attribute), uniform_samples, *output_attribute); @@ -138,19 +138,28 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count) static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, const SampleModeParam &mode_param) { - std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + Span<SplinePtr> input_splines = input_curve.splines(); - for (const SplinePtr &spline : input_curve.splines()) { - if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { - BLI_assert(mode_param.count); - output_curve->add_spline(resample_spline(*spline, *mode_param.count)); - } - else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { - BLI_assert(mode_param.length); - const float length = spline->length(); - const int count = std::max(int(length / *mode_param.length), 1); - output_curve->add_spline(resample_spline(*spline, count)); - } + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + output_curve->resize(input_splines.size()); + MutableSpan<SplinePtr> output_splines = output_curve->splines(); + + if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + BLI_assert(mode_param.count); + output_splines[i] = resample_spline(*input_splines[i], *mode_param.count); + } + }); + } + else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const float length = input_splines[i]->length(); + const int count = std::max(int(length / *mode_param.length), 1); + output_splines[i] = resample_spline(*input_splines[i], count); + } + }); } output_curve->attributes = input_curve.attributes; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc new file mode 100644 index 00000000000..e92d22a6064 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -0,0 +1,132 @@ +/* + * 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 "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_reverse_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_reverse_out[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {-1, ""}, +}; + +namespace blender::nodes { + +/** + * Reverse the data in a MutableSpan object. + */ +template<typename T> static void reverse_data(MutableSpan<T> r_data) +{ + const int size = r_data.size(); + for (const int i : IndexRange(size / 2)) { + std::swap(r_data[size - 1 - i], r_data[i]); + } +} + +/** + * Reverse and Swap the data between 2 MutableSpans. + */ +template<typename T> static void reverse_data(MutableSpan<T> left, MutableSpan<T> right) +{ + BLI_assert(left.size() == right.size()); + const int size = left.size(); + + for (const int i : IndexRange(size / 2 + size % 2)) { + std::swap(left[i], right[size - 1 - i]); + std::swap(right[i], left[size - 1 - i]); + } +} + +static void geo_node_curve_reverse_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the reversed data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + if (!selection[i]) { + continue; + } + + reverse_data<float3>(splines[i]->positions()); + reverse_data<float>(splines[i]->radii()); + reverse_data<float>(splines[i]->tilts()); + + splines[i]->attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<blender::fn::GMutableSpan> output_attribute = + splines[i]->attributes.get_for_write(name); + if (!output_attribute) { + BLI_assert_unreachable(); + return false; + } + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + reverse_data(output_attribute->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + /* Deal with extra info on derived types. */ + if (BezierSpline *spline = dynamic_cast<BezierSpline *>(splines[i].get())) { + reverse_data<BezierSpline::HandleType>(spline->handle_types_left()); + reverse_data<BezierSpline::HandleType>(spline->handle_types_right()); + reverse_data<float3>(spline->handle_positions_left(), spline->handle_positions_right()); + } + else if (NURBSpline *spline = dynamic_cast<NURBSpline *>(splines[i].get())) { + reverse_data<float>(spline->weights()); + } + /* Nothing to do for poly splines. */ + + splines[i]->mark_cache_invalid(); + } + }); + + params.set_output("Curve", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_reverse() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_reverse_in, geo_node_curve_reverse_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc new file mode 100644 index 00000000000..3de2604cd0a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -0,0 +1,407 @@ +/* + * 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 "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_Span; +using blender::fn::GVArray_Typed; + +static bNodeSocketTemplate geo_node_curve_subdivide_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Cuts")}, + {SOCK_INT, N_("Cuts"), 1, 0, 0, 0, 0, 1000}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_subdivide_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); +} + +static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( + sizeof(NodeGeometryCurveSubdivide), __func__); + + data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; + node->storage = data; +} + +namespace blender::nodes { + +static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + +static Array<int> get_subdivided_offsets(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + Array<int> offsets(spline.segments_size() + 1); + int offset = 0; + for (const int i : IndexRange(spline.segments_size())) { + offsets[i] = offset; + offset = offset + std::max(cuts[spline_offset + i], 0) + 1; + } + offsets.last() = offset; + return offsets; +} + +template<typename T> +static void subdivide_attribute(Span<T> src, + const Span<int> offsets, + const bool is_cyclic, + MutableSpan<T> dst) +{ + const int src_size = src.size(); + threading::parallel_for(IndexRange(src_size - 1), 1024, [&](IndexRange range) { + for (const int i : range) { + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src[i]; + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); + } + } + }); + + if (is_cyclic) { + const int i = src_size - 1; + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src.last(); + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); + } + } + else { + dst.last() = src.last(); + } +} + +/** + * De Casteljau Bezier subdivision. + * + * <pre> + * handle_prev handle_next + * O----------------O + * / \ + * / x---O---x \ + * / new_* \ + * / \ + * O O + * point_prev point_next + * </pre> + */ +static void calculate_new_bezier_point(const float3 &point_prev, + float3 &handle_prev, + float3 &new_left_handle, + float3 &new_position, + float3 &new_right_handle, + float3 &handle_next, + const float3 &point_next, + const float parameter) +{ + const float3 center_point = float3::interpolate(handle_prev, handle_next, parameter); + + handle_prev = float3::interpolate(point_prev, handle_prev, parameter); + handle_next = float3::interpolate(handle_next, point_next, parameter); + new_left_handle = float3::interpolate(handle_prev, center_point, parameter); + new_right_handle = float3::interpolate(center_point, handle_next, parameter); + new_position = float3::interpolate(new_left_handle, new_right_handle, parameter); +} + +/** + * In order to generate a Bezier spline with the same shape as the input spline, apply the + * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the + * previous result point's right handle and the left handle at the end of the segment. + * + * \note Non-vector segments in the result spline are given free handles. This could possibly be + * improved with another pass that sets handles to aligned where possible, but currently that does + * not provide much benefit for the increased complexity. + */ +static void subdivide_bezier_segment(const BezierSpline &src, + const int index, + const int offset, + const int result_size, + Span<float3> src_positions, + Span<float3> src_handles_left, + Span<float3> src_handles_right, + MutableSpan<float3> dst_positions, + MutableSpan<float3> dst_handles_left, + MutableSpan<float3> dst_handles_right, + MutableSpan<BezierSpline::HandleType> dst_type_left, + MutableSpan<BezierSpline::HandleType> dst_type_right) +{ + const bool is_last_cyclic_segment = index == (src.size() - 1); + const int next_index = is_last_cyclic_segment ? 0 : index + 1; + if (src.segment_is_vector(index)) { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Vector; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector); + + dst_positions[offset] = src_positions[index]; + const float factor_delta = 1.0f / result_size; + for (const int cut : IndexRange(result_size)) { + const float factor = cut * factor_delta; + dst_positions[offset + cut] = attribute_math::mix2( + factor, src_positions[index], src_positions[next_index]); + } + } + else { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Free; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free); + + const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size; + dst_positions[offset] = src_positions[index]; + dst_handles_right[offset] = src_handles_right[index]; + dst_handles_left[i_segment_last] = src_handles_left[next_index]; + + for (const int cut : IndexRange(result_size - 1)) { + const float parameter = 1.0f / (result_size - cut); + calculate_new_bezier_point(dst_positions[offset + cut], + dst_handles_right[offset + cut], + dst_handles_left[offset + cut + 1], + dst_positions[offset + cut + 1], + dst_handles_right[offset + cut + 1], + dst_handles_left[i_segment_last], + src_positions[next_index], + parameter); + } + } +} + +static void subdivide_bezier_spline(const BezierSpline &src, + const Span<int> offsets, + BezierSpline &dst) +{ + Span<float3> src_positions = src.positions(); + Span<float3> src_handles_left = src.handle_positions_left(); + Span<float3> src_handles_right = src.handle_positions_right(); + MutableSpan<float3> dst_positions = dst.positions(); + MutableSpan<float3> dst_handles_left = dst.handle_positions_left(); + MutableSpan<float3> dst_handles_right = dst.handle_positions_right(); + MutableSpan<BezierSpline::HandleType> dst_type_left = dst.handle_types_left(); + MutableSpan<BezierSpline::HandleType> dst_type_right = dst.handle_types_right(); + + threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { + for (const int i : range) { + subdivide_bezier_segment(src, + i, + offsets[i], + offsets[i + 1] - offsets[i], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + }); + + if (src.is_cyclic()) { + const int i_last = src.size() - 1; + subdivide_bezier_segment(src, + i_last, + offsets[i_last], + offsets.last() - offsets[i_last], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + else { + dst_positions.last() = src_positions.last(); + } +} + +static void subdivide_builtin_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + subdivide_attribute<float>(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); + subdivide_attribute<float>(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); + switch (src_spline.type()) { + case Spline::Type::Poly: { + const PolySpline &src = static_cast<const PolySpline &>(src_spline); + PolySpline &dst = static_cast<PolySpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + break; + } + case Spline::Type::Bezier: { + const BezierSpline &src = static_cast<const BezierSpline &>(src_spline); + BezierSpline &dst = static_cast<BezierSpline &>(dst_spline); + subdivide_bezier_spline(src, offsets, dst); + dst.mark_cache_invalid(); + break; + } + case Spline::Type::NURBS: { + const NURBSpline &src = static_cast<const NURBSpline &>(src_spline); + NURBSpline &dst = static_cast<NURBSpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + subdivide_attribute<float>(src.weights(), offsets, is_cyclic, dst.weights()); + break; + } + } +} + +static void subdivide_dynamic_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + src_spline.attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = src_spline.attributes.get_for_read(name); + BLI_assert(src); + + if (!dst_spline.attributes.create(name, meta_data.data_type)) { + /* Since the source spline of the same type had the attribute, adding it should work. */ + BLI_assert_unreachable(); + } + + std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(name); + BLI_assert(dst); + + attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute<T>(src->typed<T>(), offsets, is_cyclic, dst->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr subdivide_spline(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + /* Since we expect to access each value many times, it should be worth it to make sure the + * attribute is a real span (especially considering the note below). Using the offset at each + * point facilitates subdividing in parallel later. */ + Array<int> offsets = get_subdivided_offsets(spline, cuts, spline_offset); + const int result_size = offsets.last() + int(!spline.is_cyclic()); + SplinePtr new_spline = spline.copy_only_settings(); + new_spline->resize(result_size); + subdivide_builtin_attributes(spline, offsets, *new_spline); + subdivide_dynamic_attributes(spline, offsets, *new_spline); + return new_spline; +} + +/** + * \note Passing the virtual array for the entire spline is possibly quite inefficient here when + * the attribute was on the point domain and stored separately for each spline already, and it + * prevents some other optimizations like skipping splines with a single attribute value of < 1. + * However, it allows the node to access builtin attribute easily, so it the makes most sense this + * way until the attribute API is refactored. + */ +static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, + const VArray<int> &cuts) +{ + const Array<int> control_point_offsets = input_curve.control_point_offsets(); + const Span<SplinePtr> input_splines = input_curve.splines(); + + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + output_curve->resize(input_splines.size()); + output_curve->attributes = input_curve.attributes; + MutableSpan<SplinePtr> output_splines = output_curve->splines(); + + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); + } + }); + + return output_curve; +} + +static void geo_node_subdivide_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", geometry_set); + return; + } + + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GVArray_Typed<int> cuts = params.get_input_attribute<int>( + "Cuts", component, ATTR_DOMAIN_POINT, 0); + if (cuts->is_single() && cuts->get_internal_single() < 1) { + params.set_output("Geometry", geometry_set); + return; + } + + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_subdivide() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_subdivide_in, geo_node_curve_subdivide_out); + ntype.draw_buttons = geo_node_curve_subdivide_layout; + node_type_storage(&ntype, + "NodeGeometryCurveSubdivide", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, geo_node_curve_subdivide_init); + node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); + ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index b6f04352929..c0d817385e2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -16,7 +16,7 @@ #include "BLI_array.hh" #include "BLI_float4x4.hh" -#include "BLI_timeit.hh" +#include "BLI_task.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -47,8 +47,8 @@ static void vert_extrude_to_mesh_data(const Spline &spline, const float3 profile_vert, MutableSpan<MVert> r_verts, MutableSpan<MEdge> r_edges, - int &vert_offset, - int &edge_offset) + int vert_offset, + int edge_offset) { Span<float3> positions = spline.evaluated_positions(); @@ -85,10 +85,10 @@ static void spline_extrude_to_mesh_data(const Spline &spline, MutableSpan<MEdge> r_edges, MutableSpan<MLoop> r_loops, MutableSpan<MPoly> r_polys, - int &vert_offset, - int &edge_offset, - int &loop_offset, - int &poly_offset) + int vert_offset, + int edge_offset, + int loop_offset, + int poly_offset) { const int spline_vert_len = spline.evaluated_points_size(); const int spline_edge_len = spline.evaluated_edges_size(); @@ -181,7 +181,7 @@ static void spline_extrude_to_mesh_data(const Spline &spline, Span<float3> normals = spline.evaluated_normals(); Span<float3> profile_positions = profile_spline.evaluated_positions(); - GVArray_Typed<float> radii = spline.interpolate_to_evaluated_points(spline.radii()); + GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); for (const int i_ring : IndexRange(spline_vert_len)) { float4x4 point_matrix = float4x4::from_normalized_axis_data( positions[i_ring], normals[i_ring], tangents[i_ring]); @@ -207,63 +207,114 @@ static void spline_extrude_to_mesh_data(const Spline &spline, } } -static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile_curve) +static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) { - int profile_vert_total = 0; - int profile_edge_total = 0; - for (const SplinePtr &profile_spline : profile_curve.splines()) { - profile_vert_total += profile_spline->evaluated_points_size(); - profile_edge_total += profile_spline->evaluated_edges_size(); - } + return curve.evaluated_points_size() * profile.evaluated_points_size(); +} - int vert_total = 0; - int edge_total = 0; - int poly_total = 0; - for (const SplinePtr &spline : curve.splines()) { - const int spline_vert_len = spline->evaluated_points_size(); - const int spline_edge_len = spline->evaluated_edges_size(); - vert_total += spline_vert_len * profile_vert_total; - poly_total += spline_edge_len * profile_edge_total; - - /* Add the ring edges, with one ring for every curve vertex, and the edge loops - * that run along the length of the curve, starting on the first profile. */ - edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len; - } - const int corner_total = poly_total * 4; +static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) +{ + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + return curve.evaluated_points_size() * profile.evaluated_edges_size() + + curve.evaluated_edges_size() * profile.evaluated_points_size(); +} - if (vert_total == 0) { - return nullptr; - } +static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile) +{ + return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; +} - Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; - MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; - MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; - MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; - mesh->flag |= ME_AUTOSMOOTH; - mesh->smoothresh = DEG2RADF(180.0f); +static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile) +{ + return curve.evaluated_edges_size() * profile.evaluated_edges_size(); +} +struct ResultOffsets { + Array<int> vert; + Array<int> edge; + Array<int> loop; + Array<int> poly; +}; +static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves) +{ + const int total = profiles.size() * curves.size(); + Array<int> vert(total + 1); + Array<int> edge(total + 1); + Array<int> loop(total + 1); + Array<int> poly(total + 1); + + int mesh_index = 0; int vert_offset = 0; int edge_offset = 0; int loop_offset = 0; int poly_offset = 0; - for (const SplinePtr &spline : curve.splines()) { - for (const SplinePtr &profile_spline : profile_curve.splines()) { - spline_extrude_to_mesh_data(*spline, - *profile_spline, - verts, - edges, - loops, - polys, - vert_offset, - edge_offset, - loop_offset, - poly_offset); + for (const int i_spline : curves.index_range()) { + for (const int i_profile : profiles.index_range()) { + vert[mesh_index] = vert_offset; + edge[mesh_index] = edge_offset; + loop[mesh_index] = loop_offset; + poly[mesh_index] = poly_offset; + vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); + edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); + loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]); + poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]); + mesh_index++; } } + vert.last() = vert_offset; + edge.last() = edge_offset; + loop.last() = loop_offset; + poly.last() = poly_offset; + + return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; +} + +/** + * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. + * Although it would be a sensible decision to use the better topology information available while + * generating the mesh to also generate the normals, that work may wasted if the output mesh is + * changed anyway in a way that affects the normals. So currently this code uses the safer / + * simpler solution of not calculating normals. + */ +static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile) +{ + Span<SplinePtr> profiles = profile.splines(); + Span<SplinePtr> curves = curve.splines(); + + const ResultOffsets offsets = calculate_result_offsets(profiles, curves); + if (offsets.vert.last() == 0) { + return nullptr; + } - BKE_mesh_calc_normals(mesh); + Mesh *mesh = BKE_mesh_new_nomain( + offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); + mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + + threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { + for (const int i_spline : curves_range) { + const int spline_start_index = i_spline * profiles.size(); + threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { + for (const int i_profile : profiles_range) { + const int i_mesh = spline_start_index + i_profile; + spline_extrude_to_mesh_data(*curves[i_spline], + *profiles[i_profile], + {mesh->mvert, mesh->totvert}, + {mesh->medge, mesh->totedge}, + {mesh->mloop, mesh->totloop}, + {mesh->mpoly, mesh->totpoly}, + offsets.vert[i_mesh], + offsets.edge[i_mesh], + offsets.loop[i_mesh], + offsets.poly[i_mesh]); + } + }); + } + }); return mesh; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc new file mode 100644 index 00000000000..2725c625913 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -0,0 +1,390 @@ +/* + * 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 "BLI_array.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_pointcloud.h" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_to_points_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Count"), 10, 0, 0, 0, 2, 100000}, + {SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.001f, FLT_MAX, PROP_DISTANCE}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_to_points_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_curve_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void geo_node_curve_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveToPoints *data = (NodeGeometryCurveToPoints *)MEM_callocN( + sizeof(NodeGeometryCurveToPoints), __func__); + + data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + node->storage = data; +} + +static void geo_node_curve_to_points_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length_socket = count_socket->next; + + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +namespace blender::nodes { + +/** + * Evaluate splines in parallel to speed up the rest of the node's execution. + */ +static void evaluate_splines(Span<SplinePtr> splines) +{ + threading::parallel_for_each(splines, [](const SplinePtr &spline) { + /* These functions fill the corresponding caches on each spline. */ + spline->evaluated_positions(); + spline->evaluated_tangents(); + spline->evaluated_normals(); + spline->evaluated_lengths(); + }); +} + +static Array<int> calculate_spline_point_offsets(GeoNodeExecParams ¶ms, + const GeometryNodeCurveSampleMode mode, + const CurveEval &curve, + const Span<SplinePtr> splines) +{ + const int size = curve.splines().size(); + switch (mode) { + case GEO_NODE_CURVE_SAMPLE_COUNT: { + const int count = params.extract_input<int>("Count"); + if (count < 1) { + return {0}; + } + Array<int> offsets(size + 1); + for (const int i : offsets.index_range()) { + offsets[i] = count * i; + } + return offsets; + } + case GEO_NODE_CURVE_SAMPLE_LENGTH: { + /* Don't allow asymptotic count increase for low resolution values. */ + const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + Array<int> offsets(size + 1); + int offset = 0; + for (const int i : IndexRange(size)) { + offsets[i] = offset; + offset += splines[i]->length() / resolution; + } + offsets.last() = offset; + return offsets; + } + case GEO_NODE_CURVE_SAMPLE_EVALUATED: { + return curve.evaluated_point_offsets(); + } + } + BLI_assert_unreachable(); + return {0}; +} + +/** + * \note This doesn't store a map for spline domain attributes. + */ +struct ResultAttributes { + int result_size; + MutableSpan<float3> positions; + MutableSpan<float> radii; + MutableSpan<float> tilts; + + Map<std::string, GMutableSpan> point_attributes; + + MutableSpan<float3> tangents; + MutableSpan<float3> normals; + MutableSpan<float3> rotations; +}; + +static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points, + const StringRef name, + const CustomDataType data_type) +{ + points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); + WriteAttributeLookup attribute = points.attribute_try_get_for_write(name); + BLI_assert(attribute); + return attribute.varray->get_internal_span(); +} + +template<typename T> +static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points, + const StringRef name) +{ + GMutableSpan attribute = create_attribute_and_retrieve_span( + points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>())); + return attribute.typed<T>(); +} + +/** + * Create references for all result point cloud attributes to simplify accessing them later on. + */ +static ResultAttributes create_point_attributes(PointCloudComponent &points, + const CurveEval &curve) +{ + ResultAttributes attributes; + + attributes.result_size = points.attribute_domain_size(ATTR_DOMAIN_POINT); + + attributes.positions = create_attribute_and_retrieve_span<float3>(points, "position"); + attributes.radii = create_attribute_and_retrieve_span<float>(points, "radius"); + attributes.tilts = create_attribute_and_retrieve_span<float>(points, "tilt"); + + /* Because of the invariants of the curve component, we use the attributes of the + * first spline as a representative for the attribute meta data all splines. */ + curve.splines().first()->attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + attributes.point_attributes.add_new( + name, create_attribute_and_retrieve_span(points, name, meta_data.data_type)); + return true; + }, + ATTR_DOMAIN_POINT); + + attributes.tangents = create_attribute_and_retrieve_span<float3>(points, "tangent"); + attributes.normals = create_attribute_and_retrieve_span<float3>(points, "normal"); + attributes.rotations = create_attribute_and_retrieve_span<float3>(points, "rotation"); + + return attributes; +} + +/** + * TODO: For non-poly splines, this has double copies that could be avoided as part + * of a general look at optimizing uses of #Spline::interpolate_to_evaluated. + */ +static void copy_evaluated_point_attributes(Span<SplinePtr> splines, + Span<int> offsets, + ResultAttributes &data) +{ + threading::parallel_for(splines.index_range(), 64, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + + data.positions.slice(offset, size).copy_from(spline.evaluated_positions()); + spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size)); + spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size)); + + for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { + const StringRef name = item.key; + GMutableSpan point_span = item.value; + + BLI_assert(spline.attributes.get_for_read(name)); + GSpan spline_span = *spline.attributes.get_for_read(name); + + spline.interpolate_to_evaluated(spline_span) + ->materialize(point_span.slice(offset, size).data()); + } + + data.tangents.slice(offset, size).copy_from(spline.evaluated_tangents()); + data.normals.slice(offset, size).copy_from(spline.evaluated_normals()); + } + }); +} + +static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines, + Span<int> offsets, + ResultAttributes &data) +{ + threading::parallel_for(splines.index_range(), 64, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + if (size == 0) { + continue; + } + + const Array<float> uniform_samples = spline.sample_uniform_index_factors(size); + + spline.sample_with_index_factors<float3>( + spline.evaluated_positions(), uniform_samples, data.positions.slice(offset, size)); + + spline.sample_with_index_factors<float>(spline.interpolate_to_evaluated(spline.radii()), + uniform_samples, + data.radii.slice(offset, size)); + + spline.sample_with_index_factors<float>(spline.interpolate_to_evaluated(spline.tilts()), + uniform_samples, + data.tilts.slice(offset, size)); + + for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) { + const StringRef name = item.key; + GMutableSpan point_span = item.value; + + BLI_assert(spline.attributes.get_for_read(name)); + GSpan spline_span = *spline.attributes.get_for_read(name); + + spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span), + uniform_samples, + point_span.slice(offset, size)); + } + + spline.sample_with_index_factors<float3>( + spline.evaluated_tangents(), uniform_samples, data.tangents.slice(offset, size)); + for (float3 &tangent : data.tangents) { + tangent.normalize(); + } + + spline.sample_with_index_factors<float3>( + spline.evaluated_normals(), uniform_samples, data.normals.slice(offset, size)); + for (float3 &normals : data.normals) { + normals.normalize(); + } + } + }); +} + +/** + * \note Use attributes from the curve component rather than the attribute data directly on the + * attribute storage to allow reading the virtual spline attributes like "cyclic" and "resolution". + */ +static void copy_spline_domain_attributes(const CurveComponent &curve_component, + Span<int> offsets, + PointCloudComponent &points) +{ + curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { + if (meta_data.domain != ATTR_DOMAIN_CURVE) { + return true; + } + GVArrayPtr spline_attribute = curve_component.attribute_get_for_read( + name, ATTR_DOMAIN_CURVE, meta_data.data_type); + const CPPType &type = spline_attribute->type(); + + OutputAttribute result_attribute = points.attribute_try_get_for_output_only( + name, ATTR_DOMAIN_POINT, meta_data.data_type); + GMutableSpan result = result_attribute.as_span(); + + for (const int i : IndexRange(spline_attribute->size())) { + const int offset = offsets[i]; + const int size = offsets[i + 1] - offsets[i]; + if (size != 0) { + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + spline_attribute->get(i, buffer); + type.fill_initialized(buffer, result[offset], size); + } + } + + result_attribute.save(); + return true; + }); +} + +static void create_default_rotation_attribute(ResultAttributes &data) +{ + threading::parallel_for(IndexRange(data.result_size), 512, [&](IndexRange range) { + for (const int i : range) { + data.rotations[i] = float4x4::from_normalized_axis_data( + {0, 0, 0}, data.normals[i], data.tangents[i]) + .to_euler(); + } + }); +} + +static void geo_node_curve_to_points_exec(GeoNodeExecParams params) +{ + NodeGeometryCurveToPoints &node_storage = *(NodeGeometryCurveToPoints *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", GeometrySet()); + return; + } + + const CurveComponent &curve_component = *geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component.get_for_read(); + const Span<SplinePtr> splines = curve.splines(); + curve.assert_valid_point_attributes(); + + evaluate_splines(splines); + + const Array<int> offsets = calculate_spline_point_offsets(params, mode, curve, splines); + const int total_size = offsets.last(); + if (total_size == 0) { + params.set_output("Geometry", GeometrySet()); + return; + } + + GeometrySet result = GeometrySet::create_with_pointcloud(BKE_pointcloud_new_nomain(total_size)); + PointCloudComponent &point_component = result.get_component_for_write<PointCloudComponent>(); + + ResultAttributes new_attributes = create_point_attributes(point_component, curve); + + switch (mode) { + case GEO_NODE_CURVE_SAMPLE_COUNT: + case GEO_NODE_CURVE_SAMPLE_LENGTH: + copy_uniform_sample_point_attributes(splines, offsets, new_attributes); + break; + case GEO_NODE_CURVE_SAMPLE_EVALUATED: + copy_evaluated_point_attributes(splines, offsets, new_attributes); + break; + } + + copy_spline_domain_attributes(curve_component, offsets, point_component); + create_default_rotation_attribute(new_attributes); + + /* The default radius is way too large for points, divide by 10. */ + for (float &radius : new_attributes.radii) { + radius *= 0.1f; + } + + params.set_output("Geometry", std::move(result)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_to_points() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_to_points_in, geo_node_curve_to_points_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_points_exec; + ntype.draw_buttons = geo_node_curve_to_points_layout; + node_type_storage( + &ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, geo_node_curve_to_points_init); + node_type_update(&ntype, geo_node_curve_to_points_update); + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 910adc467d6..b1da2dcd3c4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -120,7 +120,7 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src, static SplinePtr spline_delete(const Spline &spline, const IndexMask mask) { - SplinePtr new_spline = spline.copy_settings(); + SplinePtr new_spline = spline.copy_only_settings(); new_spline->resize(mask.size()); spline_copy_builtin_attributes(spline, *new_spline, mask); 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 adfd924f185..bc758b59987 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -23,8 +23,12 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "NOD_type_conversions.hh" + #include "node_geometry_util.hh" +using blender::fn::GVArray_For_GSpan; + static bNodeSocketTemplate geo_node_join_geometry_in[] = { {SOCK_GEOMETRY, N_("Geometry"), @@ -79,7 +83,7 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent const Mesh *first_input_mesh = src_components[0]->get_for_read(); Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); - BKE_mesh_copy_settings(new_mesh, first_input_mesh); + BKE_mesh_copy_parameters_for_eval(new_mesh, first_input_mesh); for (const int i : IndexRange(materials.size())) { Material *material = materials[i]; @@ -157,35 +161,30 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component return components; } -static Set<std::string> find_all_attribute_names(Span<const GeometryComponent *> components) +static Map<std::string, AttributeMetaData> get_final_attribute_info( + Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes) { - Set<std::string> attribute_names; - for (const GeometryComponent *component : components) { - Set<std::string> names = component->attribute_names(); - for (const std::string &name : names) { - attribute_names.add(name); - } - } - return attribute_names; -} + Map<std::string, AttributeMetaData> info; -static void determine_final_data_type_and_domain(Span<const GeometryComponent *> components, - StringRef attribute_name, - CustomDataType *r_type, - AttributeDomain *r_domain) -{ - Vector<CustomDataType> data_types; - Vector<AttributeDomain> domains; for (const GeometryComponent *component : components) { - ReadAttributeLookup attribute = component->attribute_try_get_for_read(attribute_name); - if (attribute) { - data_types.append(bke::cpp_type_to_custom_data_type(attribute.varray->type())); - domains.append(attribute.domain); - } + component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { + if (ignored_attributes.contains(name)) { + return true; + } + info.add_or_modify( + name, + [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, + [&](AttributeMetaData *meta_data_final) { + meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( + {meta_data_final->data_type, meta_data.data_type}); + meta_data_final->domain = blender::bke::attribute_domain_highest_priority( + {meta_data_final->domain, meta_data.domain}); + }); + return true; + }); } - *r_type = bke::attribute_data_type_highest_complexity(data_types); - *r_domain = bke::attribute_domain_highest_priority(domains); + return info; } static void fill_new_attribute(Span<const GeometryComponent *> src_components, @@ -219,23 +218,20 @@ static void join_attributes(Span<const GeometryComponent *> src_components, GeometryComponent &result, Span<StringRef> ignored_attributes = {}) { - Set<std::string> attribute_names = find_all_attribute_names(src_components); - for (StringRef name : ignored_attributes) { - attribute_names.remove(name); - } + const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components, + ignored_attributes); - for (const std::string &attribute_name : attribute_names) { - CustomDataType data_type; - AttributeDomain domain; - determine_final_data_type_and_domain(src_components, attribute_name, &data_type, &domain); + for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { + const StringRef name = item.key; + const AttributeMetaData &meta_data = item.value; OutputAttribute write_attribute = result.attribute_try_get_for_output_only( - attribute_name, domain, data_type); + name, meta_data.domain, meta_data.data_type); if (!write_attribute) { continue; } GMutableSpan dst_span = write_attribute.as_span(); - fill_new_attribute(src_components, attribute_name, data_type, domain, dst_span); + fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span); write_attribute.save(); } } @@ -306,6 +302,127 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet UNUSED_VARS(src_components, dst_component); } +/** + * \note This takes advantage of the fact that creating attributes on joined curves never + * changes a point attribute into a spline attribute; it is always the other way around. + */ +static void ensure_control_point_attribute(const StringRef name, + const CustomDataType data_type, + Span<CurveComponent *> src_components, + CurveEval &result) +{ + MutableSpan<SplinePtr> splines = result.splines(); + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + + /* In order to fill point attributes with spline domain attribute values where necessary, keep + * track of the curve each spline came from while iterating over the splines in the result. */ + int src_component_index = 0; + int spline_index_in_component = 0; + const CurveEval *current_curve = src_components[src_component_index]->get_for_read(); + + for (SplinePtr &spline : splines) { + std::optional<GSpan> attribute = spline->attributes.get_for_read(name); + + if (attribute) { + if (attribute->type() != type) { + /* In this case, the attribute exists, but it has the wrong type. So create a buffer + * for the converted values, do the conversion, and then replace the attribute. */ + void *converted_buffer = MEM_mallocN_aligned( + spline->size() * type.size(), type.alignment(), __func__); + + const DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); + conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type) + ->materialize(converted_buffer); + + spline->attributes.remove(name); + spline->attributes.create_by_move(name, data_type, converted_buffer); + } + } + else { + spline->attributes.create(name, data_type); + + if (current_curve->attributes.get_for_read(name)) { + /* In this case the attribute did not exist, but there is a spline domain attribute + * we can retrieve a value from, as a spline to point domain conversion. So fill the + * new attribute with the value for this spline. */ + GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read( + name, data_type, nullptr); + + BLI_assert(spline->attributes.get_for_read(name)); + std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name); + + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + current_curve_attribute->get(spline_index_in_component, buffer); + type.fill_initialized(buffer, new_attribute->data(), new_attribute->size()); + } + } + + /* Move to the next spline and maybe the next input component. */ + spline_index_in_component++; + if (spline != splines.last() && spline_index_in_component >= current_curve->splines().size()) { + src_component_index++; + spline_index_in_component = 0; + + current_curve = src_components[src_component_index]->get_for_read(); + } + } +} + +/** + * Fill data for an attribute on the new curve based on all source curves. + */ +static void ensure_spline_attribute(const StringRef name, + const CustomDataType data_type, + Span<CurveComponent *> src_components, + CurveEval &result) +{ + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + + result.attributes.create(name, data_type); + GMutableSpan result_attribute = *result.attributes.get_for_write(name); + + int offset = 0; + for (const CurveComponent *component : src_components) { + const CurveEval &curve = *component->get_for_read(); + const int size = curve.splines().size(); + if (size == 0) { + continue; + } + GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr); + GVArray_GSpan src_span{*read_attribute}; + + const void *src_buffer = src_span.data(); + type.copy_to_initialized_n(src_buffer, result_attribute[offset], size); + + offset += size; + } +} + +/** + * Special handling for copying spline attributes. This is necessary because we move the splines + * out of the source components instead of copying them, meaning we can no longer access point + * domain attributes on the source components. + * + * \warning Splines have been moved out of the source components at this point, so it + * is important to only read curve-level data (spline domain attributes) from them. + */ +static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info, + Span<CurveComponent *> src_components, + CurveEval &result) +{ + for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { + const StringRef name = item.key; + const AttributeMetaData meta_data = item.value; + + if (meta_data.domain == ATTR_DOMAIN_CURVE) { + ensure_spline_attribute(name, meta_data.data_type, src_components, result); + } + else { + ensure_control_point_attribute(name, meta_data.data_type, src_components, result); + } + } +} + static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result) { Vector<CurveComponent *> src_components; @@ -328,6 +445,11 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge return; } + /* Retrieve attribute info before moving the splines out of the input components. */ + const Map<std::string, AttributeMetaData> info = get_final_attribute_info( + {(const GeometryComponent **)src_components.data(), src_components.size()}, + {"position", "radius", "tilt", "cyclic", "resolution"}); + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); CurveEval *dst_curve = new CurveEval(); for (CurveComponent *component : src_components) { @@ -336,14 +458,9 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge dst_curve->add_spline(std::move(spline)); } } - - /* For now, remove all custom attributes, since they might have different types, - * or an attribute might not exist on all splines. */ dst_curve->attributes.reallocate(dst_curve->splines().size()); - CustomData_reset(&dst_curve->attributes.data); - for (SplinePtr &spline : dst_curve->splines()) { - CustomData_reset(&spline->attributes.data); - } + + join_curve_attributes(info, src_components, *dst_curve); dst_component.replace(dst_curve); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index 9651301cb34..3a93bc22b7e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -43,6 +43,7 @@ Mesh *create_cube_mesh(const float size) const BMeshCreateParams bmcp = {true}; const BMAllocTemplate allocsize = {8, 12, 24, 6}; BMesh *bm = BM_mesh_create(&allocsize, &bmcp); + BM_data_layer_add_named(bm, &bm->ldata, CD_MLOOPUV, nullptr); BMO_op_callf(bm, BMO_FLAG_DEFAULTS, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index a3a1b72006c..22195b9e8db 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -26,7 +26,7 @@ static bNodeSocketTemplate geo_node_mesh_primitive_ico_sphere_in[] = { {SOCK_FLOAT, N_("Radius"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE}, - {SOCK_INT, N_("Subdivisions"), 1, 0, 0, 0, 0, 7}, + {SOCK_INT, N_("Subdivisions"), 1, 0, 0, 0, 1, 7}, {-1, ""}, }; @@ -44,6 +44,7 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) const BMeshCreateParams bmcp = {true}; const BMAllocTemplate allocsize = {0, 0, 0, 0}; BMesh *bm = BM_mesh_create(&allocsize, &bmcp); + BM_data_layer_add_named(bm, &bm->ldata, CD_MLOOPUV, nullptr); BMO_op_callf(bm, BMO_FLAG_DEFAULTS, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 0fb7910c904..637003a46c7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -62,7 +62,7 @@ static void copy_attributes_to_points(CurveEval &curve, if (source_attribute_names.contains_as("tilt")) { const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>( "tilt", ATTR_DOMAIN_POINT, 0.0f); - parallel_for(splines.index_range(), 256, [&](IndexRange range) { + threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { for (const int i : range) { copy_attribute_to_points<float>( *tilt_attribute, point_to_vert_maps[i], splines[i]->tilts()); @@ -73,7 +73,7 @@ static void copy_attributes_to_points(CurveEval &curve, if (source_attribute_names.contains_as("radius")) { const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>( "radius", ATTR_DOMAIN_POINT, 1.0f); - parallel_for(splines.index_range(), 256, [&](IndexRange range) { + threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) { for (const int i : range) { copy_attribute_to_points<float>( *radius_attribute, point_to_vert_maps[i], splines[i]->radii()); @@ -97,7 +97,7 @@ static void copy_attributes_to_points(CurveEval &curve, const CustomDataType data_type = bke::cpp_type_to_custom_data_type(mesh_attribute->type()); - parallel_for(splines.index_range(), 128, [&](IndexRange range) { + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { /* Create attribute on the spline points. */ splines[i]->attributes.create(name, data_type); 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 e52ab1b2127..b119b7b31e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -189,7 +189,7 @@ static void add_instances_from_component(InstancesComponent &instances, * (anything except for collection mode with "Whole Collection" turned off). */ if (possible_handles.size() == 1) { const int handle = possible_handles.first(); - parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { for (const int i : range) { handles[i] = handle; transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); @@ -200,7 +200,7 @@ static void add_instances_from_component(InstancesComponent &instances, else { const int seed = params.get_input<int>("Seed"); Array<uint32_t> ids = get_geometry_element_ids_as_uints(src_geometry, ATTR_DOMAIN_POINT); - parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { for (const int i : range) { const int index = BLI_hash_int_2d(ids[i], seed) % possible_handles.size(); const int handle = possible_handles[index]; diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc new file mode 100644 index 00000000000..bfd6027e0fc --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -0,0 +1,321 @@ +/* + * 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 "DNA_mesh_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_raycast_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Target Geometry")}, + {SOCK_STRING, N_("Ray Direction")}, + {SOCK_VECTOR, N_("Ray Direction"), 0.0, 0.0, 1.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Ray Length")}, + {SOCK_FLOAT, N_("Ray Length"), 100.0, 0.0, 0.0, 0.0, 0.0f, FLT_MAX, PROP_DISTANCE}, + {SOCK_STRING, N_("Target Attribute")}, + {SOCK_STRING, N_("Is Hit")}, + {SOCK_STRING, N_("Hit Position")}, + {SOCK_STRING, N_("Hit Normal")}, + {SOCK_STRING, N_("Hit Distance")}, + {SOCK_STRING, N_("Hit Attribute")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_raycast_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_raycast_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); + uiItemR(layout, ptr, "input_type_ray_direction", 0, IFACE_("Ray Direction"), ICON_NONE); + uiItemR(layout, ptr, "input_type_ray_length", 0, IFACE_("Ray Length"), ICON_NONE); +} + +static void geo_node_raycast_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryRaycast *data = (NodeGeometryRaycast *)MEM_callocN(sizeof(NodeGeometryRaycast), + __func__); + data->input_type_ray_direction = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + data->input_type_ray_length = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; +} + +static void geo_node_raycast_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRaycast *node_storage = (NodeGeometryRaycast *)node->storage; + blender::nodes::update_attribute_input_socket_availabilities( + *node, + "Ray Direction", + (GeometryNodeAttributeInputMode)node_storage->input_type_ray_direction); + blender::nodes::update_attribute_input_socket_availabilities( + *node, "Ray Length", (GeometryNodeAttributeInputMode)node_storage->input_type_ray_length); +} + +namespace blender::nodes { + +static void raycast_to_mesh(const Mesh *mesh, + const VArray<float3> &ray_origins, + const VArray<float3> &ray_directions, + const VArray<float> &ray_lengths, + const MutableSpan<bool> r_hit, + const MutableSpan<int> r_hit_indices, + const MutableSpan<float3> r_hit_positions, + const MutableSpan<float3> r_hit_normals, + const MutableSpan<float> r_hit_distances) +{ + BLI_assert(ray_origins.size() == ray_directions.size()); + BLI_assert(ray_origins.size() == ray_lengths.size()); + BLI_assert(ray_origins.size() == r_hit.size() || r_hit.is_empty()); + BLI_assert(ray_origins.size() == r_hit_indices.size() || r_hit_indices.is_empty()); + BLI_assert(ray_origins.size() == r_hit_positions.size() || r_hit_positions.is_empty()); + BLI_assert(ray_origins.size() == r_hit_normals.size() || r_hit_normals.is_empty()); + BLI_assert(ray_origins.size() == r_hit_distances.size() || r_hit_distances.is_empty()); + + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(mesh), BVHTREE_FROM_LOOPTRI, 4); + + if (tree_data.tree != nullptr) { + for (const int i : ray_origins.index_range()) { + const float ray_length = ray_lengths[i]; + const float3 ray_origin = ray_origins[i]; + const float3 ray_direction = ray_directions[i].normalized(); + + BVHTreeRayHit hit; + hit.index = -1; + hit.dist = ray_length; + if (BLI_bvhtree_ray_cast(tree_data.tree, + ray_origin, + ray_direction, + 0.0f, + &hit, + tree_data.raycast_callback, + &tree_data) != -1) { + if (!r_hit.is_empty()) { + r_hit[i] = hit.index >= 0; + } + if (!r_hit_indices.is_empty()) { + /* Index should always be a valid looptri index, use 0 when hit failed. */ + r_hit_indices[i] = max_ii(hit.index, 0); + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = hit.co; + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = hit.no; + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = hit.dist; + } + } + else { + if (!r_hit.is_empty()) { + r_hit[i] = false; + } + if (!r_hit_indices.is_empty()) { + r_hit_indices[i] = 0; + } + if (!r_hit_positions.is_empty()) { + r_hit_positions[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_normals.is_empty()) { + r_hit_normals[i] = float3(0.0f, 0.0f, 0.0f); + } + if (!r_hit_distances.is_empty()) { + r_hit_distances[i] = ray_length; + } + } + } + + free_bvhtree_from_mesh(&tree_data); + } +} + +static bke::mesh_surface_sample::eAttributeMapMode get_map_mode( + GeometryNodeRaycastMapMode map_mode) +{ + switch (map_mode) { + case GEO_NODE_RAYCAST_INTERPOLATED: + return bke::mesh_surface_sample::eAttributeMapMode::INTERPOLATED; + default: + case GEO_NODE_RAYCAST_NEAREST: + return bke::mesh_surface_sample::eAttributeMapMode::NEAREST; + } +} + +static void raycast_from_points(const GeoNodeExecParams ¶ms, + const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const StringRef hit_name, + const StringRef hit_position_name, + const StringRef hit_normal_name, + const StringRef hit_distance_name, + const Span<std::string> hit_attribute_names, + const Span<std::string> hit_attribute_output_names) +{ + BLI_assert(hit_attribute_names.size() == hit_attribute_output_names.size()); + + const MeshComponent *src_mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + if (src_mesh_component == nullptr) { + return; + } + const Mesh *src_mesh = src_mesh_component->get_for_read(); + if (src_mesh == nullptr) { + return; + } + if (src_mesh->totpoly == 0) { + return; + } + + const NodeGeometryRaycast &storage = *(const NodeGeometryRaycast *)params.node().storage; + bke::mesh_surface_sample::eAttributeMapMode map_mode = get_map_mode( + (GeometryNodeRaycastMapMode)storage.mapping); + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + + GVArray_Typed<float3> ray_origins = dst_component.attribute_get_for_read<float3>( + "position", result_domain, {0, 0, 0}); + GVArray_Typed<float3> ray_directions = params.get_input_attribute<float3>( + "Ray Direction", dst_component, result_domain, {0, 0, 0}); + GVArray_Typed<float> ray_lengths = params.get_input_attribute<float>( + "Ray Length", dst_component, result_domain, 0); + + OutputAttribute_Typed<bool> hit_attribute = + dst_component.attribute_try_get_for_output_only<bool>(hit_name, result_domain); + OutputAttribute_Typed<float3> hit_position_attribute = + dst_component.attribute_try_get_for_output_only<float3>(hit_position_name, result_domain); + OutputAttribute_Typed<float3> hit_normal_attribute = + dst_component.attribute_try_get_for_output_only<float3>(hit_normal_name, result_domain); + OutputAttribute_Typed<float> hit_distance_attribute = + dst_component.attribute_try_get_for_output_only<float>(hit_distance_name, result_domain); + + /* Positions and looptri indices are always needed for interpolation, + * so create temporary arrays if no output attribute is given. + */ + Array<int> hit_indices; + Array<float3> hit_positions_internal; + if (!hit_attribute_names.is_empty()) { + hit_indices.reinitialize(ray_origins->size()); + + if (!hit_position_attribute) { + hit_positions_internal.reinitialize(ray_origins->size()); + } + } + const MutableSpan<bool> is_hit = hit_attribute ? hit_attribute.as_span() : MutableSpan<bool>(); + const MutableSpan<float3> hit_positions = hit_position_attribute ? + hit_position_attribute.as_span() : + hit_positions_internal; + const MutableSpan<float3> hit_normals = hit_normal_attribute ? hit_normal_attribute.as_span() : + MutableSpan<float3>(); + const MutableSpan<float> hit_distances = hit_distance_attribute ? + hit_distance_attribute.as_span() : + MutableSpan<float>(); + + raycast_to_mesh(src_mesh, + ray_origins, + ray_directions, + ray_lengths, + is_hit, + hit_indices, + hit_positions, + hit_normals, + hit_distances); + + hit_attribute.save(); + hit_position_attribute.save(); + hit_normal_attribute.save(); + hit_distance_attribute.save(); + + /* Custom interpolated attributes */ + bke::mesh_surface_sample::MeshAttributeInterpolator interp(src_mesh, hit_positions, hit_indices); + for (const int i : hit_attribute_names.index_range()) { + const std::optional<AttributeMetaData> meta_data = src_mesh_component->attribute_get_meta_data( + hit_attribute_names[i]); + if (meta_data) { + ReadAttributeLookup hit_attribute = src_mesh_component->attribute_try_get_for_read( + hit_attribute_names[i]); + OutputAttribute hit_attribute_output = dst_component.attribute_try_get_for_output_only( + hit_attribute_output_names[i], result_domain, meta_data->data_type); + + interp.sample_attribute(hit_attribute, hit_attribute_output, map_mode); + + hit_attribute_output.save(); + } + } +} + +static void geo_node_raycast_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet cast_geometry_set = params.extract_input<GeometrySet>("Target Geometry"); + + const std::string hit_name = params.extract_input<std::string>("Is Hit"); + const std::string hit_position_name = params.extract_input<std::string>("Hit Position"); + const std::string hit_normal_name = params.extract_input<std::string>("Hit Normal"); + const std::string hit_distance_name = params.extract_input<std::string>("Hit Distance"); + + const Array<std::string> hit_attribute_names = { + params.extract_input<std::string>("Target Attribute")}; + const Array<std::string> hit_attribute_output_names = { + params.extract_input<std::string>("Hit Attribute")}; + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + cast_geometry_set = bke::geometry_set_realize_instances(cast_geometry_set); + + static const Array<GeometryComponentType> SupportedTypes = { + GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; + for (GeometryComponentType geo_type : SupportedTypes) { + if (geometry_set.has(geo_type)) { + raycast_from_points(params, + cast_geometry_set, + geometry_set.get_component_for_write(geo_type), + hit_name, + hit_position_name, + hit_normal_name, + hit_distance_name, + hit_attribute_names, + hit_attribute_output_names); + } + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_raycast() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_raycast_in, geo_node_raycast_out); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_init(&ntype, geo_node_raycast_init); + node_type_update(&ntype, geo_node_raycast_update); + node_type_storage( + &ntype, "NodeGeometryRaycast", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_raycast_exec; + ntype.draw_buttons = geo_node_raycast_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc b/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc index 9bc963eec43..ee114741a77 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_select_by_material.cc @@ -60,7 +60,7 @@ static void select_mesh_by_material(const Mesh &mesh, material_indices.append(i); } } - parallel_for(r_selection.index_range(), 1024, [&](IndexRange range) { + threading::parallel_for(r_selection.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { r_selection[i] = material_indices.contains(mesh.mpoly[i].mat_nr); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc new file mode 100644 index 00000000000..bdc3e56783c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc @@ -0,0 +1,77 @@ +/* + * 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" + +static bNodeSocketTemplate geo_node_join_geometry_in[]{ + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_join_geometry_out[]{ + {SOCK_GEOMETRY, N_("Mesh")}, + {SOCK_GEOMETRY, N_("Point Cloud")}, + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_GEOMETRY, N_("Volume")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void geo_node_separate_components_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + /* Note that it will be possible to skip realizing instances here when instancing + * geometry directly is supported by creating corresponding geometry instances. */ + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + GeometrySet meshes; + GeometrySet point_clouds; + GeometrySet volumes; + GeometrySet curves; + + if (geometry_set.has<MeshComponent>()) { + meshes.add(*geometry_set.get_component_for_read<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + point_clouds.add(*geometry_set.get_component_for_read<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + curves.add(*geometry_set.get_component_for_read<CurveComponent>()); + } + if (geometry_set.has<VolumeComponent>()) { + volumes.add(*geometry_set.get_component_for_read<VolumeComponent>()); + } + + params.set_output("Mesh", meshes); + params.set_output("Point Cloud", point_clouds); + params.set_output("Curve", curves); + params.set_output("Volume", volumes); +} + +} // namespace blender::nodes + +void register_node_type_geo_separate_components() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SEPARATE_COMPONENTS, "Separate Components", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out); + ntype.geometry_node_execute = blender::nodes::geo_node_separate_components_exec; + nodeRegisterType(&ntype); +} |