0

I am working on a program that adds items to a three.js scene, positioning them at coordinates based on how many items already exist in the scene. Currently, the problem I'm running into is that when the user chooses a "Work at Height", two people are added to the scene. These are two separate function calls, but when the scene finishes, both people are at the same coordinates. This also happens when the user clicks to add multiple people and does not wait for each to load.

Is there a way I can force my loading function to wait for the first object to finish loading so that it loads only one model at a time?

This mention of the LoadingManager got me thinking, and tried I to use that by saving the number of files loaded and total as variables, then comparing them in a while loop, but, as expected, that just slowed the scripts down so much that my browser wanted me to stop them.

// adds objects to the basket with the appropriate rotation
      function createObjectBasket(filePath, scale, position, name, type) {
        // Load in the object and add it to the scene.
        var loader = new THREE.ObjectLoader(manager);
        /*while (gl_itemsLoaded < gl_itemsTotal) {
          
        }*/
        loader.load( filePath, function(object, materials){
          
          // rotate
          object.rotation.x = - ((Math.PI / 2) - (Math.PI / 18));
          object.rotation.y = - (Math.PI - (Math.PI / 5));
          object.rotation.z = 0; //- (Math.PI / 120);
          
          // scale
          object.scale.set(scale, scale, scale);
          
          // translate
          object.position.set(position.x, position.y, position.z);
          
          // set name for easy access
          object.name = name;
          
          // add to scene
          scene.add(object);
          
          if (type == "basket") {
            // add to object array for easy access
            basketContents["basket"].push(object);
          }
          else if (type == "person") {
            // add to object array for easy access
            basketContents["people"].push(object);
          }
          else if (type == "tool") {
            // add to object array for easy access
            basketContents["tools"].push(object);
          }
          else if (type == "attachment") {
            // add to object array for easy access
            basketContents["attachments"].push(object);
          }
        });
      }

Where I'm having the issue, this code is called (indirectly) via the following two lines:

objectAddRemove("person", "CWM_A");
objectAddRemove("person", "CWM_B");

Which then execute this function that calls createObjectBasket:

      // add/remove objects to/from basket on click
      function objectAddRemove(type, objectName) {
        // if adding items
        if (addRemove == "add") {
          
          // determine if there is still room to add more objects
          var room = true;
          // can have <= 3 people total, so if there are 3, can't add more
          if ((type == "person") && basketContents["people"].length >= 3) {
            room = false;
            return;
          }
          // no current restrictions on tools
          /*else if ((type == "tool")) {
            
          }*/
          // can only have 1 of each attachment
          else if ((type == "attachment") && (object[objectName]["contentsCount"] > 0)) {
            room = false;
            return;
          }
          
          if (room == true) {
            // if it's a person
            if (type == "person") {
              // if it's a man
              if (objectName.indexOf("M") >= 0) {
                // add model
                createObjectBasket(("models/" + objectName + ".json"), 1.5, personCoordsMan[basketContents["people"].length + 1], objectName, "person");
              }
              // if it's a woman
              else {
                // add model
                createObjectBasket(("models/" + objectName + ".json"), 1.5, personCoordsWoman[basketContents["people"].length + 1], objectName, "person");
              }
            }
            // if it's a tool
            else if (type == "tool") {
              /*createObjectBasket(("models/" + objectName + ".json"), 1.5, toolCoords[basketContents["tools"].length + 1], objectName, "tool");*/
              createObjectBasket(("models/" + objectName + ".json"), 1.5, toolCoords, objectName, "tool");
            }
            // if it's an attachment
            else if (type == "attachment") {
              createObjectBasket(("models/" + objectName + ".json"), .04, attachmentCoords[objectName], objectName, "attachment");
            }

            // increase count
            object[objectName]["contentsCount"] += 1;
            console.log(objectName);

            $('#' + objectName).children('.status').children('.checkMark').show();
          }
        }
        
        // if removing items
        else if (addRemove == "remove") {
          
          // remove objects from arrays
          if (type == "person") {
            removeObjectArray("people", objectName);
            
            // if person is found (and removed), rearrange all people to accommodate
            if (itemFound == true) {
              for (i = 0; i < basketContents["people"].length; ++i) {
                if (basketContents["people"][i].name.indexOf("M") >= 0) {
                  basketContents["people"][i].position.set(personCoordsMan[i+1].x, personCoordsMan[i+1].y, personCoordsMan[i+1].z);
                }
                else {
                  basketContents["people"][i].position.set(personCoordsWoman[i+1].x, personCoordsWoman[i+1].y, personCoordsWoman[i+1].z);
                }
              }
            }
          }
          else if (type == "tool") {
            removeObjectArray("tools", objectName);
            
            // if tool is found (and removed), rearrange all tools to accommodate
            /*if (itemFound == true) {
              
            }*/
          }
          else if (type == "attachment") {
            removeObjectArray("attachments", objectName);
          }
          
          // if all objects of that id have been removed, hide remove x mark
          if (object[objectName]["contentsCount"] <= 0) {
            $('#' + objectName).children('.status').children('.xMark').hide();
          }
          
          // if, after removing, person/object count is now 0, no remaining items can be removed, so switch to add
          if ((steps[currentStep] == "people") && (basketContents["people"].length <= 0)) {
            addItems();
          }
          else if ((steps[currentStep] == "objects") && ((basketContents["tools"].length + basketContents["attachments"].length) <= 0)) {
            addItems();
          }
        }
        // if no remaining items can be removed on this page
        else {
          addItems();
        }
      }

