UNPKG

playcanvas

Version:

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

863 lines (862 loc) 28.5 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Color } from "../core/math/color.js"; import { Debug } from "../core/debug.js"; import { Mat4 } from "../core/math/mat4.js"; import { Vec3 } from "../core/math/vec3.js"; import { Vec4 } from "../core/math/vec4.js"; import { math } from "../core/math/math.js"; import { Frustum } from "../core/shape/frustum.js"; import { VIEW_CENTER, ASPECT_AUTO, PROJECTION_PERSPECTIVE, PROJECTION_ORTHOGRAPHIC, LAYERID_WORLD, LAYERID_DEPTH, LAYERID_SKYBOX, LAYERID_UI, LAYERID_IMMEDIATE } from "./constants.js"; import { FramePassColorGrab } from "./graphics/frame-pass-color-grab.js"; import { FramePassDepthGrab } from "./graphics/frame-pass-depth-grab.js"; import { CameraShaderParams } from "./camera-shader-params.js"; const _deviceCoord = new Vec3(); const _halfSize = new Vec3(); const _point = new Vec3(); const _invViewProjMat = new Mat4(); const _xrViewProjMat = new Mat4(); const _xrViewFrustum = new Frustum(); const _frustumViewInvMat = new Mat4(); const _frustumViewMat = new Mat4(); const _frustumViewProjMat = new Mat4(); const _frustumPoints = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()]; const _Camera = class _Camera { /** * @param {GraphicsDevice} graphicsDevice - The graphics device this camera will use for * automatic aspect ratio calculation against the backbuffer size. */ constructor(graphicsDevice) { /** * @type {ShaderPassInfo|null} */ __publicField(this, "shaderPassInfo", null); /** * @type {FramePassColorGrab|null} */ __publicField(this, "renderPassColorGrab", null); /** * @type {FramePassDepthGrab|null} */ __publicField(this, "renderPassDepthGrab", null); /** * The fog parameters. * * @type {FogParams|null} */ __publicField(this, "fogParams", null); /** * Shader parameters used to generate and use matching shaders. * * @type {CameraShaderParams} */ __publicField(this, "shaderParams", new CameraShaderParams()); /** * Frame passes used to render this camera. If empty, the camera will render using the default * frame passes. * * @type {FramePass[]} */ __publicField(this, "framePasses", []); /** * Frame passes that execute before this camera's main scene rendering, after the camera's * directional shadow passes. Entries are picked up by the RenderPassForward that renders * this camera's layers. * * @type {FramePass[]} */ __publicField(this, "beforePasses", []); /** @type {number} */ __publicField(this, "jitter", 0); /** * The graphics device used by this camera. Required so the camera can compute its aspect * ratio from the backbuffer size when no render target is assigned. * * @type {GraphicsDevice} */ __publicField(this, "device"); Debug.assert(graphicsDevice, "Camera constructor requires a GraphicsDevice."); this.device = graphicsDevice; this._aspectRatio = 16 / 9; this._aspectRatioMode = ASPECT_AUTO; this._calculateProjection = null; this._calculateTransform = null; this._clearColor = new Color(0.75, 0.75, 0.75, 1); this._clearColorBuffer = true; this._clearDepth = 1; this._clearDepthBuffer = true; this._clearStencil = 0; this._clearStencilBuffer = true; this._cullFaces = true; this._farClip = 1e3; this._flipFaces = false; this._fov = 45; this._frustumCulling = true; this._horizontalFov = false; this._layers = [LAYERID_WORLD, LAYERID_DEPTH, LAYERID_SKYBOX, LAYERID_UI, LAYERID_IMMEDIATE]; this._layersSet = new Set(this._layers); this._nearClip = 0.1; this._node = null; this._orthoHeight = 10; this._projection = PROJECTION_PERSPECTIVE; this._rect = new Vec4(0, 0, 1, 1); this._renderTarget = null; this._scissorRect = new Vec4(0, 0, 1, 1); this._scissorRectClear = false; this._aperture = 16; this._shutter = 1 / 1e3; this._sensitivity = 1e3; this._projMat = new Mat4(); this._projMatDirty = true; this._projMatSkybox = new Mat4(); this._viewMat = new Mat4(); this._viewMatDirty = true; this._viewProjMat = new Mat4(); this._viewProjMatDirty = true; this._shaderMatricesVersion = 0; this._viewProjInverse = new Mat4(); this._viewProjCurrent = null; this._viewProjPrevious = new Mat4(); this._jitters = [0, 0, 0, 0]; this.frustum = new Frustum(); this._cullLayers = /* @__PURE__ */ new Set(); this._xrViews = null; this._xrProperties = { horizontalFov: this._horizontalFov, fov: this._fov, aspectRatio: this._aspectRatio, farClip: this._farClip, nearClip: this._nearClip }; } /** * Builds the projection matrix matching shader `matrix_projection` after optional flip-Y * for render targets and optional WebGPU clip-depth range adjustment. * * @param {Mat4} projection - Source projection ({@link Camera#projectionMatrix}). * @param {Mat4} out - Receives the transformed matrix. * @param {boolean} flipY - When true, apply render-target Y flip first. * @param {boolean} applyWebGpuDepthRange - When true, map clip Z from -1..1 to 0..1. * @returns {Mat4} out */ static applyShaderProjectionTransform(projection, out, flipY, applyWebGpuDepthRange) { if (!flipY && !applyWebGpuDepthRange) { out.copy(projection); return out; } if (flipY && applyWebGpuDepthRange) { const scratch = _Camera._applyShaderProjectionScratch; scratch.mul2(_Camera._flipYProjectionMatrix, projection); out.mul2(_Camera._webGpuDepthRangeMatrix, scratch); return out; } if (flipY) { out.mul2(_Camera._flipYProjectionMatrix, projection); return out; } out.mul2(_Camera._webGpuDepthRangeMatrix, projection); return out; } destroy() { this.renderPassColorGrab?.destroy(); this.renderPassColorGrab = null; this.renderPassDepthGrab?.destroy(); this.renderPassDepthGrab = null; this.framePasses.length = 0; this.beforePasses.length = 0; } /** * Store camera matrices required by TAA. Only update them once per frame. */ _storeShaderMatrices(viewProjMat, jitterX, jitterY, renderVersion) { if (this._shaderMatricesVersion !== renderVersion) { this._shaderMatricesVersion = renderVersion; this._viewProjPrevious.copy(this._viewProjCurrent ?? viewProjMat); this._viewProjCurrent ?? (this._viewProjCurrent = new Mat4()); this._viewProjCurrent.copy(viewProjMat); this._viewProjInverse.invert(viewProjMat); this._jitters[2] = this._jitters[0]; this._jitters[3] = this._jitters[1]; this._jitters[0] = jitterX; this._jitters[1] = jitterY; } } /** * True if the camera clears the full render target. (viewport / scissor are full size) */ get fullSizeClearRect() { const rect = this._scissorRectClear ? this.scissorRect : this._rect; return rect.x === 0 && rect.y === 0 && rect.z === 1 && rect.w === 1; } set aspectRatio(newValue) { if (this._aspectRatio !== newValue) { this._aspectRatio = newValue; this._projMatDirty = true; } } get aspectRatio() { if (this.xrActive) return this._xrProperties.aspectRatio; if (this._aspectRatioMode === ASPECT_AUTO) { const newValue = this.calculateAspectRatio(); if (this._aspectRatio !== newValue) { this._aspectRatio = newValue; this._projMatDirty = true; } } return this._aspectRatio; } set aspectRatioMode(newValue) { if (this._aspectRatioMode !== newValue) { this._aspectRatioMode = newValue; this._projMatDirty = true; } } get aspectRatioMode() { return this._aspectRatioMode; } set calculateProjection(newValue) { this._calculateProjection = newValue; this._projMatDirty = true; } get calculateProjection() { return this._calculateProjection; } set calculateTransform(newValue) { this._calculateTransform = newValue; } get calculateTransform() { return this._calculateTransform; } set clearColor(newValue) { this._clearColor.copy(newValue); } get clearColor() { return this._clearColor; } set clearColorBuffer(newValue) { this._clearColorBuffer = newValue; } get clearColorBuffer() { return this._clearColorBuffer; } set clearDepth(newValue) { this._clearDepth = newValue; } get clearDepth() { return this._clearDepth; } set clearDepthBuffer(newValue) { this._clearDepthBuffer = newValue; } get clearDepthBuffer() { return this._clearDepthBuffer; } set clearStencil(newValue) { this._clearStencil = newValue; } get clearStencil() { return this._clearStencil; } set clearStencilBuffer(newValue) { this._clearStencilBuffer = newValue; } get clearStencilBuffer() { return this._clearStencilBuffer; } set cullFaces(newValue) { this._cullFaces = newValue; } get cullFaces() { return this._cullFaces; } set farClip(newValue) { if (this._farClip !== newValue) { this._farClip = newValue; this._projMatDirty = true; } } get farClip() { return this.xrActive ? this._xrProperties.farClip : this._farClip; } set flipFaces(newValue) { this._flipFaces = newValue; } get flipFaces() { return this._flipFaces; } set fov(newValue) { if (this._fov !== newValue) { this._fov = newValue; this._projMatDirty = true; } } get fov() { return this.xrActive ? this._xrProperties.fov : this._fov; } set frustumCulling(newValue) { this._frustumCulling = newValue; } get frustumCulling() { return this._frustumCulling; } set horizontalFov(newValue) { if (this._horizontalFov !== newValue) { this._horizontalFov = newValue; this._projMatDirty = true; } } get horizontalFov() { return this.xrActive ? this._xrProperties.horizontalFov : this._horizontalFov; } set layers(newValue) { this._layers = newValue.slice(0); this._layersSet = new Set(this._layers); } /** * Gets the layer IDs this camera renders. Use the setter to replace the array; do not mutate * the returned array. * * @type {ReadonlyArray<number>} */ get layers() { return this._layers; } get layersSet() { return this._layersSet; } set nearClip(newValue) { if (this._nearClip !== newValue) { this._nearClip = newValue; this._projMatDirty = true; } } get nearClip() { return this.xrActive ? this._xrProperties.nearClip : this._nearClip; } set node(newValue) { this._node = newValue; } get node() { return this._node; } set orthoHeight(newValue) { if (this._orthoHeight !== newValue) { this._orthoHeight = newValue; this._projMatDirty = true; } } get orthoHeight() { return this._orthoHeight; } set projection(newValue) { if (this._projection !== newValue) { this._projection = newValue; this._projMatDirty = true; } } get projection() { return this._projection; } get projectionMatrix() { this._evaluateProjectionMatrix(); return this._projMat; } set rect(newValue) { this._rect.copy(newValue); this._projMatDirty = true; } get rect() { return this._rect; } set renderTarget(newValue) { this._renderTarget = newValue; this._projMatDirty = true; } get renderTarget() { return this._renderTarget; } set scissorRect(newValue) { this._scissorRect.copy(newValue); } get scissorRect() { return this._scissorRect; } get viewMatrix() { if (this._viewMatDirty) { const wtm = this._node.getWorldTransform(); this._viewMat.copy(wtm).invert(); this._viewMatDirty = false; } return this._viewMat; } set aperture(newValue) { this._aperture = newValue; } get aperture() { return this._aperture; } set sensitivity(newValue) { this._sensitivity = newValue; } get sensitivity() { return this._sensitivity; } set shutter(newValue) { this._shutter = newValue; } get shutter() { return this._shutter; } /** * Sets the list of {@link RenderView}s this camera renders with (one per XR eye/screen), or * null when not rendering an XR session. Set by the XR manager. * * @param {RenderView[]|null} value - The per-view list, or null when not in XR. */ set xrViews(value) { if (value !== null !== (this._xrViews !== null)) { this._projMatDirty = true; } this._xrViews = value; } /** * @type {RenderView[]|null} */ get xrViews() { return this._xrViews; } /** * True while an XR session owns this camera (equivalent to {@link Camera#xrViews} being set). * * @type {boolean} */ get xrActive() { return this._xrViews !== null; } /** * Registers a layer to be culled for this camera in the current frame. Used by the renderer's * request/execute mesh-instance culling. * * @param {Layer} layer - The layer to cull for this camera. * @returns {boolean} True if this is the first layer registered for this camera this frame, * letting the caller track the camera exactly once. * @ignore */ addCullLayer(layer) { const first = this._cullLayers.size === 0; this._cullLayers.add(layer); return first; } /** * Gets the set of layers registered to be culled for this camera in the current frame. For * read-only iteration; use {@link Camera#addCullLayer} and {@link Camera#clearCullLayers} to * mutate it. * * @type {Set<Layer>} * @ignore */ get cullLayers() { return this._cullLayers; } /** * Clears the set of layers registered to be culled for this camera, called once the camera's * culling has been performed. * * @ignore */ clearCullLayers() { this._cullLayers.clear(); } /** * Calculates the aspect ratio that should be used for the camera, based on the size of the * given render target (or the backbuffer if no render target is given), and the camera's * `rect`. The `rect` is included so that a camera rendering into a sub-region of a render * target gets the aspect ratio of the actual rendered pixel area (important for split-screen * and similar setups, otherwise rendering would appear stretched). * * @param {RenderTarget|null} [rt] - Optional render target. If unspecified, the camera's * own render target (or, if that is also null, the backbuffer) is used. * @returns {number} The computed aspect ratio. */ calculateAspectRatio(rt) { const target = rt ?? this._renderTarget; const width = target ? target.width : this.device.width; const height = target ? target.height : this.device.height; return width * this._rect.z / (height * this._rect.w); } /** * Creates a duplicate of the camera. * * @returns {Camera} A cloned Camera. */ clone() { return new _Camera(this.device).copy(this); } /** * Copies one camera to another. * * @param {Camera} other - Camera to copy. * @returns {Camera} Self for chaining. */ copy(other) { this._aspectRatio = other._aspectRatio; this._farClip = other._farClip; this._fov = other._fov; this._horizontalFov = other._horizontalFov; this._nearClip = other._nearClip; this._xrProperties.aspectRatio = other._xrProperties.aspectRatio; this._xrProperties.farClip = other._xrProperties.farClip; this._xrProperties.fov = other._xrProperties.fov; this._xrProperties.horizontalFov = other._xrProperties.horizontalFov; this._xrProperties.nearClip = other._xrProperties.nearClip; this.aspectRatioMode = other.aspectRatioMode; this.calculateProjection = other.calculateProjection; this.calculateTransform = other.calculateTransform; this.clearColor = other.clearColor; this.clearColorBuffer = other.clearColorBuffer; this.clearDepth = other.clearDepth; this.clearDepthBuffer = other.clearDepthBuffer; this.clearStencil = other.clearStencil; this.clearStencilBuffer = other.clearStencilBuffer; this.cullFaces = other.cullFaces; this.flipFaces = other.flipFaces; this.frustumCulling = other.frustumCulling; this.layers = other.layers; this.orthoHeight = other.orthoHeight; this.projection = other.projection; this.rect = other.rect; this.renderTarget = other.renderTarget; this.scissorRect = other.scissorRect; this.aperture = other.aperture; this.shutter = other.shutter; this.sensitivity = other.sensitivity; this.shaderPassInfo = other.shaderPassInfo; this.jitter = other.jitter; this._projMatDirty = true; return this; } _enableRenderPassColorGrab(device, enable) { if (enable) { if (!this.renderPassColorGrab) { this.renderPassColorGrab = new FramePassColorGrab(device); } } else { this.renderPassColorGrab?.destroy(); this.renderPassColorGrab = null; } } _enableRenderPassDepthGrab(device, renderer, enable) { if (enable) { if (!this.renderPassDepthGrab) { this.renderPassDepthGrab = new FramePassDepthGrab(device, this); } } else { this.renderPassDepthGrab?.destroy(); this.renderPassDepthGrab = null; } } _updateViewProjMat() { if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) { this._viewProjMat.mul2(this.projectionMatrix, this.viewMatrix); this._viewProjMatDirty = false; } } /** * Refreshes the derived per-view matrices of all {@link Camera#xrViews}, using this camera's * parent world transform. The renderer (and the gsplat passes, which run earlier in the frame) * call this before reading the per-view matrices. * * Note: this recomputes on every call. Within a frame the parent transform is stable, so the * 2-3 calls/frame could be collapsed to a single recompute by guarding on * `device.renderVersion` (as {@link Camera#_storeShaderMatrices} does) - left as a future * optimization, as it needs checking against cameras that render multiple times per frame * (e.g. multiple render targets). */ updateViewTransforms() { const views = this.xrViews; if (!views) { return; } const parentWorldTransform = this._node?.parent?.getWorldTransform() ?? null; for (let i = 0; i < views.length; i++) { views[i].updateTransforms(parentWorldTransform); } } /** * Updates {@link Camera#frustum} to the combined volume of all XR views, to avoid culling * objects visible in any view (e.g. the right edge of the right eye in stereo rendering). * The views are merged conservatively via {@link Frustum#add}, which handles the asymmetric * per-eye projections real headsets report (matching planes of the two eyes have different * normals, so a simple outermost-plane selection would over-cull at a distance). * * @returns {boolean} True when XR views were present and the frustum was updated, false * otherwise (the caller should fall back to the mono frustum path). * @ignore */ updateXrFrustum() { const views = this.xrViews; if (!views?.length) { return false; } _xrViewProjMat.mul2(views[0].projMat, views[0].viewOffMat); this.frustum.setFromMat4(_xrViewProjMat); for (let v = 1; v < views.length; v++) { _xrViewProjMat.mul2(views[v].projMat, views[v].viewOffMat); _xrViewFrustum.setFromMat4(_xrViewProjMat); this.frustum.add(_xrViewFrustum); } return true; } /** * Updates {@link Camera#frustum} for the camera's current transform and projection, for * visibility culling. Uses the combined (VIEW_CENTER) view; XR cameras delegate to * {@link Camera#updateXrFrustum}. Honors the {@link Camera#calculateProjection} and * {@link Camera#calculateTransform} overrides. * * @ignore */ updateFrustum() { if (this.updateXrFrustum()) { return; } const projMat = this.projectionMatrix; if (this.calculateProjection) { this.calculateProjection(projMat, VIEW_CENTER); } if (this.calculateTransform) { this.calculateTransform(_frustumViewInvMat, VIEW_CENTER); } else { const pos = this._node.getPosition(); const rot = this._node.getRotation(); _frustumViewInvMat.setTRS(pos, rot, Vec3.ONE); } _frustumViewMat.copy(_frustumViewInvMat).invert(); _frustumViewProjMat.mul2(projMat, _frustumViewMat); this.frustum.setFromMat4(_frustumViewProjMat); } /** * Convert a point from 3D world space to 2D canvas pixel space based on the camera's rect. * * @param {Vec3} worldCoord - The world space coordinate to transform. * @param {number} cw - The width of PlayCanvas' canvas element. * @param {number} ch - The height of PlayCanvas' canvas element. * @param {Vec3} [screenCoord] - 3D vector to receive screen coordinate result. * @returns {Vec3} The screen space coordinate. */ worldToScreen(worldCoord, cw, ch, screenCoord = new Vec3()) { this._updateViewProjMat(); this._viewProjMat.transformPoint(worldCoord, screenCoord); const vpm = this._viewProjMat.data; const w = worldCoord.x * vpm[3] + worldCoord.y * vpm[7] + worldCoord.z * vpm[11] + 1 * vpm[15]; screenCoord.x = (screenCoord.x / w + 1) * 0.5; screenCoord.y = (1 - screenCoord.y / w) * 0.5; const { x: rx, y: ry, z: rw, w: rh } = this._rect; screenCoord.x = screenCoord.x * rw * cw + rx * cw; screenCoord.y = screenCoord.y * rh * ch + (1 - ry - rh) * ch; return screenCoord; } /** * Convert a point from 2D canvas pixel space to 3D world space based on the camera's rect. * * @param {number} x - X coordinate on PlayCanvas' canvas element. * @param {number} y - Y coordinate on PlayCanvas' canvas element. * @param {number} z - The distance from the camera in world space to create the new point. * @param {number} cw - The width of PlayCanvas' canvas element. * @param {number} ch - The height of PlayCanvas' canvas element. * @param {Vec3} [worldCoord] - 3D vector to receive world coordinate result. * @returns {Vec3} The world space coordinate. */ screenToWorld(x, y, z, cw, ch, worldCoord = new Vec3()) { const { x: rx, y: ry, z: rw, w: rh } = this._rect; const range = this.farClip - this.nearClip; _deviceCoord.set( (x - rx * cw) / (rw * cw), 1 - (y - (1 - ry - rh) * ch) / (rh * ch), z / range ); _deviceCoord.mulScalar(2); _deviceCoord.sub(Vec3.ONE); if (this._projection === PROJECTION_PERSPECTIVE) { Mat4._getPerspectiveHalfSize(_halfSize, this.fov, this.aspectRatio, this.nearClip, this.horizontalFov); _halfSize.x *= _deviceCoord.x; _halfSize.y *= _deviceCoord.y; const invView = this._node.getWorldTransform(); _halfSize.z = -this.nearClip; invView.transformPoint(_halfSize, _point); const cameraPos = this._node.getPosition(); worldCoord.sub2(_point, cameraPos); worldCoord.normalize(); worldCoord.mulScalar(z); worldCoord.add(cameraPos); } else { this._updateViewProjMat(); _invViewProjMat.copy(this._viewProjMat).invert(); _invViewProjMat.transformPoint(_deviceCoord, worldCoord); } return worldCoord; } _evaluateProjectionMatrix() { const aspect = this.aspectRatio; if (this._projMatDirty) { if (this._projection === PROJECTION_PERSPECTIVE) { this._projMat.setPerspective(this.fov, aspect, this.nearClip, this.farClip, this.horizontalFov); this._projMatSkybox.copy(this._projMat); } else { const y = this._orthoHeight; const x = y * aspect; this._projMat.setOrtho(-x, x, -y, y, this.nearClip, this.farClip); this._projMatSkybox.setPerspective(this.fov, aspect, this.nearClip, this.farClip); } this._projMatDirty = false; } } getProjectionMatrixSkybox() { this._evaluateProjectionMatrix(); return this._projMatSkybox; } getExposure() { const ev100 = Math.log2(this._aperture * this._aperture / this._shutter * 100 / this._sensitivity); return 1 / (Math.pow(2, ev100) * 1.2); } // returns estimated size of the sphere on the screen in range of [0..1] // 0 - infinitely small, 1 - full screen or larger getScreenSize(sphere) { if (this._projection === PROJECTION_PERSPECTIVE) { const distance = this._node.getPosition().distance(sphere.center); if (distance < sphere.radius) { return 1; } const viewAngle = Math.asin(sphere.radius / distance); const sphereViewHeight = Math.tan(viewAngle); const screenViewHeight = Math.tan(this.fov / 2 * math.DEG_TO_RAD); return Math.min(sphereViewHeight / screenViewHeight, 1); } return math.clamp(sphere.radius / this._orthoHeight, 0, 1); } /** * Returns an array of corners of the frustum of the camera in the local coordinate system of the camera. * * @param {number} [near] - Near distance for the frustum points. Defaults to the near clip distance of the camera. * @param {number} [far] - Far distance for the frustum points. Defaults to the far clip distance of the camera. * @returns {Vec3[]} - An array of corners, using a global storage space. */ getFrustumCorners(near = this.nearClip, far = this.farClip) { const fov = this.fov * math.DEG_TO_RAD; let x, y; if (this.projection === PROJECTION_PERSPECTIVE) { if (this.horizontalFov) { x = near * Math.tan(fov / 2); y = x / this.aspectRatio; } else { y = near * Math.tan(fov / 2); x = y * this.aspectRatio; } } else { y = this._orthoHeight; x = y * this.aspectRatio; } const points = _frustumPoints; points[0].x = x; points[0].y = -y; points[0].z = -near; points[1].x = x; points[1].y = y; points[1].z = -near; points[2].x = -x; points[2].y = y; points[2].z = -near; points[3].x = -x; points[3].y = -y; points[3].z = -near; if (this._projection === PROJECTION_PERSPECTIVE) { if (this.horizontalFov) { x = far * Math.tan(fov / 2); y = x / this.aspectRatio; } else { y = far * Math.tan(fov / 2); x = y * this.aspectRatio; } } points[4].x = x; points[4].y = -y; points[4].z = -far; points[5].x = x; points[5].y = y; points[5].z = -far; points[6].x = -x; points[6].y = y; points[6].z = -far; points[7].x = -x; points[7].y = -y; points[7].z = -far; return points; } /** * Sets XR camera properties that should be derived physical camera in {@link XrManager}. * * @param {object} [properties] - Properties object. * @param {number} [properties.aspectRatio] - Aspect ratio. * @param {number} [properties.farClip] - Far clip. * @param {number} [properties.fov] - Field of view. * @param {boolean} [properties.horizontalFov] - Enable horizontal field of view. * @param {number} [properties.nearClip] - Near clip. */ setXrProperties(properties) { Object.assign(this._xrProperties, properties); this._projMatDirty = true; } /** * Fills the provided array with camera parameters for use in shaders. * The array format is: [1/far, far, near, isOrtho]. * * @param {Float32Array} output - Array to fill with camera parameters. * @returns {Float32Array} The output array. * @ignore */ fillShaderParams(output) { const f = this._farClip; output[0] = 1 / f; output[1] = f; output[2] = this._nearClip; output[3] = this._projection === PROJECTION_ORTHOGRAPHIC ? 1 : 0; return output; } }; /** @private */ __publicField(_Camera, "_flipYProjectionMatrix", new Mat4().setScale(1, -1, 1)); /** @private */ __publicField(_Camera, "_webGpuDepthRangeMatrix", new Mat4().set([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1 ])); /** @private */ __publicField(_Camera, "_applyShaderProjectionScratch", new Mat4()); let Camera = _Camera; export { Camera };