diff --git a/examples/jsm/lighting/LightProbeGrid.js b/examples/jsm/lighting/LightProbeGrid.js index b0644f065c3a4f..b6e414c827378f 100644 --- a/examples/jsm/lighting/LightProbeGrid.js +++ b/examples/jsm/lighting/LightProbeGrid.js @@ -45,8 +45,8 @@ let _batchTargetProbes = 0; // Reusable temp objects const _position = /*@__PURE__*/ new Vector3(); const _size = /*@__PURE__*/ new Vector3(); -const _savedViewport = /*@__PURE__*/ new Vector4(); -const _savedScissor = /*@__PURE__*/ new Vector4(); +const _currentViewport = /*@__PURE__*/ new Vector4(); +const _currentScissor = /*@__PURE__*/ new Vector4(); // Number of padding texels added at each boundary of every sub-volume in the atlas. const ATLAS_PADDING = 1; @@ -231,10 +231,19 @@ class LightProbeGrid extends Object3D { const batchTarget = _ensureBatchTarget( totalProbes ); // Save renderer state - const savedRenderTarget = renderer.getRenderTarget(); - renderer.getViewport( _savedViewport ); - renderer.getScissor( _savedScissor ); - const savedScissorTest = renderer.getScissorTest(); + const currentRenderTarget = renderer.getRenderTarget(); + renderer.getViewport( _currentViewport ); + renderer.getScissor( _currentScissor ); + const currentScissorTest = renderer.getScissorTest(); + + // Scene is static across the bake — update once and disable per-render auto updates. + const currentMatrixWorldAutoUpdate = scene.matrixWorldAutoUpdate; + if ( currentMatrixWorldAutoUpdate === true ) { + + scene.updateMatrixWorld( true ); + scene.matrixWorldAutoUpdate = false; + + } // Clear pooled batch target so skipped probes read as zero batchTarget.scissorTest = false; @@ -250,7 +259,7 @@ class LightProbeGrid extends Object3D { // Disable shadow map auto-update during bake — lights don't move between probes. // Force one shadow update on the first render so maps are initialized. - const savedShadowAutoUpdate = renderer.shadowMap.autoUpdate; + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; renderer.shadowMap.autoUpdate = false; renderer.shadowMap.needsUpdate = true; @@ -280,7 +289,7 @@ class LightProbeGrid extends Object3D { } - renderer.shadowMap.autoUpdate = savedShadowAutoUpdate; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; // Phase 2: Repack SH data from batch target into the atlas 3D texture (GPU-to-GPU). // @@ -330,10 +339,12 @@ class LightProbeGrid extends Object3D { } // Restore renderer state - renderer.setRenderTarget( savedRenderTarget ); - renderer.setViewport( _savedViewport ); - renderer.setScissor( _savedScissor ); - renderer.setScissorTest( savedScissorTest ); + renderer.setRenderTarget( currentRenderTarget ); + renderer.setViewport( _currentViewport ); + renderer.setScissor( _currentScissor ); + renderer.setScissorTest( currentScissorTest ); + + scene.matrixWorldAutoUpdate = currentMatrixWorldAutoUpdate; // console.log( `LightProbeGrid: bake complete ${ ( performance.now() - t0 ).toFixed( 1 ) }ms` ); diff --git a/examples/screenshots/webgl_loader_ifc.jpg b/examples/screenshots/webgl_loader_ifc.jpg index d5df276544631c..1cd09dcc6195ac 100644 Binary files a/examples/screenshots/webgl_loader_ifc.jpg and b/examples/screenshots/webgl_loader_ifc.jpg differ diff --git a/examples/webgl_loader_ifc.html b/examples/webgl_loader_ifc.html index fc817c191ac605..fa916f25e2952d 100644 --- a/examples/webgl_loader_ifc.html +++ b/examples/webgl_loader_ifc.html @@ -14,6 +14,8 @@
three.js - + IFC loader using + web-ifc. See main project repository for more information and BIM tools.
@@ -22,10 +24,7 @@ "imports": { "three": "../build/three.module.js", "three/addons/": "./jsm/", - "three/examples/jsm/utils/BufferGeometryUtils": "./jsm/utils/BufferGeometryUtils.js", - "three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.5.23/build/index.module.js", - "web-ifc": "https://cdn.jsdelivr.net/npm/web-ifc@0.0.36/web-ifc-api.js", - "web-ifc-three": "https://cdn.jsdelivr.net/npm/web-ifc-three@0.0.126/IFCLoader.js" + "web-ifc": "https://cdn.jsdelivr.net/npm/web-ifc@0.0.77/web-ifc-api.js" } } @@ -34,31 +33,25 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; - import { IFCLoader } from 'web-ifc-three'; - import { IFCSPACE } from 'web-ifc'; + import { IfcAPI } from 'web-ifc'; + + const WEB_IFC_VERSION = '0.0.77'; + const WEB_IFC_WASM_PATH = `https://cdn.jsdelivr.net/npm/web-ifc@${ WEB_IFC_VERSION }/`; let scene, camera, renderer; + init(); + async function init() { - //Scene scene = new THREE.Scene(); scene.background = new THREE.Color( 0x8cc7de ); - //Camera camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); - camera.position.z = - 70; - camera.position.y = 25; - camera.position.x = 90; + camera.position.set( 82.48, 22.09, - 45.24 ); - //Initial cube - const geometry = new THREE.BoxGeometry(); - const material = new THREE.MeshPhongMaterial( { color: 0xffffff } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - //Lights const directionalLight1 = new THREE.DirectionalLight( 0xffeeff, 2.5 ); directionalLight1.position.set( 1, 1, 1 ); scene.add( directionalLight1 ); @@ -70,47 +63,181 @@ const ambientLight = new THREE.AmbientLight( 0xffffee, 0.75 ); scene.add( ambientLight ); - //Setup IFC Loader - const ifcLoader = new IFCLoader(); - await ifcLoader.ifcManager.setWasmPath( 'https://cdn.jsdelivr.net/npm/web-ifc@0.0.36/', true ); - - await ifcLoader.ifcManager.parser.setupOptionalCategories( { - [ IFCSPACE ]: false, - } ); - - await ifcLoader.ifcManager.applyWebIfcConfig( { - USE_FAST_BOOLS: true - } ); - - ifcLoader.load( 'models/ifc/rac_advanced_sample_project.ifc', function ( model ) { - - scene.add( model.mesh ); - render(); - - } ); - - //Renderer renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setPixelRatio( window.devicePixelRatio ); document.body.appendChild( renderer.domElement ); - //Controls const controls = new OrbitControls( camera, renderer.domElement ); + controls.target.set( 30.86, 7.73, 0.15 ); + controls.update(); controls.addEventListener( 'change', render ); window.addEventListener( 'resize', onWindowResize ); + const ifcAPI = new IfcAPI(); + ifcAPI.SetWasmPath( WEB_IFC_WASM_PATH ); + await ifcAPI.Init(); + + const response = await fetch( 'models/ifc/rac_advanced_sample_project.ifc' ); + const data = new Uint8Array( await response.arrayBuffer() ); + + const modelID = ifcAPI.OpenModel( data, { COORDINATE_TO_ORIGIN: true } ); + loadAllGeometry( ifcAPI, modelID ); + ifcAPI.CloseModel( modelID ); + render(); } + function loadAllGeometry( ifcAPI, modelID ) { + + const opaqueGeometries = []; + const transparentGeometries = []; + const materialCache = {}; + + ifcAPI.StreamAllMeshes( modelID, ( flatMesh ) => { + + const placedGeometries = flatMesh.geometries; + + for ( let i = 0; i < placedGeometries.size(); i ++ ) { + + const placedGeometry = placedGeometries.get( i ); + const mesh = getPlacedGeometry( ifcAPI, modelID, placedGeometry, materialCache ); + const geometry = mesh.geometry.applyMatrix4( mesh.matrix ); + + if ( placedGeometry.color.w !== 1 ) { + + transparentGeometries.push( geometry ); + + } else { + + opaqueGeometries.push( geometry ); + + } + + } + + } ); + + if ( opaqueGeometries.length > 0 ) { + + const merged = BufferGeometryUtils.mergeGeometries( opaqueGeometries ); + const material = new THREE.MeshPhongMaterial( { side: THREE.DoubleSide, vertexColors: true } ); + scene.add( new THREE.Mesh( merged, material ) ); + + } + + if ( transparentGeometries.length > 0 ) { + + const merged = BufferGeometryUtils.mergeGeometries( transparentGeometries ); + const material = new THREE.MeshPhongMaterial( { + side: THREE.DoubleSide, + vertexColors: true, + transparent: true, + } ); + scene.add( new THREE.Mesh( merged, material ) ); + + } + + } + + function getPlacedGeometry( ifcAPI, modelID, placedGeometry, materialCache ) { + + const geometry = getBufferGeometry( ifcAPI, modelID, placedGeometry ); + const material = getMeshMaterial( placedGeometry.color, materialCache ); + const mesh = new THREE.Mesh( geometry, material ); + mesh.matrix = new THREE.Matrix4().fromArray( placedGeometry.flatTransformation ); + mesh.matrixAutoUpdate = false; + return mesh; + + } + + function getBufferGeometry( ifcAPI, modelID, placedGeometry ) { + + const geometry = ifcAPI.GetGeometry( modelID, placedGeometry.geometryExpressID ); + const vertexData = ifcAPI.GetVertexArray( geometry.GetVertexData(), geometry.GetVertexDataSize() ); + const indexData = ifcAPI.GetIndexArray( geometry.GetIndexData(), geometry.GetIndexDataSize() ); + + const bufferGeometry = ifcGeometryToBuffer( placedGeometry.color, vertexData, indexData ); + + // Geometry is owned by the WASM heap and must be released. + geometry.delete(); + return bufferGeometry; + + } + + function getMeshMaterial( color, materialCache ) { + + const id = `${ color.x }-${ color.y }-${ color.z }-${ color.w }`; + const cached = materialCache[ id ]; + if ( cached ) return cached; + + const material = new THREE.MeshPhongMaterial( { + color: new THREE.Color( color.x, color.y, color.z ), + side: THREE.DoubleSide, + } ); + + if ( color.w !== 1 ) { + + material.transparent = true; + material.opacity = color.w; + + } + + materialCache[ id ] = material; + return material; + + } + + const _tmpColor = new THREE.Color(); + + function ifcGeometryToBuffer( color, vertexData, indexData ) { + + // web-ifc returns interleaved [px, py, pz, nx, ny, nz] per vertex. + const vertexCount = vertexData.length / 6; + const positions = new Float32Array( vertexCount * 3 ); + const normals = new Float32Array( vertexCount * 3 ); + const colors = new Float32Array( vertexCount * 4 ); + + // IFC stores colors in sRGB display space; convert to linear once per geometry. + _tmpColor.setRGB( color.x, color.y, color.z, THREE.SRGBColorSpace ); + + for ( let v = 0; v < vertexCount; v ++ ) { + + const src = v * 6; + const dst3 = v * 3; + const dst4 = v * 4; + + positions[ dst3 + 0 ] = vertexData[ src + 0 ]; + positions[ dst3 + 1 ] = vertexData[ src + 1 ]; + positions[ dst3 + 2 ] = vertexData[ src + 2 ]; + + normals[ dst3 + 0 ] = vertexData[ src + 3 ]; + normals[ dst3 + 1 ] = vertexData[ src + 4 ]; + normals[ dst3 + 2 ] = vertexData[ src + 5 ]; + + colors[ dst4 + 0 ] = _tmpColor.r; + colors[ dst4 + 1 ] = _tmpColor.g; + colors[ dst4 + 2 ] = _tmpColor.b; + colors[ dst4 + 3 ] = color.w; + + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); + geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 4 ) ); + geometry.setIndex( new THREE.BufferAttribute( indexData, 1 ) ); + return geometry; + + } + function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); - render(); } @@ -121,8 +248,6 @@ } - init(); - diff --git a/src/renderers/common/XRManager.js b/src/renderers/common/XRManager.js index 13c50cfb9fe919..20ac220668f3ec 100644 --- a/src/renderers/common/XRManager.js +++ b/src/renderers/common/XRManager.js @@ -14,7 +14,7 @@ import { CylinderGeometry } from '../../geometries/CylinderGeometry.js'; import { PlaneGeometry } from '../../geometries/PlaneGeometry.js'; import { MeshBasicMaterial } from '../../materials/MeshBasicMaterial.js'; import { Mesh } from '../../objects/Mesh.js'; -import { warn } from '../../utils.js'; +import { warn, warnOnce } from '../../utils.js'; import { renderOutput } from '../../nodes/display/RenderOutputNode.js'; const _cameraLPos = /*@__PURE__*/ new Vector3(); @@ -591,6 +591,20 @@ class XRManager extends EventDispatcher { } + /** + * Returns the current base layer. + * + * This is an `XRProjectionLayer` when the targeted XR device supports the + * WebXR Layers API, or an `XRWebGLLayer` otherwise. + * + * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. + */ + getBaseLayer() { + + return this._glProjLayer !== null ? this._glProjLayer : this._glBaseLayer; + + } + /** * Returns the current XR binding. @@ -612,6 +626,61 @@ class XRManager extends EventDispatcher { } + /** + * Applies WebXR fixed foveation to the internal post-processing render target + * used by the first XR render pass before compositing into a projection layer. + * + * Browser-side `XRWebGLBinding.foveateBoundTexture()` failures are treated as + * non-fatal so they do not interrupt rendering. + * + * @param {RenderTarget} renderTarget - The internal render target. + */ + foveateBoundTexture( renderTarget ) { + + if ( renderTarget.isPostProcessingRenderTarget !== true ) return; + if ( this.isPresenting !== true ) return; + if ( this._glProjLayer === null ) return; + + const backend = this._renderer.backend; + + if ( backend === undefined || backend.isWebGLBackend !== true ) return; + if ( backend.state === null ) return; + + const outputRenderTarget = this._renderer.getOutputRenderTarget(); + + if ( outputRenderTarget === null || outputRenderTarget.isXRRenderTarget !== true ) return; + + const glBinding = this.getBinding(); + + if ( glBinding === null || typeof glBinding.foveateBoundTexture !== 'function' ) return; + + this._renderer._textures.updateRenderTarget( renderTarget ); + + const { textureGPU, glTextureType } = backend.get( renderTarget.texture ); + + if ( textureGPU === undefined || glTextureType === undefined ) return; + if ( renderTarget._xrFoveationTextureGPU === textureGPU ) return; + + renderTarget._xrFoveationTextureGPU = textureGPU; + + backend.state.bindTexture( glTextureType, textureGPU ); + + try { + + glBinding.foveateBoundTexture( glTextureType, this.getFoveation() ); + + } catch ( error ) { + + warnOnce( `XRManager: Unable to foveate bound XR post-processing texture. ${error.name}: ${error.message}` ); + + } finally { + + backend.state.unbindTexture(); + + } + + } + /** * Returns the current XR frame. * @@ -1652,6 +1721,9 @@ function onAnimationFrame( time, frame ) { renderer.setOutputRenderTarget( this._xrRenderTarget ); + const frameBufferTarget = renderer._getFrameBufferTarget(); + renderer.xr.foveateBoundTexture( frameBufferTarget ); + } //