From 21adf2ec89aebf3f73a19331508a40de57c3cfc8 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 18 Nov 2022 13:38:34 -0600 Subject: [PATCH] Cleanup: Split UV sample geometry node into two functions This separates the UV reverse sampling and the barycentric mixing of the mesh attribute into separate multi-functions. This separates concerns and allows for future de-duplication of the UV sampling function if that is implemented as an optimization pass. That would be helpful since it's the much more expensive operation. This was simplified by returning the triangle index in the reverse UV sampler rather than a pointer to the triangle, which required passing a span of triangles separately in a few places. --- .../editors/curves/intern/curves_ops.cc | 2 +- .../editors/sculpt_paint/curves_sculpt_add.cc | 1 + .../sculpt_paint/curves_sculpt_density.cc | 1 + .../sculpt_paint/curves_sculpt_slide.cc | 9 +- .../geometry/GEO_add_curves_on_mesh.hh | 1 + .../geometry/GEO_reverse_uv_sampler.hh | 2 +- .../geometry/intern/add_curves_on_mesh.cc | 6 +- .../geometry/intern/reverse_uv_sampler.cc | 4 +- .../node_geo_deform_curves_on_surface.cc | 6 +- .../nodes/node_geo_sample_uv_surface.cc | 157 +++++++++++++----- 10 files changed, 134 insertions(+), 55 deletions(-) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 1d2b1264477..5673c6876ed 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -635,7 +635,7 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, continue; } - const MLoopTri &looptri = *lookup_result.looptri; + const MLoopTri &looptri = surface_looptris[lookup_result.looptri_index]; const float3 &bary_coords = lookup_result.bary_weights; const float3 &p0_su = verts[loops[looptri.tri[0]].v].co; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 0d2c2d3f0c9..fa9cf3d3a04 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -228,6 +228,7 @@ struct AddOperationExecutor { add_inputs.fallback_curve_length = brush_settings_->curve_length; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); add_inputs.transforms = &transforms_; + add_inputs.surface_looptris = surface_looptris_orig; add_inputs.reverse_uv_sampler = &reverse_uv_sampler; add_inputs.surface = &surface_orig; add_inputs.corner_normals_su = corner_normals_su; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index 78e2d55e6b9..78eea553737 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -280,6 +280,7 @@ struct DensityAddOperationExecutor { add_inputs.transforms = &transforms_; add_inputs.surface = surface_orig_; add_inputs.corner_normals_su = corner_normals_su; + add_inputs.surface_looptris = surface_looptris_orig; add_inputs.reverse_uv_sampler = &reverse_uv_sampler; add_inputs.old_roots_kdtree = self_->original_curve_roots_kdtree_; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc index ae89bc1c58b..89110f72e4b 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -292,8 +292,9 @@ struct SlideOperationExecutor { /* Compute the normal at the initial surface position. */ const float3 normal_cu = math::normalize( transforms_.surface_to_curves_normal * - geometry::compute_surface_point_normal( - *result.looptri, result.bary_weights, corner_normals_orig_su_)); + geometry::compute_surface_point_normal(surface_looptris_orig_[result.looptri_index], + result.bary_weights, + corner_normals_orig_su_)); r_curves_to_slide.append({curve_i, radius_falloff, normal_cu}); } @@ -389,7 +390,7 @@ struct SlideOperationExecutor { found_invalid_uv_mapping_.store(true); continue; } - const MLoopTri &looptri_orig = *result.looptri; + const MLoopTri &looptri_orig = surface_looptris_orig_[result.looptri_index]; const float3 &bary_weights_orig = result.bary_weights; /* Gather old and new surface normal. */ @@ -397,7 +398,7 @@ struct SlideOperationExecutor { const float3 new_normal_cu = math::normalize( transforms_.surface_to_curves_normal * geometry::compute_surface_point_normal( - *result.looptri, result.bary_weights, corner_normals_orig_su_)); + looptri_orig, result.bary_weights, corner_normals_orig_su_)); /* Gather old and new surface position. */ const float3 old_first_pos_orig_cu = self_->initial_positions_cu_[first_point_i]; diff --git a/source/blender/geometry/GEO_add_curves_on_mesh.hh b/source/blender/geometry/GEO_add_curves_on_mesh.hh index 2d2ba89a18f..d0f72deeb46 100644 --- a/source/blender/geometry/GEO_add_curves_on_mesh.hh +++ b/source/blender/geometry/GEO_add_curves_on_mesh.hh @@ -30,6 +30,7 @@ struct AddCurvesOnMeshInputs { /** Information about the surface that the new curves are attached to. */ const Mesh *surface = nullptr; + Span surface_looptris; const ReverseUVSampler *reverse_uv_sampler = nullptr; Span corner_normals_su; diff --git a/source/blender/geometry/GEO_reverse_uv_sampler.hh b/source/blender/geometry/GEO_reverse_uv_sampler.hh index ee91e0b0731..62c6382c9d8 100644 --- a/source/blender/geometry/GEO_reverse_uv_sampler.hh +++ b/source/blender/geometry/GEO_reverse_uv_sampler.hh @@ -35,7 +35,7 @@ class ReverseUVSampler { struct Result { ResultType type = ResultType::None; - const MLoopTri *looptri = nullptr; + int looptri_index = -1; float3 bary_weights; }; diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index a03c9b994a9..1cc13a40fc4 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -140,6 +140,7 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const Span new_lengths_cu, const Span new_normals_su, const bke::CurvesSurfaceTransforms &transforms, + const Span looptris, const ReverseUVSampler &reverse_uv_sampler, const Span corner_normals_su) { @@ -178,7 +179,7 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, } const float3 neighbor_normal_su = compute_surface_point_normal( - *result.looptri, result.bary_weights, corner_normals_su); + looptris[result.looptri_index], result.bary_weights, corner_normals_su); const float3 neighbor_normal_cu = math::normalize(transforms.surface_to_curves_normal * neighbor_normal_su); @@ -254,7 +255,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, outputs.uv_error = true; continue; } - const MLoopTri &looptri = *result.looptri; + const MLoopTri &looptri = inputs.surface_looptris[result.looptri_index]; bary_coords.append(result.bary_weights); looptris.append(&looptri); const float3 root_position_su = attribute_math::mix3( @@ -358,6 +359,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, new_lengths_cu, new_normals_su, *inputs.transforms, + inputs.surface_looptris, *inputs.reverse_uv_sampler, inputs.corner_normals_su); } diff --git a/source/blender/geometry/intern/reverse_uv_sampler.cc b/source/blender/geometry/intern/reverse_uv_sampler.cc index f66e4a3ac2e..d69935a8fe3 100644 --- a/source/blender/geometry/intern/reverse_uv_sampler.cc +++ b/source/blender/geometry/intern/reverse_uv_sampler.cc @@ -48,7 +48,7 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const float best_dist = FLT_MAX; float3 best_bary_weights; - const MLoopTri *best_looptri; + int best_looptri; /* The distance to an edge that is allowed to be inside or outside the triangle. Without this, * the lookup can fail for floating point accuracy reasons when the uv is almost exact on an @@ -84,7 +84,7 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const if (dist < best_dist) { best_dist = dist; best_bary_weights = bary_weights; - best_looptri = &looptri; + best_looptri = looptri_index; } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index dabd2a1a9f2..d926e7e4e30 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -68,9 +68,11 @@ static void deform_curves(const CurvesGeometry &curves, const Span surface_verts_old = surface_mesh_old.verts(); const Span surface_loops_old = surface_mesh_old.loops(); + const Span surface_looptris_old = surface_mesh_old.looptris(); const Span surface_verts_new = surface_mesh_new.verts(); const Span surface_loops_new = surface_mesh_new.loops(); + const Span surface_looptris_new = surface_mesh_new.looptris(); threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { for (const int curve_i : range) { @@ -85,8 +87,8 @@ static void deform_curves(const CurvesGeometry &curves, continue; } - const MLoopTri &looptri_old = *surface_sample_old.looptri; - const MLoopTri &looptri_new = *surface_sample_new.looptri; + const MLoopTri &looptri_old = surface_looptris_old[surface_sample_old.looptri_index]; + const MLoopTri &looptri_new = surface_looptris_new[surface_sample_new.looptri_index]; const float3 &bary_weights_old = surface_sample_old.bary_weights; const float3 &bary_weights_new = surface_sample_new.bary_weights; diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc index 0a6dd569395..0e6e68036d9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc @@ -105,9 +105,8 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) } } -class SampleUVSurfaceFunction : public fn::MultiFunction { +class SampleMeshBarycentricFunction : public fn::MultiFunction { GeometrySet source_; - Field src_uv_map_field_; GField src_field_; /** @@ -122,60 +121,50 @@ class SampleUVSurfaceFunction : public fn::MultiFunction { std::optional source_context_; std::unique_ptr source_evaluator_; const GVArray *source_data_; - VArraySpan source_uv_map_; - std::optional reverse_uv_sampler_; + Span looptris_; public: - SampleUVSurfaceFunction(GeometrySet geometry, Field src_uv_map_field, GField src_field) - : source_(std::move(geometry)), - src_uv_map_field_(std::move(src_uv_map_field)), - src_field_(std::move(src_field)) + SampleMeshBarycentricFunction(GeometrySet geometry, GField src_field) + : source_(std::move(geometry)), src_field_(std::move(src_field)) { source_.ensure_owns_direct_data(); - signature_ = this->create_signature(); this->set_signature(&signature_); + signature_ = this->create_signature(); this->evaluate_source(); } fn::MFSignature create_signature() { - blender::fn::MFSignatureBuilder signature{"Sample UV Surface"}; - signature.single_input("Sample UV"); + fn::MFSignatureBuilder signature{"Sample Barycentric Triangles"}; + signature.single_input("Triangle Index"); + signature.single_input("Barycentric Weight"); signature.single_output("Value", src_field_.cpp_type()); - signature.single_output("Is Valid"); return signature.build(); } void call(IndexMask mask, fn::MFParams params, fn::MFContext /*context*/) const override { - const VArray &sample_uvs = params.readonly_single_input(0, "Sample UV"); - GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Value"); - MutableSpan valid_dst = params.uninitialized_single_output_if_required(2, - "Is Valid"); + const VArraySpan triangle_indices = params.readonly_single_input(0, + "Triangle Index"); + const VArraySpan bary_weights = params.readonly_single_input( + 1, "Barycentric Weight"); + GMutableSpan dst = params.uninitialized_single_output(2, "Value"); - const CPPType &type = src_field_.cpp_type(); - attribute_math::convert_to_static_type(type, [&](auto dummy) { + attribute_math::convert_to_static_type(src_field_.cpp_type(), [&](auto dummy) { using T = decltype(dummy); const VArray src_typed = source_data_->typed(); MutableSpan dst_typed = dst.typed(); for (const int i : mask) { - const float2 sample_uv = sample_uvs[i]; - const ReverseUVSampler::Result result = reverse_uv_sampler_->sample(sample_uv); - const bool valid = result.type == ReverseUVSampler::ResultType::Ok; - if (!dst_typed.is_empty()) { - if (valid) { - dst_typed[i] = attribute_math::mix3(result.bary_weights, - src_typed[result.looptri->tri[0]], - src_typed[result.looptri->tri[1]], - src_typed[result.looptri->tri[2]]); - } - else { - dst_typed[i] = {}; - } + const int triangle_index = triangle_indices[i]; + if (triangle_indices[i] != -1) { + dst_typed[i] = attribute_math::mix3(bary_weights[i], + src_typed[looptris_[triangle_index].tri[0]], + src_typed[looptris_[triangle_index].tri[1]], + src_typed[looptris_[triangle_index].tri[2]]); } - if (!valid_dst.is_empty()) { - valid_dst[i] = valid; + else { + dst_typed[i] = {}; } } }); @@ -185,14 +174,91 @@ class SampleUVSurfaceFunction : public fn::MultiFunction { void evaluate_source() { const Mesh &mesh = *source_.get_mesh_for_read(); + looptris_ = mesh.looptris(); source_context_.emplace(bke::MeshFieldContext{mesh, domain_}); const int domain_size = mesh.attributes().domain_size(domain_); source_evaluator_ = std::make_unique(*source_context_, domain_size); - source_evaluator_->add(src_uv_map_field_); source_evaluator_->add(src_field_); source_evaluator_->evaluate(); + source_data_ = &source_evaluator_->get_evaluated(0); + } +}; + +class ReverseUVSampleFunction : public fn::MultiFunction { + GeometrySet source_; + Field src_uv_map_field_; + + std::optional source_context_; + std::unique_ptr source_evaluator_; + VArraySpan source_uv_map_; + + std::optional reverse_uv_sampler_; + + public: + ReverseUVSampleFunction(GeometrySet geometry, Field src_uv_map_field) + : source_(std::move(geometry)), src_uv_map_field_(std::move(src_uv_map_field)) + { + source_.ensure_owns_direct_data(); + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + this->evaluate_source(); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Sample UV Surface"}; + signature.single_input("Sample UV"); + signature.single_output("Is Valid"); + signature.single_output("Triangle Index"); + signature.single_output("Barycentric Weights"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext /*context*/) const override + { + const VArraySpan sample_uvs = params.readonly_single_input(0, "Sample UV"); + MutableSpan is_valid = params.uninitialized_single_output_if_required(1, + "Is Valid"); + MutableSpan tri_index = params.uninitialized_single_output_if_required( + 2, "Triangle Index"); + MutableSpan bary_weights = params.uninitialized_single_output_if_required( + 3, "Barycentric Weights"); + + Array results(mask.min_array_size()); + reverse_uv_sampler_->sample_many(sample_uvs, results); + + if (!is_valid.is_empty()) { + std::transform(results.begin(), + results.end(), + is_valid.begin(), + [](const ReverseUVSampler::Result &result) { + return result.type == ReverseUVSampler::ResultType::Ok; + }); + } + if (!tri_index.is_empty()) { + std::transform(results.begin(), + results.end(), + tri_index.begin(), + [](const ReverseUVSampler::Result &result) { return result.looptri_index; }); + } + + if (!bary_weights.is_empty()) { + std::transform(results.begin(), + results.end(), + bary_weights.begin(), + [](const ReverseUVSampler::Result &result) { return result.bary_weights; }); + } + } + + private: + void evaluate_source() + { + const Mesh &mesh = *source_.get_mesh_for_read(); + source_context_.emplace(bke::MeshFieldContext{mesh, ATTR_DOMAIN_CORNER}); + source_evaluator_ = std::make_unique(*source_context_, mesh.totloop); + source_evaluator_->add(src_uv_map_field_); + source_evaluator_->evaluate(); source_uv_map_ = source_evaluator_->get_evaluated(0); - source_data_ = &source_evaluator_->get_evaluated(1); reverse_uv_sampler_.emplace(source_uv_map_, mesh.looptris()); } @@ -260,19 +326,24 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - const CPPType &float2_type = CPPType::get(); - + /* Do reverse sampling of the UV map first. */ const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + const CPPType &float2_type = CPPType::get(); Field source_uv_map = conversions.try_convert( params.extract_input>("Source UV Map"), float2_type); - GField field = get_input_attribute_field(params, data_type); Field sample_uvs = conversions.try_convert( params.extract_input>("Sample UV"), float2_type); - auto fn = std::make_shared( - std::move(geometry), std::move(source_uv_map), std::move(field)); - auto op = FieldOperation::Create(std::move(fn), {std::move(sample_uvs)}); - output_attribute_field(params, GField(op, 0)); - params.set_output("Is Valid", Field(op, 1)); + auto uv_op = FieldOperation::Create( + std::make_shared(geometry, std::move(source_uv_map)), + {std::move(sample_uvs)}); + params.set_output("Is Valid", Field(uv_op, 0)); + + /* Use the output of the UV sampling to interpolate the mesh attribute. */ + GField field = get_input_attribute_field(params, data_type); + auto sample_op = FieldOperation::Create( + std::make_shared(std::move(geometry), std::move(field)), + {Field(uv_op, 1), Field(uv_op, 2)}); + output_attribute_field(params, GField(sample_op, 0)); } } // namespace blender::nodes::node_geo_sample_uv_surface_cc