Package theia :: Package helpers :: Module geometry
[hide private]
[frames] | no frames]

Source Code for Module theia.helpers.geometry

  1  '''Geometry module for theia.''' 
  2   
  3  # Provides: 
  4  #   refrAngle 
  5  #   linePlaneInter 
  6  #   lineSurfInter 
  7  #   lineCylInter 
  8  #   newDir 
  9  #   rotMatrix 
 10  #   basis 
 11  #   rectToSph 
 12   
 13  from math import atan2 
 14  import numpy as np 
 15  np.seterr(divide = 'raise', invalid = 'raise')  # np raises FloatingPointError 
 16   
 17  from .tools import TotalReflectionError 
 18  from .units import pi 
 19  from . import settings 
 20   
21 -def refrAngle(theta, n1, n2):
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
32 -def linePlaneInter(pos, dirV, planeC, normV, diameter):
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, # return this if no intersect 50 'distance': 0., 51 'intersection point': np.array([0., 0., 0.], dtype=np.float64) 52 } 53 54 # If plane and dirV are orthogonal then no intersection 55 if np.dot(normV, dirV) == 0.: 56 return noInterDict 57 58 # If not then there is a solution to pos + lam*dirV in plane, it is: 59 lam = np.dot(normV, planeC - pos)/np.dot(normV, dirV) 60 61 # If lam is *negative* no intersection 62 if lam <= settings.zero: 63 return noInterDict 64 65 # find intersection point: 66 intersect = pos + lam * dirV 67 dist = np.linalg.norm(intersect - planeC) 68 69 # if too far from center, no intersection: 70 if dist >= diameter / 2.: 71 return noInterDict 72 73 return {'isHit': True, 74 'distance': lam, 75 'intersection point': intersect}
76
77 -def lineSurfInter(pos, dirV, chordC, chordNorm, kurv, diameter):
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, # return this if no intersect 101 'distance': 0., 102 'intersection point': np.array([0., 0., 0.], dtype=np.float64)} 103 104 # if surface is too plane, it is a plane 105 if np.abs(kurv) < settings.flatK: 106 return linePlaneInter(pos, dirV, chordC, chordNorm, diameter) 107 108 109 # find center of curvature of surface: 110 try: 111 theta = np.arcsin(diameter*kurv/2.) # this is half undertending angle 112 except FloatingPointError: 113 theta = pi/2. 114 sphereC = chordC + np.cos(theta)*chordNorm/kurv 115 R = 1/kurv # radius 116 PC = sphereC - pos # vector from pos to center of curvature 117 118 # first find out if there is a intersection between the line and the whole 119 # sphere. a point pos + lam * dirV is on the sphere if and only if it is 120 # at distance R from sphereC. 121 122 # discriminant of polynomial ||pos + lam*dirV - sphereC||**2 = R**2 123 delta = 4.*(np.dot(dirV,PC))**2. + 4.*(R**2. - np.linalg.norm(PC)**2.) 124 125 if delta <= 0.: 126 # no intersection at all or beam is tangent to surface 127 return noInterDict 128 129 # intersection parameters 130 lam1 = ( 2.*np.dot(dirV, PC) - np.sqrt(delta))/2. # < lam2 131 lam2 = ( 2.*np.dot(dirV, PC) + np.sqrt(delta))/2. 132 133 if lam1 < settings.zero and lam2 < settings.zero: 134 # sphere is behind 135 return noInterDict 136 137 if lam1 < settings.zero and lam2 > settings.zero: 138 # we found a point and have to verify that its on the surface (we 139 # already know its on the sphere) 140 intersect = pos + lam2 * dirV 141 localNorm = sphereC - intersect 142 localNorm = localNorm/np.linalg.norm(localNorm) 143 144 # compare angles theta and thetai (between chordN and localN) to 145 # to know if the point is on the coated surface 146 if np.dot(localNorm, chordNorm) < 0.: 147 # the intersection point is on the wrong semi-sphere: 148 return noInterDict 149 150 if np.linalg.norm(np.cross(localNorm, chordNorm)) < diameter * kurv/2. : 151 # it is on the surface 152 return {'isHit': True, 153 'distance': lam2, 154 'intersection point': intersect} 155 156 if lam1 > settings.zero and lam2 > settings.zero: 157 # we got two points, take the closest which is on the surface 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 # the first is on the surface 166 return {'isHit': True, 167 'distance': lam1, 168 'intersection point': intersect} 169 170 # try the second 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 # the second is on the surface 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, # return this if no intersect 211 'distance': 0., 212 'intersection point': np.array([0., 0., 0.], dtype=np.float64)} 213 214 # if the line is parallel to the axis of the cylinder, no intersection 215 if np.abs(np.dot(dirV,normV)) == 1.: 216 return noInterDict 217 218 # parameters 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. # center of cylinder 226 dist = np.sqrt(R**2. + thickness**2./4.) # distance from center to edge 227 228 # the cylinder's axis is faceC + x*normV for x real. this axis is at 229 # a distance R to pos + lam*dirV if a P(lam)=0 whose discriminant is: 230 delta = 4.*(dirn*PCn - PCdir)**2. - 4.*(1.-dirn**2.)*(PC2 - R**2. - PCn**2.) 231 232 if delta <= 0.: 233 #no intersection or line is tangent 234 return noInterDict 235 236 # intersection parameters 237 lam1 = (2.*(PCdir - dirn*PCn) - np.sqrt(delta))/(2.*(1. - dirn**2.))# < lam2 238 lam2 = (2.*(PCdir - dirn*PCn) + np.sqrt(delta))/(2.*(1. - dirn**2.)) 239 240 if lam1 < settings.zero and lam2 < settings.zero: 241 # cylinder is behind 242 return noInterDict 243 244 if lam1 < settings.zero and lam2 > settings.zero: 245 # we found a point and have to verify that its on the physical surface 246 intersect = pos + lam2 * dirV 247 248 # check if it is at distance sqrt(R**2 + dia**2/4) or less to center: 249 if np.linalg.norm(intersect - center) < dist: 250 # it is on the cylinder 251 return {'isHit': True, 252 'distance': lam2, 253 'intersection point': intersect} 254 255 if lam1 > settings.zero and lam2 > settings.zero: 256 # we got two points, take the closest which is on the surface 257 intersect = pos + lam1 * dirV 258 259 if np.linalg.norm(intersect - center) < dist: 260 # the first is on the surface 261 return {'isHit': True, 262 'distance': lam1, 263 'intersection point': intersect} 264 265 # try the second 266 intersect = pos + lam2 * dirV 267 268 if np.linalg.norm(intersect - center) < dist : 269 # the second is on the surface 270 return {'isHit': True, 271 'distance': lam2, 272 'intersection point': intersect} 273 274 return noInterDict
275
276 -def newDir(inc, nor, n1, n2):
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 # normal incidence case: 293 if np.abs(np.dot(inc,nor)) == 1.: 294 return {'r': nor, 295 't': inc, 296 'TR': False} 297 298 # reflected (see documentation): 299 refl = inc - 2.*np.dot(inc,nor)*nor 300 refl = refl/np.linalg.norm(refl) 301 302 # incident and refracted angles 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 # sines and cosines 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 # refracted: 320 refr = (alpha*inc + beta*nor) 321 refr = refr/np.linalg.norm(refr) 322 return {'r': refl, 323 't': refr, 324 'TR': False}
325
326 -def rotMatrix(a,b):
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
346 -def basis(a):
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: #tan(pi/2) = inf 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
367 -def rectToSph(array):
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]) #arccos(z) 376 phi = atan2(array[1], array[0]) 377 378 return theta, phi
379