1 '''Geometry module for theia.'''
2
3
4
5
6
7
8
9
10
11
12
13 from math import atan2
14 import numpy as np
15 np.seterr(divide = 'raise', invalid = 'raise')
16
17 from .tools import TotalReflectionError
18 from .units import pi
19 from . import settings
20
22 '''Returns the refraction angle at n1/n2 interface for incoming theta.
23
24 May raise a TotalReflectionError.
25 '''
26 try:
27 return np.arcsin(n1*np.sin(theta)/n2)
28 except FloatingPointError:
29 msg = 'Total reflection occured.'
30 raise TotalReflectionError(msg)
31
33 '''Computes the intersection between a line and a plane.
34
35 pos: position of the begining of the line. [3D vector]
36 dirV: directing vector of the line. [3D vector]
37 planeC: position of the center of the plane. [3D vector]
38 normV: vector normal to the plane. [3D vector]
39 diameter: diameter of the plane.
40
41 Returns a dictionary with keys:
42 'isHit': whether of not the plane is hit. [boolean]
43 'distance': geometrical distance from line origin to intersection point.
44 [float]
45 'intersection point': position of intersection point. [3D vector]
46
47
48 '''
49 noInterDict = {'isHit': False,
50 'distance': 0.,
51 'intersection point': np.array([0., 0., 0.], dtype=np.float64)
52 }
53
54
55 if np.dot(normV, dirV) == 0.:
56 return noInterDict
57
58
59 lam = np.dot(normV, planeC - pos)/np.dot(normV, dirV)
60
61
62 if lam <= settings.zero:
63 return noInterDict
64
65
66 intersect = pos + lam * dirV
67 dist = np.linalg.norm(intersect - planeC)
68
69
70 if dist >= diameter / 2.:
71 return noInterDict
72
73 return {'isHit': True,
74 'distance': lam,
75 'intersection point': intersect}
76
78 '''Computes the intersection between a line and a spherical surface.
79
80 The spherical surface is supposed to have a cylindrical symmetry around
81 the vector normal to the 'chord', ie the plane which undertends
82 the surface.
83
84 Note: the normal vector always looks to the center of the sphere and the
85 surface is supposed to occupy less than a semi-sphere
86
87 pos: position of the begingin of the line. [3D vector]
88 dirV: direction of the line. [3D vector]
89 chordC: position of the center of the 'chord'. [3D vector]
90 chordNorm: normal vector the the chord in its center. [3D vector]
91 kurv: curvature (1/ROC) of the surface. [float]
92 diameter: diameter of the surface. [float]
93
94 Returns a dictionary with keys:
95 'is Hit': whether the surface is hit or not. [boolean]
96 'distance': distance to the intersection point from pos. [float]
97 'intersection point': position of intersection point. [3D vector]
98
99 '''
100 noInterDict = {'isHit': False,
101 'distance': 0.,
102 'intersection point': np.array([0., 0., 0.], dtype=np.float64)}
103
104
105 if np.abs(kurv) < settings.flatK:
106 return linePlaneInter(pos, dirV, chordC, chordNorm, diameter)
107
108
109
110 try:
111 theta = np.arcsin(diameter*kurv/2.)
112 except FloatingPointError:
113 theta = pi/2.
114 sphereC = chordC + np.cos(theta)*chordNorm/kurv
115 R = 1/kurv
116 PC = sphereC - pos
117
118
119
120
121
122
123 delta = 4.*(np.dot(dirV,PC))**2. + 4.*(R**2. - np.linalg.norm(PC)**2.)
124
125 if delta <= 0.:
126
127 return noInterDict
128
129
130 lam1 = ( 2.*np.dot(dirV, PC) - np.sqrt(delta))/2.
131 lam2 = ( 2.*np.dot(dirV, PC) + np.sqrt(delta))/2.
132
133 if lam1 < settings.zero and lam2 < settings.zero:
134
135 return noInterDict
136
137 if lam1 < settings.zero and lam2 > settings.zero:
138
139
140 intersect = pos + lam2 * dirV
141 localNorm = sphereC - intersect
142 localNorm = localNorm/np.linalg.norm(localNorm)
143
144
145
146 if np.dot(localNorm, chordNorm) < 0.:
147
148 return noInterDict
149
150 if np.linalg.norm(np.cross(localNorm, chordNorm)) < diameter * kurv/2. :
151
152 return {'isHit': True,
153 'distance': lam2,
154 'intersection point': intersect}
155
156 if lam1 > settings.zero and lam2 > settings.zero:
157
158 intersect = pos + lam1 * dirV
159 localNorm = sphereC - intersect
160 localNorm = localNorm/np.linalg.norm(localNorm)
161
162 if np.dot(localNorm, chordNorm) > 0. :
163 if np.linalg.norm(np.cross(localNorm, chordNorm))\
164 < diameter * kurv/2.:
165
166 return {'isHit': True,
167 'distance': lam1,
168 'intersection point': intersect}
169
170
171 intersect = pos + lam2 * dirV
172 localNorm = sphereC - intersect
173 localNorm = localNorm/np.linalg.norm(localNorm)
174
175 if np.dot(localNorm, chordNorm) > 0. :
176 if np.linalg.norm(np.cross(localNorm, chordNorm))\
177 < diameter * kurv/2. :
178
179 return {'isHit': True,
180 'distance': lam2,
181 'intersection point': intersect}
182
183 return noInterDict
184
185 -def lineCylInter(pos, dirV, faceC, normV, thickness, diameter):
186 '''Computes the intersection of a line and a cylinder in 3D space.
187
188 The cylinder is specified by a disk of center faceC, an outgoing normal
189 normV, a thickness (thus behind the normal) and a diameter.
190
191 pos: origin of the line. [3D vector]
192 dirV: directing vector of the line. [3D vector]
193 faceC: center of the face of the cylinder where lies the normal vector.
194 [3D vector]
195 normV: normal vector to this face (outgoing). [3D vector]
196 thickness: thickness of the cylinder (counted from faceC and behind normV)
197 [float]
198 diameter: of the cylinder. [float]
199
200 Returns a dictionary with keys:
201 'isHit': whether of not. [boolean]
202 'distance': geometrical distance of the intersection point from pos.
203 [float]
204 'intersection point': point of intersection. [3D vector]
205
206 '''
207 diameter = float(diameter)
208 thickness = float(thickness)
209
210 noInterDict = {'isHit': False,
211 'distance': 0.,
212 'intersection point': np.array([0., 0., 0.], dtype=np.float64)}
213
214
215 if np.abs(np.dot(dirV,normV)) == 1.:
216 return noInterDict
217
218
219 PC = faceC - pos
220 dirn = np.dot(dirV, normV)
221 PCn = np.dot(PC, normV)
222 PCdir = np.dot(PC, dirV)
223 PC2 = np.dot(PC,PC)
224 R = diameter/2
225 center = faceC - thickness*normV/2.
226 dist = np.sqrt(R**2. + thickness**2./4.)
227
228
229
230 delta = 4.*(dirn*PCn - PCdir)**2. - 4.*(1.-dirn**2.)*(PC2 - R**2. - PCn**2.)
231
232 if delta <= 0.:
233
234 return noInterDict
235
236
237 lam1 = (2.*(PCdir - dirn*PCn) - np.sqrt(delta))/(2.*(1. - dirn**2.))
238 lam2 = (2.*(PCdir - dirn*PCn) + np.sqrt(delta))/(2.*(1. - dirn**2.))
239
240 if lam1 < settings.zero and lam2 < settings.zero:
241
242 return noInterDict
243
244 if lam1 < settings.zero and lam2 > settings.zero:
245
246 intersect = pos + lam2 * dirV
247
248
249 if np.linalg.norm(intersect - center) < dist:
250
251 return {'isHit': True,
252 'distance': lam2,
253 'intersection point': intersect}
254
255 if lam1 > settings.zero and lam2 > settings.zero:
256
257 intersect = pos + lam1 * dirV
258
259 if np.linalg.norm(intersect - center) < dist:
260
261 return {'isHit': True,
262 'distance': lam1,
263 'intersection point': intersect}
264
265
266 intersect = pos + lam2 * dirV
267
268 if np.linalg.norm(intersect - center) < dist :
269
270 return {'isHit': True,
271 'distance': lam2,
272 'intersection point': intersect}
273
274 return noInterDict
275
277 '''Computes the refl and refr directions produced by inc at interface n1/n2.
278
279 inc: director vector of incoming beam. [3D vector]
280 nor: normal to the interface at the intersection point. [3D vector]
281 n1: refractive index of the first medium. [float]
282 n2: idem.
283
284 Returns a dictionary with keys:
285 'r': normalized direction of reflected beam. [3D vector]
286 't': normalized direction of refracted beam. [3D vector]
287 'TR': was there total reflection?. [boolean]
288
289 Note: if total reflection then refr is None.
290
291 '''
292
293 if np.abs(np.dot(inc,nor)) == 1.:
294 return {'r': nor,
295 't': inc,
296 'TR': False}
297
298
299 refl = inc - 2.*np.dot(inc,nor)*nor
300 refl = refl/np.linalg.norm(refl)
301
302
303 theta1 = np.arccos(- np.dot(nor,inc))
304 try:
305 theta2 = refrAngle(theta1, n1, n2)
306 except TotalReflectionError :
307 return {'r': refl,
308 't': None,
309 'TR': True}
310
311
312 c1 = np.cos(theta1)
313 c2 = np.cos(theta2)
314 s1 = np.sin(theta1)
315
316 alpha = n1/n2
317 beta = n1*c1/n2 - c2
318
319
320 refr = (alpha*inc + beta*nor)
321 refr = refr/np.linalg.norm(refr)
322 return {'r': refl,
323 't': refr,
324 'TR': False}
325
327 '''Provides the rotation matrix which maps a (unit) to b (unit).
328
329 a,b: unit 3D vectors. [3D np.arrays]
330
331 Returns an np.array such that np.matmul(M,a) == b.
332
333 '''
334
335 if np.abs(np.dot(a,b)) == 1.:
336 return np.dot(a,b) *np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
337 dtype=np.float64)
338
339 v = np.cross(a,b)
340 vx = np.array([0., -v[2], v[1]], [v[2], 0., -v[0]], [-v[1], v[0], 0.],
341 dtype=np.float64)
342
343 return np.array([1., 0., 0.], [0., 1., 0.], [0., 0., 1.], dtype=np.float64)\
344 + vx + (1.0/(1.0 + np.dot(a,b)))*np.matmul(vx,vx)
345
347 '''Returns two vectors u and v such that (a, u, v) is a direct ON basis.
348
349 '''
350 ez = np.array([0., 0., 1.], dtype = np.float64)
351
352 if np.abs(np.dot(a, ez)) == 1.:
353 u = np.dot(a, ez) * np.array([1., 0., 0.], dtype = np.float64)
354 v = np.array([0., 1., 0.], dtype = np.float64)
355 return u,v
356 else:
357 theta = np.arccos(np.dot(a, ez))
358 try:
359 u = ez/np.sin(theta) - a/np.tan(theta)
360 except FloatingPointError:
361 u = ez
362 v = np.cross(a, u)
363 u = u/np.linalg.norm(u)
364 v = v/np.linalg.norm(v)
365 return u, v
366
368 '''Returns the spherical coordinates of the unitary vector given by array.
369
370 array: 3D vector (unitary). [float]
371
372 Returns the theta and phi angles in radians with theta in [0, pi] and phi
373 in [-pi, pi]
374 '''
375 theta = np.arccos(array[2])
376 phi = atan2(array[1], array[0])
377
378 return theta, phi
379