Camera.hx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. package h2d;
  2. /**
  3. A 2D camera representation attached to `h2d.Scene`.
  4. Enables ability to move, scale and rotate the scene viewport.
  5. Scene supports usage of multiple Camera instances.
  6. To configure which layers each Camera renders - `Camera.layerVisible` method should be overridden.
  7. By default, camera does not clip out the contents that are outside camera bounding box, which can be enabled through `Camera.clipViewport`.
  8. Due to Heaps event handling structure, only one Camera instance can handle the mouse/touch input, and can be set through `h2d.Scene.interactiveCamera` variable.
  9. Note that during even handing, interactive camera does not check if the Camera itself is visible nor the layers filters as well as `clipViewport` is not applied.
  10. **/
  11. @:access(h2d.RenderContext)
  12. @:access(h2d.Scene)
  13. @:allow(h2d.Scene)
  14. class Camera {
  15. /**
  16. X position of the camera in world space based on anchorX.
  17. **/
  18. public var x(default, set) : Float;
  19. /**
  20. Y position of the camera in world space based on anchorY.
  21. **/
  22. public var y(default, set) : Float;
  23. /**
  24. Horizontal scale factor of the camera. Scaling applied, using anchored position as pivot.
  25. **/
  26. public var scaleX(default, set) : Float;
  27. /**
  28. Vertical scale factor of the camera. Scaling applied, using anchored position as pivot.
  29. **/
  30. public var scaleY(default, set) : Float;
  31. /**
  32. Rotation of the camera in radians. Camera is rotated around anchored position.
  33. **/
  34. public var rotation(default, set) : Float;
  35. /**
  36. Enables viewport clipping. Allow to restrict rendering area of the camera to the viewport boundaries.
  37. Does not affect the user input when Camera is set as interactive camera.
  38. **/
  39. public var clipViewport : Bool;
  40. /**
  41. Horizontal viewport offset of the camera relative to internal scene viewport (see `h2d.Scene.scaleMode`) in scene coordinates. ( default : 0 )
  42. Automatically scales on scene resize.
  43. **/
  44. public var viewportX(get, set) : Float;
  45. /**
  46. Vertical viewport offset of the camera relative to internal scene viewport (see `h2d.Scene.scaleMode`) in scene coordinates. ( default : 0 )
  47. Automatically scales on scene resize.
  48. **/
  49. public var viewportY(get, set) : Float;
  50. /**
  51. Camera viewport width in scene coordinates. ( default : scene.width )
  52. Automatically scales on scene resize.
  53. **/
  54. public var viewportWidth(get, set) : Float;
  55. /**
  56. Camera viewport height in scene coordinates. ( default: scene.height )
  57. Automatically scales on scene resize.
  58. **/
  59. public var viewportHeight(get, set) : Float;
  60. /**
  61. Horizontal anchor position inside viewport boundaries used for positioning and resize compensation. ( default : 0 )
  62. Value is a percentile (0..1) from left viewport edge to right viewport edge with 0.5 being center.
  63. **/
  64. public var anchorX(default, set) : Float;
  65. /**
  66. Vertical anchor position inside viewport boundaries used for positioning and resize compensation. ( default : 0 )
  67. Value is a percentile (0..1) from top viewport edge to bottom viewport edge with 0.5 being center.
  68. **/
  69. public var anchorY(default, set) : Float;
  70. /**
  71. Camera visibility.
  72. Does not affect the user input when Camera is set as interactive camera.
  73. **/
  74. public var visible : Bool;
  75. /**
  76. Makes camera to follow the referenced Object position.
  77. **/
  78. public var follow : h2d.Object;
  79. /**
  80. Enables `h2d.Object.rotation` sync between `Camera.follow` object and Camera.
  81. **/
  82. public var followRotation : Bool = false;
  83. var posChanged : Bool;
  84. var viewX : Float;
  85. var viewY : Float;
  86. var viewW : Float;
  87. var viewH : Float;
  88. var matA : Float;
  89. var matB : Float;
  90. var matC : Float;
  91. var matD : Float;
  92. var absX : Float;
  93. var absY : Float;
  94. var invDet : Float;
  95. var scene : Scene;
  96. /**
  97. Create a new Camera instance and attach to the given `scene`.
  98. @param scene Optional owner Scene to which camera auto-attaches to.
  99. Note that when Camera is not attached to the Scene, a number of methods would lead to an error if called.
  100. **/
  101. public function new( ?scene : Scene ) {
  102. this.x = 0; this.y = 0;
  103. this.scaleX = 1; this.scaleY = 1;
  104. this.rotation = 0;
  105. this.anchorX = 0;
  106. this.anchorY = 0;
  107. this.viewX = 0; this.viewY = 0;
  108. this.viewW = 1; this.viewH = 1;
  109. this.visible = true;
  110. if (scene != null) scene.addCamera(this);
  111. }
  112. /**
  113. Detaches Camera from the Scene it currently attached to.
  114. **/
  115. public inline function remove() {
  116. if (scene != null) scene.removeCamera(this);
  117. }
  118. /**
  119. Override this method to set visibility only to specific layers. Renders all layers by default.
  120. Does not affect the user input when Camera is set as interactive camera.
  121. Usage example:
  122. ```haxe
  123. final LAYER_SHARED = 0;
  124. final LAYER_PLAYER_1 = 2;
  125. final LAYER_PLAYER_2 = 3;
  126. final LAYER_UI = 4;
  127. // Set first camera to only render shared layer and one that only visible to player 1.
  128. s2d.camera.layerVisible = (layer) -> layer == LAYER_SHARED || layer == LAYER_PLAYER_1;
  129. var player2 = new h2d.Camera(s2d);
  130. // Set second camera to only render shared layer and one that only visible to player 2.
  131. player2.layerVisible = (layer) -> layer == LAYER_SHARED || layer == LAYER_PLAYER_2;
  132. var ui = new h2d.Camera(s2d);
  133. // Set last camera to only render UI layer.
  134. ui.layerVisible = (layer) -> layer == LAYER_UI;
  135. ```
  136. @param layer The rendered layer index in `h2d.Scene`.
  137. @returns `true` if layer can be rendered, `false` otherwise.
  138. **/
  139. public dynamic function layerVisible( layer : Int ) : Bool {
  140. return true;
  141. }
  142. /**
  143. <span class="label">Internal usage</span>
  144. Prepares RenderContext to render the camera contents and clips viewport if necessary. Should call `Camera.exit` afterwards.
  145. **/
  146. @:dox(hide) @:noCompletion public function enter( ctx : RenderContext ) {
  147. ctx.pushCamera(this);
  148. if ( clipViewport ) {
  149. var old = ctx.inFilter;
  150. ctx.inFilter = null;
  151. ctx.pushRenderZone(viewX * scene.width, viewY * scene.height, viewW * scene.width, viewH * scene.height);
  152. ctx.inFilter = old;
  153. }
  154. }
  155. /**
  156. <span class="label">Internal usage</span>
  157. Causes RenderContext to restore the state prior to camera rendering. Should be called after `Camera.enter` when rendering is finished.
  158. **/
  159. @:dox(hide) @:noCompletion public function exit( ctx : RenderContext ) {
  160. if ( clipViewport ) {
  161. var old = ctx.inFilter;
  162. ctx.inFilter = null;
  163. ctx.popRenderZone();
  164. ctx.inFilter = old;
  165. }
  166. ctx.popCamera();
  167. }
  168. /**
  169. <span class="label">Internal usage</span>
  170. Synchronizes the camera transform matrix.
  171. **/
  172. @:access(h2d.Object) @:dox(hide) @:noCompletion
  173. public function sync( ctx : RenderContext, force : Bool = false )
  174. {
  175. if (scene == null) return;
  176. if ( follow != null ) {
  177. this.x = follow.absX;
  178. this.y = follow.absY;
  179. if ( followRotation ) this.rotation = -follow.rotation;
  180. }
  181. if ( posChanged || force ) {
  182. if ( rotation == 0 ) {
  183. matA = scaleX;
  184. matB = 0;
  185. matC = 0;
  186. matD = scaleY;
  187. } else {
  188. var cr = Math.cos(rotation);
  189. var sr = Math.sin(rotation);
  190. matA = scaleX * cr;
  191. matB = scaleX * sr;
  192. matC = scaleY * -sr;
  193. matD = scaleY * cr;
  194. }
  195. absX = Math.round(-(x * matA + y * matC) + (scene.width * anchorX * viewW) + scene.width * viewX);
  196. absY = Math.round(-(x * matB + y * matD) + (scene.height * anchorY * viewH) + scene.height * viewY);
  197. invDet = 1 / (matA * matD - matB * matC);
  198. posChanged = false;
  199. }
  200. }
  201. /**
  202. Sets the `Camera.scaleX` and `Camera.scaleY` to given `x` and `y`.
  203. **/
  204. public inline function setScale( x : Float, y : Float ) {
  205. this.scaleX = x;
  206. this.scaleY = y;
  207. }
  208. /**
  209. Multiplies the `Camera.scaleX` by `x` and `Camera.scaleY` by `y`.
  210. **/
  211. public inline function scale( x : Float, y : Float ) {
  212. this.scaleX *= x;
  213. this.scaleY *= y;
  214. }
  215. /**
  216. Sets the camera position to given `x` and `y`.
  217. **/
  218. public inline function setPosition( x : Float, y : Float ) {
  219. this.x = x;
  220. this.y = y;
  221. }
  222. /**
  223. Moves the camera position by given `dx` and `dy`.
  224. **/
  225. public inline function move( dx : Float, dy : Float ) {
  226. this.x += dx;
  227. this.y += dy;
  228. }
  229. /**
  230. Rotates the camera relative to current rotation by given `angle` in radians.
  231. **/
  232. public inline function rotate( angle : Float ) {
  233. this.rotation += angle;
  234. }
  235. /**
  236. Sets the `Camera.anchorX` and `Camera.anchorY` to given `x` and `y`.
  237. **/
  238. public inline function setAnchor( x : Float, y : Float ) {
  239. this.anchorX = x;
  240. this.anchorY = y;
  241. }
  242. /**
  243. Sets camera viewport dimensions. If `w` or `h` arguments are 0 - scene size is used (width or height respectively).
  244. Requires Camera being attached to a Scene.
  245. **/
  246. public inline function setViewport( x : Float = 0, y : Float = 0, w : Float = 0, h : Float = 0 ) {
  247. checkScene();
  248. this.viewportX = x;
  249. this.viewportY = y;
  250. this.viewportWidth = w == 0 ? scene.width : w;
  251. this.viewportHeight = h == 0 ? scene.height : h;
  252. }
  253. /**
  254. Sets camera viewport dimensions in raw format of 0..1 percentiles.
  255. **/
  256. public inline function setRawViewport( x : Float = 0, y : Float = 0, w : Float = 1, h : Float = 1 ) {
  257. this.viewX = x;
  258. this.viewY = y;
  259. this.viewW = w;
  260. this.viewH = h;
  261. posChanged = true;
  262. }
  263. // Screen <-> Camera
  264. /**
  265. Convert screen position into a local camera position.
  266. Requires Camera being attached to a Scene.
  267. **/
  268. inline function screenXToCamera( mx : Float, my : Float ) : Float {
  269. return sceneXToCamera((mx - scene.offsetX) / scene.viewportScaleX, (my - scene.offsetY) / scene.viewportScaleY);
  270. }
  271. /**
  272. Convert screen position into a local camera position.
  273. Requires Camera being attached to a Scene.
  274. **/
  275. inline function screenYToCamera( mx : Float, my : Float ) : Float {
  276. return sceneYToCamera((mx - scene.offsetX) / scene.viewportScaleX, (my - scene.offsetY) / scene.viewportScaleY);
  277. }
  278. /**
  279. Convert local camera position to absolute screen position.
  280. Requires Camera being attached to a Scene.
  281. **/
  282. inline function cameraXToScreen( mx : Float, my : Float ) : Float {
  283. return cameraXToScene(mx, my) * scene.viewportScaleX + scene.offsetX;
  284. }
  285. /**
  286. Convert local camera position to absolute screen position.
  287. Requires Camera being attached to a Scene.
  288. **/
  289. inline function cameraYToScreen( mx : Float, my : Float ) : Float {
  290. return cameraYToScene(mx, my) * scene.viewportScaleY + scene.offsetY;
  291. }
  292. // Scene <-> Camera
  293. /**
  294. Convert an absolute scene position into a local camera position.
  295. Does not represent screen position, see `Camera.screenXToCamera` to convert position with accounting of `scaleMode`.
  296. **/
  297. inline function sceneXToCamera( mx : Float, my : Float ) : Float {
  298. return ((mx - absX) * matD - (my - absY) * matC) * invDet;
  299. }
  300. /**
  301. Convert an absolute scene position into a local camera position.
  302. Does not represent screen position, see `Camera.screenYToCamera` to convert position with accounting of `scaleMode`.
  303. **/
  304. inline function sceneYToCamera( mx : Float, my : Float ) : Float {
  305. return (-(mx - absX) * matB + (my - absY) * matA) * invDet;
  306. }
  307. /**
  308. Convert local camera position into absolute scene position.
  309. Does not represent screen position, see `Camera.cameraXToScreen` to convert position with accounting of `scaleMode`.
  310. **/
  311. inline function cameraXToScene( mx : Float, my : Float ) : Float {
  312. return mx * matA + my * matC + absX;
  313. }
  314. /**
  315. Convert local camera position into absolute scene position.
  316. Does not represent screen position, see `Camera.cameraYToScreen` to convert position with accounting of `scaleMode`.
  317. **/
  318. inline function cameraYToScene( mx : Float, my : Float ) : Float {
  319. return mx * matB + my * matD + absY;
  320. }
  321. // Point/event
  322. /**
  323. <span class="label">Internal usage</span>
  324. Convert `Event.relX` and `Event.relY` to local camera position.
  325. **/
  326. @:dox(hide) @:noCompletion public function eventToCamera( e : hxd.Event ) {
  327. var x = (e.relX - scene.offsetX) / scene.viewportScaleX - absX;
  328. var y = (e.relY - scene.offsetY) / scene.viewportScaleY - absY;
  329. e.relX = (x * matD - y * matC) * invDet;
  330. e.relY = (-x * matB + y * matA) * invDet;
  331. }
  332. /**
  333. Convert screen position into a local camera position.
  334. Requires Camera being attached to a Scene.
  335. **/
  336. public function screenToCamera( pt : h2d.col.Point ) {
  337. checkScene();
  338. var x = (pt.x - scene.offsetX) / scene.viewportScaleX - absX;
  339. var y = (pt.y - scene.offsetY) / scene.viewportScaleY - absY;
  340. pt.x = (x * matD - y * matC) * invDet;
  341. pt.y = (-x * matB + y * matA) * invDet;
  342. }
  343. /**
  344. Convert local camera position to absolute screen position.
  345. Requires Camera being attached to a Scene.
  346. **/
  347. public function cameraToScreen( pt : h2d.col.Point ) {
  348. checkScene();
  349. var x = pt.x;
  350. var y = pt.y;
  351. pt.x = cameraXToScreen(x, y);
  352. pt.y = cameraYToScreen(x, y);
  353. }
  354. /**
  355. Convert an absolute scene position into a local camera position.
  356. Does not represent screen position, see `Camera.screenToCamera` to convert position with accounting of `scaleMode`.
  357. Requires Camera being attached to a Scene.
  358. **/
  359. public function sceneToCamera( pt : h2d.col.Point ) {
  360. checkScene();
  361. var x = pt.x - absX;
  362. var y = pt.y - absY;
  363. pt.x = (x * matD - y * matC) * invDet;
  364. pt.y = (-x * matB + y * matA) * invDet;
  365. }
  366. /**
  367. Convert local camera position into absolute scene position.
  368. Does not represent screen position, see `Camera.cameraToScreen` to convert position with accounting of `scaleMode`.
  369. Requires Camera being attached to a Scene.
  370. **/
  371. public function cameraToScene( pt : h2d.col.Point ) {
  372. checkScene();
  373. var x = pt.x;
  374. var y = pt.y;
  375. pt.x = cameraXToScene(x, y);
  376. pt.y = cameraYToScene(x, y);
  377. }
  378. inline function checkScene() {
  379. if (scene == null) throw "This method requires Camera to be added to the Scene";
  380. }
  381. // Setters
  382. inline function set_x( v ) {
  383. posChanged = true;
  384. return this.x = v;
  385. }
  386. inline function set_y( v ) {
  387. posChanged = true;
  388. return this.y = v;
  389. }
  390. inline function set_scaleX( v ) {
  391. posChanged = true;
  392. return this.scaleX = v;
  393. }
  394. inline function set_scaleY( v ) {
  395. posChanged = true;
  396. return this.scaleY = v;
  397. }
  398. inline function set_rotation( v ) {
  399. posChanged = true;
  400. return this.rotation = v;
  401. }
  402. inline function get_viewportX() { checkScene(); return viewX * scene.width; }
  403. inline function set_viewportX( v ) {
  404. checkScene();
  405. posChanged = true;
  406. this.viewX = Math.floor(v) / scene.width;
  407. return v;
  408. }
  409. inline function get_viewportY() { checkScene(); return viewY * scene.height; }
  410. inline function set_viewportY( v ) {
  411. checkScene();
  412. posChanged = true;
  413. this.viewY = Math.floor(v) / scene.height;
  414. return v;
  415. }
  416. inline function get_viewportWidth() { checkScene(); return viewW * scene.width; }
  417. inline function set_viewportWidth( v ) {
  418. checkScene();
  419. posChanged = true;
  420. this.viewW = Math.ceil(v) / scene.width;
  421. return v;
  422. }
  423. inline function get_viewportHeight() { checkScene(); return viewH * scene.height; }
  424. inline function set_viewportHeight( v ) {
  425. checkScene();
  426. posChanged = true;
  427. this.viewH = Math.ceil(v) / scene.height;
  428. return v;
  429. }
  430. inline function set_anchorX( v ) {
  431. posChanged = true;
  432. return anchorX = v;
  433. }
  434. inline function set_anchorY( v ) {
  435. posChanged = true;
  436. return anchorY = v;
  437. }
  438. }