Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/curves/intern/curves_ops.cc
| Show All 11 Lines | |||||
| #include "ED_curves.h" | #include "ED_curves.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "ED_select_utils.h" | #include "ED_select_utils.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "BKE_attribute_math.hh" | |||||
| #include "BKE_bvhutils.h" | #include "BKE_bvhutils.h" | ||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_curves.hh" | #include "BKE_curves.hh" | ||||
| #include "BKE_geometry_set.hh" | #include "BKE_geometry_set.hh" | ||||
| #include "BKE_layer.h" | #include "BKE_layer.h" | ||||
| #include "BKE_lib_id.h" | #include "BKE_lib_id.h" | ||||
| #include "BKE_mesh.h" | #include "BKE_mesh.h" | ||||
| #include "BKE_mesh_runtime.h" | #include "BKE_mesh_runtime.h" | ||||
| Show All 12 Lines | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_query.h" | #include "DEG_depsgraph_query.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "RNA_prototypes.h" | #include "RNA_prototypes.h" | ||||
| #include "GEO_reverse_uv_lookup.hh" | |||||
| /** | /** | ||||
| * The code below uses a suffix naming convention to indicate the coordinate space: | * The code below uses a suffix naming convention to indicate the coordinate space: | ||||
| * `cu`: Local space of the curves object that is being edited. | * `cu`: Local space of the curves object that is being edited. | ||||
| * `su`: Local space of the surface object. | * `su`: Local space of the surface object. | ||||
| * `wo`: World space. | * `wo`: World space. | ||||
| * `ha`: Local space of an individual hair in the legacy hair system. | * `ha`: Local space of an individual hair in the legacy hair system. | ||||
| */ | */ | ||||
| ▲ Show 20 Lines • Show All 123 Lines • ▼ Show 20 Lines | if (curves_id.surface == nullptr) { | ||||
| return; | return; | ||||
| } | } | ||||
| Object &surface_ob = *curves_id.surface; | Object &surface_ob = *curves_id.surface; | ||||
| if (surface_ob.type != OB_MESH) { | if (surface_ob.type != OB_MESH) { | ||||
| return; | return; | ||||
| } | } | ||||
| Mesh &surface_me = *static_cast<Mesh *>(surface_ob.data); | 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 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), | const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&surface_me), | ||||
| BKE_mesh_runtime_looptri_len(&surface_me)}; | BKE_mesh_runtime_looptri_len(&surface_me)}; | ||||
| /* Find indices of curves that can be transferred to the old hair system. */ | if (looptris.is_empty()) { | ||||
| 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; | *r_could_not_convert_some_curves = true; | ||||
| } | } | ||||
| } | |||||
| const int hairs_num = curves_indices_to_transfer.size(); | const int hair_num = curves.curves_num(); | ||||
| if (hairs_num == 0) { | if (hair_num == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| ParticleSystem *particle_system = nullptr; | ParticleSystem *particle_system = nullptr; | ||||
| LISTBASE_FOREACH (ParticleSystem *, psys, &surface_ob.particlesystem) { | LISTBASE_FOREACH (ParticleSystem *, psys, &surface_ob.particlesystem) { | ||||
| if (STREQ(psys->name, curves_ob.id.name + 2)) { | if (STREQ(psys->name, curves_ob.id.name + 2)) { | ||||
| particle_system = psys; | particle_system = psys; | ||||
| break; | break; | ||||
| Show All 9 Lines | static void try_convert_single_object(Object &curves_ob, | ||||
| ParticleSettings &settings = *particle_system->part; | ParticleSettings &settings = *particle_system->part; | ||||
| psys_free_particles(particle_system); | psys_free_particles(particle_system); | ||||
| settings.type = PART_HAIR; | settings.type = PART_HAIR; | ||||
| settings.totpart = 0; | settings.totpart = 0; | ||||
| psys_changed_type(&surface_ob, particle_system); | psys_changed_type(&surface_ob, particle_system); | ||||
| MutableSpan<ParticleData> particles{ | MutableSpan<ParticleData> particles{ | ||||
| static_cast<ParticleData *>(MEM_calloc_arrayN(hairs_num, sizeof(ParticleData), __func__)), | static_cast<ParticleData *>(MEM_calloc_arrayN(hair_num, sizeof(ParticleData), __func__)), | ||||
| hairs_num}; | hair_num}; | ||||
| /* The old hair system still uses #MFace, so make sure those are available on the mesh. */ | /* The old hair system still uses #MFace, so make sure those are available on the mesh. */ | ||||
| BKE_mesh_tessface_calc(&surface_me); | BKE_mesh_tessface_calc(&surface_me); | ||||
| /* Prepare utility data structure to map hair roots to #MFace's. */ | /* Prepare utility data structure to map hair roots to #MFace's. */ | ||||
| const Span<int> mface_to_poly_map{ | const Span<int> mface_to_poly_map{ | ||||
| static_cast<const int *>(CustomData_get_layer(&surface_me.fdata, CD_ORIGINDEX)), | static_cast<const int *>(CustomData_get_layer(&surface_me.fdata, CD_ORIGINDEX)), | ||||
| surface_me.totface}; | surface_me.totface}; | ||||
| Array<Vector<int>> poly_to_mface_map(surface_me.totpoly); | Array<Vector<int>> poly_to_mface_map(surface_me.totpoly); | ||||
| for (const int mface_i : mface_to_poly_map.index_range()) { | for (const int mface_i : mface_to_poly_map.index_range()) { | ||||
| const int poly_i = mface_to_poly_map[mface_i]; | const int poly_i = mface_to_poly_map[mface_i]; | ||||
| poly_to_mface_map[poly_i].append(mface_i); | poly_to_mface_map[poly_i].append(mface_i); | ||||
| } | } | ||||
| /* Prepare transformation matrices. */ | /* Prepare transformation matrices. */ | ||||
| const float4x4 curves_to_world_mat = curves_ob.obmat; | const float4x4 curves_to_world_mat = curves_ob.obmat; | ||||
| const float4x4 surface_to_world_mat = surface_ob.obmat; | const float4x4 surface_to_world_mat = surface_ob.obmat; | ||||
| const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); | 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; | 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()) { | for (const int new_hair_i : IndexRange(hair_num)) { | ||||
| const int curve_i = curves_indices_to_transfer[new_hair_i]; | const int curve_i = new_hair_i; | ||||
| const IndexRange points = curves.points_for_curve(curve_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_cu = positions_cu[points.first()]; | ||||
| const float3 root_pos_su = curves_to_surface_mat * root_pos_cu; | 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( | const int mface_i = find_mface_for_root_position( | ||||
| surface_me, poly_to_mface_map[poly_i], root_pos_su); | surface_me, poly_to_mface_map[poly_i], root_pos_su); | ||||
| const MFace &mface = surface_me.mface[mface_i]; | const MFace &mface = surface_me.mface[mface_i]; | ||||
| const float4 mface_weights = compute_mface_weights_for_position( | const float4 mface_weights = compute_mface_weights_for_position( | ||||
| surface_me, mface, root_pos_su); | surface_me, mface, root_pos_su); | ||||
| ParticleData &particle = particles[new_hair_i]; | ParticleData &particle = particles[new_hair_i]; | ||||
| ▲ Show 20 Lines • Show All 243 Lines • ▼ Show 20 Lines | static bool snap_curves_to_surface_poll(bContext *C) | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) | 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")); | 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) { | CTX_DATA_BEGIN (C, Object *, curves_ob, selected_objects) { | ||||
| if (curves_ob->type != OB_CURVES) { | if (curves_ob->type != OB_CURVES) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| Curves &curves_id = *static_cast<Curves *>(curves_ob->data); | Curves &curves_id = *static_cast<Curves *>(curves_ob->data); | ||||
| CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); | CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); | ||||
| if (curves_id.surface == nullptr) { | if (curves_id.surface == nullptr) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| Object &surface_ob = *curves_id.surface; | Object &surface_ob = *curves_id.surface; | ||||
| if (surface_ob.type != OB_MESH) { | if (surface_ob.type != OB_MESH) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| Mesh &surface_mesh = *static_cast<Mesh *>(surface_ob.data); | Mesh &surface_mesh = *static_cast<Mesh *>(surface_ob.data); | ||||
| MeshComponent surface_mesh_component; | |||||
| surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly); | |||||
| bool use_uvs = false; | |||||
| 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>(); | |||||
| if (!surface_uv_map.is_empty()) { | |||||
| use_uvs = true; | |||||
| } | |||||
| } | |||||
HooglyBoogly: I could imagine this looking a bit simpler and being more reusable if it was extracted to a… | |||||
| MutableSpan<float3> positions_cu = curves.positions_for_write(); | MutableSpan<float3> positions_cu = curves.positions_for_write(); | ||||
| MutableSpan<int> surface_triangle_indices = curves.surface_triangle_indices_for_write(); | MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write(); | ||||
| MutableSpan<float2> surface_triangle_coords = curves.surface_triangle_coords_for_write(); | |||||
| const Span<MLoopTri> surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh), | const Span<MLoopTri> surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh), | ||||
| BKE_mesh_runtime_looptri_len(&surface_mesh)}; | BKE_mesh_runtime_looptri_len(&surface_mesh)}; | ||||
| const float4x4 curves_to_world_mat = curves_ob->obmat; | const float4x4 curves_to_world_mat = curves_ob->obmat; | ||||
| const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); | const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); | ||||
| const float4x4 surface_to_world_mat = surface_ob.obmat; | const float4x4 surface_to_world_mat = surface_ob.obmat; | ||||
| const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); | const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); | ||||
| Show All 29 Lines | switch (attach_mode) { | ||||
| const float3 new_first_point_pos_su = nearest.co; | const float3 new_first_point_pos_su = nearest.co; | ||||
| const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su; | const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su; | ||||
| const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu; | const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu; | ||||
| for (float3 &pos_cu : positions_cu.slice(points)) { | for (float3 &pos_cu : positions_cu.slice(points)) { | ||||
| pos_cu += pos_diff_cu; | pos_cu += pos_diff_cu; | ||||
| } | } | ||||
| surface_triangle_indices[curve_i] = looptri_index; | if (use_uvs) { | ||||
| const MLoopTri &looptri = surface_looptris[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 int corner0 = looptri.tri[0]; | ||||
| const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co; | const int corner1 = looptri.tri[1]; | ||||
| const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co; | 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; | float3 bary_coords; | ||||
| interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su); | 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); | const float2 uv = attribute_math::mix3(bary_coords, uv0, uv1, uv2); | ||||
| surface_uv_coords[curve_i] = uv; | |||||
| } | |||||
| } | } | ||||
| }); | }); | ||||
| break; | break; | ||||
| } | } | ||||
| case AttachMode::Deform: { | case AttachMode::Deform: { | ||||
| if (!use_uvs) { | |||||
| BKE_report(op->reports, | |||||
| RPT_ERROR, | |||||
| "Curves do not have attachment information that can be used for deformation"); | |||||
| } | |||||
| using geometry::ReverseUVLookup; | |||||
| ReverseUVLookup reverse_uv_lookup{surface_uv_map, surface_looptris}; | |||||
| threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { | threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { | ||||
| for (const int curve_i : curves_range) { | for (const int curve_i : curves_range) { | ||||
| const IndexRange points = curves.points_for_curve(curve_i); | const IndexRange points = curves.points_for_curve(curve_i); | ||||
| const int first_point_i = points.first(); | const int first_point_i = points.first(); | ||||
| const float3 old_first_point_pos_cu = positions_cu[first_point_i]; | const float3 old_first_point_pos_cu = positions_cu[first_point_i]; | ||||
| const int looptri_index = surface_triangle_indices[curve_i]; | const float2 uv = surface_uv_coords[curve_i]; | ||||
| if (!surface_looptris.index_range().contains(looptri_index)) { | ReverseUVLookup::Result lookup_result = reverse_uv_lookup.lookup(uv); | ||||
| found_invalid_looptri_index = true; | if (lookup_result.type != ReverseUVLookup::ResultType::Ok) { | ||||
| found_invalid_uv = true; | |||||
| continue; | continue; | ||||
| } | } | ||||
| const MLoopTri &looptri = surface_looptris[looptri_index]; | const MLoopTri &looptri = *lookup_result.looptri; | ||||
| const float3 &bary_coords = lookup_result.bary_weights; | |||||
| const float3 bary_coords = bke::curves::decode_surface_bary_coord( | |||||
| surface_triangle_coords[curve_i]); | |||||
| const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co; | 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 &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; | const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co; | ||||
| float3 new_first_point_pos_su; | float3 new_first_point_pos_su; | ||||
| interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords); | interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords); | ||||
| const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su; | const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su; | ||||
| const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu; | const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu; | ||||
| for (float3 &pos_cu : positions_cu.slice(points)) { | for (float3 &pos_cu : positions_cu.slice(points)) { | ||||
| pos_cu += pos_diff_cu; | pos_cu += pos_diff_cu; | ||||
| } | } | ||||
| } | } | ||||
| }); | }); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); | DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); | ||||
| } | } | ||||
| CTX_DATA_END; | 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"); | BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface"); | ||||
| } | } | ||||
| WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr); | WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 267 Lines • Show Last 20 Lines | |||||
I could imagine this looking a bit simpler and being more reusable if it was extracted to a separate function that had const Mesh & and const Curves & inputs. Then use_uvs could just be !surface_uv_map.is_empty().