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 @@
@@ -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();
-