Load models within class

I am working on a THREE js project where I just like to work with OOP.
Right now I am setting up some classes for loading a model, and after the model is fully loaded the model has to be accessible inside another class (where my custom loading class is being imported in) as a class poperty (like: ClassWithModel.LoadedModel).

However since all loaders load models asynchronous, I cannot set a class property from null to the loaded model since it might not be fully loaded when I call the property that should contain the model.

Now my question is, can I somehow efficiently make this work? I just want to add the loaded model(s) to a class property (if there are multiple models add them to a class property as an array).

Well, of course you can do this^^. It just means that the caller has to check whether the property is null or not. A more robust solution is to construct the entire object at a point where all its resources/dependencies are ready. In any event, you have to take the asynchronous aspect of the loading process into account at some place in your app code.

It might be a good idea to read into Promises or the newer async/await syntax.

1 Like

Well I’ve tried to check wether the property is null or not but for some reason doesn’t quite work. And I have seen some examples of async await indeed but they are very confusing and inefficient.

Let’s say I have the following structure:



// customloader.js
class CustomLoader {
    constructor(path = "") {
         this._path = path;
         this.model = null; // Maybe can be outside constructor?
         this.loader = new GLTFLoader();

         this.loader.load(
             this._path,
             function(obj) {
                 this.model = obj.scene;
             }, null, null);
    
    }

    get Model() {
       return this.model;
    }
    
    

}

// main.js
import {CustomLoader} from './customloader.js';

const loader = new CustomLoader('./model.gltf');
console.log(loader.Model); // output: null

The example above is the idea I had, and all I really want is to access the model through one property: CustomLoader.Model.

EDIT :
I’ve tried using .then() in the following way:


    /* inside my class constructor... */
    this.loadedModel = [];
    this.loader.loadAsync(this.path)
        .then((gltf) => this.loadedModel.push(gltf))
   
    /* outside class constructor.. */
   get MODEL() {
        if(loadedModel)
             return loadedModel;
   }
 

Then outside that class I call it inside another class and console.log() it. But what it outputs is: > [] plus, when I dropdown the rest of it inside dev tools it shows me: 0: {scene: Group, scenes: Array(1), animations: Array(0), cameras: Array(0), asset: {…}, …} and I can’t access the array as CustomLoader.MODEL[1] because that will return undefined. Async/await is doing the same thing. What am I doing wrong?

This looks like a scope problem, your this scope within the load callback is the loader, not the class you are trying to manipulate.

Try
let parentObject = this;
this.loader.load( … , (obj) => {
parentObject.model = object.scene;
});

@becky_rose Hmm… I must’ve looked over that. Thank you, I’ll try that though I’m not fully sure if that is the solution.

Unfortunately it does not quite work. I am using the VS Code extension live server which loads the model somehow and if I console.log it, it outputs: Array(1). However I cannot access this array and if I reload the page the array is now returned as: [].

I really don’t get it. :confused:

If you’re super into OOP, one of the most fun things it allows you to do is extending classes by Three.Group / Three.Object3D.

Thanks to that:

  1. You can treat class instances as Three.Object3D - ie. you can add them to scenes, transform outside of class etc. Without worrying about the loading.
  2. You have to keep all model-specific logic inside the class at all times. It essentially prevents you from getting a bit lazy and manipulating parts of the model in random files - which would quite likely lead to spaghetti code and headaches.
  3. It’s harder to lose track of what’s added to the scene and what has to be disposed when.

Here’s a tiny OOP example.

That’s awesome! I didn’t think of that yet. I will try that.

By any chance, is it possible to add it inside another class to a property before adding it to a scene inside my main file? It’s just that I like to keep the model management classes and the classes using the models seperated.

Maybe a combination of the above and this https://discoverthreejs.com/book/first-steps/load-models/ helps.

Imo doing this with oop is a ticket to free race conditions and memory leaks. It’s just not a model that easily translates into ui management matters. The paradigm above imperative code is oop, which acts as an encapsulation layer for logic. The paradigm above oop is components which is the first time you have truly self-contained, re-usable entities with lifecycles. That is also where scheduling comes into play and this is what takes care of async and state. This stuff is trivial with components. as an example: amazing-mccarthy-hge1j - CodeSandbox

loading is then controlled by the component, not a group or a mesh, which belongs to the view layer. that also allows their parents to control loading behaviour, fallbacks, etc. this you can only do with component framework: react, angular, vie, svelte, etc. if you tried to tie scheduling to the three.group with oop it would practically throw separation of concern out of the window.