three.js load multiple objects asynchronous issue - javascript

am loading multiple models on the same time to a scene, but it fails to load all the models, it only loading one model on the scene
For example am having a building scene with the multiple objects like chairs, toys and so on inside that building, while loading those objects the only one object is loading, but somehow if i do a alert on end of the function all the models are loading
Image1 what am getting now, Image2 what actually i want
my code is follows
function load_file(floor_number,x,y,z,width,height,rotation,angle,file)
{
obj_x=x;
obj_y=y;
obj_z=z;
obj_width=width;
obj_height=height;
obj_rotation=rotation;
var object_material = new THREE.MeshBasicMaterial({
color: 0xd6d6d6,
traansparent : true,
opacity : -2.5,
side: THREE.DoubleSide
});
var loader = new THREE.JSONLoader();
loader.load("uploads/accessories/3d/code/3dfile_"+file+".js",
function(geometry, object_material)
{
var object = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(object_material));
console.log(object);
model = new THREE.Object3D();
model.add(object);
model.position.set(obj_x,obj_y,obj_z);
model.scale.set(obj_width,obj_height,obj_rotation);
model.opacity =2;
model.rotation.y = 600;
model.duration = 12000;
model.mirroredLoop = true;
model.castShadow = true;
model.receiveShadow = true;
scene.add(model);
}
);
// alert('hi'); if i remove this comment second model is loading perfectly
return true;
}
also tried to load the object's by id using Object3D.getObjectById() this is also fails
i know this is about the asynchronous problem, but i can't get ride of this, any help on this?

The problem are the globals.
Here a nice reading about Why is Global State so Evil?.
update
I played around a bit with a code similar to your, and I see now what could be what it seem a problem, but it isn't really, you can use an approach like this:
/* object declaration and initialization on load completed */
var model1;
load_file('object1', function(model) {
model1 = model;
model1.position.x = -2;
});
...
function laad_file(file, on_load_complete) {
...
loader.load("http://localhost/"+file+".js", function(geometry, object_material) {
...
scene.add(model);
/* call the callback to initialize your object */
if (on_load_complete !== undefined)
on_load_complete(model);
});
...
}
render() {
...
if (model1 !== undefined)
model1.rotation.x += 0.1;
...
}

Seems like your problem is that renderer not firing after model added to the scene. Try to call it after model added to the scene at your callback function:
scene.add(model);
renderer.render(scene, camera);

Related

Sonar Issue: Update this function so that its implementation is not identical to the one on line 159

