Source

Camera.js

  1. import * as mat4 from 'gl-matrix/mat4';
  2. import * as vec2 from 'gl-matrix/vec2';
  3. import * as vec3 from 'gl-matrix/vec3';
  4. import * as vec4 from 'gl-matrix/vec4';
  5. import * as quat from 'gl-matrix/quat';
  6. import { glMatrix } from 'gl-matrix';
  7. import { project, unproject } from './Math/gl-matrix-extension';
  8. /**
  9. * A camera
  10. *
  11. * @category Scene
  12. */
  13. class Camera {
  14. /**
  15. * Constructor
  16. *
  17. * @param {Camera.Type} [type] Type of camera
  18. */
  19. constructor(type = Camera.Type.Perspective) {
  20. /**
  21. * Camera's direction
  22. *
  23. * @type {glMatrix.vec3}
  24. * @private
  25. */
  26. this.direction = vec3.create();
  27. /**
  28. * Field of view
  29. *
  30. * @type {number}
  31. * @private
  32. */
  33. this.fov = 45.0;
  34. /**
  35. * Visibility limits: min (x) and max (y)
  36. *
  37. * @type {glMatrix.vec2}
  38. * @private
  39. */
  40. this.limits = vec2.fromValues(0.1, 100.0);
  41. /**
  42. * Projection matrix
  43. *
  44. * @type {glMatrix.mat4}
  45. * @private
  46. */
  47. this.matrixProjection = mat4.create();
  48. /**
  49. * View matrix
  50. *
  51. * @type {glMatrix.mat4}
  52. * @private
  53. */
  54. this.matrixView = mat4.create();
  55. /**
  56. * Indicates if the view matrix need an update
  57. *
  58. * @type {boolean}
  59. * @private
  60. */
  61. this.matrixViewNeedUpdate = true;
  62. /**
  63. * Resulting matrix with camera's transformations
  64. *
  65. * @type {glMatrix.mat4}
  66. * @private
  67. */
  68. this.matrixViewProjection = mat4.create();
  69. /**
  70. * Indicates if the view matrix need an update
  71. *
  72. * @type {boolean}
  73. * @private
  74. */
  75. this.matrixViewProjectionNeedUpdate = true;
  76. /**
  77. * Camera's position
  78. *
  79. * @type {glMatrix.vec3}
  80. * @private
  81. */
  82. this.position = vec3.fromValues(0.0, 0.0, 3.0);
  83. /**
  84. * Ratio: 16/9, 4/3, …
  85. *
  86. * @type {number}
  87. * @private
  88. */
  89. this.ratio = 16.0 / 9.0;
  90. /**
  91. * Camera's rotation
  92. *
  93. * @type {glMatrix.quat}
  94. * @private
  95. */
  96. this.rotation = quat.fromValues(0.0, 0.0, 0.0, 1.0);
  97. /**
  98. * Type of camera
  99. *
  100. * @type {Camera.Type}
  101. * @private
  102. */
  103. this.type = type;
  104. /**
  105. * View size with x, y, w and h values
  106. *
  107. * @type {glMatrix.vec4}
  108. * @private
  109. */
  110. this.viewport = vec4.create();
  111. /**
  112. * Zoom
  113. *
  114. * @type {number}
  115. * @default 1.0
  116. * @private
  117. */
  118. this.zoomScale = 1.0;
  119. // Force projection matrix computation
  120. this.setType(this.type);
  121. }
  122. /**
  123. * Set camera's direction: Point to look at
  124. *
  125. * @param {number} x Direction on X
  126. * @param {number} y Direction on Y
  127. * @param {number} z Direction on Z
  128. * @return {Camera} A reference to the instance
  129. */
  130. lookAt(x, y, z) {
  131. vec3.set(this.direction, x, y, z);
  132. this.matrixViewNeedUpdate = true;
  133. return this;
  134. }
  135. /**
  136. * Set camera's position
  137. *
  138. * @param {number} x Position on X
  139. * @param {number} y Position on Y
  140. * @param {number} z Position on Z
  141. * @return {Camera} A reference to the instance
  142. */
  143. move(x, y, z) {
  144. vec3.set(this.position, x, y, z);
  145. this.matrixViewNeedUpdate = true;
  146. return this;
  147. }
  148. /**
  149. * Set camera's rotation
  150. *
  151. * @param {number} yaw A floating value
  152. * @param {number} pitch A floating value
  153. * @return {Camera} A reference to the instance
  154. */
  155. rotate(yaw, pitch) {
  156. const yawQuat = quat.fromValues(0.0, 0.0, 0.0, 1.0);
  157. const pitchQuat = quat.fromValues(0.0, 0.0, 0.0, 1.0);
  158. quat.setAxisAngle(yawQuat, [0.0, 1.0, 0.0], yaw);
  159. quat.setAxisAngle(pitchQuat, [1.0, 0.0, 0.0], -pitch);
  160. quat.multiply(this.rotation, yawQuat, pitchQuat);
  161. /**
  162. * Multiply two vec4
  163. *
  164. * @param {quat} q1 First vector
  165. * @param {quat} q2 Second vector
  166. */
  167. function multiply(q1, q2) {
  168. return [q1[3] * q2[0] + q1[0] * q2[3] + q1[2] * q2[1] - q1[1] * q2[2],
  169. q1[3] * q2[1] + q1[1] * q2[3] + q1[0] * q2[2] - q1[2] * q2[0],
  170. q1[3] * q2[2] + q1[2] * q2[3] + q1[1] * q2[0] - q1[0] * q2[1],
  171. q1[3] * q2[3] + q1[0] * q2[0] + q1[1] * q2[1] - q1[2] * q2[2]];
  172. }
  173. const d = multiply(this.rotation, [this.direction[0], this.direction[1], this.direction[2], 0.0]);
  174. const p = multiply(this.rotation, [this.position[0], this.position[1], this.position[2], 0.0]);
  175. vec3.set(this.direction, d[0], d[1], d[2]);
  176. vec3.set(this.position, p[0], p[1], p[2]);
  177. this.matrixViewNeedUpdate = true;
  178. return this;
  179. }
  180. /**
  181. * Set field of view
  182. *
  183. * @param {number} value Value in degrees (default: 45)
  184. * @return {Camera} A reference to the instance
  185. */
  186. setFieldOfView(value) {
  187. this.fov = value;
  188. this.setType(this.type); // Force projection matrix update
  189. return this;
  190. }
  191. /**
  192. * Set screen's ratio
  193. *
  194. * @param {number} ratio Ratio to assign (4/3, 16/9, …)
  195. * @return {Camera} A reference to the instance
  196. */
  197. setRatio(ratio) {
  198. this.ratio = ratio;
  199. this.setType(this.type); // Force projection matrix update
  200. return this;
  201. }
  202. /**
  203. * Set camera's distances
  204. *
  205. * @param {Camera.Type} type Type asked, for 2D you should use "Orthographic"
  206. * @return {Camera} A reference to the instance
  207. */
  208. setType(type) {
  209. // Save type
  210. this.type = type;
  211. // Compute projection matrix
  212. if (type === Camera.Type.Perspective) {
  213. mat4.perspective(this.matrixProjection, glMatrix.toRadian(this.fov * this.zoomScale), this.ratio, this.limits[0], this.limits[1]);
  214. } else {
  215. mat4.ortho(this.matrixProjection,
  216. (-1.5 * this.ratio) * this.zoomScale,
  217. (+1.5 * this.ratio) * this.zoomScale,
  218. (-1.5 * this.zoomScale),
  219. (+1.5 * this.zoomScale),
  220. this.limits[0],
  221. this.limits[1]);
  222. }
  223. this.matrixViewProjectionNeedUpdate = true;
  224. return this;
  225. }
  226. /**
  227. * Set camera's distances
  228. *
  229. * @param {number} min Minimum distance to show
  230. * @param {number} max Maximum distance to show
  231. * @return {Camera} A reference to the instance
  232. */
  233. setViewDistances(min, max) {
  234. vec2.set(this.limits, min, max);
  235. this.setType(this.type); // Force projection matrix update
  236. return this;
  237. }
  238. /**
  239. * Set camera's viewport
  240. *
  241. * @param {number} x View start position on X
  242. * @param {number} y View start position on Y
  243. * @param {number} w View size on X
  244. * @param {number} h View size on Y
  245. * @return {Camera} A reference to the instance
  246. */
  247. setViewport(x, y, w, h) {
  248. vec4.set(this.viewport, x, y, w, h);
  249. this.setRatio(w / h);
  250. return this;
  251. }
  252. /**
  253. * Zoom
  254. *
  255. * @param {number} zoomValue Zoom scale to apply
  256. * @return {Camera} A reference to the instance
  257. */
  258. zoom(zoomValue) {
  259. this.zoomScale = 1.0 / zoomValue;
  260. this.setType(this.type); // Force projection matrix update
  261. return this;
  262. }
  263. /**
  264. * Get camera's position
  265. *
  266. * @return {!Array.<number>} A vector with three values: x, y and z
  267. */
  268. getPosition() {
  269. return [this.position[0], this.position[1], this.position[2]];
  270. }
  271. /**
  272. * Get camera's projection matrix
  273. *
  274. * @return {!glMatrix.mat4} A matrix
  275. */
  276. getProjectionMatrix() {
  277. return this.matrixProjection;
  278. }
  279. /**
  280. * Get camera's matrix
  281. *
  282. * @return {!glMatrix.mat4} A matrix
  283. */
  284. getViewMatrix() {
  285. if (this.matrixViewNeedUpdate) {
  286. mat4.lookAt(this.matrixView, this.position, this.direction, vec3.fromValues(0.0, 1.0, 0.0));
  287. this.matrixViewNeedUpdate = false;
  288. this.matrixViewProjectionNeedUpdate = true;
  289. }
  290. return this.matrixView;
  291. }
  292. /**
  293. * Get camera's viewport
  294. *
  295. * @return {!glMatrix.vec3} A vector with four values: x, y, w and h
  296. */
  297. getViewport() {
  298. return this.viewport;
  299. }
  300. /**
  301. * Get camera's matrix
  302. *
  303. * @return {!glMatrix.mat4} A matrix
  304. */
  305. getViewProjectionMatrix() {
  306. if (this.matrixViewProjectionNeedUpdate || this.matrixViewNeedUpdate) {
  307. mat4.multiply(this.matrixViewProjection, this.getProjectionMatrix(), this.getViewMatrix());
  308. this.matrixViewProjectionNeedUpdate = false;
  309. }
  310. return this.matrixViewProjection;
  311. }
  312. /**
  313. * Convert a point in 2D space to the 3D space
  314. *
  315. * Z value must have one of this two values:
  316. * - 0 for near plane
  317. * - 1 for far plane
  318. *
  319. * @param {Array.<number>} position Position in 2D space/a vec3
  320. * @return {!glMatrix.vec3} An array with position in 3D
  321. */
  322. screenToWorldPoint(position) {
  323. return unproject([position[0], position[1], position[2]],
  324. this.getViewMatrix(),
  325. this.getProjectionMatrix(),
  326. this.viewport);
  327. }
  328. /**
  329. * Convert a point in 3D space to the 2D space
  330. *
  331. * @param {Array.<number>} position Position in 3D space/a vec3
  332. * @return {!glMatrix.vec2} An array with position in 2D
  333. */
  334. worldToScreenPoint(position) {
  335. return project([position[0], position[1], position[2]],
  336. this.getViewMatrix(),
  337. this.getProjectionMatrix(),
  338. this.viewport);
  339. }
  340. }
  341. /**
  342. * Types
  343. *
  344. * @type {{Perspective: number, Orthographic: number}}
  345. */
  346. Camera.Type = { Perspective: 0, Orthographic: 1 };
  347. export default Camera;