objectAddRemove is also called whenever a person clicks on an image representing the desired object, so I need a way to wait for models from previous clicks to load, also (in addition to models loaded automatically through the code).

To test/view further code, you can visit this link. Select a "Work at Height", weight unit, then skip to "Add Operators". It will show that two people are in the basket (checked) but only one is visible in the basket. If you click "Remove Items" then the visible person to remove the visible person, the hidden one will show.

Thank you so much!!!

Community
  • 1
  • 1
Mary7678
  • 413
  • 6
  • 12
  • Possible duplicate of [How should I call 3 functions in order to execute them one after the other?](http://stackoverflow.com/questions/5187968/how-should-i-call-3-functions-in-order-to-execute-them-one-after-the-other) – Andy Ray Jan 03 '17 at 19:32
  • Thanks, @Andy Ray, but I think I am calling the functions this way. I am calling objectAddRemove("person", "CWM_A"); objectAddRemove("person", "CWM_B");. objectAddRemove creates an object using the function listed above (createObjectBasket). According to the link you gave me, this means the functions should run synchronously? So the second call should not execute until the first is completed? Unless I'm reading it incorrectly. – Mary7678 Jan 03 '17 at 19:47
  • No. asynchronous functions do not block program flow. All top level statements are executed in order, then at a later time the async callback functions are executed. – Andy Ray Jan 03 '17 at 20:01

3 Answers3

1

Loading external data almost always happens asynchronously and therefore making sure one model is loaded after another requires handling the onLoad events.

I'm not sure if LoadingManager supports loading models one after another.

But you can implement a simple loading queue yourself. Something in the line of:

var myQueue = ['model1.dae', 'model2.dae'];

function loadFromQueue()
{
    if (myQueue.length == 0) return;

    // Takes the first name from array and remove it from the array
    var name = myQueue.shift(); 

    // Call the loader and provide an onLoad event handler
    loader.load(name, function(model){

         // Do what you need to do with the model,
         // usually it's scene.add(model) and some transformations.

         // Call the next item in the queue
         loadFromQueue();
    });
}

Now this is a very crude queue and there are better ways to do it. But I'm using it the simplest demonstration how you can use the onLoad event handler to load models one after another.

I presume the next hurdle you'll hit will be how to pass some values to the event handler. Come back again then!

Matey
  • 1,190
  • 5
  • 7
  • Thanks, @Matey, that's really helpful!!! I took this approach and tried to switch everything to load from the queue (no issues getting the data I needed), but I'm running into a problem. My code needs to load models in 2 ways - some are automatically added by the program (in which case the solution you provided is great!), while others are loaded when the user clicks on a thumb of an object. If the user clicks twice in rapid succession, the models load on top of each other. I can't think of any way to add to the queue and load on click while waiting for the previous model. Thoughts? – Mary7678 Jan 04 '17 at 19:10
  • Then you should add a timer check and discard the second click if it happens within the timer interval. – Matey Jan 08 '17 at 20:43
0

Thanks to the suggestion from @Matey, I ended up just using timeouts to prevent the objects from displaying on top of one another. I was hoping to avoid that, since the amount of time it takes can vary by model and environment, but that was the only thing that worked for the clicks. The load queue was also still loading the models on top of one another, and I decided since I was already risking the timeout, it would be easier to implement that than to fix it.

I added timeouts to my click event using the accepted answer here, and then just added a timeout to the second person I was adding:

objectAddRemove("person", "CWM_A");
// add second person on timeout so people don't appear on top of each other
setTimeout(function() {
  objectAddRemove("person", "CWM_B");
},

That was the only place that automatically added objects needed some delay, so hopefully that does it. I'm just a little concerned with how it will function across different machines.

Community
  • 1
  • 1
Mary7678
  • 413
  • 6
  • 12
0

I actually ended up getting around this by taking a different approach - I needed variables to keep track of the objects, so I checked whether objects had been loaded based on the variable values (which were set quickly) rather than the number of three.js objects (which took much longer to load). This worked much, much better and is more reliable across systems than using a timeout.

Mary7678
  • 413
  • 6
  • 12