I am having some problems with the LoadingManager, which I am trying to use to preload my models and textures.
manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
console.log("loaded: " + loaded + " total: " + total);
};
init();
function init()
{
// instantiate a loader
var loader = new THREE.OBJLoader(manager);
var textureLoader = new THREE.TextureLoader(manager);
// load first model
loader.load('model1.obj', function (object)
{
var texture = textureLoader.load('texture1.png');
});
// load second model
loader.load('model2.obj', function (object)
{
var texture = textureLoader.load('texture2.png');
});
}
with a console output of:
OBJLoader: 80.058837890625ms
loaded: 1 total: 3
loaded: 2 total: 3
OBJLoader.js:787 OBJLoader: 5.671875ms
loaded: 3 total: 4
loaded: 4 total: 4
After the first model and texture is loaded, why does the manager think there are 3 total items to be loaded, when there are only two (model1.obj and texture1.png)? Furthermore, the total number of items to be loaded changes from 3 to 4. Is it possible to correct this so that the correct total amount of 4 is shown?
After the first model and texture is loaded, why does the manager think there are 3 total items to be loaded, when there are only two (model1.obj and texture1.png)?
Because the app will start loading both OBJ files first and then the textures. So the order is not model1.obj => texture1.png => model2.obj => texture2.png. The loading manager will be notified about model1.obj and model2.obj at the end of init(). I suggest you set breakpoints in LoadingManager.itemStart() and LoadingManager.itemEnd() to better understand the internal loading workflow.
three.js R104
Related
I have been buying animated 3D models from TurboSquid and they fall into two starkly different categories:
Ready to use - I just load the FBX file and it looks fine in my ThreeJS scene
Missing textures - The loaded FBX model looks geometrically fine and the animations work, but all the surfaces are white
I want to know how to load the provided textures using the FBXLoader module. I have noticed that all of the FBX models, which I believe are PBR material based, have this set of files:
*002_BaseColor.png
*002_Emissive.png
*002_Height.png
*002_Metallic.png
*002_Roughness.png
*002_Normal.png
I have these questions:
How would I use the FBXLoader module to load the textures "in the right places"? In other words, how do I make the calls so that the BaseColor texture goes to the right place, the Emissive texture so it goes where it should, etc?
Do I need to pass it a custom material loader to the FBXLoader and if so, which one and how?
UPDATE: I trace through the ThreeJS FBX loader code I discovered that the FBX model that is not showing any textures does not have a Texture node at all. The FBX model that does show the textures does, as you can see in this screenshot of the debugger:
I'll keep tracing. If worse comes to worse I'll write custom code to add a Textures node with the right fields since I don't know how to use Blender to add the textures there. My concern is of course that even if I create a Textures node at run-time, they still won't end up in the proper place on the model.
This is the code I am currently using to load FBX models:
this.initializeModel = function (
parentAnimManagerObj,
initModelArgs= null,
funcCallWhenInitialized = null,
) {
const methodName = self.constructor.name + '::' + `initializeModel`;
const errPrefix = '(' + methodName + ') ';
self.CAC.parentAnimManagerObj = parentAnimManagerObj;
self.CAC.modelLoader = new FBXLoader();
self.CAC.modelLoader.funcTranslateUrl =
((url) => {
// Fix up the texture file names.
const useRobotColor = misc_shared_lib.uppercaseFirstLetter(robotColor);
let useUrl =
url.replace('low_Purple_TarologistRobot', `Tarologist${useRobotColor}`);
return useUrl;
});
// PARAMETERS: url, onLoad, onProgress, onError
self.CAC.modelLoader.load(
MODEL_FILE_NAME_TAROLOGIST_ROBOT_1,
(fbxObj) => {
CommonAnimationCode.allChildrenCastAndReceiveShadows(fbxObj);
fbxObj.scale.x = initModelArgs.theScale;
fbxObj.scale.y = initModelArgs.theScale;
fbxObj.scale.z = initModelArgs.theScale;
self.CAC.modelInstance = fbxObj;
// Set the initial position.
self.CAC.modelInstance.position.x = initModelArgs.initialPos_X;
self.CAC.modelInstance.position.y = initModelArgs.initialPos_Y;
self.CAC.modelInstance.position.z = initModelArgs.initialPos_Z;
// Create an animation mixer for this model.
self.CAC.modelAnimationMixer = new THREE.AnimationMixer(self.CAC.modelInstance);
// Speed
self.CAC.modelAnimationMixer.timeScale = initModelArgs.theTimeScale;
// Add the model to the scene.
g_ThreeJsScene.add(self.CAC.modelInstance);
// Don't let this part of the code crash the entire
// load call. We may have just added a new model and
// don't know certain values yet.
try {
// Play the active action.
self.CAC.activeAction = self.CAC.modelActions[DEFAULT_IDLE_ANIMATION_NAME_FOR_TAROLOGIST_ROBOT_1];
self.CAC.activeAction.play();
// Execute the desired callback function, if any.
if (funcCallWhenInitialized)
funcCallWhenInitialized(self);
} catch (err) {
const errMsg =
errPrefix + misc_shared_lib.conformErrorObjectMsg(err);
console.error(`${errMsg} - try`);
}
},
// onProgress
undefined,
// onError
(err) => {
// Log the error message from the loader.
console.error(errPrefix + misc_shared_lib.conformErrorObjectMsg(err));
}
);
}
I have a web application where the users can add objects to the scene, move it around, change the rotation etc. But, I have a drop down which decides the unit system to be followed on the whole page for various parameters. The drop down, on change should go through a page refresh. This causes the whole scene to be reset. Is there any way to save the scene and then reload it to the previous state in three js?
A beautiful example of what you are trying to achieve is the three.js editor itself.You can find its source on github.
What it does it that it stores the editor's state in (i.e configuration, camera and scene objects) into the local storage and indexedDB (you can live with only one also) and then when the page is initialized it checks if scene's state is set there in indexedDB, it loads it from there.
You might have to do some reading of the code itself but I'll explain the main logic here.
1. Loading the scene from local storage when the page loads
You can find that in index.html when there is this piece of code
editor.storage.init( function () {
editor.storage.get( function ( state ) {
if ( state !== undefined ) {
editor.fromJSON( state );
}
var selected = editor.config.getKey( 'selected' );
if ( selected !== undefined ) {
editor.selectByUuid( selected );
}
} );
So this checks that if there is data in the state it goes to fromJSON function in /js/Editor.js which basically sets the camera and scene etc. to whatever was stored in indexedDB .See the exact code is
fromJSON: function ( json ) {
var loader = new THREE.ObjectLoader();
// backwards
if ( json.scene === undefined ) {
var scene = loader.parse( json );
this.setScene( scene );
return;
}
// TODO: Clean this up somehow
var camera = loader.parse( json.camera );
this.camera.position.copy( camera.position );
this.camera.rotation.copy( camera.rotation );
this.camera.aspect = camera.aspect;
this.camera.near = camera.near;
this.camera.far = camera.far;
this.setScene( loader.parse( json.scene ) );
this.scripts = json.scripts;
}
To check how its loaded from the local storage/IndexedDB exactly you can check Config.js and Storage.js files located in the JS folder itself.
2nd Storing the data into IndexedDB periodically
Again in index.html you can find the following code and updates the model in the IndexedDB this behavior can be triggered on an event or on a timeout or even manually by calling this piece of code editor.storage.set( editor.toJSON() );.
var saveState = function ( scene ) {
if ( editor.config.getKey( 'autosave' ) === false ) {
return;
}
clearTimeout( timeout );
timeout = setTimeout( function () {
editor.signals.savingStarted.dispatch();
timeout = setTimeout( function () {
editor.storage.set( editor.toJSON() );
editor.signals.savingFinished.dispatch();
}, 100 );
}, 1000 );
};
Hoping you can make use of this example to achieve your target.
How to show a gif image loader to indicate the status of the model on before load the 3d models using OBJMTLLoader orAssimpJSONLoader ? am using three.js version 67 and am using below code load the gif loader, but the function doesn't have any effect while load the model
var loadManager = new THREE.LoadingManager();
loadManager.onProgress = function ( item, loaded, total ) {
alert('hi');
console.log( item, loaded, total );
};
var objloader = new THREE.OBJMTLLoader(loadManager);
what am doing wrong here, am missing anything?
Having a quick read of the LoadingManager docs it seems that the class does not show progress of a single loader, but it shows the progress of many.
So if you send the loading manager 4 loaders, the onProgress event would fire 4 times with the 'loaded' variable being 1, 2, 3 and 4.
I'm loading quite large 3d models using BinaryLoader (up to 6MB), and I would like to display progress information.
Using loader.showStatus was already a 1st step (now my app displays "Loading" while the model is loading), but I would like to show which percentage of the model has already been loaded.
Reading the code for BinaryLoader I found the 'showProgress' flag, but if I set it to true, I get multiple errors like this one:
Uncaught Error: InvalidStateError: DOM Exception 11
xhr.onreadystatechange
at line BinaryLoader.js:99, which corresponds to:
callbackProgress( { total: length, loaded: xhr.responseText.length } );
I also tried configuring loader.onLoadProgress, but I wasn't able to figure out how to do it...
thanks in advance!
3 years later I ran into the same problem as you. Here is my solution:
var onLoadComplete= function (geometry) {
model = new THREE.Mesh(geometry, material);
scene.add(model);
//etc
};
var onLoadProgress = function (e) {
var percentage = Math.round((e.loaded / e.total * 100));
jQuery('#some-loader-div').html('Loading ' + percentage + '%')
};
loader.load(file_url, onLoadComplete, onLoadProgress);
Reference: http://definitelytyped.org/docs/threejs--three/classes/three.jsonloader.html#onloadprogress
Hope this helps someone!
I am building this slideshow, hereby a temp URL:
http://ferdy.dnsalias.com/apps/jungledragon/html/tag/96/homepage/slideshow/mostcomments
There are multiple ways to navigate, clicking the big image goes to the next image, clicking the arrows go to the next or previous image, and you can use your keyboard arrows as well. All of these events call a method loadImage (in slideshow.js).
The image loading is fine, however at the end of that routine I'm making a remote Ajax call using $.get. The purpose of this call is to count the view of that image. Here is the pseudo, snipped:
function loadImage(id,url) {
// general image loading routine
// enable loader indicator
$("#loading").show();
var imagePreloader = new Image();
imagePreloader.src = url;
loading = true;
$(imagePreloader).imagesLoaded(function() {
// load completed, hide the loading indicator
$("#loading").hide();
// set the image src, this effectively shows the image
var img = $("#bigimage img");
img.attr({ src: url, id: id });
imageStartTime = new Date().getTime();
// reset the image dimensions based upon its orientation
var wide = imagePreloader.width >= imagePreloader.height;
if (wide) {
img.addClass('wide');
img.removeClass('high');
img.removeAttr('height');
} else {
img.addClass('high');
img.removeClass('wide');
img.removeAttr('width');
}
// update thumb status
$(".photos li.active").removeClass('active');
$("#li-" + id).addClass('active');
// get the title and other attributes from the active thumb and set it on the big image
var imgTitle = $("#li-" + id + " a").attr('title');
var userID = $("#li-" + id + " a").attr('data-user_id');
var userName = $("#li-" + id + " a").attr('data-user_name');
$(".caption").fadeOut(400,function(){
$(".caption h1").html('' + imgTitle + '');
$(".caption small").html('Uploaded by ' + userName + '');
$(".caption").fadeIn();
});
// update counter
$(".counter").fadeOut(400,function() { $(".counter").text(parseInt($('.photos li.active .photo').attr('rel'))+1).fadeIn(); });
// call image view recording function
$.get(basepath + "image/" + id + "/record/human");
// loading routine completed
loading = false;
}
There is a lot of stuff in there that is not relevant. At the end you can see I am doing the $.get call. The problem is that it is triggered in very strange ways. The first time I navigate to a tumb, it is called once. The next time it is triggered twice. After that, it is triggered 2 or 3 times per navigation action, usually 3.
I figured it must be that my events return multiple elements and therefore call the loadimage routine multiple times. So I placed log statements in both the events and the loadimage routine. It turns out loadimage is called correctly, only once per click.
This means that it seems that the $.get is doing this within the context of a single call. I'm stunned.
Your problem may be:.imagesLoaded is a jQuery plug in that runs through all images on the page. If you want to attach a load event to the imagePreloader only, use
$(imagePreloader).load(function() {
...
}
Otherwise, please provide the code where you call the loadImage() function.
Update:
when clicking on a thumb That is the problem. $(".photos li a").live('click',... should only be called once on page load. Adding a click handler every time a thumb is clicked will not remove the previous handlers.
Another option is to change the code to $(".photos li a").unbind('click').live('click', ... which will remove the previously registered click handlers.