[Bf-blender-cvs] [280502e630e] master: Fix T103469: improve uv cylinder projection and uv sphere projection

Chris Blackbourn noreply at git.blender.org
Fri Jan 6 05:27:44 CET 2023


Commit: 280502e630e99a6723861a9cae156fabd53a7eb1
Author: Chris Blackbourn
Date:   Fri Jan 6 17:10:28 2023 +1300
Branches: master
https://developer.blender.org/rB280502e630e99a6723861a9cae156fabd53a7eb1

Fix T103469: improve uv cylinder projection and uv sphere projection

Multiple improvements to UV Cylinder and UV Sphere projection including:

 * New option "Pinch" or "Fan" to improve unwrap of faces at the poles.
 * Support for "Polar ZY" option on "View On Equator" and "Align To Object"
 * Better handling of inputs with round-off error.
 * Improved handling of faces touching the unit square border.
 * Improved handling of very wide quads spanning the right hand border.
 * Improved accuracy near to (0, 0).
 * Code cleanup and simplification.

Differential Revision: https://developer.blender.org/D16869

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

M	source/blender/blenlib/BLI_math_geom.h
M	source/blender/blenlib/intern/math_geom.c
M	source/blender/editors/uvedit/uvedit_unwrap_ops.c

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

diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index d056c42e019..e24f3c3bde8 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -1164,8 +1164,8 @@ void box_minmax_bounds_m4(float min[3], float max[3], float boundbox[2][3], floa
 /** \name Mapping
  * \{ */
 
-void map_to_tube(float *r_u, float *r_v, float x, float y, float z);
-void map_to_sphere(float *r_u, float *r_v, float x, float y, float z);
+bool map_to_tube(float *r_u, float *r_v, float x, float y, float z);
+bool map_to_sphere(float *r_u, float *r_v, float x, float y, float z);
 void map_to_plane_v2_v3v3(float r_co[2], const float co[3], const float no[3]);
 void map_to_plane_axis_angle_v2_v3v3fl(float r_co[2],
                                        const float co[3],
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index 08152976f7d..415e21cd272 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -4914,39 +4914,59 @@ void box_minmax_bounds_m4(float min[3], float max[3], float boundbox[2][3], floa
 
 /********************************** Mapping **********************************/
 
-void map_to_tube(float *r_u, float *r_v, const float x, const float y, const float z)
+static float snap_coordinate(float u)
 {
-  float len;
-
-  *r_v = (z + 1.0f) / 2.0f;
+  /* Adjust a coordinate value `u` to obtain a value inside the (closed) unit interval.
+   *   i.e. 0.0 <= snap_coordinate(u) <= 1.0.
+   * Due to round-off errors, it is possible that the value of `u` may be close to the boundary of
+   * the unit interval, but not exactly on it. In order to handle these cases, `snap_coordinate`
+   * checks whether `u` is within `epsilon` of the boundary, and if so, it snaps the return value
+   * to the boundary. */
+  if (u < 0.0f) {
+    u += 1.0f; /* Get back into the unit interval. */
+  }
+  BLI_assert(0.0f <= u);
+  BLI_assert(u <= 1.0f);
+  const float epsilon = 0.25f / 65536.0f; /* i.e. Quarter of a texel on a 65536 x 65536 texture. */
+  if (u < epsilon) {
+    return 0.0f; /* `u` is close to 0, just return 0. */
+  }
+  if (1.0f - epsilon < u) {
+    return 1.0f; /* `u` is close to 1, just return 1. */
+  }
+  return u;
+}
 
-  len = sqrtf(x * x + y * y);
-  if (len > 0.0f) {
-    *r_u = (1.0f - (atan2f(x / len, y / len) / (float)M_PI)) / 2.0f;
+bool map_to_tube(float *r_u, float *r_v, const float x, const float y, const float z)
+{
+  bool regular = true;
+  if (x * x + y * y < 1e-6f * 1e-6f) {
+    regular = false; /* We're too close to the cylinder's axis. */
+    *r_u = 0.5f;
   }
   else {
-    *r_v = *r_u = 0.0f; /* to avoid un-initialized variables */
+    /* The "Regular" case, just compute the coordinate. */
+    *r_u = snap_coordinate(atan2f(x, -y) / (float)(2.0f * M_PI));
   }
+  *r_v = (z + 1.0f) / 2.0f;
+  return regular;
 }
 
-void map_to_sphere(float *r_u, float *r_v, const float x, const float y, const float z)
+bool map_to_sphere(float *r_u, float *r_v, const float x, const float y, const float z)
 {
-  float len;
-
-  len = sqrtf(x * x + y * y + z * z);
-  if (len > 0.0f) {
-    if (UNLIKELY(x == 0.0f && y == 0.0f)) {
-      *r_u = 0.0f; /* Otherwise domain error. */
-    }
-    else {
-      *r_u = (1.0f - atan2f(x, y) / (float)M_PI) / 2.0f;
-    }
-
-    *r_v = 1.0f - saacos(z / len) / (float)M_PI;
+  bool regular = true;
+  const float epsilon = 0.25f / 65536.0f; /* i.e. Quarter of a texel on a 65536 x 65536 texture. */
+  const float len_xy = sqrtf(x * x + y * y);
+  if (len_xy <= fabsf(z) * epsilon) {
+    regular = false; /* We're on the line that runs through the north and south poles. */
+    *r_u = 0.5f;
   }
   else {
-    *r_v = *r_u = 0.0f; /* to avoid un-initialized variables */
+    /* The "Regular" case, just compute the coordinate. */
+    *r_u = snap_coordinate(atan2f(x, -y) / (float)(2.0f * M_PI));
   }
+  *r_v = snap_coordinate(atan2f(len_xy, -z) / (float)M_PI);
+  return regular;
 }
 
 void map_to_plane_v2_v3v3(float r_co[2], const float co[3], const float no[3])
diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c
index 18e920c55f4..0f8830a4521 100644
--- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c
+++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c
@@ -1318,6 +1318,9 @@ void ED_uvedit_live_unwrap_end(short cancel)
 #define POLAR_ZX 0
 #define POLAR_ZY 1
 
+#define PINCH 0
+#define FAN 1
+
 static void uv_map_transform_calc_bounds(BMEditMesh *em, float r_min[3], float r_max[3])
 {
   BMFace *efa;
@@ -1457,50 +1460,37 @@ static void uv_map_rotation_matrix_ex(float result[4][4],
   mul_m4_series(result, rotup, rotside, viewmatrix, rotobj);
 }
 
-static void uv_map_rotation_matrix(float result[4][4],
-                                   RegionView3D *rv3d,
-                                   Object *ob,
-                                   float upangledeg,
-                                   float sideangledeg,
-                                   float radius)
-{
-  const float offset[4] = {0};
-  uv_map_rotation_matrix_ex(result, rv3d, ob, upangledeg, sideangledeg, radius, offset);
-}
-
-static void uv_map_transform(bContext *C, wmOperator *op, float rotmat[4][4])
+static void uv_map_transform(bContext *C, wmOperator *op, float rotmat[3][3])
 {
-  /* context checks are messy here, making it work in both 3d view and uv editor */
   Object *obedit = CTX_data_edit_object(C);
   RegionView3D *rv3d = CTX_wm_region_view3d(C);
-  /* common operator properties */
-  int align = RNA_enum_get(op->ptr, "align");
-  int direction = RNA_enum_get(op->ptr, "direction");
-  float radius = RNA_struct_find_property(op->ptr, "radius") ? RNA_float_get(op->ptr, "radius") :
-                                                               1.0f;
-  float upangledeg, sideangledeg;
 
-  if (direction == VIEW_ON_EQUATOR) {
-    upangledeg = 90.0f;
-    sideangledeg = 0.0f;
-  }
-  else {
-    upangledeg = 0.0f;
+  const int align = RNA_enum_get(op->ptr, "align");
+  const int direction = RNA_enum_get(op->ptr, "direction");
+  const float radius = RNA_struct_find_property(op->ptr, "radius") ?
+                           RNA_float_get(op->ptr, "radius") :
+                           1.0f;
+
+  /* Be compatible to the "old" sphere/cylinder mode. */
+  if (direction == ALIGN_TO_OBJECT) {
+    unit_m3(rotmat);
+
     if (align == POLAR_ZY) {
-      sideangledeg = 0.0f;
-    }
-    else {
-      sideangledeg = 90.0f;
+      rotmat[0][0] = 0.0f;
+      rotmat[0][1] = 1.0f;
+      rotmat[1][0] = -1.0f;
+      rotmat[1][1] = 0.0f;
     }
+    return;
   }
 
-  /* be compatible to the "old" sphere/cylinder mode */
-  if (direction == ALIGN_TO_OBJECT) {
-    unit_m4(rotmat);
-  }
-  else {
-    uv_map_rotation_matrix(rotmat, rv3d, obedit, upangledeg, sideangledeg, radius);
-  }
+  const float up_angle_deg = (direction == VIEW_ON_EQUATOR) ? 90.0f : 0.0f;
+  const float side_angle_deg = (align == POLAR_ZY) == (direction == VIEW_ON_EQUATOR) ? 90.0f :
+                                                                                       0.0f;
+  const float offset[4] = {0};
+  float rotmat4[4][4];
+  uv_map_rotation_matrix_ex(rotmat4, rv3d, obedit, up_angle_deg, side_angle_deg, radius, offset);
+  copy_m3_m4(rotmat, rotmat4);
 }
 
 static void uv_transform_properties(wmOperatorType *ot, int radius)
@@ -1521,6 +1511,12 @@ static void uv_transform_properties(wmOperatorType *ot, int radius)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static const EnumPropertyItem pole_items[] = {
+      {PINCH, "PINCH", 0, "Pinch", "UVs are pinched at the poles"},
+      {FAN, "FAN", 0, "Fan", "UVs are fanned at the poles"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   RNA_def_enum(ot->srna,
                "direction",
                direction_items,
@@ -1530,9 +1526,10 @@ static void uv_transform_properties(wmOperatorType *ot, int radius)
   RNA_def_enum(ot->srna,
                "align",
                align_items,
-               VIEW_ON_EQUATOR,
+               POLAR_ZX,
                "Align",
                "How to determine rotation around the pole");
+  RNA_def_enum(ot->srna, "pole", pole_items, PINCH, "Pole", "How to handle faces at the poles");
   if (radius) {
     RNA_def_float(ot->srna,
                   "radius",
@@ -2730,55 +2727,120 @@ void UV_OT_reset(wmOperatorType *ot)
 /** \name Sphere UV Project Operator
  * \{ */
 
-static void uv_sphere_project(float target[2],
-                              const float source[3],
-                              const float center[3],
-                              const float rotmat[4][4])
-{
-  float pv[3];
-
-  sub_v3_v3v3(pv, source, center);
-  mul_m4_v3(rotmat, pv);
-
-  map_to_sphere(&target[0], &target[1], pv[0], pv[1], pv[2]);
-
-  /* split line is always zero */
-  if (target[0] >= 1.0f) {
-    target[0] -= 1.0f;
-  }
-}
+static void uv_map_mirror(BMFace *efa,
+                          const bool *regular,
+                          const bool fan,
+                          const int cd_loop_uv_offset)
+{
+  /* A heuristic to improve alignment of faces near the seam.
+   * In simple terms, we're looking for faces which span more
+   * than 0.5 units in the *u* coordinate.
+   * If we find such a face, we try and improve the unwrapping
+   * by adding (1.0, 0.0) onto some of the face's UVs.
+
+   * Note that this is only a heuristic. The property we're
+   * attempting to maintain is that the winding of the face
+   * in UV space corresponds with the handedness of the face
+   * in 3D space w.r.t to the unwrapping. Even for triangles,
+   * that property is somewhat complicated to evaluate.
+   */
 
-static void uv_map_mirror(BMEditMesh *em, BMFace *efa)
-{
+  float right_u = -1.0e30f;
   BMLoop *l;
   BMIter liter;
-  MLoopUV *luv;
   float **uvs = BLI_array_alloca(uvs, efa->len);
-  float dx;
-  int i, mi;
-
-  const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
-
-  BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
-    luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-    uvs[i] = luv->uv;
+  int j;
+  BM_ITER_ELEM_INDEX (l, &liter,

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list