async/await/loadAsync - black magic for me :-(

After a lot of ups and downs :), rearranging the code like lego blocks, I finally managed to somehow implement async/await…

This code example looks interesting but turned out to be too difficult for me to implement :worried::

const [font, hdri, gltf] = await Promise.all([
new Promise((res, rej) => fontLoader.load(“/font.json”, res, rej)),
new Promise((res, rej) => rgbeLoader.load(“/warehouse.hdr”, res, rej)),
new Promise((res, rej) => gltfLoader.load(“/model.glb”, res, rej)),
])
> more…

However, these examples have become the most inspiring for me:
1 > more…
2 > more…

The hardest part for me was separating the objLoader and mtlLoader sequences into separate lines of code:

import * as THREE from 'three';

import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';	
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, controls, scene, renderer, dirLight;

let AllModelGroup;		// group of all loaded models

async function init() {

			scene = new THREE.Scene();
			scene.background = new THREE.Color( '#ffffff' );

			renderer = new THREE.WebGLRenderer( { antialias: true } );
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			document.body.appendChild( renderer.domElement );

			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
			camera.position.set( -30, 50, 70 );

			// controls
			controls = new OrbitControls( camera, renderer.domElement );
				controls.minDistance = 1;
				controls.maxDistance = 100;
				controls.target.set(15,10,0);	// camera target	

			// lights
			dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
			dirLight.position.set(0, 50, 50);
				dirLight.castShadow = true;
				camera.add( dirLight );
				scene.add( camera );
				
			const ambientLight = new THREE.AmbientLight( 0xffffff, 1 );
			scene.add( ambientLight );

		await initModel();
		await dispGroup();	// show structure of groups loaded *.obj

	
			window.addEventListener( 'resize', onWindowResize );
}

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
	requestAnimationFrame( animate );
	controls.update();
	render();

}
function render() {
	renderer.render( scene, camera );
}


init();
animate();


async function initModel () {

	// list of *.obj models to load	
	let mod2load=[ 'box1', 'box2', 'box3']; 

	const mtlLoader = new MTLLoader();
	const objLoader = new OBJLoader();

	const LoadMaterials = async (loader, fname) => {
	  return await loader.loadAsync( fname+'.mtl');
	}

	// create an empty group of all *.obj models to load 
	// and attach it to the scene
	AllModelGroup = new THREE.Group;
	AllModelGroup.name = 'AllModelGroup';
	scene.add( AllModelGroup );

for (let i = 0; i < mod2load.length; i++) {

		let fname = mod2load[i], fpath='models/';
		
		
	let MyObjGroup = await new THREE.Group;		// the main group of one loaded model *.obj
	let MyLineGroup = await new THREE.Group;	// subgroup of hybrid edges
	let MyAllLineGroup = await new THREE.Group;	// subgroup of all edges


	await mtlLoader.setPath( fpath );
	const materials = await LoadMaterials(mtlLoader, fname);
 
		await materials.preload();

		objLoader.setMaterials( materials );
		objLoader.setPath( fpath );
		let mod3one = await objLoader.loadAsync(fname+'.obj');

		// based on the loaded model *.obj, among others generate its edges
		await mod3one.traverse(await function(node) {			

				if ( node.isMesh && node.material) {
					node.material.side = THREE.DoubleSide;
						let edges = new THREE.EdgesGeometry(node.geometry);
						let line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial( { color: '#000000' } ) );
					// assume the edges of the yellow model as hybrid, 
					// requiring showing together with the body of the model
					// and join them to MyLineGroup
					if (node.material.name =='yellow' ) {
						MyLineGroup.add( line );
					}else{
					// edges of other materials join MyAllLineGroup, 
					// which is hidden by default
						MyAllLineGroup.add( line );
					}
				}
			}); // mod3one.traverse ------------------------------

	// correct/complete data in sub/groups
	MyObjGroup.name="MyObjGroup";
	mod3one.name="ObjBody";		
	MyLineGroup.name="MyLineGroup";
	MyAllLineGroup.name="MyAllLineGroup";

	// remember the name of the file in the group
	MyObjGroup.fname=fname;
	mod3one.fname=fname;		
	MyLineGroup.fname=fname;		
	MyAllLineGroup.fname=fname;	
	
	// when the scene is loaded for the first time, hide the group of all edges
	MyAllLineGroup.visible=false;

	MyObjGroup.add( mod3one );
	MyObjGroup.add( MyLineGroup );
	MyObjGroup.add( MyAllLineGroup );

	// append the group of the currently loaded model to the group of all models
	AllModelGroup.add( MyObjGroup );

	// in the console, show the groups of the currently loaded model *.obj
	// console.log(fname);		
	// console.log(MyObjGroup);
	// console.log(MyLineGroup);
	// console.log(MyAllLineGroup);

// go to load next model *.obj
}//for

} // initModel


async function dispGroup () {	// show structure of groups loaded *.obj

	let x,y,z,q;

	console.log(' ------ scene --------');	// OK
	console.log(scene);	// OK

	console.log(' ------ AllModelGroup --------');	// OK
	x= scene.getObjectByName( "AllModelGroup" );
	console.log(x);

	for (let i = 0; i < x.children.length; i++) {
		
		console.log("=== "+x.children[i].fname+" ===");
		y=x.children[i];//getObjectByName( "MyObjGroup" );
			console.log( y.fname +" "+y.name );
		z=y.children;

		if (typeof z=='object' && z.length > 0 ){
			for (let chil of z) {
				console.log( chil.fname +" - "+chil.name );
				// console.log( chil );				
			} // podgrupy AllModelGroup
		}
	} // for
} // dispGroup

I think I have achieved my intended goal:

  • ordered obj models are loaded in the ordered order
  • loaded obj models are placed in groups in the same order
  • and it seems that the results are immediately available to be checked in the console - without any delay

Remaining issues to be resolved (someone help me ?):

  1. Can this current code be syntactically improved ?
  2. Is there too much await in it?
  3. Do I have to have the function LoadMaterials separately ?
  4. Can all this be achieved with the previous one
    arrangement of nested objLoader and mtlLoader ?
    Then I had a loading progress bar in JS syntax:

Previous syntax:

let mtlLoader = new MTLLoader();
mtlLoader.setPath( fpath );
mtlLoader.load( fname+'.mtl', function ( materials ) {
	materials.preload();
	
	let objLoader = new OBJLoader();
	objLoader.setMaterials( materials );
	objLoader.setPath( fpath );
	objLoader.setPath( fpath );
	objLoader.load( fname+'.obj', function ( mod3one ) {
		...
	},
	onProgress	//where to move this function ?
	);
});

Previously, instead of grouping loaded *.obj models, I used layers to store *.obj models. But in addition, I stored the edges of the models on two more layers. Therefore, each loaded *.obj occupied 3 layers. With this method, I could load a maximum of 10 *.obj models into the scene.
After switching to grouping of loaded objects, I got rid of these limitations and at the same time developed the existing *.obj model control properties on the stage:

  • controlling the visibility of individual models;
  • controlling the model display mode (with or without edges, etc.);



I have been satisfied for a month of learning about ThreeJs. :grinning:
ThreeJs in conjunction with LightGallery promises to be a great tool for publishing architectural and construction models (exported from ArchiCAD to *.obj) for the purpose of e.g. quick workshop communication with other designers and construction contractors.

1 Like