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