[Bf-blender-cvs] [3267c91b4d5] master: Fix T91253: Slow pose bone selection with many bones

Campbell Barton noreply at git.blender.org
Tue Feb 8 12:19:33 CET 2022


Commit: 3267c91b4d5caab7da8aef071a446dd2e86f86a9
Author: Campbell Barton
Date:   Tue Feb 8 22:16:51 2022 +1100
Branches: master
https://developer.blender.org/rB3267c91b4d5caab7da8aef071a446dd2e86f86a9

Fix T91253: Slow pose bone selection with many bones

Viewport cull bones during selection to avoid depth-picking
reading the depth buffer for bones that aren't in the viewport.

Files with thousands of bones could hang blender for seconds while
selecting. The issue could still happen with overlapping bones or when
zoomed out so all bones are under the cursor, however in practice this
rarely happens.

Now files with many bones select quickly.

Related changes include:

- Split `BKE_pchan_minmax` out of `BKE_pose_minmax`.
- Add `mat3_to_size_max_axis` to return the length of the largest
  axis (used for scaling the radius).

Reviewed By: sybren

Maniphest Tasks: T91253

Ref D13990

===================================================================

M	source/blender/blenkernel/BKE_armature.h
M	source/blender/blenkernel/intern/armature.c
M	source/blender/blenlib/BLI_math_matrix.h
M	source/blender/blenlib/intern/math_matrix.c
M	source/blender/draw/engines/overlay/overlay_armature.c

===================================================================

diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h
index 12d8135ba55..ede300b19dd 100644
--- a/source/blender/blenkernel/BKE_armature.h
+++ b/source/blender/blenkernel/BKE_armature.h
@@ -163,6 +163,30 @@ void BKE_armature_transform(struct bArmature *arm, const float mat[4][4], bool d
 /* Bounding box. */
 struct BoundBox *BKE_armature_boundbox_get(struct Object *ob);
 
+/**
+ * Calculate the axis-aligned bounds of `pchan` in world-space,
+ * taking into account custom transform when set.
+ *
+ * `r_min` and `r_max` are expanded to fit `pchan` so the caller must initialize them
+ * (typically using #INIT_MINMAX).
+ *
+ * \note The bounds are calculated based on the head & tail of the bone
+ * or the custom object's bounds (if the bone uses a custom object).
+ * Visual elements such as the envelopes radius & bendy-bone spline segments are *not* included,
+ * making this not so useful for viewport culling.
+ */
+void BKE_pchan_minmax(const struct Object *ob,
+                      const struct bPoseChannel *pchan,
+                      float r_min[3],
+                      float r_max[3]);
+/**
+ * Calculate the axis aligned bounds of the pose of `ob` in world-space.
+
+ * `r_min` and `r_max` are expanded to fit `ob->pose` so the caller must initialize them
+ * (typically using #INIT_MINMAX).
+ *
+ * \note This uses #BKE_pchan_minmax, see its documentation for details on bounds calculation.
+ */
 bool BKE_pose_minmax(
     struct Object *ob, float r_min[3], float r_max[3], bool use_hidden, bool use_select);
 
diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c
index 7feb9d08915..1a9f0a9130d 100644
--- a/source/blender/blenkernel/intern/armature.c
+++ b/source/blender/blenkernel/intern/armature.c
@@ -2679,6 +2679,35 @@ BoundBox *BKE_armature_boundbox_get(Object *ob)
   return ob->runtime.bb;
 }
 
