diff --git a/src/as/cameras/OrthographicCamera.spec.ts b/src/as/cameras/OrthographicCamera.spec.ts new file mode 100644 index 0000000..cda75c7 --- /dev/null +++ b/src/as/cameras/OrthographicCamera.spec.ts @@ -0,0 +1,81 @@ +/* global QUnit */ + +import { OrthographicCamera } from './OrthographicCamera' + +describe('Cameras', () => { + describe('OrthographicCamera', () => { + // INHERITANCE + // Not needed, we can delete this, we don't need to test that `class Foo extends Bar` works, we assume the language works. + // todo('Extending') + + // INSTANCING + // Not needed, we can delete this, we don't need to test that `new Foo` works, this will be required in all other tests anyway. + // todo('Instancing') + + // ^ NOTE, the Extending and Instancing tests were a thing of long ago, + // when JS has only functions, Three.js was sometimes testing that + // inheritance was done properly. Nowadays, we trust `class` syntax. + + // PUBLIC STUFF + todo('isOrthographicCamera') + todo('copy') + + // TODO this needs to be done or we won't know if it works + todo('setViewOffset') + + // TODO this needs to be done or we won't know if it works + todo('clearViewOffset') + + test('updateProjectionMatrix', () => { + const left: f32 = -1 + const right: f32 = 1 + const top: f32 = 1 + const bottom: f32 = -1 + const near: f32 = 1 + const far: f32 = 3 + const cam = new OrthographicCamera(left, right, top, bottom, near, far) + + // updateProjectionMatrix is called in constructor + const pMatrix = cam.projectionMatrix.elements + + // orthographic projection is given my the 4x4 Matrix + // 2/r-l 0 0 -(l+r/r-l) + // 0 2/t-b 0 -(t+b/t-b) + // 0 0 -2/f-n -(f+n/f-n) + // 0 0 0 1 + + expect(pMatrix[0]).toBe(2 / (right - left), 'm[0,0] === 2 / (r - l)') + expect(pMatrix[5]).toBe(2 / (top - bottom), 'm[1,1] === 2 / (t - b)') + expect(pMatrix[10]).toBe(-2 / (far - near), 'm[2,2] === -2 / (f - n)') + expect(pMatrix[12]).toBe(-((right + left) / (right - left)), 'm[3,0] === -(r+l/r-l)') + expect(pMatrix[13]).toBe(-((top + bottom) / (top - bottom)), 'm[3,1] === -(t+b/b-t)') + expect(pMatrix[14]).toBe(-((far + near) / (far - near)), 'm[3,2] === -(f+n/f-n)') + }) + + todo('toJSON') + + // v TODO + + // OTHERS + // TODO: no no no clone is a camera methods that relied to copy method + // QUnit.test('clone', assert => { + // var left = -1.5, + // right = 1.5, + // top = 1, + // bottom = -1, + // near = 0.1, + // far = 42 + // var cam = new OrthographicCamera(left, right, top, bottom, near, far) + + // var clonedCam = cam.clone() + + // assert.ok(cam.left === clonedCam.left, 'left is equal') + // assert.ok(cam.right === clonedCam.right, 'right is equal') + // assert.ok(cam.top === clonedCam.top, 'top is equal') + // assert.ok(cam.bottom === clonedCam.bottom, 'bottom is equal') + // assert.ok(cam.near === clonedCam.near, 'near is equal') + // assert.ok(cam.far === clonedCam.far, 'far is equal') + // assert.ok(cam.zoom === clonedCam.zoom, 'zoom is equal') + // }) + }) +}) diff --git a/src/as/cameras/OrthographicCamera.ts b/src/as/cameras/OrthographicCamera.ts new file mode 100644 index 0000000..6fce5f3 --- /dev/null +++ b/src/as/cameras/OrthographicCamera.ts @@ -0,0 +1,142 @@ +import { Camera } from './Camera' +import { Object3D } from '../core' + +// @unmanaged +@final +class OrthographicView { + enabled: boolean + fullWidth: f32 + fullHeight: f32 + offsetX: f32 + offsetY: f32 + width: f32 + height: f32 +} + +/** + * Camera with orthographic projection + * + * @example + * var camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + * scene.add( camera ); + * + * @see src/cameras/OrthographicCamera.js + */ +export class OrthographicCamera extends Camera { + readonly isOrthographicCamera: boolean = true + + type: string = 'OrthographicCamera' + + zoom: f32 = 1 + view: OrthographicView | null = null + + /** + * @param left `f32` - Camera frustum left plane. + * @param right `f32` - Camera frustum right plane. + * @param top `f32` - Camera frustum top plane. + * @param bottom `f32` - Camera frustum bottom plane. + * @param near `f32` - Camera frustum near plane. + * @param far `f32` - Camera frustum far plane. + */ + constructor( + public left: f32 = -1, + public right: f32 = 1, + public top: f32 = 1, + public bottom: f32 = -1, + public near: f32 = 0.1, + public far: f32 = 2000 + ) { + super() + this.updateProjectionMatrix() + } + + setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void { + let view = this.view + + if (!view) { + this.view = view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1, + } + } + + view.enabled = true + view.fullWidth = fullWidth + view.fullHeight = fullHeight + view.offsetX = offsetX + view.offsetY = offsetY + view.width = width + view.height = height + + this.updateProjectionMatrix() + } + + clearViewOffset(): void { + const view = this.view + + if (view) { + view.enabled = false + } + + this.updateProjectionMatrix() + } + + /** + * Updates the camera projection matrix. Must be called after change of parameters. + */ + updateProjectionMatrix(): void { + const dx = (this.right - this.left) / (2 * this.zoom) + const dy = (this.top - this.bottom) / (2 * this.zoom) + const cx = (this.right + this.left) / 2 + const cy = (this.top + this.bottom) / 2 + + let left = cx - dx + let right = cx + dx + let top = cy + dy + let bottom = cy - dy + + const view = this.view + + if (view && view.enabled) { + const zoomW = this.zoom / (view.width / view.fullWidth) + const zoomH = this.zoom / (view.height / view.fullHeight) + const scaleW = (this.right - this.left) / view.width + const scaleH = (this.top - this.bottom) / view.height + + left += scaleW * (view.offsetX / zoomW) + right = left + scaleW * (view.width / zoomW) + top -= scaleH * (view.offsetY / zoomH) + bottom = top - scaleH * (view.height / zoomH) + } + + this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far) + + this.projectionMatrixInverse.getInverse(this.projectionMatrix) + } + + // TODO copy method in Camera class + // clone(): OrthographicCamera { + // return new OrthographicCamera().copy(this) + // } + + // toJSON(meta) { + // const data = Object3D.prototype.toJSON.call(this, meta) + + // data.object.zoom = this.zoom + // data.object.left = this.left + // data.object.right = this.right + // data.object.top = this.top + // data.object.bottom = this.bottom + // data.object.near = this.near + // data.object.far = this.far + + // if (this.view !== null) data.object.view = Object.assign({}, this.view) + + // return data + // } +} diff --git a/src/as/cameras/index.spec.ts b/src/as/cameras/index.spec.ts index 36b4377..6e9e0d7 100644 --- a/src/as/cameras/index.spec.ts +++ b/src/as/cameras/index.spec.ts @@ -1,3 +1,4 @@ import './ArrayCamera.spec' import './Camera.spec' +import './OrthographicCamera.spec' import './PerspectiveCamera.spec'