import * as THREE from 'three'; /** Supersample low-DPI GPU displays too; software WebGL keeps its latency budget. */ export function viewerPixelRatio(dpr: number, software: boolean): number { const ratio = Number.isFinite(dpr) && dpr > 0 ? dpr : 1; return software ? Math.min(ratio, 1.75) : Math.min(Math.max(ratio, 1.5), 2); } /** Fit shadow texels to the loaded scene instead of a default 10 m / 512 px map. */ export function fitSceneShadow( light: THREE.DirectionalLight, center: readonly number[], extent: number, ): void { const radius = Math.max(1, extent); light.target.position.set(center[0], center[1], center[2]); light.position .set(4, -3, 7) .normalize() .multiplyScalar(radius * 2) .add(light.target.position); const shadow = light.shadow; shadow.mapSize.set(2048, 2048); Object.assign(shadow.camera, { left: -radius, right: radius, top: radius, bottom: -radius, near: radius * 0.01, far: radius * 4, }); shadow.bias = -0.00005; shadow.normalBias = radius / 4096; shadow.camera.updateProjectionMatrix(); shadow.needsUpdate = true; } /** DataTexture defaults to nearest-neighbour; oblique camera views need mipmaps. */ export function filterSurfaceTexture(texture: THREE.DataTexture, maxAnisotropy: number): void { texture.magFilter = THREE.LinearFilter; texture.minFilter = THREE.LinearMipmapLinearFilter; texture.generateMipmaps = true; texture.anisotropy = Math.min(8, maxAnisotropy); texture.needsUpdate = true; }