+void BKE_pchan_minmax(const Object *ob, const bPoseChannel *pchan, float r_min[3], float r_max[3])
+{
+  const bArmature *arm = ob->data;
+  const bPoseChannel *pchan_tx = (pchan->custom && pchan->custom_tx) ? pchan->custom_tx : pchan;
+  const BoundBox *bb_custom = ((pchan->custom) && !(arm->flag & ARM_NO_CUSTOM)) ?
+                                  BKE_object_boundbox_get(pchan->custom) :
+                                  NULL;
+  if (bb_custom) {
+    float mat[4][4], smat[4][4], rmat[4][4], tmp[4][4];
+    scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan));
+    rescale_m4(smat, pchan->custom_scale_xyz);
+    eulO_to_mat4(rmat, pchan->custom_rotation_euler, ROT_MODE_XYZ);
+    copy_m4_m4(tmp, pchan_tx->pose_mat);
+    translate_m4(tmp,
+                 pchan->custom_translation[0],
+                 pchan->custom_translation[1],
+                 pchan->custom_translation[2]);
+    mul_m4_series(mat, ob->obmat, tmp, rmat, smat);
+    BKE_boundbox_minmax(bb_custom, mat, r_min, r_max);
+  }
+  else {
+    float vec[3];
+    mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_head);
+    minmax_v3v3_v3(r_min, r_max, vec);
+    mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_tail);
+    minmax_v3v3_v3(r_min, r_max, vec);
+  }
+}
+
 bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden, bool use_select)
 {
   bool changed = false;
@@ -2692,31 +2721,8 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden
        *     (editarmature.c:2592)... Skip in this case too! */
       if (pchan->bone && (!((use_hidden == false) && (PBONE_VISIBLE(arm, pchan->bone) == false)) &&
                           !((use_select == true) && ((pchan->bone->flag & BONE_SELECTED) == 0)))) {
-        bPoseChannel *pchan_tx = (pchan->custom && pchan->custom_tx) ? pchan->custom_tx : pchan;
-        BoundBox *bb_custom = ((pchan->custom) && !(arm->flag & ARM_NO_CUSTOM)) ?
-                                  BKE_object_boundbox_get(pchan->custom) :
-                                  NULL;
-        if (bb_custom) {
-          float mat[4][4], smat[4][4], rmat[4][4], tmp[4][4];
-          scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan));
-          rescale_m4(smat, pchan->custom_scale_xyz);
-          eulO_to_mat4(rmat, pchan->custom_rotation_euler, ROT_MODE_XYZ);
-          copy_m4_m4(tmp, pchan_tx->pose_mat);
-          translate_m4(tmp,
-                       pchan->custom_translation[0],
-                       pchan->custom_translation[1],
-                       pchan->custom_translation[2]);
-          mul_m4_series(mat, ob->obmat, tmp, rmat, smat);
-          BKE_boundbox_minmax(bb_custom, mat, r_min, r_max);
-        }
-        else {
-          float vec[3];
-          mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_head);
-          minmax_v3v3_v3(r_min, r_max, vec);
-          mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_tail);
-          minmax_v3v3_v3(r_min, r_max, vec);
-        }
 
+        BKE_pchan_minmax(ob, pchan, r_min, r_max);
         changed = true;
       }
     }
diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h
index 03b6ff25a4f..1dcc1c1df49 100644
--- a/source/blender/blenlib/BLI_math_matrix.h
+++ b/source/blender/blenlib/BLI_math_matrix.h
@@ -430,6 +430,20 @@ void size_to_mat4(float R[4][4], const float size[3]);
 void mat3_to_size(float size[3], const float M[3][3]);
 void mat4_to_size(float size[3], const float M[4][4]);
 
