[Bf-blender-cvs] [871248a77d8] master: Fix texture paint UV dilation algorithm

Luca Rood noreply at git.blender.org
Mon Mar 4 15:37:06 CET 2019


Commit: 871248a77d8a9ae7dbb43ef28f3802601cbfe5c6
Author: Luca Rood
Date:   Tue Mar 5 01:24:47 2019 +1100
Branches: master
https://developer.blender.org/rB871248a77d8a9ae7dbb43ef28f3802601cbfe5c6

Fix texture paint UV dilation algorithm

Two aspects are addressed:

- Correct computation of dilation distance,
  so that dilated boundaries remain parallel to the original boundaries
  (and with the actual distance specified as bleed distance).

- Dilate with regard to adjacent seams
  instead of adjacent triangle edges, for a more correct result.
  This is especially important in the case of concave shapes,
  where spikes could overlap with actual geometry.

See: D4436

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

M	source/blender/editors/sculpt_paint/paint_image_proj.c

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

diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c
index b4abab0fabd..35dfce9aaa6 100644
--- a/source/blender/editors/sculpt_paint/paint_image_proj.c
+++ b/source/blender/editors/sculpt_paint/paint_image_proj.c
@@ -142,13 +142,20 @@ BLI_INLINE unsigned char f_to_char(const float val)
 /* projectFaceSeamFlags options */
 //#define PROJ_FACE_IGNORE	(1<<0)	/* When the face is hidden, backfacing or occluded */
 //#define PROJ_FACE_INIT	(1<<1)	/* When we have initialized the faces data */
-#define PROJ_FACE_SEAM1 (1 << 0)  /* If this face has a seam on any of its edges */
-#define PROJ_FACE_SEAM2 (1 << 1)
-#define PROJ_FACE_SEAM3 (1 << 2)
 
-#define PROJ_FACE_NOSEAM1   (1 << 4)
-#define PROJ_FACE_NOSEAM2   (1 << 5)
-#define PROJ_FACE_NOSEAM3   (1 << 6)
+/* If this face has a seam on any of its edges. */
+#define PROJ_FACE_SEAM0 (1 << 0)
+#define PROJ_FACE_SEAM1 (1 << 1)
+#define PROJ_FACE_SEAM2 (1 << 2)
+
+#define PROJ_FACE_NOSEAM0   (1 << 4)
+#define PROJ_FACE_NOSEAM1   (1 << 5)
+#define PROJ_FACE_NOSEAM2   (1 << 6)
+
+/* If the seam is completely initialized, including adjecent seams. */
+#define PROJ_FACE_SEAM_INIT0 (1 << 8)
+#define PROJ_FACE_SEAM_INIT1 (1 << 9)
+#define PROJ_FACE_SEAM_INIT2 (1 << 10)
 
 /* face winding */
 #define PROJ_FACE_WINDING_INIT 1
