From c135ecdadd8cfb46f2f2b9ec643bf9185bbd3cc7 Mon Sep 17 00:00:00 2001 From: Rudra Date: Wed, 22 Jun 2022 11:30:42 +0530 Subject: [PATCH 1/4] Orthographic camera Class and test file --- src/as/cameras/OrthographicCamera.spec.ts | 76 ++++++++++ src/as/cameras/OrthographicCamera.ts | 166 ++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 src/as/cameras/OrthographicCamera.spec.ts create mode 100644 src/as/cameras/OrthographicCamera.ts diff --git a/src/as/cameras/OrthographicCamera.spec.ts b/src/as/cameras/OrthographicCamera.spec.ts new file mode 100644 index 0000000..40e5743 --- /dev/null +++ b/src/as/cameras/OrthographicCamera.spec.ts @@ -0,0 +1,76 @@ +/* global QUnit */ + +import { OrthographicCamera } from './OrthographicCamera' + +describe('Cameras', () => { + describe('OrthographicCamera', () => { + // INHERITANCE + todo('Extending') + + // INSTANCING + todo('Instancing') + + // PUBLIC STUFF + todo('isOrthographicCamera') + todo('copy') + todo('setViewOffset') + todo('clearViewOffset') + + test('updateProjectionMatrix', () => { + const left = -1, + right = 1, + top = 1, + bottom = -1, + near = 1, + far = 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)) + expect(pMatrix[5]).toBe(2 / (top - bottom)) + expect(pMatrix[10]).toBe(-2 / (far - near)) + expect(pMatrix[12]).toBe(-((right + left) / (right - left))) + expect(pMatrix[13]).toBe(-((top + bottom) / (top - bottom))) + expect(pMatrix[14]).toBe(-((far + near) / (far - near))) + + // expect(pMatrix[0] === 2 / (right - left), 'm[0,0] === 2 / (r - l)') + // expect(pMatrix[5] === 2 / (top - bottom), 'm[1,1] === 2 / (t - b)') + // expect(pMatrix[10] === -2 / (far - near), 'm[2,2] === -2 / (f - n)') + // expect(pMatrix[12] === -((right + left) / (right - left)), 'm[3,0] === -(r+l/r-l)') + // expect(pMatrix[13] === -((top + bottom) / (top - bottom)), 'm[3,1] === -(t+b/b-t)') + // expect(pMatrix[14] === -((far + near) / (far - near)), 'm[3,2] === -(f+n/f-n)') + }) + + todo('toJSON') + + // 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..92f77eb --- /dev/null +++ b/src/as/cameras/OrthographicCamera.ts @@ -0,0 +1,166 @@ +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 { + /** + * @param left Camera frustum left plane. + * @param right Camera frustum right plane. + * @param top Camera frustum top plane. + * @param bottom Camera frustum bottom plane. + * @param near Camera frustum near plane. + * @param far Camera frustum far plane. + */ + + type: string = 'OrthographicCamera' + + zoom: f32 = 1 + view: null | OrthographicView = null + isOrthographicCamera: boolean = true + + /** + * Camera frustum left plane. + */ + // left: f32 + + /** + * Camera frustum right plane. + */ + // right: f32 + + /** + * Camera frustum top plane. + */ + // top: f32 + + /** + * Camera frustum bottom plane. + */ + // bottom: f32 + + /** + * Camera frustum near plane. + */ + // near: f32 + + /** + * Camera frustum far plane. + */ + // far: f32 + + 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() + } + + 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 + + if (this.view !== null && this.view.enabled) { + const zoomW = this.zoom / (this.view.width / this.view.fullWidth) + const zoomH = this.zoom / (this.view.height / this.view.fullHeight) + const scaleW = (this.right - this.left) / this.view.width + const scaleH = (this.top - this.bottom) / this.view.height + + left += scaleW * (this.view.offsetX / zoomW) + right = left + scaleW * (this.view.width / zoomW) + top -= scaleH * (this.view.offsetY / zoomH) + bottom = top - scaleH * (this.view.height / zoomH) + } + + this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far) + + this.projectionMatrixInverse.getInverse(this.projectionMatrix) + } + + setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void { + if (this.view === null) { + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1, + } + } + + this.view.enabled = true + this.view.fullWidth = fullWidth + this.view.fullHeight = fullHeight + this.view.offsetX = offsetX + this.view.offsetY = offsetY + this.view.width = width + this.view.height = height + + this.updateProjectionMatrix() + } + + clearViewOffset(): void { + if (this.view !== null) { + this.view.enabled = false + } + + this.updateProjectionMatrix() + } + + // 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 + // } + + /** + * Updates the camera projection matrix. Must be called after change of parameters. + */ + // updateProjectionMatrix(): void + // setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void + // clearViewOffset(): void + // toJSON(meta?: any): any +} From 4e6b4e38112503bcc0ace8983cb7b35214a69d2f Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Sun, 26 Jun 2022 11:57:29 -0700 Subject: [PATCH 2/4] join doc comments with the members --- src/as/cameras/OrthographicCamera.ts | 111 +++++++++------------------ 1 file changed, 38 insertions(+), 73 deletions(-) diff --git a/src/as/cameras/OrthographicCamera.ts b/src/as/cameras/OrthographicCamera.ts index 92f77eb..2f1ecb9 100644 --- a/src/as/cameras/OrthographicCamera.ts +++ b/src/as/cameras/OrthographicCamera.ts @@ -23,51 +23,21 @@ class OrthographicView { * @see src/cameras/OrthographicCamera.js */ export class OrthographicCamera extends Camera { - /** - * @param left Camera frustum left plane. - * @param right Camera frustum right plane. - * @param top Camera frustum top plane. - * @param bottom Camera frustum bottom plane. - * @param near Camera frustum near plane. - * @param far Camera frustum far plane. - */ + readonly isOrthographicCamera: boolean = true type: string = 'OrthographicCamera' zoom: f32 = 1 view: null | OrthographicView = null - isOrthographicCamera: boolean = true - - /** - * Camera frustum left plane. - */ - // left: f32 - - /** - * Camera frustum right plane. - */ - // right: f32 - - /** - * Camera frustum top plane. - */ - // top: f32 - - /** - * Camera frustum bottom plane. - */ - // bottom: f32 - - /** - * Camera frustum near plane. - */ - // near: f32 /** - * Camera frustum far plane. + * @param left Camera frustum left plane. + * @param right Camera frustum right plane. + * @param top Camera frustum top plane. + * @param bottom Camera frustum bottom plane. + * @param near Camera frustum near plane. + * @param far Camera frustum far plane. */ - // far: f32 - constructor( public left: f32 = -1, public right: f32 = 1, @@ -80,34 +50,6 @@ export class OrthographicCamera extends Camera { this.updateProjectionMatrix() } - 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 - - if (this.view !== null && this.view.enabled) { - const zoomW = this.zoom / (this.view.width / this.view.fullWidth) - const zoomH = this.zoom / (this.view.height / this.view.fullHeight) - const scaleW = (this.right - this.left) / this.view.width - const scaleH = (this.top - this.bottom) / this.view.height - - left += scaleW * (this.view.offsetX / zoomW) - right = left + scaleW * (this.view.width / zoomW) - top -= scaleH * (this.view.offsetY / zoomH) - bottom = top - scaleH * (this.view.height / zoomH) - } - - this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far) - - this.projectionMatrixInverse.getInverse(this.projectionMatrix) - } - setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void { if (this.view === null) { this.view = { @@ -140,6 +82,37 @@ export class OrthographicCamera extends Camera { 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 + + if (this.view !== null && this.view.enabled) { + const zoomW = this.zoom / (this.view.width / this.view.fullWidth) + const zoomH = this.zoom / (this.view.height / this.view.fullHeight) + const scaleW = (this.right - this.left) / this.view.width + const scaleH = (this.top - this.bottom) / this.view.height + + left += scaleW * (this.view.offsetX / zoomW) + right = left + scaleW * (this.view.width / zoomW) + top -= scaleH * (this.view.offsetY / zoomH) + bottom = top - scaleH * (this.view.height / zoomH) + } + + this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far) + + this.projectionMatrixInverse.getInverse(this.projectionMatrix) + } + // toJSON(meta) { // const data = Object3D.prototype.toJSON.call(this, meta) @@ -155,12 +128,4 @@ export class OrthographicCamera extends Camera { // return data // } - - /** - * Updates the camera projection matrix. Must be called after change of parameters. - */ - // updateProjectionMatrix(): void - // setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void - // clearViewOffset(): void - // toJSON(meta?: any): any } From 6ff386cbd854091702ec332b11fe4c222ed66e53 Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Sun, 26 Jun 2022 11:57:47 -0700 Subject: [PATCH 3/4] add ortho cam test to tests --- src/as/cameras/index.spec.ts | 1 + 1 file changed, 1 insertion(+) 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' From 1e7d99783068362d6b53d444de350667af6d7ed3 Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Sun, 26 Jun 2022 12:30:14 -0700 Subject: [PATCH 4/4] get current ortho cam tests passing --- src/as/cameras/OrthographicCamera.spec.ts | 47 ++++++++-------- src/as/cameras/OrthographicCamera.ts | 65 +++++++++++++---------- 2 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/as/cameras/OrthographicCamera.spec.ts b/src/as/cameras/OrthographicCamera.spec.ts index 40e5743..cda75c7 100644 --- a/src/as/cameras/OrthographicCamera.spec.ts +++ b/src/as/cameras/OrthographicCamera.spec.ts @@ -5,24 +5,34 @@ import { OrthographicCamera } from './OrthographicCamera' describe('Cameras', () => { describe('OrthographicCamera', () => { // INHERITANCE - todo('Extending') + // 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 - todo('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 = -1, - right = 1, - top = 1, - bottom = -1, - near = 1, - far = 3 + 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 @@ -34,23 +44,18 @@ describe('Cameras', () => { // 0 0 -2/f-n -(f+n/f-n) // 0 0 0 1 - expect(pMatrix[0]).toBe(2 / (right - left)) - expect(pMatrix[5]).toBe(2 / (top - bottom)) - expect(pMatrix[10]).toBe(-2 / (far - near)) - expect(pMatrix[12]).toBe(-((right + left) / (right - left))) - expect(pMatrix[13]).toBe(-((top + bottom) / (top - bottom))) - expect(pMatrix[14]).toBe(-((far + near) / (far - near))) - - // expect(pMatrix[0] === 2 / (right - left), 'm[0,0] === 2 / (r - l)') - // expect(pMatrix[5] === 2 / (top - bottom), 'm[1,1] === 2 / (t - b)') - // expect(pMatrix[10] === -2 / (far - near), 'm[2,2] === -2 / (f - n)') - // expect(pMatrix[12] === -((right + left) / (right - left)), 'm[3,0] === -(r+l/r-l)') - // expect(pMatrix[13] === -((top + bottom) / (top - bottom)), 'm[3,1] === -(t+b/b-t)') - // expect(pMatrix[14] === -((far + near) / (far - near)), 'm[3,2] === -(f+n/f-n)') + 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 => { diff --git a/src/as/cameras/OrthographicCamera.ts b/src/as/cameras/OrthographicCamera.ts index 2f1ecb9..6fce5f3 100644 --- a/src/as/cameras/OrthographicCamera.ts +++ b/src/as/cameras/OrthographicCamera.ts @@ -28,15 +28,15 @@ export class OrthographicCamera extends Camera { type: string = 'OrthographicCamera' zoom: f32 = 1 - view: null | OrthographicView = null + view: OrthographicView | null = null /** - * @param left Camera frustum left plane. - * @param right Camera frustum right plane. - * @param top Camera frustum top plane. - * @param bottom Camera frustum bottom plane. - * @param near Camera frustum near plane. - * @param far Camera frustum far plane. + * @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, @@ -51,8 +51,10 @@ export class OrthographicCamera extends Camera { } setViewOffset(fullWidth: f32, fullHeight: f32, offsetX: f32, offsetY: f32, width: f32, height: f32): void { - if (this.view === null) { - this.view = { + let view = this.view + + if (!view) { + this.view = view = { enabled: true, fullWidth: 1, fullHeight: 1, @@ -63,20 +65,22 @@ export class OrthographicCamera extends Camera { } } - this.view.enabled = true - this.view.fullWidth = fullWidth - this.view.fullHeight = fullHeight - this.view.offsetX = offsetX - this.view.offsetY = offsetY - this.view.width = width - this.view.height = height + 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 { - if (this.view !== null) { - this.view.enabled = false + const view = this.view + + if (view) { + view.enabled = false } this.updateProjectionMatrix() @@ -96,16 +100,18 @@ export class OrthographicCamera extends Camera { let top = cy + dy let bottom = cy - dy - if (this.view !== null && this.view.enabled) { - const zoomW = this.zoom / (this.view.width / this.view.fullWidth) - const zoomH = this.zoom / (this.view.height / this.view.fullHeight) - const scaleW = (this.right - this.left) / this.view.width - const scaleH = (this.top - this.bottom) / this.view.height + 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 * (this.view.offsetX / zoomW) - right = left + scaleW * (this.view.width / zoomW) - top -= scaleH * (this.view.offsetY / zoomH) - bottom = top - scaleH * (this.view.height / zoomH) + 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) @@ -113,6 +119,11 @@ export class OrthographicCamera extends Camera { 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)