From 3ed4e51437f1074402e35dd3b8a39af7948da595 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 5 Apr 2024 14:17:43 +0200 Subject: [PATCH] Fix #117338: Texture paint sampling broken with modifiers e64b3c821252fc8707b4 worked to complete a transition started during 2.8 development to avoid dynamically re-evaluating objects outside of the dependency graph's control. However, that behavior was used to make sure the original index mapping was available for the texture paint sample operator. Conceptually, sampling a texture on an arbitrary evaluated mesh should not require original indices at all. All we need to know is the UV map value under the mouse. This commit changes from using GPU index textures to a BVH tree raycast. This significantly simplifies the code and makes it work with GPU subdivision too. Though it may be slower to build the BVH tree, that should be okay because it's cached and only needs to be built once, and that's something we're want to optimize anyway. Removing the reliance on original indices also means we could paint textures on completely procedurally generated meshes, which may be an interesting feature in the future. Pull Request: https://projects.blender.org/blender/blender/pulls/120259 --- .../editors/sculpt_paint/paint_utils.cc | 229 ++++++------------ 1 file changed, 79 insertions(+), 150 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_utils.cc b/source/blender/editors/sculpt_paint/paint_utils.cc index 6243b0c4ade..5a69589abc1 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.cc +++ b/source/blender/editors/sculpt_paint/paint_utils.cc @@ -18,7 +18,9 @@ #include "BLI_listbase.h" #include "BLI_math_color.h" +#include "BLI_math_geom.h" #include "BLI_math_matrix.h" +#include "BLI_math_matrix.hh" #include "BLI_math_vector.hh" #include "BLI_rect.h" #include "BLI_utildefines.h" @@ -26,6 +28,7 @@ #include "BLT_translation.hh" #include "BKE_brush.hh" +#include "BKE_bvhutils.hh" #include "BKE_colortools.hh" #include "BKE_context.hh" #include "BKE_customdata.hh" @@ -33,6 +36,7 @@ #include "BKE_layer.hh" #include "BKE_material.h" #include "BKE_mesh.hh" +#include "BKE_mesh_sample.hh" #include "BKE_object.hh" #include "BKE_paint.hh" #include "BKE_report.hh" @@ -44,9 +48,6 @@ #include "RNA_define.hh" #include "RNA_prototypes.h" -#include "GPU_matrix.hh" -#include "GPU_state.hh" - #include "IMB_imbuf_types.hh" #include "IMB_interp.hh" @@ -59,8 +60,6 @@ #include "BLI_sys_types.h" #include "ED_mesh.hh" /* for face mask functions */ -#include "DRW_select_buffer.hh" - #include "WM_api.hh" #include "WM_types.hh" @@ -202,168 +201,104 @@ void paint_stroke_operator_properties(wmOperatorType *ot) /* 3D Paint */ -static void imapaint_project(const float matrix[4][4], const float co[3], float pco[4]) -{ - copy_v3_v3(pco, co); - pco[3] = 1.0f; - - mul_m4_v4(matrix, pco); -} - -static void imapaint_tri_weights(float matrix[4][4], - const int view[4], - const float v1[3], - const float v2[3], - const float v3[3], - const float co[2], - float w[3]) -{ - float pv1[4], pv2[4], pv3[4], h[3], divw; - float wmat[3][3], invwmat[3][3]; - - /* compute barycentric coordinates */ - - /* project the verts */ - imapaint_project(matrix, v1, pv1); - imapaint_project(matrix, v2, pv2); - imapaint_project(matrix, v3, pv3); - - /* do inverse view mapping, see gluProject man page */ - h[0] = (co[0] - view[0]) * 2.0f / view[2] - 1.0f; - h[1] = (co[1] - view[1]) * 2.0f / view[3] - 1.0f; - h[2] = 1.0f; - - /* Solve for `(w1,w2,w3)/perspdiv` in: - * `h * perspdiv = Project * Model * (w1 * v1 + w2 * v2 + w3 * v3)`. */ - - wmat[0][0] = pv1[0]; - wmat[1][0] = pv2[0]; - wmat[2][0] = pv3[0]; - wmat[0][1] = pv1[1]; - wmat[1][1] = pv2[1]; - wmat[2][1] = pv3[1]; - wmat[0][2] = pv1[3]; - wmat[1][2] = pv2[3]; - wmat[2][2] = pv3[3]; - - invert_m3_m3(invwmat, wmat); - mul_m3_v3(invwmat, h); - - copy_v3_v3(w, h); - - /* w is still divided by `perspdiv`, make it sum to one */ - divw = w[0] + w[1] + w[2]; - if (divw != 0.0f) { - mul_v3_fl(w, 1.0f / divw); - } -} - /* compute uv coordinates of mouse in face */ -static void imapaint_pick_uv(const Mesh *mesh_eval, - Scene *scene, - Object *ob_eval, - uint faceindex, - const int xy[2], - float uv[2]) +static blender::float2 imapaint_pick_uv(const Mesh *mesh_eval, + Scene *scene, + Object *ob_eval, + const int tri_index, + const blender::float3 &bary_coord) { - float p[2], w[3], absw, minabsw; - float matrix[4][4], proj[4][4]; - int view[4]; const ePaintCanvasSource mode = ePaintCanvasSource(scene->toolsettings->imapaint.mode); const blender::Span tris = mesh_eval->corner_tris(); const blender::Span tri_faces = mesh_eval->corner_tri_faces(); - const blender::Span positions = mesh_eval->vert_positions(); - const blender::Span corner_verts = mesh_eval->corner_verts(); - const int *index_mp_to_orig = static_cast( - CustomData_get_layer(&mesh_eval->face_data, CD_ORIGINDEX)); - - /* get the needed opengl matrices */ - GPU_viewport_size_get_i(view); - GPU_matrix_model_view_get(matrix); - GPU_matrix_projection_get(proj); - view[0] = view[1] = 0; - mul_m4_m4m4(matrix, matrix, ob_eval->object_to_world().ptr()); - mul_m4_m4m4(matrix, proj, matrix); - - minabsw = 1e10; - uv[0] = uv[1] = 0.0; - const int *material_indices = (const int *)CustomData_get_layer_named( &mesh_eval->face_data, CD_PROP_INT32, "material_index"); - /* test all faces in the derivedmesh with the original index of the picked face */ /* face means poly here, not triangle, indeed */ - for (const int i : tris.index_range()) { - const int face_i = tri_faces[i]; - const int findex = index_mp_to_orig ? index_mp_to_orig[face_i] : face_i; + const int face_i = tri_faces[tri_index]; - if (findex == faceindex) { - const float(*mloopuv)[2]; - const float *tri_uv[3]; - float tri_co[3][3]; + const float(*mloopuv)[2]; - for (int j = 3; j--;) { - copy_v3_v3(tri_co[j], positions[corner_verts[tris[i][j]]]); - } + if (mode == PAINT_CANVAS_SOURCE_MATERIAL) { + const Material *ma; + const TexPaintSlot *slot; - if (mode == PAINT_CANVAS_SOURCE_MATERIAL) { - const Material *ma; - const TexPaintSlot *slot; + ma = BKE_object_material_get(ob_eval, + material_indices == nullptr ? 1 : material_indices[face_i] + 1); + slot = &ma->texpaintslot[ma->paint_active_slot]; - ma = BKE_object_material_get( - ob_eval, material_indices == nullptr ? 1 : material_indices[face_i] + 1); - slot = &ma->texpaintslot[ma->paint_active_slot]; - - if (!(slot && slot->uvname && - (mloopuv = static_cast(CustomData_get_layer_named( - &mesh_eval->corner_data, CD_PROP_FLOAT2, slot->uvname))))) - { - mloopuv = static_cast( - CustomData_get_layer(&mesh_eval->corner_data, CD_PROP_FLOAT2)); - } - } - else { - mloopuv = static_cast( - CustomData_get_layer(&mesh_eval->corner_data, CD_PROP_FLOAT2)); - } - - tri_uv[0] = mloopuv[tris[i][0]]; - tri_uv[1] = mloopuv[tris[i][1]]; - tri_uv[2] = mloopuv[tris[i][2]]; - - p[0] = xy[0]; - p[1] = xy[1]; - - imapaint_tri_weights(matrix, view, UNPACK3(tri_co), p, w); - absw = fabsf(w[0]) + fabsf(w[1]) + fabsf(w[2]); - if (absw < minabsw) { - uv[0] = tri_uv[0][0] * w[0] + tri_uv[1][0] * w[1] + tri_uv[2][0] * w[2]; - uv[1] = tri_uv[0][1] * w[0] + tri_uv[1][1] * w[1] + tri_uv[2][1] * w[2]; - minabsw = absw; - } + if (!(slot && slot->uvname && + (mloopuv = static_cast(CustomData_get_layer_named( + &mesh_eval->corner_data, CD_PROP_FLOAT2, slot->uvname))))) + { + mloopuv = static_cast( + CustomData_get_layer(&mesh_eval->corner_data, CD_PROP_FLOAT2)); } } + else { + mloopuv = static_cast( + CustomData_get_layer(&mesh_eval->corner_data, CD_PROP_FLOAT2)); + } + + return blender::bke::mesh_surface_sample::sample_corner_attribute_with_bary_coords( + bary_coord, + tris[tri_index], + blender::Span(reinterpret_cast(mloopuv), mesh_eval->corners_num)); } /* returns 0 if not found, otherwise 1 */ -static int imapaint_pick_face(ViewContext *vc, const int mval[2], uint *r_index, uint faces_num) +static int imapaint_pick_face(ViewContext *vc, + const int mval[2], + int *r_tri_index, + int *r_face_index, + blender::float3 *r_bary_coord, + const Mesh &mesh) { - if (faces_num == 0) { + using namespace blender; + if (mesh.faces_num == 0) { return 0; } - /* sample only on the exact position */ - ED_view3d_select_id_validate(vc); - *r_index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, mval); + BVHTreeFromMesh mesh_bvh; + BKE_bvhtree_from_mesh_get(&mesh_bvh, &mesh, BVHTREE_FROM_CORNER_TRIS, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&mesh_bvh); }); - if ((*r_index) == 0 || (*r_index) > uint(faces_num)) { + float3 start_world, end_world; + ED_view3d_win_to_segment_clipped( + vc->depsgraph, vc->region, vc->v3d, float2(mval[0], mval[1]), start_world, end_world, true); + + const float4x4 &world_to_object = vc->obact->world_to_object(); + const float3 start_object = math::transform_point(world_to_object, start_world); + const float3 end_object = math::transform_point(world_to_object, end_world); + + BVHTreeRayHit ray_hit; + ray_hit.dist = FLT_MAX; + ray_hit.index = -1; + BLI_bvhtree_ray_cast(mesh_bvh.tree, + start_object, + math::normalize(end_object - start_object), + 0.0f, + &ray_hit, + mesh_bvh.raycast_callback, + &mesh_bvh); + if (ray_hit.index == -1) { return 0; } - (*r_index)--; + const Span positions = mesh.vert_positions(); + const Span corner_verts = mesh.corner_verts(); + const Span corner_tris = mesh.corner_tris(); + const int3 &tri = corner_tris[ray_hit.index]; + interp_weights_tri_v3(*r_bary_coord, + positions[corner_verts[tri[0]]], + positions[corner_verts[tri[1]]], + positions[corner_verts[tri[2]]], + ray_hit.co); + *r_tri_index = ray_hit.index; + *r_face_index = mesh.corner_tri_faces()[ray_hit.index]; return 1; } @@ -404,23 +339,18 @@ void paint_sample_color( bool use_material = (imapaint->mode == IMAGEPAINT_MODE_MATERIAL); if (ob) { - CustomData_MeshMasks cddata_masks = CD_MASK_BAREMESH; - cddata_masks.pmask |= CD_MASK_ORIGINDEX; - Mesh *mesh = (Mesh *)ob->data; const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); const int *material_indices = (const int *)CustomData_get_layer_named( &mesh_eval->face_data, CD_PROP_INT32, "material_index"); - const int mval[2] = {x, y}; - uint faceindex; - uint faces_num = mesh->faces_num; - if (CustomData_has_layer(&mesh_eval->corner_data, CD_PROP_FLOAT2)) { ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); - view3d_operator_needs_opengl(C); - - if (imapaint_pick_face(&vc, mval, &faceindex, faces_num)) { + const int mval[2] = {x, y}; + int tri_index; + float3 bary_coord; + int faceindex; + if (imapaint_pick_face(&vc, mval, &tri_index, &faceindex, &bary_coord, *mesh_eval)) { Image *image = nullptr; int interp = SHD_INTERP_LINEAR; @@ -449,8 +379,7 @@ void paint_sample_color( BKE_imageuser_default(&iuser); iuser.framenr = image->lastframe; - float uv[2]; - imapaint_pick_uv(mesh_eval, scene, ob_eval, faceindex, mval, uv); + float2 uv = imapaint_pick_uv(mesh_eval, scene, ob_eval, tri_index, bary_coord); if (image->source == IMA_SRC_TILED) { float new_uv[2];