If it's useful, this is the relevant Python code I'm using in my Blender importer:
Code:
def signed_angle(a, b, normal):
# See https://stackoverflow.com/a/33920320
s = a.cross(b).dot(normal)
c = a.dot(b)
return math.degrees(math.atan2(s, c))
pt1 = mathutils.Vector( (textureMap.x1, textureMap.y1, textureMap.z1) )
pt2 = mathutils.Vector( (textureMap.x2, textureMap.y2, textureMap.z2) )
pt3 = mathutils.Vector( (textureMap.x3, textureMap.y3, textureMap.z3) )
# Pre-calculate useful values
len_p1p2 = (pt2 - pt1).length
len_p1p3 = (pt3 - pt1).length
if textureMap.projType == 'PLANAR':
n1 = (pt2 - pt1).normalized()
n2 = (pt3 - pt1).normalized()
elif textureMap.projType == 'CYLINDRICAL':
n1 = (pt2 - pt1).normalized()
elif textureMap.projType == 'SPHERICAL':
# take the cross product of the two vectors extending from pt2 to get a third vector n3 at right angles to those.
n3 = (pt1 - pt2).cross(pt3 - pt2).normalized()
n2 = (pt1 - pt2).cross(n3).normalized()
for i in range(len(newPoints)):
# Get the point in question (scaling the point back to original ldraw size, just for the purposes of UV coordinate calculation)
pt = mathutils.Vector( newPoints[i] / globalScaleFactor )
if textureMap.projType == 'PLANAR':
# Calculate U
if len_p1p2 > 1e-8:
u = math.fabs((pt1 - pt).dot(n1)) / len_p1p2
else:
u = 0.0
# Calculate V
if len_p1p3 > 1e-8:
v = math.fabs((pt1 - pt).dot(n2)) / len_p1p3
else:
v = 0.0
elif textureMap.projType == 'CYLINDRICAL':
# Calculate U
distanceToPlane = (pt1 - pt).dot(n1)
ptOnPlane = pt - distanceToPlane * n1
u = LDrawGeometry.signed_angle(ptOnPlane - pt1, pt3 - pt1, -n1)
u /= textureMap.a # U is normalised
u += 0.5 # U is centred
# Calculate V
if len_p1p2 > 1e-8:
v = distanceToPlane / len_p1p2
else:
v = 0.0
elif textureMap.projType == 'SPHERICAL':
# Calculate U
distanceToPlane = (pt - pt1).dot(n3)
ptOnPlane = pt - distanceToPlane * n3
u = LDrawGeometry.signed_angle(ptOnPlane - pt1, pt2 - pt1, n3)
u /= textureMap.a # U is normalised
u += 0.5 # U is centred
# Calculate V
distanceToPlane = (pt - pt1).dot(n2)
ptOnPlane = pt - distanceToPlane * n2
v = LDrawGeometry.signed_angle(ptOnPlane - pt1, pt2 - pt1, n2)
v /= textureMap.b # V is normalised
v += 0.5 # V is centred
else:
# Unknown UV projection type
u = 0.0
v = 0.0
<store uv for point>