triangle.py 7.3 KB

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