+/**
+ * Return the largest scale on any axis, the equivalent of calling:
+ * \code{.c}
+ * mat3_to_size(size_v3, mat);
+ * size = size_v3[max_axis_v3(size_v3)];
+ * \endcode
+ * .. without 2x unnecessary `sqrtf` calls.
+ */
+float mat3_to_size_max_axis(const float M[3][3]);
+/**
+ * Only the first 3 axes are used.
+ */
+float mat4_to_size_max_axis(const float M[4][4]);
+
 /**
  * Extract scale factors from the matrix, with correction to ensure
  * exact volume in case of a sheared matrix.
diff --git a/source/blender/blenlib/intern/math_matrix.c b/source/blender/blenlib/intern/math_matrix.c
index f307672361b..9c719b12756 100644
--- a/source/blender/blenlib/intern/math_matrix.c
+++ b/source/blender/blenlib/intern/math_matrix.c
@@ -2144,6 +2144,16 @@ void mat4_to_size(float size[3], const float M[4][4])
   size[2] = len_v3(M[2]);
 }
 
+float mat3_to_size_max_axis(const float M[3][3])
+{
+  return sqrtf(max_fff(len_squared_v3(M[0]), len_squared_v3(M[1]), len_squared_v3(M[2])));
+}
+
+float mat4_to_size_max_axis(const float M[4][4])
+{
+  return sqrtf(max_fff(len_squared_v3(M[0]), len_squared_v3(M[1]), len_squared_v3(M[2])));
+}
+
 void mat4_to_size_fix_shear(float size[3], const float M[4][4])
 {
   mat4_to_size(size, M);
diff --git a/source/blender/draw/engines/overlay/overlay_armature.c b/source/blender/draw/engines/overlay/overlay_armature.c
index ccf8f9e0c36..5abebe2f32b 100644
--- a/source/blender/draw/engines/overlay/overlay_armature.c
+++ b/source/blender/draw/engines/overlay/overlay_armature.c
@@ -2037,6 +2037,126 @@ static void draw_bone_name(ArmatureDrawContext *ctx,
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Pose Bone Culling
+ *
+ * Used for selection since drawing many bones can be slow, see: T91253.
+ *
+ * Bounding spheres are used with margins added to ensure bones are included.
+ * An added margin is needed because #BKE_pchan_minmax only returns the bounds
+ * of the bones head & tail which doesn't account for parts of the bone users may select
+ * (octahedral spheres or envelope radius for example).
+ * \{ */
+
+static void pchan_culling_calc_bsphere(const Object *ob,
+                                       const bPoseChannel *pchan,
+                                       BoundSphere *r_bsphere)
+{
+  float min[3], max[3];
+  INIT_MINMAX(min, max);
+  BKE_pchan_minmax(ob, pchan, min, max);
+  mid_v3_v3v3(r_bsphere->center, min, max);
+  r_bsphere->radius = len_v3v3(min, r_bsphere->center);
+}
+
+/**
+ * \return true when bounding sphere from `pchan` intersect the view.
+ * (same for other "test" functions defined here).
+ */
+static bool pchan_culling_test_simple(const DRWView *view,
+                                      const Object *ob,
+                                      const bPoseChannel *pchan)
+{
+  BoundSphere bsphere;
+  pchan_culling_calc_bsphere(ob, pchan, &bsphere);
+  return DRW_culling_sphere_test(view, &bsphere);
+}
+
+static bool pchan_culling_test_with_radius_scale(const DRWView *view,
+                                                 const Object *ob,
+                                                 const bPoseChannel *pchan,
+                                                 const float scale)
+{
+  BoundSphere bsphere;
+  pchan_culling_calc_bsphere(ob, pchan, &bsphere);
+  bsphere.radius *= scale;
+  return DRW_culling_sphere_test(view, &bsphere);
+}
+
+static bool pchan_culling_test_custom(const DRWView *view,
+                                      const Object *ob,
+                                      const bPoseChannel *pchan)
+{
+  /* For more aggressive culling the bounding box of the custom-object could be used. */
+  return pchan_culling_test_simple(view, ob, pchan);
+}
+
+static bool pchan_culling_test_wire(const DRWView *view,
+                                    const Object *ob,
+                                    const bPoseChannel *pchan)
+{
+  BLI_assert(((const bArmature *)ob->data)->drawtype == ARM_WIRE);
+  return pchan_culling_test_simple(view, ob, pchan);
+}
+
+static bool pchan_culling_test_line(const DRWView *view,
+                                    const Object *ob,
+                                    const bPoseChannel *pchan)
+{
+  BLI_assert(((const bArmature *)ob->data)->drawtype == ARM_LINE);
+  /* Account for the end-points, as the line end-points size is in pixels, this is a rough value.
+   * Since the end-points are small the difference between having any margin or not is unlikely
+   * to be noticeable. */
+  const float scale = 1.1f;
+  return pchan_culling_test_with_radius_scale(view, ob, pchan, scale);
+}
+
+static bool pchan_culling_test_envelope(const DRWView *view,
+                                        const Object *ob,
+                                        const bPoseChannel *pchan)
+{
+  const bArmature *arm

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list