import * as THREE from 'three' ; const tmpOrigin = new THREE . Vector3 ( ) ; const tmpU = new THREE . Vector3 ( ) ; const tmpV = new THREE . Vector3 ( ) ; const tmpNormal = new THREE . Vector3 ( ) ; const tmpLookAt = new THREE . Vector3 ( ) ; const tmpProbeBox = new THREE . Vector4 ( ) ; const tmpPrevClearColor = new THREE . Color ( ) ; const tmpNormalOther = new THREE . Vector3 ( ) ; const PROBE_BG_ZERO = new THREE . Color ( '#000000' ) ; const PROBE_BG_FULL = new THREE . Color ( '#ffffff' ) ; export const PROBE_BATCH_COUNT = 8 ; export interface LightProbeSettings { targetSize : number ; offset : number ; near : number ; far : number ; } export const DEFAULT_LIGHT_PROBE_SETTINGS = { targetSize : 16 , offset : 0 , near : 0.05 , far : 50 } ; export type ProbeDataReport = { rgbaData : Float32Array ; rowPixelStride : number ; probeBox : THREE . Vector4 ; originX : number ; originY : number ; } ; export interface ProbeTexel { texelIndex : number ; originalMesh : THREE . Mesh ; originalBuffer : THREE . BufferGeometry ; faceIndex : number ; pU : number ; pV : number ; } export type ProbeBatcher = ( gl : THREE . WebGLRenderer , lightScene : THREE . Scene , texelIterator : Iterator < ProbeTexel | null > ) => Generator < { texelIndex : number ; rgba : THREE . Vector4 ; } > ; function setBlendedNormal ( out , origNormalArray , origIndexArray , faceVertexBase , pU , pV ) { const pO = 1 - pU - pV ; out . fromArray ( origNormalArray , origIndexArray [ faceVertexBase ] * 3 ) ; out . multiplyScalar ( pO ) ; tmpNormalOther . fromArray ( origNormalArray , origIndexArray [ faceVertexBase + 1 ] * 3 ) ; out . addScaledVector ( tmpNormalOther , pU ) ; tmpNormalOther . fromArray ( origNormalArray , origIndexArray [ faceVertexBase + 2 ] * 3 ) ; out . addScaledVector ( tmpNormalOther , pV ) ; out . normalize ( ) ; } function setUpProbeUp ( probeCam , mesh , origin , normal , uDir ) { probeCam . position . copy ( origin ) ; probeCam . up . copy ( uDir ) ; tmpLookAt . copy ( normal ) ; tmpLookAt . add ( origin ) ; probeCam . lookAt ( tmpLookAt ) ; probeCam . scale . set ( 1 , 1 , 1 ) ; probeCam . applyMatrix4 ( mesh . matrixWorld ) ; } function setUpProbeSide ( probeCam , mesh , origin , normal , direction , directionSign ) { probeCam . position . copy ( origin ) ; probeCam . up . copy ( normal ) ; tmpLookAt . copy ( origin ) ; tmpLookAt . addScaledVector ( direction , directionSign ) ; probeCam . lookAt ( tmpLookAt ) ; probeCam . scale . set ( 1 , 1 , 1 ) ; probeCam . applyMatrix4 ( mesh . matrixWorld ) ; } export function generatePixelAreaLookup ( probeTargetSize ) { const probePixelCount = probeTargetSize * probeTargetSize ; const lookup = new Array ( probePixelCount ) as number [ ] ; const probePixelBias = 0.5 / probeTargetSize ; for ( let py = 0 ; py < probeTargetSize ; py += 1 ) { const dy = py / probeTargetSize - 0.5 + probePixelBias ; for ( let px = 0 ; px < probeTargetSize ; px += 1 ) { const dx = px / probeTargetSize - 0.5 + probePixelBias ; const span = Math . hypot ( dx * 2 , dy * 2 ) ; const hypo = Math . hypot ( span , 1 ) ; const area = 1 / hypo ; lookup [ py * probeTargetSize + px ] = area ; } } return lookup ; } const tmpTexelRGBA = new THREE . Vector4 ( ) ; function readTexel ( readLightProbe , probePixelAreaLookup ) { let r = 0 , g = 0 , b = 0 , totalDivider = 0 ; for ( const { rgbaData : probeData , rowPixelStride , probeBox : box , originX , originY } of readLightProbe ( ) ) { const probeTargetSize = box . z ; const rowStride = rowPixelStride * 4 ; let rowStart = box . y * rowStride + box . x * 4 ; const totalMax = ( box . y + box . w ) * rowStride ; let py = originY ; while ( rowStart < totalMax ) { const rowMax = rowStart + box . z * 4 ; let px = originX ; for ( let i = rowStart ; i < rowMax ; i += 4 ) { const area = probePixelAreaLookup [ py * probeTargetSize + px ] ; r += area * probeData [ i ] ; g += area * probeData [ i + 1 ] ; b += area * probeData [ i + 2 ] ; totalDivider += area ; px += 1 ; } rowStart += rowStride ; py += 1 ; } } tmpTexelRGBA . x = r / totalDivider ; tmpTexelRGBA . y = g / totalDivider ; tmpTexelRGBA . z = b / totalDivider ; } export async function withLightProbe ( aoMode , aoDistance , settings , taskCallback ) { const probeTargetSize = settings . targetSize ; const probeBgColor = aoMode ? PROBE_BG_FULL : PROBE_BG_ZERO ; const halfSize = probeTargetSize / 2 ; const targetWidth = probeTargetSize * 4 ; const targetHeight = probeTargetSize * 2 * PROBE_BATCH_COUNT ; const probePixelAreaLookup = generatePixelAreaLookup ( probeTargetSize ) ; const probeTarget = new THREE . WebGLRenderTarget ( targetWidth , targetHeight , { type : THREE . FloatType , magFilter : THREE . NearestFilter , minFilter : THREE . NearestFilter , generateMipmaps : false } ) ; const rtFov = 90 ; const rtAspect = 1 ; const rtNear = settings . near ; const rtFar = aoMode ? aoDistance : settings . far ; const probeCam = new THREE . PerspectiveCamera ( rtFov , rtAspect , rtNear , rtFar ) ; const probeData = new Float32Array ( targetWidth * targetHeight * 4 ) ; const batchTexels = new Array ( PROBE_BATCH_COUNT ) as ( number | undefined ) [ ] ; const renderLightProbeBatch = function * renderLightProbeBatch ( gl , lightScene , texelIterator ) { gl . getClearColor ( tmpPrevClearColor ) ; const prevClearAlpha = gl . getClearAlpha ( ) ; const prevAutoClear = gl . autoClear ; const prevToneMapping = gl . toneMapping ; gl . toneMapping = THREE . LinearToneMapping ; probeTarget . scissorTest = true ; probeTarget . scissor . set ( 0 , 0 , targetWidth , targetHeight ) ; probeTarget . viewport . set ( 0 , 0 , targetWidth , targetHeight ) ; gl . setRenderTarget ( probeTarget ) ; gl . autoClear = false ; gl . setClearColor ( probeBgColor , 1 ) ; gl . clear ( true , true , false ) ; for ( let batchItem = 0 ; batchItem < PROBE_BATCH_COUNT ; batchItem += 1 ) { const texelResult = texelIterator . next ( ) ; if ( ! texelResult . done && texelResult . value ) { const { texelIndex , originalMesh , originalBuffer , faceIndex , pU , pV } = texelResult . value ; const batchOffsetY = batchItem * probeTargetSize * 2 ; batchTexels [ batchItem ] = texelIndex ; if ( ! originalBuffer . index ) { throw new Error ( 'expected indexed mesh' ) ; } const origIndexArray = originalBuffer . index . array ; const origPosArray = originalBuffer . attributes . position . array ; const origNormalArray = originalBuffer . attributes . normal . array ; const faceVertexBase = faceIndex * 3 ; tmpOrigin . fromArray ( origPosArray , origIndexArray [ faceVertexBase ] * 3 ) ; tmpU . fromArray ( origPosArray , origIndexArray [ faceVertexBase + 1 ] * 3 ) ; tmpV . fromArray ( origPosArray , origIndexArray [ faceVertexBase + 2 ] * 3 ) ; tmpU . sub ( tmpOrigin ) ; tmpV . sub ( tmpOrigin ) ; tmpOrigin . addScaledVector ( tmpU , pU ) ; tmpOrigin . addScaledVector ( tmpV , pV ) ; setBlendedNormal ( tmpNormal , origNormalArray , origIndexArray , faceVertexBase , pU , pV ) ; if ( tmpNormal . x === 0 && tmpNormal . y === 0 ) { tmpU . set ( 1 , 0 , 0 ) ; } else { tmpU . set ( 0 , 0 , 1 ) ; } tmpV . crossVectors ( tmpNormal , tmpU ) ; tmpV . normalize ( ) ; tmpU . crossVectors ( tmpNormal , tmpV ) ; tmpU . normalize ( ) ; tmpOrigin . addScaledVector ( tmpNormal , settings . offset ) ; setUpProbeUp ( probeCam , originalMesh , tmpOrigin , tmpNormal , tmpU ) ; probeTarget . viewport . set ( 0 , batchOffsetY + probeTargetSize , probeTargetSize , probeTargetSize ) ; probeTarget . scissor . set ( 0 , batchOffsetY + probeTargetSize , probeTargetSize , probeTargetSize ) ; gl . setRenderTarget ( probeTarget ) ; gl . render ( lightScene , probeCam ) ; setUpProbeSide ( probeCam , originalMesh , tmpOrigin , tmpNormal , tmpU , 1 ) ; probeTarget . viewport . set ( 0 , batchOffsetY , probeTargetSize , probeTargetSize ) ; probeTarget . scissor . set ( 0 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; gl . setRenderTarget ( probeTarget ) ; gl . render ( lightScene , probeCam ) ; setUpProbeSide ( probeCam , originalMesh , tmpOrigin , tmpNormal , tmpU , - 1 ) ; probeTarget . viewport . set ( probeTargetSize , batchOffsetY , probeTargetSize , probeTargetSize ) ; probeTarget . scissor . set ( probeTargetSize , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; gl . setRenderTarget ( probeTarget ) ; gl . render ( lightScene , probeCam ) ; setUpProbeSide ( probeCam , originalMesh , tmpOrigin , tmpNormal , tmpV , 1 ) ; probeTarget . viewport . set ( probeTargetSize * 2 , batchOffsetY , probeTargetSize , probeTargetSize ) ; probeTarget . scissor . set ( probeTargetSize * 2 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; gl . setRenderTarget ( probeTarget ) ; gl . render ( lightScene , probeCam ) ; setUpProbeSide ( probeCam , originalMesh , tmpOrigin , tmpNormal , tmpV , - 1 ) ; probeTarget . viewport . set ( probeTargetSize * 3 , batchOffsetY , probeTargetSize , probeTargetSize ) ; probeTarget . scissor . set ( probeTargetSize * 3 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; gl . setRenderTarget ( probeTarget ) ; gl . render ( lightScene , probeCam ) ; } else { batchTexels [ batchItem ] = undefined ; break ; } } gl . readRenderTargetPixels ( probeTarget , 0 , 0 , targetWidth , targetHeight , probeData ) ; gl . setRenderTarget ( null ) ; gl . setClearColor ( tmpPrevClearColor , prevClearAlpha ) ; gl . autoClear = prevAutoClear ; gl . toneMapping = prevToneMapping ; for ( let batchItem = 0 ; batchItem < PROBE_BATCH_COUNT ; batchItem += 1 ) { const renderedTexelIndex = batchTexels [ batchItem ] ; if ( renderedTexelIndex === undefined ) { break ; } const probePartsReporter = function * ( ) { const batchOffsetY = batchItem * probeTargetSize * 2 ; const rowPixelStride = probeTargetSize * 4 ; const probeDataReport = { rgbaData : probeData , rowPixelStride , probeBox : tmpProbeBox , originX : 0 , originY : 0 } ; tmpProbeBox . set ( 0 , batchOffsetY + probeTargetSize , probeTargetSize , probeTargetSize ) ; probeDataReport . originX = 0 ; probeDataReport . originY = 0 ; yield probeDataReport ; tmpProbeBox . set ( 0 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; probeDataReport . originX = 0 ; probeDataReport . originX = halfSize ; yield probeDataReport ; tmpProbeBox . set ( probeTargetSize , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; probeDataReport . originX = 0 ; probeDataReport . originX = halfSize ; yield probeDataReport ; tmpProbeBox . set ( probeTargetSize * 2 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; probeDataReport . originX = 0 ; probeDataReport . originX = halfSize ; yield probeDataReport ; tmpProbeBox . set ( probeTargetSize * 3 , batchOffsetY + halfSize , probeTargetSize , halfSize ) ; probeDataReport . originX = 0 ; probeDataReport . originX = halfSize ; yield probeDataReport ; } ; readTexel ( probePartsReporter , probePixelAreaLookup ) ; yield { texelIndex : renderedTexelIndex , rgba : tmpTexelRGBA } ; } } ; try { await taskCallback ( renderLightProbeBatch , probeTarget . texture ) ; } finally { probeTarget . dispose ( ) ; } }