import * as THREE from 'three' ; import { ProbeTexel } from './lightProbe' ; export interface AtlasMapItem { faceCount : number ; originalMesh : THREE . Mesh ; originalBuffer : THREE . BufferGeometry ; } interface AtlasMapInternalItem extends AtlasMapItem { perFaceBuffer : THREE . BufferGeometry ; } export interface AtlasMap { width : number ; height : number ; items : AtlasMapItem [ ] ; data : Float32Array ; texture : THREE . Texture ; } const ATLAS_BG_COLOR = new THREE . Color ( '#000000' ) ; const VERTEX_SHADER = `
  attribute vec4 faceInfo;
  attribute vec2 uv2;

  varying vec4 vFaceInfo;
  uniform vec2 uvOffset;

  void main() {
    vFaceInfo = faceInfo;

    gl_Position = projectionMatrix * vec4(
      uv2 + uvOffset, // UV2 is the actual position on map
      0,
      1.0
    );
  }
` ; const FRAGMENT_SHADER = `
  varying vec4 vFaceInfo;

  void main() {
    // encode the face information in map
    gl_FragColor = vFaceInfo;
  }
` ; export const ATLAS_OPT_OUT_FLAG = Symbol ( 'lightmap atlas opt out flag' ) ; export function traverseAtlasItems ( object , callback ) { if ( Object . prototype . hasOwnProperty . call ( object . userData , ATLAS_OPT_OUT_FLAG ) ) { return ; } callback ( object ) ; for ( const childObject of object . children ) { traverseAtlasItems ( childObject , callback ) ; } } function getTexelInfo ( atlasMap , texelIndex ) { const texelInfoBase = texelIndex * 4 ; const texelPosU = atlasMap . data [ texelInfoBase ] ; const texelPosV = atlasMap . data [ texelInfoBase + 1 ] ; const texelItemEnc = atlasMap . data [ texelInfoBase + 2 ] ; const texelFaceEnc = atlasMap . data [ texelInfoBase + 3 ] ; if ( texelItemEnc === 0 ) { return null ; } const texelItemIndex = Math . round ( texelItemEnc - 1 ) ; const texelFaceIndex = Math . round ( texelFaceEnc - 1 ) ; if ( texelItemIndex < 0 || texelItemIndex >= atlasMap . items . length ) { throw new Error ( `incorrect atlas map item data: ${ texelPosU }, ${ texelPosV }, ${ texelItemEnc }, ${ texelFaceEnc }` ) ; } const atlasItem = atlasMap . items [ texelItemIndex ] ; if ( texelFaceIndex < 0 || texelFaceIndex >= atlasItem . faceCount ) { throw new Error ( `incorrect atlas map face data: ${ texelPosU }, ${ texelPosV }, ${ texelItemEnc }, ${ texelFaceEnc }` ) ; } return { texelIndex , originalMesh : atlasItem . originalMesh , originalBuffer : atlasItem . originalBuffer , faceIndex : texelFaceIndex , pU : texelPosU , pV : texelPosV } ; } export function * scanAtlasTexels ( atlasMap , onFinished ) { const { width : atlasWidth , height : atlasHeight } = atlasMap ; const totalTexelCount = atlasWidth * atlasHeight ; let texelCount = 0 ; let retryCount = 0 ; while ( texelCount < totalTexelCount ) { const currentCounter = texelCount ; texelCount += 1 ; const texelInfo = getTexelInfo ( atlasMap , currentCounter ) ; if ( ! texelInfo && retryCount < 100 ) { retryCount += 1 ; continue ; } retryCount = 0 ; yield texelInfo ; } onFinished ( ) ; } function getInputItems ( originalScene ) { const items = [ ] as AtlasMapInternalItem [ ] ; traverseAtlasItems ( originalScene , ( mesh ) => { if ( ! ( mesh instanceof THREE . Mesh ) ) { return ; } const buffer = mesh . geometry ; if ( ! ( buffer instanceof THREE . BufferGeometry ) ) { return ; } const uv2Attr = buffer . attributes . uv2 ; if ( ! uv2Attr ) { return ; } const indexAttr = buffer . index ; if ( ! indexAttr ) { throw new Error ( 'expected face index array' ) ; } const faceVertexCount = indexAttr . array . length ; if ( ! ( uv2Attr instanceof THREE . BufferAttribute ) ) { throw new Error ( 'expected uv2 attribute' ) ; } const itemIndex = items . length ; const atlasPosAttr = new THREE . Float32BufferAttribute ( faceVertexCount * 3 , 3 ) ; const atlasUV2Attr = new THREE . Float32BufferAttribute ( faceVertexCount * 2 , 2 ) ; const atlasFaceInfoAttr = new THREE . Float32BufferAttribute ( faceVertexCount * 4 , 4 ) ; const indexData = indexAttr . array ; for ( let faceVertexIndex = 0 ; faceVertexIndex < faceVertexCount ; faceVertexIndex += 1 ) { const faceMod = faceVertexIndex % 3 ; atlasUV2Attr . copyAt ( faceVertexIndex , uv2Attr , indexData [ faceVertexIndex ] ) ; const facePosX = faceMod & 1 ; const facePosY = ( faceMod & 2 ) >> 1 ; const faceIndex = ( faceVertexIndex - faceMod ) / 3 ; atlasFaceInfoAttr . setXYZW ( faceVertexIndex , facePosX , facePosY , itemIndex + 1 , faceIndex + 1 ) ; } const atlasBuffer = new THREE . BufferGeometry ( ) ; atlasBuffer . setAttribute ( 'position' , atlasPosAttr ) ; atlasBuffer . setAttribute ( 'uv2' , atlasUV2Attr ) ; atlasBuffer . setAttribute ( 'faceInfo' , atlasFaceInfoAttr ) ; items . push ( { faceCount : faceVertexCount / 3 , perFaceBuffer : atlasBuffer , originalMesh : mesh , originalBuffer : buffer } ) ; } ) ; return items ; } function createOrthoScene ( inputItems ) { const orthoScene = new THREE . Scene ( ) ; orthoScene . name = 'Atlas mapper ortho scene' ; for ( const geom of inputItems ) { const mesh = new THREE . Mesh ( ) ; mesh . frustumCulled = false ; mesh . geometry = geom . perFaceBuffer ; mesh . material = new THREE . ShaderMaterial ( { side : THREE . DoubleSide , vertexShader : VERTEX_SHADER , fragmentShader : FRAGMENT_SHADER } ) ; orthoScene . add ( mesh ) ; } return orthoScene ; } export function renderAtlas ( gl , width , height , originalScene ) { const inputItems = getInputItems ( originalScene ) ; const orthoScene = createOrthoScene ( inputItems ) ; const orthoTarget = new THREE . WebGLRenderTarget ( width , height , { type : THREE . FloatType , magFilter : THREE . NearestFilter , minFilter : THREE . NearestFilter , depthBuffer : false , generateMipmaps : false } ) ; const orthoCamera = new THREE . OrthographicCamera ( 0 , 1 , 1 , 0 , 0 , 1 ) ; const orthoData = new Float32Array ( width * height * 4 ) ; const prevClearColor = new THREE . Color ( ) ; gl . getClearColor ( prevClearColor ) ; const prevClearAlpha = gl . getClearAlpha ( ) ; const prevAutoClear = gl . autoClear ; gl . setRenderTarget ( orthoTarget ) ; gl . setClearColor ( ATLAS_BG_COLOR , 0 ) ; gl . autoClear = true ; gl . render ( orthoScene , orthoCamera ) ; gl . setRenderTarget ( null ) ; gl . setClearColor ( prevClearColor , prevClearAlpha ) ; gl . autoClear = prevAutoClear ; gl . readRenderTargetPixels ( orthoTarget , 0 , 0 , width , height , orthoData ) ; orthoTarget . dispose ( ) ; return { width : width , height : height , texture : new THREE . DataTexture ( orthoData , width , height , THREE . RGBAFormat , THREE . FloatType ) , data : orthoData , items : inputItems . map ( ( { faceCount , originalMesh , originalBuffer } ) => ( { faceCount , originalMesh , originalBuffer } ) ) } ; }