diff options
author | Jacques Lucke <jacques@blender.org> | 2022-06-05 13:14:13 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-06-05 13:14:32 +0300 |
commit | 899ec8b6b8035bde44d49d228c30698499a87080 (patch) | |
tree | 1c0acb436e033eb5551531774ffb0ff9a57dd268 /source/blender/editors/curves | |
parent | 176d7bcc2eb47b9820861037f90a7fb26de8c9a0 (diff) |
Curves: use uv coordinates to attach curves to mesh
This implements the new way to attach curves to a mesh surface using
a uv map (based on the recent discussion in T95776).
The curves data block now not only stores a reference to the surface object
but also a name of a uv map on that object. Having a uv map is optional
for most operations, but it will be required later for animation (when the
curves are supposed to be deformed based on deformation of the surface).
The "Empty Hair" operator in the Add menu sets the uv map name automatically
if possible. It's possible to start working without a uv map and to attach the
curves to a uv map later on. It's also possible to reattach the curves to a new
uv map using the "Curves > Snap to Nearest Surface" operator in curves sculpt
mode.
Note, the implementation to do the reverse lookup from uv to a position on the
surface is trivial and inefficient now. A more efficient data structure will be
implemented separately soon.
Differential Revision: https://developer.blender.org/D15125
Diffstat (limited to 'source/blender/editors/curves')
-rw-r--r-- | source/blender/editors/curves/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/curves/intern/curves_ops.cc | 110 |
2 files changed, 70 insertions, 41 deletions
diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index a5d8390e7f2..3c31e8014ff 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../../blentranslation ../../depsgraph ../../functions + ../../geometry ../../makesdna ../../makesrna ../../windowmanager diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 85214e1608d..54f0a0208fb 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -17,6 +17,7 @@ #include "WM_api.h" +#include "BKE_attribute_math.hh" #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" @@ -45,6 +46,8 @@ #include "RNA_enum_types.h" #include "RNA_prototypes.h" +#include "GEO_reverse_uv_sampler.hh" + /** * The code below uses a suffix naming convention to indicate the coordinate space: * `cu`: Local space of the curves object that is being edited. @@ -184,25 +187,20 @@ static void try_convert_single_object(Object &curves_ob, } Mesh &surface_me = *static_cast<Mesh *>(surface_ob.data); + BVHTreeFromMesh surface_bvh; + BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_me, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + const Span<float3> positions_cu = curves.positions(); - const VArray<int> looptri_indices = curves.surface_triangle_indices(); const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&surface_me), BKE_mesh_runtime_looptri_len(&surface_me)}; - /* Find indices of curves that can be transferred to the old hair system. */ - Vector<int> curves_indices_to_transfer; - for (const int curve_i : curves.curves_range()) { - const int looptri_i = looptri_indices[curve_i]; - if (looptri_i >= 0 && looptri_i < looptris.size()) { - curves_indices_to_transfer.append(curve_i); - } - else { - *r_could_not_convert_some_curves = true; - } + if (looptris.is_empty()) { + *r_could_not_convert_some_curves = true; } - const int hairs_num = curves_indices_to_transfer.size(); - if (hairs_num == 0) { + const int hair_num = curves.curves_num(); + if (hair_num == 0) { return; } @@ -228,8 +226,8 @@ static void try_convert_single_object(Object &curves_ob, psys_changed_type(&surface_ob, particle_system); MutableSpan<ParticleData> particles{ - static_cast<ParticleData *>(MEM_calloc_arrayN(hairs_num, sizeof(ParticleData), __func__)), - hairs_num}; + static_cast<ParticleData *>(MEM_calloc_arrayN(hair_num, sizeof(ParticleData), __func__)), + hair_num}; /* The old hair system still uses #MFace, so make sure those are available on the mesh. */ BKE_mesh_tessface_calc(&surface_me); @@ -250,17 +248,23 @@ static void try_convert_single_object(Object &curves_ob, const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); const float4x4 curves_to_surface_mat = world_to_surface_mat * curves_to_world_mat; - for (const int new_hair_i : curves_indices_to_transfer.index_range()) { - const int curve_i = curves_indices_to_transfer[new_hair_i]; + for (const int new_hair_i : IndexRange(hair_num)) { + const int curve_i = new_hair_i; const IndexRange points = curves.points_for_curve(curve_i); - const int looptri_i = looptri_indices[curve_i]; - const MLoopTri &looptri = looptris[looptri_i]; - const int poly_i = looptri.poly; - const float3 &root_pos_cu = positions_cu[points.first()]; const float3 root_pos_su = curves_to_surface_mat * root_pos_cu; + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest( + surface_bvh.tree, root_pos_su, &nearest, surface_bvh.nearest_callback, &surface_bvh); + BLI_assert(nearest.index >= 0); + + const int looptri_i = nearest.index; + const MLoopTri &looptri = looptris[looptri_i]; + const int poly_i = looptri.poly; + const int mface_i = find_mface_for_root_position( surface_me, poly_to_mface_map[poly_i], root_pos_su); const MFace &mface = surface_me.mface[mface_i]; @@ -520,7 +524,7 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) { const AttachMode attach_mode = static_cast<AttachMode>(RNA_enum_get(op->ptr, "attach_mode")); - std::atomic<bool> found_invalid_looptri_index = false; + std::atomic<bool> found_invalid_uv = false; CTX_DATA_BEGIN (C, Object *, curves_ob, selected_objects) { if (curves_ob->type != OB_CURVES) { @@ -537,9 +541,19 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) } Mesh &surface_mesh = *static_cast<Mesh *>(surface_ob.data); + MeshComponent surface_mesh_component; + surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly); + + VArray_Span<float2> surface_uv_map; + if (curves_id.surface_uv_map != nullptr) { + surface_uv_map = surface_mesh_component + .attribute_try_get_for_read( + curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2) + .typed<float2>(); + } + MutableSpan<float3> positions_cu = curves.positions_for_write(); - MutableSpan<int> surface_triangle_indices = curves.surface_triangle_indices_for_write(); - MutableSpan<float2> surface_triangle_coords = curves.surface_triangle_coords_for_write(); + MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write(); const Span<MLoopTri> surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh), BKE_mesh_runtime_looptri_len(&surface_mesh)}; @@ -585,36 +599,50 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) pos_cu += pos_diff_cu; } - surface_triangle_indices[curve_i] = looptri_index; - - const MLoopTri &looptri = surface_looptris[looptri_index]; - const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co; - const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co; - const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co; - float3 bary_coords; - interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su); - surface_triangle_coords[curve_i] = bke::curves::encode_surface_bary_coord(bary_coords); + if (!surface_uv_map.is_empty()) { + const MLoopTri &looptri = surface_looptris[looptri_index]; + const int corner0 = looptri.tri[0]; + const int corner1 = looptri.tri[1]; + const int corner2 = looptri.tri[2]; + const float2 &uv0 = surface_uv_map[corner0]; + const float2 &uv1 = surface_uv_map[corner1]; + const float2 &uv2 = surface_uv_map[corner2]; + const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[corner0].v].co; + const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[corner1].v].co; + const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[corner2].v].co; + float3 bary_coords; + interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su); + const float2 uv = attribute_math::mix3(bary_coords, uv0, uv1, uv2); + surface_uv_coords[curve_i] = uv; + } } }); break; } case AttachMode::Deform: { + if (!surface_uv_map.is_empty()) { + BKE_report(op->reports, + RPT_ERROR, + "Curves do not have attachment information that can be used for deformation"); + } + using geometry::ReverseUVSampler; + ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris}; + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { for (const int curve_i : curves_range) { const IndexRange points = curves.points_for_curve(curve_i); const int first_point_i = points.first(); const float3 old_first_point_pos_cu = positions_cu[first_point_i]; - const int looptri_index = surface_triangle_indices[curve_i]; - if (!surface_looptris.index_range().contains(looptri_index)) { - found_invalid_looptri_index = true; + const float2 uv = surface_uv_coords[curve_i]; + ReverseUVSampler::Result lookup_result = reverse_uv_sampler.sample(uv); + if (lookup_result.type != ReverseUVSampler::ResultType::Ok) { + found_invalid_uv = true; continue; } - const MLoopTri &looptri = surface_looptris[looptri_index]; - - const float3 bary_coords = bke::curves::decode_surface_bary_coord( - surface_triangle_coords[curve_i]); + const MLoopTri &looptri = *lookup_result.looptri; + const float3 &bary_coords = lookup_result.bary_weights; const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co; const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co; @@ -638,7 +666,7 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - if (found_invalid_looptri_index) { + if (found_invalid_uv) { BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface"); } |