Im trying to make my Project a little better by fixing all issues with sonar and I am stuck on the error displayed in the title. The function is loading a blender model with the help of three.js and sonar tells me to not use the function more than once but I need to load multiple models with this function.
function raining(temp, timezone) {
let rain = [];
function loadGLTF() {
let Loader = new GLTFLoader();
Loader.load("./model/rain.gltf", (gltf) => {
Mesh = gltf.scene;
Mesh.scale.set(0.2, 0.2, 0.2);
scene.add(Mesh);
camera.target = Mesh;
Mesh.position.x = 0;
Mesh.position.y = -0.4;
Mesh.position.z = 0;
});
}
I can put the loadGLTF function out of the raining one but how can I use the function again to load my second model ./model/snow.gltf
This sonar error is not very friendly... What it really means is that you are defining a function that is the exact replica of another function defined somewhere else (on line 159).
This often is the case with redundant callbacks.
It's hard to suggest the best refactor here since we have a very small portion of your code. My guess is that the callback to Loader.load is the function being repeated. If that's the case, simply create it as a function and reuse it.
The problem could also be the loadGLTF function. First, you define it inside another function, that means it will always be "rebuilt" when raining() is called.
The best would be to define it outside of rain, and then call it inside rain.
Going further, you could refactor this to be generic for any mesh you'd like to load:
// Define Loader outside of function, keep only one and reuse it.
const Loader = new GLTFLoader();
function loadGLTF(path) {
Loader.load(path, (gltf) => {
Mesh = gltf.scene;
Mesh.scale.set(0.2, 0.2, 0.2);
scene.add(Mesh);
camera.target = Mesh;
Mesh.position.x = 0;
Mesh.position.y = -0.4;
Mesh.position.z = 0;
});
}
You could go further by passing a second parameter to define the position and scale if it is not always the same.

Switch between two scenes using the same renderer in three.js

I have been trying to find a way to be able to toggle between two scenes in three.js. I am aware that one can load a scene by using sceneLoader / exportScene combo.
Code taken from josdirksen/learning-threejs - loading a scene
var controls = new function () {
this.exportScene = function () {
var exporter = new THREE.SceneExporter();
var sceneJson = JSON.stringify(exporter.parse(scene));
localStorage.setItem('scene', sceneJson);
};
this.clearScene = function () {
scene = new THREE.Scene();
};
this.importScene = function () {
var json = (localStorage.getItem('scene'));
var sceneLoader = new THREE.SceneLoader();
sceneLoader.parse(JSON.parse(json), function (e) {
scene = e.scene;
}, '.');
}
};
From my understanding of the above code you need to have the scene loaded first before you can extract it and save to local storage before you can put it back into the scene. I am also aware that SceneLoader is now deprecated.
For my senario I want to have an initial scene load and by clicking the 'scene2' button I then want to display scene2 only and if I click the 'scene1' button go back to seeing scene1 only (see fiddle below).
A Basic Example setup
I'm not sure where to begin with this, so any pointers suggestions or advice would be helpful.
If you need to just switch to new scene, then why not have two scene object and one main scene. Try following code
/* Buttons to handle scene switch */
$("#scene2").click(function() {
scene = scene2
})
$("#scene1").click(function() {
scene = scene1
})
function init() {
....
/* I dont think you need to add camera to scene for viewing perpose. By doing this, essentially you are adding camera object to scene, and you won't be able to see it because scene is rendered using this camera and camera eye is at same location
*/
scene1 = new THREE.Scene();
// Build scene1
// scene1.add(camera);
scene2 = new THREE.Scene();
// Build scene2
// Choosing default scene as scene1
scene = scene1;
}
function render() {
// Try some checking to update what is necessary
renderer.render(scene, camera);
}
Updated jsfiddle
You can redraw the canvas by removing current scene scene.remove(mesh); and add create new mesh add into scene
Demo http://jsfiddle.net/sumitridhal/x8t801f5/4/
You can add custom controls using dat.GUI library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.4/dat.gui.js"></script>
//
var controls = new function() {
// we need the first child, since it's a multimaterial
this.radius = 10;
this.detail = 0;
this.type = 'Icosahedron';
this.redraw = function() {
// remove the old plane
scene.remove(mesh);
// create a new one
// add it to the scene.
scene.add(mesh);
}
});
var gui = new dat.GUI();
gui.add(controls, 'radius', 0, 40).step(1).onChange(controls.redraw);
gui.add(controls, 'detail', 0, 3).step(1).onChange(controls.redraw);
gui.add(controls, 'type', ['Icosahedron', 'Tetrahedron', 'Octahedron', 'Custom']).onChange(controls.redraw);
Demo http://codepen.io/sumitridhal/pen/NjbGpB

Three.js objloader + texture

I have been using Three.js for a few weeks now, I managed to apply a texture to a cube created directly via the code, but once I tried to load an OBJ file with OBJLoaderI noticed that I couldn't use the same method to load simple png or jpg textures with TextureLoader.
Now I'm not entirely sure what is going wrong, if either this is not the right way to do it, if I just can't load a simple image fine or if I'm not applying it right.
I've seen that applying a texture seems quite easy to do with an MTL file, but unfortunately I'm not sure how to make one.
My current code looks like something like this:
var loader = new THREE.OBJLoader( manager );
loader.load( 'models/tshirt.obj', function ( object ) {
object.position.x = 0;
object.position.y = -200;
object.scale.x = 10;
object.scale.y = 10;
object.scale.z = 10;
obj = object;
texture = THREE.TextureLoader('img/crate.png'),
material = new THREE.MeshLambertMaterial( { map: texture } );
mesh = new THREE.Mesh( object, material );
scene.add( mesh );
} );
But it just doesn't seem to work. The model doesn't load and gives me random errors from Three.js. If instead of the code above I change scene.add( obj); the model actually loads.
What should I be doing here? Should I just go ahead and try to make an MTL file?
My full working code can bee seen at http://creativiii.com/3Dproject/old-index.html
EDIT: The error I get when adding mesh instead of obj is
three.min.js:436 Uncaught TypeError: Cannot read property 'center' of undefined
Try this code:
var OBJFile = 'my/mesh.obj';
var MTLFile = 'my/mesh.mtl';
var JPGFile = 'my/texture.jpg';
new THREE.MTLLoader()
.load(MTLFile, function (materials) {
materials.preload();
new THREE.OBJLoader()
.setMaterials(materials)
.load(OBJFile, function (object) {
object.position.y = - 95;
var texture = new THREE.TextureLoader().load(JPGFile);
object.traverse(function (child) { // aka setTexture
if (child instanceof THREE.Mesh) {
child.material.map = texture;
}
});
scene.add(object);
});
});
The texture loading changes was taken from: How to apply texture on an object loaded by OBJLoader? .