@@ -379,14 +386,16 @@ typedef struct ProjPaintState {
 
 #ifndef PROJ_DEBUG_NOSEAMBLEED
 	/** store info about faces, if they are initialized etc*/
-	char *faceSeamFlags;
+	ushort *faceSeamFlags;
 	/** save the winding of the face in uv space,
 	 * helps as an extra validation step for seam detection. */
 	char *faceWindingFlags;
 	/** expanded UVs for faces to use as seams. */
-	float (*faceSeamUVs)[3][2];
+	float (*loopSeamUVs)[2][2];
 	/** Only needed for when seam_bleed_px is enabled, use to find UV seams. */
 	LinkNode **vertFaces;
+	/** Seams per vert, to find adjacent seams. */
+	ListBase *vertSeams;
 #endif
 
 	SpinLock *tile_lock;
@@ -394,6 +403,7 @@ typedef struct ProjPaintState {
 	Mesh *me_eval;
 	bool  me_eval_free;
 	int  totlooptri_eval;
+	int  totloop_eval;
 	int  totpoly_eval;
 	int  totedge_eval;
 	int  totvert_eval;
@@ -478,6 +488,14 @@ typedef struct {
 	ProjPaintImage *pjima;
 } TileInfo;
 
+typedef struct VertSeam {
+	struct VertSeam *next, *prev;
+	int tri;
+	uint loop;
+	float angle;
+	bool normal_cw;
+	float uv[2];
+} VertSeam;
 
 
 /* -------------------------------------------------------------------- */
@@ -1155,37 +1173,106 @@ static bool check_seam(
 	return 1;
 }
 
-#define SMALL_NUMBER  1.e-6f
-BLI_INLINE float shell_v2v2_normal_dir_to_dist(float n[2], float d[2])
+static VertSeam *find_adjacent_seam(const ProjPaintState *ps, uint loop_index, uint vert_index, VertSeam **r_seam)
 {
-	const float angle_cos = (normalize_v2(n) < SMALL_NUMBER) ? fabsf(dot_v2v2(d, n)) : 0.0f;
-	return (UNLIKELY(angle_cos < SMALL_NUMBER)) ? 1.0f : (1.0f / angle_cos);
+	ListBase *vert_seams = &ps->vertSeams[vert_index];
+	VertSeam *seam = vert_seams->first;
+	VertSeam *adjacent;
+
+	while (seam->loop != loop_index) {
+		seam = seam->next;
+	}
+
+	if (r_seam) {
+		*r_seam = seam;
+	}
+
+	/* Circulate through the (sorted) vert seam array, in the direction of the seam normal,
+	 * until we find the first opposing seam, matching in UV space. */
+	if (seam->normal_cw) {
+		LISTBASE_CIRCULAR_BACKWARD_BEGIN(vert_seams, adjacent, seam)
+		{
+			if ((adjacent->normal_cw != seam->normal_cw) &&
+			    cmp_uv(adjacent->uv, seam->uv))
+			{
+				break;
+			}
+		}
+		LISTBASE_CIRCULAR_BACKWARD_END(vert_seams, adjacent, seam);
+	}
+	else {
+		LISTBASE_CIRCULAR_FORWARD_BEGIN(vert_seams, adjacent, seam)
+		{
+			if ((adjacent->normal_cw != seam->normal_cw) &&
+			    cmp_uv(adjacent->uv, seam->uv))
+			{
+				break;
+			}
+		}
+		LISTBASE_CIRCULAR_FORWARD_END(vert_seams, adjacent, seam);
+	}
+
+	BLI_assert(adjacent);
+
+	return adjacent;
+}
+
+/* Computes the normal of two seams at their intersection,
+ * and returns the angle between the seam and its normal. */
+static float compute_seam_normal(VertSeam *seam, VertSeam *adj, float r_no[2])
+{
+	const float PI_2 = M_PI * 2.0f;
+	float angle[2];
+	float angle_rel, angle_no;
+
+	if (seam->normal_cw) {
+		angle[0] = adj->angle;
+		angle[1] = seam->angle;
+	}
+	else {
+		angle[0] = seam->angle;
+		angle[1] = adj->angle;
+	}
+
+	angle_rel = angle[1] - angle[0];
+
+	if (angle_rel < 0.0f) {
+		angle_rel += PI_2;
+	}
+
+	angle_rel *= 0.5f;
+
+	angle_no = angle_rel + angle[0];
+
+	if (angle_no > M_PI) {
+		angle_no -= PI_2;
+	}
+
+	r_no[0] = cosf(angle_no);
+	r_no[1] = sinf(angle_no);
+
+	return angle_rel;
 }
-#undef SMALL_NUMBER
 
 /* Calculate outset UV's, this is not the same as simply scaling the UVs,
  * since the outset coords are a margin that keep an even distance from the original UV's,
  * note that the image aspect is taken into account */
 static void uv_image_outset(
-        float (*orig_uv)[2], float (*outset_uv)[2], const float scaler,
-        const int ibuf_x, const int ibuf_y, const bool cw)
+        const ProjPaintState *ps, float (*orig_uv)[2],
+        uint tri_index, const int ibuf_x, const int ibuf_y)
 {
-	/* disallow shell-thickness to outset extreme values,
-	 * otherwise near zero area UV's may extend thousands of pixels. */
-	const float scale_clamp = 5.0f;
+	int fidx[2];
+	uint loop_index;
+	uint vert[2];
+	const MLoopTri *ltri = &ps->mlooptri_eval[tri_index];
 
-	float a1, a2, a3;
 	/* pixelspace uv's */
 	float puv[3][2];
-	/* normals */
-	float no1[2], no2[2], no3[2];
-	float dir1[2], dir2[2], dir3[2];
 	float ibuf_inv[2];
 
 	ibuf_inv[0] = 1.0f / (float)ibuf_x;
 	ibuf_inv[1] = 1.0f / (float)ibuf_y;
 
-	/* make UV's in pixel space so we can */
 	puv[0][0] = orig_uv[0][0] * ibuf_x;
 	puv[0][1] = orig_uv[0][1] * ibuf_y;
 
@@ -1195,89 +1282,194 @@ static void uv_image_outset(
 	puv[2][0] = orig_uv[2][0] * ibuf_x;
 	puv[2][1] = orig_uv[2][1] * ibuf_y;
 
-	/* face edge directions */
-	sub_v2_v2v2(dir1, puv[1], puv[0]);
-	sub_v2_v2v2(dir2, puv[2], puv[1]);
-	sub_v2_v2v2(dir3, puv[0], puv[2]);
-	normalize_v2(dir1);
-	normalize_v2(dir2);
-	normalize_v2(dir3);
-
-	/* here we just use the orthonormality property (a1, a2) dot (a2, -a1) = 0
-	 * to get normals from the edge directions based on the winding */
-	if (cw) {
-		no1[0] = -dir3[1] - dir1[1];
-		no1[1] =  dir3[0] + dir1[0];
-		no2[0] = -dir1[1] - dir2[1];
-		no2[1] =  dir1[0] + dir2[0];
-		no3[0] = -dir2[1] - dir3[1];
-		no3[1] =  dir2[0] + dir3[0];
+	for (fidx[0] = 0; fidx[0] < 3; fidx[0]++) {
+		float (*seam_uvs)[2];
+		float ang[2];
+
+		if ((ps->faceSeamFlags[tri_index] & (PROJ_FACE_SEAM0 << fidx[0])) == 0) {
+			continue;
+		}
+
+		loop_index = ltri->tri[fidx[0]];
+
+		seam_uvs = ps->loopSeamUVs[loop_index];
+
+		if (seam_uvs[0][0] != FLT_MAX) {
+			continue;
+		}
+
+		fidx[1] = (fidx[0] == 2) ? 0 : fidx[0] + 1;
+
+		vert[0] = ps->mloop_eval[loop_index].v;
+		vert[1] = ps->mloop_eval[ltri->tri[fidx[1]]].v;
+
+		for (uint i = 0; i < 2; i++) {
+			VertSeam *seam;
+			VertSeam *adj = find_adjacent_seam(ps, loop_index, vert[i], &seam);
+			float no[2];
+			float len_fact;
+
+			ang[i] = compute_seam_normal(seam, adj, no);
+
+			len_fact = cosf(ang[i] - M_PI_2);
+			len_fact = UNLIKELY(len_fact < FLT_EPSILON) ? FLT_MAX : (1.0f / len_fact);
+			len_fact = MIN2(len_fact, 5.0f);
+
+			mul_v2_fl(no, ps->seam_bleed_px * len_fact);
+
+			add_v2_v2v2(seam_uvs[i], puv[fidx[i]], no);
+
+			mul_v2_v2(seam_uvs[i], ibuf_inv);
+		}
+
+		/* Handle convergent normals (can self-intersect). */
+		if ((ang[0] + ang[1]) < M_PI) {
+			if (isect_seg_seg_v2_simple(orig_uv[fidx[0]], seam_uvs[0], orig_uv[fidx[1]], seam_uvs[1])) {
+				float isect_co[2];
+
+				isect_seg_seg_v2_point(orig_uv[fidx[0]], seam_uvs[0], orig_uv[fidx[1]], seam_uvs[1], isect_co);
+
+				copy_v2_v2(seam_uvs[0], isect_co);
+				copy_v2_v2(seam_uvs[1], isect_co);
+			}
+		}
+
+	}
+}
+
+static void insert_seam_vert_array(
+        const ProjPaintState *ps, MemArena *arena, const int tri_index,
+        const int fidx1, const int ibuf_x, const int ibuf_y)
+{
+	const MLoopTri *lt = &ps->mlooptri_eval[tri_index];
+	const float *lt_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt)};
+	const int fidx[2] = {fidx1, ((fidx1 + 1) % 3)};
+	float vec[2];
+
+	VertSeam *vseam = BLI_memarena_alloc(arena, sizeof(VertSeam) * 2);
+
+	vseam->prev = NULL;
+	vseam->next = NULL;
+
+	vseam->tri = tri_index;
+	vseam->loop = lt->tri[fidx[0]];
+
+	sub_v2_v2v2(vec, lt_tri_uv[fidx[1]], lt_tri_uv[fidx[0]]);
+	vec[0] *= ibuf_x;
+	vec[1] *= ibuf_y;
+	vseam->angle = atan2f(vec[1], vec[0]);
+
+	/* If face windings are not initialized, something must be wrong. */
+	BLI_assert((ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_INIT) != 0);
+	vseam->normal_cw = (ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_CW);
+
+	copy_v2_v2(vseam->uv, lt_tri_uv[fidx[0]]);
+
+	vseam[1] = vseam[0];
+	vseam[1].angle += vseam[1].angle > 0.0f ? -M_PI : M_PI;
+	vseam[1].normal_cw = !vseam[1].normal_cw;
+	copy_v2_v2(vseam[1].uv, lt_tri_uv[fidx[1]]);
+
+	for (uint i = 0; i < 2; i++) {
+		uint vert = ps->mloop_eval[lt->tri[fidx[i]]].v;
+		ListBase *list = &ps->vertSeams[vert];
+		VertSeam *item = list->first;
+
+		while (item && item->angle < vseam[i].angle) {
+			item = item->next;
+		}
+
+		BLI_insertlinkbefore(list, item, &vseam[i]);
 	}
-	else {
-		no1[0] =  dir3[1] + dir1[1];
-		no1[1] = -dir3[0] - dir1[0];
-		no2[0] =  dir1[1] + dir2[1];
-		no2[1] = -dir1[0] - dir2[0];
-		no3[0] =  dir2[1] + dir3[1];
-		no3[1] = -dir2[0] - dir3[0];
-	}
-
-	a1 = shell_v2v2_normal_dir_to_dist(no1, dir3);
-	a2 = shell_v2v2_normal_dir_to_dist(no2, dir1);
-	a3 = shell_v2v2_normal_dir_to_dist(no3, dir2);
-
-	CLAMP_MAX(a1, scale_clamp);
-	CLAMP_MAX(a2, scale_clamp);
-	CLAMP_MAX(a3, scale_clamp);
-
-	mul_v2_fl(no1, a1 * scaler);
-	mul_v2_fl(no2, a2 * scaler);
-	mul_v2_fl(no3, a3 * scaler);
-	add_v2_v2v2(outset_uv[0], puv[0], no1);
-	add_v2_v2v2(outset_uv[1], puv[1], no2);
-	add_v2_v2v2(outset_uv[2], puv[2], no3);
-
-	mul_v2_v2(outset_uv[0], ibuf_inv);
-	mul_v2_v2(outset_uv[1], ibuf_inv);
-	mul_v2_v2(outset_uv[2], ibuf_inv);
 }
 
 /*
- * Be tricky with flags, first 4 bits are PROJ_FACE_SEAM1 to 4, last 4 bits are PROJ_FACE_N

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list