Does anyone know a simple way of calculating (offline) the amount a texture is distorted when mapped to a triangle like the following?

Vertex   Position       UV Coordinates
A        (Ax, Ay, Az)   (Au, Av)
B        (Bx, By, Bz)   (Bu, Bv)
C        (Cx, Cy, Cz)   (Cu, Cv)

I'm expecting two outputs: one scaling factor along each of two perpendicular axes. (These axes may or may not align with the u and v coordinate axes, eg. for a texture that has been stretched diagonally)

For my present purposes I don't need to know the direction of the stretch/compression, just how much of it there is. I do not want a single area scaling factor, because a stretch in one direction plus a compression in the other could cancel out to give a result that is close to equal-area but far from undistorted.

I think I can get this by computing a matrix that maps (Bxyz - Axyz) onto ((Buv - Auv), 0) & (Cxyz - Axyz) onto ((Cuv - Auv), 0) (and the triangle normal onto (0,0,1)), then finding its eigenvalues, but it seems like this should simplify or offer some shortcut. Working out all the intermediates on paper gets pretty hairy.

My searches so far are returning a lot of advice on subjectively dealing with distortion using 3D modelling packages, not analytical methods for measuring distortion.

  • \$\begingroup\$ "Horizontal" and "Vertical" stretch amount need a basis XY to be meaningful. If the uv axes aren't perpendicular, it's even more ambiguous. I think? \$\endgroup\$ Commented Nov 24, 2014 at 19:33
  • \$\begingroup\$ How about just comparing 3D and UV triangles by aligning 1st point and scaling to average points 2 and 3? Then measuring deviation in square or perimeter? \$\endgroup\$
    – Kromster
    Commented Nov 24, 2014 at 19:57
  • \$\begingroup\$ @davidvanbrink, I think the scaling factors are well-defined. u and v axes are perpendicular here, but axis of stretch may deviate from those axes. \$\endgroup\$
    – DMGregory
    Commented Nov 24, 2014 at 19:59
  • \$\begingroup\$ @KromStern that would give a rough approximation, but if the edges 1-2 and 1-3 happened to lie diagonal to the stretch, the distortion would be under-estimated. \$\endgroup\$
    – DMGregory
    Commented Nov 24, 2014 at 20:01
  • \$\begingroup\$ @davidvanbrink to expand on that, if we translate A to the origin in position & UV space and consider positions in the 2D plane of the triangle, there is a matrix M that transforms positions into UVs. M can be decomposed into M = R2 x S x R1, where R1 & R2 are rotation matrices and S is a scale matrix. We can make this decomposition unique by constraining the angles we're allowed to choose for R1 & R2 (say, choosing the smallest positive angle possible in each case, but all that changes is the order/sign of output). The two scaling factors are the diagonal entries of S after this decomposition \$\endgroup\$
    – DMGregory
    Commented Nov 24, 2014 at 20:19

1 Answer 1


Edit: my original solution didn't work. The eigenvalues of the mappping matrix only correspond to the scale factors for special orientation cases.

Fortunately, I've found a solution that seems to hold in general.

If we take a collection of unit vectors in UV space and map them into 3D, they will form an ellipse, whose major and minor axes correspond to the stretch factors we want.

We don't care about the absolute orientation of these vectors after transformation, just how their length changes, according to the equation of an ellipse centered at the origin:

A * u*u + B * u*v + C * v*v = l*l

So first we need to find the parameters A, B, and C. (I'll rename the vertices to V1, V2, V3 so they don't conflict with the conventional labeling of these coefficients)

Consider the set of edges of the triangle (in both 3D and uv space):

E1 = V1 - V3
E2 = V2 - V1
E3 = V3 - V2

We can construct a system of equations to find the parameters as follows:

$$ \begin{bmatrix} E1.u * E1.u & E1.u * E1.v & E1.v * E1.v \\ E2.u * E2.u & E2.u * E2.v & E2.v * E2.v \\ E3.u * E3.u & E3.u * E3.v & E3.v * E3.v \\ \end{bmatrix} \begin{bmatrix} A \\ B \\ C \\ \end{bmatrix}= \begin{bmatrix} E1.xyz\cdot E1.xyz \\ E2.xyz\cdot E2.xyz \\ E3.xyz\cdot E3.xyz \\ \end{bmatrix}$$

Note that by construction this is insensitive to the rotation of the triangle in 3D, which is a good start, and doesn't need any normalizations to get there unlike my previous solution.

If the determinant of the coefficient matrix is zero, then the UV triangle is degenerate (at least one of the scale factors is infinite), and needs special-case handling which I'll elide for now.

After solving this by your favourite method (I used Cramer's Rule for a crude test), we have two cases for finding the axis scale factors:

float p;
float q;

if(ApproximatelyEqual(A, C))
  // Ellipse is symmetrical across the line u=v.
  // (Either theta = PI/4, or B = 0 and scaling is uniform).
  p = A - 0.5f * B;
  q = A + 0.5f * B;
  // Ellipse is non-uniformly scaled and arbitrarily rotated.
  // (Or a rounding error makes it appear slightly so)
  // Find its angle:
  float theta = atan(B/(C - A))/2f;

  // cos squared
  float c = cos(theta);
  c *= c;

  // sin squared
  float s = 1f - c;

  float divisor = 1f/(c - s);

  p = (A * c - C * s) * divisor;
  q = (C * c - A * s) * divisor;

// Note that numerical errors can sometimes cause p & q to be slightly negative.
// This isn't meaningful, so clamp to a non-negative value before taking the root.
float scale1 = sqrt(pos(p));
float scale2 = sqrt(pos(q));

Although I didn't need it, this calculates the direction of non-uniform stretch too (theta). It does not directly detect reflection, but this can be added by checking the winding of the triangle in uv space.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .