/** * A frustum is a shape that defines the viewing space of a camera. It can be used to determine * visibility of points and bounding spheres. Typically, you would not create a Frustum shape * directly, but instead query {@link CameraComponent#frustum}. * * A frustum is six {@link Plane}s, read and written with {@link getPlane} and {@link setPlane}, * and normally derived from a camera's combined view-projection matrix with {@link setFromMat4}. * {@link containsPoint} and {@link containsAabb} return a boolean. {@link containsSphere} returns 0 * for a sphere outside, 1 for one that intersects and 2 for one fully inside, so callers can skip * finer tests for objects that are entirely visible. None of the tests allocate. * * @example * // Skip work for objects the camera cannot see * const frustum = entity.camera.frustum; * if (frustum.containsAabb(meshInstance.aabb)) { * // visible: update it * } * @category Math */ export class Frustum { /** * The six planes of the frustum, packed as four floats each - the normal's x, y and z followed * by the plane's distance from the origin - in the order right, left, bottom, top, far, near. * The normals point inwards, so a point is outside a plane when * `normal.dot(point) + distance` is negative. * * This is the frustum's storage, exposed for internal use where the packed form avoids * per-plane object access. Use {@link Frustum#getPlane} and {@link Frustum#setPlane} instead. * * @type {Float32Array} * @ignore */ planeData: Float32Array; /** * @type {Plane[]} * @deprecated Use {@link Frustum#getPlane} and {@link Frustum#setPlane} instead. * @ignore */ get planes(): Plane[]; /** * Returns a clone of the specified frustum. * * @returns {Frustum} A duplicate frustum. * @example * const frustum = new Frustum(); * const clone = frustum.clone(); */ clone(): Frustum; /** * Copies the contents of a source frustum to a destination frustum. * * @param {Frustum} src - A source frustum to copy to the destination frustum. * @returns {Frustum} Self for chaining. * @example * const src = entity.camera.frustum; * const dst = new Frustum(); * dst.copy(src); */ copy(src: Frustum): Frustum; /** * Returns one of the frustum's six planes. The planes are ordered right, left, bottom, top, * far, near, and their normals point inwards. * * @param {number} index - The index of the plane, from 0 to 5. * @param {Plane} result - The plane to write to. * @returns {Plane} The supplied plane, containing the frustum plane. * @example * const plane = new Plane(); * entity.camera.frustum.getPlane(0, plane); */ getPlane(index: number, result: Plane): Plane; /** * Sets one of the frustum's six planes. The plane is normalized as it is stored, as the * frustum's tests require unit length normals. The planes are ordered right, left, bottom, top, * far, near, and their normals must point inwards. * * @param {number} index - The index of the plane, from 0 to 5. * @param {Plane} plane - The plane to store. * @returns {Frustum} Self for chaining. */ setPlane(index: number, plane: Plane): Frustum; /** * Stores a normalized plane at the given index. * * @param {number} index - The index of the plane, from 0 to 5. * @param {number} nx - The x component of the plane normal. * @param {number} ny - The y component of the plane normal. * @param {number} nz - The z component of the plane normal. * @param {number} distance - The plane's distance from the origin. * @private */ private _setPlane; /** * Updates the frustum shape based on the supplied 4x4 matrix. * * @param {Mat4} matrix - The matrix describing the shape of the frustum. * @example * // Create a perspective projection matrix * const projection = new Mat4(); * projection.setPerspective(45, 16 / 9, 1, 1000); * * // Create a frustum shape that is represented by the matrix * const frustum = new Frustum(); * frustum.setFromMat4(projection); */ setFromMat4(matrix: Mat4): void; /** * Tests whether a point is inside the frustum. Note that points lying in a frustum plane are * considered to be outside the frustum. * * @param {Vec3} point - The point to test. * @returns {boolean} True if the point is inside the frustum, false otherwise. */ containsPoint(point: Vec3): boolean; /** * Expands this frustum to also contain another frustum. The other frustum's 8 corner points * are computed, and each of this frustum's planes is pushed outwards just far enough to * contain them all. The result is a conservative convex volume that contains both frustums. * This is useful for multi-view rendering such as stereo XR, where culling should keep * objects visible in any view. * * Note: keeping each plane's orientation makes this correct for arbitrary frusta, including * the asymmetric per-eye projections of XR headsets, where matching planes of the two eyes * have different normals and a per-plane "outermost" selection would wrongly cut into the * combined volume at a distance. * * @param {Frustum} other - The other frustum to add. * @returns {Frustum} Self for chaining. */ add(other: Frustum): Frustum; /** * Tests whether a bounding sphere intersects the frustum. If the sphere is outside the * frustum, zero is returned. If the sphere intersects the frustum, 1 is returned. If the * sphere is completely inside the frustum, 2 is returned. Note that a sphere touching a * frustum plane from the outside is considered to be outside the frustum. * * @param {BoundingSphere} sphere - The sphere to test. * @returns {number} 0 if the bounding sphere is outside the frustum, 1 if it intersects the * frustum and 2 if it is contained by the frustum. */ containsSphere(sphere: BoundingSphere): number; /** * Tests whether an axis aligned bounding box intersects the frustum. * * The test is conservative in the same way the plane based sphere test is: a box lying just * outside a frustum corner can be reported as intersecting. It is however always at least as * tight as testing the box's bounding sphere, since the extent of a box along a plane normal * never exceeds the radius of its bounding sphere. * * Unlike {@link Frustum#containsSphere}, a box completely inside the frustum is not * distinguished from one merely intersecting it. Detecting that costs a comparison per plane * and no caller needs it. * * @param {BoundingBox} aabb - The bounding box to test. * @returns {boolean} True if the bounding box intersects or is inside the frustum, false if it * is completely outside. */ containsAabb(aabb: BoundingBox): boolean; } import { Plane } from './plane.js'; import type { Mat4 } from '../math/mat4.js'; import { Vec3 } from '../math/vec3.js'; import type { BoundingSphere } from './bounding-sphere.js'; import type { BoundingBox } from './bounding-box.js';