diff --git a/src/rendering/advanced-rendering.ts b/src/rendering/advanced-rendering.ts index 25927b9a..24429359 100644 --- a/src/rendering/advanced-rendering.ts +++ b/src/rendering/advanced-rendering.ts @@ -96,8 +96,17 @@ class RenderLayerPass extends RenderPass { deltaTime?: number, stencilTest?: boolean ) { - renderWithCameraLayerMask(this.renderLayerCamera, this.renderLayerMask, () => - super.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) + renderWithCameraLayerMask( + this.renderLayerCamera, + this.renderLayerMask, + () => + super.render( + renderer, + inputBuffer, + outputBuffer, + deltaTime, + stencilTest + ) ); } } @@ -119,8 +128,17 @@ class RenderLayerNormalPass extends NormalPass { deltaTime?: number, stencilTest?: boolean ) { - renderWithCameraLayerMask(this.renderLayerCamera, this.renderLayerMask, () => - super.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) + renderWithCameraLayerMask( + this.renderLayerCamera, + this.renderLayerMask, + () => + super.render( + renderer, + inputBuffer, + outputBuffer, + deltaTime, + stencilTest + ) ); } } @@ -156,7 +174,9 @@ export interface ResolvedBoxVolumeRenderPaths { water: BoxVolumeRenderPath; } -export function resolveBoxVolumeRenderPaths(settings: AdvancedRenderingSettings): ResolvedBoxVolumeRenderPaths { +export function resolveBoxVolumeRenderPaths( + settings: AdvancedRenderingSettings +): ResolvedBoxVolumeRenderPaths { if (!settings.enabled) { return { fog: "performance", @@ -170,7 +190,9 @@ export function resolveBoxVolumeRenderPaths(settings: AdvancedRenderingSettings) }; } -export function getAdvancedRenderingShadowMapType(shadowType: AdvancedRenderingShadowType) { +export function getAdvancedRenderingShadowMapType( + shadowType: AdvancedRenderingShadowType +) { switch (shadowType) { case "basic": return BasicShadowMap; @@ -181,7 +203,9 @@ export function getAdvancedRenderingShadowMapType(shadowType: AdvancedRenderingS } } -export function getAdvancedRenderingToneMappingMode(mode: AdvancedRenderingToneMappingMode): ToneMappingMode { +export function getAdvancedRenderingToneMappingMode( + mode: AdvancedRenderingToneMappingMode +): ToneMappingMode { switch (mode) { case "none": return ToneMappingMode.LINEAR; @@ -196,15 +220,23 @@ export function getAdvancedRenderingToneMappingMode(mode: AdvancedRenderingToneM } } -export function configureAdvancedRenderingRenderer(renderer: WebGLRenderer, settings: AdvancedRenderingSettings) { +export function configureAdvancedRenderingRenderer( + renderer: WebGLRenderer, + settings: AdvancedRenderingSettings +) { renderer.shadowMap.enabled = settings.enabled && settings.shadows.enabled; - renderer.shadowMap.type = getAdvancedRenderingShadowMapType(settings.shadows.type); + renderer.shadowMap.type = getAdvancedRenderingShadowMapType( + settings.shadows.type + ); renderer.toneMapping = NoToneMapping; renderer.toneMappingExposure = settings.toneMapping.exposure; } function clampAmbientOcclusionEffectRadius(radius: number) { - return Math.min(Math.max(radius, MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS), MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS); + return Math.min( + Math.max(radius, MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS), + MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS + ); } function getAmbientOcclusionSampleCount(samples: number) { @@ -224,7 +256,9 @@ export function createAdvancedRenderingComposer( depthBuffer: true, stencilBuffer: false, multisampling: 0, - frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType + frameBufferType: renderer.capabilities.isWebGL2 + ? HalfFloatType + : UnsignedByteType }); const mainRenderLayerMask = settings.ambientOcclusion.enabled ? AO_WORLD_RENDER_LAYER_MASK @@ -243,7 +277,9 @@ export function createAdvancedRenderingComposer( ); } - const effects: Array = []; + const effects: Array< + BloomEffect | DepthOfFieldEffect | ToneMappingEffect | SMAAEffect + > = []; if (settings.ambientOcclusion.enabled) { // postprocessing's internal depth-downsampling path writes zero normals unless @@ -255,8 +291,12 @@ export function createAdvancedRenderingComposer( ); composer.addPass(normalPass); - const ambientOcclusionRadius = clampAmbientOcclusionEffectRadius(settings.ambientOcclusion.radius); - const ambientOcclusionSamples = getAmbientOcclusionSampleCount(settings.ambientOcclusion.samples); + const ambientOcclusionRadius = clampAmbientOcclusionEffectRadius( + settings.ambientOcclusion.radius + ); + const ambientOcclusionSamples = getAmbientOcclusionSampleCount( + settings.ambientOcclusion.samples + ); const detailAmbientOcclusionRadius = Math.max( ambientOcclusionRadius * DETAIL_AMBIENT_OCCLUSION_RADIUS_SCALE, MIN_AMBIENT_OCCLUSION_EFFECT_RADIUS @@ -271,7 +311,9 @@ export function createAdvancedRenderingComposer( resolutionScale: COARSE_AMBIENT_OCCLUSION_RESOLUTION_SCALE, samples: ambientOcclusionSamples, radius: ambientOcclusionRadius, - intensity: settings.ambientOcclusion.intensity * COARSE_AMBIENT_OCCLUSION_INTENSITY_SCALE + intensity: + settings.ambientOcclusion.intensity * + COARSE_AMBIENT_OCCLUSION_INTENSITY_SCALE }), new SSAOEffect(camera, normalPass.texture, { depthAwareUpsampling: true, @@ -279,7 +321,9 @@ export function createAdvancedRenderingComposer( resolutionScale: DETAIL_AMBIENT_OCCLUSION_RESOLUTION_SCALE, samples: ambientOcclusionSamples, radius: detailAmbientOcclusionRadius, - intensity: settings.ambientOcclusion.intensity * DETAIL_AMBIENT_OCCLUSION_INTENSITY_SCALE + intensity: + settings.ambientOcclusion.intensity * + DETAIL_AMBIENT_OCCLUSION_INTENSITY_SCALE }) ) ); @@ -336,7 +380,10 @@ export function createAdvancedRenderingComposer( return composer; } -export function applyAdvancedRenderingRenderableShadowFlags(root: Object3D, enabled: boolean) { +export function applyAdvancedRenderingRenderableShadowFlags( + root: Object3D, + enabled: boolean +) { root.traverse((object) => { if ((object as Mesh).isMesh === true) { const mesh = object as Mesh; diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index ee4ea1cd..16c336dc 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -497,12 +497,15 @@ function smoothStep01(value: number) { } function normalizeDegrees(value: number) { - const wrapped = ((value + 180) % 360 + 360) % 360 - 180; + const wrapped = ((((value + 180) % 360) + 360) % 360) - 180; return wrapped === -180 ? 180 : wrapped; } -function resolveShortestAngleDeltaDegrees(fromDegrees: number, toDegrees: number) { +function resolveShortestAngleDeltaDegrees( + fromDegrees: number, + toDegrees: number +) { return normalizeDegrees(toDegrees - fromDegrees); } @@ -602,7 +605,11 @@ type RuntimeResolvedCameraSource = state: RuntimeDialogueAttentionState; }; -type TargetingLuxFlightState = "hidden" | "outbound" | "following" | "returning"; +type TargetingLuxFlightState = + | "hidden" + | "outbound" + | "following" + | "returning"; export class RuntimeHost { private readonly scene = new Scene(); @@ -635,7 +642,11 @@ export class RuntimeHost { private readonly targetingActiveCameraUp = new Vector3(); private readonly targetingActiveArrowDirection = new Vector3(); private readonly targetingActiveArrowLocalTipAxis = new Vector3(0, 1, 0); - private readonly targetingActiveArrowGeometry = new ConeGeometry(0.16, 0.38, 16); + private readonly targetingActiveArrowGeometry = new ConeGeometry( + 0.16, + 0.38, + 16 + ); private readonly targetingActiveArrowMaterial = new MeshBasicMaterial({ color: 0xfff2a2, depthTest: false, @@ -648,7 +659,11 @@ export class RuntimeHost { MeshBasicMaterial >[] = Array.from( { length: TARGETING_ACTIVE_ARROW_COUNT }, - () => new Mesh(this.targetingActiveArrowGeometry, this.targetingActiveArrowMaterial) + () => + new Mesh( + this.targetingActiveArrowGeometry, + this.targetingActiveArrowMaterial + ) ); private readonly targetingLuxMesh = new Mesh( new PlaneGeometry(0.32, 0.32), @@ -767,8 +782,10 @@ export class RuntimeHost { private activeCameraRigOverrideEntityId: string | null = null; private activeCameraSourceKey: RuntimeCameraSourceKey | null = null; private activeRuntimeCameraRig: RuntimeCameraRig | null = null; - private activeDialogueAttentionState: RuntimeDialogueAttentionState | null = null; - private dialogueParticipantState: RuntimeDialogueParticipantState | null = null; + private activeDialogueAttentionState: RuntimeDialogueAttentionState | null = + null; + private dialogueParticipantState: RuntimeDialogueParticipantState | null = + null; private cameraTransitionState: RuntimeCameraTransitionState | null = null; private suppressNextCameraSourceTransition = false; private cameraRigLookYawRadians = 0; @@ -1611,14 +1628,13 @@ export class RuntimeHost { return null; } - const eyePosition = - this.currentPlayerControllerTelemetry?.eyePosition ?? { - x: this.runtimeScene.spawn.position.x, - y: - this.runtimeScene.spawn.position.y + - this.runtimeScene.playerCollider.eyeHeight, - z: this.runtimeScene.spawn.position.z - }; + const eyePosition = this.currentPlayerControllerTelemetry?.eyePosition ?? { + x: this.runtimeScene.spawn.position.x, + y: + this.runtimeScene.spawn.position.y + + this.runtimeScene.playerCollider.eyeHeight, + z: this.runtimeScene.spawn.position.z + }; const feetPosition = this.currentPlayerControllerTelemetry?.feetPosition ?? this.runtimeScene.spawn.position; @@ -1661,7 +1677,9 @@ export class RuntimeHost { } this.camera.getWorldDirection(this.cameraForward); - return (Math.atan2(this.cameraForward.x, this.cameraForward.z) * 180) / Math.PI; + return ( + (Math.atan2(this.cameraForward.x, this.cameraForward.z) * 180) / Math.PI + ); } private resolvePlayerShapeHorizontalRadius() { @@ -1798,9 +1816,13 @@ export class RuntimeHost { for (let step = 1; step <= 8; step += 1) { const t = step / 8; const candidate = { - x: playerFeetPosition.x + (desiredFeetPosition.x - playerFeetPosition.x) * t, + x: + playerFeetPosition.x + + (desiredFeetPosition.x - playerFeetPosition.x) * t, y: playerFeetPosition.y, - z: playerFeetPosition.z + (desiredFeetPosition.z - playerFeetPosition.z) * t + z: + playerFeetPosition.z + + (desiredFeetPosition.z - playerFeetPosition.z) * t }; if ( @@ -1953,7 +1975,10 @@ export class RuntimeHost { position: playerFeetPosition, yawDegrees: state.playerCurrentYawDegrees }); - this.setRuntimeNpcYawDegrees(state.npcEntityId, state.npcCurrentYawDegrees); + this.setRuntimeNpcYawDegrees( + state.npcEntityId, + state.npcCurrentYawDegrees + ); return; } @@ -1973,7 +1998,10 @@ export class RuntimeHost { ) ) <= DIALOGUE_PARTICIPANT_RESTORE_EPSILON_DEGREES ) { - this.setRuntimeNpcYawDegrees(state.npcEntityId, state.npcRestoreYawDegrees); + this.setRuntimeNpcYawDegrees( + state.npcEntityId, + state.npcRestoreYawDegrees + ); this.dialogueParticipantState = null; } } @@ -2044,10 +2072,14 @@ export class RuntimeHost { railEndProgress: rig.railEndProgress }); - return sampleResolvedScenePathPosition(path, mappedProgress.railProgress); + return sampleResolvedScenePathPosition( + path, + mappedProgress.railProgress + ); } - return resolveNearestPointOnResolvedScenePath(path, targetPosition).position; + return resolveNearestPointOnResolvedScenePath(path, targetPosition) + .position; } } } @@ -2269,19 +2301,20 @@ export class RuntimeHost { return pose; } - const resolvedPosition = this.collisionWorld?.resolveThirdPersonCameraCollision( - { - x: pose.collisionPivot.x, - y: pose.collisionPivot.y, - z: pose.collisionPivot.z - }, - { - x: pose.position.x, - y: pose.position.y, - z: pose.position.z - }, - pose.collisionRadius - ); + const resolvedPosition = + this.collisionWorld?.resolveThirdPersonCameraCollision( + { + x: pose.collisionPivot.x, + y: pose.collisionPivot.y, + z: pose.collisionPivot.z + }, + { + x: pose.position.x, + y: pose.position.y, + z: pose.position.z + }, + pose.collisionRadius + ); if (resolvedPosition === undefined) { this.resetRuntimeCameraCollisionSmoothing(); @@ -2308,7 +2341,8 @@ export class RuntimeHost { private isActiveExternalCameraSource() { return ( - this.activeCameraSourceKey !== null && this.activeCameraSourceKey !== "gameplay" + this.activeCameraSourceKey !== null && + this.activeCameraSourceKey !== "gameplay" ); } @@ -2459,7 +2493,8 @@ export class RuntimeHost { return { kind: "dialogue", state: - this.activeDialogueAttentionState?.npcEntityId === dialogueNpc.entityId + this.activeDialogueAttentionState?.npcEntityId === + dialogueNpc.entityId ? this.activeDialogueAttentionState : { npcEntityId: dialogueNpc.entityId, @@ -4376,22 +4411,21 @@ export class RuntimeHost { y: this.camera.position.y, z: this.camera.position.z }); - const fogTelemetry = - this.isActiveExternalCameraSource() - ? { - cameraSubmerged: - cameraVolumeState.inWater && - cameraVolumeState.waterSurfaceHeight !== null && - this.camera.position.y < cameraVolumeState.waterSurfaceHeight, - eyePosition: { - x: this.camera.position.x, - y: this.camera.position.y, - z: this.camera.position.z - } + const fogTelemetry = this.isActiveExternalCameraSource() + ? { + cameraSubmerged: + cameraVolumeState.inWater && + cameraVolumeState.waterSurfaceHeight !== null && + this.camera.position.y < cameraVolumeState.waterSurfaceHeight, + eyePosition: { + x: this.camera.position.x, + y: this.camera.position.y, + z: this.camera.position.z } - : this.activeController === this.firstPersonController - ? this.currentPlayerControllerTelemetry - : null; + } + : this.activeController === this.firstPersonController + ? this.currentPlayerControllerTelemetry + : null; const fogState = resolveUnderwaterFogState(this.runtimeScene, fogTelemetry); if (fogState === null) { @@ -4974,7 +5008,10 @@ export class RuntimeHost { this.activeController?.update(simulationDt); this.refreshRuntimeTargetingState(); this.updateActiveRuntimeTargetLockState(cameraDt); - const activeCameraRig = this.applyActiveCameraRig(cameraDt, previousCameraPose); + const activeCameraRig = this.applyActiveCameraRig( + cameraDt, + previousCameraPose + ); this.updateRuntimeTargetingVisuals(cameraDt); if (!this.isActiveExternalCameraSource() && activeCameraRig === null) { @@ -5648,7 +5685,9 @@ export class RuntimeHost { ); } - private setActiveRuntimeTargetReference(reference: RuntimeTargetReference | null) { + private setActiveRuntimeTargetReference( + reference: RuntimeTargetReference | null + ) { this.activeRuntimeTargetReference = reference; this.activeRuntimeTargetOcclusionSeconds = 0; this.runtimeTargetSwitchInputHeld = false; @@ -5669,8 +5708,10 @@ export class RuntimeHost { switch (npc.collider.mode) { case "capsule": return ( - Math.max(npc.collider.radius, TARGETING_VISIBILITY_TARGET_CLEARANCE) + - TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING + Math.max( + npc.collider.radius, + TARGETING_VISIBILITY_TARGET_CLEARANCE + ) + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING ); case "box": return ( @@ -5682,8 +5723,7 @@ export class RuntimeHost { ) * 0.25, 0.35, 0.75 - ) + - TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING + ) + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING ); case "none": return 0.9 + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING; @@ -5717,7 +5757,8 @@ export class RuntimeHost { point: { x: number; y: number; z: number }; targetClearance: number; }> { - const targetClearance = this.resolveRuntimeTargetVisibilityClearance(target); + const targetClearance = + this.resolveRuntimeTargetVisibilityClearance(target); if (this.runtimeScene !== null && target.kind === "npc") { const npc = @@ -5730,10 +5771,8 @@ export class RuntimeHost { case "capsule": { const collider = npc.collider; const sampleClearance = - Math.max( - collider.radius, - TARGETING_VISIBILITY_TARGET_CLEARANCE - ) + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING; + Math.max(collider.radius, TARGETING_VISIBILITY_TARGET_CLEARANCE) + + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING; const yAt = (factor: number) => npc.position.y + collider.height * factor; @@ -5757,11 +5796,8 @@ export class RuntimeHost { const collider = npc.collider; const sampleClearance = clampScalar( - Math.max( - collider.size.x, - collider.size.y, - collider.size.z - ) * 0.25, + Math.max(collider.size.x, collider.size.y, collider.size.z) * + 0.25, 0.35, 0.75 ) + TARGETING_VISIBILITY_TARGET_CLEARANCE_PADDING; @@ -5855,7 +5891,8 @@ export class RuntimeHost { center: { x: number; y: number; z: number }; range: number; }): boolean { - const playerEyePosition = this.currentPlayerControllerTelemetry?.eyePosition; + const playerEyePosition = + this.currentPlayerControllerTelemetry?.eyePosition; if (playerEyePosition === undefined) { return false; @@ -6034,7 +6071,9 @@ export class RuntimeHost { y: number; z: number; }) { - const projected = new Vector3(point.x, point.y, point.z).project(this.camera); + const projected = new Vector3(point.x, point.y, point.z).project( + this.camera + ); if ( !Number.isFinite(projected.x) || @@ -6139,7 +6178,7 @@ export class RuntimeHost { const playerEyePosition = maxDistanceFromPlayer === null && !requirePlayerVisibility ? null - : this.currentPlayerControllerTelemetry?.eyePosition ?? null; + : (this.currentPlayerControllerTelemetry?.eyePosition ?? null); let bestCandidate: RuntimeTargetCandidate | null = null; let bestScreenDistanceSquared = Number.POSITIVE_INFINITY; @@ -6169,7 +6208,9 @@ export class RuntimeHost { continue; } - const screenPoint = this.resolveRuntimeTargetScreenPoint(candidate.center); + const screenPoint = this.resolveRuntimeTargetScreenPoint( + candidate.center + ); if ( screenPoint === null || @@ -6354,7 +6395,9 @@ export class RuntimeHost { visualPlacement.activeMarkerPosition.z ); this.targetingActiveGroup.quaternion.identity(); - this.targetingActiveGroup.scale.setScalar(visualPlacement.activeMarkerScale); + this.targetingActiveGroup.scale.setScalar( + visualPlacement.activeMarkerScale + ); this.targetingActiveCameraRight .setFromMatrixColumn(this.camera.matrixWorld, 0) .normalize(); @@ -6377,7 +6420,9 @@ export class RuntimeHost { this.targetingActiveCameraUp, Math.sin(angle) * localRadius ); - this.targetingActiveArrowDirection.copy(arrow.position).multiplyScalar(-1); + this.targetingActiveArrowDirection + .copy(arrow.position) + .multiplyScalar(-1); if (this.targetingActiveArrowDirection.lengthSq() > Number.EPSILON) { this.targetingActiveArrowDirection.normalize(); @@ -6447,7 +6492,8 @@ export class RuntimeHost { this.targetingVisualTime += dtSeconds; const visualPlacement = resolveRuntimeTargetVisualPlacement(visualTarget); - const bob = Math.sin(this.targetingVisualTime * TARGETING_LUX_BOB_RATE) * 0.08; + const bob = + Math.sin(this.targetingVisualTime * TARGETING_LUX_BOB_RATE) * 0.08; const sway = Math.sin(this.targetingVisualTime * TARGETING_LUX_SWAY_RATE) * TARGETING_LUX_SWAY_DISTANCE; @@ -6486,7 +6532,10 @@ export class RuntimeHost { ? TARGETING_LUX_FLIGHT_RATE : TARGETING_LUX_FOLLOW_RATE) * dtSeconds ); - this.targetingLuxGroup.position.lerp(this.targetingLuxTargetPosition, alpha); + this.targetingLuxGroup.position.lerp( + this.targetingLuxTargetPosition, + alpha + ); if ( this.targetingLuxFlightState === "outbound" && @@ -6598,7 +6647,9 @@ export class RuntimeHost { return; } - if (event.code === this.runtimeScene.playerInputBindings.keyboard.pauseTime) { + if ( + event.code === this.runtimeScene.playerInputBindings.keyboard.pauseTime + ) { event.preventDefault(); this.toggleManualPause(); this.previousPauseInputActive = true; diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 1cce6223..87981fc9 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -4861,7 +4861,10 @@ export class ViewportHost { } private isSelectedRailCameraRigPathPreviewed(pathId: string): boolean { - if (this.currentDocument === null || this.currentSelection.kind !== "entities") { + if ( + this.currentDocument === null || + this.currentSelection.kind !== "entities" + ) { return false; } diff --git a/tests/domain/advanced-rendering.test.ts b/tests/domain/advanced-rendering.test.ts index 9887d5ba..80ae32e7 100644 --- a/tests/domain/advanced-rendering.test.ts +++ b/tests/domain/advanced-rendering.test.ts @@ -14,7 +14,10 @@ const postprocessingState = vi.hoisted(() => ({ composerOptions: [] as Array>, composerPasses: [] as unknown[], normalPassTextures: [] as unknown[], - ssaoCalls: [] as Array<{ normalBuffer: unknown; options: Record }> + ssaoCalls: [] as Array<{ + normalBuffer: unknown; + options: Record; + }> })); vi.mock("postprocessing", () => { @@ -71,7 +74,10 @@ vi.mock("postprocessing", () => { class MockEffectPass extends MockPass { readonly effects: unknown[]; - constructor(readonly camera: unknown, ...effects: unknown[]) { + constructor( + readonly camera: unknown, + ...effects: unknown[] + ) { super("EffectPass"); this.effects = effects; } @@ -88,7 +94,11 @@ vi.mock("postprocessing", () => { } class MockSSAOEffect { - constructor(_camera: unknown, normalBuffer: unknown, options: Record) { + constructor( + _camera: unknown, + normalBuffer: unknown, + options: Record + ) { postprocessingState.ssaoCalls.push({ normalBuffer, options }); } } @@ -240,7 +250,11 @@ describe("createAdvancedRenderingComposer", () => { ); expect(postprocessingState.normalPassTextures).toHaveLength(1); - expect(postprocessingState.composerPasses.map((pass) => (pass as { name: string }).name)).toEqual([ + expect( + postprocessingState.composerPasses.map( + (pass) => (pass as { name: string }).name + ) + ).toEqual([ "RenderPass", "NormalPass", "EffectPass", @@ -298,7 +312,10 @@ describe("createAdvancedRenderingComposer", () => { resolutionScale: 0.75 } }); - expect(postprocessingState.ssaoCalls[1].options.radius).toBeCloseTo(0.07, 6); + expect(postprocessingState.ssaoCalls[1].options.radius).toBeCloseTo( + 0.07, + 6 + ); }); });