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 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
31 -def linePlaneInter(pos, dirV, planeC, normV, diameter):
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, # return this if no intersect 49 'distance': 0., 50 'intersection point': np.array([0., 0., 0.], dtype=np.float64) 51 } 52 53 # If plane and dirV are orthogonal then no intersection 54 if np.dot(normV, dirV) == 0.: 55 return noInterDict 56 57 # If not then there is a solution to pos + lam*dirV in plane, it is: 58 lam = np.dot(normV, planeC - pos)/np.dot(normV, dirV) 59 60 # If lam is *negative* no intersection 61 if lam <= settings.zero: 62 return noInterDict 63 64 # find intersection point: 65 intersect = pos + lam * dirV 66 dist = np.linalg.norm(intersect - planeC) 67 68 # if too far from center, no intersection: 69 if dist >= diameter / 2.: 70 return noInterDict 71 72 return {'isHit': True, 73 'distance': lam, 74 'intersection point': intersect}
75
76 -def lineSurfInter(pos, dirV, chordC, chordNorm, kurv, diameter):
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, # return this if no intersect 100 'distance': 0., 101 'intersection point': np.array([0., 0., 0.], dtype=np.float64)} 102 103 # if surface is too plane, it is a plane 104 if np.abs(kurv) < settings.flatK: 105 return linePlaneInter(pos, dirV, chordC, chordNorm, diameter) 106 107 # find center of curvature of surface: 108 theta = np.arcsin(diameter*kurv/2.) if np.abs(diameter*kurv/2.) <= 1.\ 109 else pi/2. # this is half undertending angle 110 sphereC = chordC + np.cos(theta)*chordNorm/kurv 111 R = 1/kurv # radius 112 PC = sphereC - pos # vector from pos to center of curvature 113 114 # first find out if there is a intersection between the line and the whole 115 # sphere. a point pos + lam * dirV is on the sphere if and only if it is 116 # at distance R from sphereC. 117 118 # discriminant of polynomial ||pos + lam*dirV - sphereC||**2 = R**2 119 delta = 4.*(np.dot(dirV,PC))**2. + 4.*(R**2. - np.linalg.norm(PC)**2.) 120 121 if delta <= 0.: 122 # no intersection at all or beam is tangent to surface 123 return noInterDict 124 125 # intersection parameters 126 lam1 = ( 2.*np.dot(dirV, PC) - np.sqrt(delta))/2. # < lam2 127 lam2 = ( 2.*np.dot(dirV, PC) + np.sqrt(delta))/2. 128 129 if lam1 < settings.zero and lam2 < settings.zero: 130 # sphere is behind 131 return noInterDict 132 133 if lam1 < settings.zero and lam2 > settings.zero: 134 # we found a point and have to verify that its on the surface (we 135 # already know its on the sphere) 136 intersect = pos + lam2 * dirV 137 localNorm = sphereC - intersect 138 localNorm = localNorm/np.linalg.norm(localNorm) 139 140 # compare angles theta and thetai (between chordN and localN) to 141 # to know if the point is on the coated surface 142 if np.dot(localNorm, chordNorm) < 0.: 143 # the intersection point is on the wrong semi-sphere: 144 return noInterDict 145 146 if np.linalg.norm(np.cross(localNorm, chordNorm)) < diameter * kurv/2.: 147 # it is on the surface 148 return {'isHit': True, 149 'distance': lam2, 150 'intersection point': intersect} 151 152 if lam1 > settings.zero and lam2 > settings.zero: 153 # we got two points, take the closest which is on the surface 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 # the first is on the surface 162 return {'isHit': True, 163 'distance': lam1, 164 'intersection point': intersect} 165 166 # try the second 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 # the second is on the surface 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, # return this if no intersect 207 'distance': 0., 208 'intersection point': np.array([0., 0., 0.], dtype=np.float64)} 209 210 # if the line is parallel to the axis of the cylinder, no intersection 211 if np.abs(np.dot(dirV,normV)) == 1.: 212 return noInterDict 213 214 # parameters 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. # center of cylinder 222 dist = np.sqrt(R**2. + thickness**2./4.) # distance from center to edge 223 224 # the cylinder's axis is faceC + x*normV for x real. this axis is at 225 # a distance R to pos + lam*dirV if a P(lam)=0 whose discriminant is: 226 delta = 4.*(dirn*PCn - PCdir)**2. - 4.*(1.-dirn**2.)*(PC2 - R**2. - PCn**2.) 227 228 if delta <= 0.: 229 #no intersection or line is tangent 230 return noInterDict 231 232 # intersection parameters 233 lam1 = (2.*(PCdir - dirn*PCn) - np.sqrt(delta))/(2.*(1. - dirn**2.))# < lam2 234 lam2 = (2.*(PCdir - dirn*PCn) + np.sqrt(delta))/(2.*(1. - dirn**2.)) 235 236 if lam1 < settings.zero and lam2 < settings.zero: 237 # cylinder is behind 238 return noInterDict 239 240 if lam1 < settings.zero and lam2 > settings.zero: 241 # we found a point and have to verify that its on the physical surface 242 intersect = pos + lam2 * dirV 243 244 # check if it is at distance sqrt(R**2 + dia**2/4) or less to center: 245 if np.linalg.norm(intersect - center) < dist: 246 # it is on the cylinder 247 return {'isHit': True, 248 'distance': lam2, 249 'intersection point': intersect} 250 251 if lam1 > settings.zero and lam2 > settings.zero: 252 # we got two points, take the closest which is on the surface 253 intersect = pos + lam1 * dirV 254 255 if np.linalg.norm(intersect - center) < dist: 256 # the first is on the surface 257 return {'isHit': True, 258 'distance': lam1, 259 'intersection point': intersect} 260 261 # try the second 262 intersect = pos + lam2 * dirV 263 264 if np.linalg.norm(intersect - center) < dist : 265 # the second is on the surface 266 return {'isHit': True, 267 'distance': lam2, 268 'intersection point': intersect} 269 270 return noInterDict
271
272 -def newDir(inc, nor, n1, n2):
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 # normal incidence case: 289 if np.abs(np.dot(inc,nor)) == 1.: 290 return {'r': nor, 291 't': inc, 292 'TR': False} 293 294 # reflected (see documentation): 295 refl = inc - 2.*np.dot(inc,nor)*nor 296 refl = refl/np.linalg.norm(refl) 297 298 # incident and refracted angles 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 # sines and cosines 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 # refracted: 316 refr = (alpha*inc + beta*nor) 317 refr = refr/np.linalg.norm(refr) 318 return {'r': refl, 319 't': refr, 320 'TR': False}
321
322 -def rotMatrix(a,b):
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
342 -def basis(a):
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
361 -def rectToSph(array):
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]) #arccos(z) 370 phi = atan2(array[1], array[0]) 371 372 return theta, phi
373