shapes.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. --[[
  2. Copyright (c) 2011 Matthias Richter
  3. Permission is hereby granted, free of charge, to any person obtaining a copy
  4. of this software and associated documentation files (the "Software"), to deal
  5. in the Software without restriction, including without limitation the rights
  6. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. copies of the Software, and to permit persons to whom the Software is
  8. furnished to do so, subject to the following conditions:
  9. The above copyright notice and this permission notice shall be included in
  10. all copies or substantial portions of the Software.
  11. Except as contained in this notice, the name(s) of the above copyright holders
  12. shall not be used in advertising or otherwise to promote the sale, use or
  13. other dealings in this Software without prior written authorization.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. THE SOFTWARE.
  21. ]]--
  22. local math_min, math_sqrt, math_huge = math.min, math.sqrt, math.huge
  23. local _PACKAGE, common_local = (...):match("^(.+)%.[^%.]+"), common
  24. if not (type(common) == 'table' and common.class and common.instance) then
  25. assert(common_class ~= false, 'No class commons specification available.')
  26. require(_PACKAGE .. '.class')
  27. end
  28. local vector = require(_PACKAGE .. '.vector-light')
  29. local Polygon = require(_PACKAGE .. '.polygon')
  30. local GJK = require(_PACKAGE .. '.gjk') -- actual collision detection
  31. -- reset global table `common' (required by class commons)
  32. if common_local ~= common then
  33. common_local, common = common, common_local
  34. end
  35. --
  36. -- base class
  37. --
  38. local Shape = {}
  39. function Shape:init(t)
  40. self._type = t
  41. self._rotation = 0
  42. end
  43. function Shape:moveTo(x,y)
  44. local cx,cy = self:center()
  45. self:move(x - cx, y - cy)
  46. end
  47. function Shape:rotation()
  48. return self._rotation
  49. end
  50. function Shape:rotate(angle)
  51. self._rotation = self._rotation + angle
  52. end
  53. function Shape:setRotation(angle, x,y)
  54. return self:rotate(angle - self._rotation, x,y)
  55. end
  56. --
  57. -- class definitions
  58. --
  59. local ConvexPolygonShape = {}
  60. function ConvexPolygonShape:init(polygon)
  61. Shape.init(self, 'polygon')
  62. assert(polygon:isConvex(), "Polygon is not convex.")
  63. self._polygon = polygon
  64. end
  65. local ConcavePolygonShape = {}
  66. function ConcavePolygonShape:init(poly)
  67. Shape.init(self, 'compound')
  68. self._polygon = poly
  69. self._shapes = poly:splitConvex()
  70. for i,s in ipairs(self._shapes) do
  71. self._shapes[i] = common_local.instance(ConvexPolygonShape, s)
  72. end
  73. end
  74. local CircleShape = {}
  75. function CircleShape:init(cx,cy, radius)
  76. Shape.init(self, 'circle')
  77. self._center = {x = cx, y = cy}
  78. self._radius = radius
  79. end
  80. local PointShape = {}
  81. function PointShape:init(x,y)
  82. Shape.init(self, 'point')
  83. self._pos = {x = x, y = y}
  84. end
  85. --
  86. -- collision functions
  87. --
  88. function ConvexPolygonShape:support(dx,dy)
  89. local v = self._polygon.vertices
  90. local max, vmax = -math_huge
  91. for i = 1,#v do
  92. local d = vector.dot(v[i].x,v[i].y, dx,dy)
  93. if d > max then
  94. max, vmax = d, v[i]
  95. end
  96. end
  97. return vmax.x, vmax.y
  98. end
  99. function CircleShape:support(dx,dy)
  100. return vector.add(self._center.x, self._center.y,
  101. vector.mul(self._radius, vector.normalize(dx,dy)))
  102. end
  103. -- collision dispatching:
  104. -- let circle shape or compund shape handle the collision
  105. function ConvexPolygonShape:collidesWith(other)
  106. if self == other then return false end
  107. if other._type ~= 'polygon' then
  108. local collide, sx,sy = other:collidesWith(self)
  109. return collide, sx and -sx, sy and -sy
  110. end
  111. -- else: type is POLYGON
  112. return GJK(self, other)
  113. end
  114. function ConcavePolygonShape:collidesWith(other)
  115. if self == other then return false end
  116. if other._type == 'point' then
  117. return other:collidesWith(self)
  118. end
  119. -- TODO: better way of doing this. report all the separations?
  120. local collide,dx,dy = false,0,0
  121. for _,s in ipairs(self._shapes) do
  122. local status, sx,sy = s:collidesWith(other)
  123. collide = collide or status
  124. if status then
  125. if math.abs(dx) < math.abs(sx) then
  126. dx = sx
  127. end
  128. if math.abs(dy) < math.abs(sy) then
  129. dy = sy
  130. end
  131. end
  132. end
  133. return collide, dx, dy
  134. end
  135. function CircleShape:collidesWith(other)
  136. if self == other then return false end
  137. if other._type == 'circle' then
  138. local px,py = self._center.x-other._center.x, self._center.y-other._center.y
  139. local d = vector.len2(px,py)
  140. local radii = self._radius + other._radius
  141. if d < radii*radii then
  142. -- if circles overlap, push it out upwards
  143. if d == 0 then return true, 0,radii end
  144. -- otherwise push out in best direction
  145. return true, vector.mul(radii - math_sqrt(d), vector.normalize(px,py))
  146. end
  147. return false
  148. elseif other._type == 'polygon' then
  149. return GJK(self, other)
  150. end
  151. -- else: let the other shape decide
  152. local collide, sx,sy = other:collidesWith(self)
  153. return collide, sx and -sx, sy and -sy
  154. end
  155. function PointShape:collidesWith(other)
  156. if self == other then return false end
  157. if other._type == 'point' then
  158. return (self._pos == other._pos), 0,0
  159. end
  160. return other:contains(self._pos.x, self._pos.y), 0,0
  161. end
  162. --
  163. -- point location/ray intersection
  164. --
  165. function ConvexPolygonShape:contains(x,y)
  166. return self._polygon:contains(x,y)
  167. end
  168. function ConcavePolygonShape:contains(x,y)
  169. return self._polygon:contains(x,y)
  170. end
  171. function CircleShape:contains(x,y)
  172. return vector.len2(x-self._center.x, y-self._center.y) < self._radius * self._radius
  173. end
  174. function PointShape:contains(x,y)
  175. return x == self._pos.x and y == self._pos.y
  176. end
  177. function ConcavePolygonShape:intersectsRay(x,y, dx,dy)
  178. return self._polygon:intersectsRay(x,y, dx,dy)
  179. end
  180. function ConvexPolygonShape:intersectsRay(x,y, dx,dy)
  181. return self._polygon:intersectsRay(x,y, dx,dy)
  182. end
  183. function ConcavePolygonShape:intersectionsWithRay(x,y, dx,dy)
  184. return self._polygon:intersectionsWithRay(x,y, dx,dy)
  185. end
  186. function ConvexPolygonShape:intersectionsWithRay(x,y, dx,dy)
  187. return self._polygon:intersectionsWithRay(x,y, dx,dy)
  188. end
  189. -- circle intersection if distance of ray/center is smaller
  190. -- than radius.
  191. -- with r(s) = p + d*s = (x,y) + (dx,dy) * s defining the ray and
  192. -- (x - cx)^2 + (y - cy)^2 = r^2, this problem is eqivalent to
  193. -- solving [with c = (cx,cy)]:
  194. --
  195. -- d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0
  196. function CircleShape:intersectionsWithRay(x,y, dx,dy)
  197. local pcx,pcy = x-self._center.x, y-self._center.y
  198. local a = vector.len2(dx,dy)
  199. local b = 2 * vector.dot(dx,dy, pcx,pcy)
  200. local c = vector.len2(pcx,pcy) - self._radius * self._radius
  201. local discr = b*b - 4*a*c
  202. if discr < 0 then return {} end
  203. discr = math_sqrt(discr)
  204. local ts, t1, t2 = {}, discr-b, -discr-b
  205. if t1 >= 0 then ts[#ts+1] = t1/(2*a) end
  206. if t2 >= 0 then ts[#ts+1] = t2/(2*a) end
  207. return ts
  208. end
  209. function CircleShape:intersectsRay(x,y, dx,dy)
  210. local tmin = math_huge
  211. for _, t in ipairs(self:intersectionsWithRay(x,y,dx,dy)) do
  212. tmin = math_min(t, tmin)
  213. end
  214. return tmin ~= math_huge, tmin
  215. end
  216. -- point shape intersects ray if it lies on the ray
  217. function PointShape:intersectsRay(x,y, dx,dy)
  218. local px,py = self._pos.x-x, self._pos.y-y
  219. local t = vector.dot(px,py, dx,dy) / vector.len2(dx,dy)
  220. return t >= 0, t
  221. end
  222. function PointShape:intersectionsWithRay(x,y, dx,dy)
  223. local intersects, t = self:intersectsRay(x,y, dx,dy)
  224. return intersects and {t} or {}
  225. end
  226. --
  227. -- auxiliary
  228. --
  229. function ConvexPolygonShape:center()
  230. return self._polygon.centroid.x, self._polygon.centroid.y
  231. end
  232. function ConcavePolygonShape:center()
  233. return self._polygon.centroid.x, self._polygon.centroid.y
  234. end
  235. function CircleShape:center()
  236. return self._center.x, self._center.y
  237. end
  238. function PointShape:center()
  239. return self._pos.x, self._pos.y
  240. end
  241. function ConvexPolygonShape:outcircle()
  242. local cx,cy = self:center()
  243. return cx,cy, self._polygon._radius
  244. end
  245. function ConcavePolygonShape:outcircle()
  246. local cx,cy = self:center()
  247. return cx,cy, self._polygon._radius
  248. end
  249. function CircleShape:outcircle()
  250. local cx,cy = self:center()
  251. return cx,cy, self._radius
  252. end
  253. function PointShape:outcircle()
  254. return self._pos.x, self._pos.y, 0
  255. end
  256. function ConvexPolygonShape:bbox()
  257. return self._polygon:bbox()
  258. end
  259. function ConcavePolygonShape:bbox()
  260. return self._polygon:bbox()
  261. end
  262. function CircleShape:bbox()
  263. local cx,cy = self:center()
  264. local r = self._radius
  265. return cx-r,cy-r, cx+r,cy+r
  266. end
  267. function PointShape:bbox()
  268. local x,y = self:center()
  269. return x,y,x,y
  270. end
  271. function ConvexPolygonShape:move(x,y)
  272. self._polygon:move(x,y)
  273. end
  274. function ConcavePolygonShape:move(x,y)
  275. self._polygon:move(x,y)
  276. for _,p in ipairs(self._shapes) do
  277. p:move(x,y)
  278. end
  279. end
  280. function CircleShape:move(x,y)
  281. self._center.x = self._center.x + x
  282. self._center.y = self._center.y + y
  283. end
  284. function PointShape:move(x,y)
  285. self._pos.x = self._pos.x + x
  286. self._pos.y = self._pos.y + y
  287. end
  288. function ConcavePolygonShape:rotate(angle,cx,cy)
  289. Shape.rotate(self, angle)
  290. if not (cx and cy) then
  291. cx,cy = self:center()
  292. end
  293. self._polygon:rotate(angle,cx,cy)
  294. for _,p in ipairs(self._shapes) do
  295. p:rotate(angle, cx,cy)
  296. end
  297. end
  298. function ConvexPolygonShape:rotate(angle, cx,cy)
  299. Shape.rotate(self, angle)
  300. self._polygon:rotate(angle, cx, cy)
  301. end
  302. function CircleShape:rotate(angle, cx,cy)
  303. Shape.rotate(self, angle)
  304. if not (cx and cy) then return end
  305. self._center.x,self._center.y = vector.add(cx,cy, vector.rotate(angle, self._center.x-cx, self._center.y-cy))
  306. end
  307. function PointShape:rotate(angle, cx,cy)
  308. Shape.rotate(self, angle)
  309. if not (cx and cy) then return end
  310. self._pos.x,self._pos.y = vector.add(cx,cy, vector.rotate(angle, self._pos.x-cx, self._pos.y-cy))
  311. end
  312. function ConcavePolygonShape:scale(s)
  313. assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
  314. local cx,cy = self:center()
  315. self._polygon:scale(s, cx,cy)
  316. for _, p in ipairs(self._shapes) do
  317. local dx,dy = vector.sub(cx,cy, p:center())
  318. p:scale(s)
  319. p:moveTo(cx-dx*s, cy-dy*s)
  320. end
  321. end
  322. function ConvexPolygonShape:scale(s)
  323. assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
  324. self._polygon:scale(s, self:center())
  325. end
  326. function CircleShape:scale(s)
  327. assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
  328. self._radius = self._radius * s
  329. end
  330. function PointShape:scale()
  331. -- nothing
  332. end
  333. function ConvexPolygonShape:draw(mode)
  334. mode = mode or 'line'
  335. love.graphics.polygon(mode, self._polygon:unpack())
  336. end
  337. function ConcavePolygonShape:draw(mode, wireframe)
  338. local mode = mode or 'line'
  339. if mode == 'line' then
  340. love.graphics.polygon('line', self._polygon:unpack())
  341. if not wireframe then return end
  342. end
  343. for _,p in ipairs(self._shapes) do
  344. love.graphics.polygon(mode, p._polygon:unpack())
  345. end
  346. end
  347. function CircleShape:draw(mode, segments)
  348. love.graphics.circle(mode or 'line', self:outcircle())
  349. end
  350. function PointShape:draw()
  351. love.graphics.point(self:center())
  352. end
  353. Shape = common_local.class('Shape', Shape)
  354. ConvexPolygonShape = common_local.class('ConvexPolygonShape', ConvexPolygonShape, Shape)
  355. ConcavePolygonShape = common_local.class('ConcavePolygonShape', ConcavePolygonShape, Shape)
  356. CircleShape = common_local.class('CircleShape', CircleShape, Shape)
  357. PointShape = common_local.class('PointShape', PointShape, Shape)
  358. local function newPolygonShape(polygon, ...)
  359. -- create from coordinates if needed
  360. if type(polygon) == "number" then
  361. polygon = common_local.instance(Polygon, polygon, ...)
  362. else
  363. polygon = polygon:clone()
  364. end
  365. if polygon:isConvex() then
  366. return common_local.instance(ConvexPolygonShape, polygon)
  367. end
  368. return common_local.instance(ConcavePolygonShape, polygon)
  369. end
  370. local function newCircleShape(...)
  371. return common_local.instance(CircleShape, ...)
  372. end
  373. local function newPointShape(...)
  374. return common_local.instance(PointShape, ...)
  375. end
  376. return {
  377. ConcavePolygonShape = ConcavePolygonShape,
  378. ConvexPolygonShape = ConvexPolygonShape,
  379. CircleShape = CircleShape,
  380. PointShape = PointShape,
  381. newPolygonShape = newPolygonShape,
  382. newCircleShape = newCircleShape,
  383. newPointShape = newPointShape,
  384. }