PageRenderTime 75ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/camera.lua

http://github.com/clofresh/Paradigm_Shift
Lua | 544 lines | 471 code | 43 blank | 30 comment | 44 complexity | 334315b1e80401eba489625a592eda50 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. --[[ CAMERA
  2. -- Description: Supplies an interface for scrolling, scaling, and rotating a scene.
  3. -- Contributors: Osuf Oboys
  4. -- Version:3d, April 24, 2009
  5. -- This product is released under the Lovely Public Community License v. 1.0.
  6. -- Provided that you do not alter this file and call your project a new version of CAMERA,
  7. -- then you are free to do whatever you want, including removing this header and the
  8. -- accompanying license. Otherwise, see the accompanying license.
  9. --]]
  10. -- TODO: particle system scaling
  11. -- TODO: negative scales should substitute a position with the corner that is in the upper upper left after the scaling.
  12. -- TODO:
  13. camera = {love={graphics={},mouse={}}}
  14. camera.class = {}
  15. camera.class.__index = camera.class
  16. local twopi = math.pi + math.pi
  17. -- (screenox, screenoy): the position of (ox, oy) on the screen
  18. -- (0,0): upper left (until the next LÖVE version)
  19. -- (1,0): upper right, (0,1): lower left, (1,1): lower right
  20. -- (0.5, 0.5): center of screen
  21. -- TODO: rotation
  22. function camera.new(scalex, scaley, ox, oy, screenox, screenoy, rotation)
  23. local cam = {}
  24. setmetatable(cam, camera.class)
  25. cam.scalex = scalex or 1
  26. cam.scaley = scaley or 1
  27. cam.ox = ox or 0
  28. cam.oy = oy or 0
  29. cam.screenox = screenox or 0
  30. cam.screenoy = screenoy or 0
  31. cam.rotation = rotation or 0
  32. cam.cosrot = 1
  33. cam.sinrot = 0
  34. return cam
  35. end
  36. function camera.stretchToResolution(fromwidth, fromheight, towidth, toheight, ox, oy, screenox, screenoy, rotation)
  37. fromwidth = fromwidth or love.graphics.getWindowWidth()
  38. fromheight = fromheight or love.graphics.getWindowHeight()
  39. towidth = towidth or love.graphics.getWindowWidth()
  40. toheight = toheight or love.graphics.getWindowHeight()
  41. return camera.new(towidth / fromwidth, toheight / fromheight, ox, oy, screenox, screenoy, rotation)
  42. end
  43. function camera.class:pos(x, y)
  44. x = x or 0
  45. y = y or 0
  46. x,y = ((x - self.ox) * self.cosrot - (y - self.oy) * self.sinrot) * self.scalex + love.graphics.getWindowWidth() * self.screenox,
  47. ((y - self.oy) * self.cosrot + (x - self.ox) * self.sinrot) * self.scaley + love.graphics.getWindowHeight() * self.screenoy
  48. return x, y
  49. end
  50. function camera.class:unpos(x, y)
  51. x = (x or 0)
  52. y = (y or 0)
  53. x = (x - love.graphics.getWindowWidth() * self.screenox) / self.scalex
  54. y = (y - love.graphics.getWindowHeight() * self.screenoy) / self.scaley
  55. return self.ox + x * self.cosrot + y * self.sinrot, self.oy + y * self.cosrot - x * self.sinrot
  56. end
  57. function camera.class:transformBox(x, y, w, h)
  58. x, y = self:pos(x,y)
  59. w = w * self.scalex
  60. h = h * self.scaley
  61. if w < 0 then x = x + w; w = -w; end
  62. if h < 0 then y = y + h; h = -h; end
  63. return x, y, w, h
  64. end
  65. function camera.class:untransformBox(x, y, w, h)
  66. x, y = self:unpos(x,y)
  67. w = w / self.scalex
  68. h = h / self.scaley
  69. if w < 0 then x = x + w; w = -w; end
  70. if h < 0 then y = y + h; h = -h; end
  71. return x, y, w, h
  72. end
  73. -- How should scaling work for rotations?
  74. function camera.class:scaleSym(s)
  75. return s * (self.scalex^2 / 2 + self.scaley^2 / 2)^0.5
  76. end
  77. function camera.class:unscaleSym(s)
  78. return s / (self.scalex^2 / 2 + self.scaley^2 / 2)^0.5
  79. end
  80. function camera.class:scale(x, y)
  81. return math.abs(x * self.scalex), math.abs(y * self.scaley)
  82. end
  83. function camera.class:getRotationSizeFactors(x,y)
  84. return math.abs(x * self.cosrot) + math.abs(y * self.sinrot),
  85. math.abs(y * self.cosrot) + math.abs(x * self.sinrot)
  86. end
  87. function camera.class:scaleWithRot(x, y)
  88. return math.abs(x * self.scalex * self.cosrot) + math.abs(y * self.scaley * self.sinrot),
  89. math.abs(y * self.scaley * self.cosrot) + math.abs(x * self.scalex * self.sinrot)
  90. end
  91. function camera.class:scaleX(x)
  92. return math.abs(x * self.scalex)
  93. end
  94. function camera.class:scaleY(y)
  95. return math.abs(y * self.scaley)
  96. end
  97. function camera.class:unscale(x, y)
  98. return math.abs(x / self.scalex), math.abs(y / self.scaley)
  99. end
  100. -- This cannot be determined if the rotation is 0.5pi or 1.5 pi.
  101. function camera.class:unscaleWithRot(x, y)
  102. local z = self.scalex * self.scaley * (self.cosrot^2 - self.sinrot^2)
  103. if z == 0 then
  104. -- Not a bug, should say 'x' on both sides.
  105. return self.scalex * x / (self.scalex + self.scaley), self.scaley * x / (self.scalex + self.scaley)
  106. end
  107. return (math.abs(x * self.cosrot * self.scaley) - math.abs(y * self.sinrot * self.scalex)) / z,
  108. (math.abs(y * self.cosrot * self.scalex) - math.abs(x * self.sinrot * self.scaley)) / z
  109. end
  110. function camera.class:unscaleX(x)
  111. return math.abs(x / self.scalex)
  112. end
  113. function camera.class:unscaleY(y)
  114. return math.abs(y / self.scaley)
  115. end
  116. function camera.class:setOrigin(ox,oy)
  117. self.ox = ox; self.oy = oy
  118. end
  119. function camera.class:getOrigin()
  120. return self.ox, self.oy
  121. end
  122. function camera.class:setRotation(rot)
  123. self.rotation = rot
  124. self.cosrot = math.cos(math.rad(self.rotation))
  125. self.sinrot = math.sin(math.rad(self.rotation))
  126. end
  127. function camera.class:rotateBy(rot, ox, oy)
  128. if ox then
  129. self.ox = self.ox + (ox - self.ox) * (1 - self.cosrot) + (oy - self.oy) * self.sinrot
  130. self.oy = self.oy + (oy - self.oy) * (1 - self.cosrot) - (ox - self.ox) * self.sinrot
  131. end
  132. self:setRotation((rot + self.rotation) % 360)
  133. end
  134. function camera.class:getRotation()
  135. return self.rotation
  136. end
  137. function camera.class:setScreenOrigin(ox,oy)
  138. self.screenox = ox; self.screenoy = oy
  139. end
  140. function camera.class:getScreenOrigin()
  141. return self.screenox, self.screenoy
  142. end
  143. function camera.class:setScaleFactor(sx, sy)
  144. self.scalex = sx; self.scaley = sy
  145. end
  146. function camera.class:scaleBy(sx, sy)
  147. sx = sx or 1
  148. sy = sy or sx
  149. self.scalex = self.scalex * sx
  150. self.scaley = self.scaley * sy
  151. end
  152. function camera.class:scaleXToAspectRatio()
  153. --self.scalex = love.graphics.getWindowWidth() / love.graphics.getWindowHeight() * self.scaley
  154. self.scalex = self.scaley
  155. end
  156. function camera.class:scaleYToAspectRatio()
  157. --self.scaley = love.graphics.getWindowHeight() / love.graphics.getWindowWidth() * self.scalex
  158. self.scaley = self.scalex
  159. end
  160. function camera.class:getScaleFactor()
  161. return self.scalex, self.scaley
  162. end
  163. camera.present = camera.new()
  164. camera.mouseCamera = nil
  165. function setCamera(cam)
  166. if cam then camera.present = cam end
  167. end
  168. function getCamera()
  169. return camera.present
  170. end
  171. function setMouseCamera(cam)
  172. camera.mouseCamera = cam
  173. end
  174. function getMouseCamera()
  175. return camera.mouseCamera
  176. end
  177. ---- Superseding ----
  178. camera.love.graphics.getWidth = love.graphics.getWidth
  179. love.graphics.getWindowWidth = love.graphics.getWidth
  180. function love.graphics.getWidth()
  181. return camera.present:unscaleX(love.graphics.getWindowWidth())
  182. end
  183. function love.graphics.getWidthWithRot()
  184. end
  185. camera.love.graphics.getHeight = love.graphics.getHeight
  186. love.graphics.getWindowHeight = love.graphics.getHeight
  187. function love.graphics.getHeight()
  188. return camera.present:unscaleY(love.graphics.getWindowHeight())
  189. end
  190. camera.love.graphics.setScissor = love.graphics.setScissor
  191. function love.graphics.setScissor(x, y, w, h)
  192. if x and y then
  193. x, y, w, h = camera.present:transformBox(x, y, w, h)
  194. camera.love.graphics.setScissor(x, y, math.ceil(w), math.ceil(h)) --needed by Leif GUI
  195. else
  196. camera.love.graphics.setScissor()
  197. end
  198. end
  199. camera.love.graphics.getScissor = love.graphics.getScissor
  200. function love.graphics.getScissor()
  201. local x, y, w, h = camera.love.graphics.getScissor()
  202. if not x then return nil; end
  203. x, y, w, h = camera.present:untransformBox(x, y, w, h)
  204. return x, y, w, h
  205. end
  206. -- TODO: count line CRs?
  207. function string.linecount(s)
  208. local count = 0
  209. local lf = string.byte("\n", 1)
  210. for i=1,s:len() do
  211. if s:byte(i) == lf then count = count + 1; end
  212. end
  213. return count+1
  214. end
  215. function string.lineiter(s)
  216. s = s.."\n"
  217. return s:gmatch("([^\r\n]*)\r*\n\r*")
  218. end
  219. function camera.isShapeVisible(shape)
  220. local minvisx, minvisy, maxvisx, maxvisy = camera.love.graphics.getScissor()
  221. if minvisx then
  222. maxvisx = minvisx + maxvisx
  223. maxvisy = minvisy + maxvisy
  224. else
  225. minvisx = 1; maxvisx = love.graphics.getWindowWidth()
  226. minvisy = 1; maxvisy = love.graphics.getWindowHeight()
  227. end
  228. local x1,y1,x2,y2,x3,y3,x4,y4 = shape:getBoundingBox()
  229. x1, y1 = getCamera():pos(x1, y1)
  230. x3, y3 = getCamera():pos(x3, y3)
  231. if getCamera().rotation ~= 0 then
  232. x2, y2 = getCamera():pos(x2, y2)
  233. x4, y4 = getCamera():pos(x4, y4)
  234. minx = math.min(x1, x2, x3, x4)
  235. miny = math.min(y1, y2, y3, y4)
  236. maxx = math.max(x1, x2, x3, x4)
  237. maxy = math.max(y1, y2, y3, y4)
  238. else
  239. minx = math.min(x1, x3)
  240. miny = math.min(y1, y3)
  241. maxx = math.max(x1, x3)
  242. maxy = math.max(y1, y3)
  243. end
  244. return maxx >= minvisx and minx <= maxvisx and maxy >= minvisy and miny <= maxvisy
  245. end
  246. -- Supports newlines
  247. function camera.getTextWidth(text, font)
  248. font = font or love.graphics.getFont()
  249. local maxwidth = 0
  250. for line in string.lineiter(text) do
  251. maxwidth = math.max(maxwidth, font:getWidth(line))
  252. end
  253. return maxwidth
  254. end
  255. function camera.getFontWidth(font, text)
  256. return camera.getTextWidth(text, font)
  257. end
  258. function camera.getTextHeight(text, limit, font)
  259. font = font or love.graphics.getFont()
  260. if limit then
  261. text = camera.splitTextByTextWidth(text, limit, font)
  262. end
  263. return text:linecount() * font:getHeight() * font:getLineHeight()
  264. end
  265. -- Height of text besides the first line.
  266. function camera.getTextTailHeight(text, limit, font)
  267. font = font or love.graphics.getFont()
  268. if limit then
  269. text = camera.splitTextByTextWidth(text, limit, font)
  270. end
  271. return (text:linecount() - 1) * font:getHeight() * font:getLineHeight()
  272. end
  273. function camera.splitTextByTextWidth(text, width, font)
  274. font = font or love.graphics.getFont()
  275. local s = ""
  276. for line in string.lineiter(text) do
  277. if camera.getTextWidth(line, font) <= width then
  278. s = s..line.."\n"
  279. else
  280. local tmps = ""
  281. for token in line:gmatch("%s*[^%s]+") do
  282. if tmps:len() == 0 or camera.getTextWidth(tmps..token, font) <= width then
  283. tmps = tmps..token
  284. else
  285. s = s..tmps.."\n"
  286. --TODO remove preceding whitespaces
  287. tmps = token
  288. end
  289. end
  290. s = s..tmps.."\n"
  291. end
  292. end
  293. return s:sub(1,s:len()-1)
  294. end
  295. --TODO: particle system
  296. camera.love.graphics.draw = love.graphics.draw
  297. function love.graphics.draw(elem, x, y, angle, sx, sy)
  298. x, y = camera.present:pos(x,y)
  299. angle = (angle or 0) + camera.present.rotation
  300. sx = sx or 1
  301. sy = sy or sx
  302. sx = sx * (math.abs(math.cos(math.rad(angle))^2 * camera.present.scalex) +
  303. math.abs(math.sin(math.rad(angle))^2 * camera.present.scaley))
  304. sy = sy * (math.abs(math.cos(math.rad(angle))^2 * camera.present.scaley) +
  305. math.abs(math.sin(math.rad(angle))^2 * camera.present.scalex))
  306. if camera.present.scalex * camera.present.scaley < 0 then
  307. angle = -angle
  308. end
  309. local nextElem = elem
  310. if type(elem) == "string" then
  311. local c = love.graphics:getFont():getHeight() * love.graphics:getFont():getLineHeight()
  312. if camera.present.scaley < 0 then
  313. x = x - sx * c * math.sin(math.rad(angle))
  314. y = y + sy * c * math.sin(math.rad(angle))
  315. end
  316. for line in string.lineiter(elem) do
  317. camera.love.graphics.draw(line, x, y, angle, sx, sy)
  318. x = x - sx * c * math.sin(math.rad(angle))
  319. y = y + sy * c * math.cos(math.rad(angle))
  320. end
  321. else
  322. if not pcall(camera.love.graphics.draw, elem, x, y, angle, sx, sy) then
  323. camera.love.graphics.draw(elem, x, y)
  324. end
  325. end
  326. end
  327. function love.graphics.drawParticles(system, x, y)
  328. x, y = camera.present:pos(x,y)
  329. camera.love.graphics.draw(system, x, y)
  330. end
  331. camera.love.graphics.drawf = love.graphics.drawf
  332. function love.graphics.drawf(s, x, y, limit, align, sx, sy)
  333. x, y = camera.present:pos(x,y)
  334. align = align or love.align_left
  335. s = camera.splitTextByTextWidth(s, limit * math.abs(camera.present.scalex))
  336. sx = sx or 1
  337. sy = sy or sx
  338. sx = sx * math.abs(camera.present.scalex)
  339. sy = sy * math.abs(camera.present.scaley)
  340. local angle = camera.present.rotation
  341. local cosrot = camera.present.cosrot
  342. local sinrot = camera.present.sinrot
  343. if camera.present.scalex * camera.present.scaley < 0 then
  344. sinrot = -sinrot
  345. angle = -angle
  346. end
  347. limit = limit * math.abs(camera.present.scalex)
  348. local mul = align == love.align_center and 0.5 or align == love.align_right and 1 or 0
  349. for line in string.lineiter(s) do
  350. local width = sx * camera.getTextWidth(line)
  351. camera.love.graphics.draw(line, x + (limit - width) * mul * cosrot, y + (limit - width) * mul * sinrot, angle, sx, sy)
  352. x = x - sx * love.graphics:getFont():getHeight() * love.graphics:getFont():getLineHeight() * sinrot
  353. y = y + sy * love.graphics:getFont():getHeight() * love.graphics:getFont():getLineHeight() * cosrot
  354. end
  355. end
  356. --TODO ox, oy local or not?
  357. camera.love.graphics.draws = love.graphics.draws
  358. function love.graphics.draws(image, x, y, cx, cy, w, h, angle, sx, sy, ox, oy)
  359. angle = (angle or 0) + camera.present.rotation
  360. sx = sx or 1
  361. sy = sy or sx
  362. x, y = camera.present:pos(x,y)
  363. if ox and oy then
  364. ox, oy = camera.present:pos(ox,oy)
  365. end
  366. sx = sx * math.abs(camera.present.scalex)
  367. sy = sy * math.abs(camera.present.scaley)
  368. if camera.present.scalex * camera.present.scaley < 0 then
  369. angle = -angle
  370. end
  371. if ox and oy then
  372. camera.love.graphics.draws(image, x, y, cx, cy, w, h, angle, sx, sy, ox, oy)
  373. else
  374. camera.love.graphics.draws(image, x, y, cx, cy, w, h, angle, sx, sy)
  375. end
  376. end
  377. camera.love.graphics.point = love.graphics.point
  378. function love.graphics.point(x, y)
  379. x, y = camera.present:pos(x,y)
  380. camera.love.graphics.point(x, y)
  381. end
  382. camera.love.graphics.line = love.graphics.line
  383. function love.graphics.line(x1, y1, x2, y2)
  384. x1, y1 = camera.present:pos(x1, y1)
  385. x2, y2 = camera.present:pos(x2, y2)
  386. camera.love.graphics.line(x1, y1, x2, y2)
  387. end
  388. camera.love.graphics.triangle = love.graphics.triangle
  389. function love.graphics.triangle(t, x1, y1, x2, y2, x3, y3)
  390. x1, y1 = camera.present:pos(x1, y1)
  391. x2, y2 = camera.present:pos(x2, y2)
  392. x3, y3 = camera.present:pos(x3, y3)
  393. camera.love.graphics.triangle(t, x1, y1, x2, y2, x3, y3)
  394. end
  395. camera.love.graphics.rectangle = love.graphics.rectangle
  396. function love.graphics.rectangle(t, x, y, w, h)
  397. love.graphics.polygon(t, x, y, x + w, y, x + w, y + h, x, y + h)
  398. end
  399. camera.love.graphics.quad = love.graphics.quad
  400. function love.graphics.quad(t, x1, y1, x2, y2, x3, y3, x4, y4)
  401. x1, y1 = camera.present:pos(x1, y1)
  402. x2, y2 = camera.present:pos(x2, y2)
  403. x3, y3 = camera.present:pos(x3, y3)
  404. x4, y4 = camera.present:pos(x4, y4)
  405. camera.love.graphics.quad(t, x1, y1, x2, y2, x3, y3, x4, y4)
  406. end
  407. -- Ovals not supported
  408. camera.love.graphics.circle = love.graphics.circle
  409. function love.graphics.circle(t, x, y, r, points)
  410. x, y = camera.present:pos(x, y)
  411. r = camera.present:scaleSym(r)
  412. if points then
  413. camera.love.graphics.circle(t, x, y, r, points)
  414. else
  415. camera.love.graphics.circle(t, x, y, r)
  416. end
  417. end
  418. camera.love.graphics.polygon = love.graphics.polygon
  419. function love.graphics.polygon(t, ...)
  420. if #arg > 0 then
  421. if (type((arg[1])) == "table") then
  422. arg = arg[1]
  423. end
  424. if camera.present then
  425. for i=1,#arg/2 do
  426. arg[2*i-1],arg[2*i] = camera.present:pos(arg[2*i-1], arg[2*i])
  427. end
  428. end
  429. camera.love.graphics.polygon(t, unpack(arg))
  430. end
  431. end
  432. camera.love.graphics.setLineWidth = love.graphics.setLineWidth
  433. function love.graphics.setLineWidth(width)
  434. camera.love.graphics.setLineWidth(camera.present:scaleX(width))
  435. end
  436. camera.love.graphics.setLine = love.graphics.setLine
  437. function love.graphics.setLine(width, ...)
  438. camera.love.graphics.setLine(camera.present:scaleX(width), ...)
  439. end
  440. camera.love.graphics.getLineWidth = love.graphics.getLineWidth
  441. function love.graphics.getLineWidth()
  442. return camera.present:unscaleX(camera.love.graphics.getLineWidth())
  443. end
  444. camera.love.graphics.setPointSize = love.graphics.setPointSize
  445. function love.graphics.setPointSize(size)
  446. camera.love.graphics.setPointSize(camera.present:scaleSym(size))
  447. end
  448. camera.love.graphics.setPoint = love.graphics.setPoint
  449. function love.graphics.setPoint(size, ...)
  450. camera.love.graphics.setPoint(camera.present:scaleSym(size), ...)
  451. end
  452. camera.love.graphics.getPointSize = love.graphics.getPointSize
  453. function love.graphics.getPointSize()
  454. return camera.present:unscaleSym(camera.love.graphics.getPointSize())
  455. end
  456. camera.love.graphics.getMaxPointSize = love.graphics.getMaxPointSize
  457. function love.graphics.getMaxPointSize()
  458. return camera.present:unscaleSym(camera.love.graphics.getMaxPointSize())
  459. end
  460. function camera.lateInit()
  461. camera.mousepressed_default = mousepressed or function() end
  462. camera.mousereleased_default = mousereleased or function() end
  463. camera.love.mouse.getX = love.mouse.getX
  464. camera.love.mouse.getY = love.mouse.getY
  465. camera.love.mouse.getPosition = love.mouse.getPosition
  466. camera.love.mouse.setPosition = love.mouse.setPosition
  467. function mousepressed(x, y, ...)
  468. local oldCamera = getCamera()
  469. if camera.mouseCamera then
  470. setCamera(camera.mouseCamera)
  471. end
  472. x, y = camera.present:unpos(x,y)
  473. setCamera(oldCamera)
  474. local ret = camera.mousepressed_default(x, y, ...)
  475. return ret
  476. end
  477. function mousereleased(x, y, ...)
  478. local oldCamera = getCamera()
  479. if camera.mouseCamera then
  480. setCamera(camera.mouseCamera)
  481. end
  482. x, y = camera.present:unpos(x, y)
  483. setCamera(oldCamera)
  484. local ret = camera.mousereleased_default(x, y, ...)
  485. return ret
  486. end
  487. function love.mouse.getX()
  488. local x, y = camera.present:unpos(camera.love.mouse.getX(), camera.love.mouse.getY())
  489. return x
  490. end
  491. function love.mouse.getY()
  492. local x, y = camera.present:unpos(camera.love.mouse.getX(), camera.love.mouse.getY())
  493. return y
  494. end
  495. function love.mouse.getPosition()
  496. return camera.present:unpos(camera.love.mouse.getPosition())
  497. end
  498. function love.mouse.setPosition(x, y)
  499. return camera.love.mouse.setPosition(unpack(camera.present:scale(x,y)))
  500. end
  501. end