[Beginner] Randomly load GLTF from array (and not reload current selection...)

Hey all,

The .gif shows what I’m trying to accomplish: i.imgur.com/2Am7SsJ.gif
Maybe hard to tell, but I’m clicking on a region and .GLTFs are loading…
However, I don’t want the same selection from the array to pop up on sequential clicks.
Said another way, the model should not be the same after the user clicks each time.

My guess is that this is a scoping problem, each time I use the debugger…
Seems to indicate that .addEventListener has the same selection every time.
(ie: Debugger will say that Coin Model, for example, has loaded each time. Doesn’t update)

I’ve spent ~4 hours on the problem. I gave this some effort before asking.

Thanks, ~DH


var RIGHT_Rotate = new THREE.Object3D();
function MainRIGHTLoader(CycledImport: string) {    

    // Get ORIGINAL Model
        // INDEX
            function rollRandomIndex(arr: string | any[]) {
                    return Math.floor(Math.random() * arr.length);
                }        
        
         // ARRAY 
            var modelList = [
                    { modelName: 'Zeah', modelPath: '/w/images/models/coin_zeah.glb' },
                    { modelName: 'Wiki', modelPath: '/w/images/models/coin_wiki.glb' },
                    { modelName: 'Bond', modelPath: '/w/images/models/bond.glb' },
                    { modelName: 'Hat', modelPath: '/w/images/models/hat.glb' },
                    { modelName: 'Relic', modelPath: '/w/images/models/relic.glb' }
                ];

        // Store ORIGINAL Model
            var rightHandModel = modelList[rollRandomIndex(modelList)];

        // Store NEW Model
            rightHandModel = rollNewModel(rightHandModel.modelName);
        
        // Roll New
            function rollNewModel(CycledImport: string) {
                let newModel = modelList[rollRandomIndex(modelList)];
                if (CycledImport == newModel.modelName) 
                    {
                        // Print original roll info: 
                            console.log("Original roll: " + newModel.modelName);
                            console.log("Original array size: " + modelList.length);

                        // Create a filtered array excluding duplicate roll:
                            let newModelList = modelList.filter(item => item.modelName.indexOf(newModel.modelName));

                        // Print new array size (validate we removed something):
                            console.log("New array size: " + newModelList.length);

                        // Roll new model, now with smaller list, print debug
                            newModel = newModelList[rollRandomIndex(newModelList)];
                            console.log("New model: " + JSON.stringify(newModel));
                            return newModel;
                    } else 
                        { return newModel; }};

        // PREVIOUS IMPORT
            function PullPrevious () {
                var ReturnThis = rightHandModel.modelPath;
                return ReturnThis;
                } 

        // LOADER                    
            const RIGHT_loader = new GLTFLoader();
            RIGHT_loader.load( rightHandModel.modelPath , function (gltf) {
                
                // Clear RIGHT Model 
                    while (RIGHT_Rotate.children.length) { RIGHT_Rotate.remove(RIGHT_Rotate.children[0]); }

                // RIGHT Intersection Events  
                    var Raycaster = new THREE.Raycaster();
                    var Mouse = new THREE.Vector2();

                    document.addEventListener( 'mousedown', function( event ) 
                        {
                            Mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.clientWidth ) * 2 - 1;
                            Mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.clientHeight ) * 2 + 1;    

                            Raycaster.setFromCamera( Mouse, camera );
                            var intersections = Raycaster.intersectObjects( RIGHT_Rotate.children );
                            for ( var i = 0; i < intersections.length; i++ ) {
                                if ( RIGHT_Rotate.children.length ) 
                                    {
                                        let WhatWasPrevious = PullPrevious();

                                        console.log("PAINTED NOW: " + WhatWasPrevious);

                                        while (RIGHT_Rotate.children.length) { RIGHT_Rotate.remove(RIGHT_Rotate.children[0]); }
                                        MainRIGHTLoader(rightHandModel.modelPath) 
                                    }   
                        }})

                // Set RIGHT Position                 
                    const box = new THREE.Box3().setFromObject(gltf.scene);
                    const center = box.getCenter(new THREE.Vector3());
                    const size = box.getSize(new THREE.Vector3());
                    gltf.scene.position.set(-center.x, size.y / 2 - center.y, -center.z);

                // Set RIGHT Rotation                
                    RIGHT_Rotate.add(gltf.scene);
                    RIGHT_Rotate.scale.set(RIGHT_Scale, RIGHT_Scale, RIGHT_Scale);
                    RIGHT_Rotate.position.x = 1.25;
                    RIGHT_Rotate.position.y = 0.58;
                    RIGHT_Rotate.position.z = 0.4;
                    scene.add(RIGHT_Rotate);
    })};

Perhaps another way (which I’ve been trying and failing)…
Would be to be able to detect which model is currently loaded via Raycaster, then pass that backwards

the app has to be driven by state, this seems to drive state through the app, though mutation. that is not a good thing, it will only get worse if this scales up.

try a simple state model, if you can use a state manager, zustand for instance. state contains two indices for left and right. you only change the L/R index numbers and that’s all. something then merely reacts to the state model and it reflects it in the view.

import create from 'zustand/vanilla'

const models = [...] // the gltfs
const { getState, subscribe } = create(set => ({
  left: 0,
  right: 0,
  randLeft: () => set(current => ({ left: randomizeButNot(current.left) })),
  randRight: () => set(current => ({ right: randomizeButNot(current.right) }))
}))

subscribe((current, previous) => {
  console.log("state has changed", state, "old state", previous)
  // exchange models in here
  scene.remove(scene.getObjectByName(models[previous.left])
  scene.add(models[current.left])
})

document.addEventListener( 'mousedown', event => {
  // ...
  getState().randLeft()
  // ...
  getState().randRight()

Hey drcmda,

This is an approach that I never considered.
Not confrontational question, purely to learn – why are states necessary, here?
Is it impossible to pass variables in/out of .addEventListener?

I’d like to keep the solution to ThreeJS, alone.
But maybe that cannot be done.
Thank you for taking the time to explain.
I’ll look into Zustand :slight_smile: and see if it would be a hassle to incorporate into the bundle.js (all I know).

this is a generic programming issue: everything revolves around state. problems arise when the app comes first, and then you think about dealing with essentials. relying on scoped variables and mutating them from various places only means one thing: your app will collapse now or later.