collision_shape2d.nim 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # author: Ethosa
  2. ## It provides collision shapes.
  3. import
  4. ../thirdparty/opengl,
  5. ../core/vector2,
  6. ../core/rect2,
  7. ../core/anchor,
  8. ../core/input,
  9. ../core/enums,
  10. ../core/circle2,
  11. ../nodes/node,
  12. node2d
  13. when defined(debug):
  14. import math
  15. const PI2 = PI*2
  16. type
  17. CollisionShape2DType* {.size: sizeof(int8), pure.} = enum
  18. COLLISION_SHAPE_2D_RECTANGLE,
  19. COLLISION_SHAPE_2D_CIRCLE,
  20. COLLISION_SHAPE_2D_POLYGON
  21. CollisionShape2DObj* = object of Node2DObj
  22. disable*: bool
  23. x1*, y1*, radius*: float
  24. polygon*: seq[Vector2Ref]
  25. shape_type*: CollisionShape2DType
  26. CollisionShape2DPtr* = ptr CollisionShape2DObj
  27. proc CollisionShape2D*(name: string, variable: var CollisionShape2DObj): CollisionShape2DPtr =
  28. ## Creates a new CollisionShape2D pointer.
  29. ##
  30. ## Arguments:
  31. ## - `name` is a node name.
  32. ## - `variable` is a CollisionShape2DObj variable.
  33. runnableExamples:
  34. var
  35. node_obj: CollisionShape2DObj
  36. node = CollisionShape2D("CollisionShape2D", node_obj)
  37. nodepattern(CollisionShape2DObj)
  38. node2dpattern()
  39. variable.rect_size.x = 40
  40. variable.rect_size.y = 40
  41. variable.disable = false
  42. variable.x1 = 0
  43. variable.y1 = 0
  44. variable.radius = 20
  45. variable.polygon = @[]
  46. variable.kind = COLLISION_SHAPE_2D_NODE
  47. proc CollisionShape2D*(obj: var CollisionShape2DObj): CollisionShape2DPtr {.inline.} =
  48. ## Creates a new CollisionShape2D pointer with deffault node name "CollisionShape2D".
  49. ##
  50. ## Arguments:
  51. ## - `variable` is a CollisionShape2DObj variable.
  52. runnableExamples:
  53. var
  54. node_obj: CollisionShape2DObj
  55. node = CollisionShape2D(node_obj)
  56. CollisionShape2D("CollisionShape2D", obj)
  57. method setShapeTypeRect*(self: CollisionShape2DPtr) {.base.} =
  58. ## Changes shape type to `circle`.
  59. self.shape_type = COLLISION_SHAPE_2D_RECTANGLE
  60. method setShapeTypeCircle*(self: CollisionShape2DPtr, cx, cy, radius: float) {.base.} =
  61. ## Changes shape type to `rectangle`.
  62. ##
  63. ## Arguments:
  64. ## - `cx` is a center circle position at X axis.
  65. ## - `cy` is a center circle position at Y axis.
  66. ## - `radius` is a circle radius.
  67. self.shape_type = COLLISION_SHAPE_2D_CIRCLE
  68. self.x1 = cx
  69. self.y1 = cy
  70. self.radius = radius
  71. method setShapeTypePolygon*(self: CollisionShape2DPtr, positions: varargs[Vector2Ref]) {.base.} =
  72. ## Changes shape type to `polygon`.
  73. ##
  74. ## Arguments:
  75. ## - `positions` is a varargs of polygon positions. Should be more than 2.
  76. self.shape_type = COLLISION_SHAPE_2D_POLYGON
  77. if positions.len() < 3:
  78. return
  79. self.polygon = @[]
  80. for i in positions:
  81. self.polygon.add(i)
  82. method draw*(self: CollisionShape2DPtr, w, h: GLfloat) =
  83. ## this method uses in the `window.nim`.
  84. {.warning[LockLevel]: off.}
  85. # Recalculate position.
  86. self.position = self.timed_position
  87. if self.centered:
  88. self.position = self.timed_position - self.rect_size/2
  89. else:
  90. self.position = self.timed_position
  91. self.calcGlobalPosition()
  92. # debug draw
  93. when defined(debug):
  94. let
  95. x = -w/2 + self.global_position.x
  96. y = h/2 - self.global_position.y
  97. if self.disable:
  98. glColor4f(0.5, 0.5, 0.5, 0.7)
  99. else:
  100. glColor4f(0.5, 0.6, 0.9, 0.7)
  101. case self.shape_type
  102. of COLLISION_SHAPE_2D_RECTANGLE:
  103. glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
  104. of COLLISION_SHAPE_2D_CIRCLE:
  105. glBegin(GL_TRIANGLE_FAN)
  106. glVertex3f(x + self.x1, y - self.y1, self.z_index_global)
  107. for i in 0..180:
  108. let angle = PI2*i.float/180f
  109. glVertex3f(x + self.x1 + self.radius*cos(angle), y - self.y1 - self.radius*sin(angle), self.z_index_global)
  110. glEnd()
  111. of COLLISION_SHAPE_2D_POLYGON:
  112. glBegin(GL_POLYGON)
  113. for vec2 in self.polygon:
  114. glVertex3f(x + vec2.x, y - vec2.y, self.z_index_global)
  115. glEnd()
  116. method duplicate*(self: CollisionShape2DPtr, obj: var CollisionShape2DObj): CollisionShape2DPtr {.base.} =
  117. ## Duplicates CollisionShape2D object and create a new CollisionShape2D pointer.
  118. obj = self[]
  119. obj.addr
  120. method getGlobalMousePosition*(self: CollisionShape2DPtr): Vector2Ref {.inline.} =
  121. ## Returns mouse position.
  122. Vector2Ref(x: last_event.x, y: last_event.y)
  123. method isCollide*(self: CollisionShape2DPtr, x, y: float): bool =
  124. ## Checks collision with point.
  125. ##
  126. ## Arguments:
  127. ## - `x` is a point position at X axis.
  128. ## - `y` is a point position at Y axis.
  129. self.calcGlobalPosition()
  130. if self.disable:
  131. return false
  132. case self.shape_type
  133. of COLLISION_SHAPE_2D_RECTANGLE:
  134. return Rect2(self.global_position, self.rect_size).contains(x, y)
  135. of COLLISION_SHAPE_2D_CIRCLE:
  136. let
  137. dx = x - self.x1
  138. dy = y - self.y1
  139. return dx*dx + dy*dy <= self.radius*self.radius
  140. of COLLISION_SHAPE_2D_POLYGON:
  141. result = false
  142. var next = 1
  143. let length = self.polygon.len()
  144. for i in 0..<length:
  145. inc next
  146. if next == length: next = 0
  147. let
  148. a = self.polygon[i] + self.global_position
  149. b = self.polygon[next] + self.global_position
  150. if ((a.y >= y and b.y < y) or (a.y < y and b.y >= y)) and (x < (b.x-a.x)*(y-a.y) / (b.y-a.y)+a.x):
  151. result = not result
  152. method isCollide*(self: CollisionShape2DPtr, vec2: Vector2Ref): bool =
  153. ## Checks collision with point.
  154. self.calcGlobalPosition()
  155. if self.disable:
  156. return false
  157. case self.shape_type
  158. of COLLISION_SHAPE_2D_RECTANGLE:
  159. return Rect2(self.global_position, self.rect_size).contains(vec2)
  160. of COLLISION_SHAPE_2D_CIRCLE:
  161. let
  162. dx = vec2.x - self.x1
  163. dy = vec2.y - self.y1
  164. return dx*dx + dy*dy <= self.radius*self.radius
  165. of COLLISION_SHAPE_2D_POLYGON:
  166. result = false
  167. var next = 1
  168. let length = self.polygon.len()
  169. for i in 0..<length:
  170. inc next
  171. if next == length: next = 0
  172. let
  173. a = self.polygon[i] + self.global_position
  174. b = self.polygon[next] + self.global_position
  175. if ((a.y >= vec2.y and b.y < vec2.y) or (a.y < vec2.y and b.y >= vec2.y)) and (vec2.x < (b.x-a.x)*(vec2.y-a.y) / (b.y-a.y)+a.x):
  176. result = not result
  177. method isCollide*(self, other: CollisionShape2DPtr): bool {.base.} =
  178. ## Checks collision with other CollisionShape2D object.
  179. self.calcGlobalPosition()
  180. other.calcGlobalPosition()
  181. # Return false, if collision shape is disabled.
  182. if self.disable:
  183. return false
  184. elif other.disable:
  185. return false
  186. case self.shape_type
  187. of COLLISION_SHAPE_2D_RECTANGLE:
  188. case other.shape_type:
  189. of COLLISION_SHAPE_2D_RECTANGLE:
  190. return Rect2(other.global_position, other.rect_size).intersects(Rect2(self.global_position, self.rect_size))
  191. of COLLISION_SHAPE_2D_CIRCLE:
  192. return Rect2(self.global_position, self.rect_size).isCollideWithCircle(
  193. other.global_position.x + other.x1,
  194. other.global_position.y + other.y1, other.radius)
  195. of COLLISION_SHAPE_2D_POLYGON:
  196. var
  197. rect = Rect2(self.global_position, self.rect_size)
  198. next = 1
  199. let length = other.polygon.len()
  200. for i in 0..<length:
  201. inc next
  202. if next == length: next = 0
  203. let
  204. a = other.polygon[i] + other.global_position
  205. b = other.polygon[next] + other.global_position
  206. if rect.contains(a, b):
  207. return true
  208. of COLLISION_SHAPE_2D_CIRCLE:
  209. case other.shape_type:
  210. of COLLISION_SHAPE_2D_CIRCLE:
  211. let
  212. sradii = self.radius + other.radius
  213. dx = (other.global_position.x + other.x1) - (self.global_position.x + self.x1)
  214. dy = (other.global_position.y + other.y1) - (self.global_position.y + self.y1)
  215. return dx*dx + dy*dy <= sradii*sradii
  216. of COLLISION_SHAPE_2D_RECTANGLE:
  217. return Rect2(other.global_position, other.rect_size).isCollideWithCircle(
  218. self.global_position.x + self.x1,
  219. self.global_position.y + self.y1, self.radius)
  220. of COLLISION_SHAPE_2D_POLYGON:
  221. var
  222. circle = Circle2(self.global_position.x + self.x1, self.global_position.y + self.y1, self.radius)
  223. next = 1
  224. let length = other.polygon.len()
  225. for i in 0..<length:
  226. inc next
  227. if next == length: next = 0
  228. let
  229. a = other.polygon[i] + other.global_position
  230. b = other.polygon[next] + other.global_position
  231. if circle.contains(a, b):
  232. return true
  233. return false
  234. of COLLISION_SHAPE_2D_POLYGON:
  235. case other.shape_type:
  236. of COLLISION_SHAPE_2D_RECTANGLE:
  237. var
  238. rect = Rect2(other.global_position, other.rect_size)
  239. next = 1
  240. let length = self.polygon.len()
  241. for i in 0..<length:
  242. inc next
  243. if next == length: next = 0
  244. let
  245. a = self.polygon[i] + self.global_position
  246. b = self.polygon[next] + self.global_position
  247. if rect.contains(a, b):
  248. return true
  249. of COLLISION_SHAPE_2D_POLYGON:
  250. let
  251. length = self.polygon.len()
  252. otherlength = other.polygon.len()
  253. var next = 0
  254. for i in 0..<length:
  255. inc next
  256. if next == length: next = 0
  257. let
  258. a = self.polygon[i] + self.global_position
  259. b = self.polygon[next] + self.global_position
  260. var othernext = 0
  261. for i in 0..<otherlength:
  262. inc othernext
  263. if othernext == otherlength: othernext = 0
  264. let
  265. c = other.polygon[i] + other.global_position
  266. d = other.polygon[othernext] + other.global_position
  267. if intersects(a, b, c, d):
  268. return true
  269. of COLLISION_SHAPE_2D_CIRCLE:
  270. var
  271. circle = Circle2(other.global_position.x + other.x1, other.global_position.y + other.y1, other.radius)
  272. next = 1
  273. let length = self.polygon.len()
  274. for i in 0..<length:
  275. inc next
  276. if next == length: next = 0
  277. let
  278. a = self.polygon[i] + self.global_position
  279. b = self.polygon[next] + self.global_position
  280. if circle.contains(a, b):
  281. return true
  282. return false