triangle.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. '''
  2. Created on 8 nov. 2016
  3. Triangle algorithms
  4. @author: olinox
  5. '''
  6. from math import sqrt
  7. from core import bresenham, constants
  8. from core.cube_coords import cv_off_cube, cube_round, cv_cube_off
  9. ANGLES = [1, 2, 3]
  10. def triangle(geometry, xa, ya, xh, yh, iAngle):
  11. if geometry == constants.SQUAREGRID:
  12. return triangle_sq(xa, ya, xh, yh, iAngle)
  13. elif geometry == constants.HEXGRID:
  14. return triangle_hex(xa, ya, xh, yh, iAngle)
  15. else:
  16. raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
  17. def triangle3d(geometry, xa, ya, za, xh, yh, zh, iAngle):
  18. if geometry == constants.SQUAREGRID:
  19. return triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle)
  20. elif geometry == constants.HEXGRID:
  21. return triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle)
  22. else:
  23. raise ValueError("'geometry' has to be a value from GRID_GEOMETRIES")
  24. def triangle_sq(xa, ya, xh, yh, iAngle):
  25. """Returns a list of (x, y) coordinates in a triangle
  26. A is the top of the triangle, H if the middle of the base
  27. (square grid)
  28. """
  29. if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
  30. raise TypeError("xa, ya, xh, yh should be integers")
  31. if not iAngle in ANGLES:
  32. raise ValueError("iAngle should be one of the ANGLES values")
  33. if (xa, ya) == (xh, yh):
  34. return [(xa, ya)]
  35. result = []
  36. # direction vector
  37. dx_dir, dy_dir = xh - xa, yh - ya
  38. # normal vector
  39. dx_n, dy_n = - dy_dir, dx_dir
  40. # B and C positions
  41. k = 1 / ( iAngle * sqrt(3) )
  42. xb, yb = xh + (k * dx_n), yh + (k * dy_n)
  43. xc, yc = xh + (-k * dx_n), yh + (-k * dy_n)
  44. xb, yb = round(xb), round(yb)
  45. xc, yc = round(xc), round(yc)
  46. # sides:
  47. lines = [(xa, ya, xb, yb), (xb, yb, xc, yc), (xc, yc, xa, ya)]
  48. # base (lower slope)
  49. x1, y1, x2, y2 = min(lines, key=lambda x: (abs ( (x[3] - x[1]) / (x[2] - x[0]) ) if x[2] != x[0] else 10**10))
  50. base = bresenham.squ_2d_line(x1, y1, x2, y2)
  51. y_base = y1
  52. lines.remove( (x1, y1, x2, y2) )
  53. # 'hat' (2 other sides)
  54. hat = []
  55. y_top = None
  56. for x1, y1, x2, y2 in lines:
  57. if y_top == None:
  58. y_top = y2
  59. hat.extend( bresenham.squ_2d_line(x1, y1, x2, y2) )
  60. # sense (1 if top is under base, -1 if not)
  61. sense = 1 if y_top > y_base else -1
  62. # rove over y values from base to hat
  63. for x, y in base:
  64. while not (x, y) in hat:
  65. result.append( (x, y) )
  66. y += sense
  67. result.extend(hat)
  68. return result
  69. def triangle_sq_3d(xa, ya, za, xh, yh, zh, iAngle):
  70. """returns a dictionnary {coord: (-dh, +dh)}
  71. coord keys are the cells in the triangle, (-dh, +dh) value is the vertical amplitude"""
  72. if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
  73. raise TypeError("xa, ya, za, xh, yh have to be integers")
  74. if not iAngle in ANGLES:
  75. raise ValueError("iAngle should be one of the ANGLES values")
  76. if (xa, ya) == (xh, yh):
  77. return [(xa, ya)]
  78. result = {}
  79. flat_triangle = triangle_sq(xa, ya, xh, yh, iAngle)
  80. k = 1 / ( iAngle * sqrt(3) )
  81. length = max( abs(xh - xa), abs(yh - ya) )
  82. vertical_line = bresenham.squ_2d_line(0, za, length, zh)
  83. #on cree un dictionnaire ou x est la cle, et ou la valeur est une liste de z
  84. vertical_line_dict = {d:[] for d, z in vertical_line}
  85. for d, z in vertical_line:
  86. vertical_line_dict[d].append(z)
  87. #approximation: on met a jour la hauteur en fonction de la distance au centre
  88. for x, y in flat_triangle:
  89. distance = int( max( abs(x - xa), abs(y - ya) ) )
  90. try:
  91. z_list = vertical_line_dict[ distance ]
  92. except KeyError:
  93. distance = length
  94. z_list = vertical_line_dict[ distance ]
  95. dh = int( k * distance ) + 1 if distance > 0 else 0
  96. result[ (x, y) ] = ( (min(z_list) - dh) , (max(z_list) + dh) )
  97. return result
  98. def triangle_hex(xa, ya, xh, yh, iAngle):
  99. """Returns a list of (x, y) coordinates in a triangle
  100. A is the top of the triangle, H if the middle of the base
  101. (hexagonal grid)
  102. """
  103. if not all(isinstance(c, int) for c in [xa, ya, xh, yh]):
  104. raise TypeError("xa, ya, xh, yh should be integers")
  105. if not iAngle in [1, 2, 3]:
  106. raise ValueError("iAngle should be one of the ANGLES values")
  107. if (xa, ya) == (xh, yh):
  108. return [(xa, ya)]
  109. result = []
  110. # convert to cubic coodinates (see 'cube_coords' lib)
  111. xua, yua, _ = cv_off_cube( xa, ya )
  112. xuh, yuh, zuh = cv_off_cube( xh, yh )
  113. # direction vector
  114. dx_dir, dy_dir = xuh - xua, yuh - yua
  115. # normal vector
  116. dx_n, dy_n = - (2* dy_dir + dx_dir ), (2* dx_dir + dy_dir )
  117. dz_n = (- dx_n - dy_n)
  118. # B and C positions
  119. k = 1 / ( iAngle * sqrt(3) )
  120. xub, yub, zub = xuh + (k * dx_n), yuh + (k * dy_n), zuh + (k * dz_n)
  121. xuc, yuc, zuc = xuh + (-k * dx_n), yuh + (-k * dy_n), zuh + (-k * dz_n)
  122. xub, yub, zub = cube_round(xub, yub, zub)
  123. xuc, yuc, zuc = cube_round(xuc, yuc, zuc)
  124. xb, yb = cv_cube_off(xub, yub, zub)
  125. xc, yc = cv_cube_off(xuc, yuc, zuc)
  126. # sides
  127. segments = [(xa, ya, xb, yb), (xb, yb, xc, yc), (xc, yc, xa, ya)]
  128. # base (lower slope)
  129. x1, y1, x2, y2 = min(segments, key=lambda x: (abs ( (x[3] - x[1]) / (x[2] - x[0]) ) if x[2] != x[0] else 10**10))
  130. base = bresenham.hex_2d_line(x1, y1, x2, y2)
  131. y_base = y1
  132. segments.remove( (x1, y1, x2, y2) )
  133. # 'hat' (the 2 other sides)
  134. chapeau = []
  135. y_sommet = None
  136. for x1, y1, x2, y2 in segments:
  137. if y_sommet == None:
  138. y_sommet = y2
  139. chapeau.extend( bresenham.hex_2d_line(x1, y1, x2, y2) )
  140. # sense (1 if top is under base, -1 if not)
  141. sens = 1 if y_sommet > y_base else -1
  142. # rove over y values from base to hat
  143. for x, y in base:
  144. while not (x, y) in chapeau:
  145. result.append( (x, y) )
  146. y += sens
  147. result.extend(chapeau)
  148. return result
  149. def triangle_hex_3d(xa, ya, za, xh, yh, zh, iAngle):
  150. """returns a dictionnary {coord: (-dh, +dh)}
  151. coord (x,y) keys are the cells in the triangle,
  152. (-dh, +dh) value is the vertical amplitude"""
  153. flat_trangle = triangle_hex(xa, ya, xh, yh, iAngle)
  154. if (xa, ya) == (xh, yh):
  155. return [(xa, ya)]
  156. result = {}
  157. k = 1 / ( iAngle * sqrt(3) )
  158. xua, yua, zua = cv_off_cube(xa, ya)
  159. xuh, yuh, zuh = cv_off_cube(xh, yh)
  160. length = max( abs(xuh - xua), abs(yuh - yua), abs(zuh - zua) )
  161. vertical_line = bresenham.squ_2d_line(0, za, length, zh)
  162. #on cree un dictionnaire ou x est la cle, et ou la valeur est une liste de z
  163. vertical_line_dict = {d:[] for d, z in vertical_line}
  164. for d, z in vertical_line:
  165. vertical_line_dict[d].append(z)
  166. #approximation: on met a jour la hauteur en fonction de la distance au centre
  167. for x, y in flat_trangle:
  168. xu, yu, zu = cv_off_cube(x, y)
  169. distance = int( max( abs(xu - xua), abs(yu - yua), abs(zu - zua) ) )
  170. try:
  171. z_list = vertical_line_dict[ distance ]
  172. except KeyError:
  173. distance = length
  174. z_list = vertical_line_dict[ distance ]
  175. dh = int( k * distance ) + 1 if distance > 0 else 0
  176. result[ (x, y) ] = ( (min(z_list) - dh) , (max(z_list) + dh) )
  177. return result