1 """A module for tangent space calculation."""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 from pyffi.utils.mathutils import *
41
42 -def getTangentSpace(vertices = None, normals = None, uvs = None,
43 triangles = None, orientation = False,
44 orthogonal = True):
45 """Calculate tangent space data.
46
47 >>> vertices = [(0,0,0), (0,1,0), (1,0,0)]
48 >>> normals = [(0,0,1), (0,0,1), (0,0,1)]
49 >>> uvs = [(0,0), (0,1), (1,0)]
50 >>> triangles = [(0,1,2)]
51 >>> getTangentSpace(vertices = vertices, normals = normals, uvs = uvs, triangles = triangles)
52 ([(0.0, 1.0, 0.0), (0.0, 1.0, 0.0), (0.0, 1.0, 0.0)], [(1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (1.0, 0.0, 0.0)])
53
54 :param vertices: A list of vertices (triples of floats/ints).
55 :param normals: A list of normals (triples of floats/ints).
56 :param uvs: A list of uvs (pairs of floats/ints).
57 :param triangles: A list of triangle indices (triples of ints).
58 :param orientation: Set to ``True`` to return orientation (this is used by
59 for instance Crysis).
60 :return: Two lists of vectors, tangents and binormals. If C{orientation}
61 is ``True``, then returns an extra list with orientations (containing
62 floats which describe the total signed surface of all faces sharing
63 the particular vertex).
64 """
65
66
67 if len(vertices) != len(normals) or len(vertices) != len(uvs):
68 raise ValueError(
69 "lists of vertices, normals, and uvs must have the same length")
70
71 bin = [(0,0,0) for i in xrange(len(vertices)) ]
72 tan = [(0,0,0) for i in xrange(len(vertices)) ]
73 orientations = [0 for i in xrange(len(vertices))]
74
75
76 for t1, t2, t3 in triangles:
77
78 if t1 == t2 or t2 == t3 or t3 == t1:
79 continue
80
81
82 v1 = vertices[t1]
83 v2 = vertices[t2]
84 v3 = vertices[t3]
85 w1 = uvs[t1]
86 w2 = uvs[t2]
87 w3 = uvs[t3]
88 v2v1 = vecSub(v2, v1)
89 v3v1 = vecSub(v3, v1)
90 w2w1 = vecSub(w2, w1)
91 w3w1 = vecSub(w3, w1)
92
93
94 r = w2w1[0] * w3w1[1] - w3w1[0] * w2w1[1]
95
96
97 r_sign = (1 if r >= 0 else -1)
98
99
100 sdir = (
101 r_sign * (w3w1[1] * v2v1[0] - w2w1[1] * v3v1[0]),
102 r_sign * (w3w1[1] * v2v1[1] - w2w1[1] * v3v1[1]),
103 r_sign * (w3w1[1] * v2v1[2] - w2w1[1] * v3v1[2]))
104 try:
105 sdir = vecNormalized(sdir)
106 except ZeroDivisionError:
107 continue
108 except ValueError:
109 continue
110
111 tdir = (
112 r_sign * (w2w1[0] * v3v1[0] - w3w1[0] * v2v1[0]),
113 r_sign * (w2w1[0] * v3v1[1] - w3w1[0] * v2v1[1]),
114 r_sign * (w2w1[0] * v3v1[2] - w3w1[0] * v2v1[2]))
115 try:
116 tdir = vecNormalized(tdir)
117 except ZeroDivisionError:
118 continue
119 except ValueError:
120 continue
121
122
123 for i in (t1, t2, t3):
124 tan[i] = vecAdd(tan[i], tdir)
125 bin[i] = vecAdd(bin[i], sdir)
126 orientations[i] += r
127
128
129 xvec = (1, 0, 0)
130 yvec = (0, 1, 0)
131 for i, norm in enumerate(normals):
132 if abs(1-vecNorm(norm)) > 0.01:
133 raise ValueError(
134 "tangentspace: unnormalized normal in list of normals (%s, norm is %f)" % (norm, vecNorm(norm)))
135 try:
136
137 bin[i] = vecSub(bin[i],
138 vecscalarMul(
139 norm,
140 vecDotProduct(norm, bin[i])))
141 bin[i] = vecNormalized(bin[i])
142 tan[i] = vecSub(tan[i],
143 vecscalarMul(
144 norm,
145 vecDotProduct(norm, tan[i])))
146 tan[i] = vecSub(tan[i],
147 vecscalarMul(
148 bin[i],
149 vecDotProduct(norm, bin[i])))
150 tan[i] = vecNormalized(tan[i])
151 except ZeroDivisionError:
152
153
154 bin[i] = vecCrossProduct(xvec, norm)
155 try:
156 bin[i] = vecNormalized(bin[i])
157 except ZeroDivisionError:
158 bin[i] = vecCrossProduct(yvec, norm)
159 bin[i] = vecNormalized(bin[i])
160 tan[i] = vecCrossProduct(norm, bin[i])
161
162
163 if orientation:
164 return tan, bin, orientations
165 else:
166 return tan, bin
167
168 if __name__ == "__main__":
169 import doctest
170 doctest.testmod()
171