UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

141 lines (138 loc) 5.38 kB
import { Plane } from './plane.js'; /** * @import { BoundingSphere } from './bounding-sphere.js' * @import { Mat4 } from '../math/mat4.js' * @import { Vec3 } from '../math/vec3.js' */ /** * 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}. * * @category Math */ class Frustum { /** * Returns a clone of the specified frustum. * * @returns {Frustum} A duplicate frustum. * @example * const frustum = new pc.Frustum(); * const clone = frustum.clone(); */ clone() { /** @type {this} */ const cstr = this.constructor; return new cstr().copy(this); } /** * 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 pc.Frustum(); * dst.copy(src); */ copy(src) { for(let i = 0; i < 6; i++){ this.planes[i].copy(src.planes[i]); } return this; } /** * 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 pc.Mat4(); * projection.setPerspective(45, 16 / 9, 1, 1000); * * // Create a frustum shape that is represented by the matrix * const frustum = new pc.Frustum(); * frustum.setFromMat4(projection); */ setFromMat4(matrix) { const [m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33] = matrix.data; const planes = this.planes; planes[0].set(m03 - m00, m13 - m10, m23 - m20, m33 - m30).normalize(); // RIGHT planes[1].set(m03 + m00, m13 + m10, m23 + m20, m33 + m30).normalize(); // LEFT planes[2].set(m03 + m01, m13 + m11, m23 + m21, m33 + m31).normalize(); // BOTTOM planes[3].set(m03 - m01, m13 - m11, m23 - m21, m33 - m31).normalize(); // TOP planes[4].set(m03 - m02, m13 - m12, m23 - m22, m33 - m32).normalize(); // FAR planes[5].set(m03 + m02, m13 + m12, m23 + m22, m33 + m32).normalize(); // NEAR } /** * 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) { for(let p = 0; p < 6; p++){ const { normal, distance } = this.planes[p]; if (normal.dot(point) + distance <= 0) { return false; } } return true; } /** * Expands this frustum to also contain another frustum. For each of the 6 planes, the plane * that is further out (larger distance) is kept, creating a combined frustum that encompasses * both. This is useful for multi-view rendering such as stereo XR where culling should keep * objects visible in any view. * * Note: This method assumes both frustums have similar orientation (parallel views). This is * valid for WebXR stereo rendering where eyes use parallel projection with only a horizontal * offset, not toe-in convergence. * * @param {Frustum} other - The other frustum to add. * @returns {Frustum} Self for chaining. */ add(other) { const planes = this.planes; const otherPlanes = other.planes; for(let p = 0; p < 6; p++){ if (otherPlanes[p].distance > planes[p].distance) { planes[p].copy(otherPlanes[p]); } } return this; } /** * 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) { const { center, radius } = sphere; let c = 0; for(let p = 0; p < 6; p++){ const { normal, distance } = this.planes[p]; const d = normal.dot(center) + distance; if (d <= -radius) { return 0; } if (d > radius) { c++; } } return c === 6 ? 2 : 1; } /** * Create a new Frustum instance. * * @example * const frustum = new pc.Frustum(); */ constructor(){ /** * The six planes that make up the frustum. * * @type {Plane[]} */ this.planes = []; for(let i = 0; i < 6; i++){ this.planes[i] = new Plane(); } } } export { Frustum };