Toggle between multiple 3d models in three.js using dat.gui

I'm trying to toggle between multiple 3d models (loaded with OBJMTLLoader.js) rendered in my three.js scene. I'm using dat.gui to create a dropdown list of model names and when one is chosen, the scene will add the respective obj model to the scene and remove the original one.
Here, I'm loading 2 separate models and adding the second one to the scene and then setting up the dat.gui controls:
var loader = new THREE.OBJMTLLoader();
loader.load("../assets/models/boardlego.obj", "../assets/models/boardlego.mtl", function (obj2) {
obj2.name = 'lego2';
});
loader.load("../assets/models/boardlego2.obj", "../assets/models/boardlego.mtl", function (obj) {
obj.name = 'lego';
scene.add(obj);
});
camControl = new THREE.OrbitControls(camera, renderer.domElement);
// call the render function
render();
//set up dat.gui controls
control = new function () {
this.Templates = 'shortboard';
}
addControls(control);
}
Then, the addControls function:
function addControls(controlObject) {
var gui = new dat.GUI();
gui.add(controlObject, 'Templates', ['shortboard', 'longboard', 'fish', 'funboard', 'simmons', 'parallel', 'gun']).listen();
Both models are loaded but only one is added to the scene. Is it possible to add the other model when the 'Templates' control is changed? I tried to create a separate function updateboard() and call it in the render function, like this:
function updateboard() {
if(controlObject.Templates === "longboard"){
scene.add(obj2);
}
}
But it didn't work. I also tried to set up an if statement in the render function:
function render() {
renderer.render(scene, camera);
if (scene.getObjectByName('lego')) {
scene.getObjectByName('lego').scale.set(control.Length, control.Thickness, control.Width);
}
if(control.Templates === "longboard"){
scene.add(obj2);
}
But it didn't work either. Any help would be greatly appreciated! Or if you can scout out an example that could help too! Thanks in advance.
Load your objects in array();
var my_models();
var loader = new THREE.OBJMTLLoader();
loader.load("../assets/models/boardlego.obj", "../assets/models/boardlego.mtl", function (obj2) {
obj.name = 'lego2';
my_models.push(obj);
});
scene.add(my_models[0]); // 0 would be the first model 1 is the second

ThreeJS: Remove object from scene

I'm using ThreeJS to develop a web application that displays a list of entities, each with corresponding "View" and "Hide" button; e.g. entityName View Hide. When user clicks View button, following function is called and entity drawn on screen successfully.
function loadOBJFile(objFile){
/* material of OBJ model */
var OBJMaterial = new THREE.MeshPhongMaterial({color: 0x8888ff});
var loader = new THREE.OBJLoader();
loader.load(objFile, function (object){
object.traverse (function (child){
if (child instanceof THREE.Mesh) {
child.material = OBJMaterial;
}
});
object.position.y = 0.1;
scene.add(object);
});
}
function addEntity(object) {
loadOBJFile(object.name);
}
And on clicking Hide button, following function is called:
function removeEntity(object){
scene.remove(object.name);
}
The problem is, entity is not removed from screen once loaded when Hide button is clicked. What can I do to make Hide button to work?
I did small experiment. I added scene.remove(object.name); right after scene.add(object); within addEntity function and as result, when "View" button clicked, no entity drawn (as expected) meaning that scene.remove(object.name); worked just fine within addEntity. But still I'm unable to figure out how to use it in removeEntity(object).
Also, I checked contents of scene.children and it shows: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
Complete code: http://devplace.in/~harman/model_display1.php.html
Please ask, if more detail is needed. I tested with rev-59-dev and rev-60 of ThreeJS.
Thanks. :)
I think seeing your usage for addEntity and removeEntity code would be helpful, but my first thought is are you actually setting the object.name? Try in your loader just before scene.add(object); something like this:
object.name = "test_name";
scene.add(object);
What might be happening is the default "name" for an Object3D is "", so when you then call your removeEntity function it fails due to the scene objects name being ""
Also, I notice you pass in object.name to your loader? Is this where your storing the URL to the resource? If so, I would recommend using the Object3D's built in .userData method to store that information and keep the name field for scene identification purposes.
Edit: Response to newly added Code
First thing to note is it's not a great idea to have "/" in your object name, it seems to work fine but you never know if some algorithm will decide to escape that string and break your project.
Second item is now that I've seen your code, its actually straight forward whats going on. Your delete function is trying to delete by name, you need an Object3D to delete. Try this:
function removeEntity(object) {
var selectedObject = scene.getObjectByName(object.name);
scene.remove( selectedObject );
animate();
}
Here you see I lookup your Object3D in the Three.js Scene by passing in your object tag's name attribute.
clearScene: function() {
var objsToRemove = _.rest(scene.children, 1);
_.each(objsToRemove, function( object ) {
scene.remove(object);
});
},
this uses undescore.js to iterrate over all children (except the first) in a scene (it's part of code I use to clear a scene). just make sure you render the scene at least once after deleting, because otherwise the canvas does not change! There is no need for a "special" obj flag or anything like this.
Also you don't delete the object by name, just by the object itself, so calling
scene.remove(object);
instead of scene.remove(object.name);
can be enough
PS: _.each is a function of underscore.js
I came in late but after reading the answers more clarification needs to be said.
The remove function you wrote
function removeEntity(object) {
// scene.remove(); it expects as a parameter a THREE.Object3D and not a string
scene.remove(object.name); // you are giving it a string => it will not remove the object
}
A good practice to remove 3D objects from Three.js scenes
function removeObject3D(object3D) {
if (!(object3D instanceof THREE.Object3D)) return false;
// for better memory management and performance
if (object3D.geometry) object3D.geometry.dispose();
if (object3D.material) {
if (object3D.material instanceof Array) {
// for better memory management and performance
object3D.material.forEach(material => material.dispose());
} else {
// for better memory management and performance
object3D.material.dispose();
}
}
object3D.removeFromParent(); // the parent might be the scene or another Object3D, but it is sure to be removed this way
return true;
}
If your element is not directly on you scene go back to Parent to remove it
function removeEntity(object) {
var selectedObject = scene.getObjectByName(object.name);
selectedObject.parent.remove( selectedObject );
}
THIS WORKS GREAT - I tested it
so, please SET NAME for every object
give the name to the object upon creation
mesh.name = 'nameMeshObject';
and use this if you have to delete an object
delete3DOBJ('nameMeshObject');
function delete3DOBJ(objName){
var selectedObject = scene.getObjectByName(objName);
scene.remove( selectedObject );
animate();
}
open a new scene , add object
delete an object and create new
I started to save this as a function, and call it as needed for whatever reactions require it:
function Remove(){
while(scene.children.length > 0){
scene.remove(scene.children[0]);
}
}
Now you can call the Remove(); function where appropriate.
When you use : scene.remove(object);
The object is removed from the scene, but the collision with it is still enabled !
To remove also the collsion with the object, you can use that (for an array) :
objectsArray.splice(i, 1);
Example :
for (var i = 0; i < objectsArray.length; i++) {
//::: each object ::://
var object = objectsArray[i];
//::: remove all objects from the scene ::://
scene.remove(object);
//::: remove all objects from the array ::://
objectsArray.splice(i, 1);
}
I improve Ibrahim code for removeObject3D, added some checks for geometry or material
removeObject3D(object) {
if (!(object instanceof THREE.Object3D)) return false;
// for better memory management and performance
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (object.material instanceof Array) {
// for better memory management and performance
object.material.forEach(material => material.dispose());
} else {
// for better memory management and performance
object.material.dispose();
}
}
if (object.parent) {
object.parent.remove(object);
}
// the parent might be the scene or another Object3D, but it is sure to be removed this way
return true;
}
this example might give you a different approach . I was trying to implement a similar feature in my project also with scene.remove(mesh). However disposal of geometry and material attributes of mesh worked for me!
source
Use
scene.remove(Object)
The object you want to remove from the scene
I had The same problem like you have. I try this code and it works just fine:
When you create your object put this object.is_ob = true
function loadOBJFile(objFile){
/* material of OBJ model */
var OBJMaterial = new THREE.MeshPhongMaterial({color: 0x8888ff});
var loader = new THREE.OBJLoader();
loader.load(objFile, function (object){
object.traverse (function (child){
if (child instanceof THREE.Mesh) {
child.material = OBJMaterial;
}
});
object.position.y = 0.1;
// add this code
object.is_ob = true;
scene.add(object);
});
}
function addEntity(object) {
loadOBJFile(object.name);
}
And then then you delete your object try this code:
function removeEntity(object){
var obj, i;
for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
obj = scene.children[ i ];
if ( obj.is_ob) {
scene.remove(obj);
}
}
}
Try that and tell me if that works, it seems that three js doesn't recognize the object after added to the scene. But with this trick it works.
You can use this
function removeEntity(object) {
var scene = document.querySelectorAll("scene"); //clear the objects from the scene
for (var i = 0; i < scene.length; i++) { //loop through to get all object in the scene
var scene =document.getElementById("scene");
scene.removeChild(scene.childNodes[0]); //remove all specified objects
}

Categories