_simulator.pyx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. # -*- coding: utf-8 -*-
  2. import time
  3. import threading
  4. from Queue import Queue, Empty
  5. input_queue = Queue()
  6. output_queue = Queue()
  7. from libc.stdlib cimport malloc, free, calloc
  8. cimport cython
  9. cdef import from "stdlib.h":
  10. void *memset(void *str, int c, size_t n)
  11. void *memcpy(void *str1, void *str2, size_t n)
  12. void *memmove(void *str1, void *str2, size_t n)
  13. import numpy as np
  14. cimport numpy as np
  15. import math
  16. cdef import from "math.h":
  17. double cos(double)
  18. double sin(double)
  19. double log10(double)
  20. cdef struct Ray:
  21. double dx
  22. double dy
  23. double x
  24. double y
  25. double power
  26. int dist
  27. int source_id
  28. int generation
  29. #~ cdef double NONE = 10**9
  30. #~ cdef int NONE_INT = 10**9
  31. cdef inline int fast_10log10(double v):
  32. cdef long long int_repr_of_double
  33. cdef long long first_sign_bit = 2<<50
  34. cdef long long second_sign_bit = 2<<49
  35. cdef long long third_sign_bit = 2<<48
  36. cdef int res
  37. # cast from double to int64
  38. int_repr_of_double = (<long long*>&v)[0]
  39. # estimate log10 (extract Bit 62 - 52, see IEEE-754)
  40. # assume no negative values (ignore bit 63)
  41. # divide by 3 to change from log2 to log10 (log10(2) == 0.301
  42. # and then multiplicate by 10 ( so we multiplicate by 3...)
  43. res = ((int_repr_of_double>>52) - 1023) * 3
  44. # analyze first 3 bits of significant
  45. # that will give us more accuracy (and we underestimate res by range(0..3) )
  46. if (int_repr_of_double & first_sign_bit) > 0:
  47. res += 1
  48. if (int_repr_of_double & second_sign_bit) > 0:
  49. res += 1
  50. if (int_repr_of_double & third_sign_bit) > 0:
  51. res += 1
  52. return res
  53. cdef class Rays:
  54. cdef Ray *handle
  55. cdef public int count # number of used rays
  56. cdef int spawn_idx
  57. cdef public int size # number of allocated rays
  58. def __init__(Rays self, source=None, int numrays=0, int angle=0):
  59. self.count = 0
  60. if source is not None:
  61. self.initFromSource(source, numrays, angle)
  62. elif numrays > 0:
  63. self.alloc(numrays)
  64. self.spawn_idx = 0
  65. def alloc(Rays self, num):
  66. self.count = num
  67. self.size = num
  68. free(self.handle)
  69. self.handle = <Ray*>calloc(self.count, sizeof(Ray))
  70. def initFromSource(Rays self, source, int numrays, int angle):
  71. self.alloc(numrays)
  72. cdef Ray *ray
  73. cdef double initial_power, x, y, dx, dy, alpha, pi = math.pi
  74. cdef int sourceidx, j
  75. cdef double increment = 360.0 / numrays
  76. sourceidx = source.idx
  77. initial_power = float(source.power)# / numrays
  78. x = source.x
  79. y = source.y
  80. for j in range(numrays):
  81. alpha = j * increment
  82. # increase alpha by one degree
  83. # to ensure very low number of rays are not orthogonal to walls in
  84. # rectangular maps
  85. alpha = alpha + angle
  86. dx = cos(pi / 180.0 * alpha)
  87. dy = sin(pi / 180.0 * alpha)
  88. ray = &self.handle[j]
  89. ray.x = x
  90. ray.y = y
  91. ray.dx = dx
  92. ray.dy = dy
  93. ray.power = initial_power
  94. ray.dist = 0
  95. ray.generation = 1
  96. ray.source_id = sourceidx
  97. cdef void _spawn(Rays self, Ray ray, double dampening, int dist, double dx, double dy, double x, double y) nogil:
  98. ray.generation += 1
  99. ray.power = ray.power * dampening
  100. ray.dist = dist
  101. #~ print 'spawn, %s, %s' % (dx, dy)
  102. ray.dy = dy
  103. ray.dx = dx
  104. ray.x = x
  105. ray.y = y
  106. self.handle[self.spawn_idx] = ray
  107. self.spawn_idx += 1
  108. cdef void endspawning(Rays self) nogil:
  109. ''' set's self.count to number of spawned rays '''
  110. self.count = self.spawn_idx
  111. def toNumpy(self):
  112. ''' generate a numpy representation of ray data
  113. build one array for doubles and on for ints
  114. '''
  115. cdef np.ndarray[np.float32_t, ndim=2] doubles
  116. cdef np.ndarray[np.int_t, ndim=2] ints
  117. cdef int i
  118. cdef Ray *ray
  119. doubles = np.zeros([self.count, 5], dtype=np.float32)
  120. ints = np.zeros([self.count, 4], dtype=np.int)
  121. for i in range(self.count):
  122. ray = &self.handle[i]
  123. doubles[i, 0] = ray.dx
  124. doubles[i, 1] = ray.dy
  125. doubles[i, 2] = ray.x
  126. doubles[i, 3] = ray.y
  127. doubles[i, 4] = ray.power
  128. ints[i, 0] = ray.dist
  129. ints[i, 1] = ray.source_id
  130. ints[i, 2] = ray.generation
  131. #~ ints[i, 3] = ray.medium
  132. return doubles, ints
  133. def __dealloc__(Rays self):
  134. free(self.handle)
  135. cdef class Map:
  136. cdef double *map # hold signal strength for each x, y point
  137. cdef double *map_xs, *map_ys
  138. cdef public int width
  139. cdef public int height
  140. cdef public int numsources
  141. cdef public list sources
  142. cdef char *bgra_output
  143. cdef char *bgra_buffer
  144. cdef int mapsize
  145. def __init__(Map self, int width, int height, sources):
  146. self.mapsize = width * height
  147. self.numsources = len(sources)
  148. self.sources = sources
  149. self.map = <double*>calloc(self.mapsize * self.numsources, sizeof(double))
  150. self.map_xs = <double*>calloc(self.mapsize * self.numsources, sizeof(double))
  151. self.map_ys = <double*>calloc(self.mapsize * self.numsources, sizeof(double))
  152. self.bgra_buffer = <char*>calloc(self.mapsize * 4, sizeof(char))
  153. self.width = width
  154. self.height = height
  155. def __dealloc__(Map self):
  156. free(self.map)
  157. free(self.map_xs)
  158. free(self.map_ys)
  159. def clear(self):
  160. cdef int i
  161. for i in range(self.mapsize * self.numsources):
  162. self.map[i] = 0
  163. cdef inline int _translate(Map self, int x, int y, int source):
  164. return source * self.mapsize + y * self.width + x
  165. @cython.boundscheck(False)
  166. cdef inline double _get(self, int x, int y, int source):
  167. return self.map[self._translate(x, y, source)]
  168. @cython.boundscheck(False)
  169. cdef inline _set(self, int x, int y, int source, double v):
  170. self.map[self._translate(x, y, source)] = v
  171. def get(self, x, y, source):
  172. return self._get(x, y, source)
  173. @cython.boundscheck(False)
  174. @cython.cdivision(True)
  175. def writeBGRA(Map self, int bits, np.ndarray[np.int16_t] envmap, int alpha):
  176. '''write BGRA to pointer given by bits '''
  177. cdef char *ptr = <char*>bits
  178. self.bgra_output = ptr
  179. cdef double fact
  180. cdef int idx = 0, i, source_offset, source_idx, color
  181. cdef int b = 0,
  182. memset(ptr, 0, self.mapsize * 4)
  183. for source_idx in range(self.numsources):
  184. color = source_idx % 3
  185. fact = float(self.sources[source_idx].power)
  186. source_offset = source_idx * self.mapsize
  187. #BGRA
  188. for i in range(self.mapsize):
  189. v = self.map[source_offset + i]
  190. if v > 0:
  191. # (v / fact) -> normalize to 1.0
  192. b = fast_10log10(v / fact)
  193. #~ b = int(10 * log10(v / fact))
  194. # normalize to color space 0-255
  195. # we expect values from -1 to -100
  196. b = (150 + b * 2) * 2
  197. if b > 255:
  198. b = 255
  199. else:
  200. b = 0
  201. if b > 0:
  202. idx = i * 4
  203. ptr[idx + color] = b
  204. #~ ptr[idx + 1] = ptr[idx]
  205. #~ ptr[idx + 2] = ptr[idx]
  206. if envmap[i] == 0:
  207. ptr[idx + 3] = alpha # alpha mask for no envmap layers
  208. else:
  209. ptr[idx + 3] = 20 # alpha mask
  210. #TODO: only on demand
  211. memcpy(self.bgra_buffer, ptr, self.mapsize * 4)
  212. return
  213. def drawRect(Map self, int x1, int y1, int x2, int y2):
  214. cdef int i
  215. cdef char *ptr = self.bgra_output
  216. y1 = max(0, y1)
  217. y2 = min(y2, self.height) - 1
  218. x1 = max(0, x1)
  219. x2 = min(x2, self.width) - 1
  220. memcpy(ptr, self.bgra_buffer, self.mapsize * 4)
  221. horz1 = ((y1 * self.width + x1) * 4, (y1 * self.width + x2) * 4, 4)
  222. horz2 = ((y2 * self.width + x1) * 4, (y2 * self.width + x2) * 4, 4)
  223. vert1 = ((y1 * self.width + x1) * 4, (y2 * self.width + x1) * 4, self.width * 4)
  224. vert2 = ((y1 * self.width + x2) * 4, (y2 * self.width + x2) * 4, self.width * 4)
  225. for r in (horz1, horz2, vert1, vert2):
  226. for i in range(r[0], r[1], r[2]):
  227. ptr[i + 0] = 255
  228. ptr[i + 1] = 255
  229. ptr[i + 2] = 255
  230. ptr[i + 3] = 255
  231. @cython.boundscheck(False)
  232. def asArray(Map self, int source_idx, invertY=False):
  233. ''' convert map data to numpy array with shape width/height '''
  234. cdef np.ndarray[np.double_t, ndim=2] map = np.zeros([self.height, self.width], dtype=np.double)
  235. cdef int x, y, _y
  236. for x in range(self.width):
  237. for y in range(self.height):
  238. _y = self.height - 1 - y if invertY else y
  239. map[y, x] = self._get(x, _y, source_idx)
  240. return map
  241. @cython.boundscheck(False)
  242. def fromArray(Map self, np.ndarray[np.float32_t] a):
  243. cdef int i
  244. for i in range(self.mapsize * self.numsources):
  245. self.map[i] = a[i]
  246. return
  247. cdef class Simulation:
  248. cdef public list rays
  249. cdef public Map map
  250. cdef public int width, height
  251. cdef public int max_generations, enable_reflections, enable_refractions
  252. cdef public double pixel_per_meter
  253. cdef np.ndarray material_normals
  254. #~ cdef public list generations
  255. #~ cdef public list materials
  256. #~ cdef double *raypower
  257. cdef double materials[100][2] # max 99 materials
  258. def __init__(Simulation self, int width, int height,
  259. int max_generations, enable_reflections, enable_refractions,
  260. list sources, list materials, double pixel_per_meter):
  261. self.width = width
  262. self.height = height
  263. self.map = Map(width, height, sources)
  264. self.pixel_per_meter = pixel_per_meter
  265. # use initRays() to initialize
  266. self.rays = []
  267. self.max_generations = max_generations
  268. self.enable_reflections = enable_reflections
  269. self.enable_refractions = enable_refractions
  270. # the [0] index is not used (reserved for NO material)
  271. for i, (refl, refr) in enumerate(materials[:99]):
  272. self.materials[i+1][0] = refl
  273. self.materials[i+1][1] = refr
  274. def __dealloc__(Map self):
  275. pass
  276. @cython.boundscheck(False)
  277. @cython.cdivision(True)
  278. def initRays(Simulation self, int numrays, int angle):
  279. ''' configure ray-specific aspects '''
  280. for source in self.map.sources:
  281. self.rays.append(Rays(source=source, numrays=numrays, angle=angle))
  282. @cython.boundscheck(False)
  283. @cython.cdivision(True)
  284. def loopRays(Simulation self, Rays rays, np.ndarray[np.int16_t, ndim=2] envmap):
  285. cdef int i, j, xi, yi, medium, currmedium, mapidx, dist_offset, material_normal
  286. cdef int width = self.width, height = self.height
  287. cdef double x, y, newpower, currpower, reflection, refraction
  288. cdef Ray *ray
  289. cdef Ray r
  290. cdef Rays newrays = Rays(numrays=rays.count*2)
  291. cdef int done, dist, map_source_offset, source_id
  292. cdef double clamp = 10**-10
  293. cdef np.ndarray[np.int16_t, ndim=2] material_normals = self.material_normals
  294. cdef int pixel_per_meter_log2 = int(round(math.log(self.pixel_per_meter, 2)))
  295. #~ with nogil:
  296. source_id = (&rays.handle[0]).source_id
  297. map_source_offset = source_id * self.map.mapsize
  298. for j in range(rays.count):
  299. ray = &rays.handle[j]
  300. x, y = ray.x, ray.y
  301. xi, yi = int(x), int(y)
  302. currmedium = envmap[xi, yi]
  303. #~ print xi, yi, currmedium
  304. #~ print currmedium
  305. dist = ray.dist
  306. while True:
  307. if x <= 0 or x >= width-1 or y <= 0 or y >= height:
  308. break
  309. xi, yi = int(x), int(y)
  310. if dist > 32:
  311. power = ray.power / (dist >> pixel_per_meter_log2)**2
  312. else:
  313. power = ray.power / (dist / self.pixel_per_meter)**2
  314. #~ print '%.2f %.2f %i %s' % (x, y, envmap[xi, yi], power)
  315. if power < clamp:
  316. break
  317. medium = envmap[xi, yi]
  318. if medium != currmedium:
  319. if medium == 0:
  320. currmedium = medium
  321. else:
  322. material_normal = material_normals[xi, yi]
  323. r = rays.handle[j] # copy ray struct
  324. if self.enable_reflections and currmedium == 0:
  325. reflection = self.materials[medium][0]
  326. if material_normal == 1:
  327. # horicontal material
  328. newrays._spawn(r, reflection, dist, ray.dx, -ray.dy, x-ray.dx, y-ray.dy)
  329. elif material_normal == 0:
  330. # vertical material
  331. newrays._spawn(r, reflection, dist, -ray.dx, ray.dy, x-ray.dx, y-ray.dy)
  332. if self.enable_refractions:
  333. refraction = self.materials[medium][1]
  334. newrays._spawn(r, refraction, dist, ray.dx, ray.dy, x, y)
  335. break
  336. mapidx = map_source_offset + yi * self.map.width + xi
  337. currpower = self.map.map[mapidx]
  338. # handle interference?
  339. if power > currpower:
  340. self.map.map[mapidx] = power
  341. # TODO: prepare for bresenham 10% speed loss?
  342. #~ self.map.map_xs[mapidx] = ray.x
  343. #~ self.map.map_ys[mapidx] = ray.y
  344. x = x + ray.dx
  345. y = y + ray.dy
  346. dist += 1
  347. newrays.endspawning()
  348. return newrays
  349. @cython.boundscheck(False)
  350. def loadMaterialNormals(self, np.ndarray[np.int16_t, ndim=2] envmap):
  351. ''' we do not use real normals atm, only 0 and 1 for horizontally, vertically '''
  352. cdef np.ndarray[np.int16_t, ndim=2] material_normals = np.zeros([self.width, self.height], dtype=np.int16)
  353. cdef int x, y, medium
  354. for x in range(1, self.width-1): # ignore border pixels
  355. for y in range(1, self.height-1):
  356. medium = envmap[x, y]
  357. if medium > 0:
  358. if envmap[x-1, y] == medium and envmap[x+1, y] == medium:
  359. material_normals[x, y] = 1
  360. elif envmap[x, y-1] == medium and envmap[x, y+1] == medium:
  361. material_normals[x, y] = 0
  362. elif envmap[x-1, y] == medium or envmap[x+1, y] == medium:
  363. material_normals[x, y] = 1
  364. elif envmap[x, y-1] == medium or envmap[x, y+1] == medium:
  365. material_normals[x, y] = 0
  366. #~ else:
  367. # a single pixel?
  368. #~ raise RuntimeError('unsupported normal handling')
  369. #~ self.material_normals[x, y] = 1
  370. self.material_normals = material_normals
  371. def build(Simulation self, np.ndarray[np.int16_t, ndim=2] envmap):
  372. self.map.clear()
  373. self.loadMaterialNormals(envmap)
  374. todo = self.rays
  375. gen = 0
  376. while len(todo) > 0 and gen < self.max_generations:
  377. for _rays in todo:
  378. input_queue.put((self, _rays, envmap))
  379. _todo = []
  380. for i in range(len(todo)):
  381. newRays = output_queue.get()
  382. if newRays.count > 0:
  383. _todo.append(newRays)
  384. gen += 1
  385. todo[:] = _todo
  386. return
  387. # single threaded
  388. #~ t = time.time()
  389. #~ for rays in self.rays:
  390. #~ for i in range(self.max_generations):
  391. #~ rays = self.loopRays(rays, envmap)
  392. #~ if rays.count == 0:
  393. #~ break
  394. def builder(i):
  395. try:
  396. while True:
  397. try:
  398. sim, _rays, envmap = input_queue.get()
  399. except Empty:
  400. continue
  401. else:
  402. newrays = sim.loopRays(_rays, envmap)
  403. output_queue.put(newrays)
  404. except Exception, e:
  405. print e
  406. output_queue.put(Rays())
  407. for i in range(4):
  408. from threading import Thread
  409. thread = Thread(target=builder, args=(i, ))
  410. thread.setDaemon(True)
  411. thread.start()