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

I don’t know js so well, I don’t understand processes
async/await/loadAsync - in detail these are too advanced for me :cry:.
However, I have noticed that they can affect the ORDER of adding loaded models to groups.
I noticed that the models in the group are stored in a different order than given in the for loop. Always the smaller model is in the group ahead of the larger one.
Maybe I’m wrong: is the different order of loaded models due to the asynchronous model loading process?
So can I force the model loading loop to wait until the previous model (and MATERIALS, TRAVERSE, SCENE.ADD or GROUP.ADD etc.) is fully loaded before loading the next model?
I’m sorry to say this, but I’ve been looking at various threads related to async/await/loadAsync, but due to my insufficient knowledge of JS :cry:, I can’t implement this effectively in my code.
If and what changes should I make to the simple initModel function to load several obj/mtl models:

function initModel () {

let mod2load=[ 'box1', 'box2'];
for (let i = 0; i < mod2load.length; i++) {

	let fname = mod2load[i], fpath='';

	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.load( fname+'.obj', function ( mod3one ) {

		mod3one.traverse(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' } ) );
				if (node.material.name =='yellow' ) scene.add( line );
			}
		}); // mod3one.traverse ------------------------------

		scene.add(mod3one);

		}); // load fname+'.obj' ------------------------------

	}); // load fname+'.mtl' ------------------------------
	
} // for ------------------------------

} // initModel

For an example of using async and await to load multiple assets, have a look at @drcmda’s suggestion:

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