gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
189 lines (188 loc) • 7.33 kB
JavaScript
import { Vec3 } from "../../math/Vec3.mjs";
import { Mat4 } from "../../math/Mat4.mjs";
import { Camera } from "./Camera.mjs";
//#region src/core/cameras/PerspectiveCamera.ts
/**
* Used to create a {@link PerspectiveCamera} and its projection, model and view matrices.
*
* {@link curtains/renderers/GPUCurtainsRenderer.GPUCurtainsRenderer | GPUCurtainsRenderer} and {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer | GPUCameraRenderer} automatically create their own {@link PerspectiveCamera} under the hood, so it is unlikely you'd have to create one by yourself.
*
* {@link PerspectiveCamera} default perspective settings are:
* - {@link PerspectiveCamera#fov | field of view}: 50
* - {@link PerspectiveCamera#near | near plane}: 0.01
* - {@link PerspectiveCamera#far | far plane}: 150
*
* Also note that the {@link PerspectiveCamera} default {@link PerspectiveCamera#position | position} is set at `(0, 0, 10)` so the object created with a default size do not appear too big nor too small.
*/
var PerspectiveCamera = class extends Camera {
/** @ignore */
#fov;
/**
* PerspectiveCamera constructor
* @param parameters - {@link PerspectiveCameraParams} used to create our {@link PerspectiveCamera}.
*/
constructor({ label = "Perspective camera", fov = 50, near = .1, far = 150, width = 1, height = 1, pixelRatio = 1, forceAspect = false, onMatricesChanged = () => {} } = {}) {
super({
label,
near,
far,
pixelRatio,
onMatricesChanged
});
this.forceAspect = forceAspect;
this.position.set(0, 0, 10);
this.size = {
width: 1,
height: 1
};
this.setPerspective({
fov,
near,
far,
width,
height,
pixelRatio
});
}
/**
* Get the {@link PerspectiveCamera.fov | field of view}.
*/
get fov() {
return this.#fov;
}
/**
* Set the {@link PerspectiveCamera.fov | field of view}. Update the {@link projectionMatrix} only if the field of view actually changed.
* @param fov - New field of view.
*/
set fov(fov) {
fov = Math.max(1, Math.min(fov ?? this.fov, 179));
if (fov !== this.fov) {
this.#fov = fov;
this.shouldUpdateProjectionMatrices();
}
this.setVisibleSize();
this.setCSSPerspective();
}
/**
* Set the {@link PerspectiveCamera} {@link RectSize.width | width} and {@link RectSize.height | height}. Update the {@link projectionMatrix} only if the width or height actually changed.
* @param size - New width and height values to use.
*/
setSize({ width, height }) {
if (width !== this.size.width || height !== this.size.height) this.shouldUpdateProjectionMatrices();
this.size.width = width;
this.size.height = height;
this.setVisibleSize();
this.setCSSPerspective();
}
/**
* Sets the {@link PerspectiveCamera} perspective projection settings. Update the {@link projectionMatrix} if needed.
* @param parameters - {@link PerspectiveCameraOptions} to use for the perspective projection.
*/
setPerspective({ fov = this.fov, near = this.near, far = this.far, width = this.size.width, height = this.size.height, pixelRatio = this.pixelRatio } = {}) {
this.setSize({
width,
height
});
this.pixelRatio = pixelRatio;
this.fov = fov;
this.near = near;
this.far = far;
}
/**
* Sets a {@link CSSPerspective} property based on {@link size}, {@link pixelRatio} and {@link fov}.
*
* Used to translate planes along the Z axis using pixel units as CSS would do.
*
* {@link https://stackoverflow.com/questions/22421439/convert-field-of-view-value-to-css3d-perspective-value | See reference}
*/
setCSSPerspective() {
this.CSSPerspective = Math.pow(Math.pow(this.size.width / (2 * this.pixelRatio), 2) + Math.pow(this.size.height / (2 * this.pixelRatio), 2), .5) / Math.tan(this.fov * .5 * Math.PI / 180);
}
/**
* Get visible width / height at a given z-depth from our {@link PerspectiveCamera} parameters.
*
* {@link https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269 | See reference}.
* @param depth - Depth to use for calculations.
* @returns - Visible width and height at given depth.
*/
getVisibleSizeAtDepth(depth = 0) {
const cameraOffset = this.position.z;
if (depth < cameraOffset) depth -= cameraOffset;
else depth += cameraOffset;
const vFOV = this.fov * Math.PI / 180;
const height = 2 * Math.tan(vFOV / 2) * Math.abs(depth);
return {
width: height * this.size.width / this.size.height,
height
};
}
/**
* Updates the {@link PerspectiveCamera} {@link projectionMatrix}.
*/
updateProjectionMatrix() {
this.projectionMatrix.makePerspective({
fov: this.fov,
aspect: this.size.width / this.size.height,
near: this.near,
far: this.far
});
}
/**
* Get the current {@link PerspectiveCamera} frustum planes in the [left, right, top, bottom, near, far] order, based on its {@link projectionMatrix} and {@link viewMatrix}.
* @returns - Frustum planes as an array of 6 faces in the [left, right, top, bottom, near, far] order, made of {@link Float32Array} of length 4.
* @readonly
*/
get frustumPlanes() {
const tempCamMat4 = new Mat4();
const tempCamVec3 = new Vec3();
tempCamMat4.copy(this.projectionMatrix).multiply(this.viewMatrix);
const { elements } = tempCamMat4;
const frustumPlanes = [
new Float32Array(4),
new Float32Array(4),
new Float32Array(4),
new Float32Array(4),
new Float32Array(4),
new Float32Array(4)
];
tempCamVec3.set(elements[3] + elements[0], elements[7] + elements[4], elements[11] + elements[8]);
let l = tempCamVec3.length();
frustumPlanes[0][0] = tempCamVec3.x / l;
frustumPlanes[0][1] = tempCamVec3.y / l;
frustumPlanes[0][2] = tempCamVec3.z / l;
frustumPlanes[0][3] = (elements[15] + elements[12]) / l;
tempCamVec3.set(elements[3] - elements[0], elements[7] - elements[4], elements[11] - elements[8]);
l = tempCamVec3.length();
frustumPlanes[1][0] = tempCamVec3.x / l;
frustumPlanes[1][1] = tempCamVec3.y / l;
frustumPlanes[1][2] = tempCamVec3.z / l;
frustumPlanes[1][3] = (elements[15] - elements[12]) / l;
tempCamVec3.set(elements[3] - elements[1], elements[7] - elements[5], elements[11] - elements[9]);
l = tempCamVec3.length();
frustumPlanes[2][0] = tempCamVec3.x / l;
frustumPlanes[2][1] = tempCamVec3.y / l;
frustumPlanes[2][2] = tempCamVec3.z / l;
frustumPlanes[2][3] = (elements[15] - elements[13]) / l;
tempCamVec3.set(elements[3] + elements[1], elements[7] + elements[5], elements[11] + elements[9]);
l = tempCamVec3.length();
frustumPlanes[3][0] = tempCamVec3.x / l;
frustumPlanes[3][1] = tempCamVec3.y / l;
frustumPlanes[3][2] = tempCamVec3.z / l;
frustumPlanes[3][3] = (elements[15] + elements[13]) / l;
tempCamVec3.set(elements[2], elements[6], elements[10]);
l = tempCamVec3.length();
frustumPlanes[4][0] = tempCamVec3.x / l;
frustumPlanes[4][1] = tempCamVec3.y / l;
frustumPlanes[4][2] = tempCamVec3.z / l;
frustumPlanes[4][3] = elements[14] / l;
tempCamVec3.set(elements[3] - elements[2], elements[7] - elements[6], elements[11] - elements[10]);
l = tempCamVec3.length();
frustumPlanes[5][0] = tempCamVec3.x / l;
frustumPlanes[5][1] = tempCamVec3.y / l;
frustumPlanes[5][2] = tempCamVec3.z / l;
frustumPlanes[5][3] = (elements[15] - elements[14]) / l;
return frustumPlanes;
}
};
//#endregion
export { PerspectiveCamera };