oktaeder/src/data/Camera.ts

194 lines
4.5 KiB
TypeScript

/*!
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Matrix4x4, Node } from ".";
export type Camera = OrthographicCamera | PerspectiveCamera;
export interface OrthographicCameraProps {
readonly name?: string;
readonly verticalSize: number;
readonly nearPlane: number;
readonly farPlane: number;
}
export interface PerspectiveCameraProps {
readonly name?: string;
readonly verticalFovRad: number;
readonly nearPlane: number;
readonly farPlane: number;
}
export class OrthographicCamera {
readonly type!: "OrthographicCamera";
_name: string;
_halfVerticalSize: number;
_nearPlane: number;
_farPlane: number;
/** backreference */
_node: Node | null;
constructor({
name = "",
verticalSize,
nearPlane,
farPlane,
}: OrthographicCameraProps) {
this._name = name;
this._halfVerticalSize = verticalSize;
this._nearPlane = nearPlane;
this._farPlane = farPlane;
this._node = null;
}
set name(value: string) { this._name = value; }
get name(): string { return this._name; }
set halfVerticalSize(value: number) { this._halfVerticalSize = value; }
get halfVerticalSize(): number { return this._halfVerticalSize; }
set nearPlane(value: number) { this._nearPlane = value; }
get nearPlane(): number { return this._nearPlane; }
set farPlane(value: number) { this._farPlane = value; }
get farPlane(): number { return this._farPlane; }
attach(node: Node): OrthographicCamera {
if (this._node !== null) {
this._node._camera = null;
}
if (node._camera !== null) {
node._camera._node = null;
}
node._camera = this;
this._node = node;
return this;
}
detach(): OrthographicCamera {
if (this._node === null) {
return this;
}
this._node._camera = null;
this._node = null;
return this;
}
computeProjectionMatrix(aspectRatio: number, res: Matrix4x4): Matrix4x4 {
const halfHorizontalSize = this._halfVerticalSize / aspectRatio;
return res.set(
1 / halfHorizontalSize, 0, 0, 0,
0, 1 / this._halfVerticalSize, 0, 0,
0, 0, 1 / (this._nearPlane - this._farPlane), 0,
0, 0, this._farPlane / (this._farPlane - this._nearPlane), 1,
);
}
}
export class PerspectiveCamera {
readonly type!: "PerspectiveCamera";
_name: string;
_verticalFovRad: number;
_nearPlane: number;
_farPlane: number;
/** backreference */
_node: Node | null;
constructor({
name = "",
verticalFovRad,
nearPlane,
farPlane,
}: PerspectiveCameraProps) {
this._name = name;
this._verticalFovRad = verticalFovRad;
this._nearPlane = nearPlane;
this._farPlane = farPlane;
this._node = null;
}
set name(value: string) { this._name = value; }
get name(): string { return this._name; }
set nearPlane(value: number) { this._nearPlane = value; }
get nearPlane(): number { return this._nearPlane; }
set farPlane(value: number) { this._farPlane = value; }
get farPlane(): number { return this._farPlane; }
attach(node: Node): PerspectiveCamera {
if (this._node !== null) {
this._node._camera = null;
}
if (node._camera !== null) {
node._camera._node = null;
}
node._camera = this;
this._node = node;
return this;
}
detach(): PerspectiveCamera {
if (this._node === null) {
return this;
}
this._node._camera = null;
this._node = null;
return this;
}
computeProjectionMatrix(aspectRatio: number, res: Matrix4x4): Matrix4x4 {
const halfVerticalCotangent = 1 / Math.tan(0.5 * this._verticalFovRad);
if (this._farPlane === Infinity) {
return res.set(
halfVerticalCotangent / aspectRatio, 0, 0, 0,
0, halfVerticalCotangent, 0, 0,
0, 0, 0, 1,
0, 0, this._nearPlane, 0,
);
} else {
return res.set(
halfVerticalCotangent / aspectRatio, 0, 0, 0,
0, halfVerticalCotangent, 0, 0,
0, 0, this._nearPlane / (this._nearPlane - this._farPlane), 1,
0, 0, this._nearPlane * this._farPlane / (this._farPlane - this._nearPlane), 0,
);
}
}
}
Object.defineProperty(OrthographicCamera.prototype, "type", { value: "OrthographicCamera" });
Object.defineProperty(PerspectiveCamera.prototype, "type", { value: "PerspectiveCamera" });
export function isOrthographicCamera(value: unknown): value is OrthographicCamera {
return Boolean(value) && (value as OrthographicCamera).type === "OrthographicCamera";
}
export function isPerspectiveCamera(value: unknown): value is PerspectiveCamera {
return Boolean(value) && (value as PerspectiveCamera).type === "PerspectiveCamera";
}