Camera.hx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package h3d;
  2. // use left-handed coordinate system, more suitable for 2D games X=0,Y=0 at screen top-left and Z towards user
  3. class Camera {
  4. public var zoom : Float;
  5. /**
  6. The screenRatio represents the W/H screen ratio.
  7. **/
  8. public var screenRatio : Float;
  9. /**
  10. The vertical FieldOfView, in degrees.
  11. Usually cameras are using an horizontal FOV, but the value will change depending on the screen ratio.
  12. For instance a 4:3 screen will have a lower horizontal FOV than a 16:9 one, however the vertical FOV remains constant.
  13. Use setFovX to initialize fovY based on an horizontal FOV and an initial screen ratio.
  14. **/
  15. public var fovY : Float;
  16. public var zNear : Float;
  17. public var zFar : Float;
  18. /**
  19. Set orthographic bounds.
  20. **/
  21. public var orthoBounds : h3d.col.Bounds;
  22. public var rightHanded : Bool;
  23. public var mproj : Matrix;
  24. public var mcam : Matrix;
  25. public var m : Matrix;
  26. public var pos : Vector;
  27. public var up : Vector;
  28. public var target : Vector;
  29. public var viewX : Float = 0.;
  30. public var viewY : Float = 0.;
  31. public var follow : { pos : h3d.scene.Object, target : h3d.scene.Object };
  32. public var frustum(default, null) : h3d.col.Frustum;
  33. var minv : Matrix;
  34. var mcamInv : Matrix;
  35. var mprojInv : Matrix;
  36. var needInv : Bool;
  37. public function new( fovY = 25., zoom = 1., screenRatio = 1.333333, zNear = 0.02, zFar = 4000., rightHanded = false ) {
  38. this.fovY = fovY;
  39. this.zoom = zoom;
  40. this.screenRatio = screenRatio;
  41. this.zNear = zNear;
  42. this.zFar = zFar;
  43. this.rightHanded = rightHanded;
  44. pos = new Vector(2, 3, 4);
  45. up = new Vector(0, 0, 1);
  46. target = new Vector(0, 0, 0);
  47. m = new Matrix();
  48. mcam = new Matrix();
  49. mproj = new Matrix();
  50. frustum = new h3d.col.Frustum();
  51. update();
  52. }
  53. /**
  54. Set the vertical fov based on a given horizontal fov (in degrees) for a specified screen ratio.
  55. **/
  56. public function setFovX( fovX : Float, withRatio : Float ) {
  57. var degToRad = Math.PI / 180;
  58. fovY = 2 * Math.atan( Math.tan(fovX * 0.5 * degToRad) / withRatio ) / degToRad;
  59. }
  60. /**
  61. Calculate the current horizontal fov (in degrees).
  62. **/
  63. public function getFovX() {
  64. var degToRad = Math.PI / 180;
  65. var halfFovX = Math.atan( Math.tan(fovY * 0.5 * degToRad) * screenRatio );
  66. var fovX = halfFovX * 2 / degToRad;
  67. return fovX;
  68. }
  69. public function clone() {
  70. var c = new Camera(fovY, zoom, screenRatio, zNear, zFar, rightHanded);
  71. c.pos = pos.clone();
  72. c.up = up.clone();
  73. c.target = target.clone();
  74. c.update();
  75. return c;
  76. }
  77. /**
  78. Returns the inverse of the camera matrix view and projection. Cache the result until the next update().
  79. **/
  80. public function getInverseViewProj() {
  81. if( minv == null ) minv = new h3d.Matrix();
  82. if( needInv ) {
  83. minv.initInverse(m);
  84. needInv = false;
  85. }
  86. return minv;
  87. }
  88. /**
  89. Returns the inverse of the camera matrix projection. Cache the result until the next update().
  90. **/
  91. public function getInverseProj() {
  92. if( mprojInv == null ) {
  93. mprojInv = new h3d.Matrix();
  94. mprojInv._44 = 0;
  95. }
  96. if( mprojInv._44 == 0 )
  97. mprojInv.initInverse(mproj);
  98. return mprojInv;
  99. }
  100. /**
  101. Returns the inverse of the camera matrix view only. Cache the result until the next update().
  102. **/
  103. public function getInverseView() {
  104. if( mcamInv == null ) {
  105. mcamInv = new h3d.Matrix();
  106. mcamInv._44 = 0;
  107. }
  108. if( mcamInv._44 == 0 )
  109. mcamInv.initInverse(mcam);
  110. return mcamInv;
  111. }
  112. /**
  113. Setup camera for cubemap rendering on the given face.
  114. **/
  115. public function setCubeMap( face : Int, ?position : h3d.Vector ) {
  116. var dx = 0, dy = 0, dz = 0;
  117. switch( face ) {
  118. case 0: dx = 1; up.set(0,1,0);
  119. case 1: dx = -1; up.set(0,1,0);
  120. case 2: dy = 1; up.set(0,0,-1);
  121. case 3: dy = -1; up.set(0,0,1);
  122. case 4: dz = 1; up.set(0,1,0);
  123. case 5: dz = -1; up.set(0,1,0);
  124. }
  125. if( position != null )
  126. pos.load(position);
  127. target.set(pos.x + dx,pos.y + dy,pos.z + dz);
  128. }
  129. /**
  130. Transforms a 2D screen position into the 3D one according to the current camera.
  131. The screenX and screenY values must be in the [-1,1] range.
  132. The camZ value represents the normalized z in the frustum in the [0,1] range.
  133. [unproject] can be used to get the ray from the camera position to a given screen position by using two different camZ values.
  134. For instance the 3D ray between unproject(0,0,0) and unproject(0,0,1) is the center axis of the 3D frustum.
  135. **/
  136. public function unproject( screenX : Float, screenY : Float, camZ ) {
  137. var p = new h3d.Vector(screenX, screenY, camZ);
  138. p.project(getInverseViewProj());
  139. return p;
  140. }
  141. public function rayFromScreen( pixelX : Float, pixelY : Float, sceneWidth = -1, sceneHeight = -1 ) {
  142. var engine = h3d.Engine.getCurrent();
  143. if( sceneWidth < 0 ) sceneWidth = engine.width;
  144. if( sceneHeight < 0 ) sceneHeight = engine.height;
  145. var rx = (pixelX / sceneWidth - 0.5) * 2;
  146. var ry = (0.5 - pixelY / sceneHeight) * 2;
  147. return h3d.col.Ray.fromPoints(unproject(rx, ry, 0).toPoint(), unproject(rx, ry, 1).toPoint());
  148. }
  149. public function update() {
  150. if( follow != null ) {
  151. var fpos = follow.pos.localToGlobal();
  152. var ftarget = follow.target.localToGlobal();
  153. pos.set(fpos.x, fpos.y, fpos.z);
  154. target.set(ftarget.x, ftarget.y, ftarget.z);
  155. // Animate FOV
  156. if( follow.pos.name != null ) {
  157. var p = follow.pos;
  158. while( p != null ) {
  159. if( p.currentAnimation != null ) {
  160. var v = p.currentAnimation.getPropValue(follow.pos.name, "FOVY");
  161. if( v != null ) {
  162. fovY = v;
  163. break;
  164. }
  165. }
  166. p = p.parent;
  167. }
  168. }
  169. }
  170. makeCameraMatrix(mcam);
  171. makeFrustumMatrix(mproj);
  172. m.multiply(mcam, mproj);
  173. needInv = true;
  174. if( mcamInv != null ) mcamInv._44 = 0;
  175. if( mprojInv != null ) mprojInv._44 = 0;
  176. frustum.loadMatrix(m);
  177. }
  178. public function getFrustumCorners(zMax=1., zMin=0.) : Array<h3d.Vector> {
  179. return [
  180. unproject(-1, 1, zMin), unproject(1, 1, zMin), unproject(1, -1, zMin), unproject(-1, -1, zMin),
  181. unproject(-1, 1, zMax), unproject(1, 1, zMax), unproject(1, -1, zMax), unproject(-1, -1, zMax)
  182. ];
  183. }
  184. public function lostUp() {
  185. var p2 = pos.clone();
  186. p2.normalize();
  187. return Math.abs(p2.dot(up)) > 0.999;
  188. }
  189. public function getViewDirection( dx : Float, dy : Float, dz = 0. ) {
  190. var a = new h3d.col.Point(dx,dy,dz);
  191. a.transform3x3(mcam);
  192. a.normalize();
  193. return a;
  194. }
  195. public function movePosAxis( dx : Float, dy : Float, dz = 0. ) {
  196. var p = new h3d.col.Point(dx, dy, dz);
  197. p.transform3x3(mcam);
  198. pos.x += p.x;
  199. pos.y += p.y;
  200. pos.z += p.z;
  201. }
  202. public function moveTargetAxis( dx : Float, dy : Float, dz = 0. ) {
  203. var p = new h3d.col.Point(dx, dy, dz);
  204. p.transform3x3(mcam);
  205. target.x += p.x;
  206. target.y += p.y;
  207. target.z += p.z;
  208. }
  209. public function forward(speed = 1.) {
  210. var c = 1 - 0.025 * speed;
  211. pos.set(
  212. target.x + (pos.x - target.x) * c,
  213. target.y + (pos.y - target.y) * c,
  214. target.z + (pos.z - target.z) * c
  215. );
  216. }
  217. public function backward(speed = 1.) {
  218. var c = 1 + 0.025 * speed;
  219. pos.set(
  220. target.x + (pos.x - target.x) * c,
  221. target.y + (pos.y - target.y) * c,
  222. target.z + (pos.z - target.z) * c
  223. );
  224. }
  225. function makeCameraMatrix( m : Matrix ) {
  226. // in leftHanded the z axis is positive else it's negative
  227. // this way we make sure that our [ax,ay,-az] matrix follow the same handness as our world
  228. // We build a transposed version of Matrix.lookAt
  229. var az = target.sub(pos);
  230. if( rightHanded ) az.scale(-1);
  231. az.normalize();
  232. var ax = up.cross(az);
  233. ax.normalize();
  234. if( ax.length() == 0 ) {
  235. ax.x = az.y;
  236. ax.y = az.z;
  237. ax.z = az.x;
  238. }
  239. var ay = az.cross(ax);
  240. m._11 = ax.x;
  241. m._12 = ay.x;
  242. m._13 = az.x;
  243. m._14 = 0;
  244. m._21 = ax.y;
  245. m._22 = ay.y;
  246. m._23 = az.y;
  247. m._24 = 0;
  248. m._31 = ax.z;
  249. m._32 = ay.z;
  250. m._33 = az.z;
  251. m._34 = 0;
  252. m._41 = -ax.dot(pos);
  253. m._42 = -ay.dot(pos);
  254. m._43 = -az.dot(pos);
  255. m._44 = 1;
  256. }
  257. public function setTransform( m : Matrix ) {
  258. pos.set(m._41, m._42, m._43);
  259. target.load(pos.add(m.getDirection()));
  260. }
  261. function makeFrustumMatrix( m : Matrix ) {
  262. m.zero();
  263. // this will take into account the aspect ratio and normalize the z value into [0,1] once it's been divided by w
  264. // Matrixes have to solve the following formulaes :
  265. //
  266. // transform P by Mproj and divide everything by
  267. // [x,y,-zNear,1] => [sx/zNear, sy/zNear, 0, 1]
  268. // [x,y,-zFar,1] => [sx/zFar, sy/zFar, 1, 1]
  269. // we apply the screen ratio to the height in order to have the fov being a horizontal FOV. This way we don't have to change the FOV when the screen is enlarged
  270. var bounds = orthoBounds;
  271. if( bounds != null ) {
  272. var w = 1 / (bounds.xMax - bounds.xMin);
  273. var h = 1 / (bounds.yMax - bounds.yMin);
  274. var d = 1 / (bounds.zMax - bounds.zMin);
  275. m._11 = 2 * w;
  276. m._22 = 2 * h;
  277. m._33 = d;
  278. m._41 = -(bounds.xMin + bounds.xMax) * w;
  279. m._42 = -(bounds.yMin + bounds.yMax) * h;
  280. m._43 = -bounds.zMin * d;
  281. m._44 = 1;
  282. } else {
  283. var degToRad = (Math.PI / 180);
  284. var halfFovX = Math.atan( Math.tan(fovY * 0.5 * degToRad) * screenRatio );
  285. var scale = zoom / Math.tan(halfFovX);
  286. m._11 = scale;
  287. m._22 = scale * screenRatio;
  288. m._33 = zFar / (zFar - zNear);
  289. m._34 = 1;
  290. m._43 = -(zNear * zFar) / (zFar - zNear);
  291. }
  292. m._11 += viewX * m._14;
  293. m._21 += viewX * m._24;
  294. m._31 += viewX * m._34;
  295. m._41 += viewX * m._44;
  296. m._12 += viewY * m._14;
  297. m._22 += viewY * m._24;
  298. m._32 += viewY * m._34;
  299. m._42 += viewY * m._44;
  300. // our z is negative in that case
  301. if( rightHanded ) {
  302. m._33 *= -1;
  303. m._34 *= -1;
  304. }
  305. }
  306. /**
  307. Project a 3D point into the 2D screen. Make sure to update() the camera if it's been moved before using that.
  308. **/
  309. public function project( x : Float, y : Float, z : Float, screenWidth : Float, screenHeight : Float, snapToPixel = true, ?p: h3d.Vector) {
  310. if(p == null)
  311. p = new h3d.Vector();
  312. p.set(x, y, z);
  313. p.project(m);
  314. p.x = (p.x + 1) * 0.5 * screenWidth;
  315. p.y = (-p.y + 1) * 0.5 * screenHeight;
  316. if( snapToPixel ) {
  317. p.x = Math.round(p.x);
  318. p.y = Math.round(p.y);
  319. }
  320. return p;
  321. }
  322. public function distanceToDepth( dist : Float ) {
  323. return ((zFar + zNear - 2.0 * zNear * zFar / hxd.Math.clamp(dist, zNear, zFar)) / (zFar - zNear) + 1.0) / 2.0;
  324. }
  325. public function depthToDistance( depth : Float ) {
  326. return (hxd.Math.clamp(depth, 0, 1) * zFar - zNear * zFar) / (zFar - zNear);
  327. }
  328. public function load( cam : Camera ) {
  329. pos.load(cam.pos);
  330. target.load(cam.target);
  331. up.load(cam.up);
  332. if( cam.orthoBounds != null ) {
  333. orthoBounds = new h3d.col.Bounds();
  334. orthoBounds.load(cam.orthoBounds);
  335. } else
  336. orthoBounds = null;
  337. fovY = cam.fovY;
  338. screenRatio = cam.screenRatio;
  339. zoom = cam.zoom;
  340. zNear = cam.zNear;
  341. zFar = cam.zFar;
  342. if( cam.follow != null )
  343. follow = { pos : cam.follow.pos, target : cam.follow.target };
  344. else
  345. follow = null;
  346. viewX = cam.viewX;
  347. viewY = cam.viewY;
  348. update();
  349. }
  350. }