Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a loading screen in three.js?

I have a large amount of textures and models to load into my project. I am trying to show a progress bars while everything is loading. I think the LoadingManager does just what I need as it tracks the progress of all loaded assets.

I'm using the JSONLoader and TextureLoader.

If anyone could show me how to do implement this in the example code would be awesome.

enter image description here

var camera, scene, renderer;

 init();
 animate();

 function init() {

   camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
   camera.position.z = 400;

   scene = new THREE.Scene();


   // Load Textures 
   var modeltexture1 = new THREE.TextureLoader().load('modeltexture1.jpg');
   var modeltexture2 = new THREE.TextureLoader().load('modeltexture2.jpg');
   var modeltexture3 = new THREE.TextureLoader().load('modeltexture3.jpg');
   var modeltexture4 = new THREE.TextureLoader().load('modeltexture4.jpg');
  
   // Materials
   var material = {
     "texture1": new THREE.MeshPhongMaterial({ map: modeltexture1 }),
     "texture2": new THREE.MeshPhongMaterial({ map: modeltexture2 }),
     "texture3": new THREE.MeshPhongMaterial({ map: modeltexture3 }),
     "texture4": new THREE.MeshPhongMaterial({ map: modeltexture4 }),
   }

   // Lights
   scene.add(new THREE.AmbientLight(0xcccccc));
   pointLight = new THREE.PointLight(0xff4400, 5, 30);
   pointLight.position.set(5, 0, 0);
   scene.add(pointLight);
  
   var loader = new THREE.JSONLoader();


   // Model1
   var model1 = new THREE.Object3D;
   scene.add(model1);

   loader.load("model1.js", function(geometry) {
     var mainmodel1 = new THREE.Mesh(geometry, material["texture1"]);
     model1.add(mainmodel1);
   }); 
   
   // Model2
   var model2 = new THREE.Object3D;
   scene.add(model2);

   loader.load("model2.js", function(geometry) {
     var mainmodel2 = new THREE.Mesh(geometry, material["texture2"]);
     model2.add(mainmodel2);
   });
   
   // Model3
   var model3 = new THREE.Object3D;
   scene.add(model3);

   loader.load("model3.js", function(geometry) {
     var mainmodel3 = new THREE.Mesh(geometry, material["texture3"]);
     model3.add(mainmodel3);
   });   
   
   // Model4
   var model4 = new THREE.Object3D;
   scene.add(model4);

   loader.load("model4.js", function(geometry) {
     var mainmodel4 = new THREE.Mesh(geometry, material["texture4"]);
     model4.add(mainmodel4);
   });  
   
   renderer = new THREE.WebGLRenderer();
   renderer.setPixelRatio(window.devicePixelRatio);
   renderer.setSize(window.innerWidth, window.innerHeight);
   document.body.appendChild(renderer.domElement);

   //

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

 }

 function onWindowResize() {

   camera.aspect = window.innerWidth / window.innerHeight;
   camera.updateProjectionMatrix();

   renderer.setSize(window.innerWidth, window.innerHeight);

 }

 function animate() {

   requestAnimationFrame(animate);
   renderer.render(scene, camera);

 }
body {
  margin: 0;
}
canvas {
  width: 100%;
  height: 100%
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>
like image 962
Alexander Hein Avatar asked Feb 23 '16 10:02

Alexander Hein


2 Answers

##LoadingManager Update

as @2pha points out, the below is pointless as THREE already implements a loading Manager - the below will remain for posterity, but have a look at this:

https://threejs.org/docs/?q=loading#api/loaders/managers/LoadingManager

Easy-peasy:

var progress = document.createElement('div');
var progressBar = document.createElement('div');

progress.appendChild(progressBar);

document.body.appendChild(progress);

var manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
  progressBar.style.width = (loaded / total * 100) + '%';
};

function addRandomPlaceHoldItImage(){
  var r = Math.round(Math.random() * 4000);
  new THREE.ImageLoader(manager).load('//picsum.photos/' + r + '/' + r);
}

for(var i = 0; i < 10; i++) addRandomPlaceHoldItImage();
div {
  width: 200px;
  height: 20px;
  background: #000;
  border: 2px solid #000;
}
div > div {
  width: 200px;
  height: 20px;
  background: red;
  border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>

Original Post

Something like the below should do your trick - mind that I have just quickly tested it and there is probably a couple of improvements to make (including using the progress callback in THREE js). I haven't fully refined it either, but it gives you a general implementation of how to do this. I also haven't implemented an error handler, etc... Those are things that take a bit longer to make, but you get the general idea.

You add new types of loaders (that conform to the type + Loader in THREE) and their sources, then initiating the load by simply calling load.

Hope it helps.

(I also made a random function to get some random images from placehold.it since using the same results over and over means that caching kills the server.)

function LoaderProgress(callback){
  this.length     = -1;
  this.complete   = 0;
  this.domElement = document.createElement('div');
  this.progress   = document.createElement('div');
  
  this.domElement.appendChild(this.progress);
  this.domElement.className = 'loader-progress-wrapper';
  this.progress.className   = 'loader-progress-progress';
  this.progress.style.width = '0%';
  
  this.callback = callback || function(){};
}
LoaderProgress.prototype.add = function(type, src){
  var result = this[++this.length] = {
    loader: new THREE[type + 'Loader'],
    complete: false,
    source: src
  };
  return result;
}
LoaderProgress.prototype.load = function(){
  this.complete = 0;
  for(var i = 0; i < this.length; i++){
    var current = this[i];
    if(!current.complete){
      current.loader.load(this[i].source, function(result){
        current.complete = result;
        this.complete++;
        this.update();
      }.bind(this));
    }
  }
  return this;
}
LoaderProgress.prototype.update = function(a){
  var progress = this.complete / this.length;
  this.progress.style.width = (progress * 100) + '%';
  if(progress === 1) this.callback(this);
  console.log(progress);
  return progress;
}

var loader = new LoaderProgress(function(){
  // Execute code that needfs the loaded elements here
  alert('All are loaded');
});

document.body.appendChild(loader.domElement);

function addRandomPlaceHoldItImage(){
  var r = Math.round(Math.random() * 4000);
  loader.add('Image', '//picsum.photos/' + r + '/' + r);
}

for(var i = 0; i < 10; i++)
  addRandomPlaceHoldItImage();

loader.load();
.loader-progress-wrapper {
  width: 200px;
  height: 20px;
  background: #000;
  border: 2px solid #000;
}
.loader-progress-progress {
  width: 200px;
  height: 20px;
  background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>
like image 136
somethinghere Avatar answered Oct 22 '22 07:10

somethinghere


Docs: http://threejs.org/docs/#Reference/Loaders/TextureLoader

Example: http://threejs.org/examples/#webgl_loader_obj

Code:

  var sphereMaterial = new THREE.MeshBasicMaterial();

  var onProgress = function ( xhr ) {
    if ( xhr.lengthComputable ) {
      var percentComplete = xhr.loaded / xhr.total * 100;
      console.log( Math.round(percentComplete, 2) + '% downloaded' );
    }
  };

  var loader = new THREE.TextureLoader();
  var texture = loader.load("___4MB_IMAGE.JPG___", undefined, onProgress);
  sphereMaterial.map = texture;

It solves a similar problem I had - 4MB texture that takes some time to load over the wire...

like image 41
Mars Robertson Avatar answered Oct 22 '22 08:10

Mars Robertson