geometrie.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Fonctions geometriques DMonde
  4. '''
  5. from __future__ import division
  6. from math import sqrt, atan2, pi, tan
  7. import time
  8. from PyQt4.QtCore import QPointF, QLineF
  9. from PyQt4.QtGui import QPolygonF
  10. class Geometrie(object):
  11. def __init__(self, fC = "H", dimX = 0, dimY = 0):
  12. self._fC = fC
  13. self._dimX = dimX
  14. self._dimY = dimY
  15. def initialiser(self, plateau):
  16. self._fC = plateau.formeCases
  17. self._dimX = plateau.nbCasesX
  18. self._dimY = plateau.nbCasesY
  19. def listeCases(self):
  20. """retourne la liste des cases composant le plateau en fonction de ses dimensions"""
  21. retour = []
  22. for x in range(self._dimX):
  23. for y in range(self._dimY):
  24. retour.append( (x, y) )
  25. return retour
  26. def listeCasesF(self, xmin, ymin, xmax, ymax):
  27. """liste de cases filtree (performances)"""
  28. retour = []
  29. if xmin < 0: xmin = 0
  30. if ymin < 0: ymin = 0
  31. if xmax > self._dimX: xmax = self._dimX
  32. if ymax > self._dimY: ymax = self._dimY
  33. for x in range(xmin, xmax + 1):
  34. for y in range(ymin, ymax + 1):
  35. retour.append( (x, y) )
  36. return retour
  37. def polygone(self, x, y):
  38. """renvoie l'objet graphique hexagone de la case de coordonnees (x, y)"""
  39. polygone = QPolygonF()
  40. if self._fC == "H":
  41. #si x est impair sur un plateau a cases hexagonales, le y est augmente de 0.5
  42. if 1 == (x % 2): y += 0.5
  43. polygone << QPointF(((x*0.866)+0.2886)*120, y*120) \
  44. << QPointF(((x*0.866)+0.866)*120, y*120) \
  45. << QPointF(((x*0.866)+1.1547)*120, (y+0.5)*120) \
  46. << QPointF(((x*0.866)+0.866)*120, (y+1)*120) \
  47. << QPointF(((x*0.866)+0.2886)*120, (y+1)*120) \
  48. << QPointF( (x*0.866)*120, (y+0.5)*120)
  49. else:
  50. polygone << QPointF(x*120, y*120) \
  51. << QPointF((x+1)*120, y*120) \
  52. << QPointF((x+1)*120, (y+1)*120) \
  53. << QPointF(x*120, (y+1)*120)
  54. return polygone
  55. def polygoneAgglo(self, listeCases):
  56. """renvoie un polygone contruit par agglomeration des polygones des cases de la liste
  57. les cases doivent etre adjacentes (cases hexagonales ou carrees)"""
  58. segments = []
  59. #on parcourt les faces des polygones des cases, et on ne garde que ceux qui n'ont pas de case 'en face'
  60. for coord in listeCases:
  61. polygone = self.polygone(coord[0], coord[1])
  62. voisins = self.lstCoordAdjacentes(coord[0], coord[1])
  63. for i in range(0, len(voisins)):
  64. if not voisins[i] in listeCases:
  65. j = i+1
  66. if j > len(voisins) - 1:
  67. j = 0
  68. segments.append(QLineF(polygone[i], polygone[j]))
  69. #on 'accroche' les segments les uns aux autres, dans l'ordre
  70. if not len(segments) > 0: return None
  71. segments2 = [segments[0]]
  72. for segment2 in segments2:
  73. for segment in segments:
  74. if not QLineF(segment.p1(), segment.p2()) in segments2 and not QLineF(segment.p2(), segment.p1()) in segments2:
  75. if sqrt((segment.p1().x()-segment2.p2().x())**2+(segment.p1().y()-segment2.p2().y())**2) < 1:
  76. segments2.append(QLineF(segment.p1(), segment.p2()))
  77. elif sqrt((segment.p2().x()-segment2.p2().x())**2+(segment.p2().y()-segment2.p2().y())**2) < 1:
  78. segments2.append(QLineF(segment.p2(), segment.p1()))
  79. pointsPolygone = []
  80. premierPoint = segments2[0].p1()
  81. pointsPolygone.append(premierPoint)
  82. for segment in segments2:
  83. pointSuivant = segment.p2()
  84. if pointSuivant != premierPoint:
  85. pointsPolygone.append(pointSuivant)
  86. #creation du polygone
  87. polygone = QPolygonF()
  88. for point in pointsPolygone:
  89. polygone.append(point)
  90. return polygone
  91. def coordonneesValides(self, coord):
  92. """les coordonnees entrees en parametre sont elles celles d'une case du plateau"""
  93. return (coord[0] >= 0 and coord[1] >= 0 and coord[0] < self._dimX and coord[1] < self._dimY)
  94. def filtrer(self, lst):
  95. """prend en parametre une liste de coordonnees
  96. et retourne une liste de coordonnees valides"""
  97. retour = []
  98. for coord in lst:
  99. x, y = coord[0], coord[1]
  100. if self.coordonneesValides((x, y)): retour.append(coord)
  101. return retour
  102. def coordCentreListeCases(self, listeCases):
  103. """renvoie les coordonnees centrales d'une liste de cases"""
  104. retour = None
  105. if len(listeCases) > 0:
  106. listeTriee = sorted(listeCases)
  107. posMilieu = int(len(listeCases)/2)
  108. retour = listeTriee[posMilieu]
  109. return retour
  110. def lstCoordAdjacentes(self, x, y):
  111. """renvoie la liste des coordonnees adjacentes,
  112. !!! -> sans condition d'existence sur le plateau
  113. attention: l'ordre est important"""
  114. if self._fC == "H":
  115. if 1 == (x % 2):
  116. return [(x, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1), (x-1, y)]
  117. else:
  118. return [(x, y-1), (x+1, y-1), (x+1, y), (x, y+1), (x-1, y), (x-1, y-1)]
  119. else:
  120. return [(x, y-1), (x+1, y), (x, y+1), (x-1, y)]
  121. def yR(self, x, y):
  122. """retourne le y reel lorsque les cases sont hexagonales"""
  123. return y if self._fC != "H" or x % 2 != 1 else y + 0.5
  124. def zone(self, centre, param):
  125. """renvoie un dictionnaire representant la liste des coordonnees des cases comprises dans la zone
  126. la zone en question est la liste des cases situees a une distance d des coordonnees d'origine"""
  127. # if len(param) == 2:
  128. # #on vise des coordonnees
  129. cible = param if type(param) == tuple else (-1, -1)
  130. rayon = param if type(param) == int else 99
  131. if not self.coordonneesValides(centre) or not rayon >= 0: return {}
  132. resultat = {}
  133. aVerifier = [] ; aVerifier2 = [] ; k = 0
  134. #on part de la premiere case, puis on itere a partir de chaque nouveau depart sur les voisins
  135. aVerifier.append(centre)
  136. while k <= rayon:
  137. for x, y in aVerifier:
  138. for coord in self.filtrer(self.lstCoordAdjacentes(x, y)):
  139. if not coord in aVerifier and not coord in aVerifier2 and not coord in resultat:
  140. aVerifier2.append(coord)
  141. for elt in aVerifier:
  142. resultat[elt] = k
  143. if elt == cible: rayon = k
  144. aVerifier = aVerifier2 ; aVerifier2 = [] ; k += 1
  145. return resultat
  146. def zone3d(self, centre, rayon):
  147. """ centre au format (x, y, z)
  148. renvoie les cases de la zone au format (x, y, z)"""
  149. retour = []
  150. x0, y0, z0 = centre
  151. zone = self.zone((x0, y0), rayon)
  152. for coord in zone:
  153. x, y = coord
  154. dz = rayon - zone[coord]
  155. for z in range(-dz, dz + 1):
  156. retour.append( (x, y, (z0 + z) ) )
  157. return retour
  158. def blocAdjacent(self, listeCases):
  159. """renvoie un bloc de cases adjacentes a partir de la liste en parametre"""
  160. retour = []
  161. tmp1 = [listeCases[0]]; tmp2 = [listeCases[0]]
  162. while len(tmp2) > 0:
  163. tmp2 = []
  164. #on liste les cases voisines qui sont dans la liste et pas encore verifiees
  165. for x, y in tmp1:
  166. for voisine in self.lstCoordAdjacentes(x, y):
  167. if voisine in listeCases and not voisine in tmp1 and not voisine in tmp2:
  168. tmp2.append(voisine)
  169. #chacune de ces cases prend le statut de verifiee
  170. for coord in tmp1:
  171. listeCases.remove(coord)
  172. retour.append(coord)
  173. tmp1 = tmp2
  174. return retour
  175. def ligne(self, coord1, coord2):
  176. """renvoie la ligne demandee en fonction des parametres"""
  177. if coord1[0] == coord2[0] and coord1[1] == coord2[1]: return [coord1]
  178. if len(coord1) == 2 and len(coord2) == 2: return self._ligne2d(coord1, coord2)
  179. elif len(coord1) == 3 and len(coord2) == 3: return self._ligne3d(coord1, coord2)
  180. elif len(coord1) == 3 and len(coord2) == 2: return self._ligne3d((coord1[0], coord1[1], 0), coord2)
  181. elif len(coord1) == 2 and len(coord2) == 3: return self._ligne3d(coord1, (coord2[0], coord2[1], 0))
  182. else: return [coord1]
  183. def _ligne2d(self, coord1, coord2):
  184. """renvoie la liste des cases traversees par la ligne entre les cases 1 et 2
  185. prend en parametre des coord de la forme (x,y)"""
  186. x1, y1 = coord1 ; x2, y2 = coord2
  187. return self._brH(x1, y1, x2, y2) if self._fC == "H" else self._brC(x1, y1, x2, y2)
  188. def _ligne3d(self, coord1, coord2):
  189. """renvoie la liste des cases traversees par la ligne entre les cases 1 et 2
  190. prend en parametre des coord de la forme (x,y, z)"""
  191. x1, y1, z1 = coord1 ; x2, y2, z2 = coord2
  192. ligne = self._ligne2d((x1, y1), (x2, y2))
  193. ligneZ = self._brC(0, z1, (len(ligne)-1), z2)
  194. retour = []
  195. for dist, z in ligneZ:
  196. x, y = ligne[dist]
  197. retour.append((x, y, z))
  198. return retour
  199. def _brC(self, x1, y1, x2, y2):
  200. """Algorithme ligne de Bresenham (pour cases carrees)"""
  201. retour = []
  202. #SYMETRIE DIAGONALE
  203. V = ( abs(y2 - y1) > abs(x2 - x1) )
  204. if V: y1, x1, y2, x2 = x1, y1, x2, y2
  205. #SYMETRIE VERTICALE
  206. inversee = (x1 > x2)
  207. if inversee: x2, y2, x1, y1 = x1, y1, x2, y2
  208. DX = x2 - x1 ; DY = y2 - y1
  209. decalage = 0.0
  210. saut = 1 if DY > 0 else -1
  211. alpha = ( abs( DY ) / DX )
  212. y = y1
  213. for x in range(x1, x2 + 1):
  214. coord = (y, x) if V else (x, y)
  215. retour.append(coord)
  216. decalage += alpha
  217. if decalage > 0.5:
  218. y += saut
  219. decalage -= 1.0
  220. if inversee: retour.reverse()
  221. return retour
  222. def _brH(self, x1, y1, x2, y2):
  223. """Algorithme ligne de Bresenham (pour cases hexagonales)"""
  224. inversee = (x1 > x2)
  225. if inversee:
  226. x1, x2 = x2, x1
  227. y1, y2 = y2, y1
  228. #calcul selon secteur
  229. if abs(x2 - x1) < (2*abs((y2-y1)) + abs(x2 % 2) - abs(x1 % 1)):
  230. retour = self._brH_v(x1, y1, x2, y2)
  231. else:
  232. retour = self._brH_h(x1, y1, x2, y2)
  233. # retourne la liste si les coordonnees ont ete interverties
  234. if inversee: retour.reverse()
  235. return retour
  236. def _brH_h(self, x1, y1, x2, y2):
  237. """Algorithme ligne de Bresenham (pour cases hexagonales - secteur horizontal)"""
  238. # Calcul des ecarts
  239. dx = x2 - x1 ; dy = y2 - y1
  240. if (x1 + x2) % 2 == 1:
  241. dy += 0.5 if x1 % 2 == 0 else -0.5
  242. # Calcul de l'erreur (l'ecart qui doit s'accumuler au fur et a mesure qu'on avance)
  243. k = dy / dx
  244. pas = 1
  245. # on itere sur les coordonnees de la boite qui contient les coordonnees 1 et 2
  246. retour = []
  247. d = 0.0
  248. pos = (x1, y1)
  249. retour.append(pos)
  250. while pos != (x2, y2):
  251. d += k*pas
  252. if d > 0:
  253. #on se deplace vers la case en dessous a droite
  254. x, y = pos
  255. if x % 2 == 0:
  256. pos = x + 1, y
  257. else:
  258. pos = x + 1, y + 1
  259. retour.append(pos)
  260. d -= 0.5
  261. else:
  262. #on se deplace vers la case au dessus a droite
  263. x, y = pos
  264. if x % 2 == 0:
  265. pos = x + 1, y - 1
  266. else:
  267. pos = x + 1, y
  268. retour.append(pos)
  269. d += 0.5
  270. if pos[0] > x2:
  271. retour = []
  272. break
  273. return retour
  274. def _brH_v(self, x1, y1, x2, y2):
  275. """Algorithme ligne de Bresenham (pour cases hexagonales - secteur vertical)"""
  276. #on prend comme unite la demi largeur: u = 0.5773
  277. #la demi hauteur d'un hexa vaut donc 0.8860u, ou sqrt(3)/2
  278. #[a revoir] une fois cela pose, on muliplie tout par 4dy afin d'eviter nombres flottants et divisions
  279. sens = 1 if y2 > y1 else -1
  280. # Calcul des ecarts
  281. dx = 1.5 * (x2 - x1) #en x, on a 1.5u de centre a centre
  282. dy = sens * (y2 - y1) #en y, on compte en demi hauteurs
  283. if (x1 + x2) % 2 == 1:
  284. if x1 % 2 == 0:
  285. dy += sens*0.5
  286. else:
  287. dy -= sens*0.5
  288. k = dx/(dy*sqrt(3)) #k est la tangente de l'angle par rapport a la verticale
  289. pas = 0.5*sqrt(3) #on avance par demi hauteurs
  290. retour = []
  291. d = 0.0 #decalage
  292. pos = (x1, y1)
  293. retour.append(pos)
  294. while pos != (x2, y2):
  295. d += (k*pas)
  296. if d <= 0.5:
  297. #on se deplace vers la case en dessous (ou au dessus)
  298. x, y = pos
  299. pos = x, y + sens
  300. retour.append(pos)
  301. d += (k*pas)
  302. else:
  303. #on se deplace vers la case en dessous (ou au dessus) a droite
  304. x, y = pos
  305. if (x %2 == 0 and sens == 1) or (x % 2 == 1 and sens == -1):
  306. pos = x + 1, y
  307. else:
  308. pos = x + 1, y + sens
  309. retour.append(pos)
  310. d -= 1.5
  311. if sens*pos[1] > sens*y2:
  312. retour = []
  313. break
  314. return retour
  315. def disque(self, coord1, coord2):
  316. """rendu pas terrible pour l'instant (a cause du decalage des cases?)"""
  317. x1, y1 = coord1 ; x2, y2 = coord2
  318. retour = []
  319. # k = 1.1547 if self._fC == "H" else 1
  320. k = 1
  321. rayon2 = (y2 - y1)**2 + (k* x2 - k* x1)**2
  322. for x, y in self.listeCases():
  323. if (k*x - k*x1)**2 + (self.yR(x, y) - self.yR(x1, y1))**2 <= (rayon2 + 0.25):
  324. retour.append((x, y))
  325. return retour
  326. def cone_(self, coord1, coord2, iAngle = 2):
  327. """version disque"""
  328. """on trace un disque et on compare les angles"""
  329. x1, y1 = coord1 ; x2, y2 = coord2
  330. retour = []
  331. if self._fC == "H" and 1 == (x1 % 2): y1 += 0.5
  332. if self._fC == "H" and 1 == (x2 % 2): y2 += 0.5
  333. if iAngle < 0: iAngle = 0
  334. if iAngle > 4: iAngle = 4
  335. angle = pi/[16, 12, 8, 6, 4][iAngle] # c'est le demi angle!
  336. disque = self.disque(coord1, coord2)
  337. alpha0 = atan2((x2 - x1), (y2 - y1))
  338. for coord in disque:
  339. x, y = coord
  340. d = sqrt( (y - y1)**2 + (x - x1)**2 )
  341. if self._fC == "H" and 1 == (x % 2): y += 0.5
  342. tolerance = 0.5 * atan2(1, d) #la tolerance est l'angle d'une case a la distance en cours
  343. if self.coordonneesValides( (x, y) ):
  344. alpha = atan2( (x - x1) , (y - y1) )
  345. if abs(alpha - alpha0) <= (angle + tolerance) or \
  346. ( 2*pi - abs(alpha - alpha0) ) <= (angle + tolerance):
  347. if self._fC == "H" and 1 == (x % 2): y -= 0.5
  348. retour.append( (x, y) )
  349. return retour
  350. def cone__(self, coord1, coord2, iAngle = 2):
  351. """version zone"""
  352. """on trace un disque et on compare les angles"""
  353. x1, y1 = coord1 ; x2, y2 = coord2
  354. retour = {}
  355. if self._fC == "H" and 1 == (x1 % 2): y1 += 0.5
  356. if self._fC == "H" and 1 == (x2 % 2): y2 += 0.5
  357. if iAngle < 0: iAngle = 0
  358. if iAngle > 4: iAngle = 4
  359. angle = pi/[16, 12, 8, 6, 4][iAngle] # c'est le demi angle!
  360. disque = self.zone(coord1, coord2)
  361. alpha0 = atan2((x2 - x1), (y2 - y1))
  362. for coord, d in disque.items():
  363. x, y = coord
  364. if self._fC == "H" and 1 == (x % 2): y += 0.5
  365. tolerance = 0.5 * atan2(1, d) #la tolerance est l'angle d'une case a la distance en cours
  366. if self.coordonneesValides( (x, y) ):
  367. alpha = atan2( (x - x1) , (y - y1) )
  368. if abs(alpha - alpha0) <= (angle + tolerance) or \
  369. ( 2*pi - abs(alpha - alpha0) ) <= (angle + tolerance):
  370. if self._fC == "H" and 1 == (x % 2): y -= 0.5
  371. retour[(x, y)] = d
  372. return retour
  373. def cone(self, coord1, coord2, iAngle):
  374. """renvoie un dictionnaire representant la liste des coordonnees des cases comprises dans le cone
  375. et leur distance a l'origine"""
  376. resultat = {}
  377. aVerifier = [] ; aVerifier2 = [] ; k = 0 ; rayon = 99 ; x1, y1 = coord1 ; x2, y2 = coord2
  378. if iAngle < 0: iAngle = 0
  379. if iAngle > 4: iAngle = 4
  380. angle = pi/[16, 12, 8][iAngle] # c'est le demi angle!
  381. alpha0 = atan2( (x2 - x1), (y2 - y1) )
  382. #on part de la premiere case, puis on itere a partir de chaque nouveau depart sur les voisins
  383. aVerifier.append(coord1)
  384. while k <= rayon:
  385. for x, y in aVerifier:
  386. for coordV in self.filtrer(self.lstCoordAdjacentes(x, y)):
  387. if not coordV in aVerifier and not coordV in aVerifier2 and not coordV in resultat:
  388. xv, yv = coordV
  389. tolerance = 0.5 * atan2(1, k) #la tolerance est l'angle d'une case a la distance en cours
  390. alpha = atan2( (xv - x1) , (yv - y1) )
  391. if abs(alpha - alpha0) <= (angle + tolerance) or \
  392. ( 2*pi - abs(alpha - alpha0) ) <= (angle + tolerance):
  393. aVerifier2.append(coordV)
  394. for elt in aVerifier:
  395. resultat[elt] = k
  396. if elt == coord2: rayon = k
  397. aVerifier = aVerifier2 ; aVerifier2 = [] ; k += 1
  398. return resultat
  399. def cone3d(self, coord1, coord2, iAngle = 1):
  400. x1, y1, z1 = coord1 ; x2, y2, z2 = coord2
  401. #on recupere le cone horizonthal
  402. coneH = self.cone( (x1, y1), (x2, y2), iAngle )
  403. #la ligne de mire
  404. d = max([d for d in coneH.values()])
  405. ligneZ = self._brC(0, z1, d, z2)
  406. if iAngle < 0: iAngle = 0
  407. if iAngle > 4: iAngle = 4
  408. angle = pi/[16, 12, 8][iAngle] # c'est le demi angle!
  409. retour = []
  410. for coord, dist in coneH.items():
  411. #calcul de l'ecart de hauteur
  412. z0 = ligneZ[dist][1]
  413. dz = int(round(dist * tan(angle)))
  414. for z in range(-dz, dz + 1):
  415. x, y = coord
  416. retour.append( (x, y, (z0 + z)) )
  417. return retour
  418. if __name__ == "__main__":
  419. geo = Geometrie("C", 200, 200)
  420. c = geo.cone( (0,0), (5,5), 2)
